@geode/opengeodeweb-front 10.32.2-rc.2 → 10.32.2-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,49 +1,64 @@
1
1
  <script setup>
2
- import GlassCard from "@ogw_front/components/GlassCard";
3
2
  import List from "@ogw_front/components/CameraManager/List";
4
3
  import Saver from "@ogw_front/components/CameraManager/Saver";
4
+ import ToolPanel from "@ogw_front/components/ToolPanel";
5
5
 
6
6
  const emit = defineEmits(["close"]);
7
7
 
8
- const { showDialog, width } = defineProps({
8
+ const { showDialog, width, escapeFunction } = defineProps({
9
9
  showDialog: { type: Boolean, required: true },
10
10
  width: { type: Number, required: false, default: 260 },
11
+ escapeFunction: { type: Function, default: undefined },
11
12
  });
13
+
14
+ const show = computed({
15
+ get: () => showDialog,
16
+ set: (val) => {
17
+ if (!val) {
18
+ handleClose();
19
+ }
20
+ },
21
+ });
22
+
23
+ function handleClose() {
24
+ if (escapeFunction) {
25
+ escapeFunction();
26
+ } else {
27
+ emit("close");
28
+ }
29
+ }
12
30
  </script>
13
31
 
14
32
  <template>
15
- <GlassCard
16
- v-if="showDialog"
17
- @click.stop
33
+ <ToolPanel
34
+ v-model="show"
18
35
  title="Camera Positions"
19
36
  :width="width"
20
- :ripple="false"
21
- variant="panel"
22
- padding="pa-0"
23
- class="position-absolute elevation-24"
24
- style="top: 90px; right: 55px; z-index: 1"
37
+ :escapeFunction="handleClose"
38
+ style="top: 90px; right: 55px"
39
+ z-index="1"
25
40
  >
26
41
  <v-card-text class="pa-0">
27
42
  <Saver />
28
- <v-divider></v-divider>
43
+ <v-divider />
29
44
  <List />
30
45
  </v-card-text>
31
46
 
32
- <v-divider></v-divider>
33
-
34
- <v-card-actions class="pa-2">
35
- <v-spacer></v-spacer>
36
- <v-btn
37
- variant="text"
38
- size="small"
39
- color="white"
40
- class="text-caption text-none"
41
- data-testid="closeCameraManagerButton"
42
- @click="emit('close')"
43
- >Close</v-btn
44
- >
45
- </v-card-actions>
46
- </GlassCard>
47
+ <template #actions>
48
+ <v-card-actions class="justify-center pb-3 pt-0" style="gap: 8px">
49
+ <v-btn
50
+ variant="text"
51
+ size="small"
52
+ color="white"
53
+ class="text-caption text-none"
54
+ data-testid="closeCameraManagerButton"
55
+ @click="handleClose"
56
+ >
57
+ Close
58
+ </v-btn>
59
+ </v-card-actions>
60
+ </template>
61
+ </ToolPanel>
47
62
  </template>
48
63
 
49
64
  <style scoped>
@@ -5,9 +5,10 @@ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
5
5
  import { newInstance as vtkAnnotatedCubeActor } from "@kitware/vtk.js/Rendering/Core/AnnotatedCubeActor";
6
6
  import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
7
7
 
8
- const { panel, width } = defineProps({
8
+ const { panel, width, escapeFunction } = defineProps({
9
9
  panel: { type: Boolean, default: false },
10
10
  width: { type: Number, default: 260 },
11
+ escapeFunction: { type: Function, default: undefined },
11
12
  });
12
13
 
13
14
  const show = defineModel("show", { type: Boolean, default: false });
@@ -172,6 +173,7 @@ watch(hoveredFace, (newFace, oldFace) => {
172
173
  v-model="show"
173
174
  title="Camera Orientations"
174
175
  :width="width"
176
+ :escapeFunction="escapeFunction"
175
177
  style="top: 90px; right: 55px"
176
178
  z-index="1"
177
179
  >
@@ -7,6 +7,10 @@ import { useDataStore } from "@ogw_front/stores/data";
7
7
  import { useDebounceFn } from "@vueuse/core";
8
8
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
9
9
 
10
+ const { escapeFunction } = defineProps({
11
+ escapeFunction: { type: Function, default: undefined },
12
+ });
13
+
10
14
  const show = defineModel("show", { type: Boolean, default: false });
11
15
  const dataStore = useDataStore();
12
16
  const hybridViewerStore = useHybridViewerStore();
@@ -145,7 +149,13 @@ onBeforeUnmount(cleanupLocalWidget);
145
149
  </script>
146
150
 
147
151
  <template>
148
- <ToolPanel v-model="show" title="Clipping Planes" :width="360" :click-outside="false">
152
+ <ToolPanel
153
+ v-model="show"
154
+ title="Clipping Planes"
155
+ :width="360"
156
+ :click-outside="false"
157
+ :escapeFunction="escapeFunction"
158
+ >
149
159
  <v-card-text class="pa-3 max-panel-height overflow-y-auto">
150
160
  <v-sheet
151
161
  ref="widgetContainer"
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
2
  import { computed, useAttrs } from "vue";
3
+ import { onKeyStroke } from "@vueuse/core";
3
4
 
4
- const { variant, rounded, padding, theme } = defineProps({
5
+ const { variant, rounded, padding, theme, escapeFunction } = defineProps({
5
6
  variant: {
6
7
  type: String,
7
8
  default: "panel",
@@ -10,10 +11,17 @@ const { variant, rounded, padding, theme } = defineProps({
10
11
  rounded: { type: String, default: "xl" },
11
12
  padding: { type: String, default: "pa-6" },
12
13
  theme: { type: String, default: undefined },
14
+ escapeFunction: { type: Function, default: undefined },
13
15
  });
14
16
 
15
17
  const attrs = useAttrs();
16
18
  const isInteractive = computed(() => Boolean(attrs.onClick));
19
+
20
+ onKeyStroke("Escape", () => {
21
+ if (escapeFunction) {
22
+ escapeFunction();
23
+ }
24
+ });
17
25
  </script>
18
26
 
19
27
  <template>
@@ -2,6 +2,10 @@
2
2
  import ToolPanel from "@ogw_front/components/ToolPanel";
3
3
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
4
4
 
5
+ const { escapeFunction } = defineProps({
6
+ escapeFunction: { type: Function, default: undefined },
7
+ });
8
+
5
9
  const show = defineModel("show", { type: Boolean, default: false });
6
10
  const hybridViewerStore = useHybridViewerStore();
7
11
 
@@ -57,6 +61,7 @@ async function applyManualCoords() {
57
61
  :width="300"
58
62
  close-label="Stop ruler"
59
63
  :click-outside="false"
64
+ :escapeFunction="escapeFunction"
60
65
  >
61
66
  <v-card-text class="pa-3">
62
67
  <v-switch
@@ -8,8 +8,9 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
8
8
 
9
9
  const show = defineModel({ type: Boolean, default: false });
10
10
 
11
- const { width } = defineProps({
11
+ const { width, escapeFunction } = defineProps({
12
12
  width: { type: Number, default: 260 },
13
+ escapeFunction: { type: Function, default: undefined },
13
14
  });
14
15
 
15
16
  const output_extensions =
@@ -72,6 +73,13 @@ watch(screenshot_type, (value) => {
72
73
  output_extension.value = "png";
73
74
  }
74
75
  });
76
+
77
+ function handleClose() {
78
+ if (escapeFunction) {
79
+ escapeFunction();
80
+ }
81
+ show.value = false;
82
+ }
75
83
  </script>
76
84
 
77
85
  <template>
@@ -81,6 +89,7 @@ watch(screenshot_type, (value) => {
81
89
  :width="width"
82
90
  close-label="Cancel"
83
91
  action-label="Screenshot"
92
+ :escapeFunction="escapeFunction"
84
93
  @action="takeScreenshot"
85
94
  >
86
95
  <v-container class="pa-3 py-1">
@@ -167,7 +176,7 @@ watch(screenshot_type, (value) => {
167
176
  color="white"
168
177
  data-testid="screenshotCancelButton"
169
178
  class="text-caption text-none"
170
- @click="show = false"
179
+ @click="handleClose"
171
180
  >
172
181
  Cancel
173
182
  </v-btn>
@@ -8,6 +8,10 @@ const DEFAULT_SHRINK_VALUE = 0.8;
8
8
  const MAX_SHRINK_VALUE = 1;
9
9
  const DEBOUNCE_DELAY = 100;
10
10
 
11
+ const { escapeFunction } = defineProps({
12
+ escapeFunction: { type: Function, default: undefined },
13
+ });
14
+
11
15
  const show = defineModel("show", { type: Boolean, default: false });
12
16
  const dataStore = useDataStore();
13
17
  const hybridViewerStore = useHybridViewerStore();
@@ -88,7 +92,13 @@ watch(
88
92
  </script>
89
93
 
90
94
  <template>
91
- <ToolPanel v-model="show" title="Shrink Filter" :width="340" :click-outside="false">
95
+ <ToolPanel
96
+ v-model="show"
97
+ title="Shrink Filter"
98
+ :width="340"
99
+ :click-outside="false"
100
+ :escapeFunction="escapeFunction"
101
+ >
92
102
  <v-card-text class="pa-3 max-panel-height overflow-y-auto overflow-x-hidden">
93
103
  <v-switch
94
104
  v-model="targetAllVisible"
@@ -1,18 +1,22 @@
1
1
  <script setup>
2
2
  import GlassCard from "@ogw_front/components/GlassCard";
3
3
 
4
- const { title, width, closeLabel, actionLabel, clickOutside } = defineProps({
4
+ const { title, width, closeLabel, actionLabel, clickOutside, escapeFunction } = defineProps({
5
5
  title: { type: String, default: "" },
6
6
  width: { type: Number, default: 260 },
7
7
  closeLabel: { type: String, default: "Close" },
8
8
  actionLabel: { type: String, default: undefined },
9
9
  clickOutside: { type: Boolean, default: true },
10
+ escapeFunction: { type: Function, default: undefined },
10
11
  });
11
12
 
12
13
  const model = defineModel({ type: Boolean, default: false });
13
14
  const emit = defineEmits(["action"]);
14
15
 
15
16
  function close() {
17
+ if (escapeFunction) {
18
+ escapeFunction();
19
+ }
16
20
  model.value = false;
17
21
  }
18
22
  </script>
@@ -27,6 +31,7 @@ function close() {
27
31
  variant="panel"
28
32
  padding="pa-0"
29
33
  class="position-absolute rounded-xl elevation-24 tool-panel"
34
+ :escapeFunction="close"
30
35
  v-bind="$attrs"
31
36
  >
32
37
  <v-card-text class="pa-0 overflow-hidden position-relative">
@@ -8,6 +8,7 @@ import Ruler from "@ogw_front/components/Ruler";
8
8
  import Screenshot from "@ogw_front/components/Screenshot";
9
9
  import ShrinkFilter from "@ogw_front/components/ShrinkFilter";
10
10
  import ZScaling from "@ogw_front/components/ZScaling";
11
+ import { onKeyStroke } from "@vueuse/core";
11
12
  import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
12
13
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
13
14
  import { useViewerStore } from "@ogw_front/stores/viewer";
@@ -23,6 +24,7 @@ const showShrinkFilter = ref(false);
23
24
  const showRuler = ref(false);
24
25
  const grid_scale = ref(false);
25
26
  const zScale = ref(hybridViewerStore.zScale);
27
+ const openSubMenus = ref({});
26
28
 
27
29
  watch(
28
30
  () => hybridViewerStore.zScale,
@@ -36,6 +38,15 @@ async function handleZScalingClose() {
36
38
  showZScaling.value = false;
37
39
  }
38
40
 
41
+ onKeyStroke("Escape", () => {
42
+ const openMenuKeys = Object.keys(openSubMenus.value).filter((key) => openSubMenus.value[key]);
43
+ if (openMenuKeys.length > 0) {
44
+ for (const key of openMenuKeys) {
45
+ openSubMenus.value[key] = false;
46
+ }
47
+ }
48
+ });
49
+
39
50
  const camera_options = computed(() => [
40
51
  {
41
52
  testId: "resetCameraButton",
@@ -193,6 +204,7 @@ const camera_options = computed(() => [
193
204
  <v-col>
194
205
  <v-menu
195
206
  v-if="camera_option.menu && !camera_option.action"
207
+ v-model="openSubMenus[camera_option.testId]"
196
208
  location="start"
197
209
  :close-on-content-click="false"
198
210
  >
@@ -248,17 +260,24 @@ const camera_options = computed(() => [
248
260
  panel
249
261
  @select="hybridViewerStore.setCameraOrientation"
250
262
  />
251
- <Screenshot v-model="take_screenshot" />
263
+ <Screenshot v-model="take_screenshot" :escapeFunction="() => (take_screenshot = false)" />
252
264
  <CameraManager :showDialog="show_camera_manager" @close="show_camera_manager = false" />
253
265
  <ZScaling
254
266
  v-model:show="showZScaling"
255
267
  v-model="zScale"
256
268
  :width="260"
269
+ :escapeFunction="handleZScalingClose"
257
270
  @apply="handleZScalingClose"
258
271
  />
259
- <ClippingPlanes v-model:show="showClippingPlanes" />
260
- <ShrinkFilter v-model:show="showShrinkFilter" />
261
- <Ruler v-model:show="showRuler" />
272
+ <ClippingPlanes
273
+ v-model:show="showClippingPlanes"
274
+ :escapeFunction="() => (showClippingPlanes = false)"
275
+ />
276
+ <ShrinkFilter
277
+ v-model:show="showShrinkFilter"
278
+ :escapeFunction="() => (showShrinkFilter = false)"
279
+ />
280
+ <Ruler v-model:show="showRuler" :escapeFunction="() => (showRuler = false)" />
262
281
  </template>
263
282
 
264
283
  <style module>
@@ -36,6 +36,12 @@ const dragStartY = ref(0);
36
36
  const menuX = ref(x);
37
37
  const menuY = ref(y);
38
38
 
39
+ watch(show_menu, (newVal) => {
40
+ if (!newVal) {
41
+ menuStore.closeMenu();
42
+ }
43
+ });
44
+
39
45
  watch(
40
46
  () => [x, y, containerWidth, containerHeight],
41
47
  ([newX, newY]) => {
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import ColorMapList from "@ogw_front/components/Viewer/Options/ColorMapList.vue";
3
3
  import { getPresetsWithCurrentAtTop } from "@ogw_front/utils/colormap";
4
+
4
5
  import { useDataStyleStore } from "@ogw_front/stores/data_style";
5
6
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
6
7
 
@@ -24,11 +24,23 @@ function stopHoverHighlight() {
24
24
  hybridViewerStore.clearHoverHighlight();
25
25
  }
26
26
 
27
- onKeyStroke("Escape", () => {
28
- if (hybridViewerStore.is_ruler_active) {
27
+ onKeyStroke("Escape", (event) => {
28
+ let consumed = false;
29
+ if (viewerStore.picking_mode) {
30
+ viewerStore.toggle_picking_mode(false);
31
+ consumed = true;
32
+ } else if (hybridViewerStore.is_picking) {
33
+ hybridViewerStore.is_picking = false;
34
+ consumed = true;
35
+ } else if (hybridViewerStore.is_ruler_active) {
29
36
  hybridViewerStore.deactivateRuler();
37
+ consumed = true;
30
38
  } else if (hybridViewerStore.is_hover_highlight) {
31
39
  stopHoverHighlight();
40
+ consumed = true;
41
+ }
42
+ if (consumed && event) {
43
+ event.stopImmediatePropagation();
32
44
  }
33
45
  });
34
46
 
@@ -4,8 +4,9 @@ import ToolPanel from "@ogw_front/components/ToolPanel";
4
4
  const zScale = defineModel({ type: Number, default: 1 });
5
5
  const show = defineModel("show", { type: Boolean, default: false });
6
6
 
7
- const { width } = defineProps({
7
+ const { width, escapeFunction } = defineProps({
8
8
  width: { type: Number, default: 260 },
9
+ escapeFunction: { type: Function, default: undefined },
9
10
  });
10
11
 
11
12
  const emit = defineEmits(["apply"]);
@@ -22,6 +23,7 @@ function apply() {
22
23
  title="Z Scaling Control"
23
24
  :width="width"
24
25
  action-label="Apply"
26
+ :escapeFunction="escapeFunction"
25
27
  @action="apply"
26
28
  >
27
29
  <v-container class="pa-3 py-1">
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
+ import { useToggle } from "@vueuse/core";
3
+
2
4
  import CsvSettings from "./CsvSettings.vue";
3
5
  import CsvTable from "./CsvTable.vue";
4
- import { useToggle } from "@vueuse/core";
5
6
 
6
7
  const { file, modelValue } = defineProps({
7
8
  file: { type: Object, required: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.32.2-rc.2",
3
+ "version": "10.32.2-rc.3",
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": {
@@ -0,0 +1,66 @@
1
+ // Third party imports
2
+ import { describe, expect, test, vi } from "vitest";
3
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
4
+
5
+ // Local imports
6
+ import ToolPanel from "@ogw_front/components/ToolPanel";
7
+ import { vuetify } from "@ogw_tests/utils";
8
+
9
+ const FIRST_INDEX = 0;
10
+
11
+ describe("tool panel", () => {
12
+ test("renders title and slot content when open", async () => {
13
+ const wrapper = await mountSuspended(ToolPanel, {
14
+ global: {
15
+ plugins: [vuetify],
16
+ },
17
+ props: {
18
+ modelValue: true,
19
+ title: "Test Panel",
20
+ },
21
+ slots: {
22
+ default: () => "Panel Content",
23
+ },
24
+ });
25
+
26
+ expect(wrapper.text()).toContain("Test Panel");
27
+ expect(wrapper.text()).toContain("Panel Content");
28
+ });
29
+
30
+ test("triggers escape key binding to close panel", async () => {
31
+ const wrapper = await mountSuspended(ToolPanel, {
32
+ global: {
33
+ plugins: [vuetify],
34
+ },
35
+ props: {
36
+ modelValue: true,
37
+ title: "Test Panel",
38
+ },
39
+ });
40
+
41
+ globalThis.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
42
+ await wrapper.vm.$nextTick();
43
+
44
+ expect(wrapper.emitted()).toHaveProperty("update:modelValue");
45
+ expect(wrapper.emitted("update:modelValue")[FIRST_INDEX]).toStrictEqual([false]);
46
+ });
47
+
48
+ test("calls escapeFunction prop when Escape key is pressed", async () => {
49
+ const escapeFunction = vi.fn();
50
+ const wrapper = await mountSuspended(ToolPanel, {
51
+ global: {
52
+ plugins: [vuetify],
53
+ },
54
+ props: {
55
+ escapeFunction,
56
+ modelValue: true,
57
+ title: "Test Panel",
58
+ },
59
+ });
60
+
61
+ globalThis.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
62
+ await wrapper.vm.$nextTick();
63
+
64
+ expect(escapeFunction).toHaveBeenCalledWith();
65
+ });
66
+ });