@ifc-lite/viewer 1.1.6 → 1.5.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 (164) hide show
  1. package/LICENSE +373 -0
  2. package/dist/apple-touch-icon.png +0 -0
  3. package/dist/assets/Arrow.dom-B0e15b_b.js +20 -0
  4. package/dist/assets/arrow2-bb-jcVEo.js +2 -0
  5. package/dist/assets/arrow2_bg-4Y7xYo54.wasm +0 -0
  6. package/dist/assets/arrow2_bg-BlXl-cSQ.js +1 -0
  7. package/dist/assets/arrow2_bg-BoXCojjR.wasm +0 -0
  8. package/dist/assets/desktop-cache-oPzaWXYE.js +1 -0
  9. package/dist/assets/event-DIOks52T.js +1 -0
  10. package/dist/assets/ifc-cache-BAN4vcd4.js +1 -0
  11. package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
  12. package/dist/assets/index-Dgd6vzw_.js +65252 -0
  13. package/dist/assets/index-v3mcCUPN.css +1 -0
  14. package/dist/assets/native-bridge-Ci7NLjlZ.js +111 -0
  15. package/dist/assets/wasm-bridge-Dc82YpdZ.js +1 -0
  16. package/dist/favicon-16x16-cropped.png +0 -0
  17. package/dist/favicon-16x16.png +0 -0
  18. package/dist/favicon-192x192-cropped.png +0 -0
  19. package/dist/favicon-192x192.png +0 -0
  20. package/dist/favicon-32x32-cropped.png +0 -0
  21. package/dist/favicon-32x32.png +0 -0
  22. package/dist/favicon-48x48-cropped.png +0 -0
  23. package/dist/favicon-48x48.png +0 -0
  24. package/dist/favicon-512x512-cropped.png +0 -0
  25. package/dist/favicon-512x512.png +0 -0
  26. package/dist/favicon-64x64-cropped.png +0 -0
  27. package/dist/favicon-64x64.png +0 -0
  28. package/dist/favicon-96x96-cropped.png +0 -0
  29. package/dist/favicon-96x96.png +0 -0
  30. package/dist/favicon-square-512.png +0 -0
  31. package/dist/favicon.ico +0 -0
  32. package/dist/favicon.png +0 -0
  33. package/dist/favicon.svg +3 -0
  34. package/dist/index.html +44 -0
  35. package/dist/logo.png +0 -0
  36. package/dist/manifest.json +48 -0
  37. package/index.html +33 -2
  38. package/package.json +34 -17
  39. package/public/apple-touch-icon.png +0 -0
  40. package/public/favicon-16x16-cropped.png +0 -0
  41. package/public/favicon-16x16.png +0 -0
  42. package/public/favicon-192x192-cropped.png +0 -0
  43. package/public/favicon-192x192.png +0 -0
  44. package/public/favicon-32x32-cropped.png +0 -0
  45. package/public/favicon-32x32.png +0 -0
  46. package/public/favicon-48x48-cropped.png +0 -0
  47. package/public/favicon-48x48.png +0 -0
  48. package/public/favicon-512x512-cropped.png +0 -0
  49. package/public/favicon-512x512.png +0 -0
  50. package/public/favicon-64x64-cropped.png +0 -0
  51. package/public/favicon-64x64.png +0 -0
  52. package/public/favicon-96x96-cropped.png +0 -0
  53. package/public/favicon-96x96.png +0 -0
  54. package/public/favicon-square-512.png +0 -0
  55. package/public/favicon.ico +0 -0
  56. package/public/favicon.png +0 -0
  57. package/public/favicon.svg +3 -0
  58. package/public/logo.png +0 -0
  59. package/public/manifest.json +48 -0
  60. package/src/App.tsx +2 -0
  61. package/src/components/ui/alert.tsx +62 -0
  62. package/src/components/ui/badge.tsx +39 -0
  63. package/src/components/ui/dialog.tsx +120 -0
  64. package/src/components/ui/label.tsx +27 -0
  65. package/src/components/ui/select.tsx +151 -0
  66. package/src/components/ui/switch.tsx +30 -0
  67. package/src/components/ui/table.tsx +120 -0
  68. package/src/components/ui/tabs.tsx +1 -1
  69. package/src/components/viewer/BCFPanel.tsx +1164 -0
  70. package/src/components/viewer/BulkPropertyEditor.tsx +875 -0
  71. package/src/components/viewer/DataConnector.tsx +840 -0
  72. package/src/components/viewer/DrawingSettingsPanel.tsx +536 -0
  73. package/src/components/viewer/EntityContextMenu.tsx +45 -17
  74. package/src/components/viewer/ExportChangesButton.tsx +195 -0
  75. package/src/components/viewer/ExportDialog.tsx +402 -0
  76. package/src/components/viewer/HierarchyPanel.tsx +1132 -218
  77. package/src/components/viewer/IDSPanel.tsx +661 -0
  78. package/src/components/viewer/KeyboardShortcutsDialog.tsx +245 -39
  79. package/src/components/viewer/MainToolbar.tsx +418 -94
  80. package/src/components/viewer/PropertiesPanel.tsx +1355 -91
  81. package/src/components/viewer/PropertyEditor.tsx +611 -0
  82. package/src/components/viewer/Section2DPanel.tsx +3313 -0
  83. package/src/components/viewer/SheetSetupPanel.tsx +502 -0
  84. package/src/components/viewer/StatusBar.tsx +27 -16
  85. package/src/components/viewer/TitleBlockEditor.tsx +437 -0
  86. package/src/components/viewer/ToolOverlays.tsx +935 -127
  87. package/src/components/viewer/ViewerLayout.tsx +40 -11
  88. package/src/components/viewer/Viewport.tsx +1276 -336
  89. package/src/components/viewer/ViewportContainer.tsx +554 -18
  90. package/src/components/viewer/ViewportOverlays.tsx +24 -7
  91. package/src/hooks/useBCF.ts +504 -0
  92. package/src/hooks/useIDS.ts +1065 -0
  93. package/src/hooks/useIfc.ts +1534 -205
  94. package/src/hooks/useIfcCache.ts +279 -0
  95. package/src/hooks/useKeyboardShortcuts.ts +50 -8
  96. package/src/hooks/useModelSelection.ts +61 -0
  97. package/src/hooks/useViewerSelectors.ts +218 -0
  98. package/src/hooks/useWebGPU.ts +80 -0
  99. package/src/index.css +265 -27
  100. package/src/lib/platform.ts +23 -0
  101. package/src/services/cacheService.ts +142 -0
  102. package/src/services/desktop-cache.ts +143 -0
  103. package/src/services/fs-cache.ts +212 -0
  104. package/src/services/ifc-cache.ts +14 -6
  105. package/src/store/constants.ts +85 -0
  106. package/src/store/index.ts +214 -0
  107. package/src/store/slices/bcfSlice.ts +372 -0
  108. package/src/store/slices/cameraSlice.ts +63 -0
  109. package/src/store/slices/dataSlice.test.ts +226 -0
  110. package/src/store/slices/dataSlice.ts +112 -0
  111. package/src/store/slices/drawing2DSlice.ts +340 -0
  112. package/src/store/slices/hoverSlice.ts +40 -0
  113. package/src/store/slices/idsSlice.ts +310 -0
  114. package/src/store/slices/loadingSlice.ts +33 -0
  115. package/src/store/slices/measurementSlice.test.ts +217 -0
  116. package/src/store/slices/measurementSlice.ts +293 -0
  117. package/src/store/slices/modelSlice.test.ts +271 -0
  118. package/src/store/slices/modelSlice.ts +211 -0
  119. package/src/store/slices/mutationSlice.ts +502 -0
  120. package/src/store/slices/sectionSlice.test.ts +125 -0
  121. package/src/store/slices/sectionSlice.ts +58 -0
  122. package/src/store/slices/selectionSlice.test.ts +286 -0
  123. package/src/store/slices/selectionSlice.ts +263 -0
  124. package/src/store/slices/sheetSlice.ts +565 -0
  125. package/src/store/slices/uiSlice.ts +58 -0
  126. package/src/store/slices/visibilitySlice.test.ts +304 -0
  127. package/src/store/slices/visibilitySlice.ts +277 -0
  128. package/src/store/types.test.ts +135 -0
  129. package/src/store/types.ts +248 -0
  130. package/src/store.ts +40 -515
  131. package/src/utils/ifcConfig.ts +82 -0
  132. package/src/utils/localParsingUtils.ts +287 -0
  133. package/src/utils/serverDataModel.ts +783 -0
  134. package/src/utils/spatialHierarchy.ts +283 -0
  135. package/src/utils/viewportUtils.ts +334 -0
  136. package/src/vite-env.d.ts +23 -0
  137. package/src/webgpu-types.d.ts +128 -0
  138. package/src-tauri/Cargo.toml +29 -0
  139. package/src-tauri/build.rs +7 -0
  140. package/src-tauri/capabilities/default.json +18 -0
  141. package/src-tauri/icons/128x128.png +0 -0
  142. package/src-tauri/icons/128x128@2x.png +0 -0
  143. package/src-tauri/icons/32x32.png +0 -0
  144. package/src-tauri/icons/Square107x107Logo.png +0 -0
  145. package/src-tauri/icons/Square142x142Logo.png +0 -0
  146. package/src-tauri/icons/Square150x150Logo.png +0 -0
  147. package/src-tauri/icons/Square284x284Logo.png +0 -0
  148. package/src-tauri/icons/Square30x30Logo.png +0 -0
  149. package/src-tauri/icons/Square310x310Logo.png +0 -0
  150. package/src-tauri/icons/Square44x44Logo.png +0 -0
  151. package/src-tauri/icons/Square71x71Logo.png +0 -0
  152. package/src-tauri/icons/Square89x89Logo.png +0 -0
  153. package/src-tauri/icons/StoreLogo.png +0 -0
  154. package/src-tauri/icons/icon.icns +0 -0
  155. package/src-tauri/icons/icon.ico +0 -0
  156. package/src-tauri/icons/icon.png +0 -0
  157. package/src-tauri/src/lib.rs +21 -0
  158. package/src-tauri/src/main.rs +10 -0
  159. package/src-tauri/tauri.conf.json +39 -0
  160. package/vite.config.ts +174 -26
  161. package/public/ifc-lite_bg.wasm +0 -0
  162. package/public/web-ifc.wasm +0 -0
  163. package/src/components/Viewport.tsx +0 -723
  164. package/src/components/viewer/BoxSelectionOverlay.tsx +0 -53
