@thatopen/services 0.3.16 → 0.5.0
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.
- package/README.md +62 -62
- package/dist/cli/commands/rhino.d.ts +11 -0
- package/dist/cli/lib/plugin-install.d.ts +24 -0
- package/dist/cli.js +124 -1
- package/dist/core/client.d.ts +1 -0
- package/dist/core/platform-client.d.ts +7 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.es.js +382 -370
- package/dist/vite-env.d.ts +1 -1
- package/docs/ai-quickstart.md +130 -130
- package/docs/cli/create.md +29 -29
- package/docs/cli/local-server.md +38 -38
- package/docs/cli/login.md +32 -32
- package/docs/cli/publish.md +31 -31
- package/docs/cli/run.md +26 -26
- package/docs/cli/serve.md +43 -43
- package/docs/cli/swap.md +30 -30
- package/docs/cloud/GaussianSplatConverter.md +43 -43
- package/docs/cloud/PointCloudConverter.md +53 -53
- package/docs/revit-collab-quickstart.md +256 -256
- package/package.json +95 -95
- package/resources/AGENTS.md +40 -40
- package/src/built-in/CDEManager/example.ts +214 -214
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
- package/src/built-in/ClashesManager/example.ts +283 -283
- package/src/built-in/UIManager/src/app/example.ts +97 -97
- package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
- package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
- package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
- package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
- package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
- package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
- package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/cli/templates/app/README.md +217 -217
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- package/src/cli/templates/app/src/app.ts +37 -37
- package/src/cli/templates/app/src/assets/tool-placeholder.ts +50 -50
- package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
- package/src/cli/templates/app/src/bim-components/index.ts +1 -1
- package/src/cli/templates/app/src/globals.ts +1 -1
- package/src/cli/templates/app/src/main.ts +199 -199
- package/src/cli/templates/app/src/setups/reality-capture/lib/hidden-tiles-plugin.ts +88 -88
- package/src/cli/templates/app/src/setups/reality-capture/lib/point-tile-plugin.ts +166 -166
- package/src/cli/templates/app/src/setups/reality-capture/lib/render-frame.ts +31 -31
- package/src/cli/templates/app/src/setups/reality-capture/lib/splat-tile-plugin.ts +51 -51
- package/src/cli/templates/app/src/setups/reality-capture-viewer.ts +1383 -1383
- package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
- package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
- package/src/cli/templates/app/src/ui-components/index.ts +2 -2
- package/src/cli/templates/app/tsconfig.json +16 -16
- package/src/cli/templates/app/vite.config.js +57 -57
- package/src/cli/templates/cloud-component/README.md +205 -205
- package/src/cli/templates/cloud-component/_thatopen +5 -5
- package/src/cli/templates/cloud-component/declarations.json +4 -4
- package/src/cli/templates/cloud-component/package.json +21 -21
- package/src/cli/templates/cloud-component/src/main.ts +70 -70
- package/src/cli/templates/cloud-component/tsconfig.json +16 -16
- package/src/cli/templates/cloud-component/vite.config.js +46 -46
- package/src/cli/templates/shared/AGENTS.md +8 -8
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/src/cli/templates/shared/_gitignore +5 -5
|
@@ -1,97 +1,97 @@
|
|
|
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, UIManager } from "@thatopen/services";
|
|
31
|
-
import type { App } from "./index";
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const client = PlatformClient.fromPlatformContext();
|
|
35
|
-
|
|
36
|
-
// UIManager must be in the setup call: it registers all platform web components
|
|
37
|
-
// (top-app, top-viewer, top-viewer-tools, etc.) before the DOM renders.
|
|
38
|
-
const { components } = (await client.setup(
|
|
39
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
40
|
-
{ uuid: UIManager.uuid },
|
|
41
|
-
)) as { components: OBC.Components };
|
|
42
|
-
|
|
43
|
-
components.get(UIManager).init();
|
|
44
|
-
|
|
45
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
46
|
-
|
|
47
|
-
// setup runs synchronously and returns { components, client }.
|
|
48
|
-
// waitUntil registers async tasks that appear as labelled steps in the loading screen.
|
|
49
|
-
// All tasks run in parallel; top-app mounts only after every promise resolves.
|
|
50
|
-
app.setup = (waitUntil) => {
|
|
51
|
-
waitUntil(
|
|
52
|
-
(async () => {
|
|
53
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
54
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
55
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
56
|
-
classicWorker: true,
|
|
57
|
-
});
|
|
58
|
-
})(),
|
|
59
|
-
"Fragments Core",
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
waitUntil(
|
|
63
|
-
(async () => {
|
|
64
|
-
const loader = components.get(OBC.IfcLoader);
|
|
65
|
-
await loader.setup();
|
|
66
|
-
})(),
|
|
67
|
-
"IFC Loader",
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
return { components, client };
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Each key must match an area name used in at least one layout template.
|
|
74
|
-
app.elements = {
|
|
75
|
-
viewer: () =>
|
|
76
|
-
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// CSS grid-template shorthand. Area names are the keys for app.elements.
|
|
80
|
-
// "." creates an empty unnamed cell and is never rendered.
|
|
81
|
-
app.layouts = {
|
|
82
|
-
main: {
|
|
83
|
-
label: "Main",
|
|
84
|
-
icon: "solar:3d-square-bold",
|
|
85
|
-
template: `"viewer" 1fr / 1fr`,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
app.layout = "main";
|
|
90
|
-
|
|
91
|
-
// forceLoading keeps the boot screen visible even after setup completes.
|
|
92
|
-
// Useful during development to inspect the loading UI without real async tasks.
|
|
93
|
-
// app.forceLoading = true;
|
|
94
|
-
|
|
95
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
96
|
-
container.appendChild(app);
|
|
97
|
-
document.body.style.margin = "0";
|
|
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, UIManager } from "@thatopen/services";
|
|
31
|
+
import type { App } from "./index";
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const client = PlatformClient.fromPlatformContext();
|
|
35
|
+
|
|
36
|
+
// UIManager must be in the setup call: it registers all platform web components
|
|
37
|
+
// (top-app, top-viewer, top-viewer-tools, etc.) before the DOM renders.
|
|
38
|
+
const { components } = (await client.setup(
|
|
39
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
40
|
+
{ uuid: UIManager.uuid },
|
|
41
|
+
)) as { components: OBC.Components };
|
|
42
|
+
|
|
43
|
+
components.get(UIManager).init();
|
|
44
|
+
|
|
45
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
46
|
+
|
|
47
|
+
// setup runs synchronously and returns { components, client }.
|
|
48
|
+
// waitUntil registers async tasks that appear as labelled steps in the loading screen.
|
|
49
|
+
// All tasks run in parallel; top-app mounts only after every promise resolves.
|
|
50
|
+
app.setup = (waitUntil) => {
|
|
51
|
+
waitUntil(
|
|
52
|
+
(async () => {
|
|
53
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
54
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
55
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
56
|
+
classicWorker: true,
|
|
57
|
+
});
|
|
58
|
+
})(),
|
|
59
|
+
"Fragments Core",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
waitUntil(
|
|
63
|
+
(async () => {
|
|
64
|
+
const loader = components.get(OBC.IfcLoader);
|
|
65
|
+
await loader.setup();
|
|
66
|
+
})(),
|
|
67
|
+
"IFC Loader",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return { components, client };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Each key must match an area name used in at least one layout template.
|
|
74
|
+
app.elements = {
|
|
75
|
+
viewer: () =>
|
|
76
|
+
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// CSS grid-template shorthand. Area names are the keys for app.elements.
|
|
80
|
+
// "." creates an empty unnamed cell and is never rendered.
|
|
81
|
+
app.layouts = {
|
|
82
|
+
main: {
|
|
83
|
+
label: "Main",
|
|
84
|
+
icon: "solar:3d-square-bold",
|
|
85
|
+
template: `"viewer" 1fr / 1fr`,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
app.layout = "main";
|
|
90
|
+
|
|
91
|
+
// forceLoading keeps the boot screen visible even after setup completes.
|
|
92
|
+
// Useful during development to inspect the loading UI without real async tasks.
|
|
93
|
+
// app.forceLoading = true;
|
|
94
|
+
|
|
95
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
96
|
+
container.appendChild(app);
|
|
97
|
+
document.body.style.margin = "0";
|
|
@@ -1,140 +1,140 @@
|
|
|
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, UIManager } from "@thatopen/services";
|
|
33
|
-
import {
|
|
34
|
-
appStateContext,
|
|
35
|
-
AppState,
|
|
36
|
-
AppStateController,
|
|
37
|
-
type App,
|
|
38
|
-
} from "../../index";
|
|
39
|
-
|
|
40
|
-
// Define a typed payload for this app's custom state.
|
|
41
|
-
type MyAppState = { selectedFilter: string; count: number };
|
|
42
|
-
|
|
43
|
-
// AppStateController subscribes to appStateContext and triggers a re-render
|
|
44
|
-
// on every setCustom call — across any component in the tree that holds one.
|
|
45
|
-
@customElement("filter-selector")
|
|
46
|
-
class FilterSelector extends LitElement {
|
|
47
|
-
@consume({ context: appStateContext, subscribe: true })
|
|
48
|
-
@state()
|
|
49
|
-
private _appState?: AppState;
|
|
50
|
-
|
|
51
|
-
// Declare the controller — the LitElement lifecycle handles subscription.
|
|
52
|
-
private _stateCtrl = new AppStateController<MyAppState>(this, () => this._appState);
|
|
53
|
-
|
|
54
|
-
render() {
|
|
55
|
-
const filters = ["All", "Walls", "Slabs", "Columns"];
|
|
56
|
-
const active = this._stateCtrl.custom?.selectedFilter ?? "All";
|
|
57
|
-
return html`
|
|
58
|
-
<bim-panel-section label="Filter" icon="solar:filter-bold">
|
|
59
|
-
${filters.map(
|
|
60
|
-
(f) => html`
|
|
61
|
-
<bim-button
|
|
62
|
-
.label=${f}
|
|
63
|
-
?active=${active === f}
|
|
64
|
-
@click=${() => {
|
|
65
|
-
this._appState?.setCustom<MyAppState>({ selectedFilter: f, count: 0 });
|
|
66
|
-
}}
|
|
67
|
-
></bim-button>
|
|
68
|
-
`,
|
|
69
|
-
)}
|
|
70
|
-
</bim-panel-section>
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
@customElement("result-counter")
|
|
76
|
-
class ResultCounter extends LitElement {
|
|
77
|
-
@consume({ context: appStateContext, subscribe: true })
|
|
78
|
-
@state()
|
|
79
|
-
private _appState?: AppState;
|
|
80
|
-
|
|
81
|
-
private _stateCtrl = new AppStateController<MyAppState>(this, () => this._appState);
|
|
82
|
-
|
|
83
|
-
render() {
|
|
84
|
-
const { selectedFilter = "All", count = 0 } = this._stateCtrl.custom ?? {};
|
|
85
|
-
return html`
|
|
86
|
-
<bim-panel-section label="Results" icon="solar:list-bold">
|
|
87
|
-
<bim-label>${count} elements match "${selectedFilter}"</bim-label>
|
|
88
|
-
</bim-panel-section>
|
|
89
|
-
`;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ---- app wiring ----
|
|
94
|
-
|
|
95
|
-
const client = PlatformClient.fromPlatformContext();
|
|
96
|
-
const { components } = (await client.setup(
|
|
97
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
98
|
-
{ uuid: UIManager.uuid },
|
|
99
|
-
)) as { components: OBC.Components };
|
|
100
|
-
components.get(UIManager).init();
|
|
101
|
-
|
|
102
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
103
|
-
|
|
104
|
-
// appState is available immediately — before setup completes — so components
|
|
105
|
-
// that only consume appStateContext can render while the engine is still booting.
|
|
106
|
-
app.setup = (waitUntil) => {
|
|
107
|
-
waitUntil(
|
|
108
|
-
(async () => {
|
|
109
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
110
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
111
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
112
|
-
classicWorker: true,
|
|
113
|
-
});
|
|
114
|
-
})(),
|
|
115
|
-
"Fragments Core",
|
|
116
|
-
);
|
|
117
|
-
return { components, client };
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
app.elements = {
|
|
121
|
-
panel: () => html`
|
|
122
|
-
<bim-panel label="State Demo">
|
|
123
|
-
<filter-selector></filter-selector>
|
|
124
|
-
<result-counter></result-counter>
|
|
125
|
-
</bim-panel>
|
|
126
|
-
`,
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
app.layouts = {
|
|
130
|
-
main: {
|
|
131
|
-
label: "Main",
|
|
132
|
-
icon: "solar:home-bold",
|
|
133
|
-
template: `"panel" 1fr / 22rem`,
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
app.layout = "main";
|
|
137
|
-
|
|
138
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
139
|
-
container.appendChild(app);
|
|
140
|
-
document.body.style.margin = "0";
|
|
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, UIManager } from "@thatopen/services";
|
|
33
|
+
import {
|
|
34
|
+
appStateContext,
|
|
35
|
+
AppState,
|
|
36
|
+
AppStateController,
|
|
37
|
+
type App,
|
|
38
|
+
} from "../../index";
|
|
39
|
+
|
|
40
|
+
// Define a typed payload for this app's custom state.
|
|
41
|
+
type MyAppState = { selectedFilter: string; count: number };
|
|
42
|
+
|
|
43
|
+
// AppStateController subscribes to appStateContext and triggers a re-render
|
|
44
|
+
// on every setCustom call — across any component in the tree that holds one.
|
|
45
|
+
@customElement("filter-selector")
|
|
46
|
+
class FilterSelector extends LitElement {
|
|
47
|
+
@consume({ context: appStateContext, subscribe: true })
|
|
48
|
+
@state()
|
|
49
|
+
private _appState?: AppState;
|
|
50
|
+
|
|
51
|
+
// Declare the controller — the LitElement lifecycle handles subscription.
|
|
52
|
+
private _stateCtrl = new AppStateController<MyAppState>(this, () => this._appState);
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
const filters = ["All", "Walls", "Slabs", "Columns"];
|
|
56
|
+
const active = this._stateCtrl.custom?.selectedFilter ?? "All";
|
|
57
|
+
return html`
|
|
58
|
+
<bim-panel-section label="Filter" icon="solar:filter-bold">
|
|
59
|
+
${filters.map(
|
|
60
|
+
(f) => html`
|
|
61
|
+
<bim-button
|
|
62
|
+
.label=${f}
|
|
63
|
+
?active=${active === f}
|
|
64
|
+
@click=${() => {
|
|
65
|
+
this._appState?.setCustom<MyAppState>({ selectedFilter: f, count: 0 });
|
|
66
|
+
}}
|
|
67
|
+
></bim-button>
|
|
68
|
+
`,
|
|
69
|
+
)}
|
|
70
|
+
</bim-panel-section>
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@customElement("result-counter")
|
|
76
|
+
class ResultCounter extends LitElement {
|
|
77
|
+
@consume({ context: appStateContext, subscribe: true })
|
|
78
|
+
@state()
|
|
79
|
+
private _appState?: AppState;
|
|
80
|
+
|
|
81
|
+
private _stateCtrl = new AppStateController<MyAppState>(this, () => this._appState);
|
|
82
|
+
|
|
83
|
+
render() {
|
|
84
|
+
const { selectedFilter = "All", count = 0 } = this._stateCtrl.custom ?? {};
|
|
85
|
+
return html`
|
|
86
|
+
<bim-panel-section label="Results" icon="solar:list-bold">
|
|
87
|
+
<bim-label>${count} elements match "${selectedFilter}"</bim-label>
|
|
88
|
+
</bim-panel-section>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---- app wiring ----
|
|
94
|
+
|
|
95
|
+
const client = PlatformClient.fromPlatformContext();
|
|
96
|
+
const { components } = (await client.setup(
|
|
97
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
98
|
+
{ uuid: UIManager.uuid },
|
|
99
|
+
)) as { components: OBC.Components };
|
|
100
|
+
components.get(UIManager).init();
|
|
101
|
+
|
|
102
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
103
|
+
|
|
104
|
+
// appState is available immediately — before setup completes — so components
|
|
105
|
+
// that only consume appStateContext can render while the engine is still booting.
|
|
106
|
+
app.setup = (waitUntil) => {
|
|
107
|
+
waitUntil(
|
|
108
|
+
(async () => {
|
|
109
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
110
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
111
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
112
|
+
classicWorker: true,
|
|
113
|
+
});
|
|
114
|
+
})(),
|
|
115
|
+
"Fragments Core",
|
|
116
|
+
);
|
|
117
|
+
return { components, client };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
app.elements = {
|
|
121
|
+
panel: () => html`
|
|
122
|
+
<bim-panel label="State Demo">
|
|
123
|
+
<filter-selector></filter-selector>
|
|
124
|
+
<result-counter></result-counter>
|
|
125
|
+
</bim-panel>
|
|
126
|
+
`,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
app.layouts = {
|
|
130
|
+
main: {
|
|
131
|
+
label: "Main",
|
|
132
|
+
icon: "solar:home-bold",
|
|
133
|
+
template: `"panel" 1fr / 22rem`,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
app.layout = "main";
|
|
137
|
+
|
|
138
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
139
|
+
container.appendChild(app);
|
|
140
|
+
document.body.style.margin = "0";
|