How to write Autodesk.Viewing.Extensions with webpack and react? - reactjs

I was write code within view3D v2.11, React, ES6 and webpack. But I don't know how to write Autodesk.Viewing.Extensions within webpack and React. Can anyone show me some examples?

Using webpack to write a viewer extension is no different than using webpack to write any other js application. Take a look at my extensions library repo, each extension is bundled into a separate .js or .min.js whether you build the project in dev or prod: library-javascript-viewer-extensions.
This is designed this way so each extension can be loaded dynamically independently, however if you build an entire application around the viewer, you may simply include the code for each extension along with the rest of the app and generate a single webpack bundle.
This React project contains multiple viewer extensions (some are extracted from the library mentioned above) and is bundling extensions code along with the rest of the app: forge-rcdb.
As far as React and the viewer are concerned, it's not very relevant because the viewer is a 3D component which is created dynamically, all WebGL canvas and viewer 2D elements are being generated after your app loads a model, whereas React lets you declare 2D components when the app is starting. I was playing a bit with injecting dynamically React components to the viewer div, you can take a look in that same project here:
this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(this.viewerContainer)
// Experimental !
// This this to render dynamic components
// inserted after the viewer div has been created
const reactViewerNode = document.createElement('div')
this.viewer.container.appendChild(reactViewerNode)
this.viewer.react = {
node: reactViewerNode,
components: [],
addComponent: (component) => {
this.viewer.react.components.push(component)
},
render: (props) => {
ReactDOM.render(
<div>
{
React.Children.map(
this.viewer.react.components,
(child) => React.cloneElement(child, props))
}
</div>,
reactViewerNode)
}
}
I then render those dynamic components by overriding componentDidUpdate:
componentDidUpdate () {
if (this.viewer && this.viewer.react) {
if(this.viewer.react.components.length) {
this.viewer.react.render(this.props)
}
}
}
and there is an example of use:
viewer.react.addComponent(
<DBDropdown key="test" className="react-div">
</DBDropdown>
)
I actually haven't implemented any feature using that in the live version of the app, but it should give you an idea.
Hope that helps.

Related

Getting "Unsafe attempt to load URL data:image/svg+xml..." in Safari using a through React component

I'm using rollup to bundle a react npm package that contains an icon component that takes a name as a prop and returns an Icon with that name wrapped by a react component.
This is the component code:
import sprite from './public/sprite.svg';
function Icon({ name }) {
return <svg className="svg-wrapper">
<use href={`${sprite}#${name}`} />
</svg>
);
}
The folder structure is the following:
- src
- - public
- - - sprite.svg
- - icons
- - - some-icon.svg
- - - some-other-icon.svg
- - index.tsx # component with the code mentioned above
And this is my rollup config:
export default {
plugins: [
esbuild({
sourceMap: false,
target: "esnext"
}),
image(),
svgicons({
inputFolder: "src/icons",
output: "public/sprite.svg"
}),
json()
]
}
This works fine in Chrome (although it does inline all the svg inside of the href which I think it's the purpose of this approach) but in Safari it triggers the following error:
Unsafe attempt to load URL data:image/svg+xml,%3c%3fxm ....
Domains, protocols and ports must match.
The thing is, as mentioned, this is an npm package that packages the icons as part of the js bundle (inlining) so there's not much control over how this component is served since this is handled by the browser caching (also one of the key points of using this approach). I'm quite familiar with CORS and I know that perhaps avoiding to use data:image/svg+xml uri links would fix this but would increase the complexity of the build steps of this package (needing to build the icons using svgr/svgo and then have some kind of lookup table to give back the right icon based on the name prop i.e.).
So, ultimately my question is, with the sprite approach in a react component library is there a foolproof way of avoiding these kind of issues and cross-browser inconsistencies?
Thanks in advance for any help provided.
I have been struggling with this issue for a while. I guess this is a bug on Safari throwing an error because is dealing with a dataURI as if it was an external URL.
About your code, you could expose your sprite in a public folder or publish it in a cdn (good for caching purposes) and change the way rollup is handling your svg (it seems it is packing your svg as a dataURI). Alternatively, I implemented a workaround to convert the dataURI in a blob.
import sprite from './public/sprite.svg';
function dataURItoBlobUrl(dataURI: string) {
const svg = decodeURI(dataURI).split(',')[1];
const blob = new Blob([svg], { type: "image/svg+xml" });
return URL.createObjectURL(blob);
}
const blobUrl = dataURItoBlobUrl(sprite);
export const Icon: FC<IconProps> = ({ name, ...props }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" {...props}>
<use href={`${blobUrl}#${name}`}></use>
</svg>
);
};

Full-fledged PDF.js viewer in React