@@ -0,0 +1,80 @@
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
+ import { useState, useEffect } from 'react';
6
+
7
+ export interface WebGPUStatus {
8
+ supported: boolean;
9
+ checking: boolean;
10
+ reason: string | null;
11
+ }
12
+
13
+ /**
14
+ * Robust WebGPU detection hook.
15
+ *
16
+ * Detection method:
17
+ * 1. Check if navigator.gpu exists (basic API availability)
18
+ * 2. Attempt to request a GPU adapter (confirms actual hardware/driver support)
19
+ *
20
+ * This two-step check is necessary because:
21
+ * - Some browsers expose navigator.gpu but fail to provide an adapter
22
+ * - Software rendering may be available but unsuitable for our use case
23
+ * - Driver issues can prevent adapter creation even with WebGPU support
24
+ */
25
+ export function useWebGPU(): WebGPUStatus {
26
+ const [status, setStatus] = useState<WebGPUStatus>({
27
+ supported: false,
28
+ checking: true,
29
+ reason: null,
30
+ });
31
+
32
+ useEffect(() => {
33
+ async function checkWebGPUSupport() {
34
+ // Step 1: Check if WebGPU API is available
35
+ if (!navigator.gpu) {
36
+ setStatus({
37
+ supported: false,
38
+ checking: false,
39
+ reason: 'WebGPU API not available in this browser',
40
+ });
41
+ return;
42
+ }
43
+
44
+ try {
45
+ // Step 2: Try to get a GPU adapter
46
+ // This confirms actual hardware/driver support
47
+ const adapter = await navigator.gpu.requestAdapter();
48
+
49
+ if (!adapter) {
50
+ setStatus({
51
+ supported: false,
52
+ checking: false,
53
+ reason: 'No compatible GPU adapter found',
54
+ });
55
+ return;
56
+ }
57
+
58
+ // Optional: Check for required features if needed
59
+ // const features = adapter.features;
60
+ // const limits = adapter.limits;
61
+
62
+ setStatus({
63
+ supported: true,
64
+ checking: false,
65
+ reason: null,
66
+ });
67
+ } catch (error) {
68
+ setStatus({
69
+ supported: false,
70
+ checking: false,
71
+ reason: error instanceof Error ? error.message : 'Failed to initialize WebGPU',
72
+ });
73
+ }
74
+ }
75
+
76
+ checkWebGPUSupport();
77
+ }, []);
78
+
79
+ return status;
80
+ }
package/src/index.css CHANGED
@@ -4,6 +4,75 @@
4
4
 
