@ifc-lite/viewer 1.26.0 → 1.27.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.
Files changed (89) hide show
  1. package/.turbo/turbo-build.log +38 -31
  2. package/CHANGELOG.md +29 -0
  3. package/dist/assets/{basketViewActivator-ZpTYWE3K.js → basketViewActivator-B3CdrLsb.js} +7 -7
  4. package/dist/assets/{bcf-Ctcu_Sc2.js → bcf-QeHK_Aud.js} +1 -1
  5. package/dist/assets/{browser-DXS29_v9.js → browser-BIoDDfBW.js} +1 -1
  6. package/dist/assets/{cesium-BoVuJvTC.js → cesium-CzZn5yVA.js} +319 -319
  7. package/dist/assets/{deflate-Cnx0il6E.js → deflate-B-d0SYQM.js} +1 -1
  8. package/dist/assets/exceljs.min-DsuzKYnj.js +29 -0
  9. package/dist/assets/{exporters-DSq76AVM.js → exporters-B4LbZFeT.js} +1422 -1194
  10. package/dist/assets/geometry.worker-BdH-E6NB.js +1 -0
  11. package/dist/assets/{geotiff-A5UjhI6L.js → geotiff-CrVtDRFq.js} +10 -10
  12. package/dist/assets/html2canvas.esm-Ge7aVWlp.js +5 -0
  13. package/dist/assets/{ids-DiLcGTer.js → ids-DjsGFN10.js} +4 -4
  14. package/dist/assets/ifc-lite_bg-DsYUIHm3.wasm +0 -0
  15. package/dist/assets/{index-BAH8IJVR.js → index-COYokSKc.js} +38319 -35469
  16. package/dist/assets/index-ajK6D32J.css +1 -0
  17. package/dist/assets/index.es-CY202jA3.js +6866 -0
  18. package/dist/assets/{jpeg-BzSkwo5D.js → jpeg-D4wOkf5h.js} +1 -1
  19. package/dist/assets/jspdf.es.min-DIGb9BHN.js +19571 -0
  20. package/dist/assets/jspdf.plugin.autotable-BBLUVd7n.js +2 -0
  21. package/dist/assets/{lerc-Cg2Rz-D5.js → lerc-DmW0_tgf.js} +1 -1
  22. package/dist/assets/{lzw-BBPPLW-0.js → lzw-oWetY-d6.js} +1 -1
  23. package/dist/assets/{maplibre-gl-Do6O5tDc.js → maplibre-gl-BF3Z0idw.js} +1 -1
  24. package/dist/assets/{native-bridge-CPojOeGE.js → native-bridge-BX8_tHXE.js} +1 -1
  25. package/dist/assets/{packbits-yLSpjW-V.js → packbits-F8Nkp4NY.js} +1 -1
  26. package/dist/assets/{pako.esm-Cram60i4.js → pako.esm-n3Pgozwg.js} +1 -1
  27. package/dist/assets/{parser.worker-8md211IW.js → parser.worker-D591Zu_-.js} +3 -3
  28. package/dist/assets/pdf-Dsh3HPZB.js +135 -0
  29. package/dist/assets/raw-D9iw0tmc.js +1 -0
  30. package/dist/assets/{sandbox-CsRXlgCO.js → sandbox-BAC3a-eN.js} +1735 -1660
  31. package/dist/assets/server-client-Cjwnm7il.js +706 -0
  32. package/dist/assets/{webimage-YafxjjGr.js → webimage-BLV1dgmd.js} +1 -1
  33. package/dist/assets/xlsx-Bc2HTrjC.js +142 -0
  34. package/dist/assets/{zip-BJqVbRkU.js → zip-DFgP-l20.js} +1 -1
  35. package/dist/assets/{zstd-CkSLOiuu.js → zstd-C_1HxVrA.js} +1 -1
  36. package/dist/index.html +8 -8
  37. package/package.json +10 -7
  38. package/src/components/mcp/PlaygroundChat.tsx +1 -0
  39. package/src/components/mcp/data.ts +6 -0
  40. package/src/components/mcp/playground-dispatcher.ts +277 -0
  41. package/src/components/mcp/types.ts +2 -1
  42. package/src/components/ui/combo-input.tsx +163 -0
  43. package/src/components/ui/tabs.tsx +1 -1
  44. package/src/components/viewer/PropertiesPanel.tsx +13 -6
  45. package/src/components/viewer/SearchInline.tsx +62 -2
  46. package/src/components/viewer/SearchModal.filter.builder.tsx +24 -393
  47. package/src/components/viewer/SearchModal.filter.editors.tsx +503 -0
  48. package/src/components/viewer/SearchModal.filter.tsx +64 -1
  49. package/src/components/viewer/SearchModal.tsx +19 -6
  50. package/src/components/viewer/Viewport.tsx +15 -0
  51. package/src/components/viewer/lists/ColumnHeaderMenu.tsx +84 -0
  52. package/src/components/viewer/lists/ListBuilder.tsx +789 -280
  53. package/src/components/viewer/lists/ListGroupingBar.tsx +72 -0
  54. package/src/components/viewer/lists/ListPanel.tsx +49 -5
  55. package/src/components/viewer/lists/ListResultsTable.tsx +270 -176
  56. package/src/components/viewer/lists/list-table-utils.ts +123 -0
  57. package/src/generated/mcp-catalog.json +4 -0
  58. package/src/hooks/source-key.ts +35 -0
  59. package/src/hooks/useAlignmentLines3D.ts +1 -26
  60. package/src/hooks/useGridLines3D.ts +140 -0
  61. package/src/lib/length-unit-scale.ts +41 -0
  62. package/src/lib/lists/adapter.ts +136 -11
  63. package/src/lib/lists/export/csv.ts +47 -0
  64. package/src/lib/lists/export/index.ts +49 -0
  65. package/src/lib/lists/export/model.ts +111 -0
  66. package/src/lib/lists/export/pdf.ts +67 -0
  67. package/src/lib/lists/export/xlsx.ts +83 -0
  68. package/src/lib/lists/index.ts +2 -0
  69. package/src/lib/search/filter-evaluate.test.ts +81 -0
  70. package/src/lib/search/filter-evaluate.ts +59 -87
  71. package/src/lib/search/filter-match.ts +167 -0
  72. package/src/lib/search/filter-rules.test.ts +25 -0
  73. package/src/lib/search/filter-rules.ts +75 -2
  74. package/src/lib/search/filter-schema.ts +0 -0
  75. package/src/lib/slab-edit.test.ts +72 -0
  76. package/src/lib/slab-edit.ts +159 -19
  77. package/src/sdk/adapters/export-adapter.ts +3 -3
  78. package/src/sdk/adapters/query-adapter.ts +3 -3
  79. package/src/store/slices/listSlice.ts +6 -0
  80. package/src/store/slices/mutationSlice.ts +14 -6
  81. package/src/store/slices/searchSlice.ts +29 -3
  82. package/src/utils/nativeSpatialDataStore.ts +6 -0
  83. package/src/utils/serverDataModel.test.ts +6 -0
  84. package/src/utils/serverDataModel.ts +7 -0
  85. package/dist/assets/geometry.worker-0Q9qEa6p.js +0 -1
  86. package/dist/assets/ifc-lite_bg-CEZnhM2e.wasm +0 -0
  87. package/dist/assets/index-B9Ug2EqU.css +0 -1
  88. package/dist/assets/raw-BQrAgxwT.js +0 -1
  89. package/dist/assets/server-client-Bk4c1CPO.js +0 -626
