Pass window.innderWidth in initial GET request - html

Assuming I have server-side rendered app with responsive styling based on window.innerWidth, how do I pass client's screen size in initial GET request for index.html, so that I can prepare on server and respond with appropriately styled page?
Consider the following example:
import React, { useState, useEffect } from 'react';
export const App = () => {
const [width, setWidth] = useState(globalThis?.innerWidth);
useEffect(() => {
const updateWidth = () => setWidth(window.innerWidth);
window.addEventListener('resize', updateWidth);
return () => window.removeEventListener('resize', updateWidth);
});
return <p>Your window is {width}px wide</p>;
};
While server is rendering html for above component it has no clue about .innerWidth (hence, width) and, most probably, corresponding part of the <p> will be left empty until react kicks-in client side, triggering useState() and initiating width with globalThis (= window now) .innerWidth.
So, my point is how do I pass window.innerWidth (which browser, apparently, knows when it requests index.js) insider my GET request, so that I can use it server side, e.g. with:
export function getServerSidePorps(context) {
// some logic here, extracting window width from context.req
return {
props: {width},
};
}

You cannot do it, because the browser width exists only on the client side. It's impossible to use it on the server side, before rendering the page.

Related

How to dynamically assign Puppeteer viewport size from current screen resolution?

I'm using Puppeteer to automate some page actions in an already open, fully-visible browser (non-headless). Currently, I manually set the viewport like this:
const page = await browser.newPage();
await page.setViewport({width: W, height: H});
I have to manually set W and H based on both the actual screen resolution, and on the system-wide scaling factor. This makes the script very brittle and non-portable.
I would like to have the new page always open with the largest possible visible viewport, without having to manually specify what that is. I tried some of the other solutions suggested on SO and elsewhere, such as setting the viewport to null, but I have not yet stumbled upon a working solution for my specific use case. Any help would be appreciated. Thanks!
If you want to set the W and H persistently across a launched browser you need to set defaultViewport: null together with --window-size=${W},${H} launch arg. It sets the window size and viewport on browser-level, not on page-level (which changes with each new tab).
Like this, all the newly opened tabs will share the same window size and viewport.
const browser = await puppeteer.launch({
defaultViewport: null,
args: [`--window-size=${W},${H}`]
})
If you can retrieve the screen resolution from system specifications you would be able to correctly set the viewport size from it.
Though you will probably not be able to get this information directly from javascript.
If you can get this information from a PowerShell script (see edit), you could try the following to execute that script from javascript and retrieve this information in your program in order to set your viewport dimensions.
const {spawn} = require("child_process");
async function getSomeDataFromAPowerShellScript() {
const child = spawn("powershell.exe", ["./PATH/MyPowerShellScript.ps1"]); // spawn a powershell terminal as a child process of main program and run the provided script in it
return await new Promise(resolve => {
child.stdout.on("data", (data) => { // trigger when data is send into the child terminal
console.log(data);
resolve(data);
};
});
}
A call to getSomeDataFromAPowerShellScript() will return the first outputed data in the powershell terminal as a string.
If you want to retrieve more informations than just the first output in the powershell terminal you can use this instead:
async function getSomeDataFromAPowerShellScript() {
const child = spawn("powershell.exe", ["./PATH/MyPowerShellScript.ps1"]); // spawn a powershell terminal as a child process of main program and run the provided script in it
let result = [];
return await new Promise(resolve => {
child.stdout.on("data", (data) => { // trigger when data is send into the child terminal
console.log(data);
result.push(data);
};
child.on("exit", () => { // trigger when the child process exit after execution
resolve(result);
});
});
}
Edit:
You could use this powershell script from Ben N answer here How to get the current screen resolution on windows via command line? to get the current resolution of your primary screen:
PowerShell-script.ps1
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class PInvoke {
[DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")] public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
}
"#
$hdc = [PInvoke]::GetDC([IntPtr]::Zero)
[PInvoke]::GetDeviceCaps($hdc, 118) # width
[PInvoke]::GetDeviceCaps($hdc, 117) # height
original explanation
It outputs two lines: first the horizontal resolution, then the
vertical resolution.
To run it, save it to a file (e.g. screenres.ps1) and launch it with
PowerShell:
powershell -ExecutionPolicy Bypass .\screenres.ps1
Using this answer in combination of theDavidBarton answer should achieve what you're asking for.

Can a React app return an image string that can be read into the SRC attribute of a <img> tag?

Is it possible to reference a React App that is running on another server using
<img src="https://www.react_app.com">
The idea is that the React App returns an image string (or similar) like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA ...
So that it can be read in a <img src=""> tag?
The main question is what React code simply sends back a request with the string so that it can be read in src=""?
Also is there a timeout for how long an <img src=""> attempts to fetch an image?
React component imports
import React, { useCallback, useEffect, useState, useRef } from 'react'
import classNames from 'classnames'
import { fabric } from 'fabric'
import fabricConfig from './fabricConfig'
import FileUploader from './components/FileUploader'
import ColorPicker from './components/ColorPicker'
import Checkbox from './components/Checkbox'
import Button from './components/Button'
import getRatio from './utils/getRatio'
import getInitialCanvasSize from './utils/getInitialCanvasSize'
import getImageFromURL from './utils/getImageFromURL'
import resizeCanvas from './utils/resizeCanvas'
import removeSelectedElements from './utils/removeSelectedElements'
import getCanvasObjectFilterRGB from './utils/getCanvasObjectFilterRGB'
import setAttributes from './utils/setAttributes'
import { Z, Y, DELETE } from './utils/constants'
Fetch image from URL and automatically make changes to it on load
const imageUrl = "www.something.com/image"
if (imageUrl) {
new Promise(resolve => fabric.loadSVGFromURL(imageUrl, (objects, options) => {
const group = new fabric.Group(objects)
resolve(getRatio(group, canvas))
}))
.then(({ ratio, width, height }) => {
fabric.loadSVGFromURL(imageUrl, (objects, options) => {
try {
objects.forEach(obj => {
setAttributes(obj, {
left: (obj.left * ratio) + ((canvas.width / 2) - ((width * ratio) / 2)),
top: (obj.top * ratio) + ((canvas.height / 2) - ((height * ratio) / 2)),
})
obj.scale(ratio)
// MAKE EDITS TO THE SVG OBJECT HERE
canvas.add(obj)
})
canvas.renderAll()
// HERE I AM TRYING TO SAVE THE CANVAS STATE AND SEND IT BACK TO THE THIRD PARTY WEBSITE USING GET PARAMETERS
var canvasImg = ''
if(urlParams.get("export") === "png"){
canvasImg = canvas.toDataURL("image/png")
} else if (urlParams.get("export") === "pdf") {
canvasImg = canvas.toDataURL("image/pdf")
} else {
onCanvasModified(canvas)
}
} catch(err) {
console.log('Could not retrieve that image')
}
})
})
What you want is a CDN, which serves image assets via a GET request (the img src accepts a string which it uses to fetch (GET) content). In short, a CDN serves the application with assets -- be it images, javascript, CSS or HTML. A React application is designed to update content in place via manipulating a virtual DOM; therefore, expecting it to serve assets across domains is anti-pattern. Instead, you would use a custom server (like express) or a web server (like nginx) to serve static assets.
As a simple example, imgur.com would the React application, while i.imgur.com would be their CDN to serve images like this and s.imgur.com would be their CDN to serve CSS/JS assets like this.
This answer goes into more detail how to do it; HOWEVER, this is only one of many, many ways on how accomplish the above, but the concept is still the same: Making a request to retrieve an image via an img src string.
I hesitate to provide full example code since I have no idea what stack you're working with and what your knowledge/comfort-level is regarding backend services. As such, if you want practice consuming a request that serves images on the frontend, then I'd recommend you start with this API service.
Example
Here's one of many ways to do it: Example Repo
To run the example...
1.) Clone the repo: git clone git#github.com:mattcarlotta/save-canvas-example.git
2.) Install dependencies: yarn or npm i
3.) Run yarn dev or npm dev
4.) Click one of the buttons to either save an image as PNG or as a PDF
The example includes quite a bit of notes, but to provide a brief:
User clicks button. File Ref
Depending on the clicked button, the canvas is converted to a Blob, which is then converted to a File. File Ref
This file is then sent (via POST) to an image microservice running at http://localhost:4000 listening for requests to /upload-file. File Ref
The microservice sees the request and directs to our middleware functions. File Ref
Then it directs it to the /upload-file controller. File Ref
The controller determines if the file upload was valid (in the middleware), if not it throws an error. File Ref
When valid, the file details are generated from req.file (this comes from our multer middleware function), a directory is created and a file is saved to that directory. File Ref
A filepath is then sent back to the client. File Ref
Client receives filepath from image microservice and sets it to state. File Ref
Client then renders a shareable link, a link to view the file, and a preview. File Ref
Results
Save PNG:
Save PDF:
Flow Diagram
I've tried to reproduce the project with minimal features. user can add and interact with rectangle and save the image. upon saving it would go to the server and the image data will be stored in a JSON file.
Here's the link to frontend: https://codesandbox.io/s/so-fabric-client-5bjsf
As you have mentioned, there are two different react apps; I've created two routes, /draw where the user can draw the image and /images where I fetch the images. you can consider these two routes as different react projects since the logic remains the same regardless of their origin.
On the backend side, for demonstration purposes and simplicity, I've used a JSON file and sending all the file content in response when the application wants to display the images. It could become problematic once there are hundreds of images or when you want to search them by the user. so consider using a database or any other method.
here's the backend code:
const express = require("express");
const path = require("path");
const fs = require("fs");
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.static(path.join(__dirname + "/build")));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.post("/save-image", (req, res) => {
const image = req.body.image
fs.readFile('images.json', function (err, data) {
if(err) {
console.log(err)
res.status(500)
}
var json = JSON.parse(data)
json.push({id: json.length, image})
fs.writeFile("images.json", JSON.stringify(json), (err, result) => {
if(err) console.log(err);
})
})
res.status(200);
})
app.get("/get-images", (req, res) => {
res.json(JSON.parse(fs.readFileSync("./images.json")))
})
app.listen(PORT, () => {
console.log(`up and running on port ${PORT}`);
})
images.json is just a file with [] as its content. the code is pretty much self-explainatory, I've uploaded all the code on GitHub as well -- so-fabric-demo and you can check the demo on Heroku