5
5
  @import "tailwindcss";
6
6
 
7
+ /* ═══════════════════════════════════════════════════════════════════════════
8
+ TOKYO NIGHT THEME - Dark Stormy Cyberpunk Vibes
9
+ ═══════════════════════════════════════════════════════════════════════════ */
10
+
11
+ /* Tokyo Night Color Palette */
12
+ :root {
13
+ /* Storm - Deep backgrounds */
14
+ --tokyo-storm: #1a1b26;
15
+ --tokyo-night: #16161e;
16
+ --tokyo-bg-dark: #13131a;
17
+ --tokyo-bg-highlight: #1f2335;
18
+
19
+ /* Atmosphere */
20
+ --tokyo-terminal-black: #414868;
21
+ --tokyo-comment: #565f89;
22
+ --tokyo-dark3: #3b4261;
23
+
24
+ /* Foreground */
25
+ --tokyo-fg: #a9b1d6;
26
+ --tokyo-fg-dark: #7982a9;
27
+ --tokyo-fg-gutter: #363b54;
28
+
29
+ /* Neon accents */
30
+ --tokyo-blue: #7aa2f7;
31
+ --tokyo-cyan: #7dcfff;
32
+ --tokyo-magenta: #bb9af7;
33
+ --tokyo-purple: #9d7cd8;
34
+ --tokyo-orange: #ff9e64;
35
+ --tokyo-yellow: #e0af68;
36
+ --tokyo-green: #9ece6a;
37
+ --tokyo-teal: #73daca;
38
+ --tokyo-red: #f7768e;
39
+
40
+ /* Special */
41
+ --tokyo-git-add: #449dab;
42
+ --tokyo-git-change: #6183bb;
43
+ --tokyo-git-delete: #914c54;
44
+
45
+ /* Hierarchy panel - Light mode */
46
+ --hierarchy-selected-bg: #e4e4e7;
47
+ --hierarchy-selected-text: #18181b;
48
+ --hierarchy-text: #52525b;
49
+ --hierarchy-hover-bg: #f4f4f5;
50
+ }
51
+
52
+ /* Tabs - Light mode */
53
+ :root {
54
+ --tabs-bg: #f4f4f5;
55
+ --tabs-border: #e4e4e7;
56
+ --tab-text: #71717a;
57
+ --tab-active-bg: #ffffff;
58
+ --tab-active-text: #18181b;
59
+ }
60
+
61
+ .dark {
62
+ /* Hierarchy panel - Dark mode */
63
+ --hierarchy-selected-bg: #1f2335;
64
+ --hierarchy-selected-text: #a9b1d6;
65
+ --hierarchy-text: #7982a9;
66
+ --hierarchy-hover-bg: rgba(31, 35, 53, 0.5);
67
+
68
+ /* Tabs - Dark mode */
69
+ --tabs-bg: #1f2335;
70
+ --tabs-border: #3b4261;
71
+ --tab-text: #565f89;
72
+ --tab-active-bg: #16161e;
73
+ --tab-active-text: #a9b1d6;
74
+ }
75
+
7
76
  /* Custom theme configuration */
