@geode/opengeodeweb-front 10.34.0-rc.9 → 10.34.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 (282) hide show
  1. package/{server → .build/server}/utils/app_config.js +10 -9
  2. package/{server → .build/server}/utils/cleanup.js +31 -17
  3. package/{server → .build/server}/utils/cloud.js +1 -4
  4. package/{server → .build/server}/utils/microservices.js +21 -9
  5. package/{server → .build/server}/utils/path.js +4 -4
  6. package/{server → .build/server}/utils/scripts.js +9 -9
  7. package/.build/server/utils/server_config.js +51 -0
  8. package/.build/server/utils/stream.js +6 -0
  9. package/{server → .build/server}/utils/ws_client.js +18 -3
  10. package/{shared → .build/shared}/scripts.js +10 -10
  11. package/.build/shared/utils/call_raw.js +31 -0
  12. package/{shared → .build/shared}/utils/call_schema.js +2 -2
  13. package/.build/shared/utils/fetch_raw.js +52 -0
  14. package/{shared → .build/shared}/utils/fetch_schema.js +2 -2
  15. package/{shared → .build/shared}/utils/file.js +3 -1
  16. package/{shared → .build/shared}/utils/response_handlers/load.js +10 -7
  17. package/{shared → .build/shared}/utils/validate_schema.js +1 -1
  18. package/app/components/ActionButton.vue +6 -6
  19. package/app/components/CameraManager/List.vue +5 -5
  20. package/app/components/CameraManager/Saver.vue +3 -5
  21. package/app/components/CameraManager.vue +6 -4
  22. package/app/components/CameraOrientation.vue +12 -12
  23. package/app/components/ClippingPlaneCard.vue +4 -2
  24. package/app/components/ClippingPlanes.vue +8 -8
  25. package/app/components/CrsSelector.vue +9 -10
  26. package/app/components/DeleteDialog.vue +4 -2
  27. package/app/components/DragAndDrop.vue +10 -12
  28. package/app/components/DragAndDropInternal/DragAndDropInline.vue +4 -2
  29. package/app/components/ExtensionSelector.vue +4 -4
  30. package/app/components/FeedBack/ErrorBanner.vue +3 -1
  31. package/app/components/FeedBack/Snackers.vue +4 -3
  32. package/app/components/FileSelector.vue +10 -8
  33. package/app/components/FileUploader.vue +11 -11
  34. package/app/components/GlassCard.vue +1 -1
  35. package/app/components/HybridRenderingView.vue +7 -5
  36. package/app/components/HybridViewerTooltip.vue +7 -7
  37. package/app/components/Launcher.vue +1 -1
  38. package/app/components/Loading/EcoMessages.vue +9 -3
  39. package/app/components/Loading.vue +2 -2
  40. package/app/components/MissingFilesSelector.vue +12 -10
  41. package/app/components/ObjectSelector.vue +10 -9
  42. package/app/components/PackagesVersions.vue +6 -5
  43. package/app/components/Recaptcha.vue +3 -3
  44. package/app/components/RemoteRenderingView.vue +6 -7
  45. package/app/components/Ruler.vue +3 -3
  46. package/app/components/Screenshot.vue +6 -6
  47. package/app/components/SearchBar.vue +4 -2
  48. package/app/components/ShrinkFilter.vue +7 -7
  49. package/app/components/Step.vue +6 -5
  50. package/app/components/Stepper.vue +4 -3
  51. package/app/components/ToolPanel.vue +6 -3
  52. package/app/components/ViewToolbar.vue +25 -28
  53. package/app/components/Viewer/ContextMenu/CenterButton.vue +7 -5
  54. package/app/components/Viewer/ContextMenu/CircularItems.vue +3 -3
  55. package/app/components/Viewer/ContextMenu/ContextMenu.vue +22 -19
  56. package/app/components/Viewer/ContextMenu/ContextMenuItem.vue +10 -8
  57. package/app/components/Viewer/ContextMenu/InfoCard.vue +11 -12
  58. package/app/components/Viewer/EdgedCurve/SpecificEdgesOptions.vue +13 -17
  59. package/app/components/Viewer/Generic/Mesh/CellsOptions.vue +11 -15
  60. package/app/components/Viewer/Generic/Mesh/EdgesOptions.vue +12 -16
  61. package/app/components/Viewer/Generic/Mesh/PointsOptions.vue +8 -12
  62. package/app/components/Viewer/Generic/Mesh/PolygonsOptions.vue +11 -15
  63. package/app/components/Viewer/Generic/Mesh/PolyhedraOptions.vue +11 -15
  64. package/app/components/Viewer/Generic/Model/BlocksOptions.vue +67 -38
  65. package/app/components/Viewer/Generic/Model/CornersOptions.vue +53 -23
  66. package/app/components/Viewer/Generic/Model/EdgesOptions.vue +1 -3
  67. package/app/components/Viewer/Generic/Model/LinesOptions.vue +67 -38
  68. package/app/components/Viewer/Generic/Model/ModelStyleCard.vue +31 -15
  69. package/app/components/Viewer/Generic/Model/ModelStyleOptions.vue +2 -3
  70. package/app/components/Viewer/Generic/Model/PointsOptions.vue +6 -6
  71. package/app/components/Viewer/Generic/Model/SurfacesOptions.vue +69 -38
  72. package/app/components/Viewer/ObjectTree/Base/CommonTreeView.vue +64 -46
  73. package/app/components/Viewer/ObjectTree/Base/Controls.vue +5 -5
  74. package/app/components/Viewer/ObjectTree/Base/ItemLabel.vue +13 -9
  75. package/app/components/Viewer/ObjectTree/Base/StickyHeader.vue +5 -5
  76. package/app/components/Viewer/ObjectTree/Base/TreeRow.vue +10 -10
  77. package/app/components/Viewer/ObjectTree/Box.vue +6 -4
  78. package/app/components/Viewer/ObjectTree/Layout.vue +23 -21
  79. package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +14 -14
  80. package/app/components/Viewer/ObjectTree/Views/{ModelCollections.vue → ModelTree.vue} +59 -24
  81. package/app/components/Viewer/Options/AttributeColorBar.vue +4 -2
  82. package/app/components/Viewer/Options/AttributeRangeSelector.vue +4 -2
  83. package/app/components/Viewer/Options/AttributeSelector.vue +7 -9
  84. package/app/components/Viewer/Options/ColorMapList.vue +15 -13
  85. package/app/components/Viewer/Options/ColorMapPicker.vue +4 -4
  86. package/app/components/Viewer/Options/ColorPicker.vue +223 -41
  87. package/app/components/Viewer/Options/ColoringTypeSelector.vue +16 -16
  88. package/app/components/Viewer/Options/ColormapQuickPicker.vue +7 -5
  89. package/app/components/Viewer/Options/OptionsSection.vue +1 -1
  90. package/app/components/Viewer/Options/TextureItem.vue +8 -6
  91. package/app/components/Viewer/Options/TexturesSelector.vue +1 -1
  92. package/app/components/Viewer/OverlappingObjectsPicker.vue +8 -9
  93. package/app/components/Viewer/PointSet/SpecificPointsOptions.vue +2 -6
  94. package/app/components/Viewer/Solid/SpecificPolyhedraOptions.vue +12 -16
  95. package/app/components/Viewer/Surface/PolygonsOptions.vue +12 -16
  96. package/app/components/Viewer/Ui.vue +2 -2
  97. package/app/components/Wrapper.vue +1 -4
  98. package/app/components/ZScaling.vue +5 -3
  99. package/app/components/csv-preview/CsvPreviewer.vue +38 -37
  100. package/app/components/csv-preview/CsvSettings.vue +6 -4
  101. package/app/components/csv-preview/CsvTable.vue +1 -1
  102. package/app/composables/batch_style.ts +5 -6
  103. package/app/composables/clipping_planes_widget.ts +73 -181
  104. package/app/composables/clipping_planes_widget_entries.ts +189 -0
  105. package/app/composables/copy_to_clipboard.ts +7 -0
  106. package/app/composables/global_attribute_style.ts +163 -83
  107. package/app/composables/hover_highlight.ts +46 -14
  108. package/app/composables/model_collections.ts +26 -13
  109. package/app/composables/model_components.ts +26 -13
  110. package/app/composables/project_manager.ts +76 -19
  111. package/app/composables/responsive_middle_truncate.ts +13 -13
  112. package/app/composables/stepper_tree.ts +34 -11
  113. package/app/composables/tree_filter.ts +66 -44
  114. package/app/composables/tree_keyboard_nav.ts +25 -11
  115. package/app/composables/tree_scroll.ts +44 -14
  116. package/app/composables/use_adaptive_styles.ts +66 -23
  117. package/app/composables/use_overlapping_picker.ts +66 -16
  118. package/app/composables/use_quick_colormap.ts +16 -5
  119. package/app/composables/virtual_tree.ts +75 -43
  120. package/app/composables/vtk_implicit_plane_widget.d.ts +2 -0
  121. package/app/plugins/auto_store_register.ts +2 -7
  122. package/app/plugins/click_outside_ignore_snackbars.client.ts +39 -0
  123. package/app/stores/app.ts +57 -192
  124. package/app/stores/app_helpers/extension.ts +179 -0
  125. package/app/stores/back.ts +37 -21
  126. package/app/stores/camera_manager.ts +22 -20
  127. package/app/stores/cloud.ts +17 -13
  128. package/app/stores/data.ts +88 -47
  129. package/app/stores/data_helpers/collections.ts +36 -38
  130. package/app/stores/data_helpers/mesh.ts +45 -36
  131. package/app/stores/data_style.ts +50 -28
  132. package/app/stores/feedback.ts +6 -6
  133. package/app/stores/hybrid_viewer.ts +41 -52
  134. package/app/stores/infra.ts +39 -37
  135. package/app/stores/menu.ts +23 -14
  136. package/app/stores/treeview.ts +72 -42
  137. package/app/stores/viewer.ts +39 -27
  138. package/app/utils/attributes.ts +3 -3
  139. package/app/utils/clipping_planes.ts +111 -14
  140. package/app/utils/color_picker.ts +57 -0
  141. package/app/utils/colormap.ts +58 -35
  142. package/app/utils/default_styles/index.ts +2 -2
  143. package/app/utils/default_styles/meshes.ts +107 -13
  144. package/app/utils/default_styles/models.ts +83 -14
  145. package/app/utils/extension.ts +47 -20
  146. package/app/utils/import_workflow.ts +21 -13
  147. package/app/utils/name_cleaner.ts +1 -1
  148. package/app/utils/string.ts +1 -1
  149. package/app/utils/treeview.ts +2 -2
  150. package/internal/database/base_database.ts +7 -3
  151. package/internal/database/database.ts +36 -26
  152. package/internal/database/extended_database.ts +1 -1
  153. package/internal/stores/data_style/mesh/cells/cell.ts +70 -34
  154. package/internal/stores/data_style/mesh/cells/color.ts +12 -6
  155. package/internal/stores/data_style/mesh/cells/common.ts +18 -8
  156. package/internal/stores/data_style/mesh/cells/index.ts +122 -42
  157. package/internal/stores/data_style/mesh/cells/textures.ts +12 -6
  158. package/internal/stores/data_style/mesh/cells/vertex.ts +75 -34
  159. package/internal/stores/data_style/mesh/cells/visibility.ts +15 -4
  160. package/internal/stores/data_style/mesh/edges/color.ts +12 -6
  161. package/internal/stores/data_style/mesh/edges/common.ts +14 -6
  162. package/internal/stores/data_style/mesh/edges/edge.ts +71 -39
  163. package/internal/stores/data_style/mesh/edges/index.ts +117 -42
  164. package/internal/stores/data_style/mesh/edges/vertex.ts +76 -39
  165. package/internal/stores/data_style/mesh/edges/visibility.ts +15 -4
  166. package/internal/stores/data_style/mesh/edges/width.ts +12 -4
  167. package/internal/stores/data_style/mesh/index.ts +30 -11
  168. package/internal/stores/data_style/mesh/points/color.ts +12 -6
  169. package/internal/stores/data_style/mesh/points/common.ts +21 -8
  170. package/internal/stores/data_style/mesh/points/index.ts +101 -50
  171. package/internal/stores/data_style/mesh/points/size.ts +12 -4
  172. package/internal/stores/data_style/mesh/points/vertex.ts +75 -34
  173. package/internal/stores/data_style/mesh/points/visibility.ts +17 -7
  174. package/internal/stores/data_style/mesh/polygons/color.ts +12 -6
  175. package/internal/stores/data_style/mesh/polygons/common.ts +23 -6
  176. package/internal/stores/data_style/mesh/polygons/index.ts +130 -70
  177. package/internal/stores/data_style/mesh/polygons/polygon.ts +77 -43
  178. package/internal/stores/data_style/mesh/polygons/textures.ts +12 -6
  179. package/internal/stores/data_style/mesh/polygons/vertex.ts +79 -39
  180. package/internal/stores/data_style/mesh/polygons/visibility.ts +16 -6
  181. package/internal/stores/data_style/mesh/polyhedra/color.ts +12 -6
  182. package/internal/stores/data_style/mesh/polyhedra/common.ts +14 -6
  183. package/internal/stores/data_style/mesh/polyhedra/index.ts +115 -56
  184. package/internal/stores/data_style/mesh/polyhedra/polyhedron.ts +80 -39
  185. package/internal/stores/data_style/mesh/polyhedra/vertex.ts +79 -39
  186. package/internal/stores/data_style/mesh/polyhedra/visibility.ts +16 -6
  187. package/internal/stores/data_style/model/blocks/color.ts +43 -13
  188. package/internal/stores/data_style/model/blocks/common.ts +21 -6
  189. package/internal/stores/data_style/model/blocks/index.ts +89 -59
  190. package/internal/stores/data_style/model/blocks/polyhedron.ts +101 -44
  191. package/internal/stores/data_style/model/blocks/vertex.ts +107 -42
  192. package/internal/stores/data_style/model/blocks/visibility.ts +17 -4
  193. package/internal/stores/data_style/model/color.ts +171 -83
  194. package/internal/stores/data_style/model/common.ts +70 -21
  195. package/internal/stores/data_style/model/corners/color.ts +43 -11
  196. package/internal/stores/data_style/model/corners/common.ts +25 -6
  197. package/internal/stores/data_style/model/corners/index.ts +66 -41
  198. package/internal/stores/data_style/model/corners/vertex.ts +105 -74
  199. package/internal/stores/data_style/model/corners/visibility.ts +19 -4
  200. package/internal/stores/data_style/model/edges/common.ts +12 -4
  201. package/internal/stores/data_style/model/edges/index.ts +2 -1
  202. package/internal/stores/data_style/model/edges/visibility.ts +24 -7
  203. package/internal/stores/data_style/model/index.ts +24 -11
  204. package/internal/stores/data_style/model/lines/color.ts +41 -11
  205. package/internal/stores/data_style/model/lines/common.ts +17 -6
  206. package/internal/stores/data_style/model/lines/edge.ts +105 -42
  207. package/internal/stores/data_style/model/lines/index.ts +86 -59
  208. package/internal/stores/data_style/model/lines/vertex.ts +105 -42
  209. package/internal/stores/data_style/model/lines/visibility.ts +17 -4
  210. package/internal/stores/data_style/model/points/common.ts +8 -4
  211. package/internal/stores/data_style/model/points/index.ts +10 -3
  212. package/internal/stores/data_style/model/points/size.ts +12 -4
  213. package/internal/stores/data_style/model/points/visibility.ts +16 -6
  214. package/internal/stores/data_style/model/selection.ts +80 -58
  215. package/internal/stores/data_style/model/surfaces/color.ts +41 -11
  216. package/internal/stores/data_style/model/surfaces/common.ts +23 -6
  217. package/internal/stores/data_style/model/surfaces/index.ts +89 -59
  218. package/internal/stores/data_style/model/surfaces/polygon.ts +107 -67
  219. package/internal/stores/data_style/model/surfaces/vertex.ts +111 -67
  220. package/internal/stores/data_style/model/surfaces/visibility.ts +18 -5
  221. package/internal/stores/data_style/model/visibility.ts +82 -20
  222. package/internal/stores/data_style/state.ts +77 -65
  223. package/internal/stores/hybrid_viewer/brightness.ts +49 -31
  224. package/internal/stores/hybrid_viewer/camera.ts +191 -177
  225. package/internal/stores/hybrid_viewer/camera_animation.ts +11 -10
  226. package/internal/stores/hybrid_viewer/core.ts +64 -0
  227. package/internal/stores/hybrid_viewer/filters.ts +10 -16
  228. package/internal/stores/hybrid_viewer/highlight.ts +91 -130
  229. package/internal/stores/hybrid_viewer/ruler.ts +24 -15
  230. package/internal/stores/hybrid_viewer/scene.ts +96 -117
  231. package/internal/stores/hybrid_viewer/viewport.ts +74 -65
  232. package/internal/stores/hybrid_viewer/vtk_types.ts +13 -29
  233. package/internal/utils/api_fetch.ts +54 -23
  234. package/internal/utils/upload_file.ts +74 -53
  235. package/internal/utils/viewer_call.ts +22 -13
  236. package/internal/utils/ws_client.ts +24 -13
  237. package/nuxt.config.ts +58 -1
  238. package/package.json +20 -8
  239. package/server/api/cloud/extensions/run.post.ts +6 -10
  240. package/server/api/local/app/project_folder_path.post.ts +4 -4
  241. package/server/api/local/app/run_back.post.ts +5 -7
  242. package/server/api/local/app/run_viewer.post.ts +5 -7
  243. package/server/api/local/extensions/kill.post.ts +4 -3
  244. package/server/api/local/extensions/run.post.ts +7 -10
  245. package/server/api/local/extensions/upload.put.ts +44 -54
  246. package/server/api/microservice/app/get_is_app_ready.get.ts +4 -5
  247. package/server/api/microservice/app/set_app_base_url.post.ts +5 -6
  248. package/server/api/microservice/app/set_back_base_url.post.ts +5 -6
  249. package/server/api/microservice/app/set_is_app_ready.post.ts +5 -6
  250. package/server/api/microservice/app/set_viewer_base_url.post.ts +5 -6
  251. package/server/api/microservice/extensions/download.post.ts +5 -4
  252. package/server/api/serverless/run_cloud.post.ts +53 -11
  253. package/server/utils/app_config.ts +29 -22
  254. package/server/utils/cleanup.ts +60 -30
  255. package/server/utils/cloud.ts +9 -8
  256. package/server/utils/extension.ts +23 -9
  257. package/server/utils/microservices.ts +33 -24
  258. package/server/utils/path.ts +4 -4
  259. package/server/utils/scripts.ts +26 -23
  260. package/server/utils/server.ts +2 -2
  261. package/server/utils/server_config.ts +41 -27
  262. package/server/utils/stream.ts +6 -0
  263. package/server/utils/ws_client.ts +42 -8
  264. package/shared/scripts.ts +15 -10
  265. package/shared/utils/call_raw.ts +23 -16
  266. package/shared/utils/call_schema.ts +5 -8
  267. package/shared/utils/fetch_raw.ts +87 -47
  268. package/shared/utils/fetch_schema.ts +6 -9
  269. package/shared/utils/file.ts +8 -2
  270. package/shared/utils/response_handlers/load.ts +26 -11
  271. package/shared/utils/types.ts +9 -9
  272. package/shared/utils/validate_schema.ts +1 -1
  273. package/app/components/Viewer/ObjectTree/Views/ModelComponents.vue +0 -276
  274. package/server/types/vendor.d.ts +0 -64
  275. package/server/utils/server_config.js +0 -43
  276. package/shared/utils/call_raw.js +0 -30
  277. package/shared/utils/fetch_raw.js +0 -40
  278. /package/{opengeodeweb_front_schemas.js → .build/opengeodeweb_front_schemas.js} +0 -0
  279. /package/{server → .build/server}/utils/server.js +0 -0
  280. /package/{shared → .build/shared}/app_mode.js +0 -0
  281. /package/{shared → .build/shared}/utils/parse_boolean.js +0 -0
  282. /package/{shared → .build/shared}/utils/types.js +0 -0
@@ -48,6 +48,17 @@ interface MenuMetaData {
48
48
  [key: string]: unknown;
49
49
  }
50
50
 
51
+ interface OpenMenuOptions {
52
+ readonly id: string;
53
+ readonly x: number | undefined;
54
+ readonly y: number | undefined;
55
+ readonly width: number;
56
+ readonly height: number;
57
+ readonly top: number;
58
+ readonly left: number;
59
+ readonly meta_data: MenuMetaData | undefined;
60
+ }
61
+
51
62
  const PointSet_menu = [PointSetPointsOptions];
52
63
 
53
64
  const EdgedCurve_menu = [EdgedCurvePointsOptions, EdgedCurveEdgesOptions];
@@ -146,10 +157,16 @@ export const useMenuStore = defineStore("menu", () => {
146
157
  objectType: string | undefined,
147
158
  geodeObject: string | undefined,
148
159
  ): MenuItems {
149
- if (!objectType || !geodeObject || !menus.value[objectType]) {
160
+ if (
161
+ objectType === undefined ||
162
+ objectType === "" ||
163
+ geodeObject === undefined ||
164
+ geodeObject === "" ||
165
+ !menus.value[objectType]
166
+ ) {
150
167
  return [];
151
168
  }
152
- return menus.value[objectType]?.[geodeObject] || [];
169
+ return menus.value[objectType]?.[geodeObject] ?? [];
153
170
  }
154
171
 
155
172
  function closeMenu(): void {
@@ -161,17 +178,9 @@ export const useMenuStore = defineStore("menu", () => {
161
178
  display_menu.value = false;
162
179
  }
163
180
 
164
- async function openMenu(
165
- id: string,
166
- x: number | undefined,
167
- y: number | undefined,
168
- width: number,
169
- height: number,
170
- top: number,
171
- left: number,
172
- meta_data: MenuMetaData | undefined,
173
- ): Promise<void> {
174
- await closeMenu();
181
+ function openMenu(options: OpenMenuOptions): void {
182
+ const { id, x, y, width, height, top, left, meta_data } = options;
183
+ closeMenu();
175
184
 
176
185
  if (meta_data) {
177
186
  const items = getMenuItems(meta_data.viewer_type, meta_data.geode_object_type);
@@ -181,7 +190,7 @@ export const useMenuStore = defineStore("menu", () => {
181
190
  }
182
191
 
183
192
  current_id.value = id;
184
- current_meta_data.value = meta_data || {};
193
+ current_meta_data.value = meta_data ?? {};
185
194
 
186
195
  if (x !== undefined && y !== undefined) {
187
196
  menuX.value = x;
@@ -55,6 +55,22 @@ function defaultOpenedViews(): OpenedView[] {
55
55
  return [{ type: "object", id: "main", title: "Objects", scrollTop: 0, opened: [] }];
56
56
  }
57
57
 
58
+ type ReadonlyOpenedView = Omit<OpenedView, "opened"> & {
59
+ readonly opened: readonly string[];
60
+ };
61
+ type ReadonlyTreeviewGroup = Omit<TreeviewGroup, "children"> & {
62
+ readonly children: readonly TreeviewChild[];
63
+ };
64
+ interface ReadonlyTreeviewSnapshot {
65
+ opened_views?: readonly ReadonlyOpenedView[];
66
+ panelWidth?: number;
67
+ additionalPanelWidth?: number;
68
+ rowHeights?: readonly number[];
69
+ selectionIds?: readonly string[];
70
+ selection?: readonly (string | { id: string })[];
71
+ }
72
+
73
+ // oxlint-disable-next-line max-lines-per-function, max-statements
58
74
  export const useTreeviewStore = defineStore("treeview", () => {
59
75
  const items = ref<TreeviewGroup[]>([]);
60
76
  const selection = ref<string[]>([]);
@@ -66,10 +82,12 @@ export const useTreeviewStore = defineStore("treeview", () => {
66
82
  const pendingSelectionIds = ref<string[]>([]);
67
83
  const rowHeights = ref<number[]>([]);
68
84
 
69
- const treeview_config_db = database.treeview_config as unknown as Table<
70
- TreeviewConfigRecord,
71
- string
72
- >;
85
+ // The database's table map is dynamically assembled at runtime (see internal/database/database.ts), so its exported type is a loose `{}`; cast it to the shape it actually has at runtime rather than widening every call site.
86
+ type DatabaseTables = Record<string, unknown>;
87
+ // oxlint-disable-next-line no-unsafe-type-assertion -- database's table map is dynamically typed at runtime; see comment above.
88
+ const typedDatabase = database as unknown as DatabaseTables;
89
+ // oxlint-disable-next-line no-unsafe-type-assertion -- database's table map is dynamically typed at runtime; see comment above.
90
+ const treeview_config_db = typedDatabase.treeview_config as Table<TreeviewConfigRecord, string>;
73
91
 
74
92
  async function loadConfig(): Promise<void> {
75
93
  try {
@@ -77,10 +95,10 @@ export const useTreeviewStore = defineStore("treeview", () => {
77
95
  if (config?.opened_views) {
78
96
  opened_views.value = config.opened_views;
79
97
  }
80
- if (config?.panelWidth) {
98
+ if (config?.panelWidth !== undefined && config.panelWidth !== 0) {
81
99
  panelWidth.value = config.panelWidth;
82
100
  }
83
- if (config?.additionalPanelWidth) {
101
+ if (config?.additionalPanelWidth !== undefined && config.additionalPanelWidth !== 0) {
84
102
  additionalPanelWidth.value = config.additionalPanelWidth;
85
103
  }
86
104
  if (config?.selectionIds) {
@@ -90,21 +108,24 @@ export const useTreeviewStore = defineStore("treeview", () => {
90
108
  rowHeights.value = config.rowHeights;
91
109
  }
92
110
  } catch (error) {
111
+ // oxlint-disable-next-line no-console -- surfaces persistence failures during local development.
93
112
  console.error("Failed to load treeview config:", error);
94
113
  }
95
114
  }
115
+ // oxlint-disable-next-line typescript/no-floating-promises -- loadConfig catches its own errors internally.
96
116
  loadConfig();
97
117
 
98
118
  watch(
99
119
  [opened_views, panelWidth, additionalPanelWidth, selection, rowHeights],
100
120
  () => {
101
- // oxlint-disable-next-line unicorn/prefer-structured-clone
102
- const clean_opened_views = JSON.parse(JSON.stringify(opened_views.value));
103
- // oxlint-disable-next-line unicorn/prefer-structured-clone
104
- const clean_selectionIds = JSON.parse(JSON.stringify(selection.value));
105
- // oxlint-disable-next-line unicorn/prefer-structured-clone
106
- const clean_rowHeights = JSON.parse(JSON.stringify(rowHeights.value));
107
-
121
+ // oxlint-disable-next-line unicorn/prefer-structured-clone, no-unsafe-type-assertion -- JSON round-trip of a known-shape ref; result matches the source type.
122
+ const clean_opened_views = JSON.parse(JSON.stringify(opened_views.value)) as OpenedView[];
123
+ // oxlint-disable-next-line unicorn/prefer-structured-clone, no-unsafe-type-assertion -- JSON round-trip of a known-shape ref; result matches the source type.
124
+ const clean_selectionIds = JSON.parse(JSON.stringify(selection.value)) as string[];
125
+ // oxlint-disable-next-line unicorn/prefer-structured-clone, no-unsafe-type-assertion -- JSON round-trip of a known-shape ref; result matches the source type.
126
+ const clean_rowHeights = JSON.parse(JSON.stringify(rowHeights.value)) as number[];
127
+
128
+ // oxlint-disable-next-line typescript/no-floating-promises -- best-effort persistence; failures aren't actionable here.
108
129
  treeview_config_db.put({
109
130
  id: "main",
110
131
  opened_views: clean_opened_views,
@@ -118,14 +139,14 @@ export const useTreeviewStore = defineStore("treeview", () => {
118
139
  );
119
140
 
120
141
  function closeView(id: string): void {
121
- opened_views.value = opened_views.value.filter((view) => view.id !== id);
142
+ opened_views.value = opened_views.value.filter((view: ReadonlyOpenedView) => view.id !== id);
122
143
  }
123
144
 
124
- watch(selection, (current, previous) => {
145
+ watch(selection, (current: readonly string[], previous: readonly string[]) => {
125
146
  const { removed } = compareSelections(current, previous);
126
147
  for (const id of removed) {
127
148
  const index = opened_views.value.findIndex(
128
- (view) => view.type === "component" && view.id === id,
149
+ (view: ReadonlyOpenedView) => view.type === "component" && view.id === id,
129
150
  );
130
151
  if (index !== -1) {
131
152
  closeView(id);
@@ -134,7 +155,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
134
155
  });
135
156
 
136
157
  function toggleView(id: string): void {
137
- const index = opened_views.value.findIndex((view) => view.id === id);
158
+ const index = opened_views.value.findIndex((view: ReadonlyOpenedView) => view.id === id);
138
159
  if (index !== -1) {
139
160
  closeView(id);
140
161
  } else if (id === "main") {
@@ -170,7 +191,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
170
191
  if (!found) {
171
192
  items.value.push({ id: geodeObjectType, title: geodeObjectType, children: [child] });
172
193
  const sort_options = { numeric: true, sensitivity: "base" as const };
173
- items.value.sort((groupA, groupB) =>
194
+ items.value.sort((groupA: ReadonlyTreeviewGroup, groupB: ReadonlyTreeviewGroup) =>
174
195
  groupA.title.localeCompare(groupB.title, undefined, sort_options),
175
196
  );
176
197
  }
@@ -199,12 +220,13 @@ export const useTreeviewStore = defineStore("treeview", () => {
199
220
  id: string,
200
221
  title: string | undefined,
201
222
  geodeObjectType: string,
202
- viewType: string = "model_components",
203
- ) {
223
+ viewType = "model_components",
224
+ ): void {
204
225
  const viewId = `${id}_${viewType}`;
205
- const index = opened_views.value.findIndex((view) => view.id === viewId);
226
+ const index = opened_views.value.findIndex((view: ReadonlyOpenedView) => view.id === viewId);
206
227
  if (index !== -1) {
207
- return closeView(viewId);
228
+ closeView(viewId);
229
+ return;
208
230
  }
209
231
  additionalPanelWidth.value = panelWidth.value;
210
232
  opened_views.value.push({
@@ -212,7 +234,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
212
234
  id: viewId,
213
235
  modelId: id,
214
236
  viewType,
215
- title: title || id,
237
+ title: title ?? id,
216
238
  geode_object_type: geodeObjectType,
217
239
  scrollTop: 0,
218
240
  opened: [],
@@ -228,17 +250,19 @@ export const useTreeviewStore = defineStore("treeview", () => {
228
250
  }
229
251
  }
230
252
 
231
- function importStores(snapshot: TreeviewSnapshot | undefined): void {
232
- opened_views.value = snapshot?.opened_views || defaultOpenedViews();
233
- panelWidth.value = snapshot?.panelWidth || PANEL_WIDTH;
234
- additionalPanelWidth.value = snapshot?.additionalPanelWidth || PANEL_WIDTH;
235
- rowHeights.value = snapshot?.rowHeights || [];
236
- pendingSelectionIds.value =
237
- snapshot?.selectionIds ||
238
- (snapshot?.selection || []).map((selectionItem) =>
239
- typeof selectionItem === "string" ? selectionItem : selectionItem.id,
240
- ) ||
241
- [];
253
+ function importStores(snapshot: ReadonlyTreeviewSnapshot | undefined): void {
254
+ opened_views.value = snapshot?.opened_views
255
+ ? // oxlint-disable-next-line no-unsafe-type-assertion -- narrowing the readonly parameter view back to the mutable working type; these objects aren't actually frozen at runtime.
256
+ ([...snapshot.opened_views] as OpenedView[])
257
+ : defaultOpenedViews();
258
+ panelWidth.value = snapshot?.panelWidth ?? PANEL_WIDTH;
259
+ additionalPanelWidth.value = snapshot?.additionalPanelWidth ?? PANEL_WIDTH;
260
+ rowHeights.value = snapshot?.rowHeights ? [...snapshot.rowHeights] : [];
261
+ pendingSelectionIds.value = snapshot?.selectionIds
262
+ ? [...snapshot.selectionIds]
263
+ : (snapshot?.selection ?? []).map((selectionItem: string | { id: string }) =>
264
+ typeof selectionItem === "string" ? selectionItem : selectionItem.id,
265
+ );
242
266
  }
243
267
 
244
268
  function finalizeImportSelection(): void {
@@ -278,24 +302,28 @@ export const useTreeviewStore = defineStore("treeview", () => {
278
302
  }
279
303
 
280
304
  function setScrollTop(viewId: string, scrollTop: number): void {
281
- const view = opened_views.value.find((openedView) => openedView.id === viewId);
305
+ const view = opened_views.value.find(
306
+ (openedView: ReadonlyOpenedView) => openedView.id === viewId,
307
+ );
282
308
  if (view) {
283
309
  view.scrollTop = scrollTop;
284
310
  }
285
311
  }
286
312
 
287
- function setOpened(viewId: string, opened: string[]): void {
288
- const view = opened_views.value.find((openedView) => openedView.id === viewId);
313
+ function setOpened(viewId: string, opened: readonly string[]): void {
314
+ const view = opened_views.value.find(
315
+ (openedView: ReadonlyOpenedView) => openedView.id === viewId,
316
+ );
289
317
  if (view) {
290
- view.opened = opened;
318
+ view.opened = [...opened];
291
319
  }
292
320
  }
293
321
 
294
- function setRowHeights(heights: number[]): void {
295
- rowHeights.value = heights;
322
+ function setRowHeights(heights: readonly number[]): void {
323
+ rowHeights.value = [...heights];
296
324
  }
297
325
 
298
- function exportStores() {
326
+ function exportStores(): Omit<TreeviewConfigRecord, "id"> {
299
327
  return {
300
328
  opened_views: opened_views.value,
301
329
  panelWidth: panelWidth.value,
@@ -317,7 +345,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
317
345
  break;
318
346
  }
319
347
  }
320
- const view = opened_views.value.find((openedView) => openedView.id === id);
348
+ const view = opened_views.value.find((openedView: ReadonlyOpenedView) => openedView.id === id);
321
349
  if (view) {
322
350
  view.title = newName;
323
351
  }
@@ -351,3 +379,5 @@ export const useTreeviewStore = defineStore("treeview", () => {
351
379
  clear,
352
380
  };
353
381
  });
382
+
383
+ export type { TreeviewSnapshot };
@@ -18,6 +18,7 @@ import { useInfraStore } from "@ogw_front/stores/infra";
18
18
  import { viewer_call } from "@ogw_internal/utils/viewer_call";
19
19
 
20
20
  import type { JsonRpcSchema, RequestHandlers } from "@ogw_shared/utils/types.js";
21
+ // oxlint-disable-next-line import/max-dependencies -- all imports above are required by this store.
21
22
  import type { RpcClient } from "@ogw_shared/utils/call_raw.js";
22
23
 
23
24
  interface PickedPoint {
@@ -35,6 +36,7 @@ export const useViewerStore = defineStore(
35
36
  () => {
36
37
  const infraStore = useInfraStore();
37
38
  const default_local_port = ref("1234");
39
+ // oxlint-disable-next-line no-unsafe-type-assertion -- placeholder before the real client is set by ws_connect.
38
40
  const client = ref<RpcClient>({} as RpcClient);
39
41
  const config = ref<unknown>(undefined);
40
42
  const picking_mode = ref(false);
@@ -56,21 +58,38 @@ export const useViewerStore = defineStore(
56
58
  viewer_url += "/ws";
57
59
  return viewer_url;
58
60
  });
61
+ function start_request(): void {
62
+ request_counter.value += 1;
63
+ }
64
+ function stop_request(): void {
65
+ request_counter.value -= 1;
66
+ }
59
67
  const is_busy = computed(() => request_counter.value > 0);
60
68
  function toggle_picking_mode(value: boolean): void {
61
69
  picking_mode.value = value;
62
70
  }
63
- function request(
71
+ // oxlint-disable-next-line unicorn/consistent-function-scoping
72
+ async function request(
64
73
  {
65
74
  schema,
66
75
  params = {},
67
76
  timeout = request_timeout,
68
- }: { schema: JsonRpcSchema; params?: Record<string, unknown>; timeout?: number },
77
+ }: {
78
+ schema: JsonRpcSchema;
79
+ params?: Record<string, unknown>;
80
+ timeout?: number;
81
+ },
69
82
  callbacks: RequestHandlers = {},
70
83
  ): Promise<unknown> {
71
- const store = useViewerStore();
72
- return viewer_call(
73
- store,
84
+ const microservice = {
85
+ $id: "viewer",
86
+ client: client.value,
87
+ base_url: base_url.value,
88
+ start_request,
89
+ stop_request,
90
+ };
91
+ const result = await viewer_call(
92
+ microservice,
74
93
  {
75
94
  schema,
76
95
  params,
@@ -85,6 +104,7 @@ export const useViewerStore = defineStore(
85
104
  },
86
105
  },
87
106
  );
107
+ return result;
88
108
  }
89
109
  async function set_picked_point(x: number, y: number): Promise<void> {
90
110
  const schema = opengeodeweb_viewer_schemas.opengeodeweb_viewer.viewer.get_point_position;
@@ -100,6 +120,7 @@ export const useViewerStore = defineStore(
100
120
  x: world_x,
101
121
  y: world_y,
102
122
  z: world_z,
123
+ // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the get_point_position schema.
103
124
  } = response as { x: number; y: number; z: number };
104
125
  picked_point.value = {
105
126
  x: world_x,
@@ -107,23 +128,24 @@ export const useViewerStore = defineStore(
107
128
  z: world_z,
108
129
  };
109
130
  }
110
- function ws_connect() {
131
+ async function ws_connect(): Promise<void> {
111
132
  if (status.value === Status.CONNECTED) {
112
- return undefined;
133
+ return;
113
134
  }
114
- return navigator.locks.request("viewer.ws_connect", async (lock) => {
135
+ await navigator.locks.request("viewer.ws_connect", async () => {
115
136
  if (status.value === Status.CONNECTED) {
116
137
  return;
117
138
  }
118
139
  try {
119
- console.log("VIEWER LOCK GRANTED !", lock);
120
140
  status.value = Status.CONNECTING;
121
141
  client.value = (await initWebSocketClient(base_url.value, client.value, {
122
142
  onConnectionClose: () => {
123
143
  status.value = Status.NOT_CONNECTED;
124
144
  },
145
+ // oxlint-disable-next-line no-unsafe-type-assertion -- initWebSocketClient's return is not typed as RpcClient.
125
146
  })) as unknown as RpcClient;
126
147
  connectImageStream(client.value.getConnection().getSession());
148
+ // oxlint-disable-next-line no-unsafe-type-assertion -- endBusy is not part of the RpcClient type.
127
149
  (client.value as unknown as { endBusy: () => void }).endBusy();
128
150
  const schema = opengeodeweb_viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization;
129
151
  const timeout = undefined;
@@ -133,22 +155,12 @@ export const useViewerStore = defineStore(
133
155
  });
134
156
  status.value = Status.CONNECTED;
135
157
  } catch (error) {
136
- console.error("ws_connect error", error);
137
158
  status.value = Status.NOT_CONNECTED;
138
159
  throw error;
139
160
  }
140
161
  });
141
162
  }
142
- function start_request(): void {
143
- request_counter.value += 1;
144
- }
145
- function stop_request(): void {
146
- request_counter.value -= 1;
147
- }
148
- function launch(args: { projectFolderPath?: string } = {}) {
149
- console.log("[VIEWER] Launching viewer microservice...", {
150
- args,
151
- });
163
+ async function launch(args: Readonly<{ projectFolderPath?: string }> = {}): Promise<unknown> {
152
164
  const appStore = useAppStore();
153
165
  const { COMMAND_VIEWER, NUXT_ROOT_PATH } = useRuntimeConfig().public;
154
166
  const schema = opengeodeweb_front_schemas.api.local.app.run_viewer;
@@ -157,41 +169,41 @@ export const useViewerStore = defineStore(
157
169
  NUXT_ROOT_PATH,
158
170
  args,
159
171
  };
160
- console.log("[VIEWER] params", params);
161
- return appStore.request(
172
+ const result = await appStore.request(
162
173
  {
163
174
  schema,
164
175
  params,
165
176
  },
166
177
  {
167
178
  response_function: (response: unknown) => {
179
+ // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the run_viewer schema.
168
180
  const { port: viewerPort } = response as { port: string };
169
- console.log(`[VIEWER] Viewer launched on port ${viewerPort}`);
170
181
  default_local_port.value = viewerPort;
171
182
  },
172
183
  },
173
184
  );
185
+ return result;
174
186
  }
175
187
  async function connect(): Promise<void> {
176
- console.log("[VIEWER] Connecting to viewer microservice...");
177
188
  await ws_connect();
178
- console.log("[VIEWER] Viewer connected successfully");
179
189
  }
180
- function get_version(schema: JsonRpcSchema | undefined) {
190
+ async function get_version(schema: JsonRpcSchema | undefined): Promise<unknown> {
181
191
  if (!schema) {
182
192
  return undefined;
183
193
  }
184
- return request(
194
+ const result = await request(
185
195
  {
186
196
  schema,
187
197
  },
188
198
  {
189
199
  response_function: (response: unknown) => {
200
+ // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the version schema.
190
201
  const { microservice_version } = response as { microservice_version: string };
191
202
  version.value = microservice_version;
192
203
  },
193
204
  },
194
205
  );
206
+ return result;
195
207
  }
196
208
  return {
197
209
  default_local_port,
@@ -1,6 +1,6 @@
1
1
  interface AttributeRangeSource {
2
- min_values?: number[];
3
- max_values?: number[];
2
+ min_values?: readonly number[];
3
+ max_values?: readonly number[];
4
4
  min_value?: number;
5
5
  max_value?: number;
6
6
  }
@@ -12,7 +12,7 @@ interface AttributeRange {
12
12
 
13
13
  export function getAttributeRange(
14
14
  currentAttribute: AttributeRangeSource | undefined | null,
15
- compIndex: number = 0,
15
+ compIndex = 0,
16
16
  ): AttributeRange {
17
17
  if (!currentAttribute) {
18
18
  return { min: 0, max: 1 };
@@ -1,5 +1,6 @@
1
1
  import type { Bounds } from "@kitware/vtk.js/types";
2
2
  import type vtkActor from "@kitware/vtk.js/Rendering/Core/Actor";
3
+ import type { vtkCamera } from "@kitware/vtk.js/Rendering/Core/Camera";
3
4
 
4
5
  const AXIS_SCALE = 0.45;
5
6
  const SIZE_RATIO = 0.1;
@@ -20,16 +21,15 @@ const DEFAULT_NORMALS = [NORMAL_X, NORMAL_Y, NORMAL_Z];
20
21
  const RGB_MAX_VALUE = 255;
21
22
 
22
23
  function getPlaneCssColor(index: number): string {
23
- // Modulo guarantees this index is within bounds of the non-empty PLANE_COLORS array.
24
- const rgb = PLANE_COLORS[index % PLANE_COLORS.length]!;
24
+ const rgb = PLANE_COLORS[index % PLANE_COLORS.length] ?? COLOR_BLUE;
25
25
  return `rgb(${rgb.map((channel) => Math.round(channel * RGB_MAX_VALUE)).join(",")})`;
26
26
  }
27
27
 
28
28
  function hasPlaneChanged(
29
- origin: number[],
30
- normal: number[],
31
- currentOrigin: number[] | undefined,
32
- currentNormal: number[] | undefined,
29
+ origin: readonly number[],
30
+ normal: readonly number[],
31
+ currentOrigin: readonly number[] | undefined,
32
+ currentNormal: readonly number[] | undefined,
33
33
  ): boolean {
34
34
  if (!currentOrigin) {
35
35
  return true;
@@ -40,7 +40,27 @@ function hasPlaneChanged(
40
40
  );
41
41
  }
42
42
 
43
- function getPlaneStyle(rgb: number[]) {
43
+ interface PlaneVisualState {
44
+ readonly plane: { readonly opacity: number; readonly color: readonly number[] };
45
+ readonly normal: { readonly opacity: number; readonly color: readonly number[] };
46
+ readonly origin: { readonly opacity: number; readonly color: readonly number[] };
47
+ }
48
+
49
+ interface PlaneStyle {
50
+ readonly active: PlaneVisualState;
51
+ readonly inactive: PlaneVisualState;
52
+ readonly static: {
53
+ readonly display2D: { readonly representation: number };
54
+ readonly outline: {
55
+ readonly color: readonly number[];
56
+ readonly opacity: number;
57
+ readonly representation: number;
58
+ readonly interpolation: number;
59
+ };
60
+ };
61
+ }
62
+
63
+ function getPlaneStyle(rgb: readonly number[]): PlaneStyle {
44
64
  return {
45
65
  active: {
46
66
  plane: { opacity: 1, color: rgb },
@@ -59,7 +79,7 @@ function getPlaneStyle(rgb: number[]) {
59
79
  };
60
80
  }
61
81
 
62
- function computeSceneBounds(actors: vtkActor[]): Bounds {
82
+ function computeSceneBounds(actors: readonly vtkActor[]): Bounds {
63
83
  let bounds: Bounds = [Infinity, -Infinity, Infinity, -Infinity, Infinity, -Infinity];
64
84
  for (const actor of actors) {
65
85
  const boundsOfActor = actor.getBounds();
@@ -76,12 +96,12 @@ function computeSceneBounds(actors: vtkActor[]): Bounds {
76
96
  }
77
97
 
78
98
  interface SceneBoundsInfo {
79
- center: number[];
80
- cubicBounds: number[];
99
+ readonly center: readonly number[];
100
+ readonly cubicBounds: readonly number[];
81
101
  }
82
102
 
83
- function computeSceneBoundsInfo(actors: vtkActor[]): SceneBoundsInfo {
84
- if (!actors || actors.length === 0) {
103
+ function computeSceneBoundsInfo(actors: readonly vtkActor[]): SceneBoundsInfo {
104
+ if (actors.length === 0) {
85
105
  return { center: [0, 0, 0], cubicBounds: [-1, 1, -1, 1, -1, 1] };
86
106
  }
87
107
  const [xmin, xmax, ymin, ymax, zmin, zmax] = computeSceneBounds(actors);
@@ -92,13 +112,13 @@ function computeSceneBoundsInfo(actors: vtkActor[]): SceneBoundsInfo {
92
112
  ];
93
113
 
94
114
  const maxActorExtent = Math.max(
95
- ...actors.map((actor) => {
115
+ ...actors.map((actor: vtkActor) => {
96
116
  const bounds = actor.getBounds();
97
117
  return Math.max(bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4]);
98
118
  }),
99
119
  );
100
120
  const halfExtent = maxActorExtent / 2;
101
- const [centerX, centerY, centerZ] = center as [number, number, number];
121
+ const [centerX = 0, centerY = 0, centerZ = 0] = center;
102
122
  const cubicBounds = [
103
123
  centerX - halfExtent,
104
124
  centerX + halfExtent,
@@ -110,6 +130,79 @@ function computeSceneBoundsInfo(actors: vtkActor[]): SceneBoundsInfo {
110
130
  return { center, cubicBounds };
111
131
  }
112
132
 
133
+ interface ActorEntry {
134
+ readonly actor: vtkActor;
135
+ }
136
+
137
+ interface MainCameraOptions {
138
+ readonly focal_point?: readonly number[];
139
+ readonly position?: readonly number[];
140
+ readonly view_up?: readonly number[];
141
+ }
142
+
143
+ function resolveActiveActors(
144
+ targetAllVisible: boolean,
145
+ allItems: readonly { readonly id: string }[],
146
+ selectedDatasetIds: readonly string[],
147
+ hybridDb: Record<string, ActorEntry | undefined>,
148
+ ): vtkActor[] {
149
+ const targetIds = targetAllVisible
150
+ ? allItems.map((item: { id: string }) => item.id)
151
+ : selectedDatasetIds;
152
+ const targeted = targetIds
153
+ .map((id) => hybridDb[id]?.actor)
154
+ .filter((actor: vtkActor | undefined): actor is vtkActor => actor !== undefined);
155
+ if (targeted.length > 0) {
156
+ return targeted;
157
+ }
158
+ return Object.values(hybridDb)
159
+ .filter((entry: ActorEntry | undefined): entry is ActorEntry => entry !== undefined)
160
+ .map((entry: ActorEntry) => entry.actor);
161
+ }
162
+
163
+ function computeNormalizedViewDirection(
164
+ focalPoint: readonly number[],
165
+ position: readonly number[],
166
+ ): readonly number[] | undefined {
167
+ const dir = [
168
+ (position[0] ?? 0) - (focalPoint[0] ?? 0),
169
+ (position[1] ?? 0) - (focalPoint[1] ?? 0),
170
+ (position[2] ?? 0) - (focalPoint[2] ?? 0),
171
+ ];
172
+ const dirLen = Math.hypot(...dir);
173
+ if (dirLen === 0) {
174
+ return undefined;
175
+ }
176
+ return dir.map((component) => component / dirLen);
177
+ }
178
+
179
+ // Aligns the local clipping-plane preview camera onto the same viewing direction as the
180
+ // Main viewer camera, re-centered on the clipped scene's bounds.
181
+ function alignCameraToMainCamera(
182
+ camera: vtkCamera,
183
+ mainCam: MainCameraOptions,
184
+ center: readonly number[],
185
+ ): void {
186
+ if (!mainCam.focal_point || !mainCam.position) {
187
+ return;
188
+ }
189
+ const normDir = computeNormalizedViewDirection(mainCam.focal_point, mainCam.position);
190
+ if (!normDir) {
191
+ return;
192
+ }
193
+ const distance = camera.getDistance();
194
+ camera.setFocalPoint(center[0] ?? 0, center[1] ?? 0, center[2] ?? 0);
195
+ camera.setPosition(
196
+ (center[0] ?? 0) + (normDir[0] ?? 0) * distance,
197
+ (center[1] ?? 0) + (normDir[1] ?? 0) * distance,
198
+ (center[2] ?? 0) + (normDir[2] ?? 0) * distance,
199
+ );
200
+ if (mainCam.view_up) {
201
+ const [viewUpX = 0, viewUpY = 0, viewUpZ = 0] = mainCam.view_up;
202
+ camera.setViewUp(viewUpX, viewUpY, viewUpZ);
203
+ }
204
+ }
205
+
113
206
  export {
114
207
  AXIS_SCALE,
115
208
  SIZE_RATIO,
@@ -122,4 +215,8 @@ export {
122
215
  getPlaneStyle,
123
216
  computeSceneBounds,
124
217
  computeSceneBoundsInfo,
218
+ resolveActiveActors,
219
+ alignCameraToMainCamera,
125
220
  };
221
+
222
+ export type { SceneBoundsInfo, MainCameraOptions };