├── .cosmos.config.json ├── .cosmos.webpack.js ├── .dependency-cruiser.js ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── end-to-end-tests.yml │ └── main.yml ├── .gitignore ├── .gitpod.yml ├── .husky └── pre-commit ├── .npmrc ├── Components.js ├── LICENSE.md ├── README.md ├── Styles.jsx ├── __mocks__ └── web-ifc-viewer.js ├── babel.config.js ├── bldrs-ai-conway-0.7.770.tgz ├── bldrs-ai-conway-web-ifc-adapter-0.7.770.tgz ├── bundle-analysis.json ├── cypress.config.js ├── cypress ├── e2e │ ├── apps │ │ └── apps.cy.js │ ├── create-100 │ │ └── imagine.cy.js │ ├── hide-feat │ │ └── hide-feat.cy.js │ ├── home │ │ └── about.cy.js │ ├── ifc-model │ │ └── load-sample-model.cy.js │ ├── integration │ │ └── bldrs-inside-iframe.cy.js │ ├── notes-100 │ │ ├── access-notes-list.cy.js │ │ ├── access-shared-note.cy.js │ │ ├── comment-edit-delete.cy.js │ │ ├── comments-on-a-note.cy.js │ │ ├── create-a-note.cy.js │ │ ├── delete-a-note.cy.js │ │ ├── note-contains-link-to-github-issue.cy.js │ │ ├── select-a-note.cy.js │ │ └── share-a-note.cy.js │ ├── open │ │ ├── 100 │ │ │ ├── open-model-dialog.cy.js │ │ │ ├── open-model-from-gh-ui.cy.js │ │ │ ├── open-project-from-gh-link.cy.js │ │ │ └── open-sample-model.cy.js │ │ └── 200 │ │ │ └── open-multiple-model-formats.cy.js │ ├── parallel.sh │ ├── placemarks-100 │ │ ├── marker-selection.cy.js │ │ └── marker-visibility.cy.js │ ├── profile-100 │ │ ├── login.cy.js │ │ ├── subscription.cy.js │ │ └── theme.cy.js │ ├── screenshot │ │ └── screen-from-note.cy.js │ ├── search │ │ └── 100 │ │ │ ├── githublink.cy.js │ │ │ └── permalink.cy.js │ ├── versions-100 │ │ ├── edit-a-model.cy.js │ │ ├── save-imported-model.cy.js │ │ └── show-specific-version.cy.js │ └── view-100 │ │ ├── access-element-properties.cy.js │ │ ├── cutplanes.cy.js │ │ ├── initial-model-load-and-view.cy.js │ │ ├── model-centering-and-view-reset.cy.js │ │ ├── shareable-camera-position.cy.js │ │ └── synchronized-view-and-navtree.cy.js ├── fixtures │ ├── 404.html │ ├── Momentum.ifc │ ├── TestFixture.ifc │ ├── bldrs-inside-iframe.html │ ├── bldrs-inside-iframe.js │ ├── candy-cane-bldrs.png │ ├── example.json │ ├── index.ifc │ └── test-models │ │ ├── fbx │ │ └── samba-dancing.fbx │ │ ├── obj │ │ └── Bunny.obj │ │ ├── step │ │ └── gear.step │ │ └── stl │ │ ├── pr2_head_pan.stl │ │ └── slotted_disk.stl ├── plugin │ └── index.js └── support │ ├── commands.js │ ├── e2e.js │ ├── models.js │ └── utils.js ├── netlify.toml ├── netlify └── functions │ ├── create-portal-session.js │ └── stripe-webhook.js ├── package.json ├── public ├── 404.html ├── CNAME ├── _redirects ├── event-logger-outer.html ├── favicon.ico ├── favicon.png ├── icons │ ├── LogoB.svg │ ├── LogoBWithDomain.svg │ ├── PlaceMark.svg │ ├── mod_logo.jpeg │ └── speedtest.png ├── index.html ├── index.ifc ├── logo192.png ├── logo512.png ├── mockServiceWorker.js ├── mod.html ├── mod.js ├── robots.txt ├── speedtest.png ├── subscribe │ └── index.html └── widgets │ ├── event-logger.html │ └── example.html ├── scripts ├── netlify-preinstall.js └── updateVersion.mjs ├── src ├── Auth0 │ ├── Auth0ProviderProxy.jsx │ └── Auth0Proxy.js ├── Auth0ProviderWithHistory.jsx ├── BaseRoutes.jsx ├── BaseRoutes.test.jsx ├── BaseRoutesMock.test.jsx ├── Components │ ├── About │ │ ├── AboutControl.jsx │ │ ├── AboutControl.test.jsx │ │ ├── AboutDescription.jsx │ │ ├── AboutDialog.fixture.jsx │ │ ├── AboutDialog.jsx │ │ ├── Discord.svg │ │ ├── PrivacyControl.jsx │ │ ├── PrivacyControl.test.jsx │ │ ├── component.js │ │ └── hashState.js │ ├── AlertDialog.jsx │ ├── AppBar.jsx │ ├── ApplicationError.jsx │ ├── Apps │ │ ├── AppEntry.jsx │ │ ├── AppIFrame.jsx │ │ ├── AppPanel.jsx │ │ ├── AppsControl.jsx │ │ ├── AppsListing.jsx │ │ ├── AppsMessagesHandler.js │ │ ├── AppsPanel.jsx │ │ ├── AppsRegistry.json │ │ ├── component.js │ │ └── hashState.js │ ├── Auth │ │ ├── PopupAuth.jsx │ │ └── PopupCallback.jsx │ ├── Buttons.jsx │ ├── Buttons.test.jsx │ ├── Camera │ │ ├── CameraControl.jsx │ │ ├── CameraControl.test.jsx │ │ └── hashState.js │ ├── CutPlane │ │ ├── CutPlaneMenu.jsx │ │ ├── CutPlaneMenu.test.jsx │ │ └── hashState.js │ ├── Dialog.fixture.jsx │ ├── Dialog.jsx │ ├── ElementsControl.jsx │ ├── ElementsControl.test.jsx │ ├── Help │ │ ├── HelpControl.jsx │ │ ├── HelpControl.test.jsx │ │ └── hashState.js │ ├── HideToggleButton.jsx │ ├── Hooks.jsx │ ├── Hooks.test.jsx │ ├── Imagine │ │ ├── ImagineControl.jsx │ │ ├── ImagineControl.test.jsx │ │ ├── ImagineDialog.jsx │ │ └── hashState.js │ ├── InputAutocomplete.fixture.jsx │ ├── InputAutocomplete.jsx │ ├── InputAutocomplete.test.jsx │ ├── Loader.fixture.jsx │ ├── Loader.jsx │ ├── Loader.test.jsx │ ├── LoadingBackdrop.jsx │ ├── Logo │ │ ├── Logo.fixture.jsx │ │ ├── Logo.jsx │ │ └── Logo.test.jsx │ ├── MarkdownBlogPost.jsx │ ├── Markers │ │ ├── Marker.fixture.js │ │ ├── MarkerControl.jsx │ │ ├── MarkerControl.test.jsx │ │ ├── component.js │ │ └── hashState.js │ ├── NavTree │ │ ├── NavTreeControl.jsx │ │ ├── NavTreeControl.test.jsx │ │ ├── NavTreeNode.jsx │ │ ├── NavTreePanel.jsx │ │ └── hashState.js │ ├── NoContent.jsx │ ├── Notes │ │ ├── NoteBody.jsx │ │ ├── NoteBodyEdit.jsx │ │ ├── NoteCard.jsx │ │ ├── NoteCard.test.jsx │ │ ├── NoteCardCreate.jsx │ │ ├── NoteCardCreate.test.jsx │ │ ├── NoteContent.jsx │ │ ├── NoteFooter.jsx │ │ ├── NoteMenu.jsx │ │ ├── Notes.fixture.js │ │ ├── Notes.jsx │ │ ├── Notes.test.jsx │ │ ├── NotesControl.jsx │ │ ├── NotesControl.test.jsx │ │ ├── NotesNavBar.jsx │ │ ├── NotesNavBar.test.jsx │ │ ├── NotesPanel.jsx │ │ ├── NotesPanel.test.jsx │ │ ├── component.js │ │ └── hashState.js │ ├── Open │ │ ├── GitHubFileBrowser.jsx │ │ ├── GitHubFileBrowser.test.jsx │ │ ├── OpenModelControl.fixture.jsx │ │ ├── OpenModelControl.jsx │ │ ├── OpenModelControl.test.jsx │ │ ├── OpenModelDialog.jsx │ │ ├── PleaseLogin.jsx │ │ ├── SampleModelFileSelector.jsx │ │ ├── SampleModels.jsx │ │ ├── SaveModelControl.fixture.jsx │ │ ├── SaveModelControl.jsx │ │ ├── SaveModelControl.test.jsx │ │ ├── Selector.jsx │ │ ├── SelectorSeparator.jsx │ │ ├── component.js │ │ └── hashState.js │ ├── Profile │ │ ├── ProfileControl.jsx │ │ └── ProfileControl.test.jsx │ ├── Properties │ │ ├── ExpansionPanel.jsx │ │ ├── Properties.jsx │ │ ├── Properties.test.jsx │ │ ├── Properties.testobj.json │ │ ├── PropertiesControl.jsx │ │ ├── PropertiesPanel.jsx │ │ ├── PropertiesPanel.test.jsx │ │ ├── component.js │ │ ├── hashState.js │ │ └── itemProperties.jsx │ ├── Search │ │ ├── SearchBar.jsx │ │ ├── SearchBar.test.jsx │ │ ├── SearchControl.jsx │ │ └── hashState.js │ ├── Share │ │ ├── ShareControl.jsx │ │ ├── ShareControl.test.jsx │ │ └── hashState.js │ ├── ShortcutsControl.jsx │ ├── SideDrawer │ │ ├── HorizonResizerButton.jsx │ │ ├── Panel.jsx │ │ ├── PanelTitle.jsx │ │ ├── SideDrawer.jsx │ │ ├── SideDrawer.test.jsx │ │ └── VerticalResizerButton.jsx │ ├── Stripe │ │ ├── PricingDialog.jsx │ │ └── PricingTable.jsx │ ├── TableRow.fixture.jsx │ ├── TableRow.jsx │ ├── TableRow.test.jsx │ ├── Tabs.fixture.jsx │ ├── Tabs.jsx │ ├── Toast.fixture.jsx │ ├── Toast.jsx │ ├── Toast.test.jsx │ ├── Toggle.jsx │ ├── TooltipIconButton.fixture.jsx │ ├── UserProfile.jsx │ ├── Versions │ │ ├── VersionsControl.jsx │ │ ├── VersionsPanel.fixture.js │ │ ├── VersionsPanel.jsx │ │ ├── VersionsPanel.test.jsx │ │ ├── VersionsTimeline.fixture.jsx │ │ ├── VersionsTimeline.jsx │ │ ├── VersionsTimeline.test.jsx │ │ ├── component.js │ │ ├── hashState.js │ │ ├── useVersions.jsx │ │ └── useVersions.test.jsx │ └── buttons │ │ ├── ControlButton.fixture.jsx │ │ └── TooltipIconButton.stories.jsx ├── Containers │ ├── AlertDialogAndSnackbar.jsx │ ├── AppsSideDrawer.jsx │ ├── AppsSideDrawer.test.jsx │ ├── BottomBar.jsx │ ├── CadView.jsx │ ├── CadView.test.jsx │ ├── ControlsGroup.jsx │ ├── NavTreeAndVersionsDrawer.jsx │ ├── NavTreeAndVersionsDrawer.test.jsx │ ├── NotesAndPropertiesDrawer.jsx │ ├── NotesAndPropertiesDrawer.test.jsx │ ├── OperationsGroup.jsx │ ├── OperationsGroup.test.jsx │ ├── OperationsGroupAndDrawer.jsx │ ├── RightSideDrawers.jsx │ ├── RightSideDrawers.test.jsx │ ├── RootLandscape.jsx │ ├── TabbedPanels.jsx │ ├── TabbedPanels.test.jsx │ ├── ViewerContainer.jsx │ ├── ViewerContainer.test.jsx │ ├── index.ifc │ ├── selection.js │ ├── urls.js │ ├── urls.test.js │ └── viewer.js ├── FeatureFlags.js ├── Filetype.js ├── Filetype.test.js ├── Infrastructure │ ├── ColorHelperFunctions.js │ ├── ColorHelperFunctions.test.js │ ├── CustomPostProcessor.js │ ├── IfcColor.js │ ├── IfcCustomViewSettings.js │ ├── IfcElementsStyleManager.js │ ├── IfcHighlighter.js │ ├── IfcIsolator.js │ ├── IfcViewerAPIExtended.js │ ├── PlaceMark.js │ └── ViewRulesCompiler.js ├── OPFS │ ├── OPFS.worker.js │ ├── OPFS.worker.test.js │ ├── OPFSService.js │ ├── OPFSService.test.js │ ├── utils.js │ └── utils.test.js ├── Share.fixture.jsx ├── Share.jsx ├── Share.test.jsx ├── ShareMock.jsx ├── ShareRoutes.jsx ├── Styles.jsx ├── Transitions │ └── SlideDown.jsx ├── WidgetApi │ ├── ApiConnection.js │ ├── ApiConnectionIframe.js │ ├── ApiEventsRegistry.js │ ├── Utils.js │ ├── WidgetApi.js │ ├── event-dispatchers │ │ ├── ApiEventDispatcher.js │ │ ├── ElementSelectionChangedEventDispatcher.js │ │ ├── HiddenElementsEventDispatcher.js │ │ └── ModelLoadedEventDispatcher.js │ └── event-handlers │ │ ├── ApiEventHandler.js │ │ ├── ChangeViewSettingsEventHandler.js │ │ ├── HideElementsEventHandler.js │ │ ├── HighlightElementsEventHandler.js │ │ ├── LoadModelEventHandler.js │ │ ├── SelectElementsEventHandler.js │ │ ├── SuppressAboutDialogHandler.js │ │ ├── UIComponentsVisibilityEventHandler.js │ │ └── UnhideElementsEventHandler.js ├── __mocks__ │ ├── MockComponent.jsx │ ├── MockModel.js │ ├── api-handlers.js │ ├── authentication.js │ ├── browser.js │ └── server.js ├── assets │ ├── LogoB.svg │ ├── LogoBWithDomain.svg │ ├── Logo_Buildings.svg │ ├── README.md │ ├── icons │ │ ├── Add.svg │ │ ├── AddImage.svg │ │ ├── AddNote.svg │ │ ├── Attention.svg │ │ ├── Back.svg │ │ ├── Bldrs.svg │ │ ├── Bot1.svg │ │ ├── Bot2.svg │ │ ├── Bot3.svg │ │ ├── Bot4.svg │ │ ├── Bplaza.svg │ │ ├── Camera.svg │ │ ├── Caret.svg │ │ ├── Clear.svg │ │ ├── Close.svg │ │ ├── Comment.svg │ │ ├── CutPlane.svg │ │ ├── Delete.svg │ │ ├── Eisvogel.svg │ │ ├── Elevation.svg │ │ ├── Expand.svg │ │ ├── Gear.svg │ │ ├── Glasses.svg │ │ ├── Knowledge.svg │ │ ├── Levels.svg │ │ ├── Link.svg │ │ ├── List.svg │ │ ├── LocalFileOpen.svg │ │ ├── Menu.svg │ │ ├── Model.svg │ │ ├── Momentum.svg │ │ ├── NavNext.svg │ │ ├── NavPrev.svg │ │ ├── Notes.svg │ │ ├── Open.svg │ │ ├── Openifc.svg │ │ ├── Pin.svg │ │ ├── Placeholder.svg │ │ ├── Plan.svg │ │ ├── PlanView.svg │ │ ├── Question.svg │ │ ├── QuestionGraphic.svg │ │ ├── SaveGraphic.svg │ │ ├── Schependomlaan.svg │ │ ├── Search.svg │ │ ├── Section.svg │ │ ├── Seestrasse.svg │ │ ├── Select.svg │ │ ├── Share.svg │ │ ├── Sheenstock.svg │ │ ├── Shift.svg │ │ ├── Submit.svg │ │ ├── Synch.svg │ │ ├── Tree.svg │ │ ├── Upload.svg │ │ ├── Warning.svg │ │ └── vitruvian-man.webp │ └── logo.html ├── hooks │ ├── useExistInFeature.js │ ├── useExistInFeature.test.jsx │ ├── usePageTracking.jsx │ └── usePageTracking.test.jsx ├── index.jsx ├── layouts │ ├── AboutLayout.jsx │ ├── BlogLayout.jsx │ ├── BlogPostLayout.jsx │ └── TitledLayout.jsx ├── loader │ ├── BLDLoader.js │ ├── BLDLoader.test.js │ ├── Loader.js │ ├── Loader.test.js │ ├── glb.js │ ├── matcher.js │ ├── matcher.test.js │ ├── obj.js │ ├── pdb.js │ ├── stl.js │ ├── urls.js │ ├── urls.test.js │ └── xyz.js ├── net │ └── github │ │ ├── Branches.fixture.js │ │ ├── Branches.js │ │ ├── Cache.js │ │ ├── Comments.fixture.js │ │ ├── Comments.js │ │ ├── Comments.test.js │ │ ├── Commits.fixture.js │ │ ├── Commits.js │ │ ├── Commits.test.js │ │ ├── Files.fixture.js │ │ ├── Files.js │ │ ├── Files.test.js │ │ ├── Http.js │ │ ├── Http.test.js │ │ ├── Issues.fixture.js │ │ ├── Issues.js │ │ ├── Issues.test.js │ │ ├── OctokitExport.js │ │ ├── Organizations.fixture.js │ │ ├── Organizations.js │ │ ├── Organizations.test.js │ │ ├── Repositories.fixture.js │ │ ├── Repositories.js │ │ ├── Repositories.test.js │ │ ├── utils.js │ │ └── utils.test.js ├── pages │ ├── About.jsx │ ├── About.test.jsx │ ├── blog │ │ ├── BlogIndex.jsx │ │ ├── BlogIndex.test.jsx │ │ ├── BlogRoutes.jsx │ │ ├── Post20241205.md │ │ └── Post20250225.md │ └── share │ │ ├── About.jsx │ │ ├── About.test.jsx │ │ └── Conway.jsx ├── privacy │ ├── Expires.js │ ├── analytics.js │ ├── analytics.test.js │ ├── firstTime.js │ ├── firstTime.test.js │ ├── preferences.js │ └── preferences.test.js ├── reportWebVitals.js ├── search │ └── SearchIndex.js ├── store │ ├── AppsSlice.js │ ├── BrowserSlice.js │ ├── CutPlanesSlice.js │ ├── IFCSlice.js │ ├── IfcIsolatorSlice.js │ ├── NavTreeSlice.js │ ├── NotesSlice.js │ ├── OpenSlice.js │ ├── PropertiesSlice.js │ ├── RepositorySlice.js │ ├── SearchSlice.js │ ├── ShareSlice.js │ ├── SideDrawerSlice.jsx │ ├── Store.fixture.jsx │ ├── Store.test.jsx │ ├── UIEnabledSlice.js │ ├── UISlice.js │ ├── VersionsSlice.js │ ├── useStore.js │ └── useStore.test.js ├── subscribe │ ├── index.css │ └── index.jsx ├── theme │ ├── Colors.js │ ├── Components.js │ ├── Palette.js │ ├── Theme.fixture.jsx │ ├── Theme.jsx │ ├── Theme.test.jsx │ ├── Typography.js │ └── tron-palette.png ├── utils │ ├── IfcMock.test.js │ ├── TreeUtils.js │ ├── TreeUtils.test.js │ ├── __snapshots__ │ │ └── svg.test.js.snap │ ├── alertTracking.js │ ├── arrays.js │ ├── assert.js │ ├── assert.test.js │ ├── color.js │ ├── common.js │ ├── constants.js │ ├── cookies.js │ ├── debug.js │ ├── event.js │ ├── gui-creator.js │ ├── ifc.js │ ├── ifc.test.js │ ├── loader.js │ ├── loader.test.js │ ├── location.js │ ├── location.test.js │ ├── math.js │ ├── math.test.js │ ├── mediaQuery.js │ ├── mediaQuery.test.js │ ├── navigate.js │ ├── network.js │ ├── objects.js │ ├── objects.test.js │ ├── shortcutKeys.js │ ├── strings.js │ ├── strings.test.js │ ├── svg.js │ ├── svg.test.js │ └── tests.js └── view │ ├── Picker.js │ └── Selection.js ├── tools ├── cy-percy.sh ├── esbuild │ ├── build.js │ ├── bundle-analysis.json │ ├── certificate │ │ ├── server.cert │ │ └── server.key │ ├── common.js │ ├── defines.js │ ├── defines.test.js │ ├── esbuild.test.js │ ├── package.json │ ├── plugins.js │ ├── proxy.js │ ├── serve.js │ ├── utils.js │ ├── vars.cypress.js │ ├── vars.dev.js │ └── vars.prod.js ├── imgdiff.sh ├── jest │ ├── common.js │ ├── jest-tools.config.js │ ├── jest.config.js │ ├── mdTransform.js │ ├── package.json │ ├── setupNodeFetch.cjs │ ├── setupTests.js │ ├── svgTransform.js │ ├── testEnvVars.js │ └── vars.jest.js └── tsconfig.json ├── web-ifc-viewer-1.0.209-bldrs-7.tgz └── yarn.lock /.cosmos.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hot": "true", 3 | "watchDirs": ["src"], 4 | "hostname": "0.0.0.0", 5 | "port": "8080", 6 | "publicUrl": "./", 7 | "webpack": { 8 | "configPath": ".cosmos.webpack.js" 9 | }, 10 | "plugins": ["react-cosmos-plugin-webpack"] 11 | } 12 | -------------------------------------------------------------------------------- /.cosmos.webpack.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin') 2 | const path = require('path') 3 | 4 | 5 | module.exports = { 6 | target: ['web', 'es2021'], 7 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', 8 | devServer: { 9 | publicPath: '/cosmos/', 10 | contentBase: path.join(__dirname, 'docs'), 11 | compress: true, 12 | port: 8000, 13 | }, 14 | output: { 15 | path: path.resolve(__dirname, 'docs'), 16 | publicPath: '/cosmos/', 17 | filename: 'index.html', 18 | }, 19 | plugins: [new HtmlWebpackPlugin()], 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.(js|jsx)$/, 24 | exclude: /node_modules\/(?!@bldrs-ai\\conway)/, // Exclude all of node_modules except @bldrs-ai 25 | use: { 26 | loader: 'babel-loader', 27 | options: { 28 | presets: ['@babel/preset-react'], 29 | }, 30 | }, 31 | }, 32 | { 33 | test: /\.svg$/i, 34 | use: { 35 | loader: '@svgr/webpack', 36 | options: { 37 | dimensions: false 38 | } 39 | } 40 | }, 41 | ], 42 | }, 43 | resolve: { 44 | extensions: ['.js', '.jsx', '.svg'], 45 | }, 46 | optimization: { 47 | minimize: false 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png diff=imagediff 2 | # *.png diff=bindiff 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: bldrs 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build & Unit Tests (jest) 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | env: 14 | SHARE_CONFIG: dev 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: 18 24 | 25 | - name: Create temporary .npmrc for GitHub Actions 26 | run: | 27 | echo "@bldrs-ai:registry=https://npm.pkg.github.com" > .npmrc 28 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 29 | 30 | - name: Run a multi-line script 31 | run: | 32 | echo Running tests 33 | yarn install 34 | yarn lint 35 | yarn test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # no longer check-in docs for builds. This is handled by Netlify 5 | /docs 6 | 7 | cypress/downloads 8 | cypress/e2e/logs 9 | 10 | /coverage 11 | 12 | # symlink for test-models repo 13 | public/test-models 14 | 15 | # misc 16 | .DS_Store 17 | .vscode 18 | .idea 19 | *.log 20 | *~ 21 | \#* 22 | .\#* 23 | 24 | # Local Netlify folder 25 | .netlify 26 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-full 2 | 3 | ports: 4 | # Server 5 | - port: 8080 6 | onOpen: open-browser 7 | 8 | tasks: 9 | - init: yarn install 10 | command: yarn serve 11 | 12 | github: 13 | prebuilds: 14 | branches: true 15 | pullRequests: true 16 | addCheck: false 17 | 18 | vscode: 19 | extensions: 20 | - dbaeumer.vscode-eslint 21 | - esbenp.prettier-vscode -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn precommit 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | '@babel/preset-react', 5 | ], 6 | env: { 7 | test: { 8 | presets: [ 9 | ['@babel/preset-env', {targets: {node: 'current'}}], 10 | ], 11 | }, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /bldrs-ai-conway-0.7.770.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/bldrs-ai-conway-0.7.770.tgz -------------------------------------------------------------------------------- /bldrs-ai-conway-web-ifc-adapter-0.7.770.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/bldrs-ai-conway-web-ifc-adapter-0.7.770.tgz -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const {defineConfig} = require('cypress') 2 | 3 | 4 | module.exports = import('./tools/esbuild/vars.cypress.js').then(({ 5 | default: vars, 6 | }) => { 7 | return defineConfig({ 8 | projectId: 'z36jue', 9 | e2e: { 10 | fileServerFolder: 'docs/', 11 | port: 61725, 12 | screenshotOnRunFailure: false, 13 | video: false, 14 | pageLoadTimeout: 15000, 15 | setupNodeEvents(on, config) { 16 | on('task', { 17 | log(message) { 18 | console.warn(message) // Logs message to the terminal (GitHub Actions output) 19 | return null 20 | }, 21 | }) 22 | }, 23 | }, 24 | env: { 25 | // Used in support/models.js to setup intercepts, should match what code 26 | // under tests will be using. 27 | AUTH0_DOMAIN: vars.AUTH0_DOMAIN, 28 | GITHUB_BASE_URL: vars.GITHUB_BASE_URL, 29 | GITHUB_BASE_URL_UNAUTHENTICATED: vars.GITHUB_BASE_URL_UNAUTHENTICATED, 30 | MSW_IS_ENABLED: true, 31 | OAUTH2_CLIENT_ID: vars.OAUTH2_CLIENT_ID, 32 | // TODO(pablo): cypress chrome seems to not have OPFS, so using original 33 | // instead of RAW_GIT_PROXY_URL_NEW 34 | RAW_GIT_PROXY_URL: vars.RAW_GIT_PROXY_URL, 35 | RAW_GIT_PROXY_URL_NEW: vars.RAW_GIT_PROXY_URL_NEW, 36 | }, 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /cypress/e2e/hide-feat/hide-feat.cy.js: -------------------------------------------------------------------------------- 1 | describe('Ifc Hide/Unhide E2E test suite', () => { 2 | context('Hide icon toggle', () => { 3 | beforeEach(() => { 4 | cy.setCookie('isFirstTime', '1') 5 | cy.visit('/') 6 | }) 7 | 8 | it.skip('should toggle hide icon when clicked', () => { 9 | cy.findByTestId('Navigation').click() 10 | cy.findByTestId('Navigation_panel').should('exist').click() 11 | cy.findByTestId('hide-icon').should('be.visible').click() 12 | cy.findByTestId('unhide-icon').should('exist') 13 | cy.findByTestId('hide-icon').should('not.exist') 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /cypress/e2e/home/about.cy.js: -------------------------------------------------------------------------------- 1 | import { 2 | ABOUT_MISSION, 3 | ABOUT_PAGE_TITLE, 4 | } from '../../../src/Components/About/component' 5 | 6 | 7 | /** {@link https://github.com/bldrs-ai/Share/issues/1285}*/ 8 | describe('About', () => { 9 | context('First-time user visits homepage', () => { 10 | before(() => { 11 | cy.clearCookies() 12 | cy.visit('/') 13 | }) 14 | it('about dialog is displayed', () => { 15 | cy.findByRole('dialog') 16 | .should('exist') 17 | .should('be.visible') 18 | .contains(ABOUT_MISSION) 19 | cy.title().should('eq', ABOUT_PAGE_TITLE) 20 | }) 21 | }) 22 | context('Returning use visits homepage', () => { 23 | before(() => { 24 | cy.setCookie('isFirstTime', '1') 25 | cy.visit('/') 26 | }) 27 | it('about dialog is not displayed', () => { 28 | cy.findByRole('dialog', {timeout: 300000}) 29 | .should('not.exist') 30 | }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /cypress/e2e/notes-100/access-notes-list.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import {TITLE_NOTES} from '../../../src/Components/Notes/component' 3 | import {homepageSetup, returningUserVisitsHomepageWaitForModel, auth0Login} from '../../support/utils' 4 | 5 | 6 | /** {@link https://github.com/bldrs-ai/Share/issues/1054} */ 7 | describe('Notes 100: Access notes list', () => { 8 | beforeEach(homepageSetup) 9 | context('Returning user visits homepage', () => { 10 | beforeEach(returningUserVisitsHomepageWaitForModel) 11 | context('Open Notes', () => { 12 | beforeEach(() => cy.get('[data-testid="control-button-notes"]').click()) 13 | it('Notes visible - Screen', () => { 14 | cy.get('[data-testid="list-notes"]') 15 | cy.get(`[data-testid="PanelTitle-${TITLE_NOTES}"]`).contains(TITLE_NOTES) 16 | cy.percySnapshot() 17 | }) 18 | }) 19 | context('Open Notes - authenticated', () => { 20 | beforeEach(() => { 21 | auth0Login() 22 | cy.get('[data-testid="control-button-notes"]').click() 23 | }) 24 | it('Notes visible - Screen', () => { 25 | cy.get('[data-testid="list-notes"]') 26 | cy.get(`[data-testid="PanelTitle-${TITLE_NOTES}"]`).contains(TITLE_NOTES) 27 | cy.percySnapshot() 28 | }) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /cypress/e2e/notes-100/delete-a-note.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import {homepageSetup, returningUserVisitsHomepageWaitForModel, auth0Login} from '../../support/utils' 3 | 4 | /** {@link https://github.com/bldrs-ai/Share/issues/1058} */ 5 | describe('edit a note', () => { 6 | context('User visits homepage in the logged-in state', () => { 7 | beforeEach(() => { 8 | homepageSetup() 9 | returningUserVisitsHomepageWaitForModel() 10 | cy.visit('/share/v/p/index.ifc#c:-133.022,131.828,161.85,-38.078,22.64,-2.314') 11 | auth0Login() 12 | }) 13 | it('Correct project to be loaded into the viewport and side drawer to be open - Screen', () => { 14 | cy.get('[data-testid="control-button-notes"]').click() 15 | cy.get(`:nth-child(1) > [data-testid="note-card"] [data-testid="note-menu-button"]`).click() 16 | cy.percySnapshot() 17 | cy.get('.MuiList-root > [tabindex="-1"]').click() 18 | // ToDo: the final check with the deleted note disappearing from the list 19 | // will be implemented when Pablo finished the github store mock 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /cypress/e2e/open/100/open-project-from-gh-link.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import { 3 | homepageSetup, 4 | setIsReturningUser, 5 | visitHomepageWaitForModel, 6 | } from '../../../support/utils' 7 | import { 8 | setupVirtualPathIntercept, 9 | waitForModelReady, 10 | } from '../../../support/models' 11 | 12 | 13 | /** {@link https://github.com/bldrs-ai/Share/issues/765} */ 14 | describe('Open 100: Open Project From GitHub Link', () => { 15 | beforeEach(homepageSetup) 16 | context('Returning user visits homepage, enters Model URL into search', () => { 17 | const interceptTag = 'ghModelLoad' 18 | beforeEach(() => { 19 | setIsReturningUser() 20 | visitHomepageWaitForModel() 21 | cy.get('[data-testid="control-button-search"]').click() 22 | setupVirtualPathIntercept( 23 | '/share/v/gh/Swiss-Property-AG/Momentum-Public/main/Momentum.ifc', 24 | '/Momentum.ifc', 25 | interceptTag, 26 | ) 27 | // Note this includes {enter} at end to simulate Enter keypress 28 | cy.get('[data-testid="textfield-search-query"]') 29 | .type('https://github.com/Swiss-Property-AG/Momentum-Public/blob/main/Momentum.ifc{enter}') 30 | }) 31 | 32 | // TODO(https://github.com/bldrs-ai/Share/issues/1269): fix and re-enable 33 | it.skip('Model loads - Screen', () => { 34 | waitForModelReady(interceptTag) 35 | cy.percySnapshot() 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /cypress/e2e/parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # yarn install 3 | # yarn cy-build 4 | LOGS_DIR=cypress/e2e/logs 5 | mkdir -p $LOGS_DIR 6 | 7 | run_cy_spec() { 8 | local epic=$1 9 | local specs=$2 10 | local log="$LOGS_DIR/$epic.log" 11 | ( 12 | yarn cy-spec $specs > $log 2> $log.err 13 | echo -n "$epic " 14 | tail -4 $log | egrep '(passed|failed)' 15 | )& 16 | } 17 | 18 | echo "Running cypress specs in parallel..." 19 | 20 | # Misc 21 | run_cy_spec misc cypress/e2e/apps,cypress/e2e/hide-feat,cypress/e2e/home,cypress/e2e/ifc-model,cypress/e2e/integration 22 | 23 | # Then conventional 24 | for EPIC in create-100 open notes-100 profile-100 versions-100 view-100 ; do 25 | SPECS="cypress/e2e/$EPIC" 26 | run_cy_spec $EPIC "$SPECS" 27 | done 28 | 29 | wait 30 | echo "All specs finished. See $LOGS_DIR for output from each spec" 31 | -------------------------------------------------------------------------------- /cypress/e2e/profile-100/login.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import { 3 | auth0Login, 4 | homepageSetup, 5 | returningUserVisitsHomepageWaitForModel, 6 | } from '../../support/utils' 7 | 8 | 9 | /** {@link https://github.com/bldrs-ai/Share/issues/1052} */ 10 | describe('Profile 100: Login', () => { 11 | beforeEach(homepageSetup) 12 | context('Returning user visits homepage, clicks ProfileControl', () => { 13 | beforeEach(returningUserVisitsHomepageWaitForModel) 14 | 15 | it('Should Login - Screen', () => { 16 | auth0Login() 17 | cy.percySnapshot() 18 | }) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /cypress/e2e/profile-100/theme.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import { 3 | homepageSetup, 4 | returningUserVisitsHomepageWaitForModel, 5 | waitForModel, 6 | } from '../../support/utils' 7 | 8 | 9 | /** {@link https://github.com/bldrs-ai/Share/issues/1070} */ 10 | describe('Profile 100: Theme', () => { 11 | beforeEach(homepageSetup) 12 | context('Returning user visits homepage', () => { 13 | beforeEach(returningUserVisitsHomepageWaitForModel) 14 | 15 | it('Day theme active - Screen', () => cy.percySnapshot()) 16 | 17 | context('Select ProfileControl > Night theme', () => { 18 | beforeEach(() => { 19 | cy.get('[data-testid="control-button-profile"]').click() 20 | cy.get('[data-testid="change-theme-to-night"]').click() 21 | waitForModel() 22 | }) 23 | 24 | it('Night theme active - Screen', () => cy.percySnapshot()) 25 | 26 | context('Select ProfileControl > Day theme', () => { 27 | beforeEach(() => { 28 | cy.get('[data-testid="control-button-profile"]').click() 29 | cy.get('[data-testid="change-theme-to-day"]').click() 30 | waitForModel() 31 | }) 32 | 33 | it('Day theme active - Screen', () => cy.percySnapshot()) 34 | }) 35 | }) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /cypress/e2e/screenshot/screen-from-note.cy.js: -------------------------------------------------------------------------------- 1 | describe('Note screenshot', () => { 2 | context('enable/disable feature using url parameter', () => { 3 | beforeEach(() => { 4 | cy.setCookie('isFirstTime', '1') 5 | cy.visit('/') 6 | }) 7 | 8 | it.skip('should not show screenshot button when url param not present', () => { 9 | cy.findByRole('button', {name: /Take Screenshot/}).should('not.exist') 10 | }) 11 | 12 | it.skip('should show screenshot when url param present', () => { 13 | cy.visit('/share/v/p/index.ifc?feature=screenshot') 14 | cy.get('[title="Notes"]').click() 15 | cy.get('button[title="Take Screenshot"]').should('exist') 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /cypress/e2e/search/100/githublink.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import { 3 | setupVirtualPathIntercept, 4 | waitForModelReady, 5 | } from '../../../support/models' 6 | import { 7 | homepageSetup, 8 | setIsReturningUser, 9 | visitHomepageWaitForModel, 10 | } from '../../../support/utils' 11 | 12 | 13 | describe('Search 100: GitHub Link', () => { 14 | const interceptTag = 'ghModelLoad' 15 | 16 | beforeEach(() => { 17 | homepageSetup() 18 | setIsReturningUser() 19 | }) 20 | 21 | context('Returning user visits homepage, Open Search > Enters GitHub link to Momentum', () => { 22 | beforeEach(() => { 23 | visitHomepageWaitForModel() 24 | setupVirtualPathIntercept( 25 | '/share/v/gh/Swiss-Property-AG/Momentum-Public/main/Momentum.ifc', 26 | '/Momentum.ifc', 27 | interceptTag, 28 | ) 29 | cy.get('[data-testid="control-button-search"]').click() 30 | cy.get('[data-testid="textfield-search-query"]') 31 | .type('https://github.com/Swiss-Property-AG/Momentum-Public/blob/main/Momentum.ifc{enter}') 32 | }) 33 | 34 | // TODO(https://github.com/bldrs-ai/Share/issues/1269): fix and re-enable 35 | it.skip('Momentum Loads Successfully - Screen', () => { 36 | waitForModelReady(interceptTag) 37 | cy.percySnapshot() 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /cypress/e2e/search/100/permalink.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import {waitForModelReady} from '../../../support/models' 3 | import { 4 | homepageSetup, 5 | setIsReturningUser, 6 | visitHomepageWaitForModel, 7 | } from '../../../support/utils' 8 | 9 | 10 | /** {@link https://github.com/bldrs-ai/Share/issues/1180} */ 11 | describe('Search 100: Permalink', () => { 12 | beforeEach(() => { 13 | homepageSetup() 14 | setIsReturningUser() 15 | }) 16 | 17 | context('Returning user visits homepage, Open Search > Enters "together"', () => { 18 | beforeEach(() => { 19 | visitHomepageWaitForModel() 20 | cy.get('[data-testid="control-button-search"]').click() 21 | cy.get('[data-testid="textfield-search-query"]').type('together{enter}') 22 | }) 23 | 24 | it('Search box with query visible, "Together" items highlighted in tree and scene - Screen', () => { 25 | cy.percySnapshot() 26 | }) 27 | }) 28 | 29 | context('Returning user visits permalink to "together" search', () => { 30 | beforeEach(() => { 31 | cy.visit('/share/v/p/index.ifc?q=together#n:;s:') 32 | waitForModelReady('bounceSearch') 33 | }) 34 | 35 | it('Search box with query visible, "Together" items highlighted in tree and scene - Screen', () => { 36 | cy.percySnapshot() 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /cypress/e2e/view-100/access-element-properties.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import {TITLE} from '../../../src/Components/Properties/component' 3 | import { 4 | homepageSetup, 5 | setIsReturningUser, 6 | } from '../../support/utils' 7 | import { 8 | waitForModelReady, 9 | } from '../../support/models' 10 | 11 | 12 | /** {@link https://github.com/bldrs-ai/Share/issues/1242} */ 13 | describe('View 100: Access elements property', () => { 14 | beforeEach(() => { 15 | homepageSetup() 16 | setIsReturningUser() 17 | }) 18 | 19 | context('User visits permalink to selected element and clicks properties control', () => { 20 | const interceptTag = 'twoLevelSelect' 21 | beforeEach(() => { 22 | cy.intercept('GET', '/share/v/p/index.ifc/81/621', {fixture: '404.html'}).as('twoLevelSelect') 23 | cy.visit('/share/v/p/index.ifc/81/621') 24 | waitForModelReady(interceptTag) 25 | cy.get('[data-testid="control-button-properties"]').click() 26 | }) 27 | 28 | it('Side drawer containing properties shall be visible', () => { 29 | cy.get('[data-testid="control-button-properties"]').should('be.visible') 30 | cy.get(`[data-testid="PanelTitle-${TITLE}"]`).contains(TITLE) 31 | cy.percySnapshot() 32 | }) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /cypress/e2e/view-100/model-centering-and-view-reset.cy.js: -------------------------------------------------------------------------------- 1 | import '@percy/cypress' 2 | import { 3 | homepageSetup, 4 | setIsReturningUser, 5 | waitForModel, 6 | } from '../../support/utils' 7 | 8 | 9 | /** {@link https://github.com/bldrs-ai/Share/issues/1042} */ 10 | describe('view 100: Model centering and view reset', () => { 11 | beforeEach(() => { 12 | homepageSetup() 13 | setIsReturningUser() 14 | cy.visit('/share/v/p/index.ifc#c:-38.078,-196.189,-2.314,-38.078,22.64,-2.314') 15 | waitForModel() 16 | }) 17 | 18 | /** 19 | * This is just testing that auto-zoom works. Not really user-facing behavior. 20 | * [Discord]{@link https://discord.com/channels/853953158560743424/984184622621540352/1229766172199616584} 21 | */ 22 | it('Model re-centered with when camera hash removed - Screen', () => { 23 | cy.visit('/share/v/p/index.ifc') 24 | waitForModel() 25 | cy.percySnapshot() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /cypress/fixtures/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BLDRS - Redirect 7 | 8 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /cypress/fixtures/TestFixture.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('IFC4'),'2;1'); 4 | FILE_NAME('example.ifc','2018-08-8',(''),(''),'','',''); 5 | FILE_SCHEMA(('IFC4')); 6 | ENDSEC; 7 | DATA; 8 | #100=IFCPROJECT('UUID-Project',$,'Proxy with extruded box',$,$,$,$,(#201),#301); 9 | #201=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$); 10 | #202=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#201,$,.MODEL_VIEW.,$); 11 | #301=IFCUNITASSIGNMENT((#311)); 12 | #311=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); 13 | #500=IFCBUILDING('UUID-building',$,'Test Building',$,$,#511,$,$,.ELEMENT.,$,$,$); 14 | #519=IFCRELAGGREGATES('UUID-RelAggregates',$,$,$,#100,(#500)); 15 | #1000=IFCBUILDINGELEMENTPROXY('UUID-Proxy',$,'Proxy','sample proxy',$,$,#1010,$,$); 16 | #1010=IFCPRODUCTDEFINITIONSHAPE($,$,(#1020)); 17 | #1020=IFCSHAPEREPRESENTATION(#202,'Body','SweptSolid',(#1021)); 18 | #1021=IFCEXTRUDEDAREASOLID(#1022,$,#1034,1.); 19 | #1022=IFCRECTANGLEPROFILEDEF(.AREA.,'1m x 1m rectangle',$,1.,1.); 20 | #1034=IFCDIRECTION((0.,0.,1.)); 21 | #10000=IFCRELCONTAINEDINSPATIALSTRUCTURE('UUID-Spatial',$,'Physical model',$,(#1000),#500); 22 | ENDSEC; 23 | END-ISO-10303-21; -------------------------------------------------------------------------------- /cypress/fixtures/candy-cane-bldrs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/cypress/fixtures/candy-cane-bldrs.png -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/fixtures/test-models/fbx/samba-dancing.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/cypress/fixtures/test-models/fbx/samba-dancing.fbx -------------------------------------------------------------------------------- /cypress/fixtures/test-models/stl/pr2_head_pan.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/cypress/fixtures/test-models/stl/pr2_head_pan.stl -------------------------------------------------------------------------------- /cypress/plugin/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | } 3 | -------------------------------------------------------------------------------- /cypress/support/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Setup intercept for virtual paths in share. There will also be an 3 | * interceptTag created for the bounce page as `${interceptTag}-bounce` 4 | * 5 | * @param {string} path Like /bldrs-ai/test-models/blob/main/step/gear.step.ifc 6 | * @param {string} tag Like 'loadGear' 7 | */ 8 | export function setupVirtualPathIntercept(path, fixturePath, interceptTag) { 9 | const sharePrefix = '/share/v/gh' 10 | if (!path.startsWith(sharePrefix)) { 11 | throw new Error(`Path must start with ${sharePrefix}`) 12 | } 13 | cy.intercept('GET', `${path}`, {fixture: '404.html'}) 14 | .as(`${interceptTag}-bounce`) 15 | const ghPath = path.substring(sharePrefix.length) 16 | const proxyEnv = Cypress.env('RAW_GIT_PROXY_URL') 17 | const interceptUrl = `${proxyEnv}${ghPath}` 18 | cy.log('INTERCEPT URL:', interceptUrl) 19 | cy.log(`RAW_GIT_PROXY_URL: ${Cypress.env('RAW_GIT_PROXY_URL')}`) 20 | cy.log(`interceptUrl: ${interceptUrl}`) 21 | cy.intercept('GET', interceptUrl, {fixture: fixturePath}) 22 | .as(interceptTag) 23 | } 24 | 25 | 26 | /** @param {string} tag Like 'loadMomentum' */ 27 | export function waitForModelReady(tag) { 28 | cy.wait(`@${tag}`) 29 | // TODO(pablo): same as index.ifc load 30 | cy.get('[data-model-ready="true"]').should('exist', {timeout: 1000}) 31 | const animWaitTimeMs = 1000 32 | // eslint-disable-next-line cypress/no-unnecessary-waiting 33 | cy.wait(animWaitTimeMs) 34 | } 35 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # Apply strict cross-origin isolation for all routes by default 2 | [[headers]] 3 | for = "/*" 4 | [headers.values] 5 | Cross-Origin-Opener-Policy = "same-origin" 6 | Cross-Origin-Embedder-Policy = "require-corp" 7 | 8 | # Override headers for the /subscribe route so that no strict isolation is enforced. 9 | [[headers]] 10 | for = "/subscribe/*" 11 | [headers.values] 12 | Cross-Origin-Opener-Policy = "unsafe-none" 13 | Cross-Origin-Embedder-Policy = "unsafe-none" 14 | 15 | # Redirect /subscribe to /subscribe/ to enforce a trailing slash. 16 | [[redirects]] 17 | from = "/subscribe" 18 | to = "/subscribe/" 19 | status = 301 20 | force = true -------------------------------------------------------------------------------- /netlify/functions/create-portal-session.js: -------------------------------------------------------------------------------- 1 | 2 | const Sentry = require('@sentry/serverless'); 3 | 4 | Sentry.AWSLambda.init({ 5 | dsn: process.env.SENTRY_DSN, 6 | tracesSampleRate: 1.0, 7 | environment: process.env.NODE_ENV, 8 | }); 9 | 10 | /** 11 | User clicks Profile > "Manage subscription" in Share, and this redirects 12 | to a page on Stripe that lets them configure their sub. 13 | */ 14 | exports.handler = Sentry.AWSLambda.wrapHandler(async (event) => { 15 | const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); 16 | 17 | try { 18 | const body = JSON.parse(event.body); 19 | const { stripeCustomerId } = body; 20 | 21 | const portalSession = await stripe.billingPortal.sessions.create({ 22 | customer: stripeCustomerId, 23 | return_url: 'https://bldrs.ai/', 24 | }); 25 | 26 | return { 27 | statusCode: 200, 28 | body: JSON.stringify({ url: portalSession.url }), 29 | }; 30 | } catch (error) { 31 | Sentry.captureException(error); 32 | return { statusCode: 500, body: JSON.stringify({ error: error.message }) }; 33 | } 34 | }); -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BLDRS - Redirect 7 | 8 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | bldrs.ai 2 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/event-logger-outer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 15 | 16 |
9 | 10 | 12 | 13 |
    14 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/favicon.png -------------------------------------------------------------------------------- /public/icons/LogoB.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | BLDRS.AI 11 | 22 | 23 | 26 | 29 | 32 | 35 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /public/icons/LogoBWithDomain.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | BLDRS.AI 11 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | BLDRS.AI 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/icons/PlaceMark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/mod_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/icons/mod_logo.jpeg -------------------------------------------------------------------------------- /public/icons/speedtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/icons/speedtest.png -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/logo512.png -------------------------------------------------------------------------------- /public/mod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 15 | 16 |
    9 | 10 | 12 | 13 |
      14 |
      17 | 18 | 19 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/speedtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bldrs-ai/Share/c1bd259a448ee750130faa8e465e7ebcf49f3eba/public/speedtest.png -------------------------------------------------------------------------------- /public/subscribe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Payment Page 8 | 9 | 10 | 11 | 12 |
      13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/netlify-preinstall.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { spawnSync } = require('child_process') 3 | 4 | // Netlify does not support Github Packages (or other private package registries besides npm), options are: 5 | // - Commit .npmrc to repo - However, now we have a secret token inside our repo 6 | // - Environment variable in .npmrc - However, this requires all developer machines to have the same environment variable configured 7 | // - Get creative with the preinstall script... :) 8 | 9 | // Only run this script on Netlify 10 | if (process.env.NETLIFY === 'true') { // this is a default Netlify environment variable 11 | // Check if .npmrc was already generated by this script. If it does then do nothing (otherwise we create an infinite yarn loop) 12 | if (process.env.NETLIFY_NPMRC_DONE !== 'true') { 13 | // Create .npmrc 14 | fs.writeFileSync('.npmrc', `//npm.pkg.github.com/:_authToken=${process.env.GITHUB_TOKEN}\n@bldrs-ai:registry=https://npm.pkg.github.com/\n`) 15 | fs.chmodSync('.npmrc', 0o600) 16 | // Run yarn again, because the yarn process which is executing 17 | // this script won't pick up the .npmrc file we just created. 18 | // The original yarn process will continue after this second yarn process finishes, 19 | // and when it does it will report "success Already up-to-date." 20 | spawnSync('yarn', { stdio: 'inherit', env: { ...process.env, NETLIFY_NPMRC_DONE: true } }) 21 | } 22 | } -------------------------------------------------------------------------------- /src/Auth0ProviderWithHistory.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {useNavigate} from 'react-router-dom' 3 | import {Auth0Provider} from './Auth0/Auth0ProviderProxy' 4 | 5 | 6 | /** @return {React.ReactContext} */ 7 | export default function Auth0ProviderWithHistory({children}) { 8 | const navigate = useNavigate() 9 | const onRedirect = (state) => { 10 | navigate(state && state.returnTo ? state.returnTo : 'popup-callback', {replace: true}) 11 | // navigate(0) 12 | } 13 | return ( 14 | 26 | {children} 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/BaseRoutes.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from '@testing-library/react' 3 | import MockRoutes from './BaseRoutesMock.test' 4 | import BaseRoutes from './BaseRoutes' 5 | 6 | 7 | /** 8 | * TODO(pablo): fix flaky test 9 | * RangeError: /Users/olegmoshkovich/Desktop/builders/Share/node_modules/web-ifc/web-ifc-api.js: 10 | * Maximum call stack size exceeded 11 | */ 12 | test('BaseRoutes', () => { 13 | const testLabel = 'Test node label' 14 | const {getByText} = render( 15 | {testLabel}}/>} 17 | />) 18 | expect(getByText(testLabel)).toBeInTheDocument() 19 | }) 20 | -------------------------------------------------------------------------------- /src/BaseRoutesMock.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {MemoryRouter, Routes, Route} from 'react-router-dom' 3 | import {render} from '@testing-library/react' 4 | 5 | 6 | jest.mock('three') 7 | 8 | 9 | test('mockRoutes', () => { 10 | const testLabel = 'Test node label' 11 | const {getByText} = render() 12 | expect(getByText(testLabel)).toBeInTheDocument() 13 | }) 14 | 15 | 16 | /** 17 | * @param {Array} initialEntries For react-router MemoryRouter. 18 | * @param {object} contentElt React component for Route. 19 | * @return {React.Component} React component 20 | */ 21 | export default function MockRoutes({initialEntries = ['/'], contentElt} = {}) { 22 | // TODO(pablo): would be better to not include the initialEntries 23 | // attribute if not given, but don't know how to do this in React, 24 | // so setting the default as defined in 25 | // https://reactrouter.com/docs/en/v6/routers/memory-router. 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/Components/About/AboutDescription.jsx: -------------------------------------------------------------------------------- 1 | import React, {ReactElement} from 'react' 2 | import Box from '@mui/material/Box' 3 | import Typography from '@mui/material/Typography' 4 | import {useTheme} from '@mui/material/styles' 5 | 6 | 7 | /** 8 | * A miniature view of the App to show as a guide in the About dialog. 9 | * 10 | * @return {ReactElement} 11 | */ 12 | export default function AboutDescription({setIsDialogDisplayed}) { 13 | const theme = useTheme() 14 | 15 | return ( 16 | 31 | 32 | Cross-functional online collaboration unlocks team flow, 33 | productivity and creativity. 34 | Open workspaces, open standards and open source code is the most powerful way to work. 35 | Cooperation is the unfair advantage. 36 | 37 | 38 | ) 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/Components/About/AboutDialog.fixture.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {AboutDialog} from './AboutControl' 3 | import {HelmetStoreRouteThemeCtx} from '../../Share.fixture' 4 | 5 | 6 | /** @return {React.Component} */ 7 | export default function AboutDialogFixture() { 8 | const [isDisplayed, setIsDisplayed] = useState(true) 9 | return ( 10 | 11 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/Components/About/PrivacyControl.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {fireEvent, render} from '@testing-library/react' 3 | import * as Analytics from '../../privacy/analytics' 4 | import PrivacyControl from './PrivacyControl' 5 | 6 | 7 | describe('PrivacyControl', () => { 8 | test('toggle sets analytics cookie correctly', () => { 9 | expect(Analytics.isAllowed()).toBe(true) 10 | 11 | const {getByRole} = render() 12 | const enableAnalyticsToggle = getByRole('checkbox') 13 | expect(enableAnalyticsToggle).toBeInTheDocument() 14 | 15 | fireEvent.click(enableAnalyticsToggle) 16 | expect(Analytics.isAllowed()).toBe(false) 17 | 18 | fireEvent.click(enableAnalyticsToggle) 19 | expect(Analytics.isAllowed()).toBe(true) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/Components/About/component.js: -------------------------------------------------------------------------------- 1 | export const ABOUT_MISSION = 'Build Every Thing Together' 2 | export const ABOUT_PAGE_TITLE = 'About — bldrs.ai' 3 | -------------------------------------------------------------------------------- /src/Components/About/hashState.js: -------------------------------------------------------------------------------- 1 | import {hasParams} from '../../utils/location' 2 | import {isFirst} from '../../privacy/firstTime' 3 | 4 | 5 | /** The prefix to use for the About state token */ 6 | export const HASH_PREFIX_ABOUT = 'about' 7 | 8 | 9 | /** @return {boolean} */ 10 | export function isVisibleInitially() { 11 | return isFirst() || hasParams(HASH_PREFIX_ABOUT) 12 | } 13 | -------------------------------------------------------------------------------- /src/Components/AppBar.jsx: -------------------------------------------------------------------------------- 1 | import React, {ReactElement} from 'react' 2 | import MuiAppBar from '@mui/material/AppBar' 3 | import Stack from '@mui/material/Stack' 4 | import Toolbar from '@mui/material/Toolbar' 5 | import {useTheme} from '@mui/material/styles' 6 | import ControlsGroup from './ControlsGroup' 7 | import LoginMenu from './LoginMenu' 8 | import SearchBar from './Search/SearchBar' 9 | 10 | 11 | /** 12 | * @property {boolean} isRepoActive TODO(pablo): maybe better in store 13 | * @return {ReactElement} 14 | */ 15 | export default function AppBar({isRepoActive}) { 16 | const theme = useTheme() 17 | return ( 18 | 31 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/Components/ApplicationError.jsx: -------------------------------------------------------------------------------- 1 | import React, {ReactElement} from 'react' 2 | import Box from '@mui/material/Box' 3 | import Paper from '@mui/material/Paper' 4 | import Typography from '@mui/material/Typography' 5 | import {LogoB} from './Logo/Logo' 6 | 7 | 8 | /** 9 | * This is the placeholder when an js error happens in a component. 10 | * Our fail whale. It links the user back to the homepage to start 11 | * over. 12 | * 13 | * @return {ReactElement} 14 | */ 15 | export default function ApplicationError() { 16 | return ( 17 | 23 | 24 | Oh no! 25 | 26 | 27 | 28 | We're not quite sure what went wrong. 29 | 30 | 31 | 32 | Not to worry.
      33 | You can click here to start a new session. 34 |
      35 | 36 | 37 | 38 | 39 |
      40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/Components/Apps/AppEntry.jsx: -------------------------------------------------------------------------------- 1 | import React, {ReactElement} from 'react' 2 | import Card from '@mui/material/Card' 3 | import CardActionArea from '@mui/material/CardActionArea' 4 | import CardContent from '@mui/material/CardContent' 5 | import CardMedia from '@mui/material/CardMedia' 6 | import Paper from '@mui/material/Paper' 7 | import Typography from '@mui/material/Typography' 8 | 9 | 10 | /** 11 | * @property {object} itemJson App description json 12 | * @property {Function} onClickCb Called when app's card is clicked 13 | * @return {ReactElement} 14 | */ 15 | export default function AppEntry({itemJson, onClickCb}) { 16 | return ( 17 | 18 | 19 | 20 | 31 | 32 | 33 | {itemJson.name} 34 | 35 | 36 | {itemJson.description} 37 | 38 | 39 | 40 | 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/Components/Apps/AppIFrame.jsx: -------------------------------------------------------------------------------- 1 | import React, {ReactElement, useCallback} from 'react' 2 | import Box from '@mui/material/Box' 3 | import {IFrameCommunicationChannel} from './AppsMessagesHandler' 4 | 5 | 6 | /** 7 | * @property {object} itemJson App description json 8 | * @return {ReactElement} 9 | */ 10 | export default function AppIFrame({itemJson}) { 11 | const appFrameRef = useCallback((elt) => { 12 | if (elt) { 13 | elt.addEventListener('load', () => { 14 | new IFrameCommunicationChannel(elt) 15 | }) 16 | } 17 | }, []) 18 | 19 | return ( 20 | 28 |