8
77
  @theme {
9
78
  --color-background: hsl(0 0% 100%);
@@ -12,7 +81,7 @@
12
81
  --color-card-foreground: hsl(240 10% 3.9%);
13
82
  --color-popover: hsl(0 0% 100%);
14
83
  --color-popover-foreground: hsl(240 10% 3.9%);
15
- --color-primary: hsl(217 91% 60%);
84
+ --color-primary: var(--tokyo-blue);
16
85
  --color-primary-foreground: hsl(0 0% 100%);
17
86
  --color-secondary: hsl(240 4.8% 95.9%);
18
87
  --color-secondary-foreground: hsl(240 5.9% 10%);
@@ -20,39 +89,40 @@
20
89
  --color-muted-foreground: hsl(240 3.8% 46.1%);
21
90
  --color-accent: hsl(240 4.8% 95.9%);
22
91
  --color-accent-foreground: hsl(240 5.9% 10%);
23
- --color-destructive: hsl(0 84.2% 60.2%);
92
+ --color-destructive: var(--tokyo-red);
24
93
  --color-destructive-foreground: hsl(0 0% 98%);
25
94
  --color-border: hsl(240 5.9% 90%);
26
95
  --color-input: hsl(240 5.9% 90%);
27
- --color-ring: hsl(217 91% 60%);
96
+ --color-ring: var(--tokyo-blue);
28
97
 
29
- --radius-sm: 0.25rem;
30
- --radius-md: 0.375rem;
31
- --radius-lg: 0.5rem;
32
- --radius-xl: 0.75rem;
98
+ --radius-sm: 0.125rem;
99
+ --radius-md: 0.25rem;
100
+ --radius-lg: 0.375rem;
101
+ --radius-xl: 0.5rem;
33
102
  }
