@geode/opengeodeweb-front 10.18.1-rc.1 → 10.18.1-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.
- package/app/components/Viewer/ObjectTree/Base/CommonTreeView.vue +3 -3
- package/app/components/Viewer/ObjectTree/Base/Controls.vue +4 -4
- package/app/components/Viewer/ObjectTree/Base/ItemLabel.vue +36 -2
- package/app/components/Viewer/ObjectTree/Base/TreeRow.vue +8 -4
- package/app/components/Viewer/ObjectTree/Box.vue +8 -50
- package/app/components/Viewer/ObjectTree/Layout.vue +156 -74
- package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +1 -1
- package/app/composables/use_adaptive_styles.js +55 -0
- package/app/stores/treeview.js +18 -4
- package/app/utils/string.js +26 -0
- package/package.json +1 -1
|
@@ -163,10 +163,10 @@ const { focusedIndex, handleKeyDown } = useTreeKeyboardNav(
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
.tree-row-wrapper {
|
|
166
|
-
min-height:
|
|
166
|
+
min-height: 28px !important;
|
|
167
167
|
cursor: pointer;
|
|
168
|
-
border-radius:
|
|
169
|
-
margin:
|
|
168
|
+
border-radius: 4px;
|
|
169
|
+
margin: 0 2px;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
.tree-row-wrapper.is-focused {
|
|
@@ -25,7 +25,7 @@ watch(
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<template>
|
|
28
|
-
<v-row dense align="center" class="pa-
|
|
28
|
+
<v-row dense align="center" class="pa-1 py-0">
|
|
29
29
|
<v-col cols="12">
|
|
30
30
|
<div
|
|
31
31
|
class="controls-capsule d-flex align-center rounded-pill px-1 overflow-hidden"
|
|
@@ -124,7 +124,7 @@ watch(
|
|
|
124
124
|
|
|
125
125
|
<style scoped>
|
|
126
126
|
.controls-capsule {
|
|
127
|
-
height:
|
|
127
|
+
height: 32px;
|
|
128
128
|
border: 1px solid transparent;
|
|
129
129
|
transition: all 0.3s ease;
|
|
130
130
|
width: fit-content;
|
|
@@ -144,12 +144,12 @@ watch(
|
|
|
144
144
|
:deep(.v-field__input) {
|
|
145
145
|
padding-top: 0 !important;
|
|
146
146
|
padding-bottom: 0 !important;
|
|
147
|
-
min-height:
|
|
147
|
+
min-height: 32px !important;
|
|
148
148
|
display: flex;
|
|
149
149
|
align-items: center;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
:deep(.v-field__field) {
|
|
153
|
-
height:
|
|
153
|
+
height: 32px !important;
|
|
154
154
|
}
|
|
155
155
|
</style>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { middleTruncate } from "@ogw_front/utils/string";
|
|
3
|
+
|
|
2
4
|
const { item, isLeaf } = defineProps({
|
|
3
5
|
item: { type: Object, required: true },
|
|
4
6
|
isLeaf: { type: Boolean, required: false, default: undefined },
|
|
@@ -6,8 +8,39 @@ const { item, isLeaf } = defineProps({
|
|
|
6
8
|
|
|
7
9
|
const emit = defineEmits(["contextmenu", "mouseenter", "mouseleave"]);
|
|
8
10
|
|
|
11
|
+
const labelContainer = useTemplateRef("label-container");
|
|
12
|
+
const { width: containerWidth } = useElementSize(labelContainer);
|
|
13
|
+
|
|
9
14
|
const actualItem = computed(() => item.raw || item);
|
|
10
15
|
|
|
16
|
+
const UUID_END_CHARS = 12;
|
|
17
|
+
const ELLIPSIS_LENGTH = 3;
|
|
18
|
+
const MIN_START_CHARS = 4;
|
|
19
|
+
|
|
20
|
+
const displayTitle = computed(() => {
|
|
21
|
+
const { title } = actualItem.value;
|
|
22
|
+
if (!title) {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Estimate max characters based on width (approx 9px per char for typical font)
|
|
27
|
+
// We subtract some padding/icon space
|
|
28
|
+
const estimatedCharWidth = 8.5;
|
|
29
|
+
const maxChars = Math.floor(containerWidth.value / estimatedCharWidth);
|
|
30
|
+
|
|
31
|
+
// Only truncate if the text is longer than what fits
|
|
32
|
+
if (title.length <= maxChars) {
|
|
33
|
+
return title;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Calculate dynamic start/end based on available space
|
|
37
|
+
// For UUIDs, showing the last 12 characters is often useful
|
|
38
|
+
const endChars = Math.min(UUID_END_CHARS, Math.floor(maxChars / ELLIPSIS_LENGTH));
|
|
39
|
+
const startChars = Math.max(MIN_START_CHARS, maxChars - endChars - ELLIPSIS_LENGTH);
|
|
40
|
+
|
|
41
|
+
return middleTruncate(title, maxChars, startChars, endChars);
|
|
42
|
+
});
|
|
43
|
+
|
|
11
44
|
const tooltipDisabled = computed(() => {
|
|
12
45
|
if (isLeaf !== undefined) {
|
|
13
46
|
return !isLeaf;
|
|
@@ -17,7 +50,7 @@ const tooltipDisabled = computed(() => {
|
|
|
17
50
|
</script>
|
|
18
51
|
|
|
19
52
|
<template>
|
|
20
|
-
<div class="tree-item-label-container w-100">
|
|
53
|
+
<div ref="label-container" class="tree-item-label-container w-100">
|
|
21
54
|
<v-tooltip :disabled="tooltipDisabled" location="right" open-delay="400">
|
|
22
55
|
<template #activator="{ props: tooltipProps }">
|
|
23
56
|
<span
|
|
@@ -28,7 +61,7 @@ const tooltipDisabled = computed(() => {
|
|
|
28
61
|
@mouseenter="emit('mouseenter')"
|
|
29
62
|
@mouseleave="emit('mouseleave')"
|
|
30
63
|
>
|
|
31
|
-
{{
|
|
64
|
+
{{ displayTitle }}
|
|
32
65
|
</span>
|
|
33
66
|
</template>
|
|
34
67
|
|
|
@@ -65,6 +98,7 @@ const tooltipDisabled = computed(() => {
|
|
|
65
98
|
display: inline-flex;
|
|
66
99
|
align-items: center;
|
|
67
100
|
cursor: pointer;
|
|
101
|
+
font-size: 0.8rem;
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
.inactive-item {
|
|
@@ -9,11 +9,11 @@ const { item, itemProps, selection, isSelected, getIndeterminate } = defineProps
|
|
|
9
9
|
|
|
10
10
|
defineEmits(["toggle-open", "toggle-select"]);
|
|
11
11
|
|
|
12
|
-
const INDENT_STEP =
|
|
12
|
+
const INDENT_STEP = 10;
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<div class="tree-row-content d-flex align-center px-
|
|
16
|
+
<div class="tree-row-content d-flex align-center px-2 ps-2 w-100">
|
|
17
17
|
<div
|
|
18
18
|
v-if="item.depth > 0"
|
|
19
19
|
class="flex-shrink-0"
|
|
@@ -50,7 +50,11 @@ const INDENT_STEP = 16;
|
|
|
50
50
|
|
|
51
51
|
<div class="tree-title flex-grow-1 overflow-hidden d-flex align-center ms-1 pt-1">
|
|
52
52
|
<slot name="title" :item="item.raw" :is-leaf="item.isLeaf">
|
|
53
|
-
<v-list-item-title
|
|
53
|
+
<v-list-item-title
|
|
54
|
+
:class="{ 'font-weight-bold': !item.isLeaf }"
|
|
55
|
+
class="text-black"
|
|
56
|
+
style="font-size: 0.8rem !important"
|
|
57
|
+
>
|
|
54
58
|
{{ item.raw[itemProps.title] || item.id }}
|
|
55
59
|
</v-list-item-title>
|
|
56
60
|
</slot>
|
|
@@ -64,7 +68,7 @@ const INDENT_STEP = 16;
|
|
|
64
68
|
|
|
65
69
|
<style scoped>
|
|
66
70
|
.tree-row-content {
|
|
67
|
-
min-height:
|
|
71
|
+
min-height: 28px;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
.icon-placeholder {
|
|
@@ -1,68 +1,27 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { useAdaptiveStyles } from "@ogw_front/composables/use_adaptive_styles";
|
|
3
3
|
|
|
4
4
|
const SCROLL_SYNC_DELAY = 50;
|
|
5
5
|
const SCROLL_THRESHOLD = 1;
|
|
6
|
-
const { title, closable, icon, mdiIcon, scrollTop } = defineProps({
|
|
6
|
+
const { title, closable, icon, mdiIcon, scrollTop, borderRadius } = defineProps({
|
|
7
7
|
title: { type: String, required: true },
|
|
8
8
|
closable: { type: Boolean, required: false, default: false },
|
|
9
9
|
icon: { type: String, required: false, default: "" },
|
|
10
10
|
mdiIcon: { type: String, required: false, default: "" },
|
|
11
11
|
scrollTop: { type: Number, required: false, default: 0 },
|
|
12
|
+
borderRadius: { type: String, required: false, default: "16px" },
|
|
13
|
+
borderLeft: { type: Boolean, required: false, default: true },
|
|
12
14
|
});
|
|
13
15
|
const emit = defineEmits(["close", "dragstart", "update:scrollTop"]);
|
|
14
16
|
|
|
15
17
|
const scrollContainer = useTemplateRef("scroll-container");
|
|
16
18
|
const treeviewBox = useTemplateRef("treeview-box");
|
|
17
|
-
const hybridViewerStore = useHybridViewerStore();
|
|
18
19
|
|
|
19
|
-
const
|
|
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);
|
|
20
|
+
const { adaptiveStyles } = useAdaptiveStyles(treeviewBox);
|
|
43
21
|
|
|
44
22
|
let isApplyingScroll = false;
|
|
45
23
|
let resizeObserver = undefined;
|
|
46
24
|
|
|
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
|
-
|
|
66
25
|
function handleScroll(event) {
|
|
67
26
|
if (isApplyingScroll) {
|
|
68
27
|
return;
|
|
@@ -121,7 +80,7 @@ watch(
|
|
|
121
80
|
ref="treeview-box"
|
|
122
81
|
variant="outlined"
|
|
123
82
|
class="tree-box d-flex flex-column"
|
|
124
|
-
:style="adaptiveStyles"
|
|
83
|
+
:style="[adaptiveStyles, { borderRadius, borderLeft: borderLeft ? undefined : 'none' }]"
|
|
125
84
|
>
|
|
126
85
|
<v-card-title
|
|
127
86
|
class="tree-box-header d-flex align-center"
|
|
@@ -181,7 +140,6 @@ watch(
|
|
|
181
140
|
.tree-box {
|
|
182
141
|
height: 100%;
|
|
183
142
|
min-height: 0;
|
|
184
|
-
border-radius: 16px;
|
|
185
143
|
background-color: transparent !important;
|
|
186
144
|
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
|
187
145
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
|
@@ -214,8 +172,8 @@ watch(
|
|
|
214
172
|
}
|
|
215
173
|
|
|
216
174
|
.tree-box-header {
|
|
217
|
-
height:
|
|
218
|
-
padding: 0
|
|
175
|
+
height: 32px !important;
|
|
176
|
+
padding: 0 10px !important;
|
|
219
177
|
background-color: rgba(255, 255, 255, 0.05);
|
|
220
178
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
221
179
|
flex-shrink: 0;
|
|
@@ -3,15 +3,17 @@ import GlobalObjects from "@ogw_front/components/Viewer/ObjectTree/Views/GlobalO
|
|
|
3
3
|
import ModelComponents from "@ogw_front/components/Viewer/ObjectTree/Views/ModelComponents.vue";
|
|
4
4
|
import ViewerObjectTreeBox from "@ogw_front/components/Viewer/ObjectTree/Box.vue";
|
|
5
5
|
import { geode_objects } from "@ogw_front/assets/geode_objects";
|
|
6
|
+
import { useAdaptiveStyles } from "@ogw_front/composables/use_adaptive_styles";
|
|
6
7
|
import { useTreeviewStore } from "@ogw_front/stores/treeview";
|
|
7
8
|
|
|
8
|
-
const WIDTH_MIN =
|
|
9
|
+
const WIDTH_MIN = 100;
|
|
9
10
|
const HEIGHT_MIN = 150;
|
|
10
|
-
const GAP_WIDTH =
|
|
11
|
+
const GAP_WIDTH = 0;
|
|
11
12
|
const PERCENT_100 = 100;
|
|
12
13
|
|
|
13
14
|
const TOTAL_PERCENT = 100;
|
|
14
15
|
const MAX_PANEL_WIDTH_RATIO = 0.8;
|
|
16
|
+
const AUTO_CLOSE_THRESHOLD = 80;
|
|
15
17
|
|
|
16
18
|
const { containerWidth } = defineProps({
|
|
17
19
|
containerWidth: { type: Number, required: true },
|
|
@@ -20,16 +22,23 @@ const { containerWidth } = defineProps({
|
|
|
20
22
|
const treeviewStore = useTreeviewStore();
|
|
21
23
|
const emit = defineEmits(["show-menu"]);
|
|
22
24
|
|
|
25
|
+
const activityBar = useTemplateRef("activity-bar");
|
|
26
|
+
const { adaptiveStyles: activityBarAdaptiveStyles } = useAdaptiveStyles(activityBar);
|
|
27
|
+
|
|
23
28
|
const maxWidth = computed(() => containerWidth * MAX_PANEL_WIDTH_RATIO);
|
|
24
29
|
|
|
25
|
-
const mainView = computed(() => treeviewStore.opened_views
|
|
26
|
-
const additionalViews = computed(() =>
|
|
30
|
+
const mainView = computed(() => treeviewStore.opened_views.find((view) => view.id === "main"));
|
|
31
|
+
const additionalViews = computed(() =>
|
|
32
|
+
treeviewStore.opened_views.filter((view) => view.id !== "main"),
|
|
33
|
+
);
|
|
27
34
|
|
|
28
35
|
const totalWidth = computed(() => {
|
|
29
36
|
const hasAdditional = additionalViews.value.length > 0;
|
|
30
|
-
const
|
|
37
|
+
const hasMain = Boolean(mainView.value);
|
|
38
|
+
const gap = hasAdditional && hasMain ? GAP_WIDTH : 0;
|
|
39
|
+
const firstColWidth = hasMain ? treeviewStore.panelWidth : 0;
|
|
31
40
|
const secondColWidth = hasAdditional ? treeviewStore.additionalPanelWidth : 0;
|
|
32
|
-
return `${
|
|
41
|
+
return `${firstColWidth + secondColWidth + gap}px`;
|
|
33
42
|
});
|
|
34
43
|
|
|
35
44
|
const rowHeights = computed({
|
|
@@ -90,7 +99,7 @@ function onResizeStart(event) {
|
|
|
90
99
|
const startX = event.clientX;
|
|
91
100
|
function resize(move_event) {
|
|
92
101
|
const deltaX = move_event.clientX - startX;
|
|
93
|
-
let newWidth =
|
|
102
|
+
let newWidth = startWidth + deltaX;
|
|
94
103
|
const hasAdditional = additionalViews.value.length > 0;
|
|
95
104
|
const gap = hasAdditional ? GAP_WIDTH : 0;
|
|
96
105
|
const currentTotalWidth =
|
|
@@ -98,6 +107,12 @@ function onResizeStart(event) {
|
|
|
98
107
|
if (currentTotalWidth > maxWidth.value) {
|
|
99
108
|
newWidth = maxWidth.value - (hasAdditional ? treeviewStore.additionalPanelWidth : 0) - gap;
|
|
100
109
|
}
|
|
110
|
+
|
|
111
|
+
if (newWidth < AUTO_CLOSE_THRESHOLD) {
|
|
112
|
+
treeviewStore.closeView("main");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
101
116
|
treeviewStore.setPanelWidth(Math.max(WIDTH_MIN, newWidth));
|
|
102
117
|
document.body.style.userSelect = "none";
|
|
103
118
|
}
|
|
@@ -115,11 +130,13 @@ function onAdditionalResizeStart(event) {
|
|
|
115
130
|
const startX = event.clientX;
|
|
116
131
|
function resize(move_event) {
|
|
117
132
|
const deltaX = move_event.clientX - startX;
|
|
118
|
-
|
|
133
|
+
const newWidth = startWidth + deltaX;
|
|
119
134
|
const currentTotalWidth = treeviewStore.panelWidth + newWidth + GAP_WIDTH;
|
|
120
|
-
if (
|
|
121
|
-
|
|
135
|
+
if (newWidth < AUTO_CLOSE_THRESHOLD) {
|
|
136
|
+
treeviewStore.closeView(additionalViews.value.at(-1).id);
|
|
137
|
+
return;
|
|
122
138
|
}
|
|
139
|
+
|
|
123
140
|
treeviewStore.setAdditionalPanelWidth(Math.max(WIDTH_MIN, newWidth));
|
|
124
141
|
document.body.style.userSelect = "none";
|
|
125
142
|
}
|
|
@@ -177,88 +194,154 @@ function onVerticalResizeStart(event, index) {
|
|
|
177
194
|
<template>
|
|
178
195
|
<div
|
|
179
196
|
v-if="treeviewStore.items.length > 0"
|
|
180
|
-
class="treeview-
|
|
181
|
-
:style="{ width: totalWidth }"
|
|
197
|
+
class="treeview-layout d-flex"
|
|
182
198
|
@contextmenu.prevent
|
|
183
199
|
@mousedown.stop
|
|
184
200
|
>
|
|
185
201
|
<div
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}"
|
|
202
|
+
ref="activity-bar"
|
|
203
|
+
class="activity-bar d-flex flex-column align-center py-2"
|
|
204
|
+
:style="activityBarAdaptiveStyles"
|
|
190
205
|
>
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
<v-btn
|
|
207
|
+
icon="mdi-file-tree-outline"
|
|
208
|
+
variant="text"
|
|
209
|
+
:color="mainView ? 'primary' : 'black'"
|
|
210
|
+
class="mb-2"
|
|
211
|
+
v-tooltip="'Toggle Objects'"
|
|
212
|
+
@click="treeviewStore.toggleView('main')"
|
|
213
|
+
/>
|
|
199
214
|
</div>
|
|
200
215
|
|
|
201
|
-
<div
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
@
|
|
218
|
-
@drop="onDrop(index + 1)"
|
|
216
|
+
<div class="treeview-container d-flex" :style="{ width: totalWidth }">
|
|
217
|
+
<div
|
|
218
|
+
v-if="mainView"
|
|
219
|
+
class="column main-column"
|
|
220
|
+
:style="{
|
|
221
|
+
width: `${treeviewStore.panelWidth}px`,
|
|
222
|
+
}"
|
|
223
|
+
>
|
|
224
|
+
<ViewerObjectTreeBox
|
|
225
|
+
:title="mainView?.title || 'Objects'"
|
|
226
|
+
mdi-icon="mdi-file-tree-outline"
|
|
227
|
+
:scroll-top="mainView?.scrollTop || 0"
|
|
228
|
+
closable
|
|
229
|
+
:border-radius="additionalViews.length > 0 ? '0' : '0 16px 16px 0'"
|
|
230
|
+
:border-left="false"
|
|
231
|
+
@close="treeviewStore.closeView('main')"
|
|
232
|
+
@update:scroll-top="mainView && treeviewStore.setScrollTop(mainView.id, $event)"
|
|
219
233
|
>
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
<GlobalObjects data-testid="mainObjectTree" @show-menu="emit('show-menu', $event)" />
|
|
235
|
+
</ViewerObjectTreeBox>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div
|
|
239
|
+
v-if="mainView && additionalViews.length > 0"
|
|
240
|
+
class="column-separator"
|
|
241
|
+
@mousedown="onResizeStart"
|
|
242
|
+
/>
|
|
243
|
+
|
|
244
|
+
<div
|
|
245
|
+
v-if="additionalViews.length > 0"
|
|
246
|
+
class="column additional-column"
|
|
247
|
+
:style="{
|
|
248
|
+
width: `${treeviewStore.additionalPanelWidth}px`,
|
|
249
|
+
}"
|
|
250
|
+
>
|
|
251
|
+
<template v-for="(view, index) in additionalViews" :key="view.id">
|
|
252
|
+
<div
|
|
253
|
+
class="view-wrapper"
|
|
254
|
+
:class="{
|
|
255
|
+
'drag-over': draggedIndex !== undefined && draggedIndex !== index + 1,
|
|
256
|
+
}"
|
|
257
|
+
:style="{ flex: `0 0 ${rowHeights[index]}%` }"
|
|
258
|
+
@dragover="onDragOver"
|
|
259
|
+
@drop="onDrop(index + 1)"
|
|
228
260
|
>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
261
|
+
<ViewerObjectTreeBox
|
|
262
|
+
:title="view.title"
|
|
263
|
+
:icon="geode_objects[view.geode_object_type]?.image"
|
|
264
|
+
:scroll-top="view.scrollTop"
|
|
265
|
+
closable
|
|
266
|
+
:border-radius="index === additionalViews.length - 1 ? '0 16px 16px 0' : '0'"
|
|
267
|
+
:border-left="false"
|
|
268
|
+
@close="treeviewStore.closeView(view.id)"
|
|
269
|
+
@dragstart="onDragStart(index + 1)"
|
|
270
|
+
@update:scroll-top="treeviewStore.setScrollTop(view.id, $event)"
|
|
271
|
+
>
|
|
272
|
+
<ModelComponents
|
|
273
|
+
data-testid="modelComponentsObjectTree"
|
|
274
|
+
:id="view.id"
|
|
275
|
+
@show-menu="emit('show-menu', $event)"
|
|
276
|
+
/>
|
|
277
|
+
</ViewerObjectTreeBox>
|
|
278
|
+
</div>
|
|
279
|
+
<div
|
|
280
|
+
v-if="index < additionalViews.length - 1"
|
|
281
|
+
class="v-split-resizer"
|
|
282
|
+
@mousedown="onVerticalResizeStart($event, index)"
|
|
283
|
+
/>
|
|
284
|
+
</template>
|
|
285
|
+
</div>
|
|
286
|
+
<div
|
|
287
|
+
v-if="treeviewStore.opened_views.length > 0"
|
|
288
|
+
class="total-resizer"
|
|
289
|
+
@mousedown="
|
|
290
|
+
additionalViews.length > 0
|
|
291
|
+
? onAdditionalResizeStart($event)
|
|
292
|
+
: mainView
|
|
293
|
+
? onResizeStart($event)
|
|
294
|
+
: undefined
|
|
295
|
+
"
|
|
296
|
+
/>
|
|
242
297
|
</div>
|
|
243
|
-
<div
|
|
244
|
-
class="total-resizer"
|
|
245
|
-
@mousedown="
|
|
246
|
-
additionalViews.length > 0 ? onAdditionalResizeStart($event) : onResizeStart($event)
|
|
247
|
-
"
|
|
248
|
-
/>
|
|
249
298
|
</div>
|
|
250
299
|
</template>
|
|
251
300
|
|
|
252
301
|
<style scoped>
|
|
253
|
-
.treeview-
|
|
302
|
+
.treeview-layout {
|
|
254
303
|
position: absolute;
|
|
255
304
|
z-index: 2;
|
|
256
305
|
left: 0;
|
|
257
306
|
top: 0;
|
|
258
307
|
height: calc(100vh - 100px);
|
|
259
|
-
margin-top:
|
|
260
|
-
margin-left: 10px;
|
|
308
|
+
margin-top: 8px;
|
|
261
309
|
pointer-events: auto;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.activity-bar {
|
|
313
|
+
width: 48px;
|
|
314
|
+
height: 100%;
|
|
315
|
+
border-radius: 16px 0 0 16px;
|
|
316
|
+
margin-left: 10px;
|
|
317
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
|
318
|
+
position: relative;
|
|
319
|
+
overflow: hidden;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.activity-bar::before {
|
|
323
|
+
content: "";
|
|
324
|
+
position: absolute;
|
|
325
|
+
inset: 0;
|
|
326
|
+
background: rgba(255, 255, 255, var(--adaptive-opacity));
|
|
327
|
+
backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness));
|
|
328
|
+
-webkit-backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness));
|
|
329
|
+
mix-blend-mode: lighten;
|
|
330
|
+
z-index: 0;
|
|
331
|
+
pointer-events: none;
|
|
332
|
+
border-radius: inherit;
|
|
333
|
+
transition:
|
|
334
|
+
background-color 0.3s ease,
|
|
335
|
+
backdrop-filter 0.3s ease;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.activity-bar > * {
|
|
339
|
+
position: relative;
|
|
340
|
+
z-index: 1;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.treeview-container {
|
|
344
|
+
height: 100%;
|
|
262
345
|
width: max-content;
|
|
263
346
|
min-width: min-content;
|
|
264
347
|
}
|
|
@@ -289,7 +372,6 @@ function onVerticalResizeStart(event, index) {
|
|
|
289
372
|
display: flex;
|
|
290
373
|
flex-direction: column;
|
|
291
374
|
overflow: hidden;
|
|
292
|
-
padding: 2px;
|
|
293
375
|
transition: transform 0.2s;
|
|
294
376
|
min-height: 150px;
|
|
295
377
|
}
|
|
@@ -361,6 +443,6 @@ function onVerticalResizeStart(event, index) {
|
|
|
361
443
|
}
|
|
362
444
|
|
|
363
445
|
.total-resizer:hover {
|
|
364
|
-
background-color:
|
|
446
|
+
background-color: transparent;
|
|
365
447
|
}
|
|
366
448
|
</style>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { computed, ref, watch } from "vue";
|
|
2
|
+
import { useElementBounding, useThrottleFn } from "@vueuse/core";
|
|
3
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
4
|
+
|
|
5
|
+
const LUMINANCE_THRESHOLD = 0.65;
|
|
6
|
+
const ADAPTIVE_EXPONENT = 0.3;
|
|
7
|
+
|
|
8
|
+
const MIN_BLUR = 8;
|
|
9
|
+
const MAX_BLUR = 25;
|
|
10
|
+
|
|
11
|
+
const MIN_OPACITY = 0;
|
|
12
|
+
const MAX_OPACITY = 0.5;
|
|
13
|
+
|
|
14
|
+
const MIN_BOOST = 1;
|
|
15
|
+
const MAX_BOOST = 1.2;
|
|
16
|
+
const ADAPTIVE_REFRESH_RATE = 150;
|
|
17
|
+
|
|
18
|
+
export function useAdaptiveStyles(targetRef) {
|
|
19
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
20
|
+
const { x, y, width, height } = useElementBounding(targetRef);
|
|
21
|
+
const brightness = ref(LUMINANCE_THRESHOLD);
|
|
22
|
+
|
|
23
|
+
const updateBrightness = useThrottleFn(() => {
|
|
24
|
+
brightness.value = hybridViewerStore.getAverageBrightness({
|
|
25
|
+
x: x.value,
|
|
26
|
+
y: y.value,
|
|
27
|
+
width: width.value,
|
|
28
|
+
height: height.value,
|
|
29
|
+
});
|
|
30
|
+
}, ADAPTIVE_REFRESH_RATE);
|
|
31
|
+
|
|
32
|
+
watch([x, y, width, height, () => hybridViewerStore.latestImage], updateBrightness, {
|
|
33
|
+
immediate: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const adaptiveStyles = computed(() => {
|
|
37
|
+
const normalized = Math.min(1, brightness.value / LUMINANCE_THRESHOLD);
|
|
38
|
+
const darkFactor = (1 - normalized) ** ADAPTIVE_EXPONENT;
|
|
39
|
+
|
|
40
|
+
const blur = MIN_BLUR + darkFactor * (MAX_BLUR - MIN_BLUR);
|
|
41
|
+
const opacity = MIN_OPACITY + darkFactor * (MAX_OPACITY - MIN_OPACITY);
|
|
42
|
+
const brightnessBoost = MIN_BOOST + darkFactor * (MAX_BOOST - MIN_BOOST);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"--adaptive-blur": `${blur}px`,
|
|
46
|
+
"--adaptive-opacity": opacity,
|
|
47
|
+
"--adaptive-brightness": brightnessBoost,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
adaptiveStyles,
|
|
53
|
+
brightness,
|
|
54
|
+
};
|
|
55
|
+
}
|
package/app/stores/treeview.js
CHANGED
|
@@ -68,9 +68,22 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
function closeView(
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
function closeView(id) {
|
|
72
|
+
opened_views.value = opened_views.value.filter((view) => view.id !== id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function toggleView(id) {
|
|
76
|
+
const index = opened_views.value.findIndex((view) => view.id === id);
|
|
77
|
+
if (index !== -1) {
|
|
78
|
+
closeView(id);
|
|
79
|
+
} else if (id === "main") {
|
|
80
|
+
opened_views.value.unshift({
|
|
81
|
+
type: "object",
|
|
82
|
+
id: "main",
|
|
83
|
+
title: "Objects",
|
|
84
|
+
scrollTop: 0,
|
|
85
|
+
opened: [],
|
|
86
|
+
});
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
|
|
@@ -116,7 +129,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
116
129
|
function displayAdditionalTree(id, title, geodeObjectType) {
|
|
117
130
|
const index = opened_views.value.findIndex((view) => view.id === id);
|
|
118
131
|
if (index !== -1) {
|
|
119
|
-
return closeView(
|
|
132
|
+
return closeView(id);
|
|
120
133
|
}
|
|
121
134
|
additionalPanelWidth.value = panelWidth.value;
|
|
122
135
|
opened_views.value.push({
|
|
@@ -247,6 +260,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
247
260
|
renameItem,
|
|
248
261
|
displayAdditionalTree,
|
|
249
262
|
closeView,
|
|
263
|
+
toggleView,
|
|
250
264
|
moveView,
|
|
251
265
|
importStores,
|
|
252
266
|
displayFileTree,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const DEFAULT_MAX_LENGTH = 20;
|
|
2
|
+
const DEFAULT_START_CHARS = 8;
|
|
3
|
+
const DEFAULT_END_CHARS = 6;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Truncates a string in the middle.
|
|
7
|
+
* @param {string} text - The string to truncate.
|
|
8
|
+
* @param {number} maxLength - The maximum length of the string before truncation.
|
|
9
|
+
* @param {number} startChars - Number of characters to keep at the beginning.
|
|
10
|
+
* @param {number} endChars - Number of characters to keep at the end.
|
|
11
|
+
* @returns {string} The truncated string.
|
|
12
|
+
*/
|
|
13
|
+
export function middleTruncate(
|
|
14
|
+
text,
|
|
15
|
+
maxLength = DEFAULT_MAX_LENGTH,
|
|
16
|
+
startChars = DEFAULT_START_CHARS,
|
|
17
|
+
endChars = DEFAULT_END_CHARS,
|
|
18
|
+
) {
|
|
19
|
+
if (!text || text.length <= maxLength) {
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const start = text.slice(0, startChars);
|
|
24
|
+
const end = text.slice(-endChars);
|
|
25
|
+
return `${start}...${end}`;
|
|
26
|
+
}
|
package/package.json
CHANGED