Disable side panel when exporting Kepler.gl map?

I loaded my data into kepler.gl (https://kepler.gl/) and created a visual I would like to post online by embedding the code into a blogpost.
However, I want the readers/users not to be able to see and access the side panel, but rather only with the main view of the map.
Is there any way to do so or any parameters I can change when exporting the html?
To solve the issue, one must replace the reducers block:
const reducers = (function createReducers(redux, keplerGl) {
return redux.combineReducers({
// mount keplerGl reducer
keplerGl: keplerGl.keplerGlReducer
});
}(Redux, KeplerGl));
With the following:
const reducers = (function createReducers(redux, keplerGl) {
const customizedKeplerGlReducer = keplerGl.keplerGlReducer.initialState({
uiState: {readOnly: true}
});
return redux.combineReducers({
// mount keplerGl reducer
keplerGl: customizedKeplerGlReducer
});
}(Redux, KeplerGl));
And in the end, change the line with addDataToMap to:
store.dispatch(keplerGl.addDataToMap(loadedData, {readOnly: true}));

Puppeteer: Screenshot lazy images not working [duplicate]

This question already has answers here:
Puppeteer wait for all images to load then take screenshot
(5 answers)
Closed 2 years ago.
I doesn't seems to be able to capture screenshot from https://today.line.me/HK/pc successfully.
In my Puppeteer script, I have also initiate a scroll to the bottom of the page and up again to ensure images are loaded. But for some reason it does't seems to work on the line URL above.
function wait (ms) {
return new Promise(resolve => setTimeout(() => resolve(), ms));
}
const puppeteer = require('puppeteer');
async function run() {
let browser = await puppeteer.launch({headless: false});
let page = await browser.newPage();
await page.goto('https://today.line.me/HK/pc', {waitUntil: 'load'});
//https://today.line.me/HK/pc
// Get the height of the rendered page
const bodyHandle = await page.$('body');
const { height } = await bodyHandle.boundingBox();
await bodyHandle.dispose();
// Scroll one viewport at a time, pausing to let content load
const viewportHeight = page.viewport().height+200;
let viewportIncr = 0;
while (viewportIncr + viewportHeight < height) {
await page.evaluate(_viewportHeight => {
window.scrollBy(0, _viewportHeight);
}, viewportHeight);
await wait(4000);
viewportIncr = viewportIncr + viewportHeight;
}
// Scroll back to top
await page.evaluate(_ => {
window.scrollTo(0, 0);
});
// Some extra delay to let images load
await wait(2000);
await page.setViewport({ width: 1366, height: 768});
await page.screenshot({ path: './image.png', fullPage: true });
}
run();
For anyone wondering, there are many strategies to render lazy loaded images or assets in Puppeteer but not all of them work equally well. Small implementation details in the website that you're attempting to screenshot could change the final result so if you want to have an implementation that works well across many case scenarios you will need to isolate each generic case and address it individually.
I know this because I run a small Screenshot API service and I had to address many cases separately. This is a big task of this project since there seems to be always something new that needs to be addressed with new libraries and UI techniques being used every day.
That being said I think there are some rendering strategies that have good coverage. Probably the best one is a combination of waiting and scrolling through the page like OP did but also making sure to take into account the order of the operations. Here is a slightly modified version of OP's original code.
//Scroll and Wait Strategy
function waitFor (ms) {
return new Promise(resolve => setTimeout(() => resolve(), ms));
}
async function capturePage(browser, url) {
// Load the page that you're trying to screenshot.
const page = await browser.newPage();
await page.goto(url, {waitUntil: 'load'}); // Wait until networkidle2 could work better.
// Set the viewport before scrolling
await page.setViewport({ width: 1366, height: 768});
// Get the height of the page after navigating to it.
// This strategy to calculate height doesn't work always though.
const bodyHandle = await page.$('body');
const { height } = await bodyHandle.boundingBox();
await bodyHandle.dispose();
// Scroll viewport by viewport, allow the content to load
const calculatedVh = page.viewport().height;
let vhIncrease = 0;
while (vhIncrease + calculatedVh < height) {
// Here we pass the calculated viewport height to the context
// of the page and we scroll by that amount
await page.evaluate(_calculatedVh => {
window.scrollBy(0, _calculatedVh);
}, calculatedVh);
await waitFor(300);
vhIncrease = vhIncrease + calculatedVh;
}
// Setting the viewport to the full height might reveal extra elements
await page.setViewport({ width: 1366, height: calculatedVh});
// Wait for a little bit more
await waitFor(1000);
// Scroll back to the top of the page by using evaluate again.
await page.evaluate(_ => {
window.scrollTo(0, 0);
});
return await page.screenshot({type: 'png'});
}
Some key differences here are:
You want to set the viewport from the beginning and operate with that fixed viewport.
You can change the wait time and introduce arbitrary waits to experiment. Sometimes this causes elements that are hanging behind network events to reveal.
Changing the viewport to the full height of the page can also reveal elements as if you were scrolling. You can test this in a real browser by using a vertical monitor. However make sure to go back to the original viewport height, because the viewport also affects the intended rendering.
One thing to understand here is that waiting alone it's not necessarily going to trigger the loading of lazy assets. Scrolling through the height of the document allows the viewport to reveal those elements that need to be within the viewport to get loaded.
Another caveat is that sometimes you need to wait for a relatively long time for the asset to load so in the example above you might need to experiment with the amount of time you're waiting after each scroll. Also as I mentioned arbitrary waits in the general execution sometimes have an effect on whether an asset load or not.
In general, when using Puppeteer for screenshots, you want to make sure that your logic resembles real user behavior. Your goal is to reproduce rending scenarios as if someone was firing Chrome in their computer and navigating to that website.
I have resolved this issue by changing the logic on how I can scroll the page and wait for delay.
A solution that worked for me:
Adjust the timeout limit for my test runner (mocha).
// package.json
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject",
"test": "mocha --timeout=5000" <--- set timeout to something higher than 2 seconds
},
Wait for x seconds where x ~ half of what you set above, then take srcreenshot.
var path = require("path"); // built in with NodeJS
await new Promise((resolve) => setTimeout(() => resolve(), 2000));
var file_path = path.join(__dirname, "__screenshots__/initial.png");
await page.screenshot({ path: file_path });

Prefetch resources for async routes

Is there a way to prefetch or preload async routes? I'm exploring how to do this right now with RR2/3. The basic idea is I code split on every route but I'd like to be able to cache bundles for connected pages in a service worker before visiting that page. So what I'd like to do is have a custom <Link> that every time it's rendered, it tries to cache the resources of the page it's linked to. This would make page transitions considerably faster. What I don't know is if there's a way to emulate navigating to a route so that the resources will be fetched. Is there an API for this or some sort of tricky way to do this someone can think of?
This is what I came up. It's a component that wraps the React Router Link component and in componentDidMount (so only runs on the client not the server) check if in production (no need to run this during development) and if this is a browser that doesn't support Service Workers (this check is specific to my use case). Then manually match against the location and call any async getComponent functions.
import React from 'react'
import Link from 'react-router/lib/Link'
class GatsbyLink extends React.Component {
componentDidMount () {
// Only enable prefetching of Link resources in production and for browsers that
// don't support service workers *cough* Safari/IE *cough*.
if (process.env.NODE_ENV === 'production' && !('serviceWorker' in navigator)) {
const routes = require('my-routes')
const { createMemoryHistory } = require('history')
const matchRoutes = require('react-router/lib/matchRoutes')
const getComponents = require('react-router/lib/getComponents')
const createLocation = createMemoryHistory().createLocation
if (typeof routes !== 'undefined') {
matchRoutes([routes], createLocation(this.props.to), (error, nextState) => {
getComponents(nextState, () => console.log('loaded bundle(s) for ' + this.props.to))
})
}
}
}
render () {
return <Link {...this.props} />
}
}
module.exports = GatsbyLink
You could just do a require.ensure... section in a timeout when the Link is mounted. That should require the code split and load it up async. The timeout will ensure it get's loaded in a separate file.
I would recommend using RR4 for code splitting as I found in RR3 the async required routes get re-included and re-rendered if a child route changes. In my case, I had the componentWillMount of my routes being fired for any child route changes. e.g. Navigating from /agent/step-1 to /agent/step-2 will cause the Component for /agent to be unmounted and re-mounted.