34
103
 
35
- /* Dark mode colors */
104
+ /* Dark mode - Tokyo Night Storm */
105
+ :root.dark,
36
106
  .dark {
37
- --color-background: hsl(240 10% 3.9%);
38
- --color-foreground: hsl(0 0% 98%);
39
- --color-card: hsl(240 10% 3.9%);
40
- --color-card-foreground: hsl(0 0% 98%);
41
- --color-popover: hsl(240 10% 3.9%);
42
- --color-popover-foreground: hsl(0 0% 98%);
43
- --color-primary: hsl(217 91% 60%);
44
- --color-primary-foreground: hsl(0 0% 100%);
45
- --color-secondary: hsl(240 3.7% 15.9%);
46
- --color-secondary-foreground: hsl(0 0% 98%);
47
- --color-muted: hsl(240 3.7% 15.9%);
48
- --color-muted-foreground: hsl(240 5% 64.9%);
49
- --color-accent: hsl(240 3.7% 15.9%);
50
- --color-accent-foreground: hsl(0 0% 98%);
51
- --color-destructive: hsl(0 62.8% 30.6%);
52
- --color-destructive-foreground: hsl(0 0% 98%);
53
- --color-border: hsl(240 3.7% 15.9%);
54
- --color-input: hsl(240 3.7% 15.9%);
55
- --color-ring: hsl(217 91% 60%);
107
+ --color-background: var(--tokyo-storm) !important;
108
+ --color-foreground: var(--tokyo-fg) !important;
109
+ --color-card: var(--tokyo-night) !important;
110
+ --color-card-foreground: var(--tokyo-fg) !important;
111
+ --color-popover: var(--tokyo-night) !important;
112
+ --color-popover-foreground: var(--tokyo-fg) !important;
113
+ --color-primary: var(--tokyo-blue);
114
+ --color-primary-foreground: var(--tokyo-night);
115
+ --color-secondary: var(--tokyo-bg-highlight);
116
+ --color-secondary-foreground: var(--tokyo-fg);
117
+ --color-muted: var(--tokyo-bg-highlight);
118
+ --color-muted-foreground: var(--tokyo-comment);
119
+ --color-accent: var(--tokyo-bg-highlight);
120
+ --color-accent-foreground: var(--tokyo-fg);
121
+ --color-destructive: var(--tokyo-red);
122
+ --color-destructive-foreground: var(--tokyo-fg);
123
+ --color-border: var(--tokyo-dark3);
124
+ --color-input: var(--tokyo-bg-highlight);
125
+ --color-ring: var(--tokyo-blue);
56
126
  }