@@ -0,0 +1,72 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * Status / control strip above the results table. Shows the active grouping
7
+ * and sum columns as removable chips, plus expand/collapse and live totals —
8
+ * the connective tissue between the table and the list definition.
9
+ */
10
+
11
+ import { Group, Sigma, X, ChevronsDownUp, ChevronsUpDown } from 'lucide-react';
12
+ import { cn } from '@/lib/utils';
13
+
14
+ interface ListGroupingBarProps {
15
+ groupLabel: string | null;
16
+ sums: { id: string; label: string }[];
17
+ groupCount: number;
18
+ count: number;
19
+ allExpanded: boolean;
20
+ onClearGroup: () => void;
21
+ onRemoveSum: (id: string) => void;
22
+ onToggleExpandAll: () => void;
23
+ }
24
+
25
+ function Chip({ icon, children, onRemove }: { icon: React.ReactNode; children: React.ReactNode; onRemove: () => void }) {
26
+ return (
27
+ <span className="inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 py-0.5 pl-2 pr-1 text-[11px] font-medium text-foreground">
28
+ {icon}
29
+ <span className="max-w-[12rem] truncate">{children}</span>
30
+ <button
31
+ onClick={onRemove}
32
+ className="ml-0.5 rounded-full p-0.5 text-muted-foreground hover:bg-primary/20 hover:text-foreground"
33
+ aria-label="Remove"
34
+ >
35
+ <X className="h-3 w-3" />
36
+ </button>
37
+ </span>
38
+ );
39
+ }
40
+
41
+ export function ListGroupingBar({
42
+ groupLabel, sums, groupCount, count, allExpanded,
43
+ onClearGroup, onRemoveSum, onToggleExpandAll,
44
+ }: ListGroupingBarProps) {
45
+ const grouped = groupLabel !== null;
46
+ return (
47
+ <div className="flex flex-wrap items-center gap-1.5 border-b bg-muted/30 px-3 py-1.5 text-xs">
48
+ {grouped && (
49
+ <button
50
+ onClick={onToggleExpandAll}
51
+ className="mr-0.5 inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground"
52
+ title={allExpanded ? 'Collapse all groups' : 'Expand all groups'}
53
+ >
54
+ {allExpanded ? <ChevronsDownUp className="h-3.5 w-3.5" /> : <ChevronsUpDown className="h-3.5 w-3.5" />}
55
+ </button>
56
+ )}
57
+
58
+ {grouped
59
+ ? <Chip icon={<Group className="h-3 w-3 text-primary" />} onRemove={onClearGroup}>Grouped by {groupLabel}</Chip>
60
+ : <span className="text-muted-foreground">No grouping — use a column&apos;s <span className="font-medium text-foreground">⋮</span> menu to group or sum</span>}
61
+
62
+ {sums.map((s) => (
63
+ <Chip key={s.id} icon={<Sigma className="h-3 w-3 text-primary" />} onRemove={() => onRemoveSum(s.id)}>{s.label}</Chip>
64
+ ))}
65
+
66
+ <span className={cn('ml-auto whitespace-nowrap font-medium text-muted-foreground')}>
67
+ {grouped && <>{groupCount.toLocaleString()} group{groupCount === 1 ? '' : 's'} · </>}
68
+ {count.toLocaleString()} element{count === 1 ? '' : 's'}
69
+ </span>
70
+ </div>
71
+ );
72
+ }
@@ -34,12 +34,14 @@ import { useViewerStore } from '@/store';
34
34
  import { useIfc } from '@/hooks/useIfc';
