@geode/opengeodeweb-front 10.14.1 → 10.14.2-rc.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 (68) hide show
  1. package/.oxlintrc.json +12 -2
  2. package/app/components/Basic/Slider.vue +7 -0
  3. package/app/components/Basic/Switch.vue +7 -0
  4. package/app/components/HybridRenderingView.vue +1 -1
  5. package/app/components/SearchBar.vue +1 -1
  6. package/app/components/Viewer/ContextMenu.vue +1 -3
  7. package/app/components/Viewer/ContextMenuItem.vue +2 -2
  8. package/app/components/Viewer/EdgedCurve/SpecificEdgesOptions.vue +5 -9
  9. package/app/components/Viewer/Generic/Mesh/CellsOptions.vue +7 -2
  10. package/app/components/Viewer/Generic/Mesh/EdgesOptions.vue +11 -9
  11. package/app/components/Viewer/Generic/Mesh/PointsOptions.vue +14 -9
  12. package/app/components/Viewer/Generic/Mesh/PolygonsOptions.vue +10 -2
  13. package/app/components/Viewer/Generic/Mesh/PolyhedraOptions.vue +10 -2
  14. package/app/components/Viewer/Generic/Model/PointsOptions.vue +2 -14
  15. package/app/components/Viewer/ObjectTree/Base/CommonTreeView.vue +189 -0
  16. package/app/components/Viewer/ObjectTree/Base/Controls.vue +124 -38
  17. package/app/components/Viewer/ObjectTree/Base/ItemLabel.vue +43 -18
  18. package/app/components/Viewer/ObjectTree/Base/StickyHeader.vue +46 -0
  19. package/app/components/Viewer/ObjectTree/Base/TreeRow.vue +77 -0
  20. package/app/components/Viewer/ObjectTree/Box.vue +106 -15
  21. package/app/components/Viewer/ObjectTree/Layout.vue +14 -12
  22. package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +48 -33
  23. package/app/components/Viewer/ObjectTree/Views/ModelComponents.vue +102 -66
  24. package/app/components/Viewer/Options/Sliders/Size.vue +8 -0
  25. package/app/components/Viewer/Options/Sliders/Slider.vue +17 -0
  26. package/app/components/Viewer/Options/Sliders/Width.vue +8 -0
  27. package/app/components/Viewer/Options/VisibilitySwitch.vue +3 -1
  28. package/app/components/Viewer/PointSet/SpecificPointsOptions.vue +1 -5
  29. package/app/components/Viewer/Surface/Triangulated/TrianglesOptions.vue +11 -0
  30. package/app/composables/hover_highlight.js +85 -0
  31. package/app/composables/model_components.js +68 -0
  32. package/app/composables/{use_tree_filter.js → tree_filter.js} +49 -31
  33. package/app/composables/tree_keyboard_nav.js +81 -0
  34. package/app/composables/tree_scroll.js +91 -0
  35. package/app/composables/virtual_tree.js +164 -0
  36. package/app/stores/data.js +41 -1
  37. package/app/stores/hybrid_viewer.js +30 -38
  38. package/app/stores/menu.js +8 -14
  39. package/app/utils/hybrid_viewer.js +101 -0
  40. package/package.json +3 -3
  41. package/tests/integration/setup.js +2 -1
  42. package/tests/integration/stores/data_style/mesh/cells.nuxt.test.js +2 -3
  43. package/tests/integration/stores/data_style/mesh/edges.nuxt.test.js +2 -3
  44. package/tests/integration/stores/data_style/mesh/index.nuxt.test.js +2 -3
  45. package/tests/integration/stores/data_style/mesh/points.nuxt.test.js +2 -3
  46. package/tests/integration/stores/data_style/mesh/polygons.nuxt.test.js +2 -3
  47. package/tests/integration/stores/data_style/mesh/polyhedra.nuxt.test.js +2 -3
  48. package/tests/integration/stores/data_style/model/blocks.nuxt.test.js +2 -3
  49. package/tests/integration/stores/data_style/model/corners.nuxt.test.js +2 -3
  50. package/tests/integration/stores/data_style/model/edges.nuxt.test.js +2 -3
  51. package/tests/integration/stores/data_style/model/index.nuxt.test.js +2 -3
  52. package/tests/integration/stores/data_style/model/lines.nuxt.test.js +2 -3
  53. package/tests/integration/stores/data_style/model/points.nuxt.test.js +2 -3
  54. package/tests/integration/stores/data_style/model/surfaces.nuxt.test.js +2 -3
  55. package/tests/integration/stores/viewer.nuxt.test.js +2 -2
  56. package/app/components/Viewer/HybridSolid/EdgesOptions.vue +0 -12
  57. package/app/components/Viewer/HybridSolid/PointsOptions.vue +0 -12
  58. package/app/components/Viewer/HybridSolid/PolygonsOptions.vue +0 -12
  59. package/app/components/Viewer/HybridSolid/PolyhedraOptions.vue +0 -12
  60. package/app/components/Viewer/PolygonalSurface/EdgesOptions.vue +0 -12
  61. package/app/components/Viewer/PolygonalSurface/PointsOptions.vue +0 -12
  62. package/app/components/Viewer/TriangulatedSurface/TrianglesOptions.vue +0 -16
  63. package/app/composables/use_hover_highlight.js +0 -48
  64. package/tests/integration/microservices/back/requirements.txt +0 -7
  65. package/tests/integration/microservices/viewer/requirements.txt +0 -7
  66. /package/app/components/Viewer/{TriangulatedSurface → Surface}/EdgesOptions.vue +0 -0
  67. /package/app/components/Viewer/{TriangulatedSurface → Surface}/PointsOptions.vue +0 -0
  68. /package/app/components/Viewer/{PolygonalSurface/SpecificPolygonsOptions.vue → Surface/PolygonsOptions.vue} +0 -0
@@ -1,12 +1,13 @@
1
1
  <script setup>
2
+ import CommonTreeView from "@ogw_front/components/Viewer/ObjectTree/Base/CommonTreeView.vue";
2
3
  import ObjectTreeControls from "@ogw_front/components/Viewer/ObjectTree/Base/Controls.vue";
3
4
  import ObjectTreeItemLabel from "@ogw_front/components/Viewer/ObjectTree/Base/ItemLabel.vue";
4
5
  import { compareSelections } from "@ogw_front/utils/treeview";
5
6
  import { useDataStore } from "@ogw_front/stores/data";
6
7
  import { useDataStyleStore } from "@ogw_front/stores/data_style";
7
- import { useHoverhighlight } from "@ogw_front/composables/use_hover_highlight";
8
+ import { useHoverhighlight } from "@ogw_front/composables/hover_highlight";
8
9
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
9
- import { useTreeFilter } from "@ogw_front/composables/use_tree_filter";
10
+ import { useTreeFilter } from "@ogw_front/composables/tree_filter";
10
11
  import { useTreeviewStore } from "@ogw_front/stores/treeview";
11
12
 
12
13
  const treeviewStore = useTreeviewStore();
@@ -32,7 +33,7 @@ const {
32
33
  toggleSort,
33
34
  customFilter,
34
35
  applySearchFilter,
35
- } = useTreeFilter(toRef(() => treeviewStore.items));
36
+ } = useTreeFilter(() => treeviewStore.items, { recursiveSort: true });
36
37
 
37
38
  function onUpdateSelection(val) {
38
39
  treeviewStore.selection = applySearchFilter(val, treeviewStore.selection);
@@ -74,18 +75,28 @@ function isModel(item) {
74
75
  );
75
76
  }
76
77
 
77
- async function handleHoverEnter(item) {
78
+ function handleHoverEnter({ item, immediate = false }) {
78
79
  const actualItem = item.raw || item;
79
- const is_model = isModel(item);
80
- let block_ids = [];
81
- if (is_model) {
82
- block_ids = await dataStore.getAllModelComponentsViewerIds(actualItem.id);
80
+
81
+ if (!actualItem.viewer_type) {
82
+ return;
83
83
  }
84
- onHoverEnter(actualItem.id, block_ids, is_model ? "model" : "mesh");
84
+
85
+ const is_model = isModel(item);
86
+
87
+ onHoverEnter(
88
+ actualItem.id,
89
+ async () => (is_model ? await dataStore.getAllModelComponentsViewerIds(actualItem.id) : []),
90
+ is_model ? "model" : "mesh",
91
+ immediate,
92
+ );
85
93
  }
86
94
 
87
- function handleHoverLeave(item) {
95
+ function handleHoverLeave({ item }) {
88
96
  const actualItem = item.raw || item;
97
+ if (!actualItem.viewer_type) {
98
+ return;
99
+ }
89
100
  onHoverLeave(actualItem.id);
90
101
  }
91
102
  </script>
@@ -98,26 +109,29 @@ function handleHoverLeave(item) {
98
109
  :filter-options="filterOptions"
99
110
  :available-filter-options="availableFilterOptions"
100
111
  @toggle-sort="toggleSort"
112
+ @collapse-all="opened = []"
101
113
  />
102
114
 
103
- <v-treeview
115
+ <CommonTreeView
104
116
  :selected="visibleSelection"
105
117
  v-model:opened="opened"
106
118
  :items="processedItems"
107
- :search="search"
108
- :custom-filter="customFilter"
109
- class="transparent-treeview"
110
- item-value="id"
111
- select-strategy="classic"
112
- selectable
113
- items-registration="props"
119
+ :options="{
120
+ selection: { selectable: true },
121
+ search,
122
+ customFilter,
123
+ }"
124
+ :scroll-top="mainView?.scrollTop || 0"
125
+ class="transparent-treeview virtual-tree-height"
114
126
  @update:selected="onUpdateSelection"
127
+ @update:scroll-top="treeviewStore.setScrollTop(mainView.id, $event)"
128
+ @hover:enter="handleHoverEnter"
129
+ @hover:leave="handleHoverLeave"
115
130
  >
116
- <template #title="{ item }">
131
+ <template #title="{ item, isLeaf }">
117
132
  <ObjectTreeItemLabel
118
133
  :item="item"
119
- @mouseenter="handleHoverEnter(item)"
120
- @mouseleave="handleHoverLeave(item)"
134
+ :is-leaf="isLeaf"
121
135
  @contextmenu="emit('show-menu', { event: $event, itemId: item.id })"
122
136
  />
123
137
  </template>
@@ -135,25 +149,26 @@ function handleHoverLeave(item) {
135
149
  "
136
150
  />
137
151
  </template>
138
- </v-treeview>
152
+ </CommonTreeView>
139
153
  </div>
140
154
  </template>
141
155
 
142
156
  <style scoped>
143
- .transparent-treeview {
144
- background-color: transparent;
145
- margin: 4px 0;
146
- }
147
-
148
- :deep(.v-list-item) {
149
- transition: background-color 0.2s ease;
157
+ .tree-view-container {
158
+ height: 100%;
159
+ display: flex;
160
+ flex-direction: column;
161
+ overflow: hidden;
162
+ min-height: 0;
150
163
  }
151
164
 
152
- :deep(.v-list-item--active > .v-list-item__overlay) {
153
- opacity: 0 !important;
165
+ .virtual-tree-height {
166
+ flex-grow: 1;
167
+ min-height: 0;
154
168
  }
155
169
 
156
- :deep(.v-list-item:hover > .v-list-item__overlay) {
157
- opacity: 0.1 !important;
170
+ .transparent-treeview {
171
+ background-color: transparent;
172
+ margin: 4px 0;
158
173
  }
159
174
  </style>
@@ -1,85 +1,113 @@
1
1
  <script setup>
2
+ import { sortAndFormatItems, useTreeFilter } from "@ogw_front/composables/tree_filter";
3
+ import CommonTreeView from "@ogw_front/components/Viewer/ObjectTree/Base/CommonTreeView.vue";
2
4
  import FetchingData from "@ogw_front/components/FetchingData.vue";
3
5
  import ObjectTreeControls from "@ogw_front/components/Viewer/ObjectTree/Base/Controls.vue";
4
6
  import ObjectTreeItemLabel from "@ogw_front/components/Viewer/ObjectTree/Base/ItemLabel.vue";
5
- import { compareSelections } from "@ogw_front/utils/treeview";
6
- import { useDataStore } from "@ogw_front/stores/data";
7
- import { useDataStyleStore } from "@ogw_front/stores/data_style";
8
- import { useHoverhighlight } from "@ogw_front/composables/use_hover_highlight";
9
- import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
10
- import { useTreeFilter } from "@ogw_front/composables/use_tree_filter";
7
+ import { useHoverhighlight } from "@ogw_front/composables/hover_highlight";
8
+ import { useModelComponents } from "@ogw_front/composables/model_components";
11
9
  import { useTreeviewStore } from "@ogw_front/stores/treeview";
12
10
 
13
- const { id: viewId } = defineProps({ id: { type: String, required: true } });
11
+ const { id } = defineProps({ id: { type: String, required: true } });
14
12
  const { onHoverEnter, onHoverLeave } = useHoverhighlight();
15
13
  const emit = defineEmits(["show-menu"]);
16
14
 
17
- const dataStore = useDataStore();
18
- const dataStyleStore = useDataStyleStore();
19
- const hybridViewerStore = useHybridViewerStore();
20
15
  const treeviewStore = useTreeviewStore();
16
+ const {
17
+ items: rawItems,
18
+ componentsCache,
19
+ localCategories,
20
+ selection: visibleComponents,
21
+ updateVisibility,
22
+ } = useModelComponents(id);
23
+
24
+ const currentView = computed(() => treeviewStore.opened_views.find((view) => view.id === id));
21
25
 
22
- const currentView = computed(() => treeviewStore.opened_views.find((view) => view.id === viewId));
23
26
  const opened = computed({
24
27
  get: () => currentView.value?.opened || [],
25
- set: (val) => treeviewStore.setOpened(viewId, val),
28
+ set: (val) => treeviewStore.setOpened(id, val),
26
29
  });
27
30
 
28
- const items = dataStore.refFormatedMeshComponents(viewId);
29
- const mesh_components_selection = dataStyleStore.visibleMeshComponents(viewId);
30
-
31
31
  const {
32
32
  search,
33
33
  sortType,
34
34
  filterOptions,
35
- processedItems,
35
+ processedItems: filteredCategories,
36
36
  availableFilterOptions,
37
37
  toggleSort,
38
38
  customFilter,
39
39
  applySearchFilter,
40
- } = useTreeFilter(items);
40
+ } = useTreeFilter(localCategories);
41
41
 
42
- async function onSelectionChange(newSelection) {
43
- const current = applySearchFilter(newSelection, mesh_components_selection.value);
44
- const previous = mesh_components_selection.value;
45
- const { added, removed } = compareSelections(current, previous);
42
+ function onUpdateSelection(newSelection) {
43
+ const finalSelection = applySearchFilter(newSelection, visibleComponents.value);
44
+ updateVisibility(finalSelection);
45
+ }
46
46
 
47
- if (added.length === 0 && removed.length === 0) {
48
- return;
47
+ const visibleSelection = computed(() => applySearchFilter(visibleComponents.value, []));
48
+
49
+ const itemsForTreeView = computed(() => {
50
+ if (search.value && componentsCache.value) {
51
+ const query = search.value.toLowerCase();
52
+ const result = [];
53
+ for (const type of Object.keys(componentsCache.value)) {
54
+ const matches = componentsCache.value[type].filter(
55
+ (component) =>
56
+ component.title.toLowerCase().includes(query) ||
57
+ component.id.toLowerCase().includes(query),
58
+ );
59
+ if (matches.length > 0) {
60
+ result.push({
61
+ id: type,
62
+ title: `${type}s (${matches.length})`,
63
+ children: sortAndFormatItems(matches, sortType.value),
64
+ });
65
+ }
66
+ }
67
+ return result;
49
68
  }
50
69
 
51
- if (added.length > 0) {
52
- await dataStyleStore.setModelComponentsVisibility(viewId, added, true);
70
+ const result = [];
71
+ for (const category of filteredCategories.value) {
72
+ result.push({
73
+ ...category,
74
+ children: sortAndFormatItems(componentsCache.value?.[category.id], sortType.value),
75
+ });
53
76
  }
54
- if (removed.length > 0) {
55
- await dataStyleStore.setModelComponentsVisibility(viewId, removed, false);
56
- }
57
- hybridViewerStore.remoteRender();
58
- }
59
-
60
- const visibleSelection = computed(() => applySearchFilter(mesh_components_selection.value, []));
77
+ return result;
78
+ });
61
79
 
62
80
  function showContextMenu(event, item) {
63
81
  const actualItem = item.raw || item;
64
82
  emit("show-menu", {
65
83
  event,
66
- itemId: actualItem.category ? actualItem.id : viewId,
84
+ itemId: actualItem.category ? actualItem.id : id,
67
85
  context_type: actualItem.category ? "model_component" : "model_component_type",
68
- modelId: viewId,
86
+ modelId: id,
69
87
  modelComponentType: actualItem.category ? undefined : actualItem.id,
70
88
  });
71
89
  }
72
90
 
73
- function handleHoverEnter(item) {
91
+ function handleHoverEnter({ item, immediate = false }) {
74
92
  const actualItem = item.raw || item;
75
- const block_ids = actualItem.category
76
- ? [actualItem.viewer_id]
77
- : actualItem.children?.map((child) => child.viewer_id) || [];
78
- onHoverEnter(viewId, block_ids);
93
+
94
+ if (!actualItem.category && (!actualItem.children || actualItem.children.length === 0)) {
95
+ return;
96
+ }
97
+
98
+ onHoverEnter(
99
+ id,
100
+ () =>
101
+ actualItem.category
102
+ ? [actualItem.viewer_id]
103
+ : actualItem.children?.map((child) => child.viewer_id) || [],
104
+ "model",
105
+ immediate,
106
+ );
79
107
  }
80
108
 
81
109
  function handleHoverLeave() {
82
- onHoverLeave(viewId);
110
+ onHoverLeave(id);
83
111
  }
84
112
  </script>
85
113
 
@@ -91,52 +119,60 @@ function handleHoverLeave() {
91
119
  :filter-options="filterOptions"
92
120
  :available-filter-options="availableFilterOptions"
93
121
  @toggle-sort="toggleSort"
122
+ @collapse-all="opened = []"
94
123
  />
95
124
 
96
- <FetchingData v-if="items === undefined" :size="48" :width="4" text="" />
125
+ <FetchingData v-if="rawItems === undefined" :size="48" :width="4" text="" />
97
126
 
98
- <v-treeview
99
- v-else
127
+ <CommonTreeView
100
128
  :selected="visibleSelection"
101
129
  v-model:opened="opened"
102
- :items="processedItems"
103
- :search="search"
104
- :custom-filter="customFilter"
105
- class="transparent-treeview"
106
- item-value="id"
107
- select-strategy="classic"
108
- selectable
109
- items-registration="props"
110
- @update:selected="onSelectionChange"
130
+ :items="itemsForTreeView"
131
+ :options="{
132
+ selection: { selectable: true, strategy: 'classic' },
133
+ search,
134
+ customFilter,
135
+ }"
136
+ :scroll-top="currentView?.scrollTop || 0"
137
+ class="transparent-treeview virtual-tree-height"
138
+ @update:selected="onUpdateSelection"
139
+ @click:item="onUpdateSelection([$event.id, ...visibleComponents])"
140
+ @update:scroll-top="treeviewStore.setScrollTop(id, $event)"
141
+ @hover:enter="handleHoverEnter"
142
+ @hover:leave="handleHoverLeave"
111
143
  >
112
- <template #title="{ item }">
144
+ <template #title="{ item, isLeaf }">
113
145
  <ObjectTreeItemLabel
114
146
  :item="item"
147
+ :is-leaf="isLeaf"
115
148
  show-tooltip
116
- @mouseenter="handleHoverEnter(item)"
117
- @mouseleave="handleHoverLeave(item)"
118
- @contextmenu="showContextMenu($event, item)"
149
+ class="text-body-1"
150
+ @contextmenu.prevent.stop="showContextMenu($event, item)"
119
151
  />
120
152
  </template>
121
- </v-treeview>
153
+ </CommonTreeView>
122
154
  </div>
123
155
  </template>
124
156
 
125
157
  <style scoped>
126
- .transparent-treeview {
127
- background-color: transparent;
128
- margin: 4px 0;
158
+ .tree-view-container {
159
+ height: 100%;
160
+ display: flex;
161
+ flex-direction: column;
162
+ overflow: hidden;
163
+ min-height: 0;
129
164
  }
130
165
 
131
- :deep(.v-list-item) {
132
- transition: background-color 0.2s ease;
166
+ .virtual-tree-height {
167
+ flex-grow: 1;
168
+ min-height: 0;
133
169
  }
134
170
 
135
- :deep(.v-list-item--active > .v-list-item__overlay) {
136
- opacity: 0 !important;
171
+ .transparent-treeview {
172
+ background-color: transparent;
137
173
  }
138
174
 
139
- :deep(.v-list-item:hover > .v-list-item__overlay) {
140
- opacity: 0.1 !important;
175
+ :deep(.v-list-item__overlay) {
176
+ display: none !important;
141
177
  }
142
178
  </style>
@@ -0,0 +1,8 @@
1
+ <script setup>
2
+ import Slider from "./Slider";
3
+ const size = defineModel();
4
+ </script>
5
+
6
+ <template>
7
+ <Slider v-model="size" :tooltip="'Size'" />
8
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup>
2
+ import BasicSlider from "@ogw_front/components/Basic/Slider";
3
+
4
+ const model = defineModel();
5
+ const { tooltip } = defineProps({
6
+ tooltip: { type: String, required: true },
7
+ });
8
+ </script>
9
+
10
+ <template>
11
+ <v-col cols="auto" justify="center">
12
+ <v-icon size="30" icon="mdi-ruler" v-tooltip:left="tooltip" />
13
+ </v-col>
14
+ <v-col justify="center">
15
+ <BasicSlider v-model="model" />
16
+ </v-col>
17
+ </template>
@@ -0,0 +1,8 @@
1
+ <script setup>
2
+ import Slider from "./Slider";
3
+ const width = defineModel();
4
+ </script>
5
+
6
+ <template>
7
+ <Slider v-model="width" :tooltip="'Width'" />
8
+ </template>
@@ -1,4 +1,6 @@
1
1
  <script setup>
2
+ import BasicSwitch from "@ogw_front/components/Basic/Switch";
3
+
2
4
  const visibility = defineModel();
3
5
  </script>
4
6
 
@@ -8,7 +10,7 @@ const visibility = defineModel();
8
10
  <v-icon size="30" icon="mdi-eye" v-tooltip:left="'Visibility'" />
9
11
  </v-col>
10
12
  <v-col cols="auto" justify="center">
11
- <v-switch v-model="visibility" inset hide-details />
13
+ <BasicSwitch v-model="visibility" />
12
14
  </v-col>
13
15
  </v-row>
14
16
  </template>
@@ -67,11 +67,7 @@ const vertex_attribute_color_map = computed({
67
67
  });
68
68
  </script>
69
69
  <template>
70
- <ViewerContextMenuItem
71
- :itemProps="itemProps"
72
- tooltip="Points options"
73
- :btn_image="PointSetPoints"
74
- >
70
+ <ViewerContextMenuItem :itemProps="itemProps" :btn_image="PointSetPoints">
75
71
  <template #options>
76
72
  <ViewerOptionsVisibilitySwitch v-model="visibility" />
77
73
  <template v-if="visibility">
@@ -0,0 +1,11 @@
1
+ <script setup>
2
+ import ViewerSpecificPolygonsOptions from "@ogw_front/components/Viewer/Surface/PolygonsOptions";
3
+
4
+ const { itemProps } = defineProps({
5
+ itemProps: { type: Object, required: true },
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <ViewerSpecificPolygonsOptions :itemProps="itemProps" tooltip="Triangles options" />
11
+ </template>
@@ -0,0 +1,85 @@
1
+ import { useViewerStore } from "@ogw_front/stores/viewer";
2
+ import vtk_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
3
+
4
+ const HOVER_DELAY = 200;
5
+
6
+ export function useHoverhighlight() {
7
+ const viewerStore = useViewerStore();
8
+ let timer = undefined;
9
+ let currentId = undefined;
10
+ let currentType = undefined;
11
+
12
+ function onHoverEnter(id, block_ids_provider = [], type = "model", immediate = false) {
13
+ if (timer) {
14
+ clearTimeout(timer);
15
+ timer = undefined;
16
+ }
17
+ const schema = vtk_schemas.opengeodeweb_viewer[type].highlight;
18
+
19
+ async function highlightAction() {
20
+ currentId = id;
21
+ currentType = type;
22
+
23
+ let block_ids = [];
24
+ if (typeof block_ids_provider === "function") {
25
+ block_ids = await block_ids_provider();
26
+ } else {
27
+ block_ids = block_ids_provider;
28
+ }
29
+
30
+ block_ids = (Array.isArray(block_ids) ? block_ids : [])
31
+ .map((blockId) => Number.parseInt(blockId, 10))
32
+ .filter((blockId) => !Number.isNaN(blockId));
33
+
34
+ if (currentId !== id) {
35
+ return;
36
+ }
37
+
38
+ const params = {
39
+ id,
40
+ visibility: true,
41
+ ...(type === "model" && { block_ids }),
42
+ };
43
+ try {
44
+ await viewerStore.request(schema, params);
45
+ } catch (error) {
46
+ console.error(`Highlight failed for ${type} ${id}:`, error);
47
+ }
48
+ }
49
+
50
+ if (immediate) {
51
+ highlightAction();
52
+ } else {
53
+ timer = setTimeout(highlightAction, HOVER_DELAY);
54
+ }
55
+ }
56
+
57
+ function onHoverLeave(id) {
58
+ if (timer) {
59
+ clearTimeout(timer);
60
+ timer = undefined;
61
+ }
62
+ if (currentId === id) {
63
+ const schema = vtk_schemas.opengeodeweb_viewer[currentType].highlight;
64
+ const params = {
65
+ id,
66
+ visibility: false,
67
+ ...(currentType === "model" && { block_ids: [] }),
68
+ };
69
+ try {
70
+ viewerStore.request(schema, params);
71
+ } catch (error) {
72
+ console.error(`Unhighlight failed for ${currentType} ${id}:`, error);
73
+ }
74
+ currentId = undefined;
75
+ currentType = undefined;
76
+ }
77
+
78
+ if (!currentId) {
79
+ currentId = undefined;
80
+ currentType = undefined;
81
+ }
82
+ }
83
+
84
+ return { onHoverEnter, onHoverLeave };
85
+ }
@@ -0,0 +1,68 @@
1
+ import { compareSelections } from "@ogw_front/utils/treeview";
2
+ import { useDataStore } from "@ogw_front/stores/data";
3
+ import { useDataStyleStore } from "@ogw_front/stores/data_style";
4
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
5
+
6
+ export function useModelComponents(viewId) {
7
+ const dataStore = useDataStore();
8
+ const dataStyleStore = useDataStyleStore();
9
+ const hybridViewerStore = useHybridViewerStore();
10
+
11
+ const items = dataStore.refFormatedMeshComponents(viewId);
12
+ const componentsCache = ref(undefined);
13
+ const localCategories = ref([]);
14
+
15
+ onMounted(async () => {
16
+ const data = await dataStore.fetchAllMeshComponents(viewId);
17
+ componentsCache.value = markRaw(data);
18
+ });
19
+
20
+ watch(
21
+ items,
22
+ (newItems) => {
23
+ if (!newItems) {
24
+ localCategories.value = [];
25
+ return;
26
+ }
27
+ localCategories.value = newItems.map((newCategory) => {
28
+ const existing = localCategories.value.find((category) => category.id === newCategory.id);
29
+ if (existing) {
30
+ existing.title = newCategory.title || newCategory.id;
31
+ return existing;
32
+ }
33
+ return reactive({
34
+ ...newCategory,
35
+ title: newCategory.title || newCategory.id,
36
+ });
37
+ });
38
+ },
39
+ { immediate: true },
40
+ );
41
+
42
+ const selection = dataStyleStore.visibleMeshComponents(viewId);
43
+
44
+ async function updateVisibility(current) {
45
+ const previous = selection.value;
46
+ const { added, removed } = compareSelections(current, previous);
47
+
48
+ if (added.length === 0 && removed.length === 0) {
49
+ return;
50
+ }
51
+
52
+ if (added.length > 0) {
53
+ await dataStyleStore.setModelComponentsVisibility(viewId, added, true);
54
+ }
55
+ if (removed.length > 0) {
56
+ await dataStyleStore.setModelComponentsVisibility(viewId, removed, false);
57
+ }
58
+ hybridViewerStore.remoteRender();
59
+ }
60
+
61
+ return {
62
+ items,
63
+ componentsCache,
64
+ localCategories,
65
+ selection,
66
+ updateVisibility,
67
+ };
68
+ }