57
127
 
58
128
  /* Base styles */
@@ -66,6 +136,174 @@ body {
66
136
  font-feature-settings: "rlig" 1, "calt" 1;
67
137
  }
68
138
 
139
+ /* ═══════════════════════════════════════════════════════════════════════════
140
+ TOKYO NIGHT DARK MODE OVERRIDES
141
+ ═══════════════════════════════════════════════════════════════════════════ */
142
+
143
+ .dark body,
144
+ .dark #root {
145
+ background-color: var(--tokyo-storm) !important;
146
+ }
147
+
148
+ /* Panels and cards */
149
+ .dark .bg-white,
150
+ .dark .bg-card,
151
+ .dark .bg-background {
152
+ background-color: var(--tokyo-night) !important;
153
+ }
154
+
155
+ .dark .bg-zinc-50,
156
+ .dark .bg-zinc-100 {
157
+ background-color: var(--tokyo-bg-highlight) !important;
158
+ }
159
+
160
+ .dark .bg-zinc-900,
161
+ .dark .bg-zinc-950,
162
+ .dark .bg-black {
163
+ background-color: var(--tokyo-night) !important;
164
+ }
165
+
166
+ /* Borders */
167
+ .dark .border-zinc-200,
168
+ .dark .border-zinc-700,
169
+ .dark .border-zinc-800 {
170
+ border-color: var(--tokyo-dark3) !important;
171
+ }
172
+
173
+ .dark .border-zinc-900,
174
+ .dark .border-zinc-100 {
175
+ border-color: var(--tokyo-terminal-black) !important;
176
+ }
177
+
178
+ /* Text colors */
179
+ .dark .text-zinc-900,
180
+ .dark .text-zinc-100 {
181
+ color: var(--tokyo-fg) !important;
182
+ }
183
+
184
+ .dark .text-zinc-500,
185
+ .dark .text-zinc-400,
186
+ .dark .text-zinc-600 {
187
+ color: var(--tokyo-comment) !important;
188
+ }
189
+
190
+ .dark .text-zinc-200 {
191
+ color: var(--tokyo-fg-dark) !important;
192
+ }
193
+
194
+ /* Primary accent */
195
+ .dark .bg-primary {
196
+ background-color: var(--tokyo-blue) !important;
197
+ }
198
+
199
+ .dark .text-primary {
200
+ color: var(--tokyo-blue) !important;
201
+ }
202
+
203
+ .dark .border-primary {
204
+ border-color: var(--tokyo-blue) !important;
205
+ }
206
+
207
+ /* Clean shadows */
208
+ .dark .shadow-lg,
209
+ .dark .shadow-xl {
210
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) !important;
211
+ }
212
+
213
+ /* Selection highlight */
214
+ .dark ::selection {
215
+ background-color: var(--tokyo-bg-highlight);
216
+ color: var(--tokyo-cyan);
217
+ }
218
+
219
+ /* Scrollbar Tokyo Night style */
220
+ .dark .scrollbar-thin {
221
+ scrollbar-color: var(--tokyo-terminal-black) var(--tokyo-night);
222
+ }
223
+
224
+ .dark .scrollbar-thin::-webkit-scrollbar-thumb {
225
+ background-color: var(--tokyo-terminal-black);
226
+ }
227
+
228
+ .dark .scrollbar-thin::-webkit-scrollbar-thumb:hover {
229
+ background-color: var(--tokyo-comment);
230
+ }
231
+
232
+ /* Interactive elements */
233
+ .dark .hover\:bg-primary:hover {
234
+ background-color: var(--tokyo-blue) !important;
235
+ }
236
+
237
+ /* Force dark backgrounds on data-state active tabs */
238
+ .dark [data-state="active"] {
239
+ background-color: var(--tokyo-night) !important;
240
+ color: var(--tokyo-fg) !important;
241
+ }
242
+
243
+ /* Force dark hover backgrounds */
244
+ .dark .hover\:bg-zinc-50:hover,
245
+ .dark .hover\:bg-zinc-100:hover,
246
+ .dark .hover\:bg-zinc-200:hover,
247
+ .dark .hover\:bg-white:hover {
248
+ background-color: var(--tokyo-bg-highlight) !important;
249
+ }
250
+
251
+ /* Force dark selected/active backgrounds */
252
+ .dark .data-\[state\=active\]\:bg-white[data-state="active"],
253
+ .dark .data-\[state\=active\]\:bg-zinc-100[data-state="active"] {
254
+ background-color: var(--tokyo-night) !important;
255
+ }
256
+
257
+ /* Hierarchy panel hover */
258
+ .hierarchy-item:not(.selected):hover {
259
+ background-color: var(--hierarchy-hover-bg) !important;
260
+ }
261
+
262
+ /* Tab triggers */
263
+ .tab-trigger {
264
+ background-color: transparent !important;
265
+ color: var(--tab-text) !important;
266
+ }
267
+
268
+ .tab-trigger[data-state="active"] {
269
+ background-color: var(--tab-active-bg) !important;
270
+ color: var(--tab-active-text) !important;
271
+ font-weight: 700;
272
+ }
273
+
274
+ /* Quantity cards - cyan accent */
275
+ .dark .border-blue-200,
276
+ .dark .border-blue-800,
277
+ .dark .border-blue-900 {
278
+ border-color: var(--tokyo-cyan) !important;
279
+ border-opacity: 0.3;
280
+ }
281
+
282
+ .dark .text-blue-700,
283
+ .dark .text-blue-400 {
284
+ color: var(--tokyo-cyan) !important;
285
+ }
286
+
287
+ .dark .bg-blue-50\/20,
288
+ .dark .bg-blue-950\/20 {
289
+ background-color: rgba(125, 207, 255, 0.05) !important;
290
+ }
291
+
292
+ /* Success/Location - teal accent */
293
+ .dark .border-emerald-500\/30 {
294
+ border-color: rgba(115, 218, 202, 0.3) !important;
295
+ }
296
+
297
+ .dark .text-emerald-800,
298
+ .dark .text-emerald-400 {
299
+ color: var(--tokyo-teal) !important;
300
+ }
301
+
302
+ .dark .bg-emerald-50\/50,
303
+ .dark .bg-emerald-900\/10 {
304
+ background-color: rgba(115, 218, 202, 0.05) !important;
305
+ }
306
+
69
307
  /* Custom scrollbar */
