@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.
Files changed (42) hide show
  1. package/README.md +3 -0
  2. package/dist/built-in/index.d.ts +22 -2
  3. package/dist/cli.js +3 -3
  4. package/docs/ai-quickstart.md +86 -0
  5. package/docs/builtin/paths.json +74 -74
  6. package/docs/cli/create.md +4 -3
  7. package/package.json +1 -1
  8. package/src/built-in/index.ts +22 -2
  9. package/src/cli/templates/app/src/app.ts +37 -37
  10. package/src/cli/templates/app/src/assets/tool-placeholder.ts +50 -50
  11. package/src/cli/templates/app/src/main.ts +223 -340
  12. package/src/cli/templates/app/src/setups/camera-tools.ts +63 -63
  13. package/src/cli/templates/app/src/setups/clipper-tool.ts +20 -1
  14. package/src/cli/templates/app/src/setups/clipper.ts +75 -75
  15. package/src/cli/templates/app/src/setups/index.ts +23 -33
  16. package/src/cli/templates/app/src/setups/inspection.ts +136 -136
  17. package/src/cli/templates/app/src/setups/measurement-tool.ts +19 -1
  18. package/src/cli/templates/app/src/setups/measurements.ts +133 -133
  19. package/src/cli/templates/app/src/setups/reality-capture/lib/hidden-tiles-plugin.ts +88 -88
  20. package/src/cli/templates/app/src/setups/reality-capture/lib/point-tile-plugin.ts +166 -166
  21. package/src/cli/templates/app/src/setups/reality-capture/lib/render-frame.ts +31 -31
  22. package/src/cli/templates/app/src/setups/reality-capture/lib/splat-tile-plugin.ts +51 -51
  23. package/src/cli/templates/app/src/setups/reality-capture-viewer.ts +1383 -1383
  24. package/src/cli/templates/app/src/setups/right-sidebar.ts +178 -178
  25. package/src/cli/templates/app/src/setups/styles-panel.ts +110 -110
  26. package/src/cli/templates/app/src/setups/tool-mode-manager.ts +15 -9
  27. package/src/cli/templates/app/src/setups/tool-mode.ts +23 -23
  28. package/src/cli/templates/app/src/setups/active-tool-hud.ts +0 -69
  29. package/src/cli/templates/app/src/setups/clipper-panel.ts +0 -167
  30. package/src/cli/templates/app/src/setups/commands-panel.ts +0 -239
  31. package/src/cli/templates/app/src/setups/data-table-panel.ts +0 -2224
  32. package/src/cli/templates/app/src/setups/file-format-icons.ts +0 -95
  33. package/src/cli/templates/app/src/setups/files-panel.ts +0 -1503
  34. package/src/cli/templates/app/src/setups/fps-indicator.ts +0 -63
  35. package/src/cli/templates/app/src/setups/graphics-panel.ts +0 -490
  36. package/src/cli/templates/app/src/setups/measurement-settings-panel.ts +0 -161
  37. package/src/cli/templates/app/src/setups/model-tree.ts +0 -797
  38. package/src/cli/templates/app/src/setups/objects-panel.ts +0 -127
  39. package/src/cli/templates/app/src/setups/properties-panel.ts +0 -1048
  40. package/src/cli/templates/app/src/setups/settings-panel.ts +0 -94
  41. package/src/cli/templates/app/src/setups/visibility-toolbar.ts +0 -498
  42. package/src/cli/templates/app/src/setups/walkthrough.ts +0 -528