35
35
  import {
36
36
  executeList,
37
+ summariseListRows,
37
38
  LIST_PRESETS,
38
39
  importListDefinition,
39
40
  exportListDefinition,
40
41
  createListDataProvider,
41
42
  } from '@/lib/lists';
42
- import type { ListDefinition, ListResult, ListDataProvider } from '@/lib/lists';
43
+ import type { ListDefinition, ListResult, ListDataProvider, ListGrouping } from '@/lib/lists';
44
+ import type { IfcDataStore } from '@ifc-lite/parser';
43
45
  import { ListBuilder } from './ListBuilder';
44
46
  import { ListResultsTable } from './ListResultsTable';
45
47
 
@@ -64,6 +66,17 @@ export function ListPanel({ onClose }: ListPanelProps) {
64
66
  const setActiveListId = useViewerStore((s) => s.setActiveListId);
65
67
  const setListResult = useViewerStore((s) => s.setListResult);
66
68
  const setListExecuting = useViewerStore((s) => s.setListExecuting);
69
+ const pendingListDraft = useViewerStore((s) => s.pendingListDraft);
70
+ const setPendingListDraft = useViewerStore((s) => s.setPendingListDraft);
71
+
72
+ // A draft handed off from "Create list" (search filter) opens straight into
73
+ // the builder for column configuration, then is cleared so it fires once.
74
+ React.useEffect(() => {
75
+ if (!pendingListDraft) return;
76
+ setEditingList(pendingListDraft);
77
+ setView('builder');
78
+ setPendingListDraft(null);
79
+ }, [pendingListDraft, setPendingListDraft]);
67
80
 
68
81
  const importInputRef = React.useRef<HTMLInputElement>(null);
69
82
 
@@ -71,21 +84,22 @@ export function ListPanel({ onClose }: ListPanelProps) {
71
84
  // arrays can never drift out of alignment (skipping a model without
72
85
  // an ifcDataStore must not shift every later model's provider index).
73
86
  const modelProviderPairs = useMemo(() => {
74
- const pairs: Array<{ modelId: string; provider: ListDataProvider }> = [];
87
+ const pairs: Array<{ modelId: string; provider: ListDataProvider; store: IfcDataStore }> = [];
75
88
  if (models.size > 0) {
76
89
  for (const [modelId, model] of models) {
77
90
  // Skip native-metadata models — they don't have a parsed
78
91
  // IfcDataStore, so the list provider can't query them.
79
92
  if (!model.ifcDataStore) continue;
80
- pairs.push({ modelId, provider: createListDataProvider(model.ifcDataStore) });
93
+ pairs.push({ modelId, provider: createListDataProvider(model.ifcDataStore), store: model.ifcDataStore });
81
94
  }
82
95
  } else if (ifcDataStore) {
83
- pairs.push({ modelId: 'default', provider: createListDataProvider(ifcDataStore) });
96
+ pairs.push({ modelId: 'default', provider: createListDataProvider(ifcDataStore), store: ifcDataStore });
84
97
  }
85
98
  return pairs;
86
99
  }, [models, ifcDataStore]);
87
100
 
88
101
  const allProviders = useMemo(() => modelProviderPairs.map((p) => p.provider), [modelProviderPairs]);
102
+ const allStores = useMemo(() => modelProviderPairs.map((p) => p.store), [modelProviderPairs]);
89
103
 
90
104
  const hasData = allProviders.length > 0;
91
105
 
@@ -107,11 +121,17 @@ export function ListPanel({ onClose }: ListPanelProps) {
107
121
  const allRows = resultParts.flatMap(r => r.rows);
108
122
  const totalTime = resultParts.reduce((sum, r) => sum + r.executionTime, 0);
109
123
 
124
+ // Re-derive groups/summary over the merged rows so grouping works
125
+ // across federated models (and isn't dropped on the merge).
126
+ const { groups, summary } = summariseListRows(definition, allRows);
127
+
110
128
  setListResult({
111
129
  columns: definition.columns,
112
130
  rows: allRows,
113
131
  totalCount: allRows.length,
114
132
  executionTime: totalTime,
133
+ groups,
134
+ summary,
115
135
  });
116
136
  setView('results');
117
137
  } catch (err) {
@@ -164,6 +184,24 @@ export function ListPanel({ onClose }: ListPanelProps) {
164
184
  }
165
185
  }, [editingList]);
166
186
 
187
+ // Grouping/summing changed directly from the results table: update the
188
+ // executed definition (so Settings reflects it), persist if it's saved, and
189
+ // re-derive groups/summary over the current rows for a consistent result.
190
+ const handleGroupingFromTable = useCallback((grouping: ListGrouping | undefined) => {
191
+ const def = editingList;
192
+ if (!def) return;
193
+ const next: ListDefinition = { ...def, grouping };
194
+ setEditingList(next);
195
+ if (listDefinitions.some((d) => d.id === def.id)) {
196
+ updateListDefinition(def.id, { grouping });
197
+ }
198
+ const current = useViewerStore.getState().listResult;
199
+ if (current) {
200
+ const summ = summariseListRows(next, current.rows);
201
+ setListResult({ ...current, groups: summ.groups, summary: summ.summary });
202
+ }
203
+ }, [editingList, listDefinitions, updateListDefinition, setListResult]);
204
+
167
205
  const handleImport = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
168
206
  const file = e.target.files?.[0];
169
207
  if (!file) return;
@@ -251,6 +289,7 @@ export function ListPanel({ onClose }: ListPanelProps) {
251
289
  {view === 'builder' && hasData && (
252
290
  <ListBuilder
253
291
  providers={allProviders}
292
+ stores={allStores}
254
293
  initial={editingList}
255
294
  onSave={handleSaveList}
256
295
  onCancel={() => setView('library')}
@@ -259,7 +298,12 @@ export function ListPanel({ onClose }: ListPanelProps) {
259
298
  )}
260
299
 
261
300
  {view === 'results' && listResult && (
262
- <ListResultsTable result={listResult} />
301
+ <ListResultsTable
302
+ result={listResult}
303
+ listName={editingList?.name}
304
+ grouping={editingList?.grouping}
305
+ onGroupingChange={handleGroupingFromTable}
306
+ />
263
307
  )}
264
308
 
265
309
  {/* Hidden import input */}