@thatopen/services 0.3.1 → 0.3.2
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 +3 -0
- package/dist/built-in/index.d.ts +22 -2
- package/dist/cli.js +3 -3
- package/docs/ai-quickstart.md +86 -0
- package/docs/builtin/paths.json +74 -74
- package/docs/cli/create.md +4 -3
- package/package.json +1 -1
- package/src/built-in/index.ts +22 -2
- 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/main.ts +223 -340
- package/src/cli/templates/app/src/setups/camera-tools.ts +63 -63
- package/src/cli/templates/app/src/setups/clipper-tool.ts +20 -1
- package/src/cli/templates/app/src/setups/clipper.ts +75 -75
- package/src/cli/templates/app/src/setups/index.ts +23 -33
- package/src/cli/templates/app/src/setups/inspection.ts +136 -136
- package/src/cli/templates/app/src/setups/measurement-tool.ts +19 -1
- package/src/cli/templates/app/src/setups/measurements.ts +133 -133
- 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/setups/right-sidebar.ts +178 -178
- package/src/cli/templates/app/src/setups/styles-panel.ts +110 -110
- package/src/cli/templates/app/src/setups/tool-mode-manager.ts +15 -9
- package/src/cli/templates/app/src/setups/tool-mode.ts +23 -23
- package/src/cli/templates/app/src/setups/active-tool-hud.ts +0 -69
- package/src/cli/templates/app/src/setups/clipper-panel.ts +0 -167
- package/src/cli/templates/app/src/setups/commands-panel.ts +0 -239
- package/src/cli/templates/app/src/setups/data-table-panel.ts +0 -2224
- package/src/cli/templates/app/src/setups/file-format-icons.ts +0 -95
- package/src/cli/templates/app/src/setups/files-panel.ts +0 -1503
- package/src/cli/templates/app/src/setups/fps-indicator.ts +0 -63
- package/src/cli/templates/app/src/setups/graphics-panel.ts +0 -490
- package/src/cli/templates/app/src/setups/measurement-settings-panel.ts +0 -161
- package/src/cli/templates/app/src/setups/model-tree.ts +0 -797
- package/src/cli/templates/app/src/setups/objects-panel.ts +0 -127
- package/src/cli/templates/app/src/setups/properties-panel.ts +0 -1048
- package/src/cli/templates/app/src/setups/settings-panel.ts +0 -94
- package/src/cli/templates/app/src/setups/visibility-toolbar.ts +0 -498
- package/src/cli/templates/app/src/setups/walkthrough.ts +0 -528
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
import * as BUI from "@thatopen/ui";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Mounts several panels as SEPARATE cards stacked vertically on the RIGHT,
|
|
5
|
-
* together spanning the full viewport height. One right-docked `bim-grid
|
|
6
|
-
* floating` holds one row per region (heights split by `grow`); the floating
|
|
7
|
-
* grid's built-in 1rem gap shows between the cards, and its empty "rest" column
|
|
8
|
-
* clicks through to the model. Each region is its own `bim-panel` (own
|
|
9
|
-
* header/border), so they read as distinct cards — not one card with a divider.
|
|
10
|
-
*
|
|
11
|
-
* Each panel element should be `height: 100%` so it fills its row and scrolls
|
|
12
|
-
* internally when its content is tall.
|
|
13
|
-
*
|
|
14
|
-
* @param container the viewport element to overlay
|
|
15
|
-
* @param regions the panels to stack, top to bottom
|
|
16
|
-
* @returns the floating grid element (already appended to the container)
|
|
17
|
-
*/
|
|
18
|
-
export interface StackRegion {
|
|
19
|
-
/** The panel element for this region. */
|
|
20
|
-
element: HTMLElement;
|
|
21
|
-
/** Relative height share (CSS grid `fr`). Defaults to 1 (equal split). */
|
|
22
|
-
grow?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const rightStack = (container: HTMLElement, regions: StackRegion[]) => {
|
|
26
|
-
// ── Collapse toggle (mirror of the left files panel's) ─────────
|
|
27
|
-
// A bare chevron in its own thin grid column in the gutter, just OUTSIDE the
|
|
28
|
-
// cards (to their left). Collapsing swaps the grid layout to drop the card
|
|
29
|
-
// columns entirely; the chevron column stays. When collapsed the chevron sits
|
|
30
|
-
// inset from the right edge (the grid's 1rem padding), so it stays in view.
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
-
let gridEl: any = null;
|
|
33
|
-
let collapsed = false;
|
|
34
|
-
|
|
35
|
-
const [toggle, toggleUpdate] = BUI.Component.create<
|
|
36
|
-
HTMLElement,
|
|
37
|
-
{ collapsed: boolean }
|
|
38
|
-
>(
|
|
39
|
-
(s) => BUI.html`
|
|
40
|
-
<div
|
|
41
|
-
style="
|
|
42
|
-
height: 100%; display: flex; align-items: flex-start;
|
|
43
|
-
pointer-events: none; padding-top: 0.5rem; padding-right: 0.1rem;
|
|
44
|
-
"
|
|
45
|
-
>
|
|
46
|
-
<span
|
|
47
|
-
role="button"
|
|
48
|
-
title=${s.collapsed ? "Show panel" : "Hide panel"}
|
|
49
|
-
@click=${() => setCollapsed(!s.collapsed)}
|
|
50
|
-
style="
|
|
51
|
-
pointer-events: auto; cursor: pointer; display: inline-flex;
|
|
52
|
-
color: var(--bim-ui_bg-contrast-80, #c9c9c9);
|
|
53
|
-
"
|
|
54
|
-
><bim-icon
|
|
55
|
-
icon=${s.collapsed ? "mdi:chevron-left" : "mdi:chevron-right"}
|
|
56
|
-
style="font-size: 1.2rem;"
|
|
57
|
-
></bim-icon></span>
|
|
58
|
-
</div>
|
|
59
|
-
`,
|
|
60
|
-
{ collapsed: false },
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const setCollapsed = (v: boolean) => {
|
|
64
|
-
collapsed = v;
|
|
65
|
-
if (gridEl) gridEl.layout = v ? "collapsed" : "main";
|
|
66
|
-
toggleUpdate({ collapsed: v });
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// ── Floating grid: cards docked full-height right, chevron in the gutter ──
|
|
70
|
-
const grid = BUI.Component.create(() => {
|
|
71
|
-
const onCreated = (element?: Element) => {
|
|
72
|
-
if (!element) return;
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
-
const g = element as any;
|
|
75
|
-
gridEl = g;
|
|
76
|
-
const names = regions.map((_, i) => `r${i}`);
|
|
77
|
-
g.elements = {
|
|
78
|
-
toggle,
|
|
79
|
-
...Object.fromEntries(regions.map((r, i) => [names[i], r.element])),
|
|
80
|
-
};
|
|
81
|
-
// One row per region; columns: rest (1fr, click-through) | toggle | cards.
|
|
82
|
-
// The "toggle" area repeats across rows → one full-height gutter column.
|
|
83
|
-
const rows = regions
|
|
84
|
-
.map((r, i) => `"rest toggle ${names[i]}" ${r.grow ?? 1}fr`)
|
|
85
|
-
.join("\n");
|
|
86
|
-
g.layouts = {
|
|
87
|
-
main: { template: `${rows}\n/ 1fr auto auto` },
|
|
88
|
-
// Collapsed: drop the card columns; keep rest + toggle (toggle ends up
|
|
89
|
-
// near the right edge, inset by the grid's 1rem padding).
|
|
90
|
-
collapsed: { template: `"rest toggle" 1fr\n/ 1fr auto` },
|
|
91
|
-
};
|
|
92
|
-
g.layout = collapsed ? "collapsed" : "main";
|
|
93
|
-
};
|
|
94
|
-
return BUI.html`
|
|
95
|
-
<bim-grid style="padding: 1rem;" ${BUI.ref(onCreated)} floating></bim-grid>
|
|
96
|
-
`;
|
|
97
|
-
});
|
|
98
|
-
container.append(grid);
|
|
99
|
-
|
|
100
|
-
return { grid };
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Mirror of {@link rightStack} for the LEFT side: cards stacked vertically on
|
|
105
|
-
* the left (full height), same 1rem gap + outer margins, with a collapse chevron
|
|
106
|
-
* in the gutter to the RIGHT of the cards (facing the viewport). Used for the
|
|
107
|
-
* Files (top) + helper (bottom) stack.
|
|
108
|
-
*/
|
|
109
|
-
export const leftStack = (container: HTMLElement, regions: StackRegion[]) => {
|
|
110
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
-
let gridEl: any = null;
|
|
112
|
-
let collapsed = false;
|
|
113
|
-
|
|
114
|
-
const [toggle, toggleUpdate] = BUI.Component.create<
|
|
115
|
-
HTMLElement,
|
|
116
|
-
{ collapsed: boolean }
|
|
117
|
-
>(
|
|
118
|
-
(s) => BUI.html`
|
|
119
|
-
<div
|
|
120
|
-
style="
|
|
121
|
-
height: 100%; display: flex; align-items: flex-start;
|
|
122
|
-
pointer-events: none; padding-top: 0.5rem; padding-left: 0.1rem;
|
|
123
|
-
"
|
|
124
|
-
>
|
|
125
|
-
<span
|
|
126
|
-
role="button"
|
|
127
|
-
title=${s.collapsed ? "Show panel" : "Hide panel"}
|
|
128
|
-
@click=${() => setCollapsed(!s.collapsed)}
|
|
129
|
-
style="
|
|
130
|
-
pointer-events: auto; cursor: pointer; display: inline-flex;
|
|
131
|
-
color: var(--bim-ui_bg-contrast-80, #c9c9c9);
|
|
132
|
-
"
|
|
133
|
-
><bim-icon
|
|
134
|
-
icon=${s.collapsed ? "mdi:chevron-right" : "mdi:chevron-left"}
|
|
135
|
-
style="font-size: 1.2rem;"
|
|
136
|
-
></bim-icon></span>
|
|
137
|
-
</div>
|
|
138
|
-
`,
|
|
139
|
-
{ collapsed: false },
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const setCollapsed = (v: boolean) => {
|
|
143
|
-
collapsed = v;
|
|
144
|
-
if (gridEl) gridEl.layout = v ? "collapsed" : "main";
|
|
145
|
-
toggleUpdate({ collapsed: v });
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const grid = BUI.Component.create(() => {
|
|
149
|
-
const onCreated = (element?: Element) => {
|
|
150
|
-
if (!element) return;
|
|
151
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
152
|
-
const g = element as any;
|
|
153
|
-
gridEl = g;
|
|
154
|
-
const names = regions.map((_, i) => `r${i}`);
|
|
155
|
-
g.elements = {
|
|
156
|
-
toggle,
|
|
157
|
-
...Object.fromEntries(regions.map((r, i) => [names[i], r.element])),
|
|
158
|
-
};
|
|
159
|
-
// Columns: cards | toggle (gutter, facing viewport) | rest (click-through).
|
|
160
|
-
const rows = regions
|
|
161
|
-
.map((r, i) => `"${names[i]} toggle rest" ${r.grow ?? 1}fr`)
|
|
162
|
-
.join("\n");
|
|
163
|
-
g.layouts = {
|
|
164
|
-
main: { template: `${rows}\n/ auto auto 1fr` },
|
|
165
|
-
// Collapsed: drop the card columns; toggle stays at the left edge (inset
|
|
166
|
-
// by the grid's 1rem padding).
|
|
167
|
-
collapsed: { template: `"toggle rest" 1fr\n/ auto 1fr` },
|
|
168
|
-
};
|
|
169
|
-
g.layout = collapsed ? "collapsed" : "main";
|
|
170
|
-
};
|
|
171
|
-
return BUI.html`
|
|
172
|
-
<bim-grid style="padding: 1rem;" ${BUI.ref(onCreated)} floating></bim-grid>
|
|
173
|
-
`;
|
|
174
|
-
});
|
|
175
|
-
container.append(grid);
|
|
176
|
-
|
|
177
|
-
return { grid };
|
|
178
|
-
};
|
|
1
|
+
import * as BUI from "@thatopen/ui";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mounts several panels as SEPARATE cards stacked vertically on the RIGHT,
|
|
5
|
+
* together spanning the full viewport height. One right-docked `bim-grid
|
|
6
|
+
* floating` holds one row per region (heights split by `grow`); the floating
|
|
7
|
+
* grid's built-in 1rem gap shows between the cards, and its empty "rest" column
|
|
8
|
+
* clicks through to the model. Each region is its own `bim-panel` (own
|
|
9
|
+
* header/border), so they read as distinct cards — not one card with a divider.
|
|
10
|
+
*
|
|
11
|
+
* Each panel element should be `height: 100%` so it fills its row and scrolls
|
|
12
|
+
* internally when its content is tall.
|
|
13
|
+
*
|
|
14
|
+
* @param container the viewport element to overlay
|
|
15
|
+
* @param regions the panels to stack, top to bottom
|
|
16
|
+
* @returns the floating grid element (already appended to the container)
|
|
17
|
+
*/
|
|
18
|
+
export interface StackRegion {
|
|
19
|
+
/** The panel element for this region. */
|
|
20
|
+
element: HTMLElement;
|
|
21
|
+
/** Relative height share (CSS grid `fr`). Defaults to 1 (equal split). */
|
|
22
|
+
grow?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const rightStack = (container: HTMLElement, regions: StackRegion[]) => {
|
|
26
|
+
// ── Collapse toggle (mirror of the left files panel's) ─────────
|
|
27
|
+
// A bare chevron in its own thin grid column in the gutter, just OUTSIDE the
|
|
28
|
+
// cards (to their left). Collapsing swaps the grid layout to drop the card
|
|
29
|
+
// columns entirely; the chevron column stays. When collapsed the chevron sits
|
|
30
|
+
// inset from the right edge (the grid's 1rem padding), so it stays in view.
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
let gridEl: any = null;
|
|
33
|
+
let collapsed = false;
|
|
34
|
+
|
|
35
|
+
const [toggle, toggleUpdate] = BUI.Component.create<
|
|
36
|
+
HTMLElement,
|
|
37
|
+
{ collapsed: boolean }
|
|
38
|
+
>(
|
|
39
|
+
(s) => BUI.html`
|
|
40
|
+
<div
|
|
41
|
+
style="
|
|
42
|
+
height: 100%; display: flex; align-items: flex-start;
|
|
43
|
+
pointer-events: none; padding-top: 0.5rem; padding-right: 0.1rem;
|
|
44
|
+
"
|
|
45
|
+
>
|
|
46
|
+
<span
|
|
47
|
+
role="button"
|
|
48
|
+
title=${s.collapsed ? "Show panel" : "Hide panel"}
|
|
49
|
+
@click=${() => setCollapsed(!s.collapsed)}
|
|
50
|
+
style="
|
|
51
|
+
pointer-events: auto; cursor: pointer; display: inline-flex;
|
|
52
|
+
color: var(--bim-ui_bg-contrast-80, #c9c9c9);
|
|
53
|
+
"
|
|
54
|
+
><bim-icon
|
|
55
|
+
icon=${s.collapsed ? "mdi:chevron-left" : "mdi:chevron-right"}
|
|
56
|
+
style="font-size: 1.2rem;"
|
|
57
|
+
></bim-icon></span>
|
|
58
|
+
</div>
|
|
59
|
+
`,
|
|
60
|
+
{ collapsed: false },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const setCollapsed = (v: boolean) => {
|
|
64
|
+
collapsed = v;
|
|
65
|
+
if (gridEl) gridEl.layout = v ? "collapsed" : "main";
|
|
66
|
+
toggleUpdate({ collapsed: v });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ── Floating grid: cards docked full-height right, chevron in the gutter ──
|
|
70
|
+
const grid = BUI.Component.create(() => {
|
|
71
|
+
const onCreated = (element?: Element) => {
|
|
72
|
+
if (!element) return;
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
const g = element as any;
|
|
75
|
+
gridEl = g;
|
|
76
|
+
const names = regions.map((_, i) => `r${i}`);
|
|
77
|
+
g.elements = {
|
|
78
|
+
toggle,
|
|
79
|
+
...Object.fromEntries(regions.map((r, i) => [names[i], r.element])),
|
|
80
|
+
};
|
|
81
|
+
// One row per region; columns: rest (1fr, click-through) | toggle | cards.
|
|
82
|
+
// The "toggle" area repeats across rows → one full-height gutter column.
|
|
83
|
+
const rows = regions
|
|
84
|
+
.map((r, i) => `"rest toggle ${names[i]}" ${r.grow ?? 1}fr`)
|
|
85
|
+
.join("\n");
|
|
86
|
+
g.layouts = {
|
|
87
|
+
main: { template: `${rows}\n/ 1fr auto auto` },
|
|
88
|
+
// Collapsed: drop the card columns; keep rest + toggle (toggle ends up
|
|
89
|
+
// near the right edge, inset by the grid's 1rem padding).
|
|
90
|
+
collapsed: { template: `"rest toggle" 1fr\n/ 1fr auto` },
|
|
91
|
+
};
|
|
92
|
+
g.layout = collapsed ? "collapsed" : "main";
|
|
93
|
+
};
|
|
94
|
+
return BUI.html`
|
|
95
|
+
<bim-grid style="padding: 1rem;" ${BUI.ref(onCreated)} floating></bim-grid>
|
|
96
|
+
`;
|
|
97
|
+
});
|
|
98
|
+
container.append(grid);
|
|
99
|
+
|
|
100
|
+
return { grid };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Mirror of {@link rightStack} for the LEFT side: cards stacked vertically on
|
|
105
|
+
* the left (full height), same 1rem gap + outer margins, with a collapse chevron
|
|
106
|
+
* in the gutter to the RIGHT of the cards (facing the viewport). Used for the
|
|
107
|
+
* Files (top) + helper (bottom) stack.
|
|
108
|
+
*/
|
|
109
|
+
export const leftStack = (container: HTMLElement, regions: StackRegion[]) => {
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
let gridEl: any = null;
|
|
112
|
+
let collapsed = false;
|
|
113
|
+
|
|
114
|
+
const [toggle, toggleUpdate] = BUI.Component.create<
|
|
115
|
+
HTMLElement,
|
|
116
|
+
{ collapsed: boolean }
|
|
117
|
+
>(
|
|
118
|
+
(s) => BUI.html`
|
|
119
|
+
<div
|
|
120
|
+
style="
|
|
121
|
+
height: 100%; display: flex; align-items: flex-start;
|
|
122
|
+
pointer-events: none; padding-top: 0.5rem; padding-left: 0.1rem;
|
|
123
|
+
"
|
|
124
|
+
>
|
|
125
|
+
<span
|
|
126
|
+
role="button"
|
|
127
|
+
title=${s.collapsed ? "Show panel" : "Hide panel"}
|
|
128
|
+
@click=${() => setCollapsed(!s.collapsed)}
|
|
129
|
+
style="
|
|
130
|
+
pointer-events: auto; cursor: pointer; display: inline-flex;
|
|
131
|
+
color: var(--bim-ui_bg-contrast-80, #c9c9c9);
|
|
132
|
+
"
|
|
133
|
+
><bim-icon
|
|
134
|
+
icon=${s.collapsed ? "mdi:chevron-right" : "mdi:chevron-left"}
|
|
135
|
+
style="font-size: 1.2rem;"
|
|
136
|
+
></bim-icon></span>
|
|
137
|
+
</div>
|
|
138
|
+
`,
|
|
139
|
+
{ collapsed: false },
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const setCollapsed = (v: boolean) => {
|
|
143
|
+
collapsed = v;
|
|
144
|
+
if (gridEl) gridEl.layout = v ? "collapsed" : "main";
|
|
145
|
+
toggleUpdate({ collapsed: v });
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const grid = BUI.Component.create(() => {
|
|
149
|
+
const onCreated = (element?: Element) => {
|
|
150
|
+
if (!element) return;
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
152
|
+
const g = element as any;
|
|
153
|
+
gridEl = g;
|
|
154
|
+
const names = regions.map((_, i) => `r${i}`);
|
|
155
|
+
g.elements = {
|
|
156
|
+
toggle,
|
|
157
|
+
...Object.fromEntries(regions.map((r, i) => [names[i], r.element])),
|
|
158
|
+
};
|
|
159
|
+
// Columns: cards | toggle (gutter, facing viewport) | rest (click-through).
|
|
160
|
+
const rows = regions
|
|
161
|
+
.map((r, i) => `"${names[i]} toggle rest" ${r.grow ?? 1}fr`)
|
|
162
|
+
.join("\n");
|
|
163
|
+
g.layouts = {
|
|
164
|
+
main: { template: `${rows}\n/ auto auto 1fr` },
|
|
165
|
+
// Collapsed: drop the card columns; toggle stays at the left edge (inset
|
|
166
|
+
// by the grid's 1rem padding).
|
|
167
|
+
collapsed: { template: `"toggle rest" 1fr\n/ auto 1fr` },
|
|
168
|
+
};
|
|
169
|
+
g.layout = collapsed ? "collapsed" : "main";
|
|
170
|
+
};
|
|
171
|
+
return BUI.html`
|
|
172
|
+
<bim-grid style="padding: 1rem;" ${BUI.ref(onCreated)} floating></bim-grid>
|
|
173
|
+
`;
|
|
174
|
+
});
|
|
175
|
+
container.append(grid);
|
|
176
|
+
|
|
177
|
+
return { grid };
|
|
178
|
+
};
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
import * as OBC from "@thatopen/components";
|
|
2
|
-
import * as BUI from "@thatopen/ui";
|
|
3
|
-
import { styles, StyleSetting } from "./styles";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* UI side of the Styles tool. Consumes Worker 1's style DESCRIPTOR
|
|
7
|
-
* (`styles(components, world).settings`) and renders it generically in the
|
|
8
|
-
* bottom-left helper panel: checkbox (bool), slider (number), color picker
|
|
9
|
-
* (color), dropdown (enum) — each wired live to the setting's get/set. Settings
|
|
10
|
-
* are grouped by their `group` field.
|
|
11
|
-
*
|
|
12
|
-
* Returned as a "panel tool" `{ label, icon, render }` for the toolbar; `render`
|
|
13
|
-
* receives a `refresh` callback to re-read all controls after a change (needed
|
|
14
|
-
* because e.g. the preset enum rewrites several other settings).
|
|
15
|
-
*/
|
|
16
|
-
const control = (s: StyleSetting, refresh: () => void) => {
|
|
17
|
-
switch (s.type) {
|
|
18
|
-
case "bool":
|
|
19
|
-
return BUI.html`<bim-checkbox
|
|
20
|
-
label=${s.label}
|
|
21
|
-
?checked=${s.get()}
|
|
22
|
-
@change=${(e: Event) => {
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
-
s.set(!!(e.target as any).value);
|
|
25
|
-
refresh();
|
|
26
|
-
}}
|
|
27
|
-
></bim-checkbox>`;
|
|
28
|
-
case "number":
|
|
29
|
-
return BUI.html`<bim-number-input
|
|
30
|
-
label=${s.label}
|
|
31
|
-
slider
|
|
32
|
-
value=${s.get()}
|
|
33
|
-
min=${s.min}
|
|
34
|
-
max=${s.max}
|
|
35
|
-
step=${s.step}
|
|
36
|
-
@change=${(e: Event) => {
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
s.set(Number((e.target as any).value));
|
|
39
|
-
refresh();
|
|
40
|
-
}}
|
|
41
|
-
></bim-number-input>`;
|
|
42
|
-
case "color":
|
|
43
|
-
return BUI.html`<bim-color-input
|
|
44
|
-
label=${s.label}
|
|
45
|
-
color=${s.get()}
|
|
46
|
-
@input=${(e: Event) => {
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
-
const t = e.target as any;
|
|
49
|
-
s.set(String(t.color ?? t.value));
|
|
50
|
-
refresh();
|
|
51
|
-
}}
|
|
52
|
-
></bim-color-input>`;
|
|
53
|
-
case "enum":
|
|
54
|
-
return BUI.html`<bim-dropdown
|
|
55
|
-
label=${s.label}
|
|
56
|
-
@change=${(e: Event) => {
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
-
const picked = (e.target as any).value?.[0];
|
|
59
|
-
const match = s.options.find((o) => o.label === picked);
|
|
60
|
-
if (match) {
|
|
61
|
-
s.set(match.value);
|
|
62
|
-
refresh();
|
|
63
|
-
}
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
${s.options.map(
|
|
67
|
-
(o) => BUI.html`<bim-option label=${o.label} ?checked=${o.value === s.get()}></bim-option>`,
|
|
68
|
-
)}
|
|
69
|
-
</bim-dropdown>`;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const stylesTool = (components: OBC.Components, world: OBC.World) => ({
|
|
74
|
-
label: "Styles",
|
|
75
|
-
icon: "mdi:palette",
|
|
76
|
-
render: (refresh: () => void) => {
|
|
77
|
-
const { settings } = styles(components, world);
|
|
78
|
-
// Group settings by their `group` field, preserving first-seen order.
|
|
79
|
-
const groups: { name: string; items: StyleSetting[] }[] = [];
|
|
80
|
-
for (const s of settings) {
|
|
81
|
-
let g = groups.find((x) => x.name === s.group);
|
|
82
|
-
if (!g) {
|
|
83
|
-
g = { name: s.group, items: [] };
|
|
84
|
-
groups.push(g);
|
|
85
|
-
}
|
|
86
|
-
g.items.push(s);
|
|
87
|
-
}
|
|
88
|
-
return BUI.html`
|
|
89
|
-
<div style="display: flex; flex-direction: column; gap: 0.6rem;">
|
|
90
|
-
${groups.map(
|
|
91
|
-
(g) => BUI.html`
|
|
92
|
-
<div>
|
|
93
|
-
<div style="
|
|
94
|
-
font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em;
|
|
95
|
-
text-transform: uppercase; opacity: 0.6;
|
|
96
|
-
color: var(--bim-ui_bg-contrast-100, #e3e3e3);
|
|
97
|
-
padding: 0.1rem 0 0.3rem; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1));
|
|
98
|
-
">${g.name}</div>
|
|
99
|
-
<div style="display: flex; flex-direction: column; padding-top: 0.25rem;">
|
|
100
|
-
${g.items.map(
|
|
101
|
-
(s) => BUI.html`<div style="padding: 0.3rem 0; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255, 255, 255, 0.1));">${control(s, refresh)}</div>`,
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
`,
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
`;
|
|
109
|
-
},
|
|
110
|
-
});
|
|
1
|
+
import * as OBC from "@thatopen/components";
|
|
2
|
+
import * as BUI from "@thatopen/ui";
|
|
3
|
+
import { styles, StyleSetting } from "./styles";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* UI side of the Styles tool. Consumes Worker 1's style DESCRIPTOR
|
|
7
|
+
* (`styles(components, world).settings`) and renders it generically in the
|
|
8
|
+
* bottom-left helper panel: checkbox (bool), slider (number), color picker
|
|
9
|
+
* (color), dropdown (enum) — each wired live to the setting's get/set. Settings
|
|
10
|
+
* are grouped by their `group` field.
|
|
11
|
+
*
|
|
12
|
+
* Returned as a "panel tool" `{ label, icon, render }` for the toolbar; `render`
|
|
13
|
+
* receives a `refresh` callback to re-read all controls after a change (needed
|
|
14
|
+
* because e.g. the preset enum rewrites several other settings).
|
|
15
|
+
*/
|
|
16
|
+
const control = (s: StyleSetting, refresh: () => void) => {
|
|
17
|
+
switch (s.type) {
|
|
18
|
+
case "bool":
|
|
19
|
+
return BUI.html`<bim-checkbox
|
|
20
|
+
label=${s.label}
|
|
21
|
+
?checked=${s.get()}
|
|
22
|
+
@change=${(e: Event) => {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
s.set(!!(e.target as any).value);
|
|
25
|
+
refresh();
|
|
26
|
+
}}
|
|
27
|
+
></bim-checkbox>`;
|
|
28
|
+
case "number":
|
|
29
|
+
return BUI.html`<bim-number-input
|
|
30
|
+
label=${s.label}
|
|
31
|
+
slider
|
|
32
|
+
value=${s.get()}
|
|
33
|
+
min=${s.min}
|
|
34
|
+
max=${s.max}
|
|
35
|
+
step=${s.step}
|
|
36
|
+
@change=${(e: Event) => {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
s.set(Number((e.target as any).value));
|
|
39
|
+
refresh();
|
|
40
|
+
}}
|
|
41
|
+
></bim-number-input>`;
|
|
42
|
+
case "color":
|
|
43
|
+
return BUI.html`<bim-color-input
|
|
44
|
+
label=${s.label}
|
|
45
|
+
color=${s.get()}
|
|
46
|
+
@input=${(e: Event) => {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
const t = e.target as any;
|
|
49
|
+
s.set(String(t.color ?? t.value));
|
|
50
|
+
refresh();
|
|
51
|
+
}}
|
|
52
|
+
></bim-color-input>`;
|
|
53
|
+
case "enum":
|
|
54
|
+
return BUI.html`<bim-dropdown
|
|
55
|
+
label=${s.label}
|
|
56
|
+
@change=${(e: Event) => {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
const picked = (e.target as any).value?.[0];
|
|
59
|
+
const match = s.options.find((o) => o.label === picked);
|
|
60
|
+
if (match) {
|
|
61
|
+
s.set(match.value);
|
|
62
|
+
refresh();
|
|
63
|
+
}
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
${s.options.map(
|
|
67
|
+
(o) => BUI.html`<bim-option label=${o.label} ?checked=${o.value === s.get()}></bim-option>`,
|
|
68
|
+
)}
|
|
69
|
+
</bim-dropdown>`;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const stylesTool = (components: OBC.Components, world: OBC.World) => ({
|
|
74
|
+
label: "Styles",
|
|
75
|
+
icon: "mdi:palette",
|
|
76
|
+
render: (refresh: () => void) => {
|
|
77
|
+
const { settings } = styles(components, world);
|
|
78
|
+
// Group settings by their `group` field, preserving first-seen order.
|
|
79
|
+
const groups: { name: string; items: StyleSetting[] }[] = [];
|
|
80
|
+
for (const s of settings) {
|
|
81
|
+
let g = groups.find((x) => x.name === s.group);
|
|
82
|
+
if (!g) {
|
|
83
|
+
g = { name: s.group, items: [] };
|
|
84
|
+
groups.push(g);
|
|
85
|
+
}
|
|
86
|
+
g.items.push(s);
|
|
87
|
+
}
|
|
88
|
+
return BUI.html`
|
|
89
|
+
<div style="display: flex; flex-direction: column; gap: 0.6rem;">
|
|
90
|
+
${groups.map(
|
|
91
|
+
(g) => BUI.html`
|
|
92
|
+
<div>
|
|
93
|
+
<div style="
|
|
94
|
+
font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em;
|
|
95
|
+
text-transform: uppercase; opacity: 0.6;
|
|
96
|
+
color: var(--bim-ui_bg-contrast-100, #e3e3e3);
|
|
97
|
+
padding: 0.1rem 0 0.3rem; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1));
|
|
98
|
+
">${g.name}</div>
|
|
99
|
+
<div style="display: flex; flex-direction: column; padding-top: 0.25rem;">
|
|
100
|
+
${g.items.map(
|
|
101
|
+
(s) => BUI.html`<div style="padding: 0.3rem 0; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255, 255, 255, 0.1));">${control(s, refresh)}</div>`,
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
`,
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
`;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
@@ -137,14 +137,20 @@ export class ToolModeManager {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
// Real OBC.Component wrapper → per-`components` singleton resolved by UUID, so
|
|
141
|
+
// the coordinator is shared across bundle boundaries (toolbar built-in + tools +
|
|
142
|
+
// the app's panels all drive the SAME manager).
|
|
143
|
+
export class ToolModeManagerComponent extends OBC.Component {
|
|
144
|
+
static readonly uuid = "9b2c7e10-3a4f-4c21-8b6d-1f0a2c3d4e53" as const;
|
|
145
|
+
enabled = true;
|
|
146
|
+
readonly manager: ToolModeManager;
|
|
147
|
+
constructor(components: OBC.Components) {
|
|
148
|
+
super(components);
|
|
149
|
+
components.add(ToolModeManagerComponent.uuid, this);
|
|
150
|
+
this.manager = new ToolModeManager(components);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
141
153
|
|
|
142
154
|
/** Per-Components singleton accessor. */
|
|
143
|
-
export const toolModeManager = (components: OBC.Components): ToolModeManager =>
|
|
144
|
-
|
|
145
|
-
if (!manager) {
|
|
146
|
-
manager = new ToolModeManager(components);
|
|
147
|
-
instances.set(components, manager);
|
|
148
|
-
}
|
|
149
|
-
return manager;
|
|
150
|
-
};
|
|
155
|
+
export const toolModeManager = (components: OBC.Components): ToolModeManager =>
|
|
156
|
+
components.get(ToolModeManagerComponent).manager;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Controller returned by each toolbar mode-tool setup (`clipper`,
|
|
3
|
-
* `lengthMeasurement`, `areaMeasurement`, …). The toolbar — owned by Worker 2 —
|
|
4
|
-
* calls {@link activate} to enter the tool's mode and {@link deactivate} to exit
|
|
5
|
-
* it, and guarantees only one mode is active at a time (mutual exclusion).
|
|
6
|
-
*
|
|
7
|
-
* Contract:
|
|
8
|
-
* - `activate()` — enable the tool's pointer/keyboard listeners + cursor.
|
|
9
|
-
* - `deactivate()` — remove those listeners and drop any transient in-progress
|
|
10
|
-
* state. It must NOT discard finished results (e.g. existing
|
|
11
|
-
* section planes / measurements persist across mode switches).
|
|
12
|
-
* Both are idempotent.
|
|
13
|
-
*/
|
|
14
|
-
export interface ModeTool {
|
|
15
|
-
/** Human label, used for the toolbar button tooltip. */
|
|
16
|
-
label: string;
|
|
17
|
-
/** Iconify icon name, e.g. "mdi:scissors-cutting". */
|
|
18
|
-
icon: string;
|
|
19
|
-
/** Enter the tool's mode. */
|
|
20
|
-
activate(): void;
|
|
21
|
-
/** Exit the tool's mode and clean up transient state. */
|
|
22
|
-
deactivate(): void;
|
|
23
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Controller returned by each toolbar mode-tool setup (`clipper`,
|
|
3
|
+
* `lengthMeasurement`, `areaMeasurement`, …). The toolbar — owned by Worker 2 —
|
|
4
|
+
* calls {@link activate} to enter the tool's mode and {@link deactivate} to exit
|
|
5
|
+
* it, and guarantees only one mode is active at a time (mutual exclusion).
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - `activate()` — enable the tool's pointer/keyboard listeners + cursor.
|
|
9
|
+
* - `deactivate()` — remove those listeners and drop any transient in-progress
|
|
10
|
+
* state. It must NOT discard finished results (e.g. existing
|
|
11
|
+
* section planes / measurements persist across mode switches).
|
|
12
|
+
* Both are idempotent.
|
|
13
|
+
*/
|
|
14
|
+
export interface ModeTool {
|
|
15
|
+
/** Human label, used for the toolbar button tooltip. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Iconify icon name, e.g. "mdi:scissors-cutting". */
|
|
18
|
+
icon: string;
|
|
19
|
+
/** Enter the tool's mode. */
|
|
20
|
+
activate(): void;
|
|
21
|
+
/** Exit the tool's mode and clean up transient state. */
|
|
22
|
+
deactivate(): void;
|
|
23
|
+
}
|