@geode/opengeodeweb-front 10.14.1 → 10.14.2-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/.oxlintrc.json +12 -2
- package/app/components/SearchBar.vue +1 -1
- package/app/components/Viewer/ContextMenu.vue +1 -3
- package/app/components/Viewer/ContextMenuItem.vue +2 -2
- package/app/components/Viewer/ObjectTree/Base/CommonTreeView.vue +189 -0
- package/app/components/Viewer/ObjectTree/Base/Controls.vue +124 -38
- package/app/components/Viewer/ObjectTree/Base/ItemLabel.vue +43 -18
- package/app/components/Viewer/ObjectTree/Base/StickyHeader.vue +46 -0
- package/app/components/Viewer/ObjectTree/Base/TreeRow.vue +77 -0
- package/app/components/Viewer/ObjectTree/Box.vue +106 -15
- package/app/components/Viewer/ObjectTree/Layout.vue +14 -12
- package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +48 -33
- package/app/components/Viewer/ObjectTree/Views/ModelComponents.vue +102 -66
- package/app/composables/hover_highlight.js +85 -0
- package/app/composables/model_components.js +68 -0
- package/app/composables/{use_tree_filter.js → tree_filter.js} +49 -31
- package/app/composables/tree_keyboard_nav.js +81 -0
- package/app/composables/tree_scroll.js +91 -0
- package/app/composables/virtual_tree.js +164 -0
- package/app/stores/data.js +41 -1
- package/app/stores/hybrid_viewer.js +30 -38
- package/app/utils/hybrid_viewer.js +101 -0
- package/package.json +3 -3
- package/app/composables/use_hover_highlight.js +0 -48
|
@@ -1,20 +1,68 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
3
|
+
|
|
2
4
|
const SCROLL_SYNC_DELAY = 50;
|
|
3
5
|
const SCROLL_THRESHOLD = 1;
|
|
4
6
|
const { title, closable, icon, mdiIcon, scrollTop } = defineProps({
|
|
5
7
|
title: { type: String, required: true },
|
|
6
|
-
closable: { type: Boolean, default: false },
|
|
7
|
-
icon: { type: String, default: "" },
|
|
8
|
-
mdiIcon: { type: String, default: "" },
|
|
9
|
-
scrollTop: { type: Number, default: 0 },
|
|
8
|
+
closable: { type: Boolean, required: false, default: false },
|
|
9
|
+
icon: { type: String, required: false, default: "" },
|
|
10
|
+
mdiIcon: { type: String, required: false, default: "" },
|
|
11
|
+
scrollTop: { type: Number, required: false, default: 0 },
|
|
10
12
|
});
|
|
11
|
-
|
|
12
13
|
const emit = defineEmits(["close", "dragstart", "update:scrollTop"]);
|
|
13
14
|
|
|
14
|
-
const scrollContainer =
|
|
15
|
+
const scrollContainer = useTemplateRef("scroll-container");
|
|
16
|
+
const treeviewBox = useTemplateRef("treeview-box");
|
|
17
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
18
|
+
|
|
19
|
+
const LUMINANCE_THRESHOLD = 0.65;
|
|
20
|
+
const ADAPTIVE_EXPONENT = 0.3;
|
|
21
|
+
|
|
22
|
+
const MIN_BLUR = 8;
|
|
23
|
+
const MAX_BLUR = 25;
|
|
24
|
+
|
|
25
|
+
const MIN_OPACITY = 0;
|
|
26
|
+
const MAX_OPACITY = 0.5;
|
|
27
|
+
|
|
28
|
+
const MIN_BOOST = 1;
|
|
29
|
+
const MAX_BOOST = 1.2;
|
|
30
|
+
const ADAPTIVE_REFRESH_RATE = 150;
|
|
31
|
+
|
|
32
|
+
const { x, y, width, height } = useElementBounding(treeviewBox);
|
|
33
|
+
const brightness = ref(LUMINANCE_THRESHOLD);
|
|
34
|
+
|
|
35
|
+
const updateBrightness = useThrottleFn(() => {
|
|
36
|
+
brightness.value = hybridViewerStore.getAverageBrightness({
|
|
37
|
+
x: x.value,
|
|
38
|
+
y: y.value,
|
|
39
|
+
width: width.value,
|
|
40
|
+
height: height.value,
|
|
41
|
+
});
|
|
42
|
+
}, ADAPTIVE_REFRESH_RATE);
|
|
43
|
+
|
|
15
44
|
let isApplyingScroll = false;
|
|
16
45
|
let resizeObserver = undefined;
|
|
17
46
|
|
|
47
|
+
watch([x, y, width, height, () => hybridViewerStore.latestImage], updateBrightness, {
|
|
48
|
+
immediate: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const adaptiveStyles = computed(() => {
|
|
52
|
+
const normalized = Math.min(1, brightness.value / LUMINANCE_THRESHOLD);
|
|
53
|
+
const darkFactor = (1 - normalized) ** ADAPTIVE_EXPONENT;
|
|
54
|
+
|
|
55
|
+
const blur = MIN_BLUR + darkFactor * (MAX_BLUR - MIN_BLUR);
|
|
56
|
+
const opacity = MIN_OPACITY + darkFactor * (MAX_OPACITY - MIN_OPACITY);
|
|
57
|
+
const brightnessBoost = MIN_BOOST + darkFactor * (MAX_BOOST - MIN_BOOST);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
"--adaptive-blur": `${blur}px`,
|
|
61
|
+
"--adaptive-opacity": opacity,
|
|
62
|
+
"--adaptive-brightness": brightnessBoost,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
18
66
|
function handleScroll(event) {
|
|
19
67
|
if (isApplyingScroll) {
|
|
20
68
|
return;
|
|
@@ -69,7 +117,12 @@ watch(
|
|
|
69
117
|
</script>
|
|
70
118
|
|
|
71
119
|
<template>
|
|
72
|
-
<v-card
|
|
120
|
+
<v-card
|
|
121
|
+
ref="treeview-box"
|
|
122
|
+
variant="outlined"
|
|
123
|
+
class="tree-box d-flex flex-column"
|
|
124
|
+
:style="adaptiveStyles"
|
|
125
|
+
>
|
|
73
126
|
<v-card-title
|
|
74
127
|
class="tree-box-header d-flex align-center"
|
|
75
128
|
:class="{ 'cursor-grab': closable }"
|
|
@@ -89,7 +142,15 @@ watch(
|
|
|
89
142
|
<v-icon v-else-if="closable" size="24" class="mr-2">mdi-drag-variant</v-icon>
|
|
90
143
|
<span
|
|
91
144
|
class="text-subtitle-2 font-weight-bold d-inline-flex align-center"
|
|
92
|
-
style="
|
|
145
|
+
style="
|
|
146
|
+
height: 24px;
|
|
147
|
+
line-height: 1;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
text-overflow: ellipsis;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
flex-shrink: 1;
|
|
152
|
+
min-width: 0;
|
|
153
|
+
"
|
|
93
154
|
>
|
|
94
155
|
{{ title }}
|
|
95
156
|
</span>
|
|
@@ -103,10 +164,11 @@ watch(
|
|
|
103
164
|
/>
|
|
104
165
|
</v-card-title>
|
|
105
166
|
<v-divider />
|
|
106
|
-
<v-card-text class="pa-0 flex-grow-1 overflow-hidden d-flex flex-column">
|
|
167
|
+
<v-card-text class="pa-0 flex-grow-1 overflow-hidden d-flex flex-column" style="min-height: 0">
|
|
107
168
|
<div
|
|
108
|
-
ref="
|
|
109
|
-
class="flex-grow-1 overflow-y-
|
|
169
|
+
ref="scroll-container"
|
|
170
|
+
class="flex-grow-1 overflow-y-hidden overflow-x-hidden d-flex flex-column"
|
|
171
|
+
style="min-height: 0"
|
|
110
172
|
@scroll="handleScroll"
|
|
111
173
|
>
|
|
112
174
|
<slot />
|
|
@@ -118,15 +180,44 @@ watch(
|
|
|
118
180
|
<style scoped>
|
|
119
181
|
.tree-box {
|
|
120
182
|
height: 100%;
|
|
183
|
+
min-height: 0;
|
|
121
184
|
border-radius: 16px;
|
|
122
185
|
background-color: transparent !important;
|
|
123
|
-
|
|
124
|
-
|
|
186
|
+
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
|
187
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
|
188
|
+
position: relative;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
transition:
|
|
191
|
+
background-color 0.3s ease,
|
|
192
|
+
backdrop-filter 0.3s ease;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.tree-box::before {
|
|
196
|
+
content: "";
|
|
197
|
+
position: absolute;
|
|
198
|
+
inset: 0;
|
|
199
|
+
background: rgba(255, 255, 255, var(--adaptive-opacity));
|
|
200
|
+
backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness));
|
|
201
|
+
-webkit-backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness));
|
|
202
|
+
mix-blend-mode: lighten;
|
|
203
|
+
z-index: 0;
|
|
204
|
+
pointer-events: none;
|
|
205
|
+
border-radius: inherit;
|
|
206
|
+
transition:
|
|
207
|
+
background-color 0.3s ease,
|
|
208
|
+
backdrop-filter 0.3s ease;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.tree-box > * {
|
|
212
|
+
position: relative;
|
|
213
|
+
z-index: 1;
|
|
125
214
|
}
|
|
126
215
|
|
|
127
216
|
.tree-box-header {
|
|
128
217
|
height: 40px !important;
|
|
129
|
-
padding: 0
|
|
130
|
-
background-color: rgba(255, 255, 255, 0.
|
|
218
|
+
padding: 0 12px !important;
|
|
219
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
220
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
221
|
+
flex-shrink: 0;
|
|
131
222
|
}
|
|
132
223
|
</style>
|
|
@@ -48,7 +48,7 @@ watch(
|
|
|
48
48
|
{ immediate: true },
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
watch(maxWidth, (newMax) => {
|
|
51
|
+
watch([maxWidth, () => additionalViews.value.length], ([newMax]) => {
|
|
52
52
|
const hasAdditional = additionalViews.value.length > 0;
|
|
53
53
|
const gap = hasAdditional ? GAP_WIDTH : 0;
|
|
54
54
|
const total =
|
|
@@ -143,20 +143,20 @@ function onVerticalResizeStart(event, index) {
|
|
|
143
143
|
const deltaPercent = (deltaY / containerHeight) * PERCENT_100;
|
|
144
144
|
const minHeightPercent = (HEIGHT_MIN / containerHeight) * PERCENT_100;
|
|
145
145
|
|
|
146
|
-
let
|
|
147
|
-
let
|
|
146
|
+
let newHeight1 = startHeight1 + deltaPercent;
|
|
147
|
+
let newHeight2 = startHeight2 - deltaPercent;
|
|
148
148
|
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} else if (
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
if (newHeight1 < minHeightPercent) {
|
|
150
|
+
newHeight1 = minHeightPercent;
|
|
151
|
+
newHeight2 = startHeight1 + startHeight2 - minHeightPercent;
|
|
152
|
+
} else if (newHeight2 < minHeightPercent) {
|
|
153
|
+
newHeight2 = minHeightPercent;
|
|
154
|
+
newHeight1 = startHeight1 + startHeight2 - minHeightPercent;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
const newHeights = [...rowHeights.value];
|
|
158
|
-
newHeights[index] =
|
|
159
|
-
newHeights[index + 1] =
|
|
158
|
+
newHeights[index] = newHeight1;
|
|
159
|
+
newHeights[index + 1] = newHeight2;
|
|
160
160
|
treeviewStore.setRowHeights(newHeights);
|
|
161
161
|
|
|
162
162
|
document.body.style.userSelect = "none";
|
|
@@ -272,7 +272,7 @@ function onVerticalResizeStart(event, index) {
|
|
|
272
272
|
display: flex;
|
|
273
273
|
flex-direction: column;
|
|
274
274
|
height: 100%;
|
|
275
|
-
overflow-y:
|
|
275
|
+
overflow-y: hidden;
|
|
276
276
|
overflow-x: hidden;
|
|
277
277
|
flex-shrink: 0;
|
|
278
278
|
}
|
|
@@ -286,6 +286,8 @@ function onVerticalResizeStart(event, index) {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
.view-wrapper {
|
|
289
|
+
display: flex;
|
|
290
|
+
flex-direction: column;
|
|
289
291
|
overflow: hidden;
|
|
290
292
|
padding: 2px;
|
|
291
293
|
transition: transform 0.2s;
|
|
@@ -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/
|
|
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/
|
|
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(
|
|
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
|
-
|
|
78
|
+
function handleHoverEnter({ item, immediate = false }) {
|
|
78
79
|
const actualItem = item.raw || item;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
block_ids = await dataStore.getAllModelComponentsViewerIds(actualItem.id);
|
|
80
|
+
|
|
81
|
+
if (!actualItem.viewer_type) {
|
|
82
|
+
return;
|
|
83
83
|
}
|
|
84
|
-
|
|
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
|
-
<
|
|
115
|
+
<CommonTreeView
|
|
104
116
|
:selected="visibleSelection"
|
|
105
117
|
v-model:opened="opened"
|
|
106
118
|
:items="processedItems"
|
|
107
|
-
:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
</
|
|
152
|
+
</CommonTreeView>
|
|
139
153
|
</div>
|
|
140
154
|
</template>
|
|
141
155
|
|
|
142
156
|
<style scoped>
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
:
|
|
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
|
-
|
|
153
|
-
|
|
165
|
+
.virtual-tree-height {
|
|
166
|
+
flex-grow: 1;
|
|
167
|
+
min-height: 0;
|
|
154
168
|
}
|
|
155
169
|
|
|
156
|
-
|
|
157
|
-
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
|
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(
|
|
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(
|
|
40
|
+
} = useTreeFilter(localCategories);
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
function onUpdateSelection(newSelection) {
|
|
43
|
+
const finalSelection = applySearchFilter(newSelection, visibleComponents.value);
|
|
44
|
+
updateVisibility(finalSelection);
|
|
45
|
+
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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 :
|
|
84
|
+
itemId: actualItem.category ? actualItem.id : id,
|
|
67
85
|
context_type: actualItem.category ? "model_component" : "model_component_type",
|
|
68
|
-
modelId:
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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(
|
|
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="
|
|
125
|
+
<FetchingData v-if="rawItems === undefined" :size="48" :width="4" text="" />
|
|
97
126
|
|
|
98
|
-
<
|
|
99
|
-
v-else
|
|
127
|
+
<CommonTreeView
|
|
100
128
|
:selected="visibleSelection"
|
|
101
129
|
v-model:opened="opened"
|
|
102
|
-
:items="
|
|
103
|
-
:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@update:selected="
|
|
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
|
-
|
|
117
|
-
@
|
|
118
|
-
@contextmenu="showContextMenu($event, item)"
|
|
149
|
+
class="text-body-1"
|
|
150
|
+
@contextmenu.prevent.stop="showContextMenu($event, item)"
|
|
119
151
|
/>
|
|
120
152
|
</template>
|
|
121
|
-
</
|
|
153
|
+
</CommonTreeView>
|
|
122
154
|
</div>
|
|
123
155
|
</template>
|
|
124
156
|
|
|
125
157
|
<style scoped>
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
132
|
-
|
|
166
|
+
.virtual-tree-height {
|
|
167
|
+
flex-grow: 1;
|
|
168
|
+
min-height: 0;
|
|
133
169
|
}
|
|
134
170
|
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
.transparent-treeview {
|
|
172
|
+
background-color: transparent;
|
|
137
173
|
}
|
|
138
174
|
|
|
139
|
-
:deep(.v-list-
|
|
140
|
-
|
|
175
|
+
:deep(.v-list-item__overlay) {
|
|
176
|
+
display: none !important;
|
|
141
177
|
}
|
|
142
178
|
</style>
|