├── .gitattributes ├── .github ├── CODEOWNERS └── workflows │ └── codeql.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Bentley-CLA.pdf ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── agent-app └── query-agent │ ├── .vscode │ ├── launch.json │ └── settings.json │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── src │ ├── BriefcaseProvider.ts │ ├── ChangeSummaryExtractor.ts │ ├── Main.ts │ ├── QueryAgent.ts │ ├── QueryAgentConfig.ts │ └── test │ │ ├── IModelQueryAgent.test.ts │ │ └── TestMockObjects.ts │ └── tsconfig.json ├── common ├── config │ ├── azure-pipelines │ │ ├── ci-build.yaml │ │ └── integration-build.yaml │ └── rush │ │ ├── .npmrc │ │ ├── command-line.json │ │ └── pnpm-lock.yaml └── scripts │ ├── install-run-rush.js │ ├── install-run-rushx.js │ └── install-run.js ├── interactive-app ├── basic-viewport-app │ ├── .env.local │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── public │ │ ├── appicon.ico │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── backend │ │ │ └── main.ts │ │ ├── common │ │ │ └── rpcs.ts │ │ ├── frontend │ │ │ ├── api │ │ │ │ └── BasicViewportApp.ts │ │ │ ├── components │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── Components.scss │ │ │ │ └── Toolbar.tsx │ │ │ ├── index.css │ │ │ └── index.tsx │ │ └── index.ts │ ├── tsconfig.backend.json │ └── tsconfig.json ├── ninezone-sample-app │ ├── .env.local │ ├── .vscode │ │ ├── extensions.json │ │ ├── launch.json │ │ └── settings.json │ ├── LICENSE.md │ ├── README.md │ ├── docs │ │ ├── header.png │ │ └── panels.png │ ├── package.json │ ├── public │ │ ├── appicon.ico │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── locales │ │ │ └── en │ │ │ │ └── NineZoneSample.json │ │ └── manifest.json │ ├── src │ │ ├── backend │ │ │ ├── electronmain.ts │ │ │ └── webmain.ts │ │ ├── common │ │ │ ├── LoggerCategory.ts │ │ │ └── rpcs.ts │ │ ├── frontend │ │ │ ├── api │ │ │ │ └── rpc.ts │ │ │ ├── app-ui │ │ │ │ ├── AppUi.tsx │ │ │ │ ├── backstage │ │ │ │ │ ├── AppBackstageComposer.tsx │ │ │ │ │ └── AppBackstageItemProvider.tsx │ │ │ │ ├── contentviews │ │ │ │ │ └── TableContent.tsx │ │ │ │ ├── frontstages │ │ │ │ │ ├── SampleFrontstage.tsx │ │ │ │ │ └── SampleFrontstage2.tsx │ │ │ │ ├── statusbars │ │ │ │ │ └── AppStatusBar.tsx │ │ │ │ └── widgets │ │ │ │ │ ├── PropertyGridWidget.tsx │ │ │ │ │ ├── TableWidget.tsx │ │ │ │ │ └── TreeWidget.tsx │ │ │ ├── app │ │ │ │ └── NineZoneSampleApp.ts │ │ │ ├── components │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── Properties.tsx │ │ │ │ ├── Table.ruleset.json │ │ │ │ ├── Table.tsx │ │ │ │ ├── Tree.ruleset.json │ │ │ │ ├── Tree.tsx │ │ │ │ └── Viewport.tsx │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ └── index.ts │ ├── tsconfig.backend.json │ └── tsconfig.json └── simple-viewer-app │ ├── .env.local │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json │ ├── LICENSE.md │ ├── README.md │ ├── docs │ └── header.png │ ├── package.json │ ├── public │ ├── appicon.ico │ ├── favicon.ico │ ├── index.html │ ├── locales │ │ └── en │ │ │ └── SimpleViewer.json │ └── manifest.json │ ├── src │ ├── backend │ │ ├── electronmain.ts │ │ └── webmain.ts │ ├── common │ │ ├── LoggerCategory.ts │ │ └── rpcs.ts │ ├── frontend │ │ ├── api │ │ │ ├── SimpleViewerApp.ts │ │ │ └── rpc.ts │ │ ├── components │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── Components.scss │ │ │ ├── Properties.tsx │ │ │ ├── Table.ruleset.json │ │ │ ├── Table.tsx │ │ │ ├── Toolbar.tsx │ │ │ ├── Tree.ruleset.json │ │ │ ├── Tree.tsx │ │ │ └── Viewport.tsx │ │ ├── index.css │ │ └── index.tsx │ └── index.ts │ ├── test │ ├── electron │ │ └── basic.test.ts │ ├── end-to-end │ │ ├── helpers.ts │ │ ├── setupTests.ts │ │ └── views │ │ │ ├── Content.test.ts │ │ │ ├── OpenIModel.test.ts │ │ │ └── SignIn.test.ts │ ├── unit │ │ ├── assets │ │ │ └── Properties_60InstancesWithUrl2.ibim │ │ ├── frontend │ │ │ └── components │ │ │ │ ├── Properties.test.tsx │ │ │ │ └── Tree.test.tsx │ │ └── index.test.ts │ └── utils │ │ ├── mocha.opts │ │ └── setupTests.js │ ├── tsconfig.backend.json │ └── tsconfig.json ├── rush.json └── tools └── imodel-changeset-test-utility ├── .npmignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE.md ├── README.md ├── package.json ├── src ├── ChangesetGenerationConfig.ts ├── ChangesetGenerationHarness.ts ├── ChangesetGenerator.ts ├── HubUtility.ts ├── IModelChangesetCLUtility.ts ├── IModelDbHandler.ts ├── TestChangesetSequence.ts ├── index.ts └── test │ ├── IModelChangesetTestUtility.test.ts │ └── TestMockObjects.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Prevent Git to auto detect text files and perform LF normalization. 2 | * -text 3 | 4 | # The item with `binary` is treated as binary file. 5 | # The item with `eol=lf` is converted to LF on checkin, back to LF on checkout. 6 | 7 | package.json text eol=lf 8 | 9 | *.gitignore text eol=lf 10 | *.gitattributes text eol=lf 11 | *.svg text elf=lf 12 | 13 | # NPM "bin" scripts MUST have LF, or else the executable fails to run on Mac. 14 | # This fnmatch expression only matches files in a "bin" folder and without 15 | # a period in the filename. 16 | /*/*/bin/+([!.]) -text 17 | 18 | # Don't allow people to merge changes to these generated files, because the result 19 | # may be invalid. You need to run "rush update" again. 20 | pnpm-lock.yaml merge=binary 21 | shrinkwrap.yaml merge=binary 22 | npm-shrinkwrap.json merge=binary -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This CODEOWNERS file follows the format of Github's CODEOWNERS 2 | # Documentation for reference, https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 3 | 4 | # This is a comment. 5 | # Each line is a file pattern followed by one or more owners. 6 | 7 | # These owners will be the default owners for everything in 8 | # the repo. Unless a later match takes precedence, 9 | # @global-owner1 and @global-owner2 will be requested for 10 | # review when someone opens a pull request. 11 | * @Josh-Schifter @BaoTon @roopsaini @williamkbentley @calebmshafer 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 12 * * 6' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # If this run was triggered by a pull request event, then checkout 26 | # the head of the pull request instead of the merge commit. 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v1 33 | # Override language selection by uncommenting this and choosing your languages 34 | # with: 35 | # languages: go, javascript, csharp, python, cpp, java 36 | 37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 38 | # If this step fails, then you should remove it and run the build manually (see below) 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | # ℹ️ Command-line programs to run using the OS shell. 43 | # 📚 https://git.io/JvXDl 44 | 45 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 46 | # and modify them (or add more) to build your code if your project 47 | # uses a compiled language 48 | 49 | #- run: | 50 | # make bootstrap 51 | # make release 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v1 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | 5 | # Coverage directory used by tools like istanbul or nyc 6 | coverage 7 | .nyc_output 8 | 9 | # Dependency directories 10 | node_modules/ 11 | package-lock.json 12 | 13 | # Optional npm cache directory 14 | .npm 15 | 16 | # Optional eslint cache 17 | .eslintcache 18 | 19 | # Optional REPL history 20 | .node_repl_history 21 | 22 | # Output of 'npm pack' 23 | *.tgz 24 | 25 | # Yarn Integrity file 26 | .yarn-integrity 27 | 28 | # dotenv environment variables file 29 | .env 30 | .env.*.bat 31 | 32 | # Common toolchain intermediate files 33 | temp 34 | .DS_Store 35 | 36 | # Rush files 37 | common/temp/** 38 | package-deps.json 39 | .rush 40 | 41 | # Build output 42 | lib 43 | build 44 | *.tsbuildinfo 45 | output 46 | env_vars.* 47 | react-app-env.d.ts 48 | 49 | # misc 50 | *.TileCache* 51 | *.Tiles 52 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "search.exclude": { 4 | "**/node_modules": true, 5 | "**/.rush": true 6 | }, 7 | "files.exclude": { 8 | "**/.rush": true, 9 | "**/*.build.log": true, 10 | "**/*.build.error.log": true, 11 | }, 12 | "files.associations": { 13 | "*.snap": "javascript", 14 | ".nycrc": "json", 15 | "certa.json": "jsonc" 16 | }, 17 | "cSpell.words": [ 18 | "changesets" 19 | ], 20 | } -------------------------------------------------------------------------------- /Bentley-CLA.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/Bentley-CLA.pdf -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.9.0 4 | The default response type for OIDC clients has changed in this repo. It now defaults to `responseType: "code"`. To use an implicit grant type client, the `OidcFrontendClientConfiguration` will need to be adjusted. SPA clients created on the [iModel.js registration dashboard](https://imodeljs.github.io/iModelJs-docs-output/getting-started/registration-dashboard/) are now created with grant type authorization_code. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to iModel.js Samples 2 | 3 | Welcome, and thank you for your interest in contributing to the iModel.js samples! 4 | 5 | There are many ways to contribute. 6 | The goal of this document is to provide a high-level overview of how you can get involved. 7 | 8 | ## Asking Questions 9 | 10 | Have a question? 11 | Rather than opening an issue, please ask away on [Stack Overflow](https://stackoverflow.com/tags/imodeljs) using the tag `imodeljs`. 12 | 13 | The community will be eager to assist you. Your well-worded question will serve as a resource to others searching for help. 14 | 15 | ## Providing Feedback 16 | 17 | Your comments and feedback are welcome. For general comments or discussion please [click here](https://github.com/imodeljs/imodeljs-samples/labels/discussion) to contribute via GitHub issues using the `discussion` label. 18 | 19 | ## Reporting Issues 20 | 21 | Have you identified a reproducible problem in iModel.js? 22 | Have a feature request? 23 | We want to hear about it! 24 | Here's how you can make reporting your issue as effective as possible. 25 | 26 | ### Look For an Existing Issue 27 | 28 | Before you create a new issue, please do a search in [open issues](https://github.com/imodeljs/imodeljs-samples/issues) to see if the issue or feature request has already been filed. 29 | 30 | If you find that your issue already exists, please add relevant comments and your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). 31 | Use a reaction in place of a "+1" comment: 32 | 33 | * 👍 - upvote 34 | * 👎 - downvote 35 | 36 | If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below. 37 | 38 | ### Writing Good Bug Reports and Feature Requests 39 | 40 | File a single issue per problem and feature request. 41 | Do not enumerate multiple bugs or feature requests in the same issue. 42 | 43 | Do not add your issue as a comment to an existing issue unless it's for the identical input. 44 | Many issues look similar, but have different causes. 45 | 46 | The more information you can provide, the more likely someone will be successful reproducing the issue and finding a fix. 47 | 48 | Please include the following with each issue: 49 | 50 | * A short description of the issue that becomes the title 51 | * Versions of relevant iModel.js packages 52 | * Minimal steps to reproduce the issue or a code snippet that demonstrates the issue 53 | * What you expected to see, versus what you actually saw 54 | * Images that help explain the issue 55 | * Any relevant error messages, logs, or other details 56 | * Impact of the issue 57 | * Use the [`bug`](https://github.com/imodeljs/imodeljs-samples/labels/bug) or [`enhancement`](https://github.com/imodeljs/imodeljs-samples/labels/enhancement) label to identify the type of issue you are filing 58 | 59 | Don't feel bad if the developers can't reproduce the issue right away. 60 | They will simply ask for more information! 61 | 62 | ### Follow Your Issue 63 | 64 | You may be asked to clarify things or try different approaches, so please follow your issue and be responsive. 65 | 66 | ## Contributions 67 | 68 | We'd love to accept your contributions to iModel.js. 69 | There are just a few guidelines you need to follow. 70 | 71 | ### Contributor License Agreement (CLA) 72 | 73 | You must sign a [Contribution License Agreement with Bentley](Bentley-CLA.pdf) before your contributions will be accepted. 74 | This a one-time requirement for Bentley projects in GitHub. 75 | You can read more about [Contributor License Agreements](https://en.wikipedia.org/wiki/Contributor_License_Agreement) on Wikipedia. 76 | 77 | > Note: a CLA is not required if the change is trivial (such as fixing a spelling error or a typo). 78 | 79 | ### Pull Requests 80 | 81 | All submissions go through a review process. 82 | We use GitHub pull requests for this purpose. 83 | Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. 84 | 85 | ### Types of Contributions 86 | 87 | We welcome contributions, large or small, including: 88 | 89 | * Bug fixes 90 | * New features 91 | * Documentation corrections or additions 92 | * Example code snippets 93 | * Sample data 94 | 95 | Thank you for taking the time to contribute to open source and making great projects like iModel.js possible! 96 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2018-2020 Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /agent-app/query-agent/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/lib/Main.js", 12 | "args": [], 13 | }, 14 | { 15 | "type": "node", 16 | "request": "launch", 17 | "name": "Mocha All", 18 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 19 | "args": [ 20 | "-r", 21 | "ts-node/register", 22 | "--timeout", 23 | "999999", 24 | "--colors", 25 | "${workspaceFolder}/src/**/*test.ts", 26 | ], 27 | "console": "integratedTerminal", 28 | "internalConsoleOptions": "neverOpen", 29 | "protocol": "inspector" 30 | }, 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /agent-app/query-agent/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/node_modules": true, 4 | "**/lib": true 5 | }, 6 | "cSpell.words": [ 7 | "backend", 8 | "bbox", 9 | "bentleyjs", 10 | "ecschema", 11 | "imodel", 12 | "imodeljs", 13 | "undefine" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /agent-app/query-agent/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2018-2020 Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /agent-app/query-agent/README.md: -------------------------------------------------------------------------------- 1 | # imodel-query-agent 2 | 3 | Copyright © Bentley Systems, Incorporated. All rights reserved. 4 | 5 | imodel-query-agent is an example of an [agent](https://imodeljs.github.io/iModelJs-docs-output//learning/app/#imodel-agents) that illustrates use of the iModel.js API to listen and query changes made to iModels on the iModelHub. A separate optional [imodel-changeset-test-utility](../../tools/imodel-changeset-test-utility/README.md) can be used to generate sample change sets that can then be consumed by this sample. 6 | 7 | More specifically, this sample application demonstrates use of iModel.js API to: 8 | 9 | * Listen to 'Change Sets' posted to the iModels on the iModelHub. 10 | * Listen to 'Named Versions' created for iModels on the iModelHub. 11 | * Query and download these Change Sets from the iModelHub. 12 | * Parse the Change Sets to construct a 'Change Summary' of useful information contained in them. 13 | * Iterate and query the information contained in the Change Summary. 14 | 15 | Watch the console for various messages that show the progress: 16 | 17 | * Register event handlers that listen to Change Sets being posted to the iModelHub. 18 | * Register event handlers that listen to Named Versions created on the iModelHub. 19 | * Receive notification of a new Change Set posted to the iModelHub. 20 | * Receive notification of a new Named Version created on the iModelHub. 21 | * Extract Change Summary information from the Change Set. 22 | * Dump the contents of the Change Summary as a JSON file to disk. 23 | 24 | See http://imodeljs.org for comprehensive documentation on the iModel.js API and the various constructs used in this sample. 25 | 26 | ## Important note about registrations using iModel.js v0.191.0 and prior 27 | 28 | **The authorization method of agent clients has been changed in versions >=0.192.0. Therefore all registrations using agent clients created before 0.192.0 have been invalidated. To continue using your app with versions >=0.192.0 please create a new agent registration [here](https://imodeljs.github.io/iModelJs-docs-output/getting-started/registration-dashboard/). (i.e. You will need a client_id that starts with service-xxxxxx if using iModel.js versions >=0.192.0.) The new registration process is easy, fully automated, and can be completed in minutes.** 29 | 30 | ## Development Setup 31 | 32 | 1. Follow the [Agent Development Setup](../../README.md) section under Sample Agent Apps to configure, install dependencies, build, and run the app. 33 | 34 | 2. (Optional) Follow the Development Setup process to setup the [imodel-changeset-test-utility](../../tools/imodel-changeset-test-utility/README.md). The utility can be used to generate and push sample change sets to the iModelHub that can then be consumed by this sample. 35 | 36 | ## Run Query Agent 37 | 38 | 1. Start this sample application with `npm start`. 39 | 40 | 2. (Optional) Immediately start the imodel-changeset-test-utility to generate and push change sets by following the procedure documented in its [README](../../tools/imodel-changeset-test-utility/README.md). 41 | 42 | ## Run automated tests 43 | 44 | The sample includes some tests to validate its behavior - these are useful for internal testing: 45 | 46 | * Use `npm test` to run unit tests 47 | * Use `npm run test:integration` to run integration tests 48 | 49 | ## Debugging 50 | 51 | To debug the Query Agent Code click the launch configuration `Attach to Main.js` in the debug menu for VS code. Then in your terminal run `npm run start:debug` and wait for breakpoints to be hit. 52 | 53 | ## Contributing 54 | 55 | [Contributing to iModel.js](https://github.com/imodeljs/imodeljs/blob/master/CONTRIBUTING.md) 56 | -------------------------------------------------------------------------------- /agent-app/query-agent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imodel-query-agent", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Demonstrates using iModel.js to create an agent that queries changes to an iModel", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc 1>&2", 9 | "clean": "rimraf lib tsconfig.tsbuildinfo .rush", 10 | "lint": "eslint ./src/**/*.ts 1>&2", 11 | "test": "betools test --grep \"#integration\" --invert", 12 | "test:integration": "betools test --timeout 240000 --grep \"#integration\"", 13 | "cover": "nyc npm test", 14 | "start:debug": "node --inspect=12345 ./lib/Main.js", 15 | "start": "node ./lib/Main.js" 16 | }, 17 | "author": { 18 | "name": "Bentley Systems, Inc.", 19 | "url": "http://www.bentley.com" 20 | }, 21 | "dependencies": { 22 | "@bentley/bentleyjs-core": "^2.11.0", 23 | "@bentley/context-registry-client": "^2.11.0", 24 | "@bentley/geometry-core": "^2.11.0", 25 | "@bentley/imodelhub-client": "^2.11.0", 26 | "@bentley/imodeljs-backend": "^2.11.0", 27 | "@bentley/backend-itwin-client": "^2.11.0", 28 | "@bentley/imodeljs-common": "^2.11.0", 29 | "@bentley/itwin-client": "^2.11.0", 30 | "@bentley/telemetry-client": "^2.11.0", 31 | "path": "^0.12.7" 32 | }, 33 | "devDependencies": { 34 | "@bentley/build-tools": "^2.11.0", 35 | "@types/body-parser": "^1.17.0", 36 | "@types/chai": "^4.1.7", 37 | "@types/mocha": "^5.2.5", 38 | "@types/node": "^10.5.7", 39 | "chai": "^4.2.0", 40 | "debug": "^3.1.0", 41 | "imodel-changeset-test-utility": "0.0.57", 42 | "mocha": "^5.2.0", 43 | "npm-run-all": "^4.1.5", 44 | "nyc": "^14.0.0", 45 | "rimraf": "^2.6.2", 46 | "eslint": "^6.8.0", 47 | "typemoq": "^2.1.0", 48 | "typescript": "~3.7.4" 49 | }, 50 | "nyc": { 51 | "nycrc-path": "./node_modules/@bentley/build-tools/.nycrc" 52 | }, 53 | "eslintConfig": { 54 | "extends": "./node_modules/@bentley/build-tools/.eslintrc.js", 55 | "parserOptions": { 56 | "project": [ 57 | "tsconfig.json" 58 | ] 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /agent-app/query-agent/src/BriefcaseProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { BriefcaseDb, BriefcaseManager } from "@bentley/imodeljs-backend"; 6 | import { IModelVersion, LocalBriefcaseProps } from "@bentley/imodeljs-common"; 7 | import { AuthorizedClientRequestContext } from "@bentley/itwin-client"; 8 | 9 | export class BriefcaseProvider { 10 | private _iModelDb?: BriefcaseDb; 11 | public async getBriefcase(requestContext: AuthorizedClientRequestContext, projectId: string, iModelId: string, changeSetId: string): Promise { 12 | requestContext.enter(); 13 | 14 | if (!this._iModelDb) { 15 | // Downloads and opens a new local briefcase of the iModel at the specified version 16 | const briefcaseProps: LocalBriefcaseProps = await BriefcaseManager.downloadBriefcase(requestContext, { contextId: projectId, iModelId, asOf: IModelVersion.asOfChangeSet(changeSetId).toJSON() }); 17 | requestContext.enter(); 18 | this._iModelDb = await BriefcaseDb.open(requestContext, { fileName: briefcaseProps.fileName, readonly: false }); 19 | } else { 20 | // Update the existing local briefcase of the iModel to the specified version 21 | await this._iModelDb.pullAndMergeChanges(requestContext, IModelVersion.asOfChangeSet(changeSetId)); 22 | } 23 | return this._iModelDb; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /agent-app/query-agent/src/Main.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { QueryAgentConfig } from "./QueryAgentConfig"; 6 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 7 | import { QueryAgent } from "./QueryAgent"; 8 | 9 | QueryAgentConfig.setupConfig(); 10 | 11 | Logger.initializeToConsole(); 12 | Logger.setLevelDefault(LogLevel.Error); 13 | Logger.setLevel(QueryAgentConfig.loggingCategory, LogLevel.Trace); 14 | 15 | (async () => { // eslint-disable-line @typescript-eslint/no-floating-promises 16 | try { 17 | const agent = new QueryAgent(); 18 | await agent.initialize(); 19 | await agent.run(QueryAgentConfig.listenTime); 20 | Logger.logTrace(QueryAgentConfig.loggingCategory, "Query Agent Web Server finished executing successfully."); 21 | } catch (error) { 22 | Logger.logError(QueryAgentConfig.loggingCategory, error); 23 | Logger.logTrace(QueryAgentConfig.loggingCategory, "Query Agent Web Server finished executing unsuccessfully."); 24 | process.exitCode = 1; 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /agent-app/query-agent/src/QueryAgentConfig.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /* eslint-disable @typescript-eslint/naming-convention */ 6 | 7 | import * as path from "path"; 8 | import { Config } from "@bentley/bentleyjs-core"; 9 | import { AgentAuthorizationClientConfiguration } from "@bentley/backend-itwin-client"; 10 | 11 | /** 12 | * Configuration for Query Agent: uses provided command if necessary first, second it will attempt to look 13 | * for the npm config generated environment variable, third it will use hard coded values. 14 | */ 15 | export class QueryAgentConfig { 16 | public static setupConfig() { 17 | Config.App.merge({ 18 | 19 | // ----------------------------------------------------------------------------------------------------------- 20 | // Client registration details (REQUIRED) 21 | // Must set these variables before testing - create a client registration using 22 | // the developer registration procedure here - https://git.io/fx8YP. 23 | // Note: These can be set in the environment also - e.g., "set ims_agent_client_id=agent_test_client" 24 | // ----------------------------------------------------------------------------------------------------------- 25 | // imjs_agent_client_id: "Set this to client id", 26 | // imjs_agent_client_secret: "Set this to the client secret", 27 | 28 | // ----------------------------------------------------------------------------------------------------------- 29 | // Test iModel (REQUIRED) 30 | // Must set these variables before testing - create a new project and iModel with the 31 | // developer registration procedure here - https://git.io/fx8YP 32 | // Note: This can be set in the environment also - e.g., "set imjs_agent_imodel_name=MyiModel" 33 | // ----------------------------------------------------------------------------------------------------------- 34 | // imjs_agent_imodel_name: "Set this to the name of the sample iModel", 35 | 36 | // ----------------------------------------------------------------------------------------------------------- 37 | // Other application settings (NOT REQUIRED) 38 | // Note: These can be set in the environment also - e.g., "set agent_app_port=3000" 39 | // ----------------------------------------------------------------------------------------------------------- 40 | // imjs_agent_project_name: "Set this to the name of the sample project", // In most cases, this will match the test iModel name 41 | agent_app_port: process.env.AGENT_APP_PORT || 3000, 42 | agent_app_listen_time: process.env.AGENT_APP_LISTEN_TIME || 40000, 43 | imjs_buddi_resolve_url_using_region: process.env.IMJS_BUDDI_RESOLVE_URL_USING_REGION, 44 | imjs_default_relying_party_uri: "https://connect-wsg20.bentley.com", 45 | }); 46 | } 47 | 48 | public static get iModelName(): string { 49 | return Config.App.getString("imjs_agent_imodel_name"); 50 | } 51 | 52 | public static get projectName(): string { 53 | return Config.App.getString("imjs_agent_project_name", QueryAgentConfig.iModelName); 54 | } 55 | 56 | public static get oidcAgentClientConfiguration(): AgentAuthorizationClientConfiguration { 57 | return { 58 | clientId: Config.App.getString("imjs_agent_client_id"), 59 | clientSecret: Config.App.getString("imjs_agent_client_secret"), 60 | scope: "urlps-third-party context-registry-service:read-only imodelhub", 61 | }; 62 | } 63 | 64 | public static get outputDir(): string { 65 | return path.join(__dirname, "output"); 66 | } 67 | 68 | public static get changeSummaryDir(): string { 69 | return path.join(QueryAgentConfig.outputDir, "changeSummaries"); 70 | } 71 | 72 | public static get port(): number { 73 | return Config.App.getNumber("agent_app_port"); 74 | } 75 | 76 | public static get listenTime(): number { 77 | return Config.App.getNumber("agent_app_listen_time"); 78 | } 79 | 80 | public static get loggingCategory(): string { 81 | return "imodel-query-agent"; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /agent-app/query-agent/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "baseUrl": "./node_modules/", 7 | "outDir": "./lib", 8 | "rootDir": "./src", 9 | "skipLibCheck": true 10 | }, 11 | "include": [ 12 | "./src/**/*.ts" 13 | ], 14 | "exclude": [ 15 | "lib", 16 | "node_modules" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /common/config/azure-pipelines/ci-build.yaml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | jobs: 5 | - job: 6 | condition: succeeded() 7 | strategy: 8 | maxParallel: 3 9 | matrix: 10 | 'Windows': 11 | os: vs2017-win2016 12 | 'Linux': 13 | os: ubuntu-18.04 14 | 'Mac': 15 | os: macOS-latest 16 | pool: 17 | vmImage: $(os) 18 | steps: 19 | - checkout: self 20 | clean: all 21 | - task: NodeTool@0 22 | displayName: 'Install Node 12.x' 23 | inputs: 24 | versionSpec: 12.x 25 | checkLatest: true 26 | 27 | - script: node ./common/scripts/install-run-rush.js check 28 | displayName: Rush check 29 | - script: node ./common/scripts/install-run-rush.js install 30 | displayName: Rush install 31 | - script: node ./common/scripts/install-run-rush.js lint 32 | displayName: Rush lint 33 | - script: node ./common/scripts/install-run-rush.js build -v 34 | displayName: Rush build 35 | - script: node ./common/scripts/install-run-rush.js test -v 36 | displayName: Rush test 37 | -------------------------------------------------------------------------------- /common/config/azure-pipelines/integration-build.yaml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | jobs: 5 | - job: 6 | condition: succeeded() 7 | strategy: 8 | maxParallel: 1 9 | matrix: 10 | 'GP-Backend - MacOS': 11 | imjs_backend: 1 12 | os: macOS-latest 13 | 'Local-Backend - MacOS': 14 | imjs_backend: 0 15 | os: macOS-latest 16 | 17 | pool: 18 | vmImage: $(os) 19 | steps: 20 | - checkout: self 21 | clean: all 22 | - task: NodeTool@0 23 | displayName: 'Install Node 12.x' 24 | inputs: 25 | versionSpec: 12.x 26 | checkLatest: true 27 | - script: node ./common/scripts/install-run-rush.js install 28 | displayName: Rush install 29 | - script: node ./common/scripts/install-run-rush.js build -v --to simple-viewer-app 30 | displayName: Rush build - Simple Viewer 31 | - script: node ./common/scripts/install-run-rush.js build -v --to imodel-query-agent 32 | displayName: Rush build - iModel Query Agent 33 | - script: node ./common/scripts/install-run-rush.js build -v --to imodel-changeset-test-utility 34 | displayName: Rush build - iModel Changeset Utility 35 | - script: node ./common/scripts/install-run-rush.js test:integration -v 36 | displayName: Rush Integration Test 37 | -------------------------------------------------------------------------------- /common/config/rush/.npmrc: -------------------------------------------------------------------------------- 1 | # Rush uses this file to configure the package registry, regardless of whether the 2 | # package manager is PNPM, NPM, or Yarn. Prior to invoking the package manager, 3 | # Rush will always copy this file to the folder where installation is performed. 4 | # When NPM is the package manager, Rush works around NPM's processing of 5 | # undefined environment variables by deleting any lines that reference undefined 6 | # environment variables. 7 | # 8 | # DO NOT SPECIFY AUTHENTICATION CREDENTIALS IN THIS FILE. It should only be used 9 | # to configure registry sources. 10 | 11 | registry=https://registry.npmjs.org/ 12 | @bentley:registry=https://registry.npmjs.org/ 13 | always-auth=true 14 | -------------------------------------------------------------------------------- /common/config/rush/command-line.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", 3 | "commands": [ 4 | { 5 | "name": "clean", 6 | "commandKind": "bulk", 7 | "summary": "Cleans build output within each package", 8 | "description": "Iterates through each package in the monorepo and runs the 'clean' script", 9 | "enableParallelism": true, 10 | "ignoreMissingScript": false 11 | }, 12 | { 13 | "name": "lint", 14 | "commandKind": "bulk", 15 | "summary": "Run lint rules on each package", 16 | "description": "Iterates through each package in the monorepo and runs the 'lint' script", 17 | "enableParallelism": true, 18 | "ignoreMissingScript": false, 19 | "allowWarningsInSuccessfulBuild": true 20 | }, 21 | { 22 | "name": "test", 23 | "commandKind": "bulk", 24 | "summary": "Run test script for each package", 25 | "description": "Iterates through each package in the monorepo and runs the 'test' script", 26 | "enableParallelism": true, 27 | "ignoreMissingScript": false, 28 | "allowWarningsInSuccessfulBuild": true 29 | }, 30 | { 31 | "name": "test:integration", 32 | "commandKind": "bulk", 33 | "summary": "Run integration tests for each package", 34 | "description": "Iterates through each package in the monorepo and runs the 'test:integration' script", 35 | "enableParallelism": true, 36 | "ignoreMissingScript": true, 37 | "allowWarningsInSuccessfulBuild": true 38 | }, 39 | ] 40 | } -------------------------------------------------------------------------------- /common/scripts/install-run-rush.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 3 | // See the @microsoft/rush package's LICENSE file for license information. 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. 6 | // 7 | // This script is intended for usage in an automated build environment where the Rush command may not have 8 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush 9 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to it. 10 | // An example usage would be: 11 | // 12 | // node common/scripts/install-run-rush.js install 13 | // 14 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ 15 | const path = require("path"); 16 | const fs = require("fs"); 17 | const install_run_1 = require("./install-run"); 18 | const PACKAGE_NAME = '@microsoft/rush'; 19 | const RUSH_PREVIEW_VERSION = 'RUSH_PREVIEW_VERSION'; 20 | function _getRushVersion() { 21 | const rushPreviewVersion = process.env[RUSH_PREVIEW_VERSION]; 22 | if (rushPreviewVersion !== undefined) { 23 | console.log(`Using Rush version from environment variable ${RUSH_PREVIEW_VERSION}=${rushPreviewVersion}`); 24 | return rushPreviewVersion; 25 | } 26 | const rushJsonFolder = install_run_1.findRushJsonFolder(); 27 | const rushJsonPath = path.join(rushJsonFolder, install_run_1.RUSH_JSON_FILENAME); 28 | try { 29 | const rushJsonContents = fs.readFileSync(rushJsonPath, 'utf-8'); 30 | // Use a regular expression to parse out the rushVersion value because rush.json supports comments, 31 | // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script. 32 | const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/); 33 | return rushJsonMatches[1]; 34 | } 35 | catch (e) { 36 | throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` + 37 | "The 'rushVersion' field is either not assigned in rush.json or was specified " + 38 | 'using an unexpected syntax.'); 39 | } 40 | } 41 | function _run() { 42 | const [nodePath /* Ex: /bin/node */, scriptPath /* /repo/common/scripts/install-run-rush.js */, ...packageBinArgs /* [build, --to, myproject] */] = process.argv; 43 | // Detect if this script was directly invoked, or if the install-run-rushx script was invokved to select the 44 | // appropriate binary inside the rush package to run 45 | const scriptName = path.basename(scriptPath); 46 | const bin = scriptName.toLowerCase() === 'install-run-rushx.js' ? 'rushx' : 'rush'; 47 | if (!nodePath || !scriptPath) { 48 | throw new Error('Unexpected exception: could not detect node path or script path'); 49 | } 50 | if (process.argv.length < 3) { 51 | console.log(`Usage: ${scriptName} [args...]`); 52 | if (scriptName === 'install-run-rush.js') { 53 | console.log(`Example: ${scriptName} build --to myproject`); 54 | } 55 | else { 56 | console.log(`Example: ${scriptName} custom-command`); 57 | } 58 | process.exit(1); 59 | } 60 | install_run_1.runWithErrorAndStatusCode(() => { 61 | const version = _getRushVersion(); 62 | console.log(`The rush.json configuration requests Rush version ${version}`); 63 | return install_run_1.installAndRun(PACKAGE_NAME, version, bin, packageBinArgs); 64 | }); 65 | } 66 | _run(); 67 | //# sourceMappingURL=install-run-rush.js.map -------------------------------------------------------------------------------- /common/scripts/install-run-rushx.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 3 | // See the @microsoft/rush package's LICENSE file for license information. 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. 6 | // 7 | // This script is intended for usage in an automated build environment where the Rush command may not have 8 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush 9 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the 10 | // rushx command. 11 | // 12 | // An example usage would be: 13 | // 14 | // node common/scripts/install-run-rushx.js custom-command 15 | // 16 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ 17 | require("./install-run-rush"); 18 | //# sourceMappingURL=install-run-rushx.js.map -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/.env.local: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------------------------------------- 2 | # Project and iModel (REQUIRED) 3 | # Must un-comment the lines below and set these variables before testing - create a new project and 4 | # iModel with the developer registration procedure here - https://git.io/fx8YP 5 | # ----------------------------------------------------------------------------------------------------------- 6 | 7 | # Set this to the name of the sample iModel 8 | imjs_test_imodel= 9 | 10 | # (Optional) Set this to the name of the sample project. Defaults to name of the iModel. 11 | # imjs_test_project= 12 | 13 | # (Optional) Un-comment to use general-purpose-backend. Default value is 0 = local backend 14 | # imjs_backend=1 15 | 16 | # Set this to the registered clientId 17 | imjs_browser_test_client_id=imodeljs-spa-samples-2686 18 | 19 | # Set this to be the registered redirect URI 20 | imjs_browser_test_redirect_uri="http://localhost:3000/signin-callback.html" 21 | 22 | # Set this to be the scopes of services the application needs to access 23 | imjs_browser_test_scope="openid email profile organization imodelhub context-registry-service:read-only product-settings-service general-purpose-imodeljs-backend imodeljs-router urlps-third-party" 24 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2018-2020 Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/README.md: -------------------------------------------------------------------------------- 1 | # Basic Viewport App 2 | 3 | Copyright © Bentley Systems, Incorporated. All rights reserved. 4 | 5 | An iModel.js sample application that demonstrates the minimum setup for opening an iModel and viewing its graphics in a viewport with basic viewing tools. 6 | 7 | * _Viewport_: Renders geometric data onto an HTMLCanvasElement. 8 | * _Toolbar_: Includes basic viewport tools in top-right corner of viewport (select, fit, rotate, pan, zoom). 9 | 10 | This app serves as a guide on how you can embed one or more of these components into your own application. 11 | See http://imodeljs.org for comprehensive documentation on the iModel.js API and the various constructs used in this sample. 12 | 13 | ## Purpose 14 | 15 | The purpose of this application is to demonstrate the following: 16 | 17 | * [Dependencies](./package.json) required for iModel.js-based frontend applications. 18 | * [Scripts](./package.json) recommended to build and run iModel.js-based applications. 19 | * How to set up a simple [backend for web](./src/backend/BackendServer.ts) and 20 | * How to set up a simple [frontend for web](./src/frontend/api/BasicViewportApp.ts). 21 | * How to obtain an [access token](https://www.imodeljs.org/learning/common/accesstoken/) used to access iModelHub and other services. 22 | * How to [consume](./src/frontend/components/App.tsx) iModel.js React components. 23 | * How to [setup a viewport](./src/frontend/components/App.tsx#L106). 24 | * How to include 25 | [tools](./src/frontend/components/Toolbar.tsx) in a 26 | [viewport](./src/frontend/components/App.tsx#L205). 27 | 28 | ## Development Setup 29 | 30 | Follow the [App Development Setup](../../README.md) section under Sample Interactive Apps to configure, install dependencies, build, and run the app. 31 | 32 | ## Contributing 33 | 34 | [Contributing to iModel.js](https://github.com/imodeljs/imodeljs/blob/master/CONTRIBUTING.md) 35 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-viewport-app", 3 | "description": "Basic Viewport App", 4 | "license": "MIT", 5 | "author": { 6 | "name": "Bentley Systems, Inc.", 7 | "url": "http://www.bentley.com" 8 | }, 9 | "os": [ 10 | "win32", 11 | "linux", 12 | "darwin" 13 | ], 14 | "version": "0.0.0", 15 | "private": true, 16 | "scripts": { 17 | "build": "npm run build:backend & npm run build:frontend", 18 | "build:frontend": "cross-env GENERATE_SOURCEMAP=false USE_FAST_SASS=true react-scripts build", 19 | "build:backend": "tsc -p tsconfig.backend.json", 20 | "clean": "rimraf build lib .rush", 21 | "lint": "eslint --config package.json --no-eslintrc ./src/**/*.{ts,tsx} 1>&2", 22 | "start": "npm run start:servers", 23 | "start:webserver": "cross-env USE_FAST_SASS=true react-scripts start", 24 | "start:backend": "node lib/backend/main.js", 25 | "start:servers": "run-p \"start:webserver\" \"start:backend\"", 26 | "test": "" 27 | }, 28 | "dependencies": { 29 | "@bentley/backend-itwin-client": "^2.11.0", 30 | "@bentley/bentleyjs-core": "^2.11.0", 31 | "@bentley/context-registry-client": "^2.11.0", 32 | "@bentley/express-server": "^2.11.0", 33 | "@bentley/frontend-authorization-client": "^2.11.0", 34 | "@bentley/geometry-core": "^2.11.0", 35 | "@bentley/icons-generic-webfont": "^1.0.0", 36 | "@bentley/imodelhub-client": "^2.11.0", 37 | "@bentley/imodeljs-backend": "^2.11.0", 38 | "@bentley/imodeljs-common": "^2.11.0", 39 | "@bentley/imodeljs-frontend": "^2.11.0", 40 | "@bentley/imodeljs-i18n": "^2.11.0", 41 | "@bentley/imodeljs-quantity": "^2.11.0", 42 | "@bentley/itwin-client": "^2.11.0", 43 | "@bentley/rbac-client": "^2.11.0", 44 | "@bentley/ui-abstract": "^2.11.0", 45 | "@bentley/ui-components": "^2.11.0", 46 | "@bentley/ui-core": "^2.11.0", 47 | "@bentley/telemetry-client": "^2.11.0", 48 | "react": "^16.13.0", 49 | "react-dom": "^16.13.0" 50 | }, 51 | "devDependencies": { 52 | "@bentley/build-tools": "^2.11.0", 53 | "@bentley/ecschema-metadata": "^2.11.0", 54 | "@bentley/orbitgt-core": "^2.11.0", 55 | "@bentley/product-settings-client": "^2.11.0", 56 | "@bentley/react-scripts": "3.4.1", 57 | "@bentley/webgl-compatibility": "^2.11.0", 58 | "@types/react": "^16.9.0", 59 | "@types/react-dom": "^16.9.0", 60 | "cross-env": "^5.1.4", 61 | "node-sass": "^4.0.0", 62 | "npm-run-all": "^4.1.5", 63 | "rimraf": "^2.6.2", 64 | "eslint": "^6.8.0", 65 | "typescript": "~3.7.4", 66 | "webpack": "4.42.0" 67 | }, 68 | "homepage": "http://localhost:3000/", 69 | "browserslist": [ 70 | "electron 6.0.0", 71 | "last 4 chrome version", 72 | "last 4 firefox version", 73 | "last 4 safari version", 74 | "last 4 ios version", 75 | "last 4 ChromeAndroid version", 76 | "last 4 edge version", 77 | "not dead", 78 | "not <0.2%" 79 | ], 80 | "eslintConfig": { 81 | "extends": "./node_modules/@bentley/build-tools/.eslintrc.js", 82 | "parserOptions": { 83 | "project": [ 84 | "tsconfig.json", 85 | "tsconfig.backend.json" 86 | ] 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/public/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/basic-viewport-app/public/appicon.ico -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/basic-viewport-app/public/favicon.ico -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Basic Viewport App 11 | 14 | 15 | 16 | 17 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "basic-viewport-app", 3 | "name": "Basic Viewport App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/backend/main.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 6 | import { IModelJsExpressServer } from "@bentley/express-server"; 7 | import { IModelHost } from "@bentley/imodeljs-backend"; 8 | import { BentleyCloudRpcManager } from "@bentley/imodeljs-common"; 9 | 10 | import { getSupportedRpcs } from "../common/rpcs"; 11 | 12 | // Setup logging immediately to pick up any logging during IModelHost.startup() 13 | Logger.initializeToConsole(); 14 | Logger.setLevelDefault(LogLevel.Warning); 15 | Logger.setLevel("basic-viewport-app", LogLevel.Info); 16 | 17 | (async () => { // eslint-disable-line @typescript-eslint/no-floating-promises 18 | try { 19 | // Initialize iModelHost 20 | await IModelHost.startup(); 21 | 22 | // Get RPCs supported by this backend 23 | const rpcs = getSupportedRpcs(); 24 | 25 | // Setup the RPC interfaces and the backend metadata with the BentleyCloudRpcManager 26 | const rpcConfig = BentleyCloudRpcManager.initializeImpl({ info: { title: "basic-viewport-app", version: "v1.0" } }, rpcs); 27 | 28 | // Initialize Web Server backend 29 | const port = Number(process.env.PORT || 3001); 30 | const server = new IModelJsExpressServer(rpcConfig.protocol); 31 | await server.initialize(port); 32 | Logger.logInfo("basic-viewport-app", `RPC backend for basic-viewport-app listening on port ${port}`); 33 | } catch (error) { 34 | Logger.logError("basic-viewport-app", error); 35 | process.exitCode = 1; 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/common/rpcs.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelReadRpcInterface, IModelTileRpcInterface, RpcInterfaceDefinition } from "@bentley/imodeljs-common"; 6 | 7 | /** 8 | * Returns a list of RPCs supported by this application 9 | */ 10 | export function getSupportedRpcs(): RpcInterfaceDefinition[] { 11 | return [ 12 | IModelReadRpcInterface, 13 | IModelTileRpcInterface, 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/frontend/api/BasicViewportApp.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { ClientRequestContext, Config } from "@bentley/bentleyjs-core"; 6 | import { BrowserAuthorizationCallbackHandler, BrowserAuthorizationClient, BrowserAuthorizationClientConfiguration } from "@bentley/frontend-authorization-client"; 7 | import { BentleyCloudRpcManager, BentleyCloudRpcParams } from "@bentley/imodeljs-common"; 8 | import { FrontendRequestContext, IModelApp } from "@bentley/imodeljs-frontend"; 9 | import { UrlDiscoveryClient } from "@bentley/itwin-client"; 10 | import { UiComponents } from "@bentley/ui-components"; 11 | import { getSupportedRpcs } from "../../common/rpcs"; 12 | 13 | /** 14 | * List of possible backends that basic-viewport-app can use 15 | */ 16 | export enum UseBackend { 17 | /** Use local basic-viewport-app backend */ 18 | Local = 0, 19 | 20 | /** Use deployed general-purpose backend */ 21 | GeneralPurpose = 1, 22 | } 23 | 24 | // Boiler plate code 25 | export class BasicViewportApp { 26 | 27 | public static get oidcClient() { return IModelApp.authorizationClient as BrowserAuthorizationClient; } 28 | 29 | public static async startup() { 30 | await IModelApp.startup({ applicationVersion: "1.0.0" }); 31 | 32 | // initialize OIDC 33 | await BasicViewportApp.initializeOidc(); 34 | 35 | // contains various initialization promises which need 36 | // to be fulfilled before the app is ready 37 | const initPromises = new Array>(); 38 | 39 | // initialize RPC communication 40 | initPromises.push(BasicViewportApp.initializeRpc()); 41 | 42 | // initialize UiComponents 43 | initPromises.push(UiComponents.initialize(IModelApp.i18n)); 44 | 45 | // the app is ready when all initialization promises are fulfilled 46 | await Promise.all(initPromises); 47 | } 48 | 49 | private static async initializeRpc(): Promise { 50 | let rpcParams = await this.getConnectionInfo(); 51 | const rpcInterfaces = getSupportedRpcs(); 52 | // Initialize the local backend if UseBackend.GeneralPurpose is not set. 53 | if (!rpcParams) 54 | rpcParams = { info: { title: "basic-viewport-app", version: "v1.0" }, uriPrefix: "http://localhost:3001" }; 55 | BentleyCloudRpcManager.initializeClient(rpcParams, rpcInterfaces); 56 | } 57 | 58 | private static async initializeOidc() { 59 | const clientId = Config.App.getString("imjs_browser_test_client_id"); 60 | const redirectUri = Config.App.getString("imjs_browser_test_redirect_uri"); 61 | const scope = Config.App.getString("imjs_browser_test_scope"); 62 | const responseType = "code"; 63 | const oidcConfig: BrowserAuthorizationClientConfiguration = { clientId, redirectUri, scope, responseType }; 64 | 65 | await BrowserAuthorizationCallbackHandler.handleSigninCallback(oidcConfig.redirectUri); 66 | IModelApp.authorizationClient = new BrowserAuthorizationClient(oidcConfig); 67 | 68 | try { 69 | await BasicViewportApp.oidcClient.signInSilent(new ClientRequestContext()); 70 | } catch (err) { } 71 | } 72 | 73 | private static async getConnectionInfo(): Promise { 74 | const usedBackend = Config.App.getNumber("imjs_backend", UseBackend.Local); 75 | 76 | if (usedBackend === UseBackend.GeneralPurpose) { 77 | const urlClient = new UrlDiscoveryClient(); 78 | const requestContext = new FrontendRequestContext(); 79 | const orchestratorUrl = await urlClient.discoverUrl(requestContext, "iModelJsOrchestrator.K8S", undefined); 80 | return { info: { title: "general-purpose-imodeljs-backend", version: "v2.0" }, uriPrefix: orchestratorUrl }; 81 | } 82 | 83 | if (usedBackend === UseBackend.Local) 84 | return undefined; 85 | 86 | throw new Error(`Invalid backend "${usedBackend}" specified in configuration`); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/frontend/components/App.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | .app { 7 | display: flex; 8 | flex-flow: column; 9 | height: 100vh; 10 | overflow: hidden; 11 | } 12 | 13 | .app .button-open-imodel { 14 | position: absolute; 15 | width: 200px; 16 | text-align: center; 17 | top: 40%; 18 | left: 50%; 19 | margin-left: -100px; 20 | margin-right: 100px; 21 | padding-left: 0; 22 | padding-right: 0; 23 | } 24 | 25 | .app .button-signout { 26 | position: absolute; 27 | width: 200px; 28 | text-align: center; 29 | top: 50%; 30 | left: 50%; 31 | margin-left: -100px; 32 | margin-right: 100px; 33 | padding-left: 0; 34 | padding-right: 0; 35 | } 36 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/frontend/components/Components.scss: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | .toolbar { 7 | float: right; 8 | background-origin: border-box; 9 | top: 3%; 10 | right: 1%; 11 | position: absolute; 12 | -webkit-transition: all 500ms ease; 13 | -o-transition: all 500ms ease; 14 | transition: all 500ms ease; 15 | cursor: pointer; 16 | }; 17 | 18 | .toolbar a { 19 | padding: 12px; 20 | color: black; 21 | text-decoration: none; 22 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.22))); 23 | background-color: #f9f9f9; 24 | border: 1px #4d575f solid; 25 | } 26 | 27 | .toolbar a:hover { 28 | color: #0072b8 29 | } 30 | 31 | .icon { 32 | background: none; 33 | } 34 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/frontend/components/Toolbar.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { FitViewTool, IModelApp, PanViewTool, RotateViewTool, SelectionTool, ZoomViewTool } from "@bentley/imodeljs-frontend"; 7 | import * as React from "react"; 8 | import "./Components.scss"; 9 | 10 | /** Toolbar containing simple navigation tools */ 11 | const toolbar = () => { 12 | return ( 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | }; 22 | 23 | /** 24 | * See the https://imodeljs.org/learning/frontend/tools/ 25 | * for more details and available tools. 26 | */ 27 | 28 | const select = () => { 29 | IModelApp.tools.run(SelectionTool.toolId); 30 | }; 31 | 32 | const fitView = () => { 33 | IModelApp.tools.run(FitViewTool.toolId, IModelApp.viewManager.selectedView); 34 | }; 35 | 36 | const rotate = () => { 37 | IModelApp.tools.run(RotateViewTool.toolId, IModelApp.viewManager.selectedView); 38 | }; 39 | 40 | const pan = () => { 41 | IModelApp.tools.run(PanViewTool.toolId, IModelApp.viewManager.selectedView); 42 | }; 43 | 44 | const zoom = () => { 45 | IModelApp.tools.run(ZoomViewTool.toolId, IModelApp.viewManager.selectedView); 46 | }; 47 | 48 | export default toolbar; 49 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/frontend/index.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | font-family: sans-serif; 10 | } 11 | 12 | h3 { 13 | margin-bottom: 0.3em; 14 | } 15 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/frontend/index.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import * as ReactDOM from "react-dom"; 7 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 8 | import { BasicViewportApp } from "./api/BasicViewportApp"; 9 | import App from "./components/App"; 10 | import "./index.css"; 11 | 12 | // Setup logging immediately to pick up any logging during BasicViewportApp.startup() 13 | Logger.initializeToConsole(); 14 | Logger.setLevelDefault(LogLevel.Warning); // Set all logging to a default of Warning 15 | Logger.setLevel("basic-viewport-app", LogLevel.Info); // Override the above default and set only App level logging to Info. 16 | 17 | (async () => { // eslint-disable-line @typescript-eslint/no-floating-promises 18 | // initialize the application 19 | await BasicViewportApp.startup(); 20 | 21 | // when initialization is complete, render 22 | ReactDOM.render( 23 | , 24 | document.getElementById("root") as HTMLElement, 25 | ); 26 | })(); 27 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/src/index.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // react-scripts requires an index.ts file in this location. Using it to import the code 7 | // from the frontend. 8 | 9 | import "./frontend/index"; 10 | -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/tsconfig.backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "outDir": "./lib" 7 | }, 8 | "include": [ 9 | "./src/backend/*.ts", 10 | "./src/common/*.ts" 11 | ], 12 | "exclude": [ 13 | "lib", 14 | "node_modules" 15 | ] 16 | } -------------------------------------------------------------------------------- /interactive-app/basic-viewport-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "resolveJsonModule": true, 6 | "baseUrl": "./node_modules", 7 | "outDir": "./lib", 8 | "allowJs": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "types": [ 16 | "@bentley/react-scripts" 17 | ] 18 | }, 19 | "include": [ 20 | "./**/*.ts", 21 | "./**/*.tsx" 22 | ], 23 | "exclude": [ 24 | "lib", 25 | "node_modules", 26 | "test" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/.env.local: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------------------------------------- 2 | # Project and iModel (REQUIRED) 3 | # Must un-comment the lines below and set these variables before testing - create a new project and 4 | # iModel with the developer registration procedure here - https://git.io/fx8YP 5 | # ----------------------------------------------------------------------------------------------------------- 6 | 7 | # Set this to the name of the sample iModel 8 | imjs_test_imodel= 9 | 10 | # (Optional) Set this to the name of the sample project. Defaults to name of the iModel. 11 | # imjs_test_project= 12 | 13 | # (Optional) Un-comment to use general-purpose-backend. Default value is 0 = local backend 14 | # imjs_backend=1 15 | 16 | # ----------------------------------------------------------------------------------------------------------- 17 | # Client registration (RECOMMENDED but OPTIONAL) 18 | # Must set these variables before deployment, but the supplied defaults can be used for testing on localhost. 19 | # Create a client registration using the procedure here - https://git.io/fx8YP (Developer registration). For the purpose 20 | # of running this sample on localhost, ensure your registration includes http://localhost:3000/signin-oidc as a 21 | # valid redirect URI. 22 | # ----------------------------------------------------------------------------------------------------------- 23 | 24 | # Set this to the registered clientId 25 | # Note: "imodeljs-spa-samples-2686" is setup to work with the (default) localhost redirect URI below 26 | imjs_browser_test_client_id="imodeljs-spa-samples-2686" 27 | 28 | # Use this client id when running electron app 29 | imjs_electron_test_client_id="imodeljs-electron-samples" 30 | 31 | # Set this to be the registered redirect URI 32 | # Note: "http://localhost:3000/signin-callback" is setup to work with the (default) web clientId above 33 | imjs_browser_test_redirect_uri="http://localhost:3000/signin-callback.html" 34 | 35 | # Set this to be the registered post signout redirect URI 36 | imjs_browser_test_post_signout_redirect_uri="http://localhost:3000/" 37 | 38 | # This redirect uri is set up to be used with the electron clientId above 39 | imjs_electron_test_redirect_uri="http://localhost:3000/signin-callback" 40 | 41 | # Set this to be the scopes of services the application needs to access 42 | # Note: The default value set above ensures the minimal working of the application 43 | imjs_browser_test_scope="openid email profile organization imodelhub context-registry-service:read-only product-settings-service general-purpose-imodeljs-backend imodeljs-router urlps-third-party" 44 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 6 | "eg2.tslint", 7 | "msjsdiag.debugger-for-chrome" 8 | ] 9 | } -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "compounds": [ 7 | { 8 | "name": "Web: All", 9 | "configurations": [ 10 | "Web: Server (backend)", 11 | "Web: Chrome (frontend)" 12 | ] 13 | }, 14 | { 15 | "name": "Electron: All", 16 | "configurations": [ 17 | "Electron: Main (backend)", 18 | "Electron: Renderer (frontend)" 19 | ] 20 | } 21 | ], 22 | "configurations": [ 23 | { 24 | "type": "node", 25 | "request": "launch", 26 | "name": "Web: Server (backend)", 27 | "protocol": "inspector", 28 | "runtimeExecutable": "node", 29 | "program": "${workspaceFolder}/node_modules/nodemon/bin/nodemon.js", 30 | "cwd": "${workspaceFolder}", 31 | "args": [ 32 | "--inspect=41016", 33 | "--watch", 34 | "${workspaceFolder}/lib/main.js", 35 | "${workspaceFolder}/lib/main.js", 36 | ], 37 | "port": 41016, 38 | "showAsyncStacks": true, 39 | "smartStep": true, 40 | "restart": true 41 | }, 42 | { 43 | "name": "Web: Chrome (frontend)", 44 | "type": "chrome", 45 | "request": "launch", 46 | "url": "http://localhost:3000/", 47 | "webRoot": "${workspaceFolder}/src", 48 | "sourceMapPathOverrides": { 49 | "webpack:///src/*": "${webRoot}/*" 50 | }, 51 | "smartStep": true 52 | }, 53 | { 54 | "type": "node", 55 | "request": "launch", 56 | "name": "Electron: Main (backend)", 57 | "protocol": "inspector", 58 | "runtimeExecutable": "node", 59 | "program": "${workspaceFolder}/node_modules/nodemon/bin/nodemon.js", 60 | "cwd": "${workspaceFolder}", 61 | "args": [ 62 | "--watch", 63 | "${workspaceFolder}/lib/main.js", 64 | "${workspaceFolder}/node_modules/electron/cli.js", 65 | "${workspaceFolder}/lib/main.js", 66 | "--remote-debugging-port=9223", 67 | "--inspect-brk=41016", 68 | ], 69 | "smartStep": true, 70 | "restart": true, 71 | "port": 41016 72 | }, 73 | { 74 | "name": "Electron: Renderer (frontend)", 75 | "type": "chrome", 76 | "request": "attach", 77 | "port": 9223, 78 | "url": "http://localhost:3000", 79 | "webRoot": "${workspaceFolder}/src", 80 | "sourceMapPathOverrides": { 81 | "webpack:///src/*": "${webRoot}/*" 82 | }, 83 | "smartStep": true 84 | }, 85 | { 86 | "type": "node", 87 | "request": "launch", 88 | "name": "iModelJs Backend", 89 | "program": "${workspaceFolder}/node_modules/@bentley/webpack-tools/bin/bentley-webpack-tools.js", 90 | "cwd": "${workspaceFolder}", 91 | "args": [ 92 | "start", 93 | "--debug=9229" 94 | ], 95 | "port": 9229, 96 | "protocol": "inspector", 97 | "outputCapture": "std", 98 | "timeout": 100000, 99 | } 100 | ] 101 | } -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.detectIndentation": false, 4 | "editor.insertSpaces": true, 5 | "editor.tabSize": 2, 6 | "editor.trimAutoWhitespace": true, 7 | "files.exclude": { 8 | "**/lib/**": false, 9 | "**/node_modules/**": false, 10 | "**/*.TileCache*": true, 11 | }, 12 | "files.insertFinalNewline": true, 13 | "files.trimFinalNewlines": true, 14 | "files.trimTrailingWhitespace": true, 15 | "cSpell.words": [ 16 | "BACKSTAGESHOW", 17 | "backstagehide", 18 | "imodels" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2018-2020 Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/README.md: -------------------------------------------------------------------------------- 1 | # 9-Zone Sample App 2 | 3 | Copyright © Bentley Systems, Inc. 2019 4 | 5 | This iModel.js sample application uses the Bentley 9-Zone UI layout pattern and demonstrates opening an iModel and viewing its data. 6 | For an overview of the 9-Zone UI layout pattern, see [9-Zone UI Pattern](https://imodeljs.github.io/iModelJs-docs-output//learning/ui/ninezone/). 7 | 8 | The user interface is structured using nine zones located in the corners and along the edges of the screen. 9 | Each zone contains one or more widgets. The entire screen contains the main content. There may be multiple content views in this area 10 | separated by splitters. 11 | 12 | - The zone in the top left contains tool buttons and a Backstage button. 13 | - The zone in the top center contains tool settings. 14 | - The zone in the top right contains navigation buttons and a 3d cube navigation aid. 15 | - The zone in the center right contains the _Tree_ component. 16 | - The zone in the bottom right contains the _Property Grid_ component. 17 | - The zone across the bottom contains a status bar. 18 | - The main content contains two views: the _Viewport_ component rendering geometric data, and the _Table_ component. 19 | 20 | The data is presented using the following components: 21 | 22 | - _Viewport_: Renders geometric data onto an HTMLCanvasElement. 23 | - _Tree_: Displays a hierarchical view of iModel contents. 24 | - _Property Grid_: Displays properties of selected element(s). 25 | - _Table_: Displays element properties in a tabular format. 26 | 27 | There is also a panel on the right edge that is empty initially. The widgets in the center right and bottom right zones can be dragged to this panel by 28 | dragging their tabs. The widgets can also be dragged back to the zones. 29 | 30 | A second frontstage may be activated from the backstage (opened by pressing the Home button). This frontstage displays two viewports side-by-side and uses panels to display the 3 data components. The view layout may also be switched to display a single viewport by clicking the button below the Home button. 31 | 32 | This app serves as a guide on how you can develop your own 9-zone application. 33 | See http://imodeljs.org for comprehensive documentation on the iModel.js API and the various constructs used in this sample. 34 | 35 | ![](./docs/header.png) 36 | 37 | ![](./docs/panels.png) 38 | 39 | ## Development setup 40 | 41 | Follow the [App Development Setup](../../README.md) section under Sample Interactive Apps to configure, install dependencies, build, and run the app. 42 | 43 | ## Purpose 44 | 45 | The purpose of this application is to demonstrate the following: 46 | 47 | - [Dependencies](./package.json) required for iModel.js-based frontend applications. 48 | - [Scripts](./package.json) recommended to build and run iModel.js-based applications. 49 | - How to set up a simple backend for 50 | [web](./src/backend/web/BackendServer.ts) and 51 | [electron](./src/backend/electron/main.ts). 52 | - How to set up a simple [frontend for web and electron](./src/frontend/app/NineZoneSampleApp.ts). 53 | - How to implement unified selection between a 54 | [viewport](./src/frontend/components/Viewport.tsx), 55 | [tree](./src/frontend/components/Tree.tsx), 56 | [property grid](./src/frontend/components/Properties.tsx) and a 57 | [table](./src/frontend/components/Table.tsx). 58 | - How to implement a [Frontstage with multiple Zones and Toolbars](./src/frontend/app-ui/frontstages/SampleFrontstage.tsx), 59 | Widgets ( 60 | [Property Grid](./src/frontend/app-ui/widgets/PropertyGridWidget.tsx), 61 | [Tree](./src/frontend/app-ui/widgets/TreeWidget.tsx) 62 | ), Content views ( 63 | [Table](./src/frontend/app-ui/contentviews/TableContent.tsx), 64 | Viewport 65 | ), a [Status Bar](./src/frontend/app-ui/statusbars/AppStatusBar.tsx) 66 | and a [Backstage](./src/frontend/app-ui/backstage/AppBackstageItemProvider.tsx). 67 | - How to implement a [Frontstage with multiple Viewports](./src/frontend/app-ui/frontstages/SampleFrontstage2.tsx) 68 | 69 | For more information about developing a 9-Zone app, see [Learning ui-framework Package](https://imodeljs.github.io/iModelJs-docs-output//learning/ui/framework/). 70 | 71 | ## Contributing 72 | 73 | [Contributing to iModel.js](https://github.com/imodeljs/imodeljs/blob/master/CONTRIBUTING.md) 74 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/docs/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/ninezone-sample-app/docs/header.png -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/docs/panels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/ninezone-sample-app/docs/panels.png -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ninezone-sample-app", 3 | "description": "9-Zone Sample App", 4 | "license": "MIT", 5 | "author": { 6 | "name": "Bentley Systems, Inc.", 7 | "url": "http://www.bentley.com" 8 | }, 9 | "os": [ 10 | "win32", 11 | "linux", 12 | "darwin" 13 | ], 14 | "engines": { 15 | "node": ">=10.11.0 <13.0" 16 | }, 17 | "version": "0.0.0", 18 | "private": true, 19 | "scripts": { 20 | "build": "npm run build:backend & npm run build:frontend", 21 | "build:frontend": "cross-env GENERATE_SOURCEMAP=false USE_FAST_SASS=true react-scripts build", 22 | "build:backend": "tsc -p tsconfig.backend.json", 23 | "clean": "rimraf lib build .rush", 24 | "lint": "eslint --config package.json --no-eslintrc ./src/**/*.{ts,tsx} 1>&2", 25 | "electron": "cross-env NODE_ENV=development electron lib/backend/electronmain.js", 26 | "start": "npm run start:servers", 27 | "start:webserver": "cross-env USE_FAST_SASS=true react-scripts start", 28 | "start:backend": "node lib/backend/webmain.js", 29 | "start:servers": "run-p \"start:webserver\" \"start:backend\"", 30 | "test": "" 31 | }, 32 | "dependencies": { 33 | "@bentley/bentleyjs-core": "^2.11.0", 34 | "@bentley/context-registry-client": "^2.11.0", 35 | "@bentley/ecschema-metadata": "^2.11.0", 36 | "@bentley/electron-manager": "^2.11.0", 37 | "@bentley/express-server": "^2.11.0", 38 | "@bentley/frontend-authorization-client": "^2.11.0", 39 | "@bentley/geometry-core": "^2.11.0", 40 | "@bentley/icons-generic-webfont": "^1.0.0", 41 | "@bentley/imodelhub-client": "^2.11.0", 42 | "@bentley/imodeljs-backend": "^2.11.0", 43 | "@bentley/backend-itwin-client": "^2.11.0", 44 | "@bentley/imodeljs-common": "^2.11.0", 45 | "@bentley/imodeljs-frontend": "^2.11.0", 46 | "@bentley/imodeljs-i18n": "^2.11.0", 47 | "@bentley/imodeljs-quantity": "^2.11.0", 48 | "@bentley/imodeljs-markup": "^2.11.0", 49 | "@bentley/itwin-client": "^2.11.0", 50 | "@bentley/presentation-backend": "^2.11.0", 51 | "@bentley/presentation-common": "^2.11.0", 52 | "@bentley/presentation-components": "^2.11.0", 53 | "@bentley/presentation-frontend": "^2.11.0", 54 | "@bentley/rbac-client": "^2.11.0", 55 | "@bentley/ui-abstract": "^2.11.0", 56 | "@bentley/ui-components": "^2.11.0", 57 | "@bentley/ui-core": "^2.11.0", 58 | "@bentley/ui-framework": "^2.11.0", 59 | "@bentley/ui-ninezone": "^2.11.0", 60 | "@bentley/telemetry-client": "^2.11.0", 61 | "electron": "^8.2.1", 62 | "electron-devtools-installer": "^2.2.3", 63 | "react": "^16.13.0", 64 | "react-dom": "^16.13.0", 65 | "react-redux": "^7.2.0", 66 | "react-resize-detector": "^3.4.0", 67 | "redux": "^4.0.0" 68 | }, 69 | "devDependencies": { 70 | "@bentley/build-tools": "^2.11.0", 71 | "@bentley/ecschema-metadata": "^2.11.0", 72 | "@bentley/orbitgt-core": "^2.11.0", 73 | "@bentley/product-settings-client": "^2.11.0", 74 | "@bentley/react-scripts": "3.4.1", 75 | "@bentley/webgl-compatibility": "^2.11.0", 76 | "@types/electron-devtools-installer": "^2.2.0", 77 | "@types/react": "^16.9.0", 78 | "@types/react-dom": "^16.9.0", 79 | "@types/react-redux": "^6.0.14", 80 | "@types/react-resize-detector": "^3.1.0", 81 | "cross-env": "^5.1.4", 82 | "node-sass": "^4.0.0", 83 | "npm-run-all": "^4.1.5", 84 | "rimraf": "^2.6.2", 85 | "eslint": "^6.8.0", 86 | "typescript": "~3.7.4", 87 | "webpack": "4.42.0" 88 | }, 89 | "homepage": "http://localhost:3000/", 90 | "browserslist": [ 91 | "electron 6.0.0", 92 | "last 4 chrome version", 93 | "last 4 firefox version", 94 | "last 4 safari version", 95 | "last 4 ios version", 96 | "last 4 ChromeAndroid version", 97 | "last 4 edge version", 98 | "not dead", 99 | "not <0.2%" 100 | ], 101 | "eslintConfig": { 102 | "extends": "./node_modules/@bentley/build-tools/.eslintrc.js", 103 | "parserOptions": { 104 | "project": [ 105 | "tsconfig.json", 106 | "tsconfig.backend.json" 107 | ] 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/public/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/ninezone-sample-app/public/appicon.ico -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/ninezone-sample-app/public/favicon.ico -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 9-Zone Sample App 16 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 39 |
40 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/public/locales/en/NineZoneSample.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome-message": "Welcome to 9-Zone Sample App", 3 | "signing-in": "Signing-in", 4 | "components": { 5 | "imodel-picker": { 6 | "open-imodel": "Open Sample iModel", 7 | "signout": "Sign Out" 8 | }, 9 | "properties": "Properties", 10 | "table": "Table", 11 | "tree": "Tree" 12 | }, 13 | "buttons": { 14 | "switchLayouts": "Switch Layouts", 15 | "switchToLayout1": "One graphics view", 16 | "switchToLayout2": "Two graphics views" 17 | }, 18 | "no-content": "No data for this content view", 19 | "backstage": { 20 | "sampleFrontstage": "Single View Frontstage", 21 | "sampleFrontstage2": "Multiple Views Frontstage" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "ninezone-sample-app", 3 | "name": "9-Zone Sample App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/backend/electronmain.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as path from "path"; 6 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 7 | import { ElectronManagerOptions, IModelJsElectronManager, WebpackDevServerElectronManager } from "@bentley/electron-manager"; 8 | import { IModelHost } from "@bentley/imodeljs-backend"; 9 | import { ElectronRpcManager } from "@bentley/imodeljs-common"; 10 | import { Presentation } from "@bentley/presentation-backend"; 11 | import { AppLoggerCategory } from "../common/LoggerCategory"; 12 | import { getSupportedRpcs } from "../common/rpcs"; 13 | 14 | // Setup logging immediately to pick up any logging during IModelHost.startup() 15 | Logger.initializeToConsole(); 16 | Logger.setLevelDefault(LogLevel.Warning); 17 | Logger.setLevel(AppLoggerCategory.Backend, LogLevel.Trace); 18 | 19 | /** 20 | * Initializes Electron backend 21 | */ 22 | const electronMain = async () => { 23 | 24 | // Initialize iModelHost 25 | await IModelHost.startup(); 26 | 27 | // Initialize Presentation 28 | Presentation.initialize(); 29 | 30 | // Get RPCs supported by this backend 31 | const rpcs = getSupportedRpcs(); 32 | 33 | // tell ElectronRpcManager which RPC interfaces to handle 34 | ElectronRpcManager.initializeImpl({}, rpcs); 35 | 36 | const opts: ElectronManagerOptions = { 37 | webResourcesPath: path.join(__dirname, "..", "..", "..", "build"), 38 | }; 39 | const manager = (process.env.NODE_ENV === "development") ? new WebpackDevServerElectronManager(opts) : new IModelJsElectronManager(opts); 40 | await manager.initialize({ 41 | width: 1280, 42 | height: 800, 43 | autoHideMenuBar: true, 44 | show: false, 45 | }); 46 | 47 | if (manager.mainWindow) { 48 | manager.mainWindow.show(); 49 | } 50 | 51 | }; 52 | 53 | try {// execute this immediately when we load 54 | electronMain(); // eslint-disable-line @typescript-eslint/no-floating-promises 55 | } catch (error) { 56 | Logger.logError(AppLoggerCategory.Backend, error); 57 | process.exitCode = 1; 58 | } 59 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/backend/webmain.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelJsExpressServer } from "@bentley/express-server"; 6 | import { BentleyCloudRpcManager } from "@bentley/imodeljs-common"; 7 | import { AppLoggerCategory } from "../common/LoggerCategory"; 8 | import { Logger } from "@bentley/bentleyjs-core"; 9 | import { IModelHost } from "@bentley/imodeljs-backend"; 10 | import { Presentation } from "@bentley/presentation-backend"; 11 | 12 | import { getSupportedRpcs } from "../common/rpcs"; 13 | 14 | /** 15 | * Initializes Web Server backend 16 | */ 17 | // function called when we start the backend webserver 18 | const webMain = async () => { // tell BentleyCloudRpcManager which RPC interfaces to handle 19 | try { 20 | // Initialize iModelHost 21 | await IModelHost.startup(); 22 | 23 | // Initialize Presentation 24 | Presentation.initialize(); 25 | // Get RPCs supported by this backend 26 | const rpcs = getSupportedRpcs(); 27 | 28 | const rpcConfig = BentleyCloudRpcManager.initializeImpl({ info: { title: "ninezone-sample-app", version: "v1.0" } }, rpcs); 29 | 30 | const port = Number(process.env.PORT || 3001); 31 | const server = new IModelJsExpressServer(rpcConfig.protocol); 32 | await server.initialize(port); 33 | Logger.logInfo(AppLoggerCategory.Backend, `RPC backend for ninezone-sample-app listening on port ${port}`); 34 | } catch (error) { 35 | Logger.logError(AppLoggerCategory.Backend, error); 36 | process.exitCode = 1; 37 | } 38 | }; 39 | 40 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 41 | webMain(); 42 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/common/LoggerCategory.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** List of LoggerCategories for this app. For more details on Logging Categories, check out the [Category](https://www.imodeljs.org/learning/common/logging/#categories) documentation. */ 7 | export enum AppLoggerCategory { 8 | Frontend = "ninezone-sample-app.Frontend", 9 | Backend = "ninezone-sample-app.Backend", 10 | } 11 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/common/rpcs.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelReadRpcInterface, IModelTileRpcInterface, RpcInterfaceDefinition, SnapshotIModelRpcInterface } from "@bentley/imodeljs-common"; 6 | import { PresentationRpcInterface } from "@bentley/presentation-common"; 7 | 8 | /** 9 | * Returns a list of RPCs supported by this application 10 | */ 11 | export function getSupportedRpcs(): RpcInterfaceDefinition[] { 12 | return [ 13 | IModelReadRpcInterface, 14 | IModelTileRpcInterface, 15 | PresentationRpcInterface, 16 | SnapshotIModelRpcInterface, 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/api/rpc.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { BentleyCloudRpcManager, BentleyCloudRpcParams, ElectronRpcConfiguration, ElectronRpcManager, RpcConfiguration } from "@bentley/imodeljs-common"; 6 | import { getSupportedRpcs } from "../../common/rpcs"; 7 | 8 | /** 9 | * Initializes RPC communication based on the platform 10 | */ 11 | export function initRpc(rpcParams?: BentleyCloudRpcParams): RpcConfiguration { 12 | let config: RpcConfiguration; 13 | const rpcInterfaces = getSupportedRpcs(); 14 | if (ElectronRpcConfiguration.isElectron) { 15 | // initializes RPC for Electron 16 | config = ElectronRpcManager.initializeClient({}, rpcInterfaces); 17 | } else { 18 | // initialize RPC for web apps 19 | if (!rpcParams) 20 | rpcParams = { info: { title: "ninezone-sample-app", version: "v1.0" }, uriPrefix: "http://localhost:3001" }; 21 | config = BentleyCloudRpcManager.initializeClient(rpcParams, rpcInterfaces); 22 | } 23 | return config; 24 | } 25 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/AppUi.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelApp, IModelConnection, ViewState } from "@bentley/imodeljs-frontend"; 6 | import { Presentation } from "@bentley/presentation-frontend/lib/presentation-frontend/Presentation"; 7 | import { BackstageManager, CommandItemDef, ConfigurableUiManager, FrontstageManager, SyncUiEventDispatcher, UiFramework } from "@bentley/ui-framework"; 8 | import { SampleFrontstage } from "./frontstages/SampleFrontstage"; 9 | import { SampleFrontstage2 } from "./frontstages/SampleFrontstage2"; 10 | 11 | /** 12 | * Example Ui Configuration for an iModel.js App 13 | */ 14 | export class AppUi { 15 | 16 | // Initialize the ConfigurableUiManager 17 | public static async initialize() { 18 | // initialize UiFramework 19 | await UiFramework.initialize(undefined); 20 | 21 | // initialize Presentation 22 | await Presentation.initialize({ activeLocale: IModelApp.i18n.languageList()[0] }); 23 | 24 | ConfigurableUiManager.initialize(); 25 | } 26 | 27 | // Command that toggles the backstage 28 | public static get backstageToggleCommand(): CommandItemDef { 29 | return BackstageManager.getBackstageToggleCommand(); 30 | } 31 | 32 | /** Handle when an iModel and the views have been selected */ 33 | public static handleIModelViewsSelected(iModelConnection: IModelConnection, viewStates: ViewState[]): void { 34 | // Set the iModelConnection in the Redux store 35 | UiFramework.setIModelConnection(iModelConnection); 36 | UiFramework.setDefaultViewState(viewStates[0]); 37 | 38 | // Tell the SyncUiEventDispatcher about the iModelConnection 39 | SyncUiEventDispatcher.initializeConnectionEvents(iModelConnection); 40 | 41 | // We create a FrontStage that contains the views that we want. 42 | const frontstageProvider = new SampleFrontstage(viewStates); 43 | FrontstageManager.addFrontstageProvider(frontstageProvider); 44 | 45 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 46 | FrontstageManager.setActiveFrontstageDef(frontstageProvider.frontstageDef).then(() => { 47 | // Frontstage is ready 48 | }); 49 | 50 | // We create a FrontStage that contains the views that we want. 51 | const frontstageProvider2 = new SampleFrontstage2(viewStates); 52 | FrontstageManager.addFrontstageProvider(frontstageProvider2); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/backstage/AppBackstageComposer.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { UserInfo } from "@bentley/itwin-client"; 6 | import { BackstageComposer, UserProfileBackstageItem } from "@bentley/ui-framework"; 7 | import * as React from "react"; 8 | import { connect } from "react-redux"; 9 | import { RootState } from "../../app/NineZoneSampleApp"; 10 | import { AppBackstageItemProvider } from "./AppBackstageItemProvider"; 11 | 12 | function mapStateToProps(state: RootState) { 13 | const frameworkState = state.frameworkState; 14 | 15 | if (!frameworkState) 16 | return undefined; 17 | 18 | return { userInfo: frameworkState.sessionState.userInfo }; 19 | } 20 | 21 | interface AppBackstageComposerProps { 22 | /** AccessToken from sign-in */ 23 | userInfo: UserInfo | undefined; 24 | } 25 | 26 | export class AppBackstageComposerComponent extends React.PureComponent { 27 | private _itemsProvider = new AppBackstageItemProvider(); 28 | public render() { 29 | return ( 30 | } 32 | items={[...this._itemsProvider.backstageItems]} 33 | /> 34 | ); 35 | } 36 | } 37 | 38 | export const AppBackstageComposer = connect(mapStateToProps)(AppBackstageComposerComponent); // eslint-disable-line @typescript-eslint/naming-convention 39 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/backstage/AppBackstageItemProvider.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { IModelApp } from "@bentley/imodeljs-frontend"; 7 | import { BackstageItem, BackstageItemUtilities } from "@bentley/ui-abstract"; 8 | 9 | export class AppBackstageItemProvider { 10 | /** id of provider */ 11 | public readonly id = "ninezone-sample-app.AppBackstageItemProvider"; 12 | 13 | private _backstageItems: ReadonlyArray | undefined = undefined; 14 | 15 | public get backstageItems(): ReadonlyArray { 16 | if (!this._backstageItems) { 17 | this._backstageItems = [ 18 | BackstageItemUtilities.createStageLauncher("SampleFrontstage", 100, 10, 19 | IModelApp.i18n.translate("NineZoneSample:backstage.sampleFrontstage"), undefined, "icon-placeholder"), 20 | BackstageItemUtilities.createStageLauncher("SampleFrontstage2", 100, 20, 21 | IModelApp.i18n.translate("NineZoneSample:backstage.sampleFrontstage2"), undefined, "icon-placeholder"), 22 | ]; 23 | } 24 | return this._backstageItems; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/contentviews/TableContent.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | 7 | import { 8 | ConfigurableCreateInfo, 9 | ContentControl, 10 | } from "@bentley/ui-framework"; 11 | 12 | import SimpleTableComponent from "../../components/Table"; 13 | 14 | /** 15 | * Table content 16 | */ 17 | export class TableContent extends ContentControl { 18 | constructor(info: ConfigurableCreateInfo, options: any) { 19 | super(info, options); 20 | 21 | if (options.iModelConnection) { 22 | this.reactNode = ; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/statusbars/AppStatusBar.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { ActivityCenterField, MessageCenterField, StatusBarWidgetControl, StatusBarWidgetControlArgs, ToolAssistanceField } from "@bentley/ui-framework"; 6 | import * as React from "react"; 7 | 8 | /** 9 | * Status Bar example widget 10 | */ 11 | export class AppStatusBarWidget extends StatusBarWidgetControl { 12 | public getReactNode(controlArgs: StatusBarWidgetControlArgs): React.ReactNode { 13 | const { isInFooterMode, onOpenWidget, openWidget, toastTargetRef } = controlArgs; 14 | 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/widgets/PropertyGridWidget.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Orientation } from "@bentley/ui-core"; 6 | import { ConfigurableCreateInfo, WidgetControl } from "@bentley/ui-framework"; 7 | import * as React from "react"; 8 | import SimplePropertiesComponent from "../../components/Properties"; 9 | 10 | /** A widget control for displaying the PropertyGrid React component */ 11 | export class PropertyGridWidget extends WidgetControl { 12 | constructor(info: ConfigurableCreateInfo, options: any) { 13 | super(info, options); 14 | 15 | if (options.iModelConnection) { 16 | const orientation = Orientation.Vertical; 17 | this.reactNode = ; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/widgets/TableWidget.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { ConfigurableCreateInfo, WidgetControl } from "@bentley/ui-framework"; 6 | import * as React from "react"; 7 | import SimpleTableComponent from "../../components/Table"; 8 | 9 | /** A widget control for displaying the Table React component */ 10 | export class TableWidget extends WidgetControl { 11 | constructor(info: ConfigurableCreateInfo, options: any) { 12 | super(info, options); 13 | 14 | if (options.iModelConnection) { 15 | this.reactNode = ; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/app-ui/widgets/TreeWidget.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { ConfigurableCreateInfo, WidgetControl } from "@bentley/ui-framework"; 6 | import * as React from "react"; 7 | import SimpleTreeComponent from "../../components/Tree"; 8 | 9 | /** A widget control for displaying the Tree React component */ 10 | export class TreeWidget extends WidgetControl { 11 | constructor(info: ConfigurableCreateInfo, options: any) { 12 | super(info, options); 13 | 14 | if (options.iModelConnection) { 15 | this.reactNode = ; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/App.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | .App { 7 | display: flex; 8 | flex-flow: column; 9 | height: 100vh; 10 | overflow: hidden; 11 | } 12 | 13 | .App .Header { 14 | background-color: #222; 15 | color: white; 16 | padding-left: 20px; 17 | } 18 | 19 | .App .button-open-imodel { 20 | position: absolute; 21 | width: 200px; 22 | text-align: center; 23 | top: 40%; 24 | left: 50%; 25 | margin-left: -100px; 26 | margin-right: 100px; 27 | padding-left: 0; 28 | padding-right: 0; 29 | } 30 | 31 | .App .button-signout { 32 | position: absolute; 33 | width: 200px; 34 | text-align: center; 35 | top: 50%; 36 | left: 50%; 37 | margin-left: -100px; 38 | margin-right: 100px; 39 | padding-left: 0; 40 | padding-right: 0; 41 | } 42 | 43 | .App .Content { 44 | position: relative; 45 | height: 100%; 46 | width: 100%; 47 | } 48 | 49 | .App .Content .top-left { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | height: 70%; 54 | width: 70%; 55 | } 56 | 57 | .App .Content .bottom { 58 | position: absolute; 59 | bottom: 0; 60 | left: 0; 61 | height: 30%; 62 | width: 70%; 63 | } 64 | 65 | .App .Content .right { 66 | position: absolute; 67 | top: 0; 68 | right: 0; 69 | height: 100%; 70 | width: 30%; 71 | } 72 | 73 | .App .Content .right .top { 74 | position: absolute; 75 | top: 0; 76 | height: 50%; 77 | width: 100%; 78 | overflow: auto; 79 | } 80 | 81 | .App .Content .right .bottom { 82 | position: absolute; 83 | bottom: 0; 84 | height: 50%; 85 | width: 100%; 86 | overflow: auto; 87 | } 88 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/Properties.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import { IModelConnection } from "@bentley/imodeljs-frontend"; 7 | import { PresentationPropertyDataProvider, usePropertyDataProviderWithUnifiedSelection } from "@bentley/presentation-components"; 8 | import { FillCentered, Orientation, useDisposable } from "@bentley/ui-core"; 9 | import { VirtualizedPropertyGridWithDataProvider } from "@bentley/ui-components"; 10 | 11 | /** React properties for the property grid component */ 12 | export interface Props { 13 | /** iModel whose contents should be displayed in the property grid */ 14 | imodel: IModelConnection; 15 | /** Orientation of the PropertyGrid rows */ 16 | orientation: Orientation; 17 | } 18 | 19 | /** Property grid component for the viewer app */ 20 | export default function SimplePropertiesComponent(props: Props) { // eslint-disable-line @typescript-eslint/naming-convention 21 | const dataProvider = useDisposable(React.useCallback(() => new PresentationPropertyDataProvider({ imodel: props.imodel }), [props.imodel])); 22 | const { isOverLimit } = usePropertyDataProviderWithUnifiedSelection({ dataProvider }); 23 | let content: JSX.Element; 24 | if (isOverLimit) { 25 | content = ({"Too many elements."}); 26 | } else { 27 | content = (); 33 | } 34 | 35 | return ( 36 | content 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/Table.ruleset.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/@bentley/presentation-common/Ruleset.schema.json", 3 | "id": "ninezone-sample-app/Table", 4 | "rules": [ 5 | { 6 | "ruleType": "Content", 7 | "condition": "SelectedNode.IsOfClass(\"Model\", \"BisCore\")", 8 | "onlyIfNotHandled": true, 9 | "specifications": [ 10 | { 11 | "specType": "ContentRelatedInstances", 12 | "relationshipPaths": [ 13 | { 14 | "relationship": { 15 | "schemaName": "BisCore", 16 | "className": "ModelContainsElements" 17 | }, 18 | "direction": "Forward" 19 | } 20 | ] 21 | } 22 | ] 23 | }, 24 | { 25 | "ruleType": "Content", 26 | "condition": "SelectedNode.IsOfClass(\"Category\", \"BisCore\")", 27 | "onlyIfNotHandled": true, 28 | "specifications": [ 29 | { 30 | "specType": "ContentRelatedInstances", 31 | "relationshipPaths": [ 32 | { 33 | "relationship": { 34 | "schemaName": "BisCore", 35 | "className": "GeometricElement2dIsInCategory" 36 | }, 37 | "direction": "Backward" 38 | }, 39 | { 40 | "relationship": { 41 | "schemaName": "BisCore", 42 | "className": "GeometricElement3dIsInCategory" 43 | }, 44 | "direction": "Backward" 45 | } 46 | ] 47 | } 48 | ] 49 | }, 50 | { 51 | "ruleType": "Content", 52 | "condition": "SelectedNode.IsOfClass(\"Element\", \"BisCore\")", 53 | "onlyIfNotHandled": true, 54 | "specifications": [ 55 | { 56 | "specType": "ContentRelatedInstances", 57 | "relationshipPaths": [ 58 | { 59 | "relationship": { 60 | "schemaName": "BisCore", 61 | "className": "ElementOwnsChildElements" 62 | }, 63 | "direction": "Forward", 64 | "count": "*" 65 | } 66 | ] 67 | }, 68 | { 69 | "specType": "SelectedNodeInstances" 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/Table.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /* eslint-disable no-duplicate-imports */ 6 | import * as React from "react"; 7 | import { useCallback } from "react"; 8 | import { IModelConnection } from "@bentley/imodeljs-frontend"; 9 | import { PresentationTableDataProvider, tableWithUnifiedSelection } from "@bentley/presentation-components"; 10 | import { useDisposable } from "@bentley/ui-core"; 11 | import { Table } from "@bentley/ui-components"; 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-var-requires 14 | const RULESET_TABLE = require("./Table.ruleset.json"); 15 | 16 | // create a HOC table component that supports unified selection 17 | // eslint-disable-next-line @typescript-eslint/naming-convention 18 | const SimpleTable = tableWithUnifiedSelection(Table); 19 | 20 | /** React properties for the table component */ 21 | export interface Props { 22 | /** iModel whose contents should be displayed in the table */ 23 | imodel: IModelConnection; 24 | } 25 | 26 | /** Table component for the viewer app */ 27 | export default function SimpleTableComponent(props: Props) { // eslint-disable-line @typescript-eslint/naming-convention 28 | const dataProvider = useDisposable(useCallback(() => new PresentationTableDataProvider({ imodel: props.imodel, ruleset: RULESET_TABLE }), [props.imodel])); 29 | return ( 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/Tree.ruleset.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/@bentley/presentation-common/Ruleset.schema.json", 3 | "id": "ninezone-sample-app/Tree", 4 | "rules": [ 5 | { 6 | "ruleType": "RootNodes", 7 | "autoExpand": true, 8 | "specifications": [ 9 | { 10 | "specType": "InstanceNodesOfSpecificClasses", 11 | "classes": [ 12 | { 13 | "schemaName": "BisCore", 14 | "classNames": [ 15 | "Subject" 16 | ] 17 | } 18 | ], 19 | "instanceFilter": "this.Parent = NULL", 20 | "arePolymorphic": false, 21 | "groupByClass": false, 22 | "groupByLabel": false 23 | } 24 | ] 25 | }, 26 | { 27 | "ruleType": "ChildNodes", 28 | "condition": "ParentNode.IsOfClass(\"Subject\", \"BisCore\")", 29 | "onlyIfNotHandled": true, 30 | "specifications": [ 31 | { 32 | "specType": "RelatedInstanceNodes", 33 | "relationshipPaths": [ 34 | { 35 | "relationship": { 36 | "schemaName": "BisCore", 37 | "className": "SubjectOwnsSubjects" 38 | }, 39 | "direction": "Forward" 40 | } 41 | ], 42 | "groupByClass": false, 43 | "groupByLabel": false 44 | }, 45 | { 46 | "specType": "InstanceNodesOfSpecificClasses", 47 | "classes": { 48 | "schemaName": "BisCore", 49 | "classNames": [ 50 | "Model" 51 | ] 52 | }, 53 | "arePolymorphic": true, 54 | "relatedInstances": [ 55 | { 56 | "relationshipPath": { 57 | "relationship": { 58 | "schemaName": "BisCore", 59 | "className": "ModelModelsElement" 60 | }, 61 | "direction": "Forward", 62 | "targetClass": { 63 | "schemaName": "BisCore", 64 | "className": "InformationPartitionElement" 65 | } 66 | }, 67 | "alias": "partition", 68 | "isRequired": true 69 | } 70 | ], 71 | "instanceFilter": "partition.Parent.Id = parent.ECInstanceId AND NOT this.IsPrivate", 72 | "groupByClass": false, 73 | "groupByLabel": false 74 | } 75 | ] 76 | }, 77 | { 78 | "ruleType": "ChildNodes", 79 | "condition": "ParentNode.IsOfClass(\"Model\", \"BisCore\")", 80 | "onlyIfNotHandled": true, 81 | "specifications": [ 82 | { 83 | "specType": "RelatedInstanceNodes", 84 | "relationshipPaths": [ 85 | { 86 | "relationship": { 87 | "schemaName": "BisCore", 88 | "className": "ModelContainsElements" 89 | }, 90 | "direction": "Forward" 91 | } 92 | ], 93 | "instanceFilter": "this.Parent = NULL", 94 | "groupByClass": false, 95 | "groupByLabel": false 96 | } 97 | ] 98 | }, 99 | { 100 | "ruleType": "ChildNodes", 101 | "condition": "ParentNode.IsOfClass(\"Element\", \"BisCore\")", 102 | "onlyIfNotHandled": true, 103 | "specifications": [ 104 | { 105 | "specType": "RelatedInstanceNodes", 106 | "relationshipPaths": [ 107 | { 108 | "relationship": { 109 | "schemaName": "BisCore", 110 | "className": "ElementOwnsChildElements" 111 | }, 112 | "direction": "Forward" 113 | } 114 | ], 115 | "groupByClass": false, 116 | "groupByLabel": false 117 | } 118 | ] 119 | }, 120 | { 121 | "ruleType": "ImageIdOverride", 122 | "condition": "ThisNode.IsOfClass(\"Subject\", \"BisCore\")", 123 | "imageIdExpression": "IIF(this.Parent.Id = NULL, \"icon-imodel-hollow-2\", \"icon-folder\")" 124 | }, 125 | { 126 | "ruleType": "ImageIdOverride", 127 | "condition": "ThisNode.IsOfClass(\"Model\", \"BisCore\")", 128 | "imageIdExpression": "\"icon-model\"" 129 | }, 130 | { 131 | "ruleType": "ImageIdOverride", 132 | "condition": "ThisNode.IsOfClass(\"Element\", \"BisCore\")", 133 | "imageIdExpression": "\"icon-item\"" 134 | } 135 | ] 136 | } 137 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/Tree.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelConnection } from "@bentley/imodeljs-frontend"; 6 | import { usePresentationTreeNodeLoader, useUnifiedSelectionTreeEventHandler } from "@bentley/presentation-components"; 7 | import { ControlledTree, SelectionMode, useVisibleTreeNodes } from "@bentley/ui-components"; 8 | import * as React from "react"; 9 | const RULESET_TREE = require("./Tree.ruleset.json"); // eslint-disable-line @typescript-eslint/no-var-requires 10 | 11 | /** React properties for the tree component */ 12 | export interface Props { 13 | /** iModel whose contents should be displayed in the tree */ 14 | imodel: IModelConnection; 15 | } 16 | 17 | /** Tree component for the viewer app */ 18 | export default function SimpleTreeComponent(props: Props) { // eslint-disable-line @typescript-eslint/naming-convention 19 | const nodeLoader = usePresentationTreeNodeLoader({ imodel: props.imodel, ruleset: RULESET_TREE, pagingSize: 20 }); 20 | return ( 21 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/components/Viewport.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import { viewWithUnifiedSelection } from "@bentley/presentation-components"; 7 | import { ViewportComponent, ViewportProps } from "@bentley/ui-components"; 8 | 9 | // create a HOC viewport component that supports unified selection 10 | // eslint-disable-next-line @typescript-eslint/naming-convention 11 | const SimpleViewport = viewWithUnifiedSelection(ViewportComponent); 12 | 13 | /** Viewport component for the viewer app */ 14 | export default function SimpleViewportComponent(props: ViewportProps) { // eslint-disable-line @typescript-eslint/naming-convention 15 | return ( 16 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/index.scss: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | font-family: sans-serif; 10 | } 11 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/frontend/index.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import * as ReactDOM from "react-dom"; 7 | 8 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 9 | 10 | import { AppLoggerCategory } from "../common/LoggerCategory"; 11 | import { NineZoneSampleApp } from "./app/NineZoneSampleApp"; 12 | import { AppUi } from "./app-ui/AppUi"; 13 | import App from "./components/App"; 14 | import "./index.scss"; 15 | 16 | // Setup logging immediately to pick up any logging during NineZoneSampleApp.startup() 17 | Logger.initializeToConsole(); 18 | Logger.setLevelDefault(LogLevel.Warning); 19 | Logger.setLevel(AppLoggerCategory.Frontend, LogLevel.Info); 20 | 21 | (async () => { // eslint-disable-line @typescript-eslint/no-floating-promises 22 | // Start the app. 23 | await NineZoneSampleApp.startup(); 24 | 25 | // Initialize the AppUi & ConfigurableUiManager 26 | await AppUi.initialize(); 27 | 28 | // when initialization is complete, render 29 | ReactDOM.render( 30 | , 31 | document.getElementById("root") as HTMLElement, 32 | ); 33 | })(); 34 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/src/index.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // react-scripts requires an index.ts file in this location. Using it to import the code 7 | // from the frontend. 8 | 9 | import "./frontend/index"; 10 | -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/tsconfig.backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "outDir": "./lib" 7 | }, 8 | "include": [ 9 | "./src/backend/*.ts", 10 | "./src/common/*.ts" 11 | ], 12 | "exclude": [ 13 | "lib", 14 | "node_modules" 15 | ] 16 | } -------------------------------------------------------------------------------- /interactive-app/ninezone-sample-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "baseUrl": "./node_modules", 6 | "outDir": "./lib", 7 | "resolveJsonModule": true, 8 | "allowJs": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "types": [ 16 | "@bentley/react-scripts" 17 | ] 18 | }, 19 | "include": [ 20 | "./src/**/*.ts", 21 | "./src/**/*.tsx" 22 | ], 23 | "exclude": [ 24 | "lib", 25 | "node_modules" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/.env.local: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------------------------------------- 2 | # Project and iModel (REQUIRED) 3 | # Must un-comment the lines below and set these variables before testing - create a new project and 4 | # iModel with the developer registration procedure here - https://git.io/fx8YP 5 | # ----------------------------------------------------------------------------------------------------------- 6 | 7 | # Set this to the name of the sample iModel 8 | imjs_test_imodel= 9 | 10 | # (Optional) Set this to the name of the sample project. Defaults to name of the iModel. 11 | # imjs_test_project= 12 | 13 | # (Optional) Un-comment to use general-purpose-backend. Default value is 0 = local backend 14 | # imjs_backend=1 15 | 16 | # ----------------------------------------------------------------------------------------------------------- 17 | # Client registration (RECOMMENDED but OPTIONAL) 18 | # Must set these variables before deployment, but the supplied defaults can be used for testing on localhost. 19 | # Create a client registration using the procedure here - https://git.io/fx8YP (Developer registration). For the purpose 20 | # of running this sample on localhost, ensure your registration includes http://localhost:3000/signin-oidc as a 21 | # valid redirect URI. 22 | # ----------------------------------------------------------------------------------------------------------- 23 | 24 | # Set this to the registered clientId 25 | # Note: "imodeljs-spa-samples-2686" is setup to work with the (default) localhost redirect URI below 26 | imjs_browser_test_client_id="imodeljs-spa-samples-2686" 27 | 28 | # Use this client id when running electron app 29 | imjs_electron_test_client_id="imodeljs-electron-samples" 30 | 31 | # Set this to be the registered redirect URI 32 | # Note: "http://localhost:3000/signin-callback" is setup to work with the (default) web clientId above 33 | imjs_browser_test_redirect_uri="http://localhost:3000/signin-callback.html" 34 | 35 | # Set this to be the registered post signout redirect URI 36 | imjs_browser_test_post_signout_redirect_uri="http://localhost:3000/" 37 | 38 | # This redirect uri is set up to be used with the electron clientId above 39 | imjs_electron_test_redirect_uri="http://localhost:3000/signin-callback" 40 | 41 | # Set this to be the scopes of services the application needs to access 42 | # Note: The default value set above ensures the minimal working of the application 43 | imjs_browser_test_scope="openid email profile organization imodelhub context-registry-service:read-only product-settings-service general-purpose-imodeljs-backend imodeljs-router urlps-third-party" 44 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 6 | "ms-vscode.vscode-typescript-tslint-plugin", 7 | "msjsdiag.debugger-for-chrome" 8 | ] 9 | } -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "compounds": [ 7 | { 8 | "name": "Web: All", 9 | "configurations": [ 10 | "Web: Server (webserver)", 11 | "Web: Chrome (frontend)", 12 | ] 13 | }, 14 | { 15 | "name": "Electron: All", 16 | "configurations": [ 17 | "Electron: Main (backend)", 18 | "Electron: Renderer (frontend)" 19 | ] 20 | } 21 | ], 22 | "configurations": [ 23 | { 24 | "type": "node", 25 | "request": "launch", 26 | "name": "Web: Server (webserver)", 27 | "protocol": "inspector", 28 | "program": "${workspaceFolder}/node_modules/env-cmd/bin/env-cmd.js", 29 | "cwd": "${workspaceFolder}", 30 | "showAsyncStacks": true, 31 | "smartStep": true, 32 | "restart": true, 33 | "args": [ 34 | "./lib/webresources/config.json", 35 | "node", 36 | "${workspaceFolder}/node_modules/npm-run-all/bin/run-p/index.js", 37 | "start:webserver", 38 | "start:backend" 39 | ] 40 | }, 41 | { 42 | "type": "node", 43 | "request": "launch", 44 | "name": "Web: Server (backend)", 45 | "protocol": "inspector", 46 | "program": "${workspaceFolder}/lib/backend/main.js", 47 | "cwd": "${workspaceFolder}", 48 | "showAsyncStacks": true, 49 | "smartStep": true, 50 | "restart": true 51 | }, 52 | { 53 | "type": "node", 54 | "request": "launch", 55 | "name": "Web: Server (backend) for tests", 56 | "protocol": "inspector", 57 | "program": "${workspaceFolder}/lib/backend/main.js", 58 | "cwd": "${workspaceFolder}", 59 | "env": { 60 | "imjs_test_project": "Retail Building Sample", 61 | "imjs_test_imodel": "Retail Building Sample", 62 | }, 63 | "showAsyncStacks": true, 64 | "smartStep": true, 65 | "restart": true 66 | }, 67 | { 68 | "name": "Web: Chrome (frontend)", 69 | "type": "chrome", 70 | "request": "launch", 71 | "url": "http://localhost:3000/", 72 | "webRoot": "${workspaceFolder}/src", 73 | "sourceMapPathOverrides": { 74 | "webpack:///src/*": "${webRoot}/*" 75 | }, 76 | "smartStep": true, 77 | "sourceMaps": true 78 | }, 79 | { 80 | "type": "node", 81 | "request": "launch", 82 | "name": "Electron: Main (backend)", 83 | "protocol": "inspector", 84 | "cwd": "${workspaceFolder}", 85 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 86 | "windows": { 87 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 88 | }, 89 | "env": { 90 | "BROWSER": "none" 91 | }, 92 | "args": [ 93 | "lib/backend/main.js" 94 | ], 95 | }, 96 | { 97 | "name": "E2E Tests only", 98 | "type": "node", 99 | "request": "launch", 100 | "protocol": "inspector", 101 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 102 | "cwd": "${workspaceFolder}/test/end-to-end", 103 | "args": [ 104 | "--opts", 105 | "${workspaceFolder}/test/end-to-end/mocha.opts", 106 | "${workspaceFolder}/test/end-to-end/**/*.test.ts*" 107 | ] 108 | }, 109 | { 110 | "name": "Unit Tests only", 111 | "type": "node", 112 | "request": "launch", 113 | "protocol": "inspector", 114 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 115 | "cwd": "${workspaceFolder}", 116 | "args": [ 117 | "--opts", 118 | "${workspaceFolder}/test/unit/mocha.opts", 119 | "${workspaceFolder}/test/unit/**/*.test.ts*" 120 | ] 121 | }, 122 | { 123 | "name": "Electron test", 124 | "type": "node", 125 | "request": "launch", 126 | "protocol": "inspector", 127 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 128 | "cwd": "${workspaceFolder}", 129 | "args": [ 130 | "--opts", 131 | "${workspaceFolder}/test/electron/mocha.opts", 132 | "${workspaceFolder}/test/electron/**/*.test.ts*" 133 | ], 134 | "env": { 135 | "NODE_ENV": "test", 136 | } 137 | } 138 | ] 139 | } 140 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.detectIndentation": false, 4 | "editor.insertSpaces": true, 5 | "editor.tabSize": 2, 6 | "editor.trimAutoWhitespace": true, 7 | "files.associations": { 8 | "*.snap": "javascript" 9 | }, 10 | "files.exclude": { 11 | "**/*.TileCache*": true, 12 | }, 13 | "files.insertFinalNewline": true, 14 | "files.trimFinalNewlines": true, 15 | "files.trimTrailingWhitespace": true 16 | } 17 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2018-2020 Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/README.md: -------------------------------------------------------------------------------- 1 | # Simple Viewer App 2 | 3 | Copyright © Bentley Systems, Incorporated. All rights reserved. 4 | 5 | An iModel.js sample application that demonstrates opening an iModel and viewing its data. The data is presented using the following components: 6 | 7 | * _Viewport_: Renders geometric data onto an HTMLCanvasElement. 8 | * _Tree_: Displays a hierarchical view of iModel contents. 9 | * _Property Grid_: Displays properties of selected element(s). 10 | * _Table_: Displays element properties in a tabular format. 11 | 12 | This app serves as a guide on how you can embed one or more of these components into your own application. 13 | See http://imodeljs.org for comprehensive documentation on the iModel.js API and the various constructs used in this sample. 14 | 15 | ![Screenshot of the application](./docs/header.png) 16 | 17 | ## Development Setup 18 | 19 | Follow the [App Development Setup](../../README.md) section under Sample Interactive Apps to configure, install dependencies, build, and run the app. 20 | 21 | ## Testing 22 | 23 | Run both e2e and unit tests with `npm test` 24 | 25 | ### End-to-end tests 26 | 27 | You can run just end-to-end tests with `npm run test:e2e`. But it takes a while 28 | to build and start the tests, so if want to actively change something within them, 29 | first launch the app with `npm run test:e2e:start-app` and when it's done `npm run test:e2e:test-app` 30 | 31 | If you want to see what tests do behind the scenes, you can launch them in non 32 | headless mode. Edit the file in *./test/end-to-end/setupTests.ts* and add 33 | 34 | ```js 35 | { headless: false } 36 | ``` 37 | 38 | to puppeteer launch options. Like this 39 | 40 | ```ts 41 | before(async () => { 42 | browser = await Puppeteer.launch({ headless: false }); 43 | }); 44 | ``` 45 | 46 | ### Unit tests 47 | 48 | Run with `npm run test:unit` 49 | 50 | ## Purpose 51 | 52 | The purpose of this application is to demonstrate the following: 53 | 54 | * [Dependencies](./package.json) required for iModel.js-based frontend applications. 55 | * [Scripts](./package.json) recommended to build and run iModel.js-based applications. 56 | * How to set up a simple backend for 57 | [web](./src/backend/web/BackendServer.ts) and 58 | [electron](./src/backend/electron/main.ts). 59 | * How to set up a simple [frontend for web and electron](./src/frontend/api/SimpleViewerApp.ts). 60 | * How to [consume](./src/frontend/components/App.tsx) iModel.js React components. 61 | * How to implement unified selection between a 62 | [viewport](./src/frontend/components/Viewport.tsx), 63 | [tree](./src/frontend/components/Tree.tsx), 64 | [property grid](./src/frontend/components/Properties.tsx) and a 65 | [table](./src/frontend/components/Table.tsx). 66 | * How to include 67 | [tools](./src/frontend/components/Toolbar.tsx) in a 68 | [viewport](./src/frontend/components/Viewport.tsx). 69 | 70 | ## Contributing 71 | 72 | [Contributing to iModel.js](https://github.com/imodeljs/imodeljs/blob/master/CONTRIBUTING.md) 73 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/docs/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/simple-viewer-app/docs/header.png -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/public/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/simple-viewer-app/public/appicon.ico -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/simple-viewer-app/public/favicon.ico -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | Simple Viewer App 16 | 19 | 20 | 21 | 22 | 23 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/public/locales/en/SimpleViewer.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome-message": "Welcome to Simple Viewer App", 3 | "signing-in": "Signing-in", 4 | "components": { 5 | "imodel-picker": { 6 | "open-imodel": "Open Sample iModel", 7 | "signout": "Sign Out" 8 | }, 9 | "properties": "Properties", 10 | "table": "Table", 11 | "tree": "Tree" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "simple-viewer-app", 3 | "name": "Simple Viewer App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/backend/electronmain.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as path from "path"; 6 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 7 | import { ElectronManagerOptions, IModelJsElectronManager, WebpackDevServerElectronManager } from "@bentley/electron-manager"; 8 | import { IModelHost } from "@bentley/imodeljs-backend"; 9 | import { ElectronRpcManager } from "@bentley/imodeljs-common"; 10 | import { Presentation } from "@bentley/presentation-backend"; 11 | import { AppLoggerCategory } from "../common/LoggerCategory"; 12 | import { getSupportedRpcs } from "../common/rpcs"; 13 | 14 | // Setup logging immediately to pick up any logging during IModelHost.startup() 15 | Logger.initializeToConsole(); 16 | Logger.setLevelDefault(LogLevel.Warning); 17 | Logger.setLevel(AppLoggerCategory.Backend, LogLevel.Trace); 18 | 19 | /** 20 | * Initializes Electron backend 21 | */ 22 | const electronMain = async () => { 23 | 24 | // Initialize iModelHost 25 | await IModelHost.startup(); 26 | 27 | // Initialize Presentation 28 | Presentation.initialize(); 29 | 30 | // Get RPCs supported by this backend 31 | const rpcs = getSupportedRpcs(); 32 | 33 | // tell ElectronRpcManager which RPC interfaces to handle 34 | ElectronRpcManager.initializeImpl({}, rpcs); 35 | 36 | const opts: ElectronManagerOptions = { 37 | webResourcesPath: path.join(__dirname, "..", "..", "..", "build"), 38 | }; 39 | const manager = (process.env.NODE_ENV === "development") ? new WebpackDevServerElectronManager(opts) : new IModelJsElectronManager(opts); 40 | await manager.initialize({ 41 | width: 1280, 42 | height: 800, 43 | autoHideMenuBar: true, 44 | show: false, 45 | }); 46 | 47 | if (manager.mainWindow) { 48 | manager.mainWindow.show(); 49 | } 50 | 51 | }; 52 | 53 | try {// execute this immediately when we load 54 | electronMain(); // eslint-disable-line @typescript-eslint/no-floating-promises 55 | } catch (error) { 56 | Logger.logError(AppLoggerCategory.Backend, error); 57 | process.exitCode = 1; 58 | } 59 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/backend/webmain.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelJsExpressServer } from "@bentley/express-server"; 6 | import { BentleyCloudRpcManager } from "@bentley/imodeljs-common"; 7 | import { AppLoggerCategory } from "../common/LoggerCategory"; 8 | import { Logger } from "@bentley/bentleyjs-core"; 9 | import { IModelHost } from "@bentley/imodeljs-backend"; 10 | import { Presentation } from "@bentley/presentation-backend"; 11 | 12 | import { getSupportedRpcs } from "../common/rpcs"; 13 | 14 | /** 15 | * Initializes Web Server backend 16 | */ 17 | // function called when we start the backend webserver 18 | const webMain = async () => { // tell BentleyCloudRpcManager which RPC interfaces to handle 19 | try { 20 | // Initialize iModelHost 21 | await IModelHost.startup(); 22 | 23 | // Initialize Presentation 24 | Presentation.initialize(); 25 | // Get RPCs supported by this backend 26 | const rpcs = getSupportedRpcs(); 27 | 28 | const rpcConfig = BentleyCloudRpcManager.initializeImpl({ info: { title: "simple-viewer-app", version: "v1.0" } }, rpcs); 29 | 30 | const port = Number(process.env.PORT || 3001); 31 | const server = new IModelJsExpressServer(rpcConfig.protocol); 32 | await server.initialize(port); 33 | Logger.logInfo(AppLoggerCategory.Backend, `RPC backend for simple-viewer-app listening on port ${port}`); 34 | } catch (error) { 35 | Logger.logError(AppLoggerCategory.Backend, error); 36 | process.exitCode = 1; 37 | } 38 | }; 39 | 40 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 41 | webMain(); 42 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/common/LoggerCategory.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** List of LoggerCategories for this app. For more details on Logging Categories, check out the [Category](https://www.imodeljs.org/learning/common/logging/#categories) documentation. */ 7 | export enum AppLoggerCategory { 8 | Frontend = "simple-viewer-app.Frontend", 9 | Backend = "simple-viewer-app.Backend", 10 | } 11 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/common/rpcs.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { IModelReadRpcInterface, IModelTileRpcInterface, RpcInterfaceDefinition, SnapshotIModelRpcInterface } from "@bentley/imodeljs-common"; 6 | import { PresentationRpcInterface } from "@bentley/presentation-common"; 7 | 8 | /** 9 | * Returns a list of RPCs supported by this application 10 | */ 11 | export function getSupportedRpcs(): RpcInterfaceDefinition[] { 12 | return [ 13 | IModelReadRpcInterface, 14 | IModelTileRpcInterface, 15 | PresentationRpcInterface, 16 | SnapshotIModelRpcInterface, 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/api/SimpleViewerApp.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { ClientRequestContext, Config, isElectronRenderer } from "@bentley/bentleyjs-core"; 6 | import { BrowserAuthorizationCallbackHandler, BrowserAuthorizationClient, BrowserAuthorizationClientConfiguration } from "@bentley/frontend-authorization-client"; 7 | import { BentleyCloudRpcParams, DesktopAuthorizationClientConfiguration } from "@bentley/imodeljs-common"; 8 | import { DesktopAuthorizationClient, FrontendRequestContext, IModelApp } from "@bentley/imodeljs-frontend"; 9 | import { UrlDiscoveryClient } from "@bentley/itwin-client"; 10 | import { Presentation } from "@bentley/presentation-frontend"; 11 | import { UiComponents } from "@bentley/ui-components"; 12 | import { initRpc } from "./rpc"; 13 | 14 | /** 15 | * List of possible backends that simple-viewer-app can use 16 | */ 17 | export enum UseBackend { 18 | /** Use local simple-viewer-app backend */ 19 | Local = 0, 20 | 21 | /** Use deployed general-purpose backend */ 22 | GeneralPurpose = 1, 23 | } 24 | 25 | export class SimpleViewerApp { 26 | 27 | public static get oidcClient() { return IModelApp.authorizationClient!; } 28 | 29 | public static async startup() { 30 | await IModelApp.startup({ applicationVersion: "1.0.0" }); 31 | 32 | // initialize OIDC 33 | await SimpleViewerApp.initializeOidc(); 34 | 35 | // contains various initialization promises which need 36 | // to be fulfilled before the app is ready 37 | const initPromises = new Array>(); 38 | 39 | // initialize RPC communication 40 | initPromises.push(SimpleViewerApp.initializeRpc()); 41 | 42 | // initialize localization for the app 43 | initPromises.push(IModelApp.i18n.registerNamespace("SimpleViewer").readFinished); 44 | 45 | // initialize UiComponents 46 | initPromises.push(UiComponents.initialize(IModelApp.i18n)); 47 | 48 | // initialize Presentation 49 | initPromises.push(Presentation.initialize({ 50 | activeLocale: IModelApp.i18n.languageList()[0], 51 | }).then(() => { 52 | Presentation.selection.scopes.activeScope = "functional-element"; 53 | })); 54 | 55 | // the app is ready when all initialization promises are fulfilled 56 | await Promise.all(initPromises); 57 | } 58 | 59 | private static async initializeRpc(): Promise { 60 | const rpcParams = await this.getConnectionInfo(); 61 | initRpc(rpcParams); 62 | } 63 | 64 | private static async initializeOidc(): Promise { 65 | const scope = Config.App.getString("imjs_browser_test_scope"); 66 | if (isElectronRenderer) { 67 | const clientId = Config.App.getString("imjs_electron_test_client_id"); 68 | const redirectUri = Config.App.getString("imjs_electron_test_redirect_uri"); 69 | const oidcConfiguration: DesktopAuthorizationClientConfiguration = { clientId, redirectUri, scope: `${scope} offline_access` }; 70 | const desktopClient = new DesktopAuthorizationClient(oidcConfiguration); 71 | await desktopClient.initialize(new ClientRequestContext()); 72 | IModelApp.authorizationClient = desktopClient; 73 | } else { 74 | const clientId = Config.App.getString("imjs_browser_test_client_id"); 75 | const redirectUri = Config.App.getString("imjs_browser_test_redirect_uri"); 76 | const postSignoutRedirectUri = Config.App.get("imjs_browser_test_post_signout_redirect_uri"); 77 | const oidcConfiguration: BrowserAuthorizationClientConfiguration = { clientId, redirectUri, postSignoutRedirectUri, scope: `${scope} imodeljs-router`, responseType: "code" }; 78 | await BrowserAuthorizationCallbackHandler.handleSigninCallback(oidcConfiguration.redirectUri); 79 | IModelApp.authorizationClient = new BrowserAuthorizationClient(oidcConfiguration); 80 | try { 81 | await (SimpleViewerApp.oidcClient as BrowserAuthorizationClient).signInSilent(new ClientRequestContext()); 82 | } catch (err) { } 83 | } 84 | } 85 | 86 | private static async getConnectionInfo(): Promise { 87 | const usedBackend = Config.App.getNumber("imjs_backend", UseBackend.Local); 88 | 89 | if (usedBackend === UseBackend.GeneralPurpose) { 90 | const urlClient = new UrlDiscoveryClient(); 91 | const requestContext = new FrontendRequestContext(); 92 | const orchestratorUrl = await urlClient.discoverUrl(requestContext, "iModelJsOrchestrator.K8S", undefined); 93 | return { info: { title: "general-purpose-imodeljs-backend", version: "v2.0" }, uriPrefix: orchestratorUrl }; 94 | } 95 | 96 | if (usedBackend === UseBackend.Local) 97 | return undefined; 98 | 99 | throw new Error(`Invalid backend "${usedBackend}" specified in configuration`); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/api/rpc.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { BentleyCloudRpcManager, BentleyCloudRpcParams, ElectronRpcConfiguration, ElectronRpcManager, RpcConfiguration } from "@bentley/imodeljs-common"; 6 | import { getSupportedRpcs } from "../../common/rpcs"; 7 | 8 | /** 9 | * Initializes RPC communication based on the platform 10 | */ 11 | export function initRpc(rpcParams?: BentleyCloudRpcParams): RpcConfiguration { 12 | let config: RpcConfiguration; 13 | const rpcInterfaces = getSupportedRpcs(); 14 | if (ElectronRpcConfiguration.isElectron) { 15 | // initializes RPC for Electron 16 | config = ElectronRpcManager.initializeClient({}, rpcInterfaces); 17 | } else { 18 | // initialize RPC for web apps 19 | if (!rpcParams) 20 | rpcParams = { info: { title: "simple-viewer-app", version: "v1.0" }, uriPrefix: "http://localhost:3001" }; 21 | config = BentleyCloudRpcManager.initializeClient(rpcParams, rpcInterfaces); 22 | } 23 | return config; 24 | } 25 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/App.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | .app { 7 | display: flex; 8 | flex-flow: column; 9 | height: 100vh; 10 | overflow: hidden; 11 | } 12 | 13 | .app-header { 14 | background-color: #222; 15 | color: white; 16 | padding-left: 20px; 17 | } 18 | 19 | .app .button-open-imodel { 20 | position: absolute; 21 | width: 200px; 22 | text-align: center; 23 | top: 40%; 24 | left: 50%; 25 | margin-left: -100px; 26 | margin-right: 100px; 27 | padding-left: 0; 28 | padding-right: 0; 29 | } 30 | 31 | .app .button-signout { 32 | position: absolute; 33 | width: 200px; 34 | text-align: center; 35 | top: 50%; 36 | left: 50%; 37 | margin-left: -100px; 38 | margin-right: 100px; 39 | padding-left: 0; 40 | padding-right: 0; 41 | } 42 | 43 | .app-content { 44 | position: relative; 45 | height: 100%; 46 | width: 100%; 47 | } 48 | 49 | .app-content .top-left { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | height: 70%; 54 | width: 70%; 55 | } 56 | 57 | .app-content .bottom { 58 | position: absolute; 59 | bottom: 0; 60 | left: 0; 61 | height: 30%; 62 | width: 70%; 63 | } 64 | 65 | .app-content .right { 66 | position: absolute; 67 | top: 0; 68 | right: 0; 69 | height: 100%; 70 | width: 30%; 71 | } 72 | 73 | .app-content .right .top { 74 | position: absolute; 75 | top: 0; 76 | height: 50%; 77 | width: 100%; 78 | overflow: hidden; 79 | display: flex; 80 | flex-direction: column; 81 | } 82 | 83 | .app-content .right .bottom { 84 | position: absolute; 85 | bottom: 0; 86 | height: 50%; 87 | width: 100%; 88 | overflow: hidden; 89 | display: flex; 90 | flex-direction: column; 91 | } 92 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Components.scss: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | .toolbar { 7 | float: right; 8 | background-origin: border-box; 9 | top: 3%; 10 | right: 1%; 11 | position: absolute; 12 | -webkit-transition: all 500ms ease; 13 | -o-transition: all 500ms ease; 14 | transition: all 500ms ease; 15 | cursor: pointer; 16 | }; 17 | 18 | .toolbar a { 19 | padding: 12px; 20 | color: black; 21 | text-decoration: none; 22 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.22))); 23 | background-color: #f9f9f9; 24 | border: 1px #4d575f solid; 25 | } 26 | 27 | .toolbar a:hover { 28 | color: #0072b8 29 | } 30 | 31 | .icon { 32 | background: none; 33 | } 34 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Properties.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import { IModelApp, IModelConnection } from "@bentley/imodeljs-frontend"; 7 | import { FillCentered, Orientation, useOptionalDisposable } from "@bentley/ui-core"; 8 | import { 9 | IPresentationPropertyDataProvider, 10 | PresentationPropertyDataProvider, 11 | usePropertyDataProviderWithUnifiedSelection, 12 | } from "@bentley/presentation-components"; 13 | import { VirtualizedPropertyGridWithDataProvider } from "@bentley/ui-components"; 14 | 15 | /** React properties for the property pane component, that accepts an iModel connection with ruleset id */ 16 | export interface IModelConnectionProps { 17 | /** iModel whose contents should be displayed in the property pane */ 18 | imodel: IModelConnection; 19 | } 20 | 21 | /** React properties for the property pane component, that accepts a data provider */ 22 | export interface DataProviderProps { 23 | /** Custom property pane data provider. */ 24 | dataProvider: IPresentationPropertyDataProvider; 25 | } 26 | 27 | /** React properties for the property pane component */ 28 | export type Props = IModelConnectionProps | DataProviderProps; 29 | 30 | /** Property grid component for the viewer app */ 31 | export default function SimplePropertiesComponent(props: Props) { // eslint-disable-line @typescript-eslint/naming-convention 32 | const imodel = (props as IModelConnectionProps).imodel; 33 | const imodelDataProvider = useOptionalDisposable(React.useCallback(() => { 34 | if (imodel) 35 | return new PresentationPropertyDataProvider({ imodel }); 36 | return undefined; 37 | }, [imodel])); 38 | const dataProvider: IPresentationPropertyDataProvider = imodelDataProvider ?? (props as any).dataProvider; 39 | const { isOverLimit } = usePropertyDataProviderWithUnifiedSelection({ dataProvider }); 40 | let content: JSX.Element; 41 | if (isOverLimit) { 42 | content = ({"Too many elements."}); 43 | } else { 44 | content = (); 50 | } 51 | return ( 52 | <> 53 |

{IModelApp.i18n.translate("SimpleViewer:components.properties")}

54 |
55 | {content} 56 |
57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Table.ruleset.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/@bentley/presentation-common/Ruleset.schema.json", 3 | "id": "simple-viewer-app/Table", 4 | "rules": [ 5 | { 6 | "ruleType": "Content", 7 | "condition": "SelectedNode.IsOfClass(\"Model\", \"BisCore\")", 8 | "onlyIfNotHandled": true, 9 | "specifications": [ 10 | { 11 | "specType": "ContentRelatedInstances", 12 | "relationshipPaths": [ 13 | { 14 | "relationship": { 15 | "schemaName": "BisCore", 16 | "className": "ModelContainsElements" 17 | }, 18 | "direction": "Forward" 19 | } 20 | ] 21 | } 22 | ] 23 | }, 24 | { 25 | "ruleType": "Content", 26 | "condition": "SelectedNode.IsOfClass(\"Category\", \"BisCore\")", 27 | "onlyIfNotHandled": true, 28 | "specifications": [ 29 | { 30 | "specType": "ContentRelatedInstances", 31 | "relationshipPaths": [ 32 | { 33 | "relationship": { 34 | "schemaName": "BisCore", 35 | "className": "GeometricElement2dIsInCategory" 36 | }, 37 | "direction": "Backward" 38 | }, 39 | { 40 | "relationship": { 41 | "schemaName": "BisCore", 42 | "className": "GeometricElement3dIsInCategory" 43 | }, 44 | "direction": "Backward" 45 | } 46 | ] 47 | } 48 | ] 49 | }, 50 | { 51 | "ruleType": "Content", 52 | "condition": "SelectedNode.IsOfClass(\"Element\", \"BisCore\")", 53 | "onlyIfNotHandled": true, 54 | "specifications": [ 55 | { 56 | "specType": "ContentRelatedInstances", 57 | "relationshipPaths": [ 58 | { 59 | "relationship": { 60 | "schemaName": "BisCore", 61 | "className": "ElementOwnsChildElements" 62 | }, 63 | "direction": "Forward", 64 | "count": "*" 65 | } 66 | ] 67 | }, 68 | { 69 | "specType": "SelectedNodeInstances" 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Table.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /* eslint-disable no-duplicate-imports */ 6 | import * as React from "react"; 7 | import { useCallback } from "react"; 8 | import { IModelConnection } from "@bentley/imodeljs-frontend"; 9 | import { useOptionalDisposable } from "@bentley/ui-core"; 10 | import { Table } from "@bentley/ui-components"; 11 | import { 12 | IPresentationTableDataProvider, 13 | PresentationTableDataProvider, 14 | tableWithUnifiedSelection, 15 | } from "@bentley/presentation-components"; 16 | // eslint-disable-next-line @typescript-eslint/no-var-requires 17 | const RULESET_TABLE = require("./Table.ruleset.json"); 18 | 19 | // create a HOC table component that supports unified selection 20 | // eslint-disable-next-line @typescript-eslint/naming-convention 21 | const SimpleTable = tableWithUnifiedSelection(Table); 22 | 23 | /** React properties for the table component, that accepts an iModel connection with ruleset id */ 24 | export interface IModelConnectionProps { 25 | /** iModel whose contents should be displayed in the table */ 26 | imodel: IModelConnection; 27 | } 28 | 29 | /** React properties for the table component, that accepts a data provider */ 30 | export interface DataProviderProps { 31 | /** Custom property pane data provider. */ 32 | dataProvider: IPresentationTableDataProvider; 33 | } 34 | 35 | /** React properties for the table component */ 36 | export type Props = IModelConnectionProps | DataProviderProps; 37 | 38 | /** Table component for the viewer app */ 39 | export default function SimpleTableComponent(props: Props) { // eslint-disable-line @typescript-eslint/naming-convention 40 | const imodel = (props as IModelConnectionProps).imodel; 41 | const imodelDataProvider = useOptionalDisposable(useCallback(() => { 42 | if (imodel) 43 | return new PresentationTableDataProvider({ imodel, ruleset: RULESET_TABLE }); 44 | return undefined; 45 | }, [imodel])); 46 | const dataProvider: IPresentationTableDataProvider = imodelDataProvider ?? ((props as any).dataProvider).dataProvider; 47 | return ( 48 |
49 | 50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Toolbar.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { FitViewTool, IModelApp, PanViewTool, RotateViewTool, SelectionTool, ZoomViewTool } from "@bentley/imodeljs-frontend"; 7 | import * as React from "react"; 8 | import "./Components.scss"; 9 | 10 | /* eslint-disable react/jsx-key */ 11 | 12 | /** Toolbar containing simple navigation tools */ 13 | const toolbar = () => { 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | /** 26 | * See the https://imodeljs.github.io/iModelJs-docs-output/learning/frontend/tools/ 27 | * for more details and available tools. 28 | */ 29 | 30 | const select = () => { 31 | IModelApp.tools.run(SelectionTool.toolId); 32 | }; 33 | 34 | const fitView = () => { 35 | IModelApp.tools.run(FitViewTool.toolId, IModelApp.viewManager.selectedView); 36 | }; 37 | 38 | const rotate = () => { 39 | IModelApp.tools.run(RotateViewTool.toolId, IModelApp.viewManager.selectedView); 40 | }; 41 | 42 | const pan = () => { 43 | IModelApp.tools.run(PanViewTool.toolId, IModelApp.viewManager.selectedView); 44 | }; 45 | 46 | const zoom = () => { 47 | IModelApp.tools.run(ZoomViewTool.toolId, IModelApp.viewManager.selectedView); 48 | }; 49 | 50 | export default toolbar; 51 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Tree.ruleset.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/@bentley/presentation-common/Ruleset.schema.json", 3 | "id": "simple-viewer-app/Tree", 4 | "rules": [ 5 | { 6 | "ruleType": "RootNodes", 7 | "autoExpand": true, 8 | "specifications": [ 9 | { 10 | "specType": "InstanceNodesOfSpecificClasses", 11 | "classes": [ 12 | { 13 | "schemaName": "BisCore", 14 | "classNames": [ 15 | "Subject" 16 | ] 17 | } 18 | ], 19 | "instanceFilter": "this.Parent = NULL", 20 | "arePolymorphic": false, 21 | "groupByClass": false, 22 | "groupByLabel": false 23 | } 24 | ] 25 | }, 26 | { 27 | "ruleType": "ChildNodes", 28 | "condition": "ParentNode.IsOfClass(\"Subject\", \"BisCore\")", 29 | "onlyIfNotHandled": true, 30 | "specifications": [ 31 | { 32 | "specType": "RelatedInstanceNodes", 33 | "relationshipPaths": [ 34 | { 35 | "relationship": { 36 | "schemaName": "BisCore", 37 | "className": "SubjectOwnsSubjects" 38 | }, 39 | "direction": "Forward" 40 | } 41 | ], 42 | "groupByClass": false, 43 | "groupByLabel": false 44 | }, 45 | { 46 | "specType": "InstanceNodesOfSpecificClasses", 47 | "classes": { 48 | "schemaName": "BisCore", 49 | "classNames": [ 50 | "Model" 51 | ] 52 | }, 53 | "arePolymorphic": true, 54 | "relatedInstances": [ 55 | { 56 | "relationshipPath": { 57 | "relationship": { 58 | "schemaName": "BisCore", 59 | "className": "ModelModelsElement" 60 | }, 61 | "direction": "Forward", 62 | "targetClass": { 63 | "schemaName": "BisCore", 64 | "className": "InformationPartitionElement" 65 | } 66 | }, 67 | "alias": "partition", 68 | "isRequired": true 69 | } 70 | ], 71 | "instanceFilter": "partition.Parent.Id = parent.ECInstanceId AND NOT this.IsPrivate", 72 | "groupByClass": false, 73 | "groupByLabel": false 74 | } 75 | ] 76 | }, 77 | { 78 | "ruleType": "ChildNodes", 79 | "condition": "ParentNode.IsOfClass(\"Model\", \"BisCore\")", 80 | "onlyIfNotHandled": true, 81 | "specifications": [ 82 | { 83 | "specType": "RelatedInstanceNodes", 84 | "relationshipPaths": [ 85 | { 86 | "relationship": { 87 | "schemaName": "BisCore", 88 | "className": "ModelContainsElements" 89 | }, 90 | "direction": "Forward" 91 | } 92 | ], 93 | "instanceFilter": "this.Parent = NULL", 94 | "groupByClass": false, 95 | "groupByLabel": false 96 | } 97 | ] 98 | }, 99 | { 100 | "ruleType": "ChildNodes", 101 | "condition": "ParentNode.IsOfClass(\"Element\", \"BisCore\")", 102 | "onlyIfNotHandled": true, 103 | "specifications": [ 104 | { 105 | "specType": "RelatedInstanceNodes", 106 | "relationshipPaths": [ 107 | { 108 | "relationship": { 109 | "schemaName": "BisCore", 110 | "className": "ElementOwnsChildElements" 111 | }, 112 | "direction": "Forward" 113 | } 114 | ], 115 | "groupByClass": false, 116 | "groupByLabel": false 117 | } 118 | ] 119 | }, 120 | { 121 | "ruleType": "ImageIdOverride", 122 | "condition": "ThisNode.IsOfClass(\"Subject\", \"BisCore\")", 123 | "imageIdExpression": "IIF(this.Parent.Id = NULL, \"icon-imodel-hollow-2\", \"icon-folder\")" 124 | }, 125 | { 126 | "ruleType": "ImageIdOverride", 127 | "condition": "ThisNode.IsOfClass(\"Model\", \"BisCore\")", 128 | "imageIdExpression": "\"icon-model\"" 129 | }, 130 | { 131 | "ruleType": "ImageIdOverride", 132 | "condition": "ThisNode.IsOfClass(\"Element\", \"BisCore\")", 133 | "imageIdExpression": "\"icon-item\"" 134 | } 135 | ] 136 | } 137 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Tree.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /* eslint-disable no-duplicate-imports */ 6 | import * as React from "react"; 7 | import { useCallback } from "react"; 8 | import { IModelApp, IModelConnection } from "@bentley/imodeljs-frontend"; 9 | import { useOptionalDisposable } from "@bentley/ui-core"; 10 | import { ControlledTree, SelectionMode, usePagedTreeNodeLoader, useTreeModelSource, useVisibleTreeNodes } from "@bentley/ui-components"; 11 | import { IPresentationTreeDataProvider, PresentationTreeDataProvider, useUnifiedSelectionTreeEventHandler } from "@bentley/presentation-components"; 12 | const RULESET_TREE = require("./Tree.ruleset.json"); // eslint-disable-line @typescript-eslint/no-var-requires 13 | 14 | /** React properties for the tree component, that accepts an iModel connection with ruleset id */ 15 | export interface IModelConnectionProps { 16 | /** iModel whose contents should be displayed in the tree */ 17 | imodel: IModelConnection; 18 | } 19 | 20 | /** React properties for the tree component, that accepts a data provider */ 21 | export interface DataProviderProps { 22 | /** Custom tree data provider. */ 23 | dataProvider: IPresentationTreeDataProvider; 24 | } 25 | 26 | /** React properties for the tree component */ 27 | export type Props = IModelConnectionProps | DataProviderProps; 28 | 29 | /** Tree component for the viewer app */ 30 | export default function SimpleTreeComponent(props: Props) { // eslint-disable-line @typescript-eslint/naming-convention 31 | const imodel = (props as IModelConnectionProps).imodel; 32 | const pagingSize = 20; 33 | const imodelDataProvider = useOptionalDisposable(useCallback(() => { 34 | if (imodel) 35 | return new PresentationTreeDataProvider({ imodel, ruleset: RULESET_TREE, pagingSize }); 36 | return undefined; 37 | }, [imodel])); 38 | const dataProvider = imodelDataProvider ?? (props as DataProviderProps).dataProvider; 39 | const modelSource = useTreeModelSource(dataProvider); 40 | const nodeLoader = usePagedTreeNodeLoader(dataProvider, 20, modelSource); 41 | const eventsHandler = useUnifiedSelectionTreeEventHandler({ nodeLoader, collapsedChildrenDisposalEnabled: true }); 42 | return ( 43 | <> 44 |

{IModelApp.i18n.translate("SimpleViewer:components.tree")}

45 |
46 | 53 |
54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/components/Viewport.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import { Id64String } from "@bentley/bentleyjs-core"; 7 | import { IModelConnection } from "@bentley/imodeljs-frontend"; 8 | import { ViewportComponent } from "@bentley/ui-components"; 9 | import { viewWithUnifiedSelection } from "@bentley/presentation-components"; 10 | import Toolbar from "./Toolbar"; 11 | 12 | // create a HOC viewport component that supports unified selection 13 | // eslint-disable-next-line @typescript-eslint/naming-convention 14 | const SimpleViewport = viewWithUnifiedSelection(ViewportComponent); 15 | 16 | /** React properties for the viewport component */ 17 | export interface Props { 18 | /** iModel whose contents should be displayed in the viewport */ 19 | imodel: IModelConnection; 20 | /** View definition to use when the viewport is first loaded */ 21 | viewDefinitionId: Id64String; 22 | } 23 | 24 | /** Viewport component for the viewer app */ 25 | export default class SimpleViewportComponent extends React.Component { 26 | public render() { 27 | return ( 28 | <> 29 | 33 | 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/index.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | font-family: sans-serif; 10 | } 11 | 12 | h3 { 13 | margin-bottom: 0.3em; 14 | } 15 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/frontend/index.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as React from "react"; 6 | import * as ReactDOM from "react-dom"; 7 | import { Logger, LogLevel } from "@bentley/bentleyjs-core"; 8 | import { AppLoggerCategory } from "../common/LoggerCategory"; 9 | import { SimpleViewerApp } from "./api/SimpleViewerApp"; 10 | import App from "./components/App"; 11 | import "./index.css"; 12 | 13 | // Setup logging immediately to pick up any logging during SimpleViewerApp.startup() 14 | Logger.initializeToConsole(); 15 | Logger.setLevelDefault(LogLevel.Warning); // Set all logging to a default of Warning 16 | Logger.setLevel(AppLoggerCategory.Frontend, LogLevel.Info); // Override the above default and set only App level logging to Info. 17 | 18 | (async () => { // eslint-disable-line @typescript-eslint/no-floating-promises 19 | // initialize the application 20 | await SimpleViewerApp.startup(); 21 | 22 | ReactDOM.render( 23 | , 24 | document.getElementById("root") as HTMLElement, 25 | ); 26 | })(); 27 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/src/index.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // react-scripts requires an index.ts file in this location. Using it to import the code 7 | // from the frontend. 8 | 9 | import "./frontend/index"; 10 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/electron/basic.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Application } from "spectron"; 6 | import { assert } from "chai"; 7 | import * as path from "path"; 8 | import * as app from "electron"; 9 | 10 | describe("Application", () => { 11 | let testApp: Application; 12 | before(async () => { 13 | testApp = new Application({ 14 | path: app as any, 15 | args: [path.join(__dirname, "..", "..", "lib/backend/electronmain.js")], 16 | requireName: "electronRequire", 17 | }); 18 | return testApp.start(); 19 | }); 20 | 21 | after(async () => { 22 | if (testApp && testApp.isRunning()) { 23 | return testApp.stop(); 24 | } else { 25 | return testApp; 26 | } 27 | }); 28 | 29 | it("opens window", () => { 30 | testApp.client.waitUntilWindowLoaded(); 31 | return testApp.client.getWindowCount().then((count) => { 32 | // Only one window should open, note that if 33 | // dev tools are open window count will equal 2 34 | assert.equal(count, 1); 35 | }); 36 | }); 37 | 38 | it("opens sign-in page", async () => { 39 | return testApp.client 40 | .waitForExist(".components-signin-prompt", 55000); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/end-to-end/helpers.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Config } from "@bentley/bentleyjs-core"; 6 | import * as Puppeteer from "puppeteer"; 7 | 8 | /** Wait for the specified text to appear on the page */ 9 | export async function waitForText(page: Puppeteer.Page, text: string, options?: Puppeteer.WaitForSelectorOptions) { 10 | await page.waitForXPath(`//text()[contains(., '${text}')]`, { visible: true, ...options }); 11 | } 12 | 13 | /** Find an element in the DOM by specified text */ 14 | export async function findByText(element: Puppeteer.Page | Puppeteer.ElementHandle, text: string) { 15 | const elements = await element.$x(`//text()[contains(., '${text}')]/..`); 16 | 17 | if (elements.length) 18 | return elements[0]; 19 | 20 | throw Error(`Element "${text}" not found!`); 21 | } 22 | 23 | /** Sign in to the main page using test credentials */ 24 | export async function signIn(page: Puppeteer.Page) { 25 | const userName = Config.App.getString("imjs_test_regular_user_name"); 26 | const pw = Config.App.getString("imjs_test_regular_user_password"); 27 | 28 | await page.waitForSelector(".components-signin-button"); 29 | await page.click(".components-signin-button"); 30 | 31 | await page.waitForNavigation({ 32 | // Need to wait for 'load' here due to slower connections. With a fast connection, 33 | // the redirect happens so quickly it doesn't hit the 500 ms threshold that puppeteer expects for an idle network, "networkidle2". 34 | waitUntil: "load", 35 | }); 36 | 37 | if (-1 !== page.url().indexOf("/IMS/Account/Login")) 38 | await fillInSignin(page, userName, pw); 39 | else 40 | await newSignin(page, userName, pw); 41 | } 42 | 43 | /** Fill in sign in form with test credentials and submit */ 44 | async function fillInSignin(page: Puppeteer.Page, userName: string, pw: string) { 45 | await page.waitForSelector("#submitLogon"); 46 | 47 | await page.type("#EmailAddress", userName); 48 | await page.type("#Password", pw); 49 | 50 | await page.click("#submitLogon"); 51 | 52 | // Try to catch failed logins 53 | try { 54 | const errorSelectors = ["#messageControlDiv", ".consent-buttons"]; 55 | const jsHandle = await page.waitForFunction((selectors) => { 56 | for (const selector of selectors) { 57 | if (document.querySelector(selector) !== null) { 58 | return selector; 59 | } 60 | } 61 | return false; 62 | }, { timeout: 2000 }, errorSelectors); 63 | const selector = await jsHandle.jsonValue(); 64 | 65 | // If .consent-buttons is found. Click the consent button 66 | if (selector === errorSelectors[1]) { 67 | await page.click("#connect-main > div > div > form > div.consent-buttons > div.consent-buttons-nowrap > button.bwc-button-primary"); 68 | } else if (selector === errorSelectors[0]) { 69 | // #messageControlDiv found. Throw failed login error. 70 | throw new Error(`Failed login to ${page.url()} for ${Config.App.getString("imjs_test_regular_user_name")}`); 71 | } 72 | } catch (e) { 73 | // Ignore Timeout errors 74 | if (!e.name.includes("Timeout")) { 75 | throw e; 76 | } 77 | } 78 | } 79 | 80 | async function newSignin(page: Puppeteer.Page, userName: string, pw: string) { 81 | await page.waitForSelector("#identifierInput"); 82 | await page.type("#identifierInput", userName); 83 | await page.waitForSelector(".allow"); 84 | await page.$eval(".allow", (button: any) => button.click()); 85 | await page.waitForSelector("#password"); 86 | await page.type("#password", pw); 87 | await page.$eval(".allow", (button: any) => button.click()); 88 | } 89 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/end-to-end/setupTests.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as Puppeteer from "puppeteer"; 7 | import * as os from "os"; 8 | 9 | export let page: Puppeteer.Page; 10 | export let browser: Puppeteer.Browser; 11 | 12 | beforeEach(async () => { 13 | let launchOptions: Puppeteer.LaunchOptions = { dumpio: true, headless: false }; 14 | 15 | if (os.platform() === "linux") { 16 | launchOptions = { 17 | args: ["--no-sandbox"], // , "--disable-setuid-sandbox"], 18 | }; 19 | } 20 | browser = await Puppeteer.launch(launchOptions); 21 | page = await browser.newPage(); 22 | await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36"); 23 | await page.setViewport({ height: 1080, width: 1920 }); 24 | await page.goto("http://localhost:3000", { timeout: 0, waitUntil: "domcontentloaded" }); 25 | }); 26 | 27 | afterEach(async () => { 28 | await page.close(); 29 | await browser.close(); 30 | }); 31 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/end-to-end/views/Content.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { expect } from "chai"; 7 | import * as Puppeteer from "puppeteer"; 8 | import { Config } from "@bentley/bentleyjs-core"; 9 | import { findByText, signIn } from "../helpers"; 10 | import { page } from "../setupTests"; 11 | 12 | async function openIModel() { 13 | 14 | // Wait for "Open iModel" button to appear 15 | await page.waitForSelector(".button-open-imodel"); 16 | 17 | // Set up a promise for alert popup 18 | const dialogPromise = new Promise((response) => page.on("dialog", response)); 19 | 20 | // Open iModel 21 | await page.click(".button-open-imodel"); 22 | 23 | // If there's an alert with an error message, catch it earlier than 1 min 24 | await Promise.race([ 25 | dialogPromise.then((dialog) => { throw new Error((dialog as Puppeteer.Dialog).message()); }), 26 | page.waitFor(5000), 27 | ]); 28 | 29 | // Wait for at least one node to show up in the tree 30 | await page.waitForSelector(`[data-testid="tree-node"]`, { timeout: 60000 }); // 1 min. timeout, IModel may take a long time to load 31 | } 32 | 33 | async function findNode(text: string) { 34 | const selector = `//div[contains(@data-testid, "tree-node") and contains(., "${text}")]`; 35 | 36 | // Wait for Node to render 37 | await page.waitForXPath(selector, { visible: true }); 38 | 39 | // Find Node 40 | const elementHandles = await page.$x(selector); 41 | if (!elementHandles[0]) 42 | throw Error(`Node "${text}" not found!`); 43 | 44 | return elementHandles[0]; 45 | } 46 | 47 | describe("Content view", () => { 48 | 49 | it("renders after loading iModel", async () => { 50 | await signIn(page); 51 | await openIModel(); 52 | 53 | // Make sure that iModel canvas has appeared 54 | await page.waitForSelector("canvas"); 55 | 56 | // Make sure that property pane is rendered 57 | await page.waitForSelector(".components-property-grid"); 58 | 59 | // Make sure that table is rendered 60 | await page.waitForSelector(".components-table"); 61 | 62 | // Make sure that toolbar is rendered 63 | await page.waitForSelector(".toolbar"); 64 | }); 65 | 66 | it("loads table and property data after clicking on a tree node", async () => { 67 | await signIn(page); 68 | await openIModel(); 69 | 70 | // Make sure that neither table nor property pane renders before data is loaded 71 | expect(async () => page.$(".components-table .components-table-cell")).to.throw; 72 | expect(async () => page.$(".components-property-grid .components-property-category-block")).to.throw; 73 | 74 | // Find and select root node 75 | const nodeHandle = await findNode(Config.App.getString("imjs_test_project")); 76 | await nodeHandle.click(); 77 | 78 | // Wait for table to load 79 | await page.waitForSelector(".components-table .components-table-row", { timeout: 50000 }); 80 | 81 | // Limit search to table 82 | const tableHandle = await page.$(".components-table"); 83 | expect(tableHandle, "Table wrapper not found!").to.exist; 84 | // Find root node's content in table 85 | await findByText(tableHandle!, Config.App.getString("imjs_test_project")); 86 | 87 | // Expand properties 88 | await page.waitFor(".uicore-expandable-blocks-block .title"); 89 | const expanderHandle = await page.$$(".uicore-expandable-blocks-block .title"); 90 | expect(expanderHandle[0], "Property Pane block not found!").to.exist; 91 | await expanderHandle[0]!.click(); 92 | 93 | // Limit search to properties 94 | const propertiesHandle = await page.$(".components-property-grid-wrapper"); 95 | expect(propertiesHandle, "Property Pane wrapper not found!").to.exist; 96 | // Find root node's content in properties 97 | await findByText(propertiesHandle!, Config.App.getString("imjs_test_project")); 98 | }); 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/end-to-end/views/OpenIModel.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { page } from "../setupTests"; 7 | import { signIn } from "../helpers"; 8 | 9 | describe("Open iModel view", () => { 10 | 11 | it("renders after sign in", async () => { 12 | await signIn(page); 13 | await page.waitForSelector(".button-open-imodel", { timeout: 5000 }); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/end-to-end/views/SignIn.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { page } from "../setupTests"; 7 | 8 | describe("Sign in view", () => { 9 | 10 | it("renders initially", async () => { 11 | await page.waitForSelector(".components-signin-button"); 12 | 13 | // Verify that welcome message exists 14 | await page.waitForSelector(".components-signin-prompt"); 15 | 16 | // Verify that "Register" link exists 17 | await page.waitForSelector(".components-signin-register a"); 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/unit/assets/Properties_60InstancesWithUrl2.ibim: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imodeljs/imodeljs-samples/a30ae7c5f9653747f5767a463ab77226e14e8187/interactive-app/simple-viewer-app/test/unit/assets/Properties_60InstancesWithUrl2.ibim -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/unit/frontend/components/Properties.test.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as React from "react"; 7 | import * as moq from "typemoq"; 8 | import { render } from "@testing-library/react"; 9 | import { expect } from "chai"; 10 | import { PropertyRecord } from "@bentley/ui-abstract"; 11 | import { IModelApp, IModelConnection } from "@bentley/imodeljs-frontend"; 12 | import { PropertyData, PropertyDataChangeEvent } from "@bentley/ui-components"; 13 | import { KeySet } from "@bentley/presentation-common"; 14 | import { IPresentationPropertyDataProvider } from "@bentley/presentation-components"; 15 | import PropertiesComponent from "../../../../src/frontend/components/Properties"; 16 | 17 | const iModelConnectionMock = moq.Mock.ofType(); 18 | 19 | class EmptyPropertyDataProvider implements IPresentationPropertyDataProvider { 20 | public displayType = "test"; 21 | public keys = new KeySet(); 22 | public selectionInfo = undefined; 23 | public imodel = iModelConnectionMock.object; 24 | public rulesetId = ""; 25 | 26 | protected _data: PropertyData = { 27 | label: PropertyRecord.fromString("Empty data"), 28 | categories: [], 29 | records: { test: [] }, 30 | }; 31 | 32 | public dispose() { } 33 | 34 | public getContentDescriptor = async () => undefined; 35 | public getContentSetSize = async () => 0; 36 | public getContent = async () => undefined; 37 | public getFieldByPropertyRecord = async () => undefined; 38 | 39 | public getData = async () => this._data; 40 | public onDataChanged = new PropertyDataChangeEvent(); 41 | } 42 | 43 | describe("Properties", () => { 44 | 45 | it("renders header", () => { 46 | const renderWrapper = render(); 47 | const header = renderWrapper.getByTestId("property-pane-component-header"); 48 | expect(header.innerHTML).to.equal(IModelApp.i18n.translate("SimpleViewer:components.properties")); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/unit/frontend/components/Tree.test.tsx: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as React from "react"; 7 | import * as moq from "typemoq"; 8 | import { render } from "@testing-library/react"; 9 | import { waitForElement } from "@testing-library/dom"; 10 | import { expect } from "chai"; 11 | 12 | import { IModelApp, IModelConnection } from "@bentley/imodeljs-frontend"; 13 | import { IPresentationTreeDataProvider } from "@bentley/presentation-components"; 14 | import { PropertyRecord } from "@bentley/ui-abstract"; 15 | import { TreeNodeItem } from "@bentley/ui-components"; 16 | import TreeComponent from "../../../../src/frontend/components/Tree"; 17 | 18 | const iModelConnectionMock = moq.Mock.ofType(); 19 | 20 | class EmptyTreeDataProvider implements IPresentationTreeDataProvider { 21 | protected _nodes: TreeNodeItem[] = []; 22 | public get imodel() { return iModelConnectionMock.object; } 23 | public getFilteredNodePaths = async () => []; 24 | public get onTreeNodeChanged() { return undefined; } 25 | public get rulesetId() { return ""; } 26 | public getNodeKey = () => ({ type: "testType", pathFromRoot: ["root"] }); 27 | public getNodes = async () => this._nodes; 28 | public getNodesCount = async () => this._nodes.length; 29 | public loadHierarchy = async () => { return; }; 30 | public dispose() {return; } 31 | } 32 | 33 | describe("Tree", () => { 34 | 35 | it("renders header and tree component", () => { 36 | const renderWrapper = render(); 37 | const header = renderWrapper.getByTestId("tree-component-header"); 38 | expect(header.innerHTML).to.be.equal(IModelApp.i18n.translate("SimpleViewer:components.tree")); 39 | expect(renderWrapper.container.querySelector(".components-controlledTree-loader")).to.not.be.empty; 40 | }); 41 | 42 | describe("Tree content", () => { 43 | 44 | class DataProvider extends EmptyTreeDataProvider { 45 | protected _nodes: TreeNodeItem[] = [ 46 | { 47 | id: "1", 48 | label: PropertyRecord.fromString("Node 1"), 49 | }, 50 | { 51 | id: "2", 52 | label: PropertyRecord.fromString("Node 2"), 53 | }, 54 | ]; 55 | } 56 | 57 | before(() => { 58 | // note: this is needed for AutoSizer used by the Tree to 59 | // have non-zero size and render the virtualized list 60 | Object.defineProperties(HTMLElement.prototype, { 61 | offsetHeight: { get: () => 200 }, 62 | offsetWidth: { get: () => 200 }, 63 | }); 64 | }); 65 | 66 | after(() => { 67 | Object.defineProperties(HTMLElement.prototype, { 68 | offsetHeight: { get: () => 0 }, 69 | offsetWidth: { get: () => 0 }, 70 | }); 71 | }); 72 | 73 | it("renders 'no data' when data provider is empty", async () => { 74 | const renderWrapper = render(); 75 | expect(renderWrapper.container.querySelector(".components-controlledTree-loader")).to.not.be.empty; 76 | const noDataLabel = await waitForElement(() => renderWrapper.getByText(IModelApp.i18n.translate("UiComponents:general.noData"))); 77 | expect(noDataLabel).to.not.be.undefined; 78 | }); 79 | 80 | it("renders all nodes from data provider when it's not empty", async () => { 81 | const renderWrapper = render(); 82 | expect(renderWrapper.container.querySelector(".components-controlledTree-loader")).to.not.be.empty; 83 | 84 | const nodes = await waitForElement(() => renderWrapper.getAllByTestId("tree-node")); 85 | expect(nodes.length).to.equal(2); 86 | expect(renderWrapper.getByText("Node 1")).to.not.be.undefined; 87 | }); 88 | 89 | }); 90 | 91 | }); 92 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/unit/index.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as typemoq from "typemoq"; 7 | import { IModelApp, NoRenderApp } from "@bentley/imodeljs-frontend"; 8 | import { UiCore } from "@bentley/ui-core"; 9 | import { UiComponents } from "@bentley/ui-components"; 10 | import { cleanup } from "@testing-library/react"; 11 | import { Presentation, SelectionManager, SelectionScopesManager } from "@bentley/presentation-frontend"; 12 | import { I18NOptions } from "@bentley/imodeljs-i18n"; 13 | 14 | function supplyI18NOptions(): I18NOptions { 15 | return { urlTemplate: `${window.location.origin}/locales/{{lng}}/{{ns}}.json` }; 16 | } 17 | 18 | before(async () => { 19 | await NoRenderApp.startup({ i18n: supplyI18NOptions() }); 20 | 21 | await UiComponents.initialize(IModelApp.i18n); 22 | await Presentation.initialize(); 23 | 24 | // Presentation.selection needs to be set, because WithUnifiedSelection requires a SelectionHandler. 25 | // If selection handler is not provided through props, the HOC creates a new SelectionHandler by 26 | // using Presentation.selection 27 | Presentation.setSelectionManager(new SelectionManager({ scopes: typemoq.Mock.ofType().object })); 28 | }); 29 | 30 | after(() => { 31 | UiCore.terminate(); 32 | UiComponents.terminate(); 33 | }); 34 | 35 | afterEach(() => { 36 | cleanup(); 37 | }); 38 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/utils/mocha.opts: -------------------------------------------------------------------------------- 1 | # A couple standard opts used for all test types 2 | --check-leaks 3 | --no-timeouts 4 | 5 | # Required for all test types because the *.ts files are provided to mocha. If the compiled .js files were given, this wouldn't be needed. 6 | --require ts-node/register 7 | 8 | # Allows the "window" to be defined in the tests for compilation 9 | --require jsdom-global/register 10 | 11 | # Ignore style imports that would normally be resolved by Webpack, however in Mocha they'll throw errors. 12 | --require ignore-styles 13 | 14 | # Run as a precursor to the entire test run to 15 | --file ./test/utils/setupTests.js 16 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/test/utils/setupTests.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | // A workaround to react-testing-library {dom-testing-library {wait-for-expect}} breaking somewhere, 6 | // because somewhere (most likely in jsdom) window.Date becomes undefined. 7 | // Similar issue mentioned in https://github.com/vuejs/vue-test-utils/issues/936 8 | require('jsdom-global')(); 9 | window.Date = Date; 10 | 11 | // Global setup for chai and snapshot testing to avoid doing it in each test file 12 | const chai = require("chai"); 13 | const chaiJestSnapshot = require("chai-jest-snapshot"); 14 | 15 | chai.use(chaiJestSnapshot); 16 | 17 | // Fix node's module loader to strip ?sprite from SVG imports 18 | const m = require("module"); 19 | const origLoader = m._load; 20 | m._load = (request, parent, isMain) => { 21 | return origLoader(request.replace("?sprite", ""), parent, isMain); 22 | }; 23 | 24 | beforeEach(function () { 25 | const sourceFilePath = this.currentTest.file.replace("lib\\test", "src\\test").replace(/\.(jsx?|tsx?)$/, ""); 26 | const snapPath = sourceFilePath + ".snap"; 27 | 28 | chaiJestSnapshot.setFilename(snapPath); 29 | chaiJestSnapshot.setTestName(this.currentTest.fullTitle()); 30 | }); 31 | 32 | // This is required by the iModel.js I18n module (specifically the i18next package). 33 | global.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; 34 | -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/tsconfig.backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "outDir": "./lib", 7 | "types": [ 8 | "mocha" 9 | ] 10 | }, 11 | "include": [ 12 | "./src/backend/*.ts", 13 | "./src/common/*.ts" 14 | ], 15 | "exclude": [ 16 | "lib", 17 | "node_modules" 18 | ] 19 | } -------------------------------------------------------------------------------- /interactive-app/simple-viewer-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "resolveJsonModule": true, 6 | "baseUrl": "./node_modules", 7 | "outDir": "./lib", 8 | "allowJs": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "types": [ 16 | "@bentley/react-scripts" 17 | ] 18 | }, 19 | "include": [ 20 | "./src/**/*.ts", 21 | "./src/**/*.tsx" 22 | ], 23 | "exclude": [ 24 | "lib", 25 | "node_modules", 26 | "test" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /rush.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", 3 | "rushVersion": "5.30.3", 4 | "pnpmVersion": "4.12.5", 5 | "nodeSupportedVersionRange": ">=10.6.0 <13.0.0", 6 | "projectFolderMinDepth": 2, 7 | "ensureConsistentVersions": true, 8 | "repository": { 9 | "url": "https://github.com/imodeljs/imodeljs-samples" 10 | }, 11 | "projects": [ 12 | { 13 | "packageName": "imodel-query-agent", 14 | "projectFolder": "agent-app/query-agent" 15 | }, 16 | { 17 | "packageName": "basic-viewport-app", 18 | "projectFolder": "interactive-app/basic-viewport-app" 19 | }, 20 | { 21 | "packageName": "ninezone-sample-app", 22 | "projectFolder": "interactive-app/ninezone-sample-app" 23 | }, 24 | { 25 | "packageName": "simple-viewer-app", 26 | "projectFolder": "interactive-app/simple-viewer-app" 27 | }, 28 | { 29 | "packageName": "imodel-changeset-test-utility", 30 | "projectFolder": "tools/imodel-changeset-test-utility" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/.npmignore: -------------------------------------------------------------------------------- 1 | # start off ignoring everything, and then add back only the files we want 2 | * 3 | !lib/**/*.d.ts 4 | !lib/**/*.js 5 | !lib/assets/** 6 | output/** 7 | assets/** 8 | test 9 | !public/** 10 | !package.json 11 | !*.md 12 | .nyc_ouput -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/lib/IModelChangesetCLUtility.js", 12 | "args": [], 13 | "outFiles": [ 14 | "${workspaceFolder}/lib/**/*.js" 15 | ] 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "Mocha All", 21 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 22 | "args": [ 23 | "-r", 24 | "ts-node/register", 25 | "--timeout", 26 | "999999", 27 | "--colors", 28 | "${workspaceFolder}/src/**/*test.ts", 29 | ], 30 | "console": "integratedTerminal", 31 | "internalConsoleOptions": "neverOpen", 32 | "protocol": "inspector" 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/node_modules": true, 4 | "**/lib": true 5 | }, 6 | "cSpell.words": [ 7 | "backend", 8 | "bbox", 9 | "bentleyjs", 10 | "ecschema", 11 | "imodel", 12 | "imodeljs", 13 | "undefine" 14 | ] 15 | } -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2018-2020 Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/README.md: -------------------------------------------------------------------------------- 1 | # imodel-changeset-test-utility 2 | 3 | Copyright © Bentley Systems, Incorporated. All rights reserved. 4 | 5 | Test utility for generating and pushing change sets to an iModel in the iModelHub. The utility creates change sets by inserting and updating geometric elements, and periodically pushes them to the iModelHub. 6 | 7 | This utility is meant to be used for testing sample applications like the [imodel-query-agent](../../agent-app/query-agent/README.md). 8 | 9 | ## Important note about registrations using iModel.js v0.191.0 and prior 10 | 11 | **The authorization method of agent clients has been changed in versions >=0.192.0. Therefore all registrations using agent clients created before 0.192.0 have been invalidated. To continue using your app with versions >=0.192.0 please create a new agent registration [here](https://imodeljs.github.io/iModelJs-docs-output/getting-started/registration-dashboard/). (i.e. You will need a client_id that starts with service-xxxxxx if using iModel.js versions >=0.192.0.) The new registration process is easy, fully automated, and can be completed in minutes.** 12 | 13 | ## Development Setup 14 | 15 | 1. Get the registration details of the sample application you are testing - for example, if testing imodel-query-agent, see the section on development setup in [imodel-query-agent](../../agent-app/query-agent/README.md). 16 | 17 | 2. Edit [src/ChangesetGenerationConfig.ts](./src/ChangesetGenerationConfig.ts) to set the values you obtained after registration in step 1. 18 | 19 | 3. Follow the [Tools Development Setup](../../README.md) section under Tools to install dependencies and build the imodel-changeset-test-utility. 20 | 21 | ## Run Change Set Test Utility 22 | 23 | * Run `npm start`. Watch the console for various messages that show the progress. 24 | 25 | ## Test 26 | 27 | The utility includes some tests to validate it's behavior - these are useful for internal testing: 28 | 29 | * Unit tests: `npm test` 30 | * Integration tests: `npm run test:integration` 31 | 32 | ## Contributing 33 | 34 | [Contributing to iModel.js](https://github.com/imodeljs/imodeljs/blob/master/CONTRIBUTING.md) 35 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imodel-changeset-test-utility", 3 | "version": "0.0.57", 4 | "main": "lib/index.js", 5 | "typings": "lib/index", 6 | "description": "Creates and pushes configurable change sets to iModels", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc 1>&2", 10 | "clean": "rimraf lib tsconfig.tsbuildinfo .rush", 11 | "clean-output": "rimraf ./lib/output", 12 | "lint": "eslint ./src/**/*.ts 1>&2", 13 | "start": "node ./lib/IModelChangesetCLUtility.js", 14 | "test": "betools test --grep \"#integration\" --invert", 15 | "test:integration": "mocha --timeout 300000 --require ts-node/register --require source-map-support/register ./src/test/**/*.test.ts --grep \"#integration\"", 16 | "cover": "npm run clean-output && nyc mocha --timeout 300000 --require ts-node/register --require source-map-support/register ./src/test/**/*.test.ts" 17 | }, 18 | "author": { 19 | "name": "Bentley Systems, Inc.", 20 | "url": "http://www.bentley.com" 21 | }, 22 | "dependencies": { 23 | "@bentley/bentleyjs-core": "^2.11.0", 24 | "@bentley/context-registry-client": "^2.11.0", 25 | "@bentley/geometry-core": "^2.11.0", 26 | "@bentley/imodelhub-client": "^2.11.0", 27 | "@bentley/imodeljs-backend": "^2.11.0", 28 | "@bentley/backend-itwin-client": "^2.11.0", 29 | "@bentley/imodeljs-common": "^2.11.0", 30 | "@bentley/itwin-client": "^2.11.0", 31 | "minimist": "^1.2.0", 32 | "path": "^0.12.7" 33 | }, 34 | "devDependencies": { 35 | "@bentley/build-tools": "^2.11.0", 36 | "@types/chai": "^4.1.7", 37 | "@types/mocha": "^5.2.5", 38 | "@types/node": "^10.5.7", 39 | "chai": "^4.2.0", 40 | "debug": "^3.1.0", 41 | "mocha": "^5.2.0", 42 | "nyc": "^14.0.0", 43 | "rimraf": "^2.6.2", 44 | "source-map-support": "^0.5.6", 45 | "eslint": "^6.8.0", 46 | "ts-node": "^7.0.1", 47 | "typemoq": "^2.1.0", 48 | "typescript": "~3.7.4" 49 | }, 50 | "nyc": { 51 | "include": [ 52 | "./src/**/*.ts" 53 | ], 54 | "exclude": [ 55 | "./src/test/*", 56 | "**/*.d.ts" 57 | ], 58 | "extension": [ 59 | ".ts" 60 | ], 61 | "require": [ 62 | "source-map-support/register", 63 | "ts-node/register" 64 | ], 65 | "reporter": [ 66 | "text", 67 | "text-summary", 68 | "lcov", 69 | "cobertura" 70 | ], 71 | "report-dir": "./lib/test/coverage", 72 | "all": true 73 | }, 74 | "eslintConfig": { 75 | "extends": "./node_modules/@bentley/build-tools/.eslintrc.js", 76 | "parserOptions": { 77 | "project": [ 78 | "tsconfig.json" 79 | ] 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/src/ChangesetGenerationConfig.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /* eslint-disable @typescript-eslint/naming-convention */ 6 | 7 | import * as path from "path"; 8 | import { Config } from "@bentley/bentleyjs-core"; 9 | import { AgentAuthorizationClientConfiguration } from "@bentley/backend-itwin-client"; 10 | 11 | /** 12 | * Setup configuration for the application 13 | */ 14 | export class ChangesetGenerationConfig { 15 | public static setupConfig() { 16 | Config.App.merge({ 17 | // ----------------------------------------------------------------------------------------------------------- 18 | // Client registration details (REQUIRED) 19 | // Must set these variables before testing - create a client registration using 20 | // the developer registration procedure here - https://git.io/fx8YP. 21 | // Note: These can be set in the environment also - e.g., "set ims_agent_client_id=agent_test_client" 22 | // ----------------------------------------------------------------------------------------------------------- 23 | // imjs_agent_client_id: "Set this to client id", 24 | // imjs_agent_client_secret: "Set this to the client secret", 25 | 26 | // ----------------------------------------------------------------------------------------------------------- 27 | // Test iModel (REQUIRED) 28 | // developer registration procedure here - https://git.io/fx8YP 29 | // Note: This can be set in the environment also - e.g., "set imjs_agent_imodel_name=MyiModel" 30 | // ----------------------------------------------------------------------------------------------------------- 31 | // imjs_agent_imodel_name: "Set this to the name of the sample iModel", 32 | 33 | // ----------------------------------------------------------------------------------------------------------- 34 | // Other application settings (NOT REQUIRED) 35 | // Note: These can be set in the environment also - e.g., "set imjs_agent_project_name=MyProject" 36 | // ----------------------------------------------------------------------------------------------------------- 37 | // imjs_agent_project_name: "Set this to the name of the sample project", // In most cases, this will match the test iModel name 38 | imjs_default_relying_party_uri: "https://connect-wsg20.bentley.com", 39 | }); 40 | } 41 | 42 | public static get iModelName(): string { 43 | return Config.App.getString("imjs_agent_imodel_name"); 44 | } 45 | 46 | public static get projectName(): string { 47 | return Config.App.getString("imjs_agent_project_name", ChangesetGenerationConfig.iModelName); 48 | } 49 | 50 | public static get oidcAgentClientConfiguration(): AgentAuthorizationClientConfiguration { 51 | return { 52 | clientId: Config.App.getString("imjs_agent_client_id"), 53 | clientSecret: Config.App.getString("imjs_agent_client_secret"), 54 | scope: "urlps-third-party context-registry-service:read-only imodelhub", 55 | }; 56 | } 57 | 58 | public static get outputDir(): string { 59 | return path.join(__dirname, "output"); 60 | } 61 | 62 | public static get port(): number { 63 | return Config.App.getNumber("imjs_agent_port"); 64 | } 65 | 66 | public static get listenTime(): number { 67 | return Config.App.getNumber("imjs_agent_listen_time"); 68 | } 69 | 70 | public static get loggingCategory(): string { 71 | return "imodel-changeset-test-utility"; 72 | } 73 | 74 | public static get numChangesets(): number { 75 | return 2; 76 | } 77 | public static get numCreatedPerChangeset(): number { 78 | return 5; 79 | } 80 | public static get changesetPushDelay(): number { 81 | return 2000; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/src/IModelChangesetCLUtility.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { RequestHost } from "@bentley/backend-itwin-client"; 6 | import { ChangesetGenerationHarness } from "./ChangesetGenerationHarness"; 7 | import { TestChangesetSequence } from "./TestChangesetSequence"; 8 | import { ChangesetGenerationConfig } from "./ChangesetGenerationConfig"; 9 | 10 | class ProcessHandler { 11 | constructor(private _process: NodeJS.Process) { } 12 | public exitSuccessfully() { this._process.exit(); } 13 | public exitWithError() { this._process.exit(1); } 14 | } 15 | /** Main entry point for Command Line Utility */ 16 | export const main = async (_process: NodeJS.Process, harness?: ChangesetGenerationHarness): Promise => { 17 | 18 | // Initialize basic settings for all backend HTTP requests 19 | await RequestHost.initialize(); 20 | 21 | if (!harness) 22 | harness = new ChangesetGenerationHarness(); 23 | const processHandler = new ProcessHandler(_process); 24 | // Generate changeset sequence 25 | const changesetSequence: TestChangesetSequence = new TestChangesetSequence(ChangesetGenerationConfig.numChangesets, ChangesetGenerationConfig.numCreatedPerChangeset, 26 | ChangesetGenerationConfig.changesetPushDelay); 27 | let success = false; 28 | try { 29 | success = await harness.generateChangesets(changesetSequence); 30 | } catch { } 31 | if (success) 32 | processHandler.exitSuccessfully(); 33 | processHandler.exitWithError(); 34 | }; 35 | 36 | // Invoke main if IModelChangesetCLUtility.js is being run directly 37 | if (require.main === module) { 38 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 39 | main(process); 40 | } 41 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/src/TestChangesetSequence.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** Class containing changeset sequence meta-data. Can be used to generate changesets over elements in the iModelDb */ 7 | export class TestChangesetSequence { 8 | public readonly elementsUpdatedPerChangeset: number; 9 | public readonly elementsDeletedPerChangeset: number; 10 | public constructor(public changesetCount: number, public elementsCreatedPerChangeset: number = 2, 11 | public changesetPushDelay: number = 1000) { 12 | // Each Changeset will consist of inserting N elements, and updating and deleting N elements if they exist. 13 | // Each changeset creation/push will pause for 'changesetPushDelay' ms before creating another changeset 14 | this.elementsUpdatedPerChangeset = Math.floor(elementsCreatedPerChangeset / 2); 15 | this.elementsDeletedPerChangeset = this.elementsUpdatedPerChangeset; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/src/index.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Bentley Systems, Incorporated. All rights reserved. 3 | * See LICENSE.md in the project root for license terms and full copyright notice. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export * from "./ChangesetGenerationHarness"; 7 | export * from "./ChangesetGenerator"; 8 | export * from "./ChangesetGenerationConfig"; 9 | export * from "./HubUtility"; 10 | export * from "./IModelChangesetCLUtility"; 11 | export * from "./IModelDbHandler"; 12 | export * from "./TestChangesetSequence"; 13 | -------------------------------------------------------------------------------- /tools/imodel-changeset-test-utility/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@bentley/build-tools/tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "outDir": "./lib", 7 | "rootDir": "./src", 8 | "skipLibCheck": true 9 | }, 10 | "include": [ 11 | "./src/**/*.ts" 12 | ], 13 | "exclude": [ 14 | "lib", 15 | "node_modules" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------