@thatopen/services 0.3.15 → 0.4.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/built-in/index.d.ts +165 -2
- package/dist/cli.js +27 -13
- 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 +396 -374
- package/dist/vite-env.d.ts +1 -1
- package/docs/builtin/paths.json +94 -86
- 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/package.json +4 -4
- 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/ProjectFileStorage/example.ts +150 -0
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -160
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -0
- package/src/built-in/index.ts +1048 -881
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- 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/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,159 +1,159 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## Persistent banners with top:banner
|
|
3
|
-
---
|
|
4
|
-
Some application states need a persistent, visible indicator that stays on
|
|
5
|
-
screen until the state is explicitly cleared — an archived-files mode, a
|
|
6
|
-
read-only permission warning, a degraded-connectivity notice. A toast
|
|
7
|
-
auto-dismisses after a few seconds, which is wrong for these cases. A modal
|
|
8
|
-
blocks interaction entirely, which is too heavy.
|
|
9
|
-
|
|
10
|
-
top-app provides a banner slot at the top of the layout that occupies real
|
|
11
|
-
space (pushes content down) and stays visible until dismissed programmatically.
|
|
12
|
-
Any descendant component can show or hide it by dispatching standard DOM events
|
|
13
|
-
— no reference to the root element required.
|
|
14
|
-
|
|
15
|
-
This tutorial covers showing a banner from a child component by dispatching
|
|
16
|
-
top:banner; customising the banner type, icon, message, and description;
|
|
17
|
-
making a banner user-dismissible with the dismissible flag; and hiding it
|
|
18
|
-
programmatically with top:banner:hide.
|
|
19
|
-
|
|
20
|
-
By the end, you'll have an app where a panel can activate and deactivate a
|
|
21
|
-
persistent banner that pushes the layout content down while it is visible.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { html, LitElement } from "lit";
|
|
25
|
-
import { customElement, state } from "lit/decorators.js";
|
|
26
|
-
import * as THREE from "three";
|
|
27
|
-
import * as OBC from "@thatopen/components";
|
|
28
|
-
import * as OBF from "@thatopen/components-front";
|
|
29
|
-
import * as FRAGS from "@thatopen/fragments";
|
|
30
|
-
import * as BUI from "@thatopen/ui";
|
|
31
|
-
import { PlatformClient, UIManager } from "@thatopen/services";
|
|
32
|
-
import type { App } from "../../index";
|
|
33
|
-
|
|
34
|
-
// A child component that controls a banner without holding a reference to top-app.
|
|
35
|
-
// It dispatches top:banner to show and top:banner:hide to remove it.
|
|
36
|
-
@customElement("banner-controller")
|
|
37
|
-
class BannerController extends LitElement {
|
|
38
|
-
@state() private _active = false;
|
|
39
|
-
|
|
40
|
-
private _showReadOnly() {
|
|
41
|
-
this._active = true;
|
|
42
|
-
|
|
43
|
-
// top:banner shows a persistent bar at the top of the nearest top-app ancestor.
|
|
44
|
-
// It replaces any existing banner — only one banner is shown at a time.
|
|
45
|
-
this.dispatchEvent(
|
|
46
|
-
new CustomEvent("top:banner", {
|
|
47
|
-
bubbles: true,
|
|
48
|
-
composed: true,
|
|
49
|
-
detail: {
|
|
50
|
-
type: "warning",
|
|
51
|
-
icon: "tabler:lock",
|
|
52
|
-
message: "Read-only mode",
|
|
53
|
-
description: "You do not have write permissions on this project.",
|
|
54
|
-
// dismissible:true adds a close button on the banner itself.
|
|
55
|
-
// Omit it (or set false) when the banner must only be cleared by code.
|
|
56
|
-
dismissible: true,
|
|
57
|
-
},
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private _showArchived() {
|
|
63
|
-
this._active = true;
|
|
64
|
-
|
|
65
|
-
// Calling top:banner again replaces the previous banner immediately.
|
|
66
|
-
this.dispatchEvent(
|
|
67
|
-
new CustomEvent("top:banner", {
|
|
68
|
-
bubbles: true,
|
|
69
|
-
composed: true,
|
|
70
|
-
detail: {
|
|
71
|
-
type: "warning",
|
|
72
|
-
icon: "tabler:archive",
|
|
73
|
-
message: "Viewing archived files",
|
|
74
|
-
description: "Archived items are permanently deleted after 30 days.",
|
|
75
|
-
// No dismissible — this banner should only be cleared when the mode exits.
|
|
76
|
-
},
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private _hide() {
|
|
82
|
-
this._active = false;
|
|
83
|
-
|
|
84
|
-
// top:banner:hide clears the current banner unconditionally.
|
|
85
|
-
this.dispatchEvent(
|
|
86
|
-
new CustomEvent("top:banner:hide", { bubbles: true, composed: true }),
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
render() {
|
|
91
|
-
return html`
|
|
92
|
-
<bim-panel label="Banner Controller">
|
|
93
|
-
<bim-panel-section label="Banners" icon="tabler:message">
|
|
94
|
-
<bim-button
|
|
95
|
-
label="Show read-only banner (dismissible)"
|
|
96
|
-
icon="tabler:lock"
|
|
97
|
-
@click=${this._showReadOnly}
|
|
98
|
-
></bim-button>
|
|
99
|
-
<bim-button
|
|
100
|
-
label="Show archived banner (programmatic only)"
|
|
101
|
-
icon="tabler:archive"
|
|
102
|
-
@click=${this._showArchived}
|
|
103
|
-
></bim-button>
|
|
104
|
-
<bim-button
|
|
105
|
-
label="Hide banner"
|
|
106
|
-
icon="tabler:x"
|
|
107
|
-
?disabled=${!this._active}
|
|
108
|
-
@click=${this._hide}
|
|
109
|
-
></bim-button>
|
|
110
|
-
</bim-panel-section>
|
|
111
|
-
</bim-panel>
|
|
112
|
-
`;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---- app wiring ----
|
|
117
|
-
|
|
118
|
-
const client = PlatformClient.fromPlatformContext();
|
|
119
|
-
const { components } = (await client.setup(
|
|
120
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
121
|
-
{ uuid: UIManager.uuid },
|
|
122
|
-
)) as { components: OBC.Components };
|
|
123
|
-
components.get(UIManager).init();
|
|
124
|
-
|
|
125
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
126
|
-
|
|
127
|
-
app.setup = (waitUntil) => {
|
|
128
|
-
waitUntil(
|
|
129
|
-
(async () => {
|
|
130
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
131
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
132
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
133
|
-
classicWorker: true,
|
|
134
|
-
});
|
|
135
|
-
})(),
|
|
136
|
-
"Fragments Core",
|
|
137
|
-
);
|
|
138
|
-
return { components, client };
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
app.elements = {
|
|
142
|
-
viewer: () =>
|
|
143
|
-
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
144
|
-
panel: () => html`<banner-controller></banner-controller>`,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
app.base = "viewer";
|
|
148
|
-
app.layouts = {
|
|
149
|
-
main: {
|
|
150
|
-
label: "Main",
|
|
151
|
-
icon: "solar:home-bold",
|
|
152
|
-
template: `"panel" 1fr / 22rem`,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
app.layout = "main";
|
|
156
|
-
|
|
157
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
158
|
-
container.appendChild(app);
|
|
159
|
-
document.body.style.margin = "0";
|
|
1
|
+
/* MD
|
|
2
|
+
## Persistent banners with top:banner
|
|
3
|
+
---
|
|
4
|
+
Some application states need a persistent, visible indicator that stays on
|
|
5
|
+
screen until the state is explicitly cleared — an archived-files mode, a
|
|
6
|
+
read-only permission warning, a degraded-connectivity notice. A toast
|
|
7
|
+
auto-dismisses after a few seconds, which is wrong for these cases. A modal
|
|
8
|
+
blocks interaction entirely, which is too heavy.
|
|
9
|
+
|
|
10
|
+
top-app provides a banner slot at the top of the layout that occupies real
|
|
11
|
+
space (pushes content down) and stays visible until dismissed programmatically.
|
|
12
|
+
Any descendant component can show or hide it by dispatching standard DOM events
|
|
13
|
+
— no reference to the root element required.
|
|
14
|
+
|
|
15
|
+
This tutorial covers showing a banner from a child component by dispatching
|
|
16
|
+
top:banner; customising the banner type, icon, message, and description;
|
|
17
|
+
making a banner user-dismissible with the dismissible flag; and hiding it
|
|
18
|
+
programmatically with top:banner:hide.
|
|
19
|
+
|
|
20
|
+
By the end, you'll have an app where a panel can activate and deactivate a
|
|
21
|
+
persistent banner that pushes the layout content down while it is visible.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { html, LitElement } from "lit";
|
|
25
|
+
import { customElement, state } from "lit/decorators.js";
|
|
26
|
+
import * as THREE from "three";
|
|
27
|
+
import * as OBC from "@thatopen/components";
|
|
28
|
+
import * as OBF from "@thatopen/components-front";
|
|
29
|
+
import * as FRAGS from "@thatopen/fragments";
|
|
30
|
+
import * as BUI from "@thatopen/ui";
|
|
31
|
+
import { PlatformClient, UIManager } from "@thatopen/services";
|
|
32
|
+
import type { App } from "../../index";
|
|
33
|
+
|
|
34
|
+
// A child component that controls a banner without holding a reference to top-app.
|
|
35
|
+
// It dispatches top:banner to show and top:banner:hide to remove it.
|
|
36
|
+
@customElement("banner-controller")
|
|
37
|
+
class BannerController extends LitElement {
|
|
38
|
+
@state() private _active = false;
|
|
39
|
+
|
|
40
|
+
private _showReadOnly() {
|
|
41
|
+
this._active = true;
|
|
42
|
+
|
|
43
|
+
// top:banner shows a persistent bar at the top of the nearest top-app ancestor.
|
|
44
|
+
// It replaces any existing banner — only one banner is shown at a time.
|
|
45
|
+
this.dispatchEvent(
|
|
46
|
+
new CustomEvent("top:banner", {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
composed: true,
|
|
49
|
+
detail: {
|
|
50
|
+
type: "warning",
|
|
51
|
+
icon: "tabler:lock",
|
|
52
|
+
message: "Read-only mode",
|
|
53
|
+
description: "You do not have write permissions on this project.",
|
|
54
|
+
// dismissible:true adds a close button on the banner itself.
|
|
55
|
+
// Omit it (or set false) when the banner must only be cleared by code.
|
|
56
|
+
dismissible: true,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _showArchived() {
|
|
63
|
+
this._active = true;
|
|
64
|
+
|
|
65
|
+
// Calling top:banner again replaces the previous banner immediately.
|
|
66
|
+
this.dispatchEvent(
|
|
67
|
+
new CustomEvent("top:banner", {
|
|
68
|
+
bubbles: true,
|
|
69
|
+
composed: true,
|
|
70
|
+
detail: {
|
|
71
|
+
type: "warning",
|
|
72
|
+
icon: "tabler:archive",
|
|
73
|
+
message: "Viewing archived files",
|
|
74
|
+
description: "Archived items are permanently deleted after 30 days.",
|
|
75
|
+
// No dismissible — this banner should only be cleared when the mode exits.
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private _hide() {
|
|
82
|
+
this._active = false;
|
|
83
|
+
|
|
84
|
+
// top:banner:hide clears the current banner unconditionally.
|
|
85
|
+
this.dispatchEvent(
|
|
86
|
+
new CustomEvent("top:banner:hide", { bubbles: true, composed: true }),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
render() {
|
|
91
|
+
return html`
|
|
92
|
+
<bim-panel label="Banner Controller">
|
|
93
|
+
<bim-panel-section label="Banners" icon="tabler:message">
|
|
94
|
+
<bim-button
|
|
95
|
+
label="Show read-only banner (dismissible)"
|
|
96
|
+
icon="tabler:lock"
|
|
97
|
+
@click=${this._showReadOnly}
|
|
98
|
+
></bim-button>
|
|
99
|
+
<bim-button
|
|
100
|
+
label="Show archived banner (programmatic only)"
|
|
101
|
+
icon="tabler:archive"
|
|
102
|
+
@click=${this._showArchived}
|
|
103
|
+
></bim-button>
|
|
104
|
+
<bim-button
|
|
105
|
+
label="Hide banner"
|
|
106
|
+
icon="tabler:x"
|
|
107
|
+
?disabled=${!this._active}
|
|
108
|
+
@click=${this._hide}
|
|
109
|
+
></bim-button>
|
|
110
|
+
</bim-panel-section>
|
|
111
|
+
</bim-panel>
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---- app wiring ----
|
|
117
|
+
|
|
118
|
+
const client = PlatformClient.fromPlatformContext();
|
|
119
|
+
const { components } = (await client.setup(
|
|
120
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
121
|
+
{ uuid: UIManager.uuid },
|
|
122
|
+
)) as { components: OBC.Components };
|
|
123
|
+
components.get(UIManager).init();
|
|
124
|
+
|
|
125
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
126
|
+
|
|
127
|
+
app.setup = (waitUntil) => {
|
|
128
|
+
waitUntil(
|
|
129
|
+
(async () => {
|
|
130
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
131
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
132
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
133
|
+
classicWorker: true,
|
|
134
|
+
});
|
|
135
|
+
})(),
|
|
136
|
+
"Fragments Core",
|
|
137
|
+
);
|
|
138
|
+
return { components, client };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
app.elements = {
|
|
142
|
+
viewer: () =>
|
|
143
|
+
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
144
|
+
panel: () => html`<banner-controller></banner-controller>`,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
app.base = "viewer";
|
|
148
|
+
app.layouts = {
|
|
149
|
+
main: {
|
|
150
|
+
label: "Main",
|
|
151
|
+
icon: "solar:home-bold",
|
|
152
|
+
template: `"panel" 1fr / 22rem`,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
app.layout = "main";
|
|
156
|
+
|
|
157
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
158
|
+
container.appendChild(app);
|
|
159
|
+
document.body.style.margin = "0";
|
|
@@ -1,123 +1,123 @@
|
|
|
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, UIManager } from "@thatopen/services";
|
|
31
|
-
import {
|
|
32
|
-
componentsContext,
|
|
33
|
-
clientContext,
|
|
34
|
-
projectDataContext,
|
|
35
|
-
appStateContext,
|
|
36
|
-
type App,
|
|
37
|
-
type AppState,
|
|
38
|
-
} from "../../index";
|
|
39
|
-
|
|
40
|
-
// appStateContext is provided by top-app immediately — before setup completes.
|
|
41
|
-
// componentsContext, clientContext, and projectDataContext become available only
|
|
42
|
-
// after app.setup returns and all waitUntil promises resolve.
|
|
43
|
-
@customElement("project-info-panel")
|
|
44
|
-
class ProjectInfoPanel extends LitElement {
|
|
45
|
-
@consume({ context: appStateContext, subscribe: true })
|
|
46
|
-
@state()
|
|
47
|
-
private _appState?: AppState;
|
|
48
|
-
|
|
49
|
-
@consume({ context: componentsContext, subscribe: true })
|
|
50
|
-
@state()
|
|
51
|
-
private _components?: OBC.Components;
|
|
52
|
-
|
|
53
|
-
@consume({ context: clientContext, subscribe: true })
|
|
54
|
-
@state()
|
|
55
|
-
private _client?: PlatformClient;
|
|
56
|
-
|
|
57
|
-
@consume({ context: projectDataContext, subscribe: true })
|
|
58
|
-
@state()
|
|
59
|
-
private _projectData?: ProjectData;
|
|
60
|
-
|
|
61
|
-
render() {
|
|
62
|
-
if (!this._components || !this._client || !this._projectData) {
|
|
63
|
-
return html`<bim-label>Loading…</bim-label>`;
|
|
64
|
-
}
|
|
65
|
-
return html`
|
|
66
|
-
<bim-panel label="Project Info">
|
|
67
|
-
<bim-panel-section label="Project" icon="solar:buildings-bold">
|
|
68
|
-
<bim-label .value=${this._projectData.name}></bim-label>
|
|
69
|
-
</bim-panel-section>
|
|
70
|
-
<bim-panel-section label="Engine" icon="solar:cpu-bold">
|
|
71
|
-
<bim-label>Components ready</bim-label>
|
|
72
|
-
</bim-panel-section>
|
|
73
|
-
</bim-panel>
|
|
74
|
-
`;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ---- app wiring ----
|
|
79
|
-
|
|
80
|
-
const client = PlatformClient.fromPlatformContext();
|
|
81
|
-
const { components } = (await client.setup(
|
|
82
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
83
|
-
{ uuid: UIManager.uuid },
|
|
84
|
-
)) as { components: OBC.Components };
|
|
85
|
-
components.get(UIManager).init();
|
|
86
|
-
|
|
87
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
88
|
-
|
|
89
|
-
app.setup = (waitUntil) => {
|
|
90
|
-
waitUntil(
|
|
91
|
-
(async () => {
|
|
92
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
93
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
94
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
95
|
-
classicWorker: true,
|
|
96
|
-
});
|
|
97
|
-
})(),
|
|
98
|
-
"Fragments Core",
|
|
99
|
-
);
|
|
100
|
-
return { components, client };
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
app.elements = {
|
|
104
|
-
viewer: () =>
|
|
105
|
-
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
106
|
-
|
|
107
|
-
// Any element rendered inside top-app can consume the contexts above.
|
|
108
|
-
info: () => html`<project-info-panel></project-info-panel>`,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
app.base = "viewer";
|
|
112
|
-
app.layouts = {
|
|
113
|
-
main: {
|
|
114
|
-
label: "Main",
|
|
115
|
-
icon: "solar:home-bold",
|
|
116
|
-
template: `"info" 1fr / 22rem`,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
app.layout = "main";
|
|
120
|
-
|
|
121
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
122
|
-
container.appendChild(app);
|
|
123
|
-
document.body.style.margin = "0";
|
|
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, UIManager } from "@thatopen/services";
|
|
31
|
+
import {
|
|
32
|
+
componentsContext,
|
|
33
|
+
clientContext,
|
|
34
|
+
projectDataContext,
|
|
35
|
+
appStateContext,
|
|
36
|
+
type App,
|
|
37
|
+
type AppState,
|
|
38
|
+
} from "../../index";
|
|
39
|
+
|
|
40
|
+
// appStateContext is provided by top-app immediately — before setup completes.
|
|
41
|
+
// componentsContext, clientContext, and projectDataContext become available only
|
|
42
|
+
// after app.setup returns and all waitUntil promises resolve.
|
|
43
|
+
@customElement("project-info-panel")
|
|
44
|
+
class ProjectInfoPanel extends LitElement {
|
|
45
|
+
@consume({ context: appStateContext, subscribe: true })
|
|
46
|
+
@state()
|
|
47
|
+
private _appState?: AppState;
|
|
48
|
+
|
|
49
|
+
@consume({ context: componentsContext, subscribe: true })
|
|
50
|
+
@state()
|
|
51
|
+
private _components?: OBC.Components;
|
|
52
|
+
|
|
53
|
+
@consume({ context: clientContext, subscribe: true })
|
|
54
|
+
@state()
|
|
55
|
+
private _client?: PlatformClient;
|
|
56
|
+
|
|
57
|
+
@consume({ context: projectDataContext, subscribe: true })
|
|
58
|
+
@state()
|
|
59
|
+
private _projectData?: ProjectData;
|
|
60
|
+
|
|
61
|
+
render() {
|
|
62
|
+
if (!this._components || !this._client || !this._projectData) {
|
|
63
|
+
return html`<bim-label>Loading…</bim-label>`;
|
|
64
|
+
}
|
|
65
|
+
return html`
|
|
66
|
+
<bim-panel label="Project Info">
|
|
67
|
+
<bim-panel-section label="Project" icon="solar:buildings-bold">
|
|
68
|
+
<bim-label .value=${this._projectData.name}></bim-label>
|
|
69
|
+
</bim-panel-section>
|
|
70
|
+
<bim-panel-section label="Engine" icon="solar:cpu-bold">
|
|
71
|
+
<bim-label>Components ready</bim-label>
|
|
72
|
+
</bim-panel-section>
|
|
73
|
+
</bim-panel>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---- app wiring ----
|
|
79
|
+
|
|
80
|
+
const client = PlatformClient.fromPlatformContext();
|
|
81
|
+
const { components } = (await client.setup(
|
|
82
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
83
|
+
{ uuid: UIManager.uuid },
|
|
84
|
+
)) as { components: OBC.Components };
|
|
85
|
+
components.get(UIManager).init();
|
|
86
|
+
|
|
87
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
88
|
+
|
|
89
|
+
app.setup = (waitUntil) => {
|
|
90
|
+
waitUntil(
|
|
91
|
+
(async () => {
|
|
92
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
93
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
94
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
95
|
+
classicWorker: true,
|
|
96
|
+
});
|
|
97
|
+
})(),
|
|
98
|
+
"Fragments Core",
|
|
99
|
+
);
|
|
100
|
+
return { components, client };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
app.elements = {
|
|
104
|
+
viewer: () =>
|
|
105
|
+
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
106
|
+
|
|
107
|
+
// Any element rendered inside top-app can consume the contexts above.
|
|
108
|
+
info: () => html`<project-info-panel></project-info-panel>`,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
app.base = "viewer";
|
|
112
|
+
app.layouts = {
|
|
113
|
+
main: {
|
|
114
|
+
label: "Main",
|
|
115
|
+
icon: "solar:home-bold",
|
|
116
|
+
template: `"info" 1fr / 22rem`,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
app.layout = "main";
|
|
120
|
+
|
|
121
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
122
|
+
container.appendChild(app);
|
|
123
|
+
document.body.style.margin = "0";
|