@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.
@@ -163,10 +163,10 @@ const { focusedIndex, handleKeyDown } = useTreeKeyboardNav(
163
163
  }
164
164
 
165
165
  .tree-row-wrapper {
166
- min-height: 44px !important;
166
+ min-height: 28px !important;
167
167
  cursor: pointer;
168
- border-radius: 8px;
169
- margin: 1px 4px;
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-2 py-1">
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: 40px;
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: 40px !important;
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: 40px !important;
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
- {{ actualItem.title }}
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 = 16;
12
+ const INDENT_STEP = 10;
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div class="tree-row-content d-flex align-center px-4 ps-3 w-100">
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 :class="{ 'font-weight-bold': !item.isLeaf }" class="text-black">
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: 44px;
71
+ min-height: 28px;
68
72
  }
69
73
 
70
74
  .icon-placeholder {
@@ -1,68 +1,27 @@
1
1
  <script setup>
2
- import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
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 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);
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: 40px !important;
218
- padding: 0 12px !important;
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 = 200;
9
+ const WIDTH_MIN = 100;
9
10
  const HEIGHT_MIN = 150;
10
- const GAP_WIDTH = 10;
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[0]);
26
- const additionalViews = computed(() => treeviewStore.opened_views.slice(1));
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 gap = hasAdditional ? GAP_WIDTH : 0;
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 `${treeviewStore.panelWidth + secondColWidth + gap}px`;
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 = Math.max(WIDTH_MIN, startWidth + deltaX);
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
- let newWidth = Math.max(WIDTH_MIN, startWidth + deltaX);
133
+ const newWidth = startWidth + deltaX;
119
134
  const currentTotalWidth = treeviewStore.panelWidth + newWidth + GAP_WIDTH;
120
- if (currentTotalWidth > maxWidth.value) {
121
- newWidth = maxWidth.value - treeviewStore.panelWidth - GAP_WIDTH;
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-container d-flex"
181
- :style="{ width: totalWidth }"
197
+ class="treeview-layout d-flex"
182
198
  @contextmenu.prevent
183
199
  @mousedown.stop
184
200
  >
185
201
  <div
186
- class="column main-column"
187
- :style="{
188
- width: `${treeviewStore.panelWidth}px`,
189
- }"
202
+ ref="activity-bar"
203
+ class="activity-bar d-flex flex-column align-center py-2"
204
+ :style="activityBarAdaptiveStyles"
190
205
  >
191
- <ViewerObjectTreeBox
192
- :title="mainView.title"
193
- mdi-icon="mdi-file-tree-outline"
194
- :scroll-top="mainView.scrollTop"
195
- @update:scroll-top="treeviewStore.setScrollTop(mainView.id, $event)"
196
- >
197
- <GlobalObjects data-testid="mainObjectTree" @show-menu="emit('show-menu', $event)" />
198
- </ViewerObjectTreeBox>
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 v-if="additionalViews.length > 0" class="column-separator" @mousedown="onResizeStart" />
202
-
203
- <div
204
- v-if="additionalViews.length > 0"
205
- class="column additional-column"
206
- :style="{
207
- width: `${treeviewStore.additionalPanelWidth}px`,
208
- }"
209
- >
210
- <template v-for="(view, index) in additionalViews" :key="view.id">
211
- <div
212
- class="view-wrapper"
213
- :class="{
214
- 'drag-over': draggedIndex !== undefined && draggedIndex !== index + 1,
215
- }"
216
- :style="{ flex: `0 0 ${rowHeights[index]}%` }"
217
- @dragover="onDragOver"
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
- <ViewerObjectTreeBox
221
- :title="view.title"
222
- :icon="geode_objects[view.geode_object_type]?.image"
223
- :scroll-top="view.scrollTop"
224
- closable
225
- @close="treeviewStore.closeView(index + 1)"
226
- @dragstart="onDragStart(index + 1)"
227
- @update:scroll-top="treeviewStore.setScrollTop(view.id, $event)"
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
- <ModelComponents
230
- data-testid="modelComponentsObjectTree"
231
- :id="view.id"
232
- @show-menu="emit('show-menu', $event)"
233
- />
234
- </ViewerObjectTreeBox>
235
- </div>
236
- <div
237
- v-if="index < additionalViews.length - 1"
238
- class="v-split-resizer"
239
- @mousedown="onVerticalResizeStart($event, index)"
240
- />
241
- </template>
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-container {
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: 10px;
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: rgba(0, 0, 0, 0.2);
446
+ background-color: transparent;
365
447
  }
366
448
  </style>
@@ -193,6 +193,6 @@ function expandAll() {
193
193
 
194
194
  .transparent-treeview {
195
195
  background-color: transparent;
196
- margin: 4px 0;
196
+ margin: 2px 0;
197
197
  }
198
198
  </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
+ }
@@ -68,9 +68,22 @@ export const useTreeviewStore = defineStore("treeview", () => {
68
68
  }
69
69
  });
70
70
 
71
- function closeView(index) {
72
- if (index > 0) {
73
- opened_views.value = opened_views.value.filter((view, view_index) => view_index !== index);
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(index);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.18.1-rc.1",
3
+ "version": "10.18.1-rc.2",
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": {