70
308
  .scrollbar-thin {
71
309
  scrollbar-width: thin;
@@ -0,0 +1,23 @@
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
+ * Platform detection utilities for runtime bridge pattern
7
+ * Routes to WASM (browser) or native (Tauri desktop) implementations
8
+ */
9
+
10
+ /**
11
+ * Detects if running in Tauri desktop environment
12
+ */
13
+ export function isTauri(): boolean {
14
+ return typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
15
+ }
16
+
17
+ /**
18
+ * Platform-specific cache implementation
19
+ * Returns 'indexeddb' for browser, 'filesystem' for desktop
20
+ */
21
+ export function getCacheType(): 'indexeddb' | 'filesystem' {
22
+ return isTauri() ? 'filesystem' : 'indexeddb';
23
+ }
@@ -0,0 +1,142 @@
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
+ * Platform-agnostic cache service
7
+ * Dynamically loads the appropriate cache implementation based on platform:
8
+ * - Tauri (desktop): Uses native filesystem via desktop-cache.ts
9
+ * - Web: Uses IndexedDB via ifc-cache.ts
10
+ *
11
+ * Extracted from useIfc.ts for reusability and testability
12
+ */
13
+
14
+ import { isTauri } from '../utils/ifcConfig.js';
15
+
16
+ // ============================================================================
17
+ // Types
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Result from cache lookup
22
+ */
23
+ export interface CacheResult {
24
+ /** Serialized cache buffer containing data store and geometry */
25
+ buffer: ArrayBuffer;
26
+ /** Original IFC source file for on-demand property extraction */
27
+ sourceBuffer?: ArrayBuffer;
28
+ }
29
+
30
+ /**
31
+ * Function signature for getting cached data
32
+ */
33
+ export type GetCachedFn = (key: string) => Promise<CacheResult | null>;
34
+
35
+ /**
36
+ * Function signature for setting cached data
37
+ */
38
+ export type SetCachedFn = (
39
+ key: string,
40
+ data: ArrayBuffer,
41
+ fileName: string,
42
+ fileSize: number,
43
+ sourceBuffer?: ArrayBuffer
44
+ ) => Promise<void>;
45
+
46
+ /**
47
+ * Function signature for deleting cached data
48
+ */
49
+ export type DeleteCachedFn = (key: string) => Promise<void>;
50
+
51
+ /**
52
+ * Cache service interface
53
+ */
54
+ export interface ICacheService {
55
+ getCached: GetCachedFn;
56
+ setCached: SetCachedFn;
57
+ deleteCached: DeleteCachedFn;
58
+ }
59
+
60
+ // ============================================================================
61
+ // Service Singleton
62
+ // ============================================================================
63
+
64
+ /** Cached service instance - loaded once per session */
65
+ let cacheService: ICacheService | null = null;
66
+
67
+ /**
68
+ * Get the cache service for the current platform
69
+ * Lazily loads the appropriate implementation
70
+ */
71
+ export async function getCacheService(): Promise<ICacheService> {
72
+ if (cacheService) return cacheService;
73
+
74
+ if (isTauri) {
75
+ // Desktop: Use Tauri native filesystem
76
+ const mod = await import('./desktop-cache.js');
77
+ cacheService = {
78
+ getCached: mod.getCached,
79
+ setCached: mod.setCached,
80
+ deleteCached: mod.deleteCached,
81
+ };
82
+ } else {
83
+ // Web: Use IndexedDB
84
+ const mod = await import('./ifc-cache.js');
85
+ cacheService = {
86
+ getCached: mod.getCached,
87
+ setCached: mod.setCached,
88
+ deleteCached: mod.deleteCached,
89
+ };
90
+ }
91
+
92
+ return cacheService;
93
+ }
94
+
95
+ // ============================================================================
96
+ // Convenience Functions
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Get cached data by key
101
+ * @param key - Cache key (typically xxhash64 of source file)
102
+ * @returns Cache result with buffer and optional source, or null if not found
103
+ */
104
+ export async function getCached(key: string): Promise<CacheResult | null> {
105
+ const service = await getCacheService();
106
+ return service.getCached(key);
107
+ }
108
+
109
+ /**
110
+ * Store data in cache
111
+ * @param key - Cache key (typically xxhash64 of source file)
112
+ * @param data - Serialized cache buffer
113
+ * @param fileName - Original file name for logging
114
+ * @param fileSize - Original file size for logging
115
+ * @param sourceBuffer - Optional source file for on-demand extraction
116
+ */
117
+ export async function setCached(
118
+ key: string,
119
+ data: ArrayBuffer,
120
+ fileName: string,
121
+ fileSize: number,
122
+ sourceBuffer?: ArrayBuffer
123
+ ): Promise<void> {
124
+ const service = await getCacheService();
125
+ return service.setCached(key, data, fileName, fileSize, sourceBuffer);
126
+ }
127
+
128
+ /**
129
+ * Delete a cache entry by key
130
+ * @param key - Cache key to delete
131
+ */
132
+ export async function deleteCached(key: string): Promise<void> {
133
+ const service = await getCacheService();
134
+ return service.deleteCached(key);
135
+ }
136
+
137
+ /**
138
+ * Reset the cache service singleton (useful for testing)
139
+ */
140
+ export function resetCacheService(): void {
141
+ cacheService = null;
142
+ }