@thatopen/services 0.0.6 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +46 -46
  2. package/dist/built-in/index.d.ts +723 -0
  3. package/dist/cli.js +22 -0
  4. package/dist/core/client.d.ts +10 -1
  5. package/dist/index.cjs.js +2 -2
  6. package/dist/index.es.js +87 -71
  7. package/dist/types/files.d.ts +6 -0
  8. package/dist/vite-env.d.ts +1 -1
  9. package/docs/builtin/paths.json +22 -6
  10. package/package.json +99 -99
  11. package/resources/AGENTS.md +5 -3
  12. package/src/built-in/AppManager/example.ts +86 -86
  13. package/src/built-in/CDEManager/example.ts +214 -214
  14. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  15. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  16. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  17. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  18. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  19. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  20. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  21. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  22. package/src/built-in/FileList/example.ts +107 -107
  23. package/src/built-in/TabbedNavigation/example.ts +115 -115
  24. package/src/built-in/UIManager/src/app/example.ts +99 -0
  25. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +142 -0
  26. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +125 -0
  27. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +92 -0
  28. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +161 -0
  29. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +104 -0
  30. package/src/built-in/ViewportsManager/example.ts +106 -106
  31. package/src/built-in/index.ts +755 -755
  32. package/src/cli/templates/app/README.md +241 -241
  33. package/src/cli/templates/app/index.html +27 -27
  34. package/src/cli/templates/app/package.json +27 -25
  35. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +87 -91
  36. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  37. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  38. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  39. package/src/cli/templates/app/src/main.ts +56 -89
  40. package/src/cli/templates/app/src/setups/cloud-runner.ts +9 -13
  41. package/src/cli/templates/app/src/setups/fragments.ts +18 -0
  42. package/src/cli/templates/app/src/setups/ifc-loader.ts +12 -0
  43. package/src/cli/templates/app/src/setups/index.ts +3 -3
  44. package/src/cli/templates/app/src/ui-components/AppPanel/index.ts +160 -0
  45. package/src/cli/templates/app/src/ui-components/index.ts +1 -2
  46. package/src/cli/templates/app/tsconfig.json +17 -16
  47. package/src/cli/templates/cloud-component/README.md +205 -205
  48. package/src/cli/templates/cloud-component/_thatopen +5 -5
  49. package/src/cli/templates/cloud-component/declarations.json +4 -4
  50. package/src/cli/templates/cloud-component/package.json +21 -21
  51. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  52. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  53. package/src/cli/templates/shared/CLAUDE.md +1 -1
  54. package/src/cli/templates/shared/_gitignore +4 -4
  55. package/src/cli/templates/app/src/app.ts +0 -16
  56. package/src/cli/templates/app/src/globals.ts +0 -1
  57. package/src/cli/templates/app/src/setups/ui-manager.ts +0 -27
  58. package/src/cli/templates/app/src/setups/viewports-manager.ts +0 -22
  59. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +0 -26
  60. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +0 -1
  61. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +0 -15
  62. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +0 -37
  63. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +0 -1
  64. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +0 -14
