├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── example ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── shaver │ │ ├── 0.svf │ │ ├── geometry0.pack │ │ ├── geometry1.pack │ │ ├── geometry2.pack │ │ ├── geometry3.pack │ │ ├── image0.png │ │ ├── objects_attrs.json.gz │ │ ├── objects_avs.json.gz │ │ ├── objects_ids.json.gz │ │ ├── objects_offs.json.gz │ │ └── objects_vals.json.gz ├── src │ ├── App.test.tsx │ ├── App.tsx │ ├── ExampleExtension.ts │ ├── ExampleTool.ts │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ └── setupTests.ts ├── tsconfig.json └── yarn.lock ├── package-lock.json ├── package.json ├── src ├── .eslintrc ├── defaults.ts ├── extension.ts ├── helpers.ts ├── hooks.ts ├── index.test.tsx ├── index.tsx ├── react-app-env.d.ts ├── tool-interface.ts └── typings.d.ts ├── tsconfig.json ├── tsconfig.test.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react", 9 | "plugin:@typescript-eslint/eslint-recommended" 10 | ], 11 | "env": { 12 | "node": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "ecmaFeatures": { 17 | "legacyDecorators": true, 18 | "jsx": true 19 | } 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "16" 24 | } 25 | }, 26 | "rules": { 27 | "space-before-function-paren": 0, 28 | "react/prop-types": 0, 29 | "react/jsx-handler-names": 0, 30 | "react/jsx-fragments": 0, 31 | "react/no-unused-prop-types": 0, 32 | "import/export": 0, 33 | "semi": [2, "always"] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-adsk-forge-viewer-ts 2 | 3 | 4 | 5 | React Typescript Autodesk Forge Viewer. 6 | 7 | 8 | 9 | [![NPM](https://img.shields.io/npm/v/@contecht/react-adsk-forge-viewer.svg)](https://www.npmjs.com/package/@contecht/react-adsk-forge-viewer) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 10 | 11 | 12 | 13 | ## Install 14 | 15 | 16 | 17 | ```bash 18 | 19 | npm install @contecht/react-adsk-forge-viewer 20 | 21 | ``` 22 | 23 | 24 | 25 | ## Basic Usage 26 | 27 | 28 | 29 | ```tsx 30 | 31 | import React from 'react'; 32 | 33 | import { ForgeViewer } from '@contecht/react-adsk-forge-viewer'; 34 | 35 | 36 | 37 | const token = "dXtgfg433432e4445..."; // Forge token 2 or 3 legged 38 | 39 | const urn = "dJggddssvc_ggddd..."; // base64 encoded model urn 40 | 41 | 42 | 43 | 44 | const Container = () => { 45 | 46 | return 47 | 48 | } 49 | 50 | ``` 51 | 52 | ## Props 53 | 54 | - local (optional) {boolean} - specifies environment, by default `derivativeV2`, when `true` turns to `Local` and allows to load a model from SVF file, default `false` 55 | - token (local:false) {string} - 2 or 3 legged access_token retrieved from Forge Authentication API endpoints 56 | - urn (local:false) {string} - base64 encoded item urn 57 | - path (local:true) {string} - path to SVF file 58 | - version (optional) {string} - Forge Viewer script version, default: `7.*` 59 | - headless (optional) {boolean} - if true mounts a headless viewer without toolbars and other widgets, default: `true` 60 | - initializerOptions (optional) {Autodesk.Viewing.InitializerOptions} - viewer initializer options, see: [docs](https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/#initializer-options-callback), default: `{env: 'AutodeskProduction', api: 'derivativeV2'}` 61 | - viewerOptions (optional) {object} -3D viewer options, see: [docs](https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/Viewer3D/#new-viewer3d-container-config), default: `{}` 62 | - onDocumentLoadSuccess (optional) {(d: Autodesk.Viewing.Document) => Autodesk.Viewing.BubbleNode} - callback triggered on successful document load, pass a new one to select different viewables, must return a viewable to display, default: `(viewerDocument) => viewerDocument.getRoot().getDefaultGeometry();` 63 | - onDocumentLoadError (optional) {(code?: number, msg?: string, msgs?: any[]) => void} - error handling callback, default: `(args) => console.error(args)` 64 | - viewableOptions - (optional) {object} - viewable options see: [docs](https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/Viewer3D/#loaddocumentnode-avdocument-manifestnode-options), default: `{}` 65 | - onInit (optional) {(o: any) => void} - function to trigger after successful viewer initialization, default: `undefined` 66 | - extensions (optional) {ForgeExtension[]} - array of extensions to load on viewer start, more about extensions in the section below, default: `undefined` 67 | - activeTool (optional) {ToolInterface} - Tool interface implementing viewer interactions. Constructor recives additionally viewerOptions as extOptions, default: `undefined` 68 | - style (optional) {object} - React inline style to be applied to viewer container div 69 | - disableLoader (optional) {boolean} - remove Forge spinner while initializing the viewer, default: `false` 70 | 71 | ## Local files 72 | 73 | Forge Viewer can display models without calling the API, using SVF files downloaded from Model Derivatives API. This method doesn't require any access token. File must be loaded via http. 74 | 75 | ```tsx 76 | import React from 'react'; 77 | 78 | import { ForgeViewer } from '@contecht/react-adsk-forge-viewer'; 79 | 80 | const svf = '../assets/model/file.svf'; 81 | 82 | const Container = () => { 83 | 84 | return 88 | 89 | } 90 | ``` 91 | 92 | ## Extensions 93 | 94 | Viewer Extensions contain functionality extending viewer capabilities, can be loaded using property `extensions`, example: 95 | 96 | ```tsx 97 | import { ForgeExtension } from '@contecht/react-adsk-forge-viewer'; 98 | 99 | declare var THREE: any; 100 | 101 | declare var Autodesk: any; 102 | 103 | export default class ExampleExtension extends ForgeExtension { 104 | 105 | extensionName = 'ExampleExtension'; 106 | 107 | load(): boolean { 108 | 109 | // change selection color to red 110 | 111 | const red = new THREE.Color(1,0,0); 112 | 113 | this.viewer.setSelectionColor(red, Autodesk.Viewing.SelectionType.MIXED); 114 | 115 | return true; 116 | } 117 | 118 | unload(): boolean { 119 | 120 | return true; 121 | } 122 | 123 | activate() {} 124 | 125 | deactivate() {} 126 | 127 | } 128 | ``` 129 | 130 | ## Tool Interface 131 | 132 | ToolInterface is a base class for viewer interactions. It supports the following event handlers: 133 | - handleSingleClick 134 | - handleDoubleClick 135 | - handleSingleTap 136 | - handleDoubleTap 137 | - handleKeyDown 138 | - handleKeyUp 139 | - handleWheelInput 140 | - handleButtonDown 141 | - handleButtonUp 142 | - handleMouseMove 143 | - handleGesture 144 | - handleBlur 145 | - handleResize 146 | 147 | Can be registered and activated using property `activeTool`. In case of more complex workflow it's recommended to handle (de)registering and (de)activating inside an extension to have full controll over the tool lifecycle. 148 | 149 | Example: 150 | ``` 151 | import { ToolInterface } from 'react-adsk-forge-viewer-ts'; 152 | 153 | export default class ExampleTool extends ToolInterface { 154 | public toolName = 'ExampleTool'; 155 | 156 | activate() {} 157 | deactivate() {} 158 | register() {} 159 | deregister() {} 160 | handleSingleClick(event: any) { 161 | // method executed on every mouse button click 162 | // do something with the event here 163 | const hitTest = this.viewer.clientToWorld(event.canvasX, event.canvasY, true); 164 | console.info(hitTest); 165 | return true; 166 | } 167 | } 168 | ``` 169 | 170 | ## Advanced Usage 171 | 172 | ```tsx 173 | 174 | import React from 'react'; 175 | import { ForgeViewer } from '@contecht/react-adsk-forge-viewer'; 176 | import ExampleExtension from './extensions/example-extension'; 177 | import ExampleTool from './tools/example-tool'; 178 | const token = "dXtgfg433432e4445..."; // Forge token 2 or 3 legged 179 | const urn = "dJggddssvc_ggddd..."; // base64 encoded model urn 180 | 181 | const initializerOptions = { 182 | language: 'de' 183 | } 184 | const viewerOptions = { 185 | theme: 'light' 186 | } 187 | const onDocumentLoadSuccess = (viewerDocument) => { 188 | const viewables = viewerDocument.getRoot().search({'type':'geometry'}); 189 | return viewables.find(v => v.is2D()); 190 | } 191 | const viewableOptions = { 192 | globalOffset: {x:0,y:0,z:25} 193 | } 194 | 195 | const ViewerContainer = () => { 196 | 197 | return( 198 | 210 | ) 211 | 212 | } 213 | ``` 214 | 215 | ## Author 216 | ``` 217 | Damian Harasymczuk 218 | ``` 219 | 220 | ## License 221 | 222 | 223 | 224 | MIT © -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | It is linked to the react-adsk-forge-viewer-ts package in the parent directory for development purposes. 4 | 5 | You can run `yarn install` and then `yarn start` to test your package. 6 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-adsk-forge-viewer-ts-example", 3 | "homepage": ".", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start", 8 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build", 9 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test", 10 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject" 11 | }, 12 | "dependencies": { 13 | "@testing-library/jest-dom": "link:../node_modules/@testing-library/jest-dom", 14 | "@testing-library/react": "link:../node_modules/@testing-library/react", 15 | "@testing-library/user-event": "link:../node_modules/@testing-library/user-event", 16 | "@types/jest": "link:../node_modules/@types/jest", 17 | "@types/node": "link:../node_modules/@types/node", 18 | "@types/react": "link:../node_modules/@types/react", 19 | "@types/react-dom": "link:../node_modules/@types/react-dom", 20 | "react": "link:../node_modules/react", 21 | "react-dom": "link:../node_modules/react-dom", 22 | "react-scripts": "link:../node_modules/react-scripts", 23 | "typescript": "link:../node_modules/typescript", 24 | "react-adsk-forge-viewer-ts": "link:.." 25 | }, 26 | "devDependencies": { 27 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3" 28 | }, 29 | "eslintConfig": { 30 | "extends": "react-app" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | react-adsk-forge-viewer-ts 28 | 29 | 30 | 31 | 34 | 35 |
36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-adsk-forge-viewer-ts", 3 | "name": "react-adsk-forge-viewer-ts", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/public/shaver/0.svf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/0.svf -------------------------------------------------------------------------------- /example/public/shaver/geometry0.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/geometry0.pack -------------------------------------------------------------------------------- /example/public/shaver/geometry1.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/geometry1.pack -------------------------------------------------------------------------------- /example/public/shaver/geometry2.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/geometry2.pack -------------------------------------------------------------------------------- /example/public/shaver/geometry3.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/geometry3.pack -------------------------------------------------------------------------------- /example/public/shaver/image0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/image0.png -------------------------------------------------------------------------------- /example/public/shaver/objects_attrs.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/objects_attrs.json.gz -------------------------------------------------------------------------------- /example/public/shaver/objects_avs.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/objects_avs.json.gz -------------------------------------------------------------------------------- /example/public/shaver/objects_ids.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/objects_ids.json.gz -------------------------------------------------------------------------------- /example/public/shaver/objects_offs.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/objects_offs.json.gz -------------------------------------------------------------------------------- /example/public/shaver/objects_vals.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmh126/react-adsk-forge-viewer-ts/297709a8b0a11f10fcb407974e11dac8418b61a5/example/public/shaver/objects_vals.json.gz -------------------------------------------------------------------------------- /example/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | ReactDOM.unmountComponentAtNode(div) 9 | }) 10 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ForgeViewer } from 'react-adsk-forge-viewer-ts'; 3 | import ExampleExtension from './ExampleExtension'; 4 | import ExampleTool from './ExampleTool'; 5 | //const token = 'eyJhbGciOi...' 6 | //const urn = 'dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLm5aUnFBSVZsUmtLWFdIeXYyZlpIR3c_dmVyc2lvbj0x' 7 | const extensions = [ExampleExtension] 8 | 9 | const App = () => { 10 | return 19 | } 20 | 21 | export default App 22 | -------------------------------------------------------------------------------- /example/src/ExampleExtension.ts: -------------------------------------------------------------------------------- 1 | import { ForgeExtension } from 'react-adsk-forge-viewer-ts'; 2 | 3 | declare var THREE: any; 4 | declare var Autodesk: any; 5 | 6 | export default class ExampleExtension extends ForgeExtension { 7 | public static extensionName = 'ChangedName'; 8 | 9 | load(): boolean { 10 | // change selection color to red 11 | const red = new THREE.Color(1,0,0); 12 | this.viewer.setSelectionColor(red, Autodesk.Viewing.SelectionType.MIXED); 13 | return true; 14 | } 15 | 16 | unload(): boolean { 17 | return true; 18 | } 19 | 20 | activate() {} 21 | deactivate() {} 22 | 23 | } -------------------------------------------------------------------------------- /example/src/ExampleTool.ts: -------------------------------------------------------------------------------- 1 | import { ToolInterface } from 'react-adsk-forge-viewer-ts'; 2 | 3 | export default class ExampleTool extends ToolInterface { 4 | public toolName = 'ExampleTool'; 5 | 6 | activate() {} 7 | deactivate() {} 8 | register() {} 9 | deregister() {} 10 | handleSingleClick(event: any) { 11 | const hitTest = this.viewer.clientToWorld(event.canvasX, event.canvasY, true); 12 | console.info(hitTest); 13 | return true; 14 | } 15 | } -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import App from './App' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": [ 6 | "dom", 7 | "esnext" 8 | ], 9 | "moduleResolution": "node", 10 | "jsx": "react", 11 | "sourceMap": true, 12 | "declaration": true, 13 | "esModuleInterop": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "allowSyntheticDefaultImports": true, 22 | "target": "es5", 23 | "allowJs": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "forceConsistentCasingInFileNames": true, 27 | "resolveJsonModule": true, 28 | "isolatedModules": true, 29 | "noEmit": true 30 | }, 31 | "include": [ 32 | "src" 33 | ], 34 | "exclude": [ 35 | "node_modules", 36 | "build" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@contecht/react-adsk-forge-viewer", 3 | "version": "1.1.7", 4 | "description": "React Typescript Autodesk Forge Viewer", 5 | "author": "Damian Harasymczuk", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "module": "dist/index.modern.js", 9 | "source": "src/index.tsx", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/dmh126/react-adsk-forge-viewer-ts.git" 13 | }, 14 | "engines": { 15 | "node": ">=10" 16 | }, 17 | "scripts": { 18 | "build": "microbundle-crl --no-compress --format modern,cjs", 19 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 20 | "prepare": "run-s build", 21 | "test": "run-s test:unit test:lint test:build", 22 | "test:build": "run-s build", 23 | "test:lint": "eslint", 24 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 25 | "test:watch": "react-scripts test --env=jsdom", 26 | "predeploy": "cd example && yarn install && yarn run build", 27 | "deploy": "gh-pages -d example/build" 28 | }, 29 | "peerDependencies": { 30 | "react": "^18.0.0" 31 | }, 32 | "devDependencies": { 33 | "@testing-library/jest-dom": "^4.2.4", 34 | "@testing-library/react": "^9.5.0", 35 | "@testing-library/user-event": "^7.2.1", 36 | "@types/jest": "^25.1.4", 37 | "@types/node": "^12.12.38", 38 | "@types/react": "^16.9.27", 39 | "@types/react-dom": "^16.9.7", 40 | "@typescript-eslint/eslint-plugin": "^2.26.0", 41 | "@typescript-eslint/parser": "^2.26.0", 42 | "microbundle-crl": "^0.13.10", 43 | "babel-eslint": "^10.0.3", 44 | "cross-env": "^7.0.2", 45 | "eslint": "^6.8.0", 46 | "eslint-config-prettier": "^6.7.0", 47 | "eslint-config-standard": "^14.1.0", 48 | "eslint-config-standard-react": "^9.2.0", 49 | "eslint-plugin-import": "^2.18.2", 50 | "eslint-plugin-node": "^11.0.0", 51 | "eslint-plugin-prettier": "^3.1.1", 52 | "eslint-plugin-promise": "^4.2.1", 53 | "eslint-plugin-react": "^7.17.0", 54 | "eslint-plugin-standard": "^4.0.1", 55 | "gh-pages": "^2.2.0", 56 | "npm-run-all": "^4.1.5", 57 | "prettier": "^2.0.4", 58 | "react": "^16.13.1", 59 | "react-dom": "^16.13.1", 60 | "react-scripts": "^5.0.1", 61 | "typescript": "^3.7.5" 62 | }, 63 | "files": [ 64 | "dist", 65 | "README.md" 66 | ], 67 | "dependencies": { 68 | "@types/forge-viewer": "^7.32.2" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_VERSION: string = '7.*'; 2 | export const DEFAULT_INITIALIZER_OPTIONS: object = { 3 | env: 'AutodeskProduction', 4 | api: 'derivativeV2' 5 | }; 6 | export const DEFAULT_VIEWER_OPTIONS: object = {}; 7 | export const DEFAULT_VIEWABLE_OPTIONS: object = {}; 8 | export const DEFAULT_DOCUMENT_LOAD_SUCCESS = ( 9 | viewerDocument: Autodesk.Viewing.Document 10 | ): Autodesk.Viewing.BubbleNode => viewerDocument.getRoot().getDefaultGeometry(); 11 | export const DEFAULT_DOCUMENT_LOAD_ERROR = ( 12 | errorCode?: Autodesk.Viewing.ErrorCodes, 13 | errorMsg?: string, 14 | messages?: any[] 15 | ): void => { 16 | console.error(errorCode, errorMsg, messages); 17 | }; 18 | export const DEFAULT_ON_INIT = (): void => {}; 19 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export abstract class Extension { 4 | public static extensionName: string = ''; 5 | protected viewer: Autodesk.Viewing.Viewer3D; 6 | protected extOptions: Autodesk.Viewing.ExtensionOptions; 7 | 8 | constructor( 9 | viewer: Autodesk.Viewing.Viewer3D, 10 | options?: Autodesk.Viewing.ExtensionOptions 11 | ) { 12 | this.viewer = viewer; 13 | this.extOptions = options || ({} as Autodesk.Viewing.ExtensionOptions); 14 | } 15 | 16 | public abstract load(): void; 17 | public abstract unload(): void; 18 | public abstract activate(): void; 19 | public abstract deactivate(): void; 20 | } 21 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | export const loadScripts = (version: string): Promise => 2 | new Promise((resolve, reject) => { 3 | let ready: boolean = false; 4 | const script: HTMLScriptElement = document.createElement('script'); 5 | script.src = `https://developer.api.autodesk.com/modelderivative/v2/viewers/${version}/viewer3D.min.js`; 6 | script.async = true; 7 | document.body.appendChild(script); 8 | const style: HTMLLinkElement = document.createElement('link'); 9 | style.rel = 'stylesheet'; 10 | style.type = 'text/css'; 11 | style.href = `https://developer.api.autodesk.com/modelderivative/v2/viewers/${version}/style.min.css`; 12 | document.body.appendChild(style); 13 | 14 | script.onload = (): void => { 15 | if (!ready) { 16 | ready = true; 17 | resolve(script); 18 | } 19 | }; 20 | script.onerror = (msg: any): void => { 21 | console.error(msg); 22 | reject(new Error('Error loading Forge script.')); 23 | }; 24 | script.onabort = (msg: UIEvent): void => { 25 | console.error(msg); 26 | reject(new Error('Forge script loading aborted.')); 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { loadScripts } from './helpers'; 3 | import { 4 | DEFAULT_DOCUMENT_LOAD_ERROR, 5 | DEFAULT_DOCUMENT_LOAD_SUCCESS, 6 | DEFAULT_INITIALIZER_OPTIONS, 7 | DEFAULT_VIEWER_OPTIONS, 8 | DEFAULT_VIEWABLE_OPTIONS, 9 | DEFAULT_VERSION, 10 | DEFAULT_ON_INIT 11 | } from './defaults'; 12 | 13 | type State = { 14 | scriptsLoaded: boolean; 15 | scriptElement: HTMLScriptElement | null; 16 | styleElement: HTMLScriptElement | null; 17 | }; 18 | type Refs = { 19 | viewer: any; 20 | }; 21 | type Setters = { 22 | setScriptsLoaded: React.Dispatch>; 23 | }; 24 | 25 | type Hooks = { 26 | refs: Refs; 27 | state?: State; 28 | setters?: Setters; 29 | style: React.CSSProperties; 30 | }; 31 | 32 | export function useHooks({ 33 | local = false, 34 | version = DEFAULT_VERSION, 35 | path, 36 | token, 37 | urn, 38 | initializerOptions, 39 | viewerOptions, 40 | headless = false, 41 | onDocumentLoadSuccess = DEFAULT_DOCUMENT_LOAD_SUCCESS, 42 | onDocumentLoadError = DEFAULT_DOCUMENT_LOAD_ERROR, 43 | viewableOptions = DEFAULT_VIEWABLE_OPTIONS, 44 | onInit = DEFAULT_ON_INIT, 45 | extensions, 46 | style = {}, 47 | disableLoader = false, 48 | activeTool 49 | }: any): Hooks { 50 | // declare state 51 | const [scriptsLoaded, setScriptsLoaded] = React.useState(false); 52 | const [ 53 | scriptElement, 54 | setScriptElement 55 | ] = React.useState(null); 56 | const [ 57 | styleElement, 58 | setStyleElement 59 | ] = React.useState(null); 60 | // viewer container ref 61 | const viewerRef = React.useRef(null); 62 | // viewer object 63 | let viewer: Autodesk.Viewing.Viewer3D; 64 | // forge initializer options, default settings for Derivatives API 65 | // if passed 'local' as true it changes that to Local and allows to load SVF 66 | // see: https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/#initializer-options-callback 67 | const initializerOpts: Autodesk.Viewing.InitializerOptions = Object.assign( 68 | DEFAULT_INITIALIZER_OPTIONS, 69 | initializerOptions 70 | ); 71 | if (local) { 72 | initializerOpts.env = 'Local'; 73 | } else { 74 | initializerOpts.getAccessToken = (done: (t: string, n: number) => void) => 75 | done(token, 3600); 76 | } 77 | // viewer options 78 | // see: https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/Viewer3D/#new-viewer3d-container-config 79 | const viewerOpts: Autodesk.Viewing.Viewer3DConfig = Object.assign( 80 | DEFAULT_VIEWER_OPTIONS, 81 | viewerOptions 82 | ); 83 | // initialize the viewer, depending on some parameters can instantiate a headless viewer, 84 | // can load SVF files, can load all the provided extensions 85 | const initialize = (): void => { 86 | Autodesk.Viewing.Initializer(initializerOpts, () => { 87 | if (headless) { 88 | viewer = new Autodesk.Viewing.Viewer3D(viewerRef.current, viewerOpts); 89 | } else { 90 | viewer = new Autodesk.Viewing.GuiViewer3D( 91 | viewerRef.current, 92 | viewerOpts 93 | ); 94 | } 95 | viewer.addEventListener(Autodesk.Viewing.VIEWER_INITIALIZED, (e) => { 96 | if (disableLoader) { 97 | const spinnerContainer = (viewer as any)._loadingSpinner.domElement; 98 | while (spinnerContainer.hasChildNodes()) { 99 | spinnerContainer.removeChild(spinnerContainer.lastChild); 100 | } 101 | } 102 | onInit(e); 103 | }); 104 | const startedCode = viewer.start(path); 105 | if (startedCode > 0) { 106 | console.error('Failed to create a Viewer: WebGL not supported.'); 107 | return; 108 | } 109 | if (extensions) { 110 | extensions.forEach((extension: any) => { 111 | Autodesk.Viewing.theExtensionManager.registerExtension( 112 | extension.extensionName, 113 | extension 114 | ); 115 | viewer.loadExtension(extension.extensionName, viewerOptions); 116 | }); 117 | } 118 | if (activeTool) { 119 | const ToolConstructor = activeTool; 120 | // eslint-disable-next-line dot-notation 121 | const toolController = viewer['toolController']; 122 | const tool = new ToolConstructor(viewer, viewerOptions); 123 | toolController.registerTool(tool); 124 | toolController.activateTool(tool.toolName); 125 | } 126 | }); 127 | }; 128 | // onDocumentLoadSuccess wrapper 129 | const handleDocumentLoad = ( 130 | viewerDocument: Autodesk.Viewing.Document 131 | ): void => { 132 | const viewable = onDocumentLoadSuccess(viewerDocument); 133 | viewer.loadDocumentNode(viewerDocument, viewable, viewableOptions); 134 | }; 135 | // load model using Derivatives API 136 | const loadModel = (): void => { 137 | Autodesk.Viewing.Document.load( 138 | `urn:${urn}`, 139 | handleDocumentLoad, 140 | onDocumentLoadError 141 | ); 142 | }; 143 | // triggered on forge scripts loaded 144 | React.useEffect(() => { 145 | if (!window.Autodesk) { 146 | loadScripts(version) 147 | .then(({ script, style }) => { 148 | setScriptElement(script); 149 | setStyleElement(style); 150 | setScriptsLoaded(true); 151 | }) 152 | .catch((error) => { 153 | console.error(error); 154 | }); 155 | } else { 156 | setScriptsLoaded(true); 157 | } 158 | if (scriptsLoaded) { 159 | initialize(); 160 | if (!local) loadModel(); 161 | } 162 | return () => { 163 | if (scriptElement && styleElement) { 164 | document.body.removeChild(scriptElement); 165 | document.body.removeChild(styleElement); 166 | } 167 | }; 168 | }, [scriptsLoaded]); 169 | 170 | return { 171 | refs: { 172 | viewer: viewerRef 173 | }, 174 | style 175 | }; 176 | } 177 | -------------------------------------------------------------------------------- /src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { ForgeViewer } from '.'; 2 | 3 | describe('ForgeViewer', () => { 4 | it('is truthy', () => { 5 | expect(ForgeViewer).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useHooks } from './hooks'; 3 | 4 | interface LocalProps { 5 | local?: true; 6 | path: string; 7 | } 8 | 9 | interface ApiProps { 10 | local?: false; 11 | token: string; 12 | urn: string; 13 | onDocumentLoadSuccess?: ( 14 | d: Autodesk.Viewing.Document 15 | ) => Autodesk.Viewing.BubbleNode; 16 | onDocumentLoadError?: ( 17 | errorCode?: Autodesk.Viewing.ErrorCodes, 18 | errorMsg?: string, 19 | messages?: any[] 20 | ) => void; 21 | } 22 | 23 | interface DefaultProps { 24 | version?: string; 25 | initializerOptions?: Autodesk.Viewing.InitializerOptions; 26 | viewerOptions?: Autodesk.Viewing.Viewer3DConfig; 27 | headless?: boolean; 28 | viewableOptions?: Autodesk.Viewing.LoadModelOptions; 29 | onInit?: (v?: any) => void; 30 | extensions?: any[]; 31 | style?: any; 32 | disableLoader?: boolean; 33 | activeTool?: any; 34 | } 35 | 36 | type Props = (ApiProps | LocalProps) & DefaultProps; 37 | 38 | export const ForgeViewer = (props: Props): React.ReactElement => { 39 | const { refs, style } = useHooks(props); 40 | return
; 41 | }; 42 | 43 | export { Extension as ForgeExtension } from './extension'; 44 | export { ToolInterface } from './tool-interface'; 45 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/tool-interface.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export interface ToolInterface { 3 | handleSingleClick?(event: any, button: number): boolean; 4 | handleDoubleClick?(event: any, button: number): boolean; 5 | handleSingleTap?(event: any): boolean; 6 | handleDoubleTap?(event: any): boolean; 7 | handleKeyDown?(event: any, keyCode: number): boolean; 8 | handleKeyUp?(event: any, keyCode: number): boolean; 9 | handleWheelInput?(delta: number): boolean; 10 | handleButtonDown?(event: any, button: number): boolean; 11 | handleButtonUp?(event: any, button: number): boolean; 12 | handleMouseMove?(event: any): boolean; 13 | handleGesture?(event: any): boolean; 14 | handleBlur?(event: any): boolean; 15 | handleResize?(): void; 16 | } 17 | 18 | export abstract class ToolInterface { 19 | public abstract toolName: string; 20 | protected viewer: Autodesk.Viewing.Viewer3D; 21 | protected extOptions: any; 22 | 23 | constructor(viewer: Autodesk.Viewing.Viewer3D, options?: any) { 24 | this.viewer = viewer; 25 | this.extOptions = options || {}; 26 | } 27 | 28 | public abstract register(): void; 29 | public abstract deregister(): void; 30 | public abstract activate(): void; 31 | public abstract deactivate(): void; 32 | 33 | getName(): string { 34 | return this.toolName; 35 | } 36 | 37 | getNames(): string[] { 38 | return [this.toolName]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'forge-viewer'; 2 | 3 | declare module 'react-adsk-forge-viewer-ts' { 4 | export const ForgeViewer: (props: Props) => React.ReactElement; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": [ 6 | "dom", 7 | "esnext" 8 | ], 9 | "moduleResolution": "node", 10 | "jsx": "react", 11 | "sourceMap": true, 12 | "declaration": true, 13 | "esModuleInterop": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "allowSyntheticDefaultImports": true, 22 | "target": "es5", 23 | "allowJs": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "forceConsistentCasingInFileNames": true, 27 | "resolveJsonModule": true, 28 | "isolatedModules": true, 29 | "noEmit": true 30 | }, 31 | "include": [ 32 | "src" 33 | ], 34 | "exclude": [ 35 | "node_modules", 36 | "dist", 37 | "example" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } --------------------------------------------------------------------------------