@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,163 +1,163 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## Confirmation modals with top:modal
|
|
3
|
-
---
|
|
4
|
-
Destructive or irreversible actions — deleting records, overwriting files,
|
|
5
|
-
leaving unsaved changes — should ask the user to confirm before proceeding.
|
|
6
|
-
Opening a custom dialog element from a deeply nested component means threading
|
|
7
|
-
a reference to a controller up through every layer of the tree.
|
|
8
|
-
|
|
9
|
-
top-app provides a modal slot that any descendant can trigger by dispatching
|
|
10
|
-
a single DOM event. The modal blocks the UI, shows a configurable message and
|
|
11
|
-
action labels, and calls back into the originating component when the user
|
|
12
|
-
confirms or cancels — no reference to the root element required.
|
|
13
|
-
|
|
14
|
-
This tutorial covers showing a modal from a child component by dispatching
|
|
15
|
-
top:modal; setting the label, icon, and button labels; running an async
|
|
16
|
-
confirm handler while the modal shows a loading spinner; and handling the
|
|
17
|
-
cancel path without side effects.
|
|
18
|
-
|
|
19
|
-
By the end, you'll have an app where a panel can trigger confirmation dialogs
|
|
20
|
-
for actions of varying severity, each with its own copy and async behaviour.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { html, LitElement } from "lit";
|
|
24
|
-
import { customElement, state } from "lit/decorators.js";
|
|
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
|
-
// A child component that triggers modals without holding a reference to top-app.
|
|
34
|
-
// It dispatches top:modal and receives the result via onConfirm / onCancel callbacks.
|
|
35
|
-
@customElement("modal-actions")
|
|
36
|
-
class ModalActions extends LitElement {
|
|
37
|
-
@state() private _lastAction = "—";
|
|
38
|
-
|
|
39
|
-
private _confirmDelete() {
|
|
40
|
-
// top:modal opens a blocking dialog on the nearest top-app ancestor.
|
|
41
|
-
// The modal stays open (with a loading spinner) while onConfirm is awaited.
|
|
42
|
-
this.dispatchEvent(
|
|
43
|
-
new CustomEvent("top:modal", {
|
|
44
|
-
bubbles: true,
|
|
45
|
-
composed: true,
|
|
46
|
-
detail: {
|
|
47
|
-
label: "Delete model",
|
|
48
|
-
icon: "tabler:trash",
|
|
49
|
-
confirmLabel: "Delete",
|
|
50
|
-
cancelLabel: "Keep",
|
|
51
|
-
content: html`
|
|
52
|
-
<bim-label style="white-space:normal;">
|
|
53
|
-
This will permanently remove the model and all its versions.
|
|
54
|
-
This action cannot be undone.
|
|
55
|
-
</bim-label>
|
|
56
|
-
`,
|
|
57
|
-
onConfirm: async () => {
|
|
58
|
-
// Simulate an async delete operation.
|
|
59
|
-
// The modal shows a loading spinner until this promise resolves.
|
|
60
|
-
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
|
61
|
-
this._lastAction = "Model deleted";
|
|
62
|
-
},
|
|
63
|
-
onCancel: () => {
|
|
64
|
-
this._lastAction = "Delete cancelled";
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private _confirmOverwrite() {
|
|
72
|
-
this.dispatchEvent(
|
|
73
|
-
new CustomEvent("top:modal", {
|
|
74
|
-
bubbles: true,
|
|
75
|
-
composed: true,
|
|
76
|
-
detail: {
|
|
77
|
-
label: "File already exists",
|
|
78
|
-
icon: "tabler:alert-triangle",
|
|
79
|
-
confirmLabel: "Overwrite",
|
|
80
|
-
cancelLabel: "Cancel",
|
|
81
|
-
content: html`
|
|
82
|
-
<bim-label style="white-space:normal;">
|
|
83
|
-
A file named <strong>structure.ifc</strong> already exists in this
|
|
84
|
-
folder. Uploading will create a new version alongside it.
|
|
85
|
-
</bim-label>
|
|
86
|
-
`,
|
|
87
|
-
// onConfirm can be synchronous too — the modal closes immediately.
|
|
88
|
-
onConfirm: () => {
|
|
89
|
-
this._lastAction = "Overwrite confirmed";
|
|
90
|
-
},
|
|
91
|
-
onCancel: () => {
|
|
92
|
-
this._lastAction = "Overwrite cancelled";
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
}),
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
render() {
|
|
100
|
-
return html`
|
|
101
|
-
<bim-panel label="Modal Actions">
|
|
102
|
-
<bim-panel-section label="Actions" icon="tabler:click">
|
|
103
|
-
<bim-button
|
|
104
|
-
label="Delete model (async confirm)"
|
|
105
|
-
icon="tabler:trash"
|
|
106
|
-
@click=${this._confirmDelete}
|
|
107
|
-
></bim-button>
|
|
108
|
-
<bim-button
|
|
109
|
-
label="Overwrite file (sync confirm)"
|
|
110
|
-
icon="tabler:file-upload"
|
|
111
|
-
@click=${this._confirmOverwrite}
|
|
112
|
-
></bim-button>
|
|
113
|
-
<bim-label>Last action: ${this._lastAction}</bim-label>
|
|
114
|
-
</bim-panel-section>
|
|
115
|
-
</bim-panel>
|
|
116
|
-
`;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ---- app wiring ----
|
|
121
|
-
|
|
122
|
-
const client = PlatformClient.fromPlatformContext();
|
|
123
|
-
const { components } = (await client.setup(
|
|
124
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
125
|
-
{ uuid: UIManager.uuid },
|
|
126
|
-
)) as { components: OBC.Components };
|
|
127
|
-
components.get(UIManager).init();
|
|
128
|
-
|
|
129
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
130
|
-
|
|
131
|
-
app.setup = (waitUntil) => {
|
|
132
|
-
waitUntil(
|
|
133
|
-
(async () => {
|
|
134
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
135
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
136
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
137
|
-
classicWorker: true,
|
|
138
|
-
});
|
|
139
|
-
})(),
|
|
140
|
-
"Fragments Core",
|
|
141
|
-
);
|
|
142
|
-
return { components, client };
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
app.elements = {
|
|
146
|
-
viewer: () =>
|
|
147
|
-
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
148
|
-
panel: () => html`<modal-actions></modal-actions>`,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
app.base = "viewer";
|
|
152
|
-
app.layouts = {
|
|
153
|
-
main: {
|
|
154
|
-
label: "Main",
|
|
155
|
-
icon: "solar:home-bold",
|
|
156
|
-
template: `"panel" 1fr / 22rem`,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
app.layout = "main";
|
|
160
|
-
|
|
161
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
162
|
-
container.appendChild(app);
|
|
163
|
-
document.body.style.margin = "0";
|
|
1
|
+
/* MD
|
|
2
|
+
## Confirmation modals with top:modal
|
|
3
|
+
---
|
|
4
|
+
Destructive or irreversible actions — deleting records, overwriting files,
|
|
5
|
+
leaving unsaved changes — should ask the user to confirm before proceeding.
|
|
6
|
+
Opening a custom dialog element from a deeply nested component means threading
|
|
7
|
+
a reference to a controller up through every layer of the tree.
|
|
8
|
+
|
|
9
|
+
top-app provides a modal slot that any descendant can trigger by dispatching
|
|
10
|
+
a single DOM event. The modal blocks the UI, shows a configurable message and
|
|
11
|
+
action labels, and calls back into the originating component when the user
|
|
12
|
+
confirms or cancels — no reference to the root element required.
|
|
13
|
+
|
|
14
|
+
This tutorial covers showing a modal from a child component by dispatching
|
|
15
|
+
top:modal; setting the label, icon, and button labels; running an async
|
|
16
|
+
confirm handler while the modal shows a loading spinner; and handling the
|
|
17
|
+
cancel path without side effects.
|
|
18
|
+
|
|
19
|
+
By the end, you'll have an app where a panel can trigger confirmation dialogs
|
|
20
|
+
for actions of varying severity, each with its own copy and async behaviour.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { html, LitElement } from "lit";
|
|
24
|
+
import { customElement, state } from "lit/decorators.js";
|
|
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
|
+
// A child component that triggers modals without holding a reference to top-app.
|
|
34
|
+
// It dispatches top:modal and receives the result via onConfirm / onCancel callbacks.
|
|
35
|
+
@customElement("modal-actions")
|
|
36
|
+
class ModalActions extends LitElement {
|
|
37
|
+
@state() private _lastAction = "—";
|
|
38
|
+
|
|
39
|
+
private _confirmDelete() {
|
|
40
|
+
// top:modal opens a blocking dialog on the nearest top-app ancestor.
|
|
41
|
+
// The modal stays open (with a loading spinner) while onConfirm is awaited.
|
|
42
|
+
this.dispatchEvent(
|
|
43
|
+
new CustomEvent("top:modal", {
|
|
44
|
+
bubbles: true,
|
|
45
|
+
composed: true,
|
|
46
|
+
detail: {
|
|
47
|
+
label: "Delete model",
|
|
48
|
+
icon: "tabler:trash",
|
|
49
|
+
confirmLabel: "Delete",
|
|
50
|
+
cancelLabel: "Keep",
|
|
51
|
+
content: html`
|
|
52
|
+
<bim-label style="white-space:normal;">
|
|
53
|
+
This will permanently remove the model and all its versions.
|
|
54
|
+
This action cannot be undone.
|
|
55
|
+
</bim-label>
|
|
56
|
+
`,
|
|
57
|
+
onConfirm: async () => {
|
|
58
|
+
// Simulate an async delete operation.
|
|
59
|
+
// The modal shows a loading spinner until this promise resolves.
|
|
60
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
|
61
|
+
this._lastAction = "Model deleted";
|
|
62
|
+
},
|
|
63
|
+
onCancel: () => {
|
|
64
|
+
this._lastAction = "Delete cancelled";
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _confirmOverwrite() {
|
|
72
|
+
this.dispatchEvent(
|
|
73
|
+
new CustomEvent("top:modal", {
|
|
74
|
+
bubbles: true,
|
|
75
|
+
composed: true,
|
|
76
|
+
detail: {
|
|
77
|
+
label: "File already exists",
|
|
78
|
+
icon: "tabler:alert-triangle",
|
|
79
|
+
confirmLabel: "Overwrite",
|
|
80
|
+
cancelLabel: "Cancel",
|
|
81
|
+
content: html`
|
|
82
|
+
<bim-label style="white-space:normal;">
|
|
83
|
+
A file named <strong>structure.ifc</strong> already exists in this
|
|
84
|
+
folder. Uploading will create a new version alongside it.
|
|
85
|
+
</bim-label>
|
|
86
|
+
`,
|
|
87
|
+
// onConfirm can be synchronous too — the modal closes immediately.
|
|
88
|
+
onConfirm: () => {
|
|
89
|
+
this._lastAction = "Overwrite confirmed";
|
|
90
|
+
},
|
|
91
|
+
onCancel: () => {
|
|
92
|
+
this._lastAction = "Overwrite cancelled";
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
render() {
|
|
100
|
+
return html`
|
|
101
|
+
<bim-panel label="Modal Actions">
|
|
102
|
+
<bim-panel-section label="Actions" icon="tabler:click">
|
|
103
|
+
<bim-button
|
|
104
|
+
label="Delete model (async confirm)"
|
|
105
|
+
icon="tabler:trash"
|
|
106
|
+
@click=${this._confirmDelete}
|
|
107
|
+
></bim-button>
|
|
108
|
+
<bim-button
|
|
109
|
+
label="Overwrite file (sync confirm)"
|
|
110
|
+
icon="tabler:file-upload"
|
|
111
|
+
@click=${this._confirmOverwrite}
|
|
112
|
+
></bim-button>
|
|
113
|
+
<bim-label>Last action: ${this._lastAction}</bim-label>
|
|
114
|
+
</bim-panel-section>
|
|
115
|
+
</bim-panel>
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---- app wiring ----
|
|
121
|
+
|
|
122
|
+
const client = PlatformClient.fromPlatformContext();
|
|
123
|
+
const { components } = (await client.setup(
|
|
124
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
125
|
+
{ uuid: UIManager.uuid },
|
|
126
|
+
)) as { components: OBC.Components };
|
|
127
|
+
components.get(UIManager).init();
|
|
128
|
+
|
|
129
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
130
|
+
|
|
131
|
+
app.setup = (waitUntil) => {
|
|
132
|
+
waitUntil(
|
|
133
|
+
(async () => {
|
|
134
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
135
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
136
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
137
|
+
classicWorker: true,
|
|
138
|
+
});
|
|
139
|
+
})(),
|
|
140
|
+
"Fragments Core",
|
|
141
|
+
);
|
|
142
|
+
return { components, client };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
app.elements = {
|
|
146
|
+
viewer: () =>
|
|
147
|
+
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
148
|
+
panel: () => html`<modal-actions></modal-actions>`,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
app.base = "viewer";
|
|
152
|
+
app.layouts = {
|
|
153
|
+
main: {
|
|
154
|
+
label: "Main",
|
|
155
|
+
icon: "solar:home-bold",
|
|
156
|
+
template: `"panel" 1fr / 22rem`,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
app.layout = "main";
|
|
160
|
+
|
|
161
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
162
|
+
container.appendChild(app);
|
|
163
|
+
document.body.style.margin = "0";
|
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## Toasts and cross-component notifications
|
|
3
|
-
---
|
|
4
|
-
Developers building BIM processing UIs need to surface status updates, errors,
|
|
5
|
-
and async feedback to users from different points in the component tree —
|
|
6
|
-
but reaching back to the app root to trigger a notification from a deeply
|
|
7
|
-
nested component requires threading a reference through every layer in between.
|
|
8
|
-
|
|
9
|
-
top-app provides a built-in toast system that any descendant can trigger by
|
|
10
|
-
dispatching a standard DOM event. Toasts are auto-dismissed after a type-based
|
|
11
|
-
duration and stack at the bottom-left of the screen.
|
|
12
|
-
|
|
13
|
-
This tutorial covers showing a toast on app boot via showToast; building a
|
|
14
|
-
component that dispatches top:notification to report async progress and
|
|
15
|
-
completion without a reference to the root; and using the four toast types
|
|
16
|
-
(success, error, warning, info) with their respective durations.
|
|
17
|
-
|
|
18
|
-
By the end, you'll have an app with a boot confirmation toast and a panel that
|
|
19
|
-
reports async work status without holding any reference to top-app.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { html, LitElement } from "lit";
|
|
23
|
-
import { customElement, state } from "lit/decorators.js";
|
|
24
|
-
import * as THREE from "three";
|
|
25
|
-
import * as OBC from "@thatopen/components";
|
|
26
|
-
import * as OBF from "@thatopen/components-front";
|
|
27
|
-
import * as FRAGS from "@thatopen/fragments";
|
|
28
|
-
import * as BUI from "@thatopen/ui";
|
|
29
|
-
import { PlatformClient, UIManager } from "@thatopen/services";
|
|
30
|
-
import type { App, AppReadyEvent } from "../../index";
|
|
31
|
-
|
|
32
|
-
// A child component that dispatches top:notification to surface messages
|
|
33
|
-
// to the nearest top-app ancestor — no reference to the app element needed.
|
|
34
|
-
@customElement("status-reporter")
|
|
35
|
-
class StatusReporter extends LitElement {
|
|
36
|
-
@state() private _busy = false;
|
|
37
|
-
|
|
38
|
-
private _simulateWork() {
|
|
39
|
-
this._busy = true;
|
|
40
|
-
|
|
41
|
-
// Dispatching top:notification from any descendant of top-app shows a toast.
|
|
42
|
-
// bubbles:true + composed:true lets it cross shadow-DOM boundaries.
|
|
43
|
-
this.dispatchEvent(
|
|
44
|
-
new CustomEvent("top:notification", {
|
|
45
|
-
bubbles: true,
|
|
46
|
-
composed: true,
|
|
47
|
-
detail: { message: "Analysis started", type: "info" },
|
|
48
|
-
}),
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
setTimeout(() => {
|
|
52
|
-
this._busy = false;
|
|
53
|
-
this.dispatchEvent(
|
|
54
|
-
new CustomEvent("top:notification", {
|
|
55
|
-
bubbles: true,
|
|
56
|
-
composed: true,
|
|
57
|
-
detail: {
|
|
58
|
-
message: "Analysis complete",
|
|
59
|
-
type: "success",
|
|
60
|
-
description: "Found 342 elements across 5 categories.",
|
|
61
|
-
},
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
64
|
-
}, 2000);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
render() {
|
|
68
|
-
return html`
|
|
69
|
-
<bim-panel label="Reporter">
|
|
70
|
-
<bim-panel-section label="Actions" icon="solar:play-bold">
|
|
71
|
-
<bim-button
|
|
72
|
-
label="Run Analysis"
|
|
73
|
-
icon="solar:play-bold"
|
|
74
|
-
.loading=${this._busy}
|
|
75
|
-
@click=${this._simulateWork}
|
|
76
|
-
></bim-button>
|
|
77
|
-
</bim-panel-section>
|
|
78
|
-
</bim-panel>
|
|
79
|
-
`;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---- app wiring ----
|
|
84
|
-
|
|
85
|
-
const client = PlatformClient.fromPlatformContext();
|
|
86
|
-
const { components } = (await client.setup(
|
|
87
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
88
|
-
{ uuid: UIManager.uuid },
|
|
89
|
-
)) as { components: OBC.Components };
|
|
90
|
-
components.get(UIManager).init();
|
|
91
|
-
|
|
92
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
93
|
-
|
|
94
|
-
app.setup = (waitUntil) => {
|
|
95
|
-
waitUntil(
|
|
96
|
-
(async () => {
|
|
97
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
98
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
99
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
100
|
-
classicWorker: true,
|
|
101
|
-
});
|
|
102
|
-
})(),
|
|
103
|
-
"Fragments Core",
|
|
104
|
-
);
|
|
105
|
-
return { components, client };
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
app.elements = {
|
|
109
|
-
viewer: () =>
|
|
110
|
-
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
111
|
-
panel: () => html`<status-reporter></status-reporter>`,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
app.base = "viewer";
|
|
115
|
-
app.layouts = {
|
|
116
|
-
main: {
|
|
117
|
-
label: "Main",
|
|
118
|
-
icon: "solar:home-bold",
|
|
119
|
-
template: `"panel" 1fr / 22rem`,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
app.layout = "main";
|
|
123
|
-
|
|
124
|
-
// top:app-ready fires after all waitUntil promises resolve.
|
|
125
|
-
// e.detail.app is a reference to the top-app element itself.
|
|
126
|
-
app.addEventListener("top:app-ready", (e) => {
|
|
127
|
-
const { app: readyApp } = (e as AppReadyEvent).detail;
|
|
128
|
-
readyApp.showToast("App ready", "success");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
132
|
-
container.appendChild(app);
|
|
133
|
-
document.body.style.margin = "0";
|
|
1
|
+
/* MD
|
|
2
|
+
## Toasts and cross-component notifications
|
|
3
|
+
---
|
|
4
|
+
Developers building BIM processing UIs need to surface status updates, errors,
|
|
5
|
+
and async feedback to users from different points in the component tree —
|
|
6
|
+
but reaching back to the app root to trigger a notification from a deeply
|
|
7
|
+
nested component requires threading a reference through every layer in between.
|
|
8
|
+
|
|
9
|
+
top-app provides a built-in toast system that any descendant can trigger by
|
|
10
|
+
dispatching a standard DOM event. Toasts are auto-dismissed after a type-based
|
|
11
|
+
duration and stack at the bottom-left of the screen.
|
|
12
|
+
|
|
13
|
+
This tutorial covers showing a toast on app boot via showToast; building a
|
|
14
|
+
component that dispatches top:notification to report async progress and
|
|
15
|
+
completion without a reference to the root; and using the four toast types
|
|
16
|
+
(success, error, warning, info) with their respective durations.
|
|
17
|
+
|
|
18
|
+
By the end, you'll have an app with a boot confirmation toast and a panel that
|
|
19
|
+
reports async work status without holding any reference to top-app.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { html, LitElement } from "lit";
|
|
23
|
+
import { customElement, state } from "lit/decorators.js";
|
|
24
|
+
import * as THREE from "three";
|
|
25
|
+
import * as OBC from "@thatopen/components";
|
|
26
|
+
import * as OBF from "@thatopen/components-front";
|
|
27
|
+
import * as FRAGS from "@thatopen/fragments";
|
|
28
|
+
import * as BUI from "@thatopen/ui";
|
|
29
|
+
import { PlatformClient, UIManager } from "@thatopen/services";
|
|
30
|
+
import type { App, AppReadyEvent } from "../../index";
|
|
31
|
+
|
|
32
|
+
// A child component that dispatches top:notification to surface messages
|
|
33
|
+
// to the nearest top-app ancestor — no reference to the app element needed.
|
|
34
|
+
@customElement("status-reporter")
|
|
35
|
+
class StatusReporter extends LitElement {
|
|
36
|
+
@state() private _busy = false;
|
|
37
|
+
|
|
38
|
+
private _simulateWork() {
|
|
39
|
+
this._busy = true;
|
|
40
|
+
|
|
41
|
+
// Dispatching top:notification from any descendant of top-app shows a toast.
|
|
42
|
+
// bubbles:true + composed:true lets it cross shadow-DOM boundaries.
|
|
43
|
+
this.dispatchEvent(
|
|
44
|
+
new CustomEvent("top:notification", {
|
|
45
|
+
bubbles: true,
|
|
46
|
+
composed: true,
|
|
47
|
+
detail: { message: "Analysis started", type: "info" },
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
this._busy = false;
|
|
53
|
+
this.dispatchEvent(
|
|
54
|
+
new CustomEvent("top:notification", {
|
|
55
|
+
bubbles: true,
|
|
56
|
+
composed: true,
|
|
57
|
+
detail: {
|
|
58
|
+
message: "Analysis complete",
|
|
59
|
+
type: "success",
|
|
60
|
+
description: "Found 342 elements across 5 categories.",
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
}, 2000);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render() {
|
|
68
|
+
return html`
|
|
69
|
+
<bim-panel label="Reporter">
|
|
70
|
+
<bim-panel-section label="Actions" icon="solar:play-bold">
|
|
71
|
+
<bim-button
|
|
72
|
+
label="Run Analysis"
|
|
73
|
+
icon="solar:play-bold"
|
|
74
|
+
.loading=${this._busy}
|
|
75
|
+
@click=${this._simulateWork}
|
|
76
|
+
></bim-button>
|
|
77
|
+
</bim-panel-section>
|
|
78
|
+
</bim-panel>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---- app wiring ----
|
|
84
|
+
|
|
85
|
+
const client = PlatformClient.fromPlatformContext();
|
|
86
|
+
const { components } = (await client.setup(
|
|
87
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
88
|
+
{ uuid: UIManager.uuid },
|
|
89
|
+
)) as { components: OBC.Components };
|
|
90
|
+
components.get(UIManager).init();
|
|
91
|
+
|
|
92
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
93
|
+
|
|
94
|
+
app.setup = (waitUntil) => {
|
|
95
|
+
waitUntil(
|
|
96
|
+
(async () => {
|
|
97
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
98
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
99
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
100
|
+
classicWorker: true,
|
|
101
|
+
});
|
|
102
|
+
})(),
|
|
103
|
+
"Fragments Core",
|
|
104
|
+
);
|
|
105
|
+
return { components, client };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
app.elements = {
|
|
109
|
+
viewer: () =>
|
|
110
|
+
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
111
|
+
panel: () => html`<status-reporter></status-reporter>`,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
app.base = "viewer";
|
|
115
|
+
app.layouts = {
|
|
116
|
+
main: {
|
|
117
|
+
label: "Main",
|
|
118
|
+
icon: "solar:home-bold",
|
|
119
|
+
template: `"panel" 1fr / 22rem`,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
app.layout = "main";
|
|
123
|
+
|
|
124
|
+
// top:app-ready fires after all waitUntil promises resolve.
|
|
125
|
+
// e.detail.app is a reference to the top-app element itself.
|
|
126
|
+
app.addEventListener("top:app-ready", (e) => {
|
|
127
|
+
const { app: readyApp } = (e as AppReadyEvent).detail;
|
|
128
|
+
readyApp.showToast("App ready", "success");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
132
|
+
container.appendChild(app);
|
|
133
|
+
document.body.style.margin = "0";
|