@@ -1,115 +1,115 @@
1
- import * as OBC from "@thatopen/components";
2
- import * as BUI from "@thatopen/ui";
3
-
4
- import appManagerDef from "../AppManager/index";
5
- import tabbedNavDef from "./index";
6
-
7
- const AppManager = appManagerDef.componentDefinition;
8
- const TabbedNavigation = tabbedNavDef.componentDefinition;
9
-
10
- // ── App type ──────────────────────────────────────────────────────────
11
-
12
- type PanelState = { label: string };
13
-
14
- interface NavApp {
15
- icons: ["DASHBOARD", "FILES", "SETTINGS"];
16
- grid: BUI.Grid<
17
- ["Dashboard", "Files", "Settings"],
18
- [
19
- { name: "tabs"; state: null },
20
- { name: "dashboard"; state: PanelState },
21
- { name: "files"; state: PanelState },
22
- { name: "settings"; state: PanelState }
23
- ]
24
- >;
25
- }
26
-
27
- // ── Initialise OBC & BUI ──────────────────────────────────────────────
28
-
29
- BUI.Manager.init();
30
- const components = new OBC.Components();
31
-
32
- const app = components.get(AppManager<NavApp>);
33
- const tabs = components.get(TabbedNavigation);
34
-
35
- // ── Templates ─────────────────────────────────────────────────────────
36
-
37
- const makePanel = (): BUI.StatefullComponent<PanelState> =>
38
- ({ label }) => BUI.html`
39
- <bim-panel active label=${label}>
40
- <bim-panel-section label="Content">
41
- <bim-label>Current screen: ${label}</bim-label>
42
- </bim-panel-section>
43
- </bim-panel>
44
- `;
45
-
46
- // The grid caches elements by template-function identity, so each tab area
47
- // needs its own function reference — otherwise the grid reuses the same
48
- // panel element across layouts.
49
- const dashboardPanel = makePanel();
50
- const filesPanel = makePanel();
51
- const settingsPanel = makePanel();
52
-
53
- // We'll fill the tabs element after init.
54
- let tabsElement: HTMLElement | null = null;
55
-
56
- // ── Init ──────────────────────────────────────────────────────────────
57
-
58
- app.init({
59
- icons: {
60
- DASHBOARD: "mdi:view-dashboard",
61
- FILES: "mdi:file-multiple",
62
- SETTINGS: "mdi:cog",
63
- },
64
- grid: (grid) => {
65
- grid.elements = {
66
- tabs: (() => {
67
- // Lazy placeholder — TabbedNavigation attaches here after app.init().
68
- const slot = document.createElement("div");
69
- slot.style.cssText = "display: flex; padding: 0.5rem;";
70
- tabsElement = slot;
71
- return slot;
72
- })(),
73
- dashboard: { template: dashboardPanel, initialState: { label: "Dashboard" } },
74
- files: { template: filesPanel, initialState: { label: "Files" } },
75
- settings: { template: settingsPanel, initialState: { label: "Settings" } },
76
- };
77
- grid.layouts = {
78
- Dashboard: {
79
- template: `
80
- "tabs" auto
81
- "dashboard" 1fr
82
- / 1fr
83
- `,
84
- icon: app.icons.DASHBOARD,
85
- },
86
- Files: {
87
- template: `
88
- "tabs" auto
89
- "files" 1fr
90
- / 1fr
91
- `,
92
- icon: app.icons.FILES,
93
- },
94
- Settings: {
95
- template: `
96
- "tabs" auto
97
- "settings" 1fr
98
- / 1fr
99
- `,
100
- icon: app.icons.SETTINGS,
101
- },
102
- };
103
- grid.layout = "Dashboard";
104
- },
105
- container: document.getElementById("container")!,
106
- });
107
-
108
- // Create the tab bar now that the grid exists.
109
- const { element: tabBar } = tabs.create();
110
- tabsElement?.appendChild(tabBar);
111
-
112
- // Hide the default sidebar — tabs replace it.
113
- app.showSidebar = false;
114
-
115
- components.init();
1
+ import * as OBC from "@thatopen/components";
2
+ import * as BUI from "@thatopen/ui";
3
+
4
+ import appManagerDef from "../AppManager/index";
5
+ import tabbedNavDef from "./index";
6
+
7
+ const AppManager = appManagerDef.componentDefinition;
8
+ const TabbedNavigation = tabbedNavDef.componentDefinition;
9
+
10
+ // ── App type ──────────────────────────────────────────────────────────
11
+
12
+ type PanelState = { label: string };
13
+
14
+ interface NavApp {
15
+ icons: ["DASHBOARD", "FILES", "SETTINGS"];
16
+ grid: BUI.Grid<
17
+ ["Dashboard", "Files", "Settings"],
18
+ [
19
+ { name: "tabs"; state: null },
20
+ { name: "dashboard"; state: PanelState },
21
+ { name: "files"; state: PanelState },
22
+ { name: "settings"; state: PanelState }
23
+ ]
24
+ >;
25
+ }
26
+
27
+ // ── Initialise OBC & BUI ──────────────────────────────────────────────
28
+
29
+ BUI.Manager.init();
30
+ const components = new OBC.Components();
31
+
32
+ const app = components.get(AppManager<NavApp>);
33
+ const tabs = components.get(TabbedNavigation);
34
+
35
+ // ── Templates ─────────────────────────────────────────────────────────
36
+
37
+ const makePanel = (): BUI.StatefullComponent<PanelState> =>
38
+ ({ label }) => BUI.html`
39
+ <bim-panel active label=${label}>
40
+ <bim-panel-section label="Content">
41
+ <bim-label>Current screen: ${label}</bim-label>
42
+ </bim-panel-section>
43
+ </bim-panel>
44
+ `;
45
+
46
+ // The grid caches elements by template-function identity, so each tab area
47
+ // needs its own function reference — otherwise the grid reuses the same
48
+ // panel element across layouts.
49
+ const dashboardPanel = makePanel();
50
+ const filesPanel = makePanel();
51
+ const settingsPanel = makePanel();
52
+
53
+ // We'll fill the tabs element after init.
54
+ let tabsElement: HTMLElement | null = null;
55
+
56
+ // ── Init ──────────────────────────────────────────────────────────────
57
+
58
+ app.init({
59
+ icons: {
60
+ DASHBOARD: "mdi:view-dashboard",
61
+ FILES: "mdi:file-multiple",
62
+ SETTINGS: "mdi:cog",
63
+ },
64
+ grid: (grid) => {
65
+ grid.elements = {
66
+ tabs: (() => {
67
+ // Lazy placeholder — TabbedNavigation attaches here after app.init().
68
+ const slot = document.createElement("div");
69
+ slot.style.cssText = "display: flex; padding: 0.5rem;";
70
+ tabsElement = slot;
71
+ return slot;
72
+ })(),
73
+ dashboard: { template: dashboardPanel, initialState: { label: "Dashboard" } },
74
+ files: { template: filesPanel, initialState: { label: "Files" } },
75
+ settings: { template: settingsPanel, initialState: { label: "Settings" } },
76
+ };
77
+ grid.layouts = {
78
+ Dashboard: {
79
+ template: `
80
+ "tabs" auto
81
+ "dashboard" 1fr
82
+ / 1fr
83
+ `,
84
+ icon: app.icons.DASHBOARD,
85
+ },
86
+ Files: {
87
+ template: `
88
+ "tabs" auto
89
+ "files" 1fr
90
+ / 1fr
91
+ `,
92
+ icon: app.icons.FILES,
93
+ },
94
+ Settings: {
95
+ template: `
96
+ "tabs" auto
97
+ "settings" 1fr
98
+ / 1fr
99
+ `,
100
+ icon: app.icons.SETTINGS,
101
+ },
102
+ };
103
+ grid.layout = "Dashboard";
104
+ },
105
+ container: document.getElementById("container")!,
106
+ });
107
+
108
+ // Create the tab bar now that the grid exists.
109
+ const { element: tabBar } = tabs.create();
110
+ tabsElement?.appendChild(tabBar);
111
+
112
+ // Hide the default sidebar — tabs replace it.
113
+ app.showSidebar = false;
114
+
115
+ components.init();
@@ -0,0 +1,99 @@
1
+ /* MD
2
+ ## top-app
3
+ ---
4
+ Front-end developers building BIM web apps need to coordinate several async
5
+ initialization steps before the UI can be shown — loading the engine, preparing
6
+ fragment workers, registering loaders — and users need visible feedback on what
7
+ is happening during that wait, rather than a blank screen.
8
+
9
+ top-app is the platform's root shell element. It manages the full boot sequence,
10
+ tracks each async setup task individually, shows a labelled loading screen until
11
+ every task completes, and then mounts the app's visual layout.
12
+
13
+ This tutorial covers initializing the platform client and registering all
14
+ platform web components; registering two async setup tasks — the fragment worker
15
+ and the IFC loader — each with its own loading-screen label; declaring a named
16
+ layout area as a CSS grid template; wiring a 3D viewer into that layout; and
17
+ using a flag to keep the loading screen visible during development.
18
+
19
+ By the end, you'll have a working app shell that boots the engine, shows a
20
+ step-by-step loading screen, and mounts a 3D viewer once every setup task
21
+ resolves.
22
+ */
23
+
24
+ import { html } from "lit";
25
+ import * as THREE from "three";
26
+ import * as OBC from "@thatopen/components";
27
+ import * as OBF from "@thatopen/components-front";
28
+ import * as FRAGS from "@thatopen/fragments";
29
+ import * as BUI from "@thatopen/ui";
30
+ import { PlatformClient } from "thatopen-services";
31
+ import uiManagerDef from "../../index";
32
+ import type { App } from "./index";
33
+
34
+ const UIManager = uiManagerDef.componentDefinition;
35
+
36
+ const client = PlatformClient.fromPlatformContext();
37
+
38
+ // UIManager must be in the setup call: it registers all platform web components
39
+ // (top-app, top-viewer, top-viewer-tools, etc.) before the DOM renders.
40
+ const { components } = (await client.setup(
41
+ { OBC, OBF, BUI, THREE, FRAGS },
42
+ { uuid: UIManager.uuid },
43
+ )) as { components: OBC.Components };
44
+
45
+ components.get(UIManager).init();
46
+
47
+ const app = document.createElement("top-app") as unknown as App;
48
+
49
+ // setup runs synchronously and returns { components, client }.
50
+ // waitUntil registers async tasks that appear as labelled steps in the loading screen.
51
+ // All tasks run in parallel; top-app mounts only after every promise resolves.
52
+ app.setup = (waitUntil) => {
53
+ waitUntil(
54
+ (async () => {
55
+ const fragments = components.get(OBC.FragmentsManager);
56
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
57
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), {
58
+ classicWorker: true,
59
+ });
60
+ })(),
61
+ "Fragments Core",
62
+ );
63
+
64
+ waitUntil(
65
+ (async () => {
66
+ const loader = components.get(OBC.IfcLoader);
67
+ await loader.setup();
68
+ })(),
69
+ "IFC Loader",
70
+ );
71
+
72
+ return { components, client };
73
+ };
74
+
75
+ // Each key must match an area name used in at least one layout template.
76
+ app.elements = {
77
+ viewer: () =>
78
+ html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
79
+ };
80
+
81
+ // CSS grid-template shorthand. Area names are the keys for app.elements.
82
+ // "." creates an empty unnamed cell and is never rendered.
83
+ app.layouts = {
84
+ main: {
85
+ label: "Main",
86
+ icon: "solar:3d-square-bold",
87
+ template: `"viewer" 1fr / 1fr`,
88
+ },
89
+ };
90
+
91
+ app.layout = "main";
92
+
93
+ // forceLoading keeps the boot screen visible even after setup completes.
94
+ // Useful during development to inspect the loading UI without real async tasks.
95
+ // app.forceLoading = true;
96
+
97
+ const container = document.getElementById("that-open-app") ?? document.body;
98
+ container.appendChild(app);
99
+ document.body.style.margin = "0";
@@ -0,0 +1,142 @@
1
+ /* MD
2
+ ## Shared reactive state with AppState
3
+ ---
4
+ Developers building interactive BIM panels often need sibling components — a
5
+ filter selector and a results table, for example — to stay in sync without a
6
+ shared parent. Lifting state to a common ancestor and threading it down as props
7
+ works for shallow trees but becomes unwieldy as the component tree grows.
8
+
9
+ top-app provides a typed reactive state object available to every component in
10
+ the tree through context. Any component can write to it, and all subscribers
11
+ re-render automatically — no shared parent required.
12
+
13
+ This tutorial covers defining a typed payload for custom app state; building a
14
+ filter selector that updates shared state on button click; building a results
15
+ counter that reads the current filter and count from shared state; composing both
16
+ into a panel inside a top-app layout; and noting when the shared state is
17
+ available relative to the engine boot sequence.
18
+
19
+ By the end, you'll have two independent components that stay in sync through
20
+ shared reactive state — a filter selector and a results counter that reflect each
21
+ other's changes without any direct coupling between them.
22
+ */
23
+
24
+ import { html, LitElement } from "lit";
25
+ import { customElement, state } from "lit/decorators.js";
26
+ import { consume } from "@lit/context";
27
+ import * as THREE from "three";
28
+ import * as OBC from "@thatopen/components";
29
+ import * as OBF from "@thatopen/components-front";
30
+ import * as FRAGS from "@thatopen/fragments";
31
+ import * as BUI from "@thatopen/ui";
32
+ import { PlatformClient } from "thatopen-services";
33
+ import uiManagerDef from "../../../../index";
34
+ import {
35
+ appStateContext,
36
+ AppState,
37
+ AppStateController,
38
+ type App,
39
+ } from "../../index";
40
+
41
+ // Define a typed payload for this app's custom state.
42
+ type MyAppState = { selectedFilter: string; count: number };
43
+
44
+ // AppStateController subscribes to appStateContext and triggers a re-render
45
+ // on every setCustom call — across any component in the tree that holds one.
46
+ @customElement("filter-selector")
47
+ class FilterSelector extends LitElement {
48
+ @consume({ context: appStateContext, subscribe: true })
49
+ @state()
50
+ private _appState?: AppState;
51
+
52
+ // Declare the controller — the LitElement lifecycle handles subscription.
53
+ private _stateCtrl = new AppStateController<MyAppState>(this, () => this._appState);
54
+
55
+ render() {
56
+ const filters = ["All", "Walls", "Slabs", "Columns"];
57
+ const active = this._stateCtrl.custom?.selectedFilter ?? "All";
58
+ return html`
59
+ <bim-panel-section label="Filter" icon="solar:filter-bold">
60
+ ${filters.map(
61
+ (f) => html`
62
+ <bim-button
63
+ .label=${f}
64
+ ?active=${active === f}
65
+ @click=${() => {
66
+ this._appState?.setCustom<MyAppState>({ selectedFilter: f, count: 0 });
67
+ }}
68
+ ></bim-button>
69
+ `,
70
+ )}
71
+ </bim-panel-section>
72
+ `;
73
+ }
74
+ }
75
+
76
+ @customElement("result-counter")
77
+ class ResultCounter extends LitElement {
78
+ @consume({ context: appStateContext, subscribe: true })
79
+ @state()
80
+ private _appState?: AppState;
81
+
82
+ private _stateCtrl = new AppStateController<MyAppState>(this, () => this._appState);
83
+
84
+ render() {
85
+ const { selectedFilter = "All", count = 0 } = this._stateCtrl.custom ?? {};
86
+ return html`
87
+ <bim-panel-section label="Results" icon="solar:list-bold">
88
+ <bim-label>${count} elements match "${selectedFilter}"</bim-label>
89
+ </bim-panel-section>
90
+ `;
91
+ }
92
+ }
93
+
94
+ // ---- app wiring ----
95
+
96
+ const UIManager = uiManagerDef.componentDefinition;
97
+ const client = PlatformClient.fromPlatformContext();
98
+ const { components } = (await client.setup(
99
+ { OBC, OBF, BUI, THREE, FRAGS },
100
+ { uuid: UIManager.uuid },
101
+ )) as { components: OBC.Components };
102
+ components.get(UIManager).init();
103
+
104
+ const app = document.createElement("top-app") as unknown as App;
105
+
106
+ // appState is available immediately — before setup completes — so components
107
+ // that only consume appStateContext can render while the engine is still booting.
108
+ app.setup = (waitUntil) => {
109
+ waitUntil(
110
+ (async () => {
111
+ const fragments = components.get(OBC.FragmentsManager);
112
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
113
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), {
114
+ classicWorker: true,
115
+ });
116
+ })(),
117
+ "Fragments Core",
118
+ );
119
+ return { components, client };
120
+ };
121
+
122
+ app.elements = {
123
+ panel: () => html`
124
+ <bim-panel label="State Demo">
125
+ <filter-selector></filter-selector>
126
+ <result-counter></result-counter>
127
+ </bim-panel>
128
+ `,
129
+ };
130
+
131
+ app.layouts = {
132
+ main: {
133
+ label: "Main",
134
+ icon: "solar:home-bold",
135
+ template: `"panel" 1fr / 22rem`,
136
+ },
137
+ };
138
+ app.layout = "main";
139
+
140
+ const container = document.getElementById("that-open-app") ?? document.body;
141
+ container.appendChild(app);
142
+ document.body.style.margin = "0";
@@ -0,0 +1,125 @@
1
+ /* MD
2
+ ## Consuming platform contexts in child components
3
+ ---
4
+ Developers building custom BIM panels need access to the 3D engine, the platform
5
+ API, and the current project's metadata — but threading these objects down through
6
+ props across multiple component layers forces every intermediate element to know
7
+ about data it doesn't use, making trees brittle and hard to refactor.
8
+
9
+ top-app publishes the engine instance, the platform client, and the current
10
+ project data as named reactive contexts, so any descendant component can subscribe
11
+ to exactly what it needs without any prop drilling from the root.
12
+
13
+ This tutorial covers defining a custom panel that subscribes to the engine
14
+ context, the platform client context, and the project data context; handling the
15
+ intermediate loading state before contexts become available after boot; and
16
+ mounting the panel inside a top-app layout.
17
+
18
+ By the end, you'll have a custom info panel that reads the project name and engine
19
+ state directly from context, with no objects passed down as props from the app root.
20
+ */
21
+
22
+ import { html, LitElement } from "lit";
23
+ import { customElement, state } from "lit/decorators.js";
24
+ import { consume } from "@lit/context";
25
+ import * as THREE from "three";
26
+ import * as OBC from "@thatopen/components";
27
+ import * as OBF from "@thatopen/components-front";
28
+ import * as FRAGS from "@thatopen/fragments";
29
+ import * as BUI from "@thatopen/ui";
30
+ import { PlatformClient, ProjectData } from "thatopen-services";
31
+ import uiManagerDef from "../../../../index";
32
+ import {
33
+ componentsContext,
34
+ clientContext,
35
+ projectDataContext,
36
+ appStateContext,
37
+ type App,
38
+ type AppState,
39
+ } from "../../index";
40
+
41
+ // appStateContext is provided by top-app immediately — before setup completes.
42
+ // componentsContext, clientContext, and projectDataContext become available only
43
+ // after app.setup returns and all waitUntil promises resolve.
44
+ @customElement("project-info-panel")
45
+ class ProjectInfoPanel extends LitElement {
46
+ @consume({ context: appStateContext, subscribe: true })
47
+ @state()
48
+ private _appState?: AppState;
49
+
50
+ @consume({ context: componentsContext, subscribe: true })
51
+ @state()
52
+ private _components?: OBC.Components;
53
+
54
+ @consume({ context: clientContext, subscribe: true })
55
+ @state()
56
+ private _client?: PlatformClient;
57
+
58
+ @consume({ context: projectDataContext, subscribe: true })
59
+ @state()
60
+ private _projectData?: ProjectData;
61
+
62
+ render() {
63
+ if (!this._components || !this._client || !this._projectData) {
64
+ return html`<bim-label>Loading…</bim-label>`;
65
+ }
66
+ return html`
67
+ <bim-panel label="Project Info">
68
+ <bim-panel-section label="Project" icon="solar:buildings-bold">
69
+ <bim-label .value=${this._projectData.name}></bim-label>
70
+ </bim-panel-section>
71
+ <bim-panel-section label="Engine" icon="solar:cpu-bold">
72
+ <bim-label>Components ready</bim-label>
73
+ </bim-panel-section>
74
+ </bim-panel>
75
+ `;
76
+ }
77
+ }
78
+
79
+ // ---- app wiring ----
80
+
81
+ const UIManager = uiManagerDef.componentDefinition;
82
+ const client = PlatformClient.fromPlatformContext();
83
+ const { components } = (await client.setup(
84
+ { OBC, OBF, BUI, THREE, FRAGS },
85
+ { uuid: UIManager.uuid },
86
+ )) as { components: OBC.Components };
87
+ components.get(UIManager).init();
88
+
89
+ const app = document.createElement("top-app") as unknown as App;
90
+
91
+ app.setup = (waitUntil) => {
92
+ waitUntil(
93
+ (async () => {
94
+ const fragments = components.get(OBC.FragmentsManager);
95
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
96
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), {
97
+ classicWorker: true,
98
+ });
99
+ })(),
100
+ "Fragments Core",
101
+ );
102
+ return { components, client };
103
+ };
104
+
105
+ app.elements = {
106
+ viewer: () =>
107
+ html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
108
+
109
+ // Any element rendered inside top-app can consume the contexts above.
110
+ info: () => html`<project-info-panel></project-info-panel>`,
111
+ };
112
+
113
+ app.base = "viewer";
114
+ app.layouts = {
115
+ main: {
116
+ label: "Main",
117
+ icon: "solar:home-bold",
118
+ template: `"info" 1fr / 22rem`,
119
+ },
120
+ };
121
+ app.layout = "main";
122
+
123
+ const container = document.getElementById("that-open-app") ?? document.body;
124
+ container.appendChild(app);
125
+ document.body.style.margin = "0";