@geode/opengeodeweb-front 10.20.1 → 10.21.0-rc.1
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 +6 -3
- package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +47 -1
- package/app/components/Viewer/ObjectTree/Views/ModelCollections.vue +222 -0
- package/app/components/Viewer/ObjectTree/Views/ModelComponents.vue +10 -4
- package/app/composables/model_collections.js +72 -0
- package/app/composables/model_components.js +5 -1
- package/app/composables/virtual_tree.js +8 -7
- package/app/stores/data.js +39 -111
- package/app/stores/data_helpers/collections.js +102 -0
- package/app/stores/data_helpers/mesh.js +122 -0
- package/app/stores/treeview.js +18 -8
- package/internal/stores/hybrid_viewer_camera_animation.js +24 -2
- package/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import GlobalObjects from "@ogw_front/components/Viewer/ObjectTree/Views/GlobalObjects.vue";
|
|
3
|
+
import ModelCollections from "@ogw_front/components/Viewer/ObjectTree/Views/ModelCollections.vue";
|
|
3
4
|
import ModelComponents from "@ogw_front/components/Viewer/ObjectTree/Views/ModelComponents.vue";
|
|
4
5
|
import ViewerObjectTreeBox from "@ogw_front/components/Viewer/ObjectTree/Box.vue";
|
|
5
6
|
import { geode_objects } from "@ogw_front/assets/geode_objects";
|
|
@@ -263,15 +264,17 @@ function onVerticalResizeStart(event, index) {
|
|
|
263
264
|
:icon="geode_objects[view.geode_object_type]?.image"
|
|
264
265
|
:scroll-top="view.scrollTop"
|
|
265
266
|
closable
|
|
266
|
-
:border-radius="index === additionalViews.length - 1 ? '
|
|
267
|
+
:border-radius="`0 ${index === 0 ? '16px' : '0'} ${index === additionalViews.length - 1 ? '16px' : '0'} 0`"
|
|
267
268
|
:border-left="false"
|
|
268
269
|
@close="treeviewStore.closeView(view.id)"
|
|
269
270
|
@dragstart="onDragStart(index + 1)"
|
|
270
271
|
@update:scroll-top="treeviewStore.setScrollTop(view.id, $event)"
|
|
271
272
|
>
|
|
272
|
-
<
|
|
273
|
+
<component
|
|
274
|
+
:is="view.viewType === 'model_collections' ? ModelCollections : ModelComponents"
|
|
273
275
|
data-testid="modelComponentsObjectTree"
|
|
274
|
-
:id="view.id"
|
|
276
|
+
:id="view.modelId || view.id"
|
|
277
|
+
:view-id="view.id"
|
|
275
278
|
@show-menu="emit('show-menu', $event)"
|
|
276
279
|
/>
|
|
277
280
|
</ViewerObjectTreeBox>
|
|
@@ -75,6 +75,31 @@ function isModel(item) {
|
|
|
75
75
|
);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
const hasCollectionsMap = reactive({});
|
|
79
|
+
|
|
80
|
+
watch(
|
|
81
|
+
() => treeviewStore.items,
|
|
82
|
+
async (newItems) => {
|
|
83
|
+
const models = newItems
|
|
84
|
+
.flatMap((group) => group.children || [])
|
|
85
|
+
.filter((item) => isModel(item));
|
|
86
|
+
const fetchPromises = models.map(async (model) => {
|
|
87
|
+
if (hasCollectionsMap[model.id] === undefined) {
|
|
88
|
+
hasCollectionsMap[model.id] = false;
|
|
89
|
+
try {
|
|
90
|
+
const hasCollections = await dataStore.hasCollectionComponents(model.id);
|
|
91
|
+
hasCollectionsMap[model.id] = hasCollections;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error("Failed to check collections", error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await Promise.all(fetchPromises);
|
|
99
|
+
},
|
|
100
|
+
{ immediate: true, deep: true },
|
|
101
|
+
);
|
|
102
|
+
|
|
78
103
|
function handleHoverEnter({ item, immediate = false }) {
|
|
79
104
|
const actualItem = item.raw || item;
|
|
80
105
|
|
|
@@ -169,7 +194,28 @@ function expandAll() {
|
|
|
169
194
|
variant="text"
|
|
170
195
|
v-tooltip="'Model\'s mesh components'"
|
|
171
196
|
@click.stop="
|
|
172
|
-
treeviewStore.displayAdditionalTree(
|
|
197
|
+
treeviewStore.displayAdditionalTree(
|
|
198
|
+
item.id,
|
|
199
|
+
item.title,
|
|
200
|
+
item.geode_object_type,
|
|
201
|
+
'model_components',
|
|
202
|
+
)
|
|
203
|
+
"
|
|
204
|
+
/>
|
|
205
|
+
<v-btn
|
|
206
|
+
v-if="isModel(item) && hasCollectionsMap[item.id]"
|
|
207
|
+
icon="mdi-format-list-group"
|
|
208
|
+
size="medium"
|
|
209
|
+
class="ml-2"
|
|
210
|
+
variant="text"
|
|
211
|
+
v-tooltip="'Model\'s collections'"
|
|
212
|
+
@click.stop="
|
|
213
|
+
treeviewStore.displayAdditionalTree(
|
|
214
|
+
item.id,
|
|
215
|
+
item.title,
|
|
216
|
+
item.geode_object_type,
|
|
217
|
+
'model_collections',
|
|
218
|
+
)
|
|
173
219
|
"
|
|
174
220
|
/>
|
|
175
221
|
</template>
|
|
@@ -0,0 +1,222 @@
|
|
|
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";
|
|
4
|
+
import FetchingData from "@ogw_front/components/FetchingData.vue";
|
|
5
|
+
import ObjectTreeControls from "@ogw_front/components/Viewer/ObjectTree/Base/Controls.vue";
|
|
6
|
+
import ObjectTreeItemLabel from "@ogw_front/components/Viewer/ObjectTree/Base/ItemLabel.vue";
|
|
7
|
+
import { useHoverhighlight } from "@ogw_front/composables/hover_highlight";
|
|
8
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
9
|
+
import { useModelCollections } from "@ogw_front/composables/model_collections";
|
|
10
|
+
import { useTreeviewStore } from "@ogw_front/stores/treeview";
|
|
11
|
+
|
|
12
|
+
const { id, viewId } = defineProps({
|
|
13
|
+
id: { type: String, required: true },
|
|
14
|
+
viewId: { type: String, required: false },
|
|
15
|
+
});
|
|
16
|
+
const actualViewId = viewId || id;
|
|
17
|
+
const { onHoverEnter, onHoverLeave } = useHoverhighlight();
|
|
18
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
19
|
+
const emit = defineEmits(["show-menu"]);
|
|
20
|
+
|
|
21
|
+
const treeviewStore = useTreeviewStore();
|
|
22
|
+
const {
|
|
23
|
+
items: rawItems,
|
|
24
|
+
collectionsCache: componentsCache,
|
|
25
|
+
localCategories,
|
|
26
|
+
selection: visibleComponents,
|
|
27
|
+
updateVisibility,
|
|
28
|
+
} = useModelCollections(id);
|
|
29
|
+
|
|
30
|
+
const currentView = computed(() =>
|
|
31
|
+
treeviewStore.opened_views.find((view) => view.id === actualViewId),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const opened = computed({
|
|
35
|
+
get: () => currentView.value?.opened || [],
|
|
36
|
+
set: (val) => treeviewStore.setOpened(actualViewId, val),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
search,
|
|
41
|
+
sortType,
|
|
42
|
+
filterOptions,
|
|
43
|
+
processedItems: filteredCategories,
|
|
44
|
+
availableFilterOptions,
|
|
45
|
+
toggleSort,
|
|
46
|
+
customFilter,
|
|
47
|
+
applySearchFilter,
|
|
48
|
+
} = useTreeFilter(localCategories);
|
|
49
|
+
|
|
50
|
+
function onUpdateSelection(newSelection) {
|
|
51
|
+
const finalSelection = applySearchFilter(newSelection, visibleComponents.value);
|
|
52
|
+
updateVisibility(finalSelection);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const visibleSelection = computed(() => applySearchFilter(visibleComponents.value, []));
|
|
56
|
+
|
|
57
|
+
const itemsForTreeView = computed(() => {
|
|
58
|
+
if (search.value && componentsCache.value) {
|
|
59
|
+
const query = search.value.toLowerCase();
|
|
60
|
+
const result = [];
|
|
61
|
+
for (const type of Object.keys(componentsCache.value)) {
|
|
62
|
+
const matches = componentsCache.value[type].filter(
|
|
63
|
+
(component) =>
|
|
64
|
+
component.title.toLowerCase().includes(query) ||
|
|
65
|
+
component.id.toLowerCase().includes(query),
|
|
66
|
+
);
|
|
67
|
+
if (matches.length > 0) {
|
|
68
|
+
result.push({
|
|
69
|
+
id: type,
|
|
70
|
+
title: `${type}s (${matches.length})`,
|
|
71
|
+
children: sortAndFormatItems(matches, sortType.value),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = [];
|
|
79
|
+
for (const category of filteredCategories.value) {
|
|
80
|
+
result.push({
|
|
81
|
+
...category,
|
|
82
|
+
children: sortAndFormatItems(componentsCache.value?.[category.id], sortType.value),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function showContextMenu(event, item) {
|
|
89
|
+
const actualItem = item.raw || item;
|
|
90
|
+
emit("show-menu", {
|
|
91
|
+
event,
|
|
92
|
+
itemId: actualItem.category ? actualItem.id : id,
|
|
93
|
+
context_type: actualItem.category ? "model_component" : "model_component_type",
|
|
94
|
+
modelId: id,
|
|
95
|
+
modelComponentType: actualItem.category ? undefined : actualItem.id,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function handleHoverEnter({ item, immediate = false }) {
|
|
100
|
+
const actualItem = item.raw || item;
|
|
101
|
+
|
|
102
|
+
if (!actualItem.category && (!actualItem.children || actualItem.children.length === 0)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const viewerIdsToHover = extractIds(actualItem);
|
|
107
|
+
|
|
108
|
+
onHoverEnter(id, () => viewerIdsToHover, "model", immediate);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handleHoverLeave() {
|
|
112
|
+
onHoverLeave(id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function expandAll() {
|
|
116
|
+
const allIds = [];
|
|
117
|
+
function traverse(itemsList) {
|
|
118
|
+
for (const item of itemsList) {
|
|
119
|
+
if (item.children && item.children.length > 0) {
|
|
120
|
+
allIds.push(item.id);
|
|
121
|
+
traverse(item.children);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
traverse(itemsForTreeView.value);
|
|
126
|
+
opened.value = allIds;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractIds(node) {
|
|
130
|
+
if (node.children && node.children.length > 0) {
|
|
131
|
+
return node.children.flatMap((child) => extractIds(child));
|
|
132
|
+
}
|
|
133
|
+
if (node.viewer_id !== undefined && node.viewer_id !== null) {
|
|
134
|
+
return [node.viewer_id];
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getLeafViewerIds(item) {
|
|
140
|
+
const actualItem = item.raw || item;
|
|
141
|
+
return extractIds(actualItem);
|
|
142
|
+
}
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<template>
|
|
146
|
+
<div class="tree-view-container">
|
|
147
|
+
<ObjectTreeControls
|
|
148
|
+
v-model:search="search"
|
|
149
|
+
:sort-type="sortType"
|
|
150
|
+
:filter-options="filterOptions"
|
|
151
|
+
:available-filter-options="availableFilterOptions"
|
|
152
|
+
:is-collapsed="opened.length === 0"
|
|
153
|
+
@toggle-sort="toggleSort"
|
|
154
|
+
@collapse-all="opened = []"
|
|
155
|
+
@expand-all="expandAll"
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
<FetchingData v-if="rawItems === undefined" :size="48" :width="4" text="" />
|
|
159
|
+
|
|
160
|
+
<CommonTreeView
|
|
161
|
+
:selected="visibleSelection"
|
|
162
|
+
v-model:opened="opened"
|
|
163
|
+
:items="itemsForTreeView"
|
|
164
|
+
:options="{
|
|
165
|
+
selection: { selectable: true, strategy: 'classic' },
|
|
166
|
+
search,
|
|
167
|
+
customFilter,
|
|
168
|
+
}"
|
|
169
|
+
:scroll-top="currentView?.scrollTop || 0"
|
|
170
|
+
class="transparent-treeview virtual-tree-height"
|
|
171
|
+
@update:selected="onUpdateSelection"
|
|
172
|
+
@click:item="onUpdateSelection([$event.id, ...visibleComponents])"
|
|
173
|
+
@update:scroll-top="treeviewStore.setScrollTop(actualViewId, $event)"
|
|
174
|
+
@hover:enter="handleHoverEnter"
|
|
175
|
+
@hover:leave="handleHoverLeave"
|
|
176
|
+
>
|
|
177
|
+
<template #title="{ item, isLeaf }">
|
|
178
|
+
<ObjectTreeItemLabel
|
|
179
|
+
:item="item"
|
|
180
|
+
:is-leaf="isLeaf"
|
|
181
|
+
show-tooltip
|
|
182
|
+
class="text-body-1"
|
|
183
|
+
@contextmenu.prevent.stop="showContextMenu($event, item)"
|
|
184
|
+
/>
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
<template #append="{ item }">
|
|
188
|
+
<v-btn
|
|
189
|
+
v-if="item.category || (item.children && item.children.length > 0)"
|
|
190
|
+
icon="mdi-target"
|
|
191
|
+
size="medium"
|
|
192
|
+
variant="text"
|
|
193
|
+
v-tooltip="'Focus camera on object'"
|
|
194
|
+
@click.stop="hybridViewerStore.focusCameraOnObject(id, getLeafViewerIds(item))"
|
|
195
|
+
/>
|
|
196
|
+
</template>
|
|
197
|
+
</CommonTreeView>
|
|
198
|
+
</div>
|
|
199
|
+
</template>
|
|
200
|
+
|
|
201
|
+
<style scoped>
|
|
202
|
+
.tree-view-container {
|
|
203
|
+
height: 100%;
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
min-height: 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.virtual-tree-height {
|
|
211
|
+
flex-grow: 1;
|
|
212
|
+
min-height: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.transparent-treeview {
|
|
216
|
+
background-color: transparent;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
:deep(.v-list-item__overlay) {
|
|
220
|
+
display: none !important;
|
|
221
|
+
}
|
|
222
|
+
</style>
|
|
@@ -9,7 +9,11 @@ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
|
9
9
|
import { useModelComponents } from "@ogw_front/composables/model_components";
|
|
10
10
|
import { useTreeviewStore } from "@ogw_front/stores/treeview";
|
|
11
11
|
|
|
12
|
-
const { id } = defineProps({
|
|
12
|
+
const { id, viewId } = defineProps({
|
|
13
|
+
id: { type: String, required: true },
|
|
14
|
+
viewId: { type: String, required: false },
|
|
15
|
+
});
|
|
16
|
+
const actualViewId = viewId || id;
|
|
13
17
|
const { onHoverEnter, onHoverLeave } = useHoverhighlight();
|
|
14
18
|
const hybridViewerStore = useHybridViewerStore();
|
|
15
19
|
const emit = defineEmits(["show-menu"]);
|
|
@@ -23,11 +27,13 @@ const {
|
|
|
23
27
|
updateVisibility,
|
|
24
28
|
} = useModelComponents(id);
|
|
25
29
|
|
|
26
|
-
const currentView = computed(() =>
|
|
30
|
+
const currentView = computed(() =>
|
|
31
|
+
treeviewStore.opened_views.find((view) => view.id === actualViewId),
|
|
32
|
+
);
|
|
27
33
|
|
|
28
34
|
const opened = computed({
|
|
29
35
|
get: () => currentView.value?.opened || [],
|
|
30
|
-
set: (val) => treeviewStore.setOpened(
|
|
36
|
+
set: (val) => treeviewStore.setOpened(actualViewId, val),
|
|
31
37
|
});
|
|
32
38
|
|
|
33
39
|
const {
|
|
@@ -155,7 +161,7 @@ function expandAll() {
|
|
|
155
161
|
class="transparent-treeview virtual-tree-height"
|
|
156
162
|
@update:selected="onUpdateSelection"
|
|
157
163
|
@click:item="onUpdateSelection([$event.id, ...visibleComponents])"
|
|
158
|
-
@update:scroll-top="treeviewStore.setScrollTop(
|
|
164
|
+
@update:scroll-top="treeviewStore.setScrollTop(actualViewId, $event)"
|
|
159
165
|
@hover:enter="handleHoverEnter"
|
|
160
166
|
@hover:leave="handleHoverLeave"
|
|
161
167
|
>
|
|
@@ -0,0 +1,72 @@
|
|
|
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 useModelCollections(viewId) {
|
|
7
|
+
const dataStore = useDataStore();
|
|
8
|
+
const dataStyleStore = useDataStyleStore();
|
|
9
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
10
|
+
|
|
11
|
+
const items = dataStore.refFormatedCollectionComponents(viewId);
|
|
12
|
+
const collectionsCache = ref(undefined);
|
|
13
|
+
const localCategories = ref([]);
|
|
14
|
+
|
|
15
|
+
onMounted(async () => {
|
|
16
|
+
const data = await dataStore.fetchAllCollectionComponents(viewId);
|
|
17
|
+
collectionsCache.value = markRaw(data);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
watch(
|
|
21
|
+
items,
|
|
22
|
+
async (newItems) => {
|
|
23
|
+
if (!newItems) {
|
|
24
|
+
localCategories.value = [];
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = await dataStore.fetchAllCollectionComponents(viewId);
|
|
29
|
+
collectionsCache.value = markRaw(data);
|
|
30
|
+
|
|
31
|
+
localCategories.value = newItems.map((newCategory) => {
|
|
32
|
+
const existing = localCategories.value.find((category) => category.id === newCategory.id);
|
|
33
|
+
if (existing) {
|
|
34
|
+
existing.title = newCategory.title || newCategory.id;
|
|
35
|
+
return existing;
|
|
36
|
+
}
|
|
37
|
+
return reactive({
|
|
38
|
+
...newCategory,
|
|
39
|
+
title: newCategory.title || newCategory.id,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
{ immediate: true },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const selection = dataStyleStore.visibleMeshComponents(viewId);
|
|
47
|
+
|
|
48
|
+
async function updateVisibility(current) {
|
|
49
|
+
const previous = selection.value;
|
|
50
|
+
const { added, removed } = compareSelections(current, previous);
|
|
51
|
+
|
|
52
|
+
if (added.length === 0 && removed.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (added.length > 0) {
|
|
57
|
+
await dataStyleStore.setModelComponentsVisibility(viewId, added, true);
|
|
58
|
+
}
|
|
59
|
+
if (removed.length > 0) {
|
|
60
|
+
await dataStyleStore.setModelComponentsVisibility(viewId, removed, false);
|
|
61
|
+
}
|
|
62
|
+
hybridViewerStore.remoteRender();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
items,
|
|
67
|
+
collectionsCache,
|
|
68
|
+
localCategories,
|
|
69
|
+
selection,
|
|
70
|
+
updateVisibility,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -19,11 +19,15 @@ export function useModelComponents(viewId) {
|
|
|
19
19
|
|
|
20
20
|
watch(
|
|
21
21
|
items,
|
|
22
|
-
(newItems) => {
|
|
22
|
+
async (newItems) => {
|
|
23
23
|
if (!newItems) {
|
|
24
24
|
localCategories.value = [];
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
const data = await dataStore.fetchAllMeshComponents(viewId);
|
|
29
|
+
componentsCache.value = markRaw(data);
|
|
30
|
+
|
|
27
31
|
localCategories.value = newItems.map((newCategory) => {
|
|
28
32
|
const existing = localCategories.value.find((category) => category.id === newCategory.id);
|
|
29
33
|
if (existing) {
|
|
@@ -30,13 +30,14 @@ export function useVirtualTree(propsIn, emit) {
|
|
|
30
30
|
emit("update:opened", [...newOpened]);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function
|
|
33
|
+
function getLeafChildrenIds(item, ids = []) {
|
|
34
34
|
const children = item[actualItemProps.value.children];
|
|
35
|
-
if (children) {
|
|
35
|
+
if (children && children.length > 0) {
|
|
36
36
|
for (const child of children) {
|
|
37
|
-
|
|
38
|
-
getAllChildrenIds(child, ids);
|
|
37
|
+
getLeafChildrenIds(child, ids);
|
|
39
38
|
}
|
|
39
|
+
} else {
|
|
40
|
+
ids.push(item[actualItemProps.value.value]);
|
|
40
41
|
}
|
|
41
42
|
return ids;
|
|
42
43
|
}
|
|
@@ -47,7 +48,7 @@ export function useVirtualTree(propsIn, emit) {
|
|
|
47
48
|
return true;
|
|
48
49
|
}
|
|
49
50
|
if (actualSelection.value.strategy === "classic") {
|
|
50
|
-
const childrenIds =
|
|
51
|
+
const childrenIds = getLeafChildrenIds(item);
|
|
51
52
|
return (
|
|
52
53
|
childrenIds.length > 0 && childrenIds.every((childId) => selectedSet.value.has(childId))
|
|
53
54
|
);
|
|
@@ -59,7 +60,7 @@ export function useVirtualTree(propsIn, emit) {
|
|
|
59
60
|
if (actualSelection.value.strategy !== "classic") {
|
|
60
61
|
return false;
|
|
61
62
|
}
|
|
62
|
-
const childrenIds =
|
|
63
|
+
const childrenIds = getLeafChildrenIds(item);
|
|
63
64
|
if (childrenIds.length === 0) {
|
|
64
65
|
return false;
|
|
65
66
|
}
|
|
@@ -75,7 +76,7 @@ export function useVirtualTree(propsIn, emit) {
|
|
|
75
76
|
const isCurrentlySelected = newSelected.has(id) || isSelected(item);
|
|
76
77
|
|
|
77
78
|
if (actualSelection.value.strategy === "classic") {
|
|
78
|
-
const childrenIds =
|
|
79
|
+
const childrenIds = getLeafChildrenIds(item);
|
|
79
80
|
if (isCurrentlySelected) {
|
|
80
81
|
newSelected.delete(id);
|
|
81
82
|
for (const childId of childrenIds) {
|
package/app/stores/data.js
CHANGED
|
@@ -5,6 +5,8 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
|
|
|
5
5
|
|
|
6
6
|
// Local imports
|
|
7
7
|
import { database } from "@ogw_internal/database/database.js";
|
|
8
|
+
import { useDataCollections } from "./data_helpers/collections.js";
|
|
9
|
+
import { useDataMesh } from "./data_helpers/mesh.js";
|
|
8
10
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
9
11
|
|
|
10
12
|
const viewer_generic_schemas = viewer_schemas.opengeodeweb_viewer.generic;
|
|
@@ -16,6 +18,27 @@ export const useDataStore = defineStore("data", () => {
|
|
|
16
18
|
const model_components_db = database.model_components;
|
|
17
19
|
const model_components_relation_db = database.model_components_relation;
|
|
18
20
|
|
|
21
|
+
const {
|
|
22
|
+
formatedMeshComponents,
|
|
23
|
+
refFormatedMeshComponents,
|
|
24
|
+
getMeshComponentsByType,
|
|
25
|
+
getAllMeshComponents,
|
|
26
|
+
fetchAllMeshComponents,
|
|
27
|
+
getMeshComponentGeodeIds,
|
|
28
|
+
getCornersGeodeIds,
|
|
29
|
+
getLinesGeodeIds,
|
|
30
|
+
getSurfacesGeodeIds,
|
|
31
|
+
getBlocksGeodeIds,
|
|
32
|
+
} = useDataMesh();
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
hasCollectionComponents,
|
|
36
|
+
getAllCollectionComponents,
|
|
37
|
+
fetchAllCollectionComponents,
|
|
38
|
+
formatedCollectionComponents,
|
|
39
|
+
refFormatedCollectionComponents,
|
|
40
|
+
} = useDataCollections();
|
|
41
|
+
|
|
19
42
|
async function item(id) {
|
|
20
43
|
const data_item = await data_db.get(id);
|
|
21
44
|
if (!data_item) {
|
|
@@ -42,84 +65,6 @@ export const useDataStore = defineStore("data", () => {
|
|
|
42
65
|
);
|
|
43
66
|
}
|
|
44
67
|
|
|
45
|
-
async function formatedMeshComponents(modelId) {
|
|
46
|
-
const items = await model_components_db.where("id").equals(modelId).toArray();
|
|
47
|
-
const componentTitles = {
|
|
48
|
-
Corner: "Corners",
|
|
49
|
-
Line: "Lines",
|
|
50
|
-
Surface: "Surfaces",
|
|
51
|
-
Block: "Blocks",
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const componentsByType = {};
|
|
55
|
-
for (const component_item of items) {
|
|
56
|
-
if (componentTitles[component_item.type]) {
|
|
57
|
-
if (!componentsByType[component_item.type]) {
|
|
58
|
-
componentsByType[component_item.type] = [];
|
|
59
|
-
}
|
|
60
|
-
componentsByType[component_item.type].push(component_item);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return Object.keys(componentTitles)
|
|
65
|
-
.filter((type) => componentsByType[type])
|
|
66
|
-
.map((type) => ({
|
|
67
|
-
id: type,
|
|
68
|
-
title: componentTitles[type],
|
|
69
|
-
children: componentsByType[type].map((meshComponent) => ({
|
|
70
|
-
id: meshComponent.geode_id,
|
|
71
|
-
title: meshComponent.name,
|
|
72
|
-
category: meshComponent.type,
|
|
73
|
-
viewer_id: Number(meshComponent.viewer_id),
|
|
74
|
-
is_active: meshComponent.is_active,
|
|
75
|
-
})),
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function getMeshComponentsByType(modelId, type) {
|
|
80
|
-
const components = await model_components_db
|
|
81
|
-
.where("[id+type]")
|
|
82
|
-
.equals([modelId, type])
|
|
83
|
-
.toArray();
|
|
84
|
-
return components.map((meshComponent) => ({
|
|
85
|
-
id: meshComponent.geode_id,
|
|
86
|
-
title: meshComponent.name,
|
|
87
|
-
category: meshComponent.type,
|
|
88
|
-
viewer_id: Number(meshComponent.viewer_id),
|
|
89
|
-
is_active: meshComponent.is_active,
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function getAllMeshComponents(modelId) {
|
|
94
|
-
const items = await model_components_db.where("id").equals(modelId).toArray();
|
|
95
|
-
return items.map((meshComponent) => ({
|
|
96
|
-
id: meshComponent.geode_id,
|
|
97
|
-
title: meshComponent.name,
|
|
98
|
-
category: meshComponent.type,
|
|
99
|
-
viewer_id: Number(meshComponent.viewer_id),
|
|
100
|
-
is_active: meshComponent.is_active,
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function fetchAllMeshComponents(modelId) {
|
|
105
|
-
const components = await getAllMeshComponents(modelId);
|
|
106
|
-
const byType = {};
|
|
107
|
-
for (const component of components) {
|
|
108
|
-
if (!byType[component.category]) {
|
|
109
|
-
byType[component.category] = [];
|
|
110
|
-
}
|
|
111
|
-
byType[component.category].push(component);
|
|
112
|
-
}
|
|
113
|
-
return byType;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function refFormatedMeshComponents(modelId) {
|
|
117
|
-
return useObservable(
|
|
118
|
-
liveQuery(() => formatedMeshComponents(modelId)),
|
|
119
|
-
{ initialValue: undefined },
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
68
|
async function meshComponentType(modelId, geode_id) {
|
|
124
69
|
const component = await model_components_db
|
|
125
70
|
.where("[id+geode_id]")
|
|
@@ -227,30 +172,6 @@ export const useDataStore = defineStore("data", () => {
|
|
|
227
172
|
await database.model_components_relation.where("id").equals(modelId).delete();
|
|
228
173
|
}
|
|
229
174
|
|
|
230
|
-
async function getMeshComponentGeodeIds(modelId, type) {
|
|
231
|
-
const components = await model_components_db
|
|
232
|
-
.where("[id+type]")
|
|
233
|
-
.equals([modelId, type])
|
|
234
|
-
.toArray();
|
|
235
|
-
return components.map((component) => component.geode_id);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
async function getCornersGeodeIds(modelId) {
|
|
239
|
-
return await getMeshComponentGeodeIds(modelId, "Corner");
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function getLinesGeodeIds(modelId) {
|
|
243
|
-
return await getMeshComponentGeodeIds(modelId, "Line");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function getSurfacesGeodeIds(modelId) {
|
|
247
|
-
return await getMeshComponentGeodeIds(modelId, "Surface");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function getBlocksGeodeIds(modelId) {
|
|
251
|
-
return await getMeshComponentGeodeIds(modelId, "Block");
|
|
252
|
-
}
|
|
253
|
-
|
|
254
175
|
async function getAllModelComponentsViewerIds(modelId) {
|
|
255
176
|
const components = await model_components_db.where("id").equals(modelId).toArray();
|
|
256
177
|
return components.map((component) => Number.parseInt(component.viewer_id, 10));
|
|
@@ -283,10 +204,6 @@ export const useDataStore = defineStore("data", () => {
|
|
|
283
204
|
allItems,
|
|
284
205
|
refItem,
|
|
285
206
|
meshComponentType,
|
|
286
|
-
formatedMeshComponents,
|
|
287
|
-
refFormatedMeshComponents,
|
|
288
|
-
getMeshComponentsByType,
|
|
289
|
-
getAllMeshComponents,
|
|
290
207
|
registerObject,
|
|
291
208
|
deregisterObject,
|
|
292
209
|
addItem,
|
|
@@ -294,18 +211,29 @@ export const useDataStore = defineStore("data", () => {
|
|
|
294
211
|
addComponentRelations,
|
|
295
212
|
deleteItem,
|
|
296
213
|
updateItem,
|
|
297
|
-
getCornersGeodeIds,
|
|
298
|
-
getLinesGeodeIds,
|
|
299
|
-
getSurfacesGeodeIds,
|
|
300
|
-
getBlocksGeodeIds,
|
|
301
214
|
getAllModelComponentsViewerIds,
|
|
302
|
-
getMeshComponentGeodeIds,
|
|
303
215
|
getMeshComponentsViewerIds,
|
|
304
216
|
getComponentByViewerId,
|
|
305
217
|
|
|
306
218
|
exportStores,
|
|
307
219
|
importStores,
|
|
308
220
|
clear,
|
|
221
|
+
|
|
222
|
+
formatedMeshComponents,
|
|
223
|
+
refFormatedMeshComponents,
|
|
224
|
+
getMeshComponentsByType,
|
|
225
|
+
getAllMeshComponents,
|
|
309
226
|
fetchAllMeshComponents,
|
|
227
|
+
getMeshComponentGeodeIds,
|
|
228
|
+
getCornersGeodeIds,
|
|
229
|
+
getLinesGeodeIds,
|
|
230
|
+
getSurfacesGeodeIds,
|
|
231
|
+
getBlocksGeodeIds,
|
|
232
|
+
|
|
233
|
+
hasCollectionComponents,
|
|
234
|
+
getAllCollectionComponents,
|
|
235
|
+
fetchAllCollectionComponents,
|
|
236
|
+
formatedCollectionComponents,
|
|
237
|
+
refFormatedCollectionComponents,
|
|
310
238
|
};
|
|
311
239
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { database } from "@ogw_internal/database/database.js";
|
|
2
|
+
import { liveQuery } from "dexie";
|
|
3
|
+
import { useDataMesh } from "./mesh.js";
|
|
4
|
+
import { useObservable } from "@vueuse/rxjs";
|
|
5
|
+
|
|
6
|
+
export function useDataCollections() {
|
|
7
|
+
const model_components_db = database.model_components;
|
|
8
|
+
const model_components_relation_db = database.model_components_relation;
|
|
9
|
+
const { getAllMeshComponents } = useDataMesh();
|
|
10
|
+
|
|
11
|
+
async function hasCollectionComponents(modelId) {
|
|
12
|
+
const count = await model_components_db
|
|
13
|
+
.where("id")
|
|
14
|
+
.equals(modelId)
|
|
15
|
+
.and((component) =>
|
|
16
|
+
["Horizon", "Fault", "FaultBlock", "StratigraphicUnit", "ModelBoundary"].includes(
|
|
17
|
+
component.type,
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
.count();
|
|
21
|
+
return count > 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getAllCollectionComponents(modelId) {
|
|
25
|
+
const items = await model_components_db.where("id").equals(modelId).toArray();
|
|
26
|
+
return items
|
|
27
|
+
.filter((component) =>
|
|
28
|
+
["Horizon", "Fault", "FaultBlock", "StratigraphicUnit", "ModelBoundary"].includes(
|
|
29
|
+
component.type,
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
.map((component) => ({
|
|
33
|
+
id: component.geode_id,
|
|
34
|
+
title: component.name,
|
|
35
|
+
category: component.type,
|
|
36
|
+
viewer_id: Number(component.viewer_id),
|
|
37
|
+
is_active: component.is_active,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function fetchAllCollectionComponents(modelId) {
|
|
42
|
+
const components = await getAllCollectionComponents(modelId);
|
|
43
|
+
const relations = await model_components_relation_db.where("id").equals(modelId).toArray();
|
|
44
|
+
const allMeshComponents = await getAllMeshComponents(modelId);
|
|
45
|
+
const meshComponentsById = {};
|
|
46
|
+
for (const meshComponent of allMeshComponents) {
|
|
47
|
+
meshComponentsById[meshComponent.id] = meshComponent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const byType = {};
|
|
51
|
+
for (const component of components) {
|
|
52
|
+
if (!byType[component.category]) {
|
|
53
|
+
byType[component.category] = [];
|
|
54
|
+
}
|
|
55
|
+
const itemRelations = relations.filter(
|
|
56
|
+
(relation) => relation.parent === component.id && relation.type === "collection",
|
|
57
|
+
);
|
|
58
|
+
const children = itemRelations
|
|
59
|
+
.map((relation) => meshComponentsById[relation.child])
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
byType[component.category].push({
|
|
62
|
+
...component,
|
|
63
|
+
children,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return byType;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function formatedCollectionComponents(modelId) {
|
|
70
|
+
const byType = await fetchAllCollectionComponents(modelId);
|
|
71
|
+
const collectionTitles = {
|
|
72
|
+
Horizon: "Horizons",
|
|
73
|
+
Fault: "Faults",
|
|
74
|
+
FaultBlock: "FaultBlocks",
|
|
75
|
+
StratigraphicUnit: "StratigraphicUnits",
|
|
76
|
+
ModelBoundary: "ModelBoundaries",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return Object.keys(collectionTitles)
|
|
80
|
+
.filter((type) => byType[type] && byType[type].length > 0)
|
|
81
|
+
.map((type) => ({
|
|
82
|
+
id: type,
|
|
83
|
+
title: collectionTitles[type],
|
|
84
|
+
children: byType[type],
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function refFormatedCollectionComponents(modelId) {
|
|
89
|
+
return useObservable(
|
|
90
|
+
liveQuery(() => formatedCollectionComponents(modelId)),
|
|
91
|
+
{ initialValue: undefined },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
hasCollectionComponents,
|
|
97
|
+
getAllCollectionComponents,
|
|
98
|
+
fetchAllCollectionComponents,
|
|
99
|
+
formatedCollectionComponents,
|
|
100
|
+
refFormatedCollectionComponents,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { database } from "@ogw_internal/database/database.js";
|
|
2
|
+
import { liveQuery } from "dexie";
|
|
3
|
+
import { useObservable } from "@vueuse/rxjs";
|
|
4
|
+
|
|
5
|
+
export function useDataMesh() {
|
|
6
|
+
const model_components_db = database.model_components;
|
|
7
|
+
|
|
8
|
+
async function formatedMeshComponents(modelId) {
|
|
9
|
+
const items = await model_components_db.where("id").equals(modelId).toArray();
|
|
10
|
+
const componentTitles = {
|
|
11
|
+
Corner: "Corners",
|
|
12
|
+
Line: "Lines",
|
|
13
|
+
Surface: "Surfaces",
|
|
14
|
+
Block: "Blocks",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const componentsByType = {};
|
|
18
|
+
for (const component_item of items) {
|
|
19
|
+
if (componentTitles[component_item.type]) {
|
|
20
|
+
if (!componentsByType[component_item.type]) {
|
|
21
|
+
componentsByType[component_item.type] = [];
|
|
22
|
+
}
|
|
23
|
+
componentsByType[component_item.type].push(component_item);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Object.keys(componentTitles)
|
|
28
|
+
.filter((type) => componentsByType[type])
|
|
29
|
+
.map((type) => ({
|
|
30
|
+
id: type,
|
|
31
|
+
title: componentTitles[type],
|
|
32
|
+
children: componentsByType[type].map((meshComponent) => ({
|
|
33
|
+
id: meshComponent.geode_id,
|
|
34
|
+
title: meshComponent.name,
|
|
35
|
+
category: meshComponent.type,
|
|
36
|
+
viewer_id: Number(meshComponent.viewer_id),
|
|
37
|
+
is_active: meshComponent.is_active,
|
|
38
|
+
})),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function refFormatedMeshComponents(modelId) {
|
|
43
|
+
return useObservable(
|
|
44
|
+
liveQuery(() => formatedMeshComponents(modelId)),
|
|
45
|
+
{ initialValue: undefined },
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function getMeshComponentsByType(modelId, type) {
|
|
50
|
+
const components = await model_components_db
|
|
51
|
+
.where("[id+type]")
|
|
52
|
+
.equals([modelId, type])
|
|
53
|
+
.toArray();
|
|
54
|
+
return components.map((meshComponent) => ({
|
|
55
|
+
id: meshComponent.geode_id,
|
|
56
|
+
title: meshComponent.name,
|
|
57
|
+
category: meshComponent.type,
|
|
58
|
+
viewer_id: Number(meshComponent.viewer_id),
|
|
59
|
+
is_active: meshComponent.is_active,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function getAllMeshComponents(modelId) {
|
|
64
|
+
const items = await model_components_db.where("id").equals(modelId).toArray();
|
|
65
|
+
return items.map((meshComponent) => ({
|
|
66
|
+
id: meshComponent.geode_id,
|
|
67
|
+
title: meshComponent.name,
|
|
68
|
+
category: meshComponent.type,
|
|
69
|
+
viewer_id: Number(meshComponent.viewer_id),
|
|
70
|
+
is_active: meshComponent.is_active,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function fetchAllMeshComponents(modelId) {
|
|
75
|
+
const components = await getAllMeshComponents(modelId);
|
|
76
|
+
const byType = {};
|
|
77
|
+
for (const component of components) {
|
|
78
|
+
if (!byType[component.category]) {
|
|
79
|
+
byType[component.category] = [];
|
|
80
|
+
}
|
|
81
|
+
byType[component.category].push(component);
|
|
82
|
+
}
|
|
83
|
+
return byType;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function getMeshComponentGeodeIds(modelId, type) {
|
|
87
|
+
const components = await model_components_db
|
|
88
|
+
.where("[id+type]")
|
|
89
|
+
.equals([modelId, type])
|
|
90
|
+
.toArray();
|
|
91
|
+
return components.map((component) => component.geode_id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function getCornersGeodeIds(modelId) {
|
|
95
|
+
return await getMeshComponentGeodeIds(modelId, "Corner");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function getLinesGeodeIds(modelId) {
|
|
99
|
+
return await getMeshComponentGeodeIds(modelId, "Line");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function getSurfacesGeodeIds(modelId) {
|
|
103
|
+
return await getMeshComponentGeodeIds(modelId, "Surface");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function getBlocksGeodeIds(modelId) {
|
|
107
|
+
return await getMeshComponentGeodeIds(modelId, "Block");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
formatedMeshComponents,
|
|
112
|
+
refFormatedMeshComponents,
|
|
113
|
+
getMeshComponentsByType,
|
|
114
|
+
getAllMeshComponents,
|
|
115
|
+
fetchAllMeshComponents,
|
|
116
|
+
getMeshComponentGeodeIds,
|
|
117
|
+
getCornersGeodeIds,
|
|
118
|
+
getLinesGeodeIds,
|
|
119
|
+
getSurfacesGeodeIds,
|
|
120
|
+
getBlocksGeodeIds,
|
|
121
|
+
};
|
|
122
|
+
}
|
package/app/stores/treeview.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineStore } from "pinia";
|
|
2
2
|
|
|
3
|
-
import { ref,
|
|
3
|
+
import { ref, watch } from "vue";
|
|
4
4
|
import { compareSelections } from "@ogw_front/utils/treeview";
|
|
5
5
|
import { database } from "@ogw_internal/database/database";
|
|
6
6
|
|
|
@@ -45,13 +45,20 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
45
45
|
watch(
|
|
46
46
|
[opened_views, panelWidth, additionalPanelWidth, selection, rowHeights],
|
|
47
47
|
() => {
|
|
48
|
+
// oxlint-disable-next-line unicorn/prefer-structured-clone
|
|
49
|
+
const clean_opened_views = JSON.parse(JSON.stringify(opened_views.value));
|
|
50
|
+
// oxlint-disable-next-line unicorn/prefer-structured-clone
|
|
51
|
+
const clean_selectionIds = JSON.parse(JSON.stringify(selection.value));
|
|
52
|
+
// oxlint-disable-next-line unicorn/prefer-structured-clone
|
|
53
|
+
const clean_rowHeights = JSON.parse(JSON.stringify(rowHeights.value));
|
|
54
|
+
|
|
48
55
|
database.treeview_config.put({
|
|
49
56
|
id: "main",
|
|
50
|
-
opened_views:
|
|
57
|
+
opened_views: clean_opened_views,
|
|
51
58
|
panelWidth: panelWidth.value,
|
|
52
59
|
additionalPanelWidth: additionalPanelWidth.value,
|
|
53
|
-
selectionIds:
|
|
54
|
-
rowHeights:
|
|
60
|
+
selectionIds: clean_selectionIds,
|
|
61
|
+
rowHeights: clean_rowHeights,
|
|
55
62
|
});
|
|
56
63
|
},
|
|
57
64
|
{ deep: true },
|
|
@@ -127,15 +134,18 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
function displayAdditionalTree(id, title, geodeObjectType) {
|
|
131
|
-
const
|
|
137
|
+
function displayAdditionalTree(id, title, geodeObjectType, viewType = "model_components") {
|
|
138
|
+
const viewId = `${id}_${viewType}`;
|
|
139
|
+
const index = opened_views.value.findIndex((view) => view.id === viewId);
|
|
132
140
|
if (index !== -1) {
|
|
133
|
-
return closeView(
|
|
141
|
+
return closeView(viewId);
|
|
134
142
|
}
|
|
135
143
|
additionalPanelWidth.value = panelWidth.value;
|
|
136
144
|
opened_views.value.push({
|
|
137
145
|
type: "component",
|
|
138
|
-
id,
|
|
146
|
+
id: viewId,
|
|
147
|
+
modelId: id,
|
|
148
|
+
viewType,
|
|
139
149
|
title: title || id,
|
|
140
150
|
geode_object_type: geodeObjectType,
|
|
141
151
|
scrollTop: 0,
|
|
@@ -4,6 +4,7 @@ const NEAR_ZERO_THRESHOLD = 1e-10;
|
|
|
4
4
|
const SLERP_LINEAR_THRESHOLD = 0.9995;
|
|
5
5
|
const LONG_ANIMATION_DURATION = 1000;
|
|
6
6
|
const SHORT_ANIMATION_DURATION = 500;
|
|
7
|
+
const MID_ANIMATION_RATIO = 0.5;
|
|
7
8
|
|
|
8
9
|
function vecSub(vector, other) {
|
|
9
10
|
return [vector[0] - other[0], vector[1] - other[1], vector[2] - other[2]];
|
|
@@ -21,7 +22,7 @@ function vecNormalize(vector) {
|
|
|
21
22
|
return [vector[0] / len, vector[1] / len, vector[2] / len];
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function slerp(from, target, ratio) {
|
|
25
|
+
function slerp(from, target, ratio, mid) {
|
|
25
26
|
const normFrom = vecNormalize(from);
|
|
26
27
|
const normTarget = vecNormalize(target);
|
|
27
28
|
let dotProduct =
|
|
@@ -34,6 +35,11 @@ function slerp(from, target, ratio) {
|
|
|
34
35
|
normFrom[2] + (normTarget[2] - normFrom[2]) * ratio,
|
|
35
36
|
]);
|
|
36
37
|
}
|
|
38
|
+
if (dotProduct < -SLERP_LINEAR_THRESHOLD && mid) {
|
|
39
|
+
return ratio < MID_ANIMATION_RATIO
|
|
40
|
+
? slerp(normFrom, mid, ratio * 2)
|
|
41
|
+
: slerp(mid, normTarget, (ratio - MID_ANIMATION_RATIO) * 2);
|
|
42
|
+
}
|
|
37
43
|
const theta = Math.acos(dotProduct);
|
|
38
44
|
const sinTheta = Math.sin(theta);
|
|
39
45
|
const weightFrom = Math.sin((1 - ratio) * theta) / sinTheta;
|
|
@@ -71,6 +77,22 @@ function animateCamera(options) {
|
|
|
71
77
|
const targetDir = vecSub(targetState.position, targetState.focal_point);
|
|
72
78
|
const startDist = vecLength(startDir);
|
|
73
79
|
const targetDist = vecLength(targetDir);
|
|
80
|
+
|
|
81
|
+
const normStart = vecNormalize(startDir);
|
|
82
|
+
const normTarget = vecNormalize(targetDir);
|
|
83
|
+
const startTargetDot =
|
|
84
|
+
normStart[0] * normTarget[0] + normStart[1] * normTarget[1] + normStart[2] * normTarget[2];
|
|
85
|
+
|
|
86
|
+
let antipodalMid = undefined;
|
|
87
|
+
if (startTargetDot < -SLERP_LINEAR_THRESHOLD) {
|
|
88
|
+
const normUp = vecNormalize(startState.view_up);
|
|
89
|
+
antipodalMid = vecNormalize([
|
|
90
|
+
normStart[1] * normUp[2] - normStart[2] * normUp[1],
|
|
91
|
+
normStart[2] * normUp[0] - normStart[0] * normUp[2],
|
|
92
|
+
normStart[0] * normUp[1] - normStart[1] * normUp[0],
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
const startTime = performance.now();
|
|
75
97
|
function animate(currentTime) {
|
|
76
98
|
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
@@ -79,7 +101,7 @@ function animateCamera(options) {
|
|
|
79
101
|
? 1 - (1 - progress) ** easeExponent
|
|
80
102
|
: progress * (2 - progress);
|
|
81
103
|
const bump = bumpMultiplier * Math.sin(Math.PI * progress);
|
|
82
|
-
const dir = slerp(startDir, targetDir, ease);
|
|
104
|
+
const dir = slerp(startDir, targetDir, ease, antipodalMid);
|
|
83
105
|
const dist = startDist + (targetDist - startDist) * ease + bump;
|
|
84
106
|
const focalPoint = startState.focal_point.map(
|
|
85
107
|
(startValue, index) => startValue + (targetState.focal_point[index] - startValue) * ease,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geode/opengeodeweb-front",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.21.0-rc.1",
|
|
4
4
|
"description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
|
|
5
5
|
"homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
|
|
6
6
|
"bugs": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"build": ""
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@geode/opengeodeweb-back": "
|
|
38
|
-
"@geode/opengeodeweb-viewer": "
|
|
37
|
+
"@geode/opengeodeweb-back": "next",
|
|
38
|
+
"@geode/opengeodeweb-viewer": "next",
|
|
39
39
|
"@google-cloud/run": "3.2.0",
|
|
40
40
|
"@kitware/vtk.js": "33.3.0",
|
|
41
41
|
"@mdi/font": "7.4.47",
|