@@ -1,69 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import { toolModeManager, type ManagedTool } from "./tool-mode-manager";
3
-
4
- /**
5
- * Active-tool HUD — a floating text overlay (top-left of the viewport) showing
6
- * what the user is currently doing, e.g. "Drawing clipping plane" /
7
- * "Measuring length".
8
- *
9
- * Fully DECOUPLED from any specific tool: it only subscribes to the global
10
- * toolModeManager's `onActiveChanged` and renders the active tool's registered
11
- * {@link ManagedTool.label} (+ optional icon), hidden when no tool is active. Any
12
- * future tool that registers a label with the manager shows up here with zero
13
- * HUD changes.
14
- *
15
- * Styled like the other in-viewer overlays (fps counter): panel surface + 1px
16
- * contrast-20 border + theme text, pointer-events off.
17
- */
18
- export interface ActiveToolHud {
19
- element: HTMLElement;
20
- }
21
-
22
- export const activeToolHud = (
23
- parent: HTMLElement,
24
- components: OBC.Components,
25
- ): ActiveToolHud => {
26
- const manager = toolModeManager(components);
27
-
28
- const el = document.createElement("div");
29
- el.style.cssText = `
30
- position: absolute; top: 0.6rem; left: 0.6rem; z-index: 10;
31
- display: none; align-items: center; gap: 0.4rem;
32
- padding: 0.25rem 0.6rem; border-radius: 0.5rem;
33
- background: var(--bim-ui_bg-contrast-10, #262629);
34
- border: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1));
35
- color: var(--bim-ui_bg-contrast-100, #e3e3e3);
36
- font: 600 0.74rem/1.2 "Plus Jakarta Sans", sans-serif;
37
- pointer-events: none; user-select: none;
38
- `;
39
- const icon = document.createElement("bim-icon");
40
- icon.style.fontSize = "0.95rem";
41
- const text = document.createElement("span");
42
- el.append(icon, text);
43
-
44
- // The viewer is position:relative, so an absolutely-positioned child overlays
45
- // the canvas correctly.
46
- if (!parent.style.position) parent.style.position = "relative";
47
- parent.append(el);
48
-
49
- const render = (tool: ManagedTool | null) => {
50
- if (!tool) {
51
- el.style.display = "none";
52
- return;
53
- }
54
- text.textContent =
55
- typeof tool.label === "function" ? tool.label() : tool.label;
56
- if (tool.icon) {
57
- icon.setAttribute("icon", tool.icon);
58
- icon.style.display = "";
59
- } else {
60
- icon.style.display = "none";
61
- }
62
- el.style.display = "flex";
63
- };
64
-
65
- manager.onActiveChanged.add(render);
66
- render(manager.active);
67
-
68
- return { element: el };
69
- };
@@ -1,167 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import * as BUI from "@thatopen/ui";
3
- import type { ClipperTool } from "./clipper-tool";
4
-
5
- /**
6
- * CLIPPING panel — a docked side-panel (a 4th layout alongside
7
- * Explorer/Files/Graphics) for section planes + per-category section styling.
8
- *
9
- * Sections:
10
- * - Section planes — master Enabled, Add plane (placing mode: double-click a
11
- * surface), Clear all.
12
- * - Active planes — one row per plane (per-plane enable + delete).
13
- * - Section styling — master fills/edges visibility + a per-IFC-category list,
14
- * each with a fill colour, an edge colour, and an include toggle.
15
- *
16
- * Vanilla BUI, matching graphics-panel.ts / files-panel.ts: `bim-panel` (native
17
- * header label+icon), muted section bands, 1px contrast-20 hairline rows, the
18
- * #3C3C41 scrollbar, library `bim-checkbox[toggle]` switches, `bim-button`s and
19
- * `bim-color-input`s. Factory returns the element WITHOUT self-mounting.
20
- */
21
- export const clipperPanel = (
22
- _components: OBC.Components,
23
- tool: ClipperTool,
24
- ) => {
25
- const [panel, update] = BUI.Component.create<BUI.Panel, { tick: number }>(
26
- (state) => {
27
- const refresh = () => update({ tick: state.tick + 1 });
28
-
29
- const enabled = tool.isEnabled();
30
- const sectionStyle = tool.getSectionStyle();
31
- const stylingVisible = tool.isStylingVisible();
32
-
33
- return BUI.html`
34
- <bim-panel label="Clipping" icon="mdi:scissors-cutting"
35
- style="width: 100%; height: 100%; pointer-events: auto;">
36
- <style>
37
- .c-vp::-webkit-scrollbar { width: 0.4rem; height: 0.4rem; }
38
- .c-vp::-webkit-scrollbar-thumb { border-radius: 0.25rem; background-color: var(--bim-scrollbar--c, #3C3C41); }
39
- .c-vp::-webkit-scrollbar-track { background-color: var(--bim-scrollbar--bgc, var(--bim-ui_bg-contrast-10)); }
40
-
41
- .c-band { box-sizing: border-box; padding: 0.4rem 0.4rem 0.4rem 1.1rem;
42
- display: flex; align-items: center; gap: 0.4rem;
43
- font-size: 0.76rem; font-weight: 500; letter-spacing: 0.01em;
44
- color: var(--bim-ui_bg-contrast-70, #a7a7ab);
45
- background: var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1));
46
- border-top: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1));
47
- border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1)); }
48
- .c-band-ico { flex: 0 0 auto; font-size: 0.9rem; color: var(--bim-ui_bg-contrast-70, #a7a7ab); }
49
- .c-band:first-child { border-top: none; }
50
-
51
- .c-row { box-sizing: border-box; min-height: 32px; display: flex;
52
- align-items: center; justify-content: space-between; gap: 0.6rem;
53
- padding: 0.35rem 0.4rem 0.35rem 1.1rem; font-size: 0.78rem;
54
- color: var(--bim-ui_bg-contrast-100, #e3e3e3);
55
- border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1)); }
56
- .c-k { opacity: 0.6; flex: 0 1 auto; min-width: 0; white-space: nowrap;
57
- overflow: hidden; text-overflow: ellipsis; }
58
- .c-v { flex: 1 1 auto; min-width: 0; display: flex; align-items: center;
59
- justify-content: flex-end; gap: 0.5rem; }
60
- .c-row bim-checkbox.c-toggle, .c-cat bim-checkbox.c-toggle { flex: 0 0 2rem; width: 2rem; }
61
- .c-row bim-button.c-del { flex: 0 0 auto; --bim-button--bgc: transparent; }
62
-
63
- .c-empty { padding: 0.6rem 0.4rem 0.6rem 1.1rem; opacity: 0.6;
64
- font-size: 0.78rem; line-height: 1.3; white-space: normal; }
65
-
66
- .c-actions { display: flex; flex-direction: column; gap: 0.4rem;
67
- padding: 0.5rem 0.4rem 0.5rem 1.1rem; }
68
- .c-actions bim-button { width: 100%; }
69
-
70
- /* Per-category style row: [toggle] [name grows] [fill][edge swatches] */
71
- .c-cat { box-sizing: border-box; min-height: 32px; display: flex;
72
- align-items: center; gap: 0.5rem;
73
- padding: 0.3rem 0.4rem 0.3rem 1.1rem; font-size: 0.78rem;
74
- color: var(--bim-ui_bg-contrast-100, #e3e3e3);
75
- border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1)); }
76
- .c-cat-name { flex: 1 1 auto; min-width: 0; opacity: 0.85;
77
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
78
- .c-cat-colors { flex: 0 0 auto; display: flex; align-items: center; gap: 0.35rem; }
79
- .c-cat-colors bim-color-input { flex: 0 0 auto; }
80
- .c-cat-off { opacity: 0.45; }
81
- .c-cat-off .c-cat-colors { pointer-events: none; }
82
- </style>
83
- <div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
84
- <div style="flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding: 0 0.4rem 0.4rem 1.1rem;">
85
- <div class="c-vp" style="flex: 1 1 auto; min-height: 0; overflow-y: auto;
86
- margin-left: -1.1rem; margin-right: -0.4rem;">
87
-
88
- <div class="c-row">
89
- <span class="c-k">Enabled</span>
90
- <span class="c-v">
91
- <bim-checkbox class="c-toggle" toggle ?checked=${enabled}
92
- @change=${(e: Event) => {
93
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
- tool.setEnabled(!!(e.target as any).checked);
95
- refresh();
96
- }}></bim-checkbox>
97
- </span>
98
- </div>
99
- <div class="c-row">
100
- <span class="c-k">Show fills / edges</span>
101
- <span class="c-v">
102
- <bim-checkbox class="c-toggle" toggle ?checked=${stylingVisible}
103
- @change=${(e: Event) => {
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- tool.setStylingVisible(!!(e.target as any).checked);
106
- refresh();
107
- }}></bim-checkbox>
108
- </span>
109
- </div>
110
- <div class="c-row">
111
- <span class="c-k">Fill color</span>
112
- <span class="c-v">
113
- <bim-color-input color=${sectionStyle.fill}
114
- @input=${(e: Event) => {
115
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
- const t = e.target as any;
117
- tool.setSectionFill(String(t.color ?? t.value));
118
- }}></bim-color-input>
119
- </span>
120
- </div>
121
- <div class="c-row">
122
- <span class="c-k">Edge color</span>
123
- <span class="c-v">
124
- <bim-color-input color=${sectionStyle.line}
125
- @input=${(e: Event) => {
126
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
- const t = e.target as any;
128
- tool.setSectionLine(String(t.color ?? t.value));
129
- }}></bim-color-input>
130
- </span>
131
- </div>
132
- <div class="c-row">
133
- <span class="c-k">Fill opacity</span>
134
- <span class="c-v">
135
- <bim-number-input slider value=${sectionStyle.opacity}
136
- min="0" max="1" step="0.05"
137
- @change=${(e: Event) => {
138
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
- tool.setSectionOpacity(Number((e.target as any).value));
140
- }}></bim-number-input>
141
- </span>
142
- </div>
143
- <div class="c-row">
144
- <span class="c-k">Edge width</span>
145
- <span class="c-v">
146
- <bim-number-input slider value=${sectionStyle.width}
147
- min="1" max="20" step="1"
148
- @change=${(e: Event) => {
149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
- tool.setSectionWidth(Number((e.target as any).value));
151
- }}></bim-number-input>
152
- </span>
153
- </div>
154
- </div>
155
- </div>
156
- </div>
157
- </bim-panel>
158
- `;
159
- },
160
- { tick: 0 },
161
- );
162
-
163
- tool.onChanged.add(() => update({ tick: 0 }));
164
- tool.onStyleChanged.add(() => update({ tick: 0 }));
165
-
166
- return panel;
167
- };
@@ -1,239 +0,0 @@
1
- import * as THREE from "three";
2
- import * as OBC from "@thatopen/components";
3
- import * as OBF from "@thatopen/components-front";
4
- import * as BUI from "@thatopen/ui";
5
- import { hider } from "./hider";
6
-
7
- /**
8
- * COMMANDS — a docked side-panel (alongside Explorer / Files / Graphics) that is
9
- * both the keyboard-shortcut registry and its palette UI.
10
- *
11
- * A single `COMMANDS` table maps each viewer action to a shortcut + an mdi icon
12
- * and a `run()` wired to the EXISTING components (the `hider` controller for
13
- * visibility, the Highlighter for the current selection + clear, camera-controls
14
- * for focus). The panel lists every command with its shortcut (command-palette
15
- * style, click-to-run), and a global `keydown` handler dispatches the shortcuts —
16
- * ignored while typing in an input so search fields aren't hijacked.
17
- *
18
- * Self-contained: returns the panel element WITHOUT self-mounting (like
19
- * modelTree / graphicsPanel) and installs its own window keydown listener.
20
- *
21
- * @param components engine components
22
- */
23
-
24
- interface ViewerCommand {
25
- id: string;
26
- label: string;
27
- icon: string;
28
- /** Display chips, e.g. ["Shift", "H"]. */
29
- keys: string[];
30
- /** Does this keydown event trigger the command? */
31
- match: (e: KeyboardEvent) => boolean;
32
- run: () => void;
33
- }
34
-
35
- const noMods = (e: KeyboardEvent) =>
36
- !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey;
37
-
38
- // Don't hijack shortcuts while the user is typing in a field.
39
- const typingInField = (e: KeyboardEvent) => {
40
- const path = (e.composedPath?.() ?? []) as HTMLElement[];
41
- const el = (path[0] ?? (e.target as HTMLElement)) || null;
42
- const active = (document.activeElement as HTMLElement) || null;
43
- const isField = (n: HTMLElement | null) =>
44
- !!n &&
45
- (n.isContentEditable ||
46
- /^(input|textarea|select)$/i.test(n.tagName) ||
47
- /^bim-(text|number)-input$/i.test(n.tagName));
48
- return path.some(isField) || isField(el) || isField(active);
49
- };
50
-
51
- export const commandsPanel = (components: OBC.Components) => {
52
- const fragments = components.get(OBC.FragmentsManager);
53
- const highlighter = components.get(OBF.Highlighter);
54
- const selectName = highlighter.config.selectName;
55
- const h = hider(components);
56
-
57
- const hasSelection = () => {
58
- const map = highlighter.selection[selectName];
59
- return !!map && Object.values(map).some((s) => s.size > 0);
60
- };
61
-
62
- const focusSelection = async () => {
63
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
- const world = [...components.get(OBC.Worlds).list.values()][0] as any;
65
- const controls = world?.camera?.controls;
66
- if (!controls) return;
67
- const map = highlighter.selection[selectName];
68
- const hasSel = !!map && Object.values(map).some((s) => s.size > 0);
69
- try {
70
- const box = new THREE.Box3();
71
- if (hasSel) {
72
- const boxes = (await fragments.getBBoxes(map)) as THREE.Box3[];
73
- for (const b of boxes) box.union(b);
74
- } else {
75
- // Nothing selected → focus = zoom to fit the WHOLE model (union of all
76
- // loaded models' bounds). This makes a dedicated fit/home button unnecessary.
77
- for (const model of fragments.list.values()) {
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- const b = (model as any).box as THREE.Box3 | undefined;
80
- if (b && !b.isEmpty()) box.union(b);
81
- }
82
- }
83
- if (box.isEmpty()) return;
84
- const sphere = box.getBoundingSphere(new THREE.Sphere());
85
- await controls.fitToSphere(sphere, true);
86
- } catch (error) {
87
- console.warn("[commands] focus failed", error);
88
- }
89
- };
90
-
91
- const clearSelection = () => void highlighter.clear(selectName);
92
-
93
- const COMMANDS: ViewerCommand[] = [
94
- {
95
- id: "focus",
96
- label: "Focus selection",
97
- icon: "mdi:image-filter-center-focus",
98
- keys: ["F"],
99
- match: (e) => e.key.toLowerCase() === "f" && noMods(e),
100
- run: () => void focusSelection(),
101
- },
102
- {
103
- id: "hide",
104
- label: "Hide selected",
105
- icon: "mdi:eye-off-outline",
106
- keys: ["H"],
107
- match: (e) => e.key.toLowerCase() === "h" && noMods(e),
108
- run: () => h.hideSelected(),
109
- },
110
- {
111
- id: "isolate",
112
- label: "Isolate selected",
113
- icon: "mdi:select-search",
114
- keys: ["Shift", "H"],
115
- match: (e) =>
116
- e.key.toLowerCase() === "h" &&
117
- e.shiftKey &&
118
- !e.ctrlKey &&
119
- !e.metaKey &&
120
- !e.altKey,
121
- run: () => h.isolateSelected(),
122
- },
123
- {
124
- id: "ghost",
125
- label: "Ghost selected",
126
- icon: "mdi:ghost-outline",
127
- keys: ["G"],
128
- match: (e) => e.key.toLowerCase() === "g" && noMods(e),
129
- run: () => h.ghostSelected(),
130
- },
131
- {
132
- id: "showAll",
133
- label: "Show all",
134
- icon: "mdi:eye-outline",
135
- keys: ["Shift", "A"],
136
- match: (e) =>
137
- e.key.toLowerCase() === "a" &&
138
- e.shiftKey &&
139
- !e.ctrlKey &&
140
- !e.metaKey &&
141
- !e.altKey,
142
- run: () => h.showAll(),
143
- },
144
- {
145
- id: "clear",
146
- label: "Clear selection",
147
- icon: "mdi:close",
148
- keys: ["Esc"],
149
- match: (e) => e.key === "Escape" && noMods(e),
150
- run: () => clearSelection(),
151
- },
152
- ];
153
-
154
- // ── Global shortcut dispatch ───────────────────────────────────
155
- const onKeyDown = (e: KeyboardEvent) => {
156
- if (typingInField(e)) return;
157
- for (const cmd of COMMANDS) {
158
- if (cmd.match(e)) {
159
- e.preventDefault();
160
- cmd.run();
161
- return;
162
- }
163
- }
164
- };
165
- window.addEventListener("keydown", onKeyDown);
166
-
167
- // ── Panel UI (command-palette list; mirrors graphics-panel chrome) ──
168
- const [panel, update] = BUI.Component.create<BUI.Panel, { query: string }>(
169
- (state) => {
170
- const q = state.query.trim().toLowerCase();
171
- const rows = COMMANDS.filter((c) => !q || c.label.toLowerCase().includes(q));
172
- return BUI.html`
173
- <bim-panel
174
- label="Commands"
175
- icon="mdi:keyboard"
176
- style="width: 100%; height: 100%; pointer-events: auto;"
177
- >
178
- <style>
179
- .cmd-vp::-webkit-scrollbar { width: 0.4rem; height: 0.4rem; }
180
- .cmd-vp::-webkit-scrollbar-thumb { border-radius: 0.25rem; background-color: var(--bim-scrollbar--c, #3C3C41); }
181
- .cmd-vp::-webkit-scrollbar-track { background-color: var(--bim-scrollbar--bgc, var(--bim-ui_bg-contrast-10)); }
182
- .cmd-row { box-sizing: border-box; min-height: 32px; display: flex;
183
- align-items: center; gap: 0.55rem; padding: 0.35rem 0.4rem 0.35rem 1.1rem;
184
- font-size: 0.78rem; line-height: 1.3; cursor: pointer;
185
- color: var(--bim-ui_bg-contrast-100, #e3e3e3);
186
- border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1)); }
187
- .cmd-row:hover { background: var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.08)); }
188
- .cmd-ico { flex: 0 0 auto; color: #99a0ae; font-size: 0.95rem; }
189
- .cmd-label { flex: 1 1 auto; min-width: 0; overflow: hidden;
190
- text-overflow: ellipsis; white-space: nowrap; }
191
- .cmd-keys { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 0.2rem; }
192
- .cmd-kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
193
- font-size: 0.66rem; line-height: 1; padding: 0.12rem 0.35rem;
194
- border-radius: 0.25rem; color: var(--bim-ui_bg-contrast-80, #c9c9c9);
195
- background: var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.08));
196
- border: 1px solid var(--bim-ui_bg-contrast-40, rgba(255,255,255,0.18)); }
197
- .cmd-plus { opacity: 0.4; font-size: 0.66rem; }
198
- </style>
199
- <div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
200
- <div style="flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding: 0.4rem 0.4rem 0.4rem 1.1rem; gap: 0.25rem;">
201
- <bim-text-input
202
- icon="mdi:magnify"
203
- icon-inside
204
- placeholder="Search commands…"
205
- style="flex: 0 0 auto; width: 100%; margin: 0 0 0.25rem -0.35rem;"
206
- @input=${(e: Event) => {
207
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
- update({ query: String((e.target as any).value ?? "") });
209
- }}
210
- ></bim-text-input>
211
- <div style="flex: 0 0 auto; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1)); margin: 0 -0.4rem 0.1rem -1.1rem;"></div>
212
- <div class="cmd-vp" style="flex: 1 1 auto; min-height: 0; overflow-y: auto; margin-left: -1.1rem; margin-right: -0.4rem;">
213
- ${rows.length === 0
214
- ? BUI.html`<div style="padding: 0.75rem 1.1rem;"><bim-label style="opacity: 0.6;">No commands.</bim-label></div>`
215
- : rows.map(
216
- (c) => BUI.html`
217
- <div class="cmd-row" title=${c.label} @click=${() => c.run()}>
218
- <bim-icon class="cmd-ico" icon=${c.icon}></bim-icon>
219
- <span class="cmd-label">${c.label}</span>
220
- <span class="cmd-keys">
221
- ${c.keys.map(
222
- (k, i) => BUI.html`${i > 0
223
- ? BUI.html`<span class="cmd-plus">+</span>`
224
- : null}<span class="cmd-kbd">${k}</span>`,
225
- )}
226
- </span>
227
- </div>`,
228
- )}
229
- </div>
230
- </div>
231
- </div>
232
- </bim-panel>
233
- `;
234
- },
235
- { query: "" },
236
- );
237
-
238
- return panel;
239
- };