I'm looking to use the full-featured PDF.js in a React component in a Next.js project, as seen in Firefox and as on this online demo. Some important features here are being able to navigate to a certain page number by typing it in, and searching for text in the PDF. Is there a React component available for that?
The library react-pdf is nice for rendering a single page, but doesn't provide a toolbar or a convenient way of lazily loading pages in a scrollable view.
Similar to the questions How to use full PDF.js viewer with toolbar in webpack and Vuejs? (where the accepted answer provides a Vue component) and Embed Full Mozilla pdf.js viewer in vue.js ( using webpack via vue-cli ), but for React.js.
I tried including including /web/viewer.html as part of the inner HTML of a React component by doing the following, but it didn't work out.
Download the latest release and extract it to a folder part of my Next.js project (which I called pdfjs). I tried several folders, such as /client, /client/components, /pages, /node_modules, and /.
Run npm install --save-dev html-loader
Use this Webpack loader that parses HTML files, by changing next.config.js to the following:
module.exports = {
// …
webpack: (config, options) => {
config.module.rules.push({
test: /\.html$/,
exclude: /node_modules/,
use: { loader: 'html-loader' }
});
return config;
},
}
Create a simple page under /pages as follows:
import React from 'react';
import PdfViewer from '../pdfjs/web/viewer.html'
export default function () {
return (
<div className="content" dangerouslySetInnerHTML={{ __html: PdfViewer }} />
);
};
After running next in terminal to start a dev server and navigating to that page in the browser, I get an error about the JavaScript heap running out of memory.
Even if my computer had enough memory, I'm not sure that this would actually result in the PDF rendering – not to mention the danger of using dangerouslySetInnerHTML. It looks like a better solution would probably be to have an actual React component rather than trying to embed an HTML file.
I think this might be more of what your after. I wrapped it in a component for you already but this is a document viewer which can view PDF documents with out much work.
import React,{ Component } from 'react';
import ReactDOM from 'react-dom';
class DocView extends React.Component{
constructor(props){
super(props);
}
render(){
var url = "https://docs.google.com/viewerng/viewer?url="+this.props.src+"&embedded=true";
return(
<iframe style={this.props.style} src={url}></iframe>
);
}
}
export default DocView;
CloudPDF offers a React PDF viewer. It is basically pdf.js but then pre-rendered on the server. This gives the possibility for lazy loading of large pdf files and still keeping performance. And by default has a nice layout for the viewer.
import CloudPdfViewer from '#openbook/cloudpdf-viewer';
export default function () {
return (
<CloudPdfViewer documentId="346467a6-fa61-43ad-b45a-d1fdc3da0007" width="100%" height="500px" />
);
};
Disclamer: I am working for CloudPDF and it is still a beta version.

How does React Lazy fetch components?

I recently read about React Lazy and how it "loads" the components during run time when they are required to be rendered. I assume that "loading" here means fetching the component from the server and then rendering it.
So my question is, how does React manage this fetching of components? How does it know the exact path from where to fetch this component (given that our code will mention the relative path but fetching will require complete server path)? Does it depend on Webpack for this?
Let's look into the React code. React.lazy is defined as follows.
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
$$typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
if (__DEV__) {
// ... additional code only in development mode
}
return lazyType;
}
As you can see, React.lazy requires a Promise which resolves to a module with a default export containing a React component (freely cited by React Docs). This also means that not React resolves the file, but import() does. import() works as documented in the MDN.
The async import() is a new function in ES6 which is not available in all browsers but can be polyfilled by Webpack and Babel/Typescript/others.
What you often see is code like the following, which automatically splits the imported file away by Webpack.
import(/* webpackChunkName: "xyz" */ './component/XYZ')
This creates a new javascript xyz.js next to your bundle script.
If you don't use Webpack, you need to create those files by yourself. Webpack just reduces the work required from you. So you don't absolutely depend on Webpack. This approach might look like the following:
// ./component/xyz.js
export default function() { return <div>Component</div> }
// ./main.js
const OtherComponent = React.lazy(() => import('./component/xyz.js'));
export default function() { return <div>Component</div> }
And the file structure:
| public
|---| main.js
|---| component
|---| --- | main.js
As you see, no webpack required. It just makes your life easier.

visual studio react image path cannot reach

So i have a visual studio project created with react.js.
I am trying to link to an image dynamically, but failing. This is the code I am trying:
At the top im setting a variable for the first part of the path:
this.LogoPath = '../images/'
And then im dynamically grabbing the name of the image from an api call.
this.setState({ imageNamePath: this.state.location.imageName })
And then im concatinating them:
{`${this.LogoPath}${this.state.location.imageName}`}
In the console, im getting:
img src='../images/the-images-name-here.png'
So it seems to be working, but it is not. I get no errors, and I have broken images. My best guess is that react is changing the images to something like:
image-name-here.6a131799.png
Surely someone has run across this before, but my google search pulled up little help.
So how do i get the images to show?
Webpack is a smart tool. One of it's strength is the trash code/files elimination from the bundle.
That means that if a file is not imported using import myFile from '../myPath/myFile.jpg'; or require('../myPath/myFile.jpg');` it won't be a part of the final bundle.
In your case you're not importing the file. You're creating a string variable instead which means nothing to webpack.
There are different options that could work in your case:
Option 1: Pre-import all images and map them in an object:
import React, {Component} from 'react';
import image1 from '../assets/image1.png';
import image2 from '../assets/image2.png';
import image3 from '../assets/image3.png';
const imageTypes = {
image1,
image2,
image3,
}
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
imageType: 'image1'
}
}
render() {
return (
<img src={imageTypes[this.state.imageType]} />
)
}
}
Option 2: Not recommended - Use require to dynamically import files (webpack configurations might be needed in order to include all possible required images in the production bundle):
class MyComponent {
constructor(props) {
super(props);
this.state = {
image: 'file1.png'
}
}
render() {
return (
<img src={require(`./assets/${this.state.image}`)} />
)
}
}
Option 3: Send the image blob (in base64) from the API.
I suggest you to use the Option 1 or Option 3, based on your requirements, such as: how often will be images be changed/added/removed. It's not normal to import files dynamically from ReactJS bundle and you might end up having a non-existing image in your project requested by the data coming from an external source.
For Option 2 you also might have some problems configuring the webpack in order to include in the bundle all the images that you'll probably need to render, even though they are not imported (hardcoded) somewhere in the JS files. Keep in mind that the React Application in production ends up being a collection of static files. You'll need to treat them as follows.

React application with external plugins

I'm building a React application bundled using Parcel or Webpack.
The application should be able to embed external React components
developed by third-parties and hosted elsewhere as modern javascript modules:
// https://example.com/scripts/hello-plugin.js
import React from 'react';
export default class HelloPlugin extends React.Component {
render() {
return "Hello from external plugin!";
}
}
Host application loads these components using asynchronous import like this, for example:
// createAsyncComponent.tsx
import * as React from 'react';
import { asyncComponent } from 'react-async-component';
export default function createAsyncComponent(url: string) {
return asyncComponent({
resolve: () => import(url).then(component => component.default),
LoadingComponent: () => <div>Loading {url}....</div>,
ErrorComponent: ({ error }) => <div>Couldn't load {url}: {error.message}</div>,
})
}
But looks like bundlers don't allow importing arbitrary urls as external javascript modules.
Webpack emits build warnings: "the request of a dependency is an expression" and the import doesn't work. Parcel doesn't report any errors, but fails when import(url) occurs at runtime.
Webpack author recommends using scriptjs or little-loader for loading external scripts.
There is a working sample that loads an UMD component from arbitrary URL like this:
public componentDidMount() {
// expose dependencies as globals
window["React"] = React;
window["PropTypes"] = PropTypes;
// async load of remote UMD component
$script(this.props.url, () => {
const target = window[this.props.name];
if (target) {
this.setState({
Component: target,
error: null,
})
} else {
this.setState({
Component: null,
error: `Cannot load component at ${this.props.url}`,
})
}
});
}
Also, I saw a similar question answered a year ago where the suggested approach also involves passing variables via a window object.
But I'd like to avoid using globals given that most modern browsers support modules out of the box.
I'm wondering if it's possible. Perhaps, any way to instruct the bundler that my import(url) is not a request for the code-split chunk of a host application, but a request for loading an external Javascript module.
In the context of Webpack, you could do something like this:
import(/* webpackIgnore: true */'https://any.url/file.js')
.then((response) => {
response.main({ /* stuff from app plugins need... */ });
});
Then your plugin file would have something like...
const main = (args) => console.log('The plugin was started.');
export { main };
export default main;
Notice you can send stuff from your app's runtime to the plugin at the initialization (i.e. when invoking main at the plugin) of the plugins so you don't end up depending on global variables.
You get caching for free as Webpack remembers (caches) that the given URL has already loaded so subsequent calls to import that URL will resolve immediately.
Note: this seems to work in Chrome, Safari & firefox but not Edge. I never bothered testing in IE or other browsers.
I've tried doing this same sort of load with UMD format on the plugin side and that doesn't seem to work with the way Webpack loads stuff. In fact it's interesting that variables declared as globals, don't end up in the window object of your runtime. You'd have to explicitly do window.aGlobalValue = ... to get something on the global scope.
Obviously you could also use requirejs - or similar - in your app and then just have your plugins follow that API.
Listen to the Webpack author. You can't do (yet) what you're trying to do with Webpack.
You will have to follow his suggested route.

Resources