@geode/opengeodeweb-front 10.38.0-rc.1 → 10.38.0-rc.3
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.
- package/app/components/Viewer/ObjectTree/Layout.vue +1 -3
- package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +0 -44
- package/app/components/Viewer/ObjectTree/Views/ModelTree.vue +45 -42
- package/app/composables/model_tree.ts +82 -0
- package/app/stores/data_style.ts +1 -0
- package/app/stores/treeview.ts +3 -22
- package/app/utils/colormap.ts +11 -6
- package/package.json +1 -1
- package/app/composables/model_collections.ts +0 -91
- package/app/composables/model_components.ts +0 -91
|
@@ -284,9 +284,7 @@ function onVerticalResizeStart(event: MouseEvent, index: number): void {
|
|
|
284
284
|
>
|
|
285
285
|
<ModelTree
|
|
286
286
|
data-testid="modelComponentsObjectTree"
|
|
287
|
-
:id="view.
|
|
288
|
-
:view-id="view.id"
|
|
289
|
-
:view-type="view.viewType"
|
|
287
|
+
:id="view.id"
|
|
290
288
|
@show-menu="emit('show-menu', $event)"
|
|
291
289
|
/>
|
|
292
290
|
</ViewerObjectTreeBox>
|
|
@@ -8,7 +8,6 @@ import CommonTreeView from "@ogw_front/components/Viewer/ObjectTree/Base/CommonT
|
|
|
8
8
|
import type { DisplayItem } from "@ogw_front/composables/virtual_tree";
|
|
9
9
|
import ObjectTreeControls from "@ogw_front/components/Viewer/ObjectTree/Base/Controls.vue";
|
|
10
10
|
import ObjectTreeItemLabel from "@ogw_front/components/Viewer/ObjectTree/Base/ItemLabel.vue";
|
|
11
|
-
import { consola } from "consola";
|
|
12
11
|
import { useDataStore } from "@ogw_front/stores/data";
|
|
13
12
|
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
14
13
|
import { useHoverhighlight } from "@ogw_front/composables/hover_highlight";
|
|
@@ -110,31 +109,6 @@ function isModel(item: TreeGroupItem): boolean {
|
|
|
110
109
|
);
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
const hasCollectionsMap = reactive<Record<string, boolean>>({});
|
|
114
|
-
|
|
115
|
-
watch(
|
|
116
|
-
() => treeviewStore.items,
|
|
117
|
-
async (newItems) => {
|
|
118
|
-
const models = newItems
|
|
119
|
-
.flatMap((group) => group.children || [])
|
|
120
|
-
.filter((item) => isModel(item));
|
|
121
|
-
const fetchPromises = models.map(async (model) => {
|
|
122
|
-
if (hasCollectionsMap[model.id] === undefined) {
|
|
123
|
-
hasCollectionsMap[model.id] = false;
|
|
124
|
-
try {
|
|
125
|
-
const hasCollections = await dataStore.hasCollectionComponents(model.id);
|
|
126
|
-
hasCollectionsMap[model.id] = hasCollections;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
consola.error("Failed to check collections", error);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
await Promise.all(fetchPromises);
|
|
134
|
-
},
|
|
135
|
-
{ immediate: true, deep: true },
|
|
136
|
-
);
|
|
137
|
-
|
|
138
112
|
function handleHoverEnter({
|
|
139
113
|
item,
|
|
140
114
|
immediate = false,
|
|
@@ -245,29 +219,11 @@ function expandAll(): void {
|
|
|
245
219
|
item.id as string,
|
|
246
220
|
item.title as string | undefined,
|
|
247
221
|
item.geode_object_type as string,
|
|
248
|
-
'model_components',
|
|
249
222
|
)
|
|
250
223
|
"
|
|
251
224
|
>
|
|
252
225
|
<v-icon size="18">mdi-magnify-expand</v-icon>
|
|
253
226
|
</v-btn>
|
|
254
|
-
<v-btn
|
|
255
|
-
v-if="isModel(item as unknown as TreeGroupItem) && hasCollectionsMap[item.id as string]"
|
|
256
|
-
data-testid="expandModelCollectionsButton"
|
|
257
|
-
icon="mdi-format-list-group"
|
|
258
|
-
size="medium"
|
|
259
|
-
class="ml-2"
|
|
260
|
-
variant="text"
|
|
261
|
-
v-tooltip="'Model\'s collections'"
|
|
262
|
-
@click.stop="
|
|
263
|
-
treeviewStore.displayAdditionalTree(
|
|
264
|
-
item.id as string,
|
|
265
|
-
item.title as string | undefined,
|
|
266
|
-
item.geode_object_type as string,
|
|
267
|
-
'model_collections',
|
|
268
|
-
)
|
|
269
|
-
"
|
|
270
|
-
/>
|
|
271
227
|
</template>
|
|
272
228
|
</template>
|
|
273
229
|
</CommonTreeView>
|
|
@@ -3,24 +3,19 @@ import { sortAndFormatItems, useTreeFilter } from "@ogw_front/composables/tree_f
|
|
|
3
3
|
import CommonTreeView from "@ogw_front/components/Viewer/ObjectTree/Base/CommonTreeView.vue";
|
|
4
4
|
import type { DisplayItem } from "@ogw_front/composables/virtual_tree";
|
|
5
5
|
import FetchingData from "@ogw_front/components/FetchingData.vue";
|
|
6
|
-
import { MESH_COMPONENT_TYPES } from "@ogw_front/utils/default_styles";
|
|
7
6
|
import type { ModelComponentMenuPayload } from "@ogw_front/utils/treeview";
|
|
8
7
|
import ObjectTreeControls from "@ogw_front/components/Viewer/ObjectTree/Base/Controls.vue";
|
|
9
8
|
import ObjectTreeItemLabel from "@ogw_front/components/Viewer/ObjectTree/Base/ItemLabel.vue";
|
|
10
9
|
import { useHoverhighlight } from "@ogw_front/composables/hover_highlight";
|
|
11
10
|
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
12
|
-
import {
|
|
13
|
-
import { useModelComponents } from "@ogw_front/composables/model_components";
|
|
11
|
+
import { useModelTree } from "@ogw_front/composables/model_tree";
|
|
14
12
|
import { useTreeviewStore } from "@ogw_front/stores/treeview";
|
|
15
13
|
|
|
16
14
|
interface Props {
|
|
17
15
|
id: string;
|
|
18
|
-
viewId?: string;
|
|
19
|
-
viewType?: string;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
const { id
|
|
23
|
-
const actualViewId = viewId || id;
|
|
18
|
+
const { id } = defineProps<Props>();
|
|
24
19
|
|
|
25
20
|
interface CollectionTreeItem {
|
|
26
21
|
raw?: CollectionTreeItem;
|
|
@@ -41,29 +36,20 @@ const emit = defineEmits<Emits>();
|
|
|
41
36
|
|
|
42
37
|
const treeviewStore = useTreeviewStore();
|
|
43
38
|
|
|
44
|
-
const isCollections = computed(() => viewType === "model_collections");
|
|
45
|
-
|
|
46
|
-
const composable = isCollections.value ? useModelCollections(id) : useModelComponents(id);
|
|
47
39
|
const {
|
|
48
|
-
|
|
40
|
+
isLoading,
|
|
49
41
|
localCategories,
|
|
42
|
+
cache,
|
|
43
|
+
collectionTypes,
|
|
50
44
|
selection: visibleComponents,
|
|
51
45
|
updateVisibility,
|
|
52
|
-
} =
|
|
53
|
-
|
|
54
|
-
const cache = computed(
|
|
55
|
-
() =>
|
|
56
|
-
("componentsCache" in composable ? composable.componentsCache.value : undefined) ??
|
|
57
|
-
("collectionsCache" in composable ? composable.collectionsCache.value : undefined),
|
|
58
|
-
);
|
|
46
|
+
} = useModelTree(id);
|
|
59
47
|
|
|
60
|
-
const currentView = computed(() =>
|
|
61
|
-
treeviewStore.opened_views.find((view) => view.id === actualViewId),
|
|
62
|
-
);
|
|
48
|
+
const currentView = computed(() => treeviewStore.opened_views.find((view) => view.id === id));
|
|
63
49
|
|
|
64
50
|
const opened = computed<string[]>({
|
|
65
51
|
get: () => currentView.value?.opened || [],
|
|
66
|
-
set: (val) => treeviewStore.setOpened(
|
|
52
|
+
set: (val) => treeviewStore.setOpened(id, val),
|
|
67
53
|
});
|
|
68
54
|
|
|
69
55
|
const {
|
|
@@ -84,25 +70,37 @@ function onUpdateSelection(newSelection: string[]): void {
|
|
|
84
70
|
|
|
85
71
|
const visibleSelection = computed<string[]>(() => applySearchFilter(visibleComponents.value, []));
|
|
86
72
|
|
|
73
|
+
function matchesSearch(item: CollectionTreeItem, query: string): boolean {
|
|
74
|
+
return (
|
|
75
|
+
(item.title ?? "").toLowerCase().includes(query) ||
|
|
76
|
+
item.id.toLowerCase().includes(query) ||
|
|
77
|
+
(item.children ?? []).some((child) => matchesSearch(child, query))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function searchGroups(query: string): CollectionTreeItem[] {
|
|
82
|
+
const groups = (cache.value ?? {}) as Record<string, CollectionTreeItem[]>;
|
|
83
|
+
const result: CollectionTreeItem[] = [];
|
|
84
|
+
for (const [type, components] of Object.entries(groups)) {
|
|
85
|
+
const matches = components.filter((component) => matchesSearch(component, query));
|
|
86
|
+
if (matches.length > 0) {
|
|
87
|
+
result.push({
|
|
88
|
+
id: type,
|
|
89
|
+
title: `${type}s (${matches.length})`,
|
|
90
|
+
children: sortAndFormatItems(matches, sortType.value) as unknown as CollectionTreeItem[],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
const itemsForTreeView = computed<CollectionTreeItem[]>(() => {
|
|
88
|
-
if (search.value
|
|
98
|
+
if (search.value) {
|
|
89
99
|
const query = search.value.toLowerCase();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
component.title.toLowerCase().includes(query) ||
|
|
95
|
-
component.id.toLowerCase().includes(query),
|
|
96
|
-
);
|
|
97
|
-
if (matches.length > 0) {
|
|
98
|
-
result.push({
|
|
99
|
-
id: type,
|
|
100
|
-
title: `${type}s (${matches.length})`,
|
|
101
|
-
children: sortAndFormatItems(matches, sortType.value) as unknown as CollectionTreeItem[],
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return result;
|
|
100
|
+
return sortAndFormatItems(
|
|
101
|
+
searchGroups(query),
|
|
102
|
+
sortType.value,
|
|
103
|
+
) as unknown as CollectionTreeItem[];
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
const result: CollectionTreeItem[] = [];
|
|
@@ -127,9 +125,13 @@ function extractComponentIds(node: CollectionTreeItem): string[] {
|
|
|
127
125
|
return [node.id];
|
|
128
126
|
}
|
|
129
127
|
|
|
128
|
+
function isCollectionNode(item: CollectionTreeItem): boolean {
|
|
129
|
+
return collectionTypes.value.has(item.category ?? item.id);
|
|
130
|
+
}
|
|
131
|
+
|
|
130
132
|
function showContextMenu(event: MouseEvent, item: CollectionTreeItem): void {
|
|
131
133
|
const actualItem = item.raw || item;
|
|
132
|
-
if (
|
|
134
|
+
if (isCollectionNode(actualItem)) {
|
|
133
135
|
emit("show-menu", {
|
|
134
136
|
event,
|
|
135
137
|
itemId: id,
|
|
@@ -217,9 +219,10 @@ function getLeafViewerIds(item: CollectionTreeItem): number[] {
|
|
|
217
219
|
@expand-all="expandAll"
|
|
218
220
|
/>
|
|
219
221
|
|
|
220
|
-
<FetchingData v-if="
|
|
222
|
+
<FetchingData v-if="isLoading" :size="48" :width="4" text="" />
|
|
221
223
|
|
|
222
224
|
<CommonTreeView
|
|
225
|
+
v-else
|
|
223
226
|
:selected="visibleSelection"
|
|
224
227
|
v-model:opened="opened"
|
|
225
228
|
v-model:active="treeviewStore.activeItems"
|
|
@@ -233,7 +236,7 @@ function getLeafViewerIds(item: CollectionTreeItem): number[] {
|
|
|
233
236
|
class="transparent-treeview virtual-tree-height"
|
|
234
237
|
@update:selected="(val) => onUpdateSelection(val as string[])"
|
|
235
238
|
@click:item="onUpdateSelection([$event.id as string, ...visibleComponents])"
|
|
236
|
-
@update:scroll-top="treeviewStore.setScrollTop(
|
|
239
|
+
@update:scroll-top="treeviewStore.setScrollTop(id, $event)"
|
|
237
240
|
@hover:enter="({ item }) => handleHoverEnter({ item: item as unknown as CollectionTreeItem })"
|
|
238
241
|
@hover:leave="handleHoverLeave"
|
|
239
242
|
@contextmenu="showContextMenu($event.event, $event.item as unknown as CollectionTreeItem)"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CollectionComponent,
|
|
3
|
+
CollectionComponentGroup,
|
|
4
|
+
} from "@ogw_front/stores/data_helpers/collections";
|
|
5
|
+
import type {
|
|
6
|
+
FormattedComponent,
|
|
7
|
+
FormattedComponentGroup,
|
|
8
|
+
} from "@ogw_front/stores/data_helpers/mesh";
|
|
9
|
+
import { compareSelections } from "@ogw_front/utils/treeview";
|
|
10
|
+
import { useDataStore } from "@ogw_front/stores/data";
|
|
11
|
+
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
12
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
13
|
+
|
|
14
|
+
type ModelTreeCategory = FormattedComponentGroup | CollectionComponentGroup;
|
|
15
|
+
type ModelTreeCache = Record<string, (FormattedComponent | CollectionComponent)[]>;
|
|
16
|
+
|
|
17
|
+
function useModelVisibility(modelId: string): {
|
|
18
|
+
selection: typeof selection;
|
|
19
|
+
updateVisibility: typeof updateVisibility;
|
|
20
|
+
} {
|
|
21
|
+
const dataStyleStore = useDataStyleStore();
|
|
22
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
23
|
+
const selection = dataStyleStore.visibleMeshComponents(modelId);
|
|
24
|
+
|
|
25
|
+
async function updateVisibility(current: readonly string[]): Promise<void> {
|
|
26
|
+
const { added, removed } = compareSelections([...current], selection.value);
|
|
27
|
+
if (added.length > 0) {
|
|
28
|
+
await dataStyleStore.setModelComponentsVisibility(modelId, added, true);
|
|
29
|
+
}
|
|
30
|
+
if (removed.length > 0) {
|
|
31
|
+
await dataStyleStore.setModelComponentsVisibility(modelId, removed, false);
|
|
32
|
+
}
|
|
33
|
+
if (added.length > 0 || removed.length > 0) {
|
|
34
|
+
await hybridViewerStore.remoteRender();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { selection, updateVisibility };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useModelTree(modelId: string): {
|
|
42
|
+
isLoading: ComputedRef<boolean>;
|
|
43
|
+
localCategories: Ref<ModelTreeCategory[]>;
|
|
44
|
+
cache: Ref<ModelTreeCache | undefined>;
|
|
45
|
+
collectionTypes: Ref<Set<string>>;
|
|
46
|
+
} & ReturnType<typeof useModelVisibility> {
|
|
47
|
+
const dataStore = useDataStore();
|
|
48
|
+
|
|
49
|
+
const meshGroups = dataStore.refFormatedMeshComponents(modelId);
|
|
50
|
+
const collectionGroups = dataStore.refFormatedCollectionComponents(modelId);
|
|
51
|
+
const cache = ref<ModelTreeCache | undefined>(undefined);
|
|
52
|
+
const localCategories = ref<ModelTreeCategory[]>([]);
|
|
53
|
+
const collectionTypes = ref<Set<string>>(new Set());
|
|
54
|
+
|
|
55
|
+
// The mesh cache also groups collection components by their type; the collections cache overrides those keys with the real collection -> mesh components hierarchy.
|
|
56
|
+
watch(
|
|
57
|
+
[meshGroups, collectionGroups],
|
|
58
|
+
async ([mesh, collections]) => {
|
|
59
|
+
if (!mesh || !collections) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const [meshByType, collectionsByType] = await Promise.all([
|
|
63
|
+
dataStore.fetchAllMeshComponents(modelId),
|
|
64
|
+
dataStore.fetchAllCollectionComponents(modelId),
|
|
65
|
+
]);
|
|
66
|
+
cache.value = markRaw({ ...meshByType, ...collectionsByType });
|
|
67
|
+
localCategories.value = [...mesh, ...collections];
|
|
68
|
+
collectionTypes.value = new Set(Object.keys(collectionsByType));
|
|
69
|
+
},
|
|
70
|
+
{ immediate: true },
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const isLoading = computed(() => cache.value === undefined);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
isLoading,
|
|
77
|
+
localCategories,
|
|
78
|
+
cache,
|
|
79
|
+
collectionTypes,
|
|
80
|
+
...useModelVisibility(modelId),
|
|
81
|
+
};
|
|
82
|
+
}
|
package/app/stores/data_style.ts
CHANGED
|
@@ -132,6 +132,7 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
132
132
|
...component_style_promises,
|
|
133
133
|
...model_component_type_style_promises,
|
|
134
134
|
]);
|
|
135
|
+
await dataStyleState.loadFromDatabase();
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
async function applyAllStylesFromState(): Promise<void> {
|
package/app/stores/treeview.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { defineStore } from "pinia";
|
|
|
6
6
|
|
|
7
7
|
import type { Table } from "dexie";
|
|
8
8
|
import { ref, watch } from "vue";
|
|
9
|
-
import { compareSelections } from "@ogw_front/utils/treeview";
|
|
10
9
|
import { database } from "@ogw_internal/database/database";
|
|
11
10
|
|
|
12
11
|
const PANEL_WIDTH = 300;
|
|
@@ -17,8 +16,6 @@ interface OpenedView {
|
|
|
17
16
|
title: string;
|
|
18
17
|
scrollTop: number;
|
|
19
18
|
opened: string[];
|
|
20
|
-
modelId?: string;
|
|
21
|
-
viewType?: string;
|
|
22
19
|
geode_object_type?: string;
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -144,18 +141,6 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
144
141
|
opened_views.value = opened_views.value.filter((view: ReadonlyOpenedView) => view.id !== id);
|
|
145
142
|
}
|
|
146
143
|
|
|
147
|
-
watch(selection, (current: readonly string[], previous: readonly string[]) => {
|
|
148
|
-
const { removed } = compareSelections(current, previous);
|
|
149
|
-
for (const id of removed) {
|
|
150
|
-
const index = opened_views.value.findIndex(
|
|
151
|
-
(view: ReadonlyOpenedView) => view.type === "component" && view.id === id,
|
|
152
|
-
);
|
|
153
|
-
if (index !== -1) {
|
|
154
|
-
closeView(id);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
144
|
function toggleView(id: string): void {
|
|
160
145
|
const index = opened_views.value.findIndex((view: ReadonlyOpenedView) => view.id === id);
|
|
161
146
|
if (index !== -1) {
|
|
@@ -229,20 +214,16 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
229
214
|
id: string,
|
|
230
215
|
title: string | undefined,
|
|
231
216
|
geodeObjectType: string,
|
|
232
|
-
viewType = "model_components",
|
|
233
217
|
): void {
|
|
234
|
-
const
|
|
235
|
-
const index = opened_views.value.findIndex((view: ReadonlyOpenedView) => view.id === viewId);
|
|
218
|
+
const index = opened_views.value.findIndex((view: ReadonlyOpenedView) => view.id === id);
|
|
236
219
|
if (index !== -1) {
|
|
237
|
-
closeView(
|
|
220
|
+
closeView(id);
|
|
238
221
|
return;
|
|
239
222
|
}
|
|
240
223
|
additionalPanelWidth.value = panelWidth.value;
|
|
241
224
|
opened_views.value.push({
|
|
242
225
|
type: "component",
|
|
243
|
-
id
|
|
244
|
-
modelId: id,
|
|
245
|
-
viewType,
|
|
226
|
+
id,
|
|
246
227
|
title: title ?? id,
|
|
247
228
|
geode_object_type: geodeObjectType,
|
|
248
229
|
scrollTop: 0,
|
package/app/utils/colormap.ts
CHANGED
|
@@ -40,14 +40,19 @@ const THREE = 3;
|
|
|
40
40
|
|
|
41
41
|
function buildColorTable(rgbPoints: readonly number[], width: number): Float32Array {
|
|
42
42
|
const lut = vtkColorTransferFunction();
|
|
43
|
+
// oxlint-disable id-length
|
|
44
|
+
const nodes = [];
|
|
43
45
|
for (let pointIdx = 0; pointIdx < rgbPoints.length; pointIdx += 4) {
|
|
44
|
-
|
|
45
|
-
rgbPoints[pointIdx] ?? 0,
|
|
46
|
-
rgbPoints[pointIdx + 1] ?? 0,
|
|
47
|
-
rgbPoints[pointIdx + 2] ?? 0,
|
|
48
|
-
rgbPoints[pointIdx + THREE] ?? 0,
|
|
49
|
-
|
|
46
|
+
nodes.push({
|
|
47
|
+
x: rgbPoints[pointIdx] ?? 0,
|
|
48
|
+
r: rgbPoints[pointIdx + 1] ?? 0,
|
|
49
|
+
g: rgbPoints[pointIdx + 2] ?? 0,
|
|
50
|
+
b: rgbPoints[pointIdx + THREE] ?? 0,
|
|
51
|
+
midpoint: 0.5,
|
|
52
|
+
sharpness: 0,
|
|
53
|
+
});
|
|
50
54
|
}
|
|
55
|
+
lut.setNodes(nodes);
|
|
51
56
|
return lut.getUint8Table(rgbPoints[0] ?? 0, rgbPoints.at(-LAST_POINT_OFFSET) ?? 0, width, true);
|
|
52
57
|
}
|
|
53
58
|
|
package/package.json
CHANGED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CollectionComponent,
|
|
3
|
-
CollectionComponentGroup,
|
|
4
|
-
} from "@ogw_front/stores/data_helpers/collections";
|
|
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 { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
9
|
-
|
|
10
|
-
// The `watch` callback receives the freshly-fetched collection groups purely for reading (matching against and copying into `localCategories`, never mutated in place), so it's typed with its own fully-readonly mirror of `CollectionComponentGroup` rather than that (intentionally mutable, see `existing.title` below) store type directly.
|
|
11
|
-
interface ReadonlyCollectionComponentGroup {
|
|
12
|
-
readonly id: string;
|
|
13
|
-
readonly title: string;
|
|
14
|
-
readonly children: readonly CollectionComponent[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useModelCollections(viewId: string): {
|
|
18
|
-
items: typeof items;
|
|
19
|
-
collectionsCache: typeof collectionsCache;
|
|
20
|
-
localCategories: typeof localCategories;
|
|
21
|
-
selection: typeof selection;
|
|
22
|
-
updateVisibility: typeof updateVisibility;
|
|
23
|
-
} {
|
|
24
|
-
const dataStore = useDataStore();
|
|
25
|
-
const dataStyleStore = useDataStyleStore();
|
|
26
|
-
const hybridViewerStore = useHybridViewerStore();
|
|
27
|
-
|
|
28
|
-
const items = dataStore.refFormatedCollectionComponents(viewId);
|
|
29
|
-
const collectionsCache = ref<Record<string, CollectionComponent[]> | undefined>(undefined);
|
|
30
|
-
const localCategories = ref<CollectionComponentGroup[]>([]);
|
|
31
|
-
|
|
32
|
-
onMounted(async () => {
|
|
33
|
-
const data = await dataStore.fetchAllCollectionComponents(viewId);
|
|
34
|
-
collectionsCache.value = markRaw(data);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
watch(
|
|
38
|
-
items,
|
|
39
|
-
async (newItems: readonly ReadonlyCollectionComponentGroup[] | undefined) => {
|
|
40
|
-
if (!newItems) {
|
|
41
|
-
localCategories.value = [];
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const data = await dataStore.fetchAllCollectionComponents(viewId);
|
|
46
|
-
collectionsCache.value = markRaw(data);
|
|
47
|
-
|
|
48
|
-
localCategories.value = newItems.map((newCategory: ReadonlyCollectionComponentGroup) => {
|
|
49
|
-
const existing = localCategories.value.find(
|
|
50
|
-
(category: ReadonlyCollectionComponentGroup) => category.id === newCategory.id,
|
|
51
|
-
);
|
|
52
|
-
if (existing) {
|
|
53
|
-
existing.title = newCategory.title || newCategory.id;
|
|
54
|
-
return existing;
|
|
55
|
-
}
|
|
56
|
-
return reactive({
|
|
57
|
-
...newCategory,
|
|
58
|
-
title: newCategory.title || newCategory.id,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
{ immediate: true },
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const selection = dataStyleStore.visibleMeshComponents(viewId);
|
|
66
|
-
|
|
67
|
-
async function updateVisibility(current: readonly string[]): Promise<void> {
|
|
68
|
-
const previous = selection.value;
|
|
69
|
-
const { added, removed } = compareSelections([...current], previous);
|
|
70
|
-
|
|
71
|
-
if (added.length === 0 && removed.length === 0) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (added.length > 0) {
|
|
76
|
-
await dataStyleStore.setModelComponentsVisibility(viewId, added, true);
|
|
77
|
-
}
|
|
78
|
-
if (removed.length > 0) {
|
|
79
|
-
await dataStyleStore.setModelComponentsVisibility(viewId, removed, false);
|
|
80
|
-
}
|
|
81
|
-
await hybridViewerStore.remoteRender();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
items,
|
|
86
|
-
collectionsCache,
|
|
87
|
-
localCategories,
|
|
88
|
-
selection,
|
|
89
|
-
updateVisibility,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
FormattedComponent,
|
|
3
|
-
FormattedComponentGroup,
|
|
4
|
-
} from "@ogw_front/stores/data_helpers/mesh";
|
|
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 { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
9
|
-
|
|
10
|
-
// The `watch` callback receives the freshly-fetched component groups purely for reading (matching against and copying into `localCategories`, never mutated in place), so it's typed with its own fully-readonly mirror of `FormattedComponentGroup` rather than that (intentionally mutable, see `existing.title` below) store type directly.
|
|
11
|
-
interface ReadonlyFormattedComponentGroup {
|
|
12
|
-
readonly id: string;
|
|
13
|
-
readonly title: string;
|
|
14
|
-
readonly children: readonly FormattedComponent[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useModelComponents(viewId: string): {
|
|
18
|
-
items: typeof items;
|
|
19
|
-
componentsCache: typeof componentsCache;
|
|
20
|
-
localCategories: typeof localCategories;
|
|
21
|
-
selection: typeof selection;
|
|
22
|
-
updateVisibility: typeof updateVisibility;
|
|
23
|
-
} {
|
|
24
|
-
const dataStore = useDataStore();
|
|
25
|
-
const dataStyleStore = useDataStyleStore();
|
|
26
|
-
const hybridViewerStore = useHybridViewerStore();
|
|
27
|
-
|
|
28
|
-
const items = dataStore.refFormatedMeshComponents(viewId);
|
|
29
|
-
const componentsCache = ref<Record<string, FormattedComponent[]> | undefined>(undefined);
|
|
30
|
-
const localCategories = ref<FormattedComponentGroup[]>([]);
|
|
31
|
-
|
|
32
|
-
onMounted(async () => {
|
|
33
|
-
const data = await dataStore.fetchAllMeshComponents(viewId);
|
|
34
|
-
componentsCache.value = markRaw(data);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
watch(
|
|
38
|
-
items,
|
|
39
|
-
async (newItems: readonly ReadonlyFormattedComponentGroup[] | undefined) => {
|
|
40
|
-
if (!newItems) {
|
|
41
|
-
localCategories.value = [];
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const data = await dataStore.fetchAllMeshComponents(viewId);
|
|
46
|
-
componentsCache.value = markRaw(data);
|
|
47
|
-
|
|
48
|
-
localCategories.value = newItems.map((newCategory: ReadonlyFormattedComponentGroup) => {
|
|
49
|
-
const existing = localCategories.value.find(
|
|
50
|
-
(category: ReadonlyFormattedComponentGroup) => category.id === newCategory.id,
|
|
51
|
-
);
|
|
52
|
-
if (existing) {
|
|
53
|
-
existing.title = newCategory.title || newCategory.id;
|
|
54
|
-
return existing;
|
|
55
|
-
}
|
|
56
|
-
return reactive({
|
|
57
|
-
...newCategory,
|
|
58
|
-
title: newCategory.title || newCategory.id,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
{ immediate: true },
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const selection = dataStyleStore.visibleMeshComponents(viewId);
|
|
66
|
-
|
|
67
|
-
async function updateVisibility(current: readonly string[]): Promise<void> {
|
|
68
|
-
const previous = selection.value;
|
|
69
|
-
const { added, removed } = compareSelections([...current], previous);
|
|
70
|
-
|
|
71
|
-
if (added.length === 0 && removed.length === 0) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (added.length > 0) {
|
|
76
|
-
await dataStyleStore.setModelComponentsVisibility(viewId, added, true);
|
|
77
|
-
}
|
|
78
|
-
if (removed.length > 0) {
|
|
79
|
-
await dataStyleStore.setModelComponentsVisibility(viewId, removed, false);
|
|
80
|
-
}
|
|
81
|
-
await hybridViewerStore.remoteRender();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
items,
|
|
86
|
-
componentsCache,
|
|
87
|
-
localCategories,
|
|
88
|
-
selection,
|
|
89
|
-
updateVisibility,
|
|
90
|
-
};
|
|
91
|
-
}
|