├── .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 |
9 |
10 | |
11 |
12 |
13 |
14 | |
15 |
16 |
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 |
37 |
39 |
--------------------------------------------------------------------------------
/public/icons/LogoBWithDomain.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
34 |
35 |
--------------------------------------------------------------------------------
/public/icons/PlaceMark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
9 |
10 | |
11 |
12 |
13 |
14 | |
15 |
16 |
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 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/Components/Apps/AppPanel.jsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement} from 'react'
2 | import useStore from '../../store/useStore'
3 | import {BackButton} from '../Buttons'
4 | import Panel from '../SideDrawer/Panel'
5 | import AppIFrame from './AppIFrame'
6 | import {removeHashParams} from './hashState'
7 |
8 |
9 | /**
10 | * @property {object} itemJson App description json
11 | * @return {ReactElement}
12 | */
13 | export default function AppPanel({itemJson}) {
14 | const setIsAppsVisible = useStore((state) => state.setIsAppsVisible)
15 | const setSelectedApp = useStore((state) => state.setSelectedApp)
16 |
17 |
18 | /** Hide panel and remove hash state */
19 | function onClose() {
20 | setIsAppsVisible(false)
21 | removeHashParams()
22 | }
23 |
24 |
25 | return (
26 | setSelectedApp(null)}/>
30 | }
31 | onClose={onClose}
32 | iconSrc={itemJson.icon}
33 | data-testid='AppPanel'
34 | >
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/Components/Apps/AppsControl.jsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement} from 'react'
2 | import useStore from '../../store/useStore'
3 | import {ControlButtonWithHashState} from '../Buttons'
4 | import {HASH_PREFIX_APPS} from './hashState'
5 | import WidgetsIcon from '@mui/icons-material/WidgetsOutlined'
6 |
7 |
8 | /**
9 | * This button hosts the AppsDialog component and toggles it open and
10 | * closed.
11 | *
12 | * @return {ReactElement}
13 | */
14 | export default function AppsControl() {
15 | const isAppsVisible = useStore((state) => state.isAppsVisible)
16 | const setIsAppsVisible = useStore((state) => state.setIsAppsVisible)
17 | return (
18 | }
21 | isDialogDisplayed={isAppsVisible}
22 | setIsDialogDisplayed={setIsAppsVisible}
23 | hashPrefix={HASH_PREFIX_APPS}
24 | placement='bottom'
25 | >
26 | <>>
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/src/Components/Apps/AppsListing.jsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement} from 'react'
2 | import Grid from '@mui/material/Unstable_Grid2'
3 | import useStore from '../../store/useStore'
4 | import AppEntry from './AppEntry'
5 | import AppsRegistry from './AppsRegistry.json'
6 |
7 |
8 | /** @return {ReactElement} */
9 | export default function AppsListing() {
10 | const setSelectedApp = useStore((state) => state.setSelectedApp)
11 | return (
12 |
13 | {AppsRegistry.map((itemJson, index) => (
14 |
15 | setSelectedApp(itemJson)}
17 | itemJson={itemJson}
18 | />
19 |
20 | ))}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/Components/Apps/AppsPanel.jsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement} from 'react'
2 | import useStore from '../../store/useStore'
3 | import Panel from '../SideDrawer/Panel'
4 | import AppsListing from './AppsListing'
5 | import {removeHashParams} from './hashState'
6 |
7 |
8 | /** @return {ReactElement} */
9 | export default function AppsPanel() {
10 | const setIsAppsVisible = useStore((state) => state.setIsAppsVisible)
11 |
12 |
13 | /** Hide panel and remove hash state */
14 | function onClose() {
15 | setIsAppsVisible(false)
16 | removeHashParams()
17 | }
18 |
19 |
20 | return (
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/Components/Apps/AppsRegistry.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "appName": "ExampleApp",
4 | "description": "Events Logger",
5 | "image": "//bldrs.ai/icons/LogoBWithDomain.svg",
6 | "action": "/widgets/event-logger.html",
7 | "icon": "//bldrs.ai/icons/LogoB.svg"
8 | }
9 | ]
10 |
--------------------------------------------------------------------------------
/src/Components/Apps/component.js:
--------------------------------------------------------------------------------
1 | export const TITLE_APP = 'App'
2 | export const TITLE_APPS = 'Apps'
3 |
--------------------------------------------------------------------------------
/src/Components/Apps/hashState.js:
--------------------------------------------------------------------------------
1 | import {hasParams, removeParams} from '../../utils/location'
2 |
3 |
4 | /** The prefix to use for the apps state token */
5 | export const HASH_PREFIX_APPS = 'apps'
6 |
7 |
8 | /** Removes hash params for apps */
9 | export function removeHashParams() {
10 | removeParams(HASH_PREFIX_APPS)
11 | }
12 |
13 |
14 | /** @return {boolean} */
15 | export function isVisibleInitially() {
16 | return hasParams(HASH_PREFIX_APPS)
17 | }
18 |
--------------------------------------------------------------------------------
/src/Components/Auth/PopupCallback.jsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from 'react'
2 | import {useAuth0} from '../../Auth0/Auth0Proxy'
3 |
4 |
5 | /**
6 | * @return {React.Component}
7 | */
8 | function PopupCallback() {
9 | const {handleRedirectCallback} = useAuth0()
10 |
11 | useEffect(() => {
12 | /**
13 | *
14 | */
15 | async function processCallback() {
16 | // Wait for Auth0 to handle the redirect callback
17 | await handleRedirectCallback()
18 | // Now that Auth0 has processed the callback and written tokens to storage,
19 | // set our flag and close the popup.
20 | localStorage.setItem('refreshAuth', 'true')
21 | window.close()
22 | }
23 | processCallback()
24 | }, [handleRedirectCallback])
25 |
26 | return Logging in, please wait…
27 | }
28 |
29 | export default PopupCallback
30 |
--------------------------------------------------------------------------------
/src/Components/Camera/hashState.js:
--------------------------------------------------------------------------------
1 | import {
2 | removeParamsFromHash as utilsRemoveParamsFromHash,
3 | removeHashParams,
4 | } from '../../utils/location'
5 |
6 |
7 | /** The prefix to use for the Camera state token */
8 | export const HASH_PREFIX_CAMERA = 'c'
9 |
10 |
11 | /**
12 | * @param {string} hash
13 | * @return {string} hash with camera params removed
14 | */
15 | export function removeParamsFromHash(hash) {
16 | return utilsRemoveParamsFromHash(hash, HASH_PREFIX_CAMERA)
17 | }
18 |
19 |
20 | /** Removes camera params from the URL if present */
21 | export function removeCameraUrlParams() {
22 | removeHashParams(window.location, HASH_PREFIX_CAMERA)
23 | }
24 |
--------------------------------------------------------------------------------
/src/Components/Dialog.fixture.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {ThemeCtx} from '../theme/Theme.fixture'
3 | import Dialog from './Dialog'
4 | import AttentionIcon from '../assets/icons/Attention.svg'
5 |
6 |
7 | export default (
8 |
9 | }
11 | headerText={'Here\'s the thing!'}
12 | isDialogDisplayed={true}
13 | // eslint-disable-next-line no-empty-function
14 | setIsDialogDisplayed={() => {}}
15 | content={'What you should know about doing the thing'}
16 | actionTitle={'Do do the thing?'}
17 | actionCb={() => {
18 | alert('You did the thing')
19 | }}
20 | />
21 |
22 | )
23 |
--------------------------------------------------------------------------------
/src/Components/Help/HelpControl.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {render, fireEvent} from '@testing-library/react'
3 | import {StoreRouteThemeCtx} from '../../Share.fixture'
4 | import HelpControl, {testId} from './HelpControl'
5 |
6 |
7 | describe('HelpControl', () => {
8 | it('renders the first page of the HelpDialog', () => {
9 | const {getByTestId, getByText} = render(, {wrapper: StoreRouteThemeCtx})
10 | const button = getByTestId(testId)
11 | fireEvent.click(button)
12 | const text = getByText('Study the model using standard sections')
13 | expect(text).toBeInTheDocument()
14 | })
15 |
16 | it('navigates to the next page when the next button is clicked', () => {
17 | const {getByTestId, getByText} = render(, {wrapper: StoreRouteThemeCtx})
18 | const button = getByTestId(testId)
19 | fireEvent.click(button)
20 | const nextPageButton = getByTestId('Next')
21 | fireEvent.click(nextPageButton)
22 | const text = getByText('Isolate selected element')
23 | expect(text).toBeInTheDocument()
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/src/Components/Help/hashState.js:
--------------------------------------------------------------------------------
1 | import {hasParams} from '../../utils/location'
2 |
3 |
4 | /** The prefix to use for the Help state token */
5 | export const HASH_PREFIX_HELP = 'help'
6 |
7 |
8 | /** @return {boolean} */
9 | export function isVisibleInitially() {
10 | return hasParams(HASH_PREFIX_HELP)
11 | }
12 |
--------------------------------------------------------------------------------
/src/Components/Hooks.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect} from 'react'
2 | import {MOBILE_WIDTH} from '../utils/constants'
3 |
4 |
5 | /**
6 | * @return {boolean} True iff window width <= MOBILE_WIDTH.
7 | */
8 | export function useIsMobile() {
9 | return useWindowDimensions().width <= MOBILE_WIDTH
10 | }
11 |
12 |
13 | /**
14 | * @return {object} {width, height}
15 | */
16 | export function useWindowDimensions() {
17 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
18 |
19 |
20 | useEffect(() => {
21 | /** Handle resize. */
22 | function handleResize() {
23 | setWindowDimensions(getWindowDimensions())
24 | }
25 |
26 | window.addEventListener('resize', handleResize)
27 | return () => window.removeEventListener('resize', handleResize)
28 | }, [])
29 |
30 |
31 | return windowDimensions
32 | }
33 |
34 |
35 | /**
36 | * @return {object} {width, height}
37 | */
38 | function getWindowDimensions() {
39 | const {innerWidth: width, innerHeight: height} = window
40 | return {
41 | width,
42 | height,
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Components/Hooks.test.jsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement} from 'react'
2 | import {render} from '@testing-library/react'
3 | import {useIsMobile} from './Hooks'
4 | import {MOBILE_WIDTH} from '../utils/constants'
5 |
6 |
7 | /** @return {ReactElement} */
8 | function TestComponent() {
9 | const isMobile = useIsMobile()
10 | return <>isMobile: {isMobile ? 'true' : 'false'}>
11 | }
12 |
13 |
14 | describe('Hooks', () => {
15 | it('useIsMobile', () => {
16 | const aLil = 10
17 | window.innerWidth = MOBILE_WIDTH + aLil
18 | expect(render().getByText('isMobile: false')).toBeInTheDocument()
19 | window.innerWidth = MOBILE_WIDTH - aLil
20 | expect(render().getByText('isMobile: false')).toBeInTheDocument()
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/src/Components/Imagine/ImagineControl.jsx:
--------------------------------------------------------------------------------
1 | import React, {ReactElement} from 'react'
2 | import useStore from '../../store/useStore'
3 | import {ControlButtonWithHashState} from '../Buttons'
4 | import {HASH_PREFIX_IMAGINE} from './hashState'
5 | import AutoFixHighOutlinedIcon from '@mui/icons-material/AutoFixHighOutlined'
6 | import ImagineDialog from './ImagineDialog'
7 |
8 |
9 | /**
10 | * This button hosts the ImagineDialog component and toggles it open and
11 | * closed.
12 | *
13 | * @return {ReactElement}
14 | */
15 | export default function ImagineControl() {
16 | const isImagineVisible = useStore((state) => state.isImagineVisible)
17 | const setIsImagineVisible = useStore((state) => state.setIsImagineVisible)
18 | return (
19 | }
22 | isDialogDisplayed={isImagineVisible}
23 | setIsDialogDisplayed={setIsImagineVisible}
24 | hashPrefix={HASH_PREFIX_IMAGINE}
25 | placement='left'
26 | >
27 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/Components/Imagine/ImagineControl.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {fireEvent, render, waitFor} from '@testing-library/react'
3 | import {HelmetStoreRouteThemeCtx, RouteThemeCtx} from '../../Share.fixture'
4 | import ImagineControl from './ImagineControl'
5 |
6 |
7 | // ImagineControl uses viewer's screenshot
8 | jest.mock('web-ifc-viewer')
9 |
10 |
11 | describe('ImagineControl', () => {
12 | it('ControlButton visible', () => {
13 | const {getByTitle} = render(, {wrapper: RouteThemeCtx})
14 | const component = getByTitle('Rendering')
15 | expect(component).toBeInTheDocument()
16 | })
17 |
18 | it('updates the title when the dialog is open', async () => {
19 | const {getByTitle} = render(, {wrapper: HelmetStoreRouteThemeCtx})
20 |
21 | const button = getByTitle('Rendering')
22 | fireEvent.click(button)
23 |
24 | await(waitFor(() => expect(document.title).toBe('Imagine')))
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/src/Components/Imagine/hashState.js:
--------------------------------------------------------------------------------
1 | import {hasParams} from '../../utils/location'
2 |
3 |
4 | /** The prefix to use for the Imagine state token */
5 | export const HASH_PREFIX_IMAGINE = 'imagine'
6 |
7 |
8 | /** @return {boolean} */
9 | export function isVisibleInitially() {
10 | return hasParams(HASH_PREFIX_IMAGINE)
11 | }
12 |
--------------------------------------------------------------------------------
/src/Components/InputAutocomplete.fixture.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import InputAutocomplete from './InputAutocomplete'
3 | import {ThemeCtx} from '../theme/Theme.fixture'
4 |
5 |
6 | const elements = [
7 | {title: 'Surfaces'},
8 | {title: 'Case'},
9 | {title: 'Gears'},
10 | {title: 'Electonics'},
11 | ]
12 |
13 | export default (
14 |
15 |
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/src/Components/InputAutocomplete.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Autocomplete from '@mui/material/Autocomplete'
3 | import TextField from '@mui/material/TextField'
4 | import Stack from '@mui/material/Stack'
5 | import {assertDefined} from '../utils/assert'
6 |
7 | /**
8 | * Input with autocomplete feature.
9 | *
10 | * @property {Array