@thatopen/services 0.5.0 → 0.7.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 +569 -0
- package/dist/cli.js +12 -23
- package/dist/index.cjs.js +2 -2
- package/dist/index.es.js +41 -49
- package/dist/vite-env.d.ts +1 -1
- package/docs/ai-quickstart.md +37 -0
- package/docs/builtin/paths.json +4 -0
- 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/flow-architecture.md +389 -0
- package/docs/flow-plugin-guide.md +337 -0
- package/docs/platform-token.md +81 -0
- package/docs/revit-collab-quickstart.md +363 -173
- package/docs/rhino-interop-quickstart.md +391 -0
- package/package.json +1 -1
- 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/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 -150
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
- package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -0
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
- package/src/built-in/index.ts +570 -0
- 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,90 +1,90 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## Floating layout
|
|
3
|
-
---
|
|
4
|
-
Developers building BIM coordination tools often want the 3D model to fill the
|
|
5
|
-
entire window while control panels float above it — but standard grid layouts
|
|
6
|
-
give every area equal standing, shrinking the viewer into one cell and leaving
|
|
7
|
-
visible seams between it and the panels.
|
|
8
|
-
|
|
9
|
-
top-app supports a floating mode where one element is mounted as an absolute
|
|
10
|
-
background that fills the container edge-to-edge, and all other layout areas
|
|
11
|
-
render as a transparent overlay on top of it with independent pointer events.
|
|
12
|
-
|
|
13
|
-
This tutorial covers assigning the viewer as the absolute background of the app;
|
|
14
|
-
defining an overlay layout where a side panel floats above the viewer without
|
|
15
|
-
sharing grid space; and annotating why the base element is excluded from the
|
|
16
|
-
layout template.
|
|
17
|
-
|
|
18
|
-
By the end, you'll have a full-screen 3D viewer with a floating side panel
|
|
19
|
-
hovering above it, with the model filling the entire window and the panel
|
|
20
|
-
receiving its own pointer events.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { html } from "lit";
|
|
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 } from "../../index";
|
|
31
|
-
|
|
32
|
-
const client = PlatformClient.fromPlatformContext();
|
|
33
|
-
const { components } = (await client.setup(
|
|
34
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
35
|
-
{ uuid: UIManager.uuid },
|
|
36
|
-
)) as { components: OBC.Components };
|
|
37
|
-
components.get(UIManager).init();
|
|
38
|
-
|
|
39
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
40
|
-
|
|
41
|
-
app.setup = (waitUntil) => {
|
|
42
|
-
waitUntil(
|
|
43
|
-
(async () => {
|
|
44
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
45
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
46
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
47
|
-
classicWorker: true,
|
|
48
|
-
});
|
|
49
|
-
})(),
|
|
50
|
-
"Fragments Core",
|
|
51
|
-
);
|
|
52
|
-
return { components, client };
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
app.elements = {
|
|
56
|
-
// "viewer" is referenced by app.base — rendered as a position:absolute layer
|
|
57
|
-
// behind all layout content so it fills the container edge-to-edge.
|
|
58
|
-
viewer: () =>
|
|
59
|
-
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
60
|
-
|
|
61
|
-
// Panels declared here appear in the floating content grid on top of the viewer.
|
|
62
|
-
// Their container has pointer-events: none; each child re-enables them individually.
|
|
63
|
-
panel: () => html`
|
|
64
|
-
<bim-panel>
|
|
65
|
-
<bim-panel-section label="Models" icon="solar:layers-bold">
|
|
66
|
-
<bim-label>Load models here</bim-label>
|
|
67
|
-
</bim-panel-section>
|
|
68
|
-
</bim-panel>
|
|
69
|
-
`,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Setting app.base to an element key mounts that element as the absolute background.
|
|
73
|
-
// Layout templates then only list the floating overlay areas — not the base element.
|
|
74
|
-
// Without app.base, every area in the template is a peer grid cell (inline mode).
|
|
75
|
-
app.base = "viewer";
|
|
76
|
-
|
|
77
|
-
app.layouts = {
|
|
78
|
-
main: {
|
|
79
|
-
label: "Main",
|
|
80
|
-
icon: "solar:home-bold",
|
|
81
|
-
// Only overlay areas appear here. "viewer" is not listed because it is the base.
|
|
82
|
-
template: `"panel" 1fr / 22rem`,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
app.layout = "main";
|
|
87
|
-
|
|
88
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
89
|
-
container.appendChild(app);
|
|
90
|
-
document.body.style.margin = "0";
|
|
1
|
+
/* MD
|
|
2
|
+
## Floating layout
|
|
3
|
+
---
|
|
4
|
+
Developers building BIM coordination tools often want the 3D model to fill the
|
|
5
|
+
entire window while control panels float above it — but standard grid layouts
|
|
6
|
+
give every area equal standing, shrinking the viewer into one cell and leaving
|
|
7
|
+
visible seams between it and the panels.
|
|
8
|
+
|
|
9
|
+
top-app supports a floating mode where one element is mounted as an absolute
|
|
10
|
+
background that fills the container edge-to-edge, and all other layout areas
|
|
11
|
+
render as a transparent overlay on top of it with independent pointer events.
|
|
12
|
+
|
|
13
|
+
This tutorial covers assigning the viewer as the absolute background of the app;
|
|
14
|
+
defining an overlay layout where a side panel floats above the viewer without
|
|
15
|
+
sharing grid space; and annotating why the base element is excluded from the
|
|
16
|
+
layout template.
|
|
17
|
+
|
|
18
|
+
By the end, you'll have a full-screen 3D viewer with a floating side panel
|
|
19
|
+
hovering above it, with the model filling the entire window and the panel
|
|
20
|
+
receiving its own pointer events.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { html } from "lit";
|
|
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 } from "../../index";
|
|
31
|
+
|
|
32
|
+
const client = PlatformClient.fromPlatformContext();
|
|
33
|
+
const { components } = (await client.setup(
|
|
34
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
35
|
+
{ uuid: UIManager.uuid },
|
|
36
|
+
)) as { components: OBC.Components };
|
|
37
|
+
components.get(UIManager).init();
|
|
38
|
+
|
|
39
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
40
|
+
|
|
41
|
+
app.setup = (waitUntil) => {
|
|
42
|
+
waitUntil(
|
|
43
|
+
(async () => {
|
|
44
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
45
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
46
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
47
|
+
classicWorker: true,
|
|
48
|
+
});
|
|
49
|
+
})(),
|
|
50
|
+
"Fragments Core",
|
|
51
|
+
);
|
|
52
|
+
return { components, client };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
app.elements = {
|
|
56
|
+
// "viewer" is referenced by app.base — rendered as a position:absolute layer
|
|
57
|
+
// behind all layout content so it fills the container edge-to-edge.
|
|
58
|
+
viewer: () =>
|
|
59
|
+
html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
60
|
+
|
|
61
|
+
// Panels declared here appear in the floating content grid on top of the viewer.
|
|
62
|
+
// Their container has pointer-events: none; each child re-enables them individually.
|
|
63
|
+
panel: () => html`
|
|
64
|
+
<bim-panel>
|
|
65
|
+
<bim-panel-section label="Models" icon="solar:layers-bold">
|
|
66
|
+
<bim-label>Load models here</bim-label>
|
|
67
|
+
</bim-panel-section>
|
|
68
|
+
</bim-panel>
|
|
69
|
+
`,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Setting app.base to an element key mounts that element as the absolute background.
|
|
73
|
+
// Layout templates then only list the floating overlay areas — not the base element.
|
|
74
|
+
// Without app.base, every area in the template is a peer grid cell (inline mode).
|
|
75
|
+
app.base = "viewer";
|
|
76
|
+
|
|
77
|
+
app.layouts = {
|
|
78
|
+
main: {
|
|
79
|
+
label: "Main",
|
|
80
|
+
icon: "solar:home-bold",
|
|
81
|
+
// Only overlay areas appear here. "viewer" is not listed because it is the base.
|
|
82
|
+
template: `"panel" 1fr / 22rem`,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
app.layout = "main";
|
|
87
|
+
|
|
88
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
89
|
+
container.appendChild(app);
|
|
90
|
+
document.body.style.margin = "0";
|
|
@@ -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";
|