@geode/opengeodeweb-front 10.16.1 → 10.17.0

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.
@@ -0,0 +1,64 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ version="1.0"
4
+ width="514px"
5
+ height="486px"
6
+ viewBox="0 0 514 486"
7
+ preserveAspectRatio="xMidYMid meet"
8
+ id="svg14"
9
+ sodipodi:docname="camera-bookmark.svg"
10
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
11
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ xmlns:svg="http://www.w3.org/2000/svg">
15
+ <defs
16
+ id="defs18" />
17
+ <sodipodi:namedview
18
+ id="namedview16"
19
+ pagecolor="#505050"
20
+ bordercolor="#eeeeee"
21
+ borderopacity="1"
22
+ inkscape:pageshadow="0"
23
+ inkscape:pageopacity="0"
24
+ inkscape:pagecheckerboard="0"
25
+ showgrid="false"
26
+ inkscape:zoom="1.1756014"
27
+ inkscape:cx="202.02426"
28
+ inkscape:cy="238.60128"
29
+ inkscape:window-width="1920"
30
+ inkscape:window-height="964"
31
+ inkscape:window-x="0"
32
+ inkscape:window-y="0"
33
+ inkscape:window-maximized="1"
34
+ inkscape:current-layer="g12">
35
+ <inkscape:grid
36
+ type="xygrid"
37
+ id="grid160" />
38
+ </sodipodi:namedview>
39
+ <g
40
+ fill="#000000"
41
+ id="g12"
42
+ transform="matrix(1.0257024,0,0,1.0257024,-37.052727,-7.1977612)">
43
+ <path
44
+ d="m 201.51433,384.71831 c -9.66032,-3.00746 -16.58659,-10.3894 -18.77383,-19.68518 -0.45568,-1.73156 -0.82021,-34.72246 -0.82021,-73.36372 l -0.0911,-70.174 25.33553,-0.45567 c 28.16074,-0.54681 28.88983,-0.72908 35.81608,-7.0174 2.18725,-1.91384 4.9213,-5.55924 6.10606,-8.29329 1.82269,-4.19222 2.0961,-6.19718 2.0961,-16.13091 v -11.39188 h 6.83513 6.83513 v -17.6802 c 0,-9.75145 0.36455,-18.77383 0.82021,-19.95858 0.54682,-1.27589 1.82271,-2.64292 3.00746,-3.00745 2.27838,-0.82022 90.49712,-0.54682 92.77549,0.18226 0.72908,0.27341 4.37449,3.82768 7.92875,7.92875 3.6454,4.10108 9.02237,10.02486 11.84756,13.12346 l 5.28584,5.65036 7.2908,0.18227 7.29081,0.18228 0.63794,-4.19222 c 0.2734,-2.27837 0.82022,-4.64788 1.18476,-5.10356 0.82021,-1.45816 27.06711,-1.36703 28.52527,0.0911 0.63795,0.63795 1.09362,2.82518 1.09362,5.01242 0,2.46065 0.45568,4.10108 1.18475,4.28335 0.54682,0.18227 3.18973,0.82021 5.65038,1.36703 7.47307,1.8227 15.67523,9.84259 17.86248,17.6802 1.54928,5.37697 2.00496,172.97434 0.5468,180.08288 -1.18475,6.10605 -5.1947,12.21209 -10.48053,16.22203 -7.92875,6.01492 -1.36703,5.74152 -127.68022,5.65038 -89.40349,0 -115.28584,-0.27341 -118.11103,-1.18475 z m 139.0721,-41.19305 c 12.7589,-4.00995 20.77879,-9.11351 31.44159,-20.04972 20.41426,-20.96106 25.24441,-52.58492 12.02983,-79.10523 -7.0174,-13.94367 -17.58907,-24.69759 -31.62386,-31.89727 -9.75146,-5.01242 -15.67523,-6.74399 -26.33804,-7.74648 -21.69014,-2.00497 -41.92213,5.83264 -57.23281,22.05468 -15.12843,16.03978 -21.59902,35.26928 -19.32064,57.14169 2.64291,24.60646 19.77631,46.93456 43.74483,57.05055 15.03728,6.37945 32.26181,7.2908 47.2991,2.55178 z m 7.56421,-176.80201 c 2.0961,-2.00498 1.8227,-5.10357 -1.00249,-11.30076 -1.5493,-3.6454 -3.09859,-5.65037 -4.28335,-5.92377 -3.82767,-0.91136 -43.83596,-1.09363 -45.84093,-0.18227 -1.27589,0.5468 -2.82519,3.00745 -4.28335,6.65286 -2.73405,6.74399 -2.91632,8.8401 -0.91135,10.75394 1.18476,1.27589 5.01243,1.45815 28.16073,1.45815 23.14831,0 26.97598,-0.18226 28.16074,-1.45815 z"
45
+ id="path2"
46
+ style="stroke-width:0.911351" />
47
+ <path
48
+ d="m 309.14483,317.55177 c -9.75145,-2.27838 -22.32809,-12.2121 -27.34052,-21.69015 -8.8401,-16.49544 -5.92377,-37.00083 7.29081,-50.67109 22.87489,-23.60398 61.97184,-14.39934 72.36123,16.95113 2.09611,6.56172 2.46065,18.86494 0.72908,24.87986 -4.00994,13.76139 -16.22203,26.06463 -29.80116,30.07457 -5.37697,1.5493 -17.4068,1.82271 -23.23944,0.45568 z"
49
+ id="path4"
50
+ style="stroke-width:0.911351" />
51
+ <path
52
+ d="m 129.80545,209.69281 c -15.94627,-0.85865 -18.39954,-1.96262 -20.73015,-9.81309 -0.85864,-2.94393 -1.10397,-22.69277 -0.85864,-56.91591 L 108.58465,90.341125 113.1232,86.170562 117.78442,82 h 44.52689 44.64955 l 14.22898,14.351642 14.35164,14.474308 v 44.52688 c 0,49.31078 -0.12266,50.41475 -7.72781,53.48133 -4.17057,1.7173 -68.93694,2.20795 -98.00822,0.85865 z m 93.22434,-11.40772 c 1.10398,-1.10397 1.47197,-12.14369 1.47197,-41.82829 v -40.47898 l -10.67174,-10.91707 -10.79439,-10.794389 -41.09231,-0.367992 -41.09231,-0.245327 -0.36799,52.377358 -0.24532,52.50002 49.31076,0.368 c 27.23132,0.12266 50.04675,0.36799 50.78273,0.61331 0.61332,0.12267 1.83995,-0.36799 2.6986,-1.22664 z"
53
+ id="path6"
54
+ style="stroke-width:1.22663" />
55
+ <path
56
+ d="m 163.78327,186.75471 c -8.21846,-4.29322 -11.65304,-9.69042 -11.65304,-18.39953 0,-14.35164 14.22897,-24.04207 27.72197,-18.8902 7.85048,3.0666 13.73833,13.98365 12.1437,22.5701 -2.45327,12.87968 -17.17291,20.48482 -28.21263,14.71963 z"
57
+ id="path8"
58
+ style="stroke-width:1.22663" />
59
+ <path
60
+ d="m 131.64541,130.20679 c -1.10397,-0.36799 -1.59463,-4.29323 -1.59463,-13.37032 v -12.75702 h 31.27921 31.27922 v 12.87968 c 0,9.4451 -0.36799,12.87967 -1.47196,13.00234 -9.32243,0.49065 -58.38787,0.73598 -59.49184,0.24532 z"
61
+ id="path10"
62
+ style="stroke-width:1.22663" />
63
+ </g>
64
+ </svg>
@@ -25,6 +25,15 @@ const emit = defineEmits(["click"]);
25
25
  icon
26
26
  @click="emit('click', $event)"
27
27
  >
28
- <v-icon :size="iconSize">{{ icon }}</v-icon>
28
+ <v-icon v-if="typeof icon === 'string' && icon.startsWith('mdi-')" :size="iconSize">{{
29
+ icon
30
+ }}</v-icon>
31
+ <v-img
32
+ v-else
33
+ :src="icon"
34
+ :height="iconSize"
35
+ :width="iconSize"
36
+ class="d-flex justify-center align-center"
37
+ />
29
38
  </v-btn>
30
39
  </template>
@@ -0,0 +1,114 @@
1
+ <script setup>
2
+ import { useCameraManagerStore } from "@ogw_front/stores/camera_manager";
3
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
4
+
5
+ const cameraManagerStore = useCameraManagerStore();
6
+ const hybridViewerStore = useHybridViewerStore();
7
+
8
+ const savedPositions = cameraManagerStore.refAllCameraPositions();
9
+
10
+ const editingId = ref(undefined);
11
+ const editingName = ref("");
12
+
13
+ async function restorePosition(positionId) {
14
+ const position = await cameraManagerStore.getCameraPosition(positionId);
15
+ if (position) {
16
+ if (hybridViewerStore.genericRenderWindow) {
17
+ hybridViewerStore.setCamera(position.camera_options);
18
+ } else {
19
+ await cameraManagerStore.restoreCameraPosition(positionId);
20
+ }
21
+ }
22
+ }
23
+
24
+ async function deletePosition(positionId) {
25
+ await cameraManagerStore.deleteCameraPosition(positionId);
26
+ }
27
+
28
+ function startEditing(position) {
29
+ editingId.value = position.id;
30
+ editingName.value = position.name;
31
+ }
32
+
33
+ async function saveRename() {
34
+ if (editingName.value) {
35
+ await cameraManagerStore.renameCameraPosition(editingId.value, editingName.value);
36
+ }
37
+ editingId.value = undefined;
38
+ }
39
+ </script>
40
+
41
+ <template>
42
+ <v-list v-if="savedPositions.length > 0" class="bg-transparent pa-2">
43
+ <v-list-item
44
+ v-for="position in savedPositions"
45
+ :key="position.id"
46
+ class="rounded-lg mb-1"
47
+ :active="editingId === position.id"
48
+ active-color="primary"
49
+ >
50
+ <template #prepend>
51
+ <v-btn
52
+ icon
53
+ variant="tonal"
54
+ color="success"
55
+ size="small"
56
+ class="mr-2"
57
+ @click="restorePosition(position.id)"
58
+ >
59
+ <v-icon size="20">mdi-play</v-icon>
60
+ <v-tooltip activator="parent" location="top">Restore</v-tooltip>
61
+ </v-btn>
62
+ </template>
63
+
64
+ <v-list-item-title class="font-weight-bold">
65
+ <v-text-field
66
+ v-if="editingId === position.id"
67
+ v-model="editingName"
68
+ density="compact"
69
+ variant="underlined"
70
+ hide-details
71
+ autofocus
72
+ @keyup.enter="saveRename"
73
+ @blur="saveRename"
74
+ ></v-text-field>
75
+ <span v-else>{{ position.name }}</span>
76
+ </v-list-item-title>
77
+
78
+ <template #append>
79
+ <div class="d-flex g-1">
80
+ <v-btn
81
+ icon
82
+ variant="text"
83
+ size="small"
84
+ color="grey-darken-1"
85
+ @click="startEditing(position)"
86
+ >
87
+ <v-icon size="18">mdi-pencil</v-icon>
88
+ <v-tooltip activator="parent" location="top">Rename</v-tooltip>
89
+ </v-btn>
90
+ <v-btn
91
+ icon
92
+ variant="text"
93
+ size="small"
94
+ color="error"
95
+ @click="deletePosition(position.id)"
96
+ >
97
+ <v-icon size="18">mdi-delete</v-icon>
98
+ <v-tooltip activator="parent" location="top">Delete</v-tooltip>
99
+ </v-btn>
100
+ </div>
101
+ </template>
102
+ </v-list-item>
103
+ </v-list>
104
+ <div v-else class="text-center text-grey-lighten-1 py-8 italic">
105
+ <v-icon size="48" class="mb-2 d-block mx-auto opacity-20">mdi-camera-off</v-icon>
106
+ No saved positions yet.
107
+ </div>
108
+ </template>
109
+
110
+ <style scoped>
111
+ :deep(.v-list-item__prepend) {
112
+ margin-inline-end: 12px !important;
113
+ }
114
+ </style>
@@ -0,0 +1,50 @@
1
+ <script setup>
2
+ import { useCameraManagerStore } from "@ogw_front/stores/camera_manager";
3
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
4
+
5
+ const cameraManagerStore = useCameraManagerStore();
6
+ const hybridViewerStore = useHybridViewerStore();
7
+
8
+ const newPositionName = ref("");
9
+
10
+ async function saveCurrentPosition() {
11
+ if (!newPositionName.value) {
12
+ return;
13
+ }
14
+ await cameraManagerStore.saveCameraPosition(
15
+ newPositionName.value,
16
+ toRaw(hybridViewerStore.camera_options),
17
+ );
18
+ newPositionName.value = "";
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <v-container class="pa-5 pb-2 bg-surface-variant-lighten-5">
24
+ <v-row dense>
25
+ <v-col cols="12">
26
+ <v-text-field
27
+ v-model="newPositionName"
28
+ label="Position Name"
29
+ placeholder="e.g. Front View"
30
+ density="compact"
31
+ variant="outlined"
32
+ hide-details
33
+ class="mb-3"
34
+ ></v-text-field>
35
+ </v-col>
36
+ <v-col cols="12" class="d-flex align-center">
37
+ <v-btn
38
+ color="primary"
39
+ variant="elevated"
40
+ block
41
+ :disabled="!newPositionName"
42
+ @click="saveCurrentPosition"
43
+ height="40"
44
+ >
45
+ Save
46
+ </v-btn>
47
+ </v-col>
48
+ </v-row>
49
+ </v-container>
50
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup>
2
+ import GlassCard from "@ogw_front/components/GlassCard";
3
+ import List from "@ogw_front/components/CameraManager/List";
4
+ import Saver from "@ogw_front/components/CameraManager/Saver";
5
+
6
+ const emit = defineEmits(["close"]);
7
+
8
+ const { show_dialog, width } = defineProps({
9
+ show_dialog: { type: Boolean, required: true },
10
+ width: { type: Number, required: false, default: 450 },
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <GlassCard
16
+ v-if="show_dialog"
17
+ @click.stop
18
+ title="Camera Positions"
19
+ :width="width"
20
+ :ripple="false"
21
+ variant="panel"
22
+ padding="pa-0"
23
+ class="position-absolute elevation-24"
24
+ style="z-index: 2; top: 90px; right: 55px"
25
+ >
26
+ <v-card-text class="pa-0">
27
+ <Saver />
28
+ <v-divider></v-divider>
29
+ <List />
30
+ </v-card-text>
31
+
32
+ <v-divider></v-divider>
33
+
34
+ <v-card-actions class="pa-4">
35
+ <v-spacer></v-spacer>
36
+ <v-btn variant="text" color="grey-darken-1" @click="emit('close')">Close</v-btn>
37
+ </v-card-actions>
38
+ </GlassCard>
39
+ </template>
@@ -30,7 +30,7 @@ const emailRules = [
30
30
  return "E-mail is required.";
31
31
  },
32
32
  (value) => {
33
- if (/.+@.+\..+/.test(value)) {
33
+ if (/.+@.+\..+/u.test(value)) {
34
34
  return true;
35
35
  }
36
36
  return "E-mail must be valid.";
@@ -1,17 +1,18 @@
1
1
  <script setup>
2
- import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
3
-
4
2
  import ActionButton from "@ogw_front/components/ActionButton.vue";
3
+ import CameraBookmarkIcon from "@ogw_front/assets/viewer_svgs/camera-bookmark.svg";
4
+ import CameraManager from "@ogw_front/components/CameraManager";
5
5
  import CameraOrientation from "@ogw_front/components/CameraOrientation.vue";
6
6
  import Screenshot from "@ogw_front/components/Screenshot";
7
7
  import ZScaling from "@ogw_front/components/ZScaling";
8
-
8
+ import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
9
9
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer";
11
11
 
12
12
  const hybridViewerStore = useHybridViewerStore();
13
13
  const viewerStore = useViewerStore();
14
14
  const take_screenshot = ref(false);
15
+ const show_camera_manager = ref(false);
15
16
  const showCameraOrientation = ref(false);
16
17
  const showZScaling = ref(false);
17
18
  const grid_scale = ref(false);
@@ -51,6 +52,14 @@ const camera_options = [
51
52
  showCameraOrientation.value = !showCameraOrientation.value;
52
53
  },
53
54
  },
55
+ {
56
+ tooltip: "Manage camera positions",
57
+ icon: CameraBookmarkIcon,
58
+ iconSize: 34,
59
+ action: () => {
60
+ show_camera_manager.value = !show_camera_manager.value;
61
+ },
62
+ },
54
63
  {
55
64
  tooltip: "Take a screenshot",
56
65
  icon: "mdi-camera",
@@ -91,6 +100,7 @@ const camera_options = [
91
100
  <ActionButton
92
101
  :icon="camera_option.icon"
93
102
  :tooltip="camera_option.tooltip"
103
+ :icon-size="camera_option.iconSize"
94
104
  tooltip-location="left"
95
105
  @click.stop="camera_option.action"
96
106
  />
@@ -103,6 +113,7 @@ const camera_options = [
103
113
  @select="hybridViewerStore.setCameraOrientation"
104
114
  />
105
115
  <Screenshot v-model="take_screenshot" />
116
+ <CameraManager :show_dialog="show_camera_manager" @close="show_camera_manager = false" />
106
117
  <ZScaling
107
118
  v-model:show="showZScaling"
108
119
  v-model="zScale"
@@ -0,0 +1,56 @@
1
+ // Third party imports
2
+ import { liveQuery } from "dexie";
3
+ import { useObservable } from "@vueuse/rxjs";
4
+ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
5
+
6
+ // Local imports
7
+ import { database } from "@ogw_internal/database/database.js";
8
+ import { useViewerStore } from "@ogw_front/stores/viewer";
9
+
10
+ export const useCameraManagerStore = defineStore("camera_manager", () => {
11
+ const viewerStore = useViewerStore();
12
+
13
+ function refAllCameraPositions() {
14
+ return useObservable(
15
+ liveQuery(() => database.camera_positions.toArray()),
16
+ { initialValue: [] },
17
+ );
18
+ }
19
+
20
+ async function getCameraPosition(id) {
21
+ return await database.camera_positions.get(id);
22
+ }
23
+
24
+ async function saveCameraPosition(name, camera_options) {
25
+ await database.camera_positions.put({
26
+ name,
27
+ camera_options,
28
+ });
29
+ }
30
+
31
+ async function restoreCameraPosition(id) {
32
+ const position = await database.camera_positions.get(id);
33
+ if (position) {
34
+ await viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.update_camera, {
35
+ camera_options: position.camera_options,
36
+ });
37
+ }
38
+ }
39
+
40
+ async function deleteCameraPosition(id) {
41
+ await database.camera_positions.delete(id);
42
+ }
43
+
44
+ async function renameCameraPosition(id, newName) {
45
+ await database.camera_positions.update(id, { name: newName });
46
+ }
47
+
48
+ return {
49
+ refAllCameraPositions,
50
+ getCameraPosition,
51
+ saveCameraPosition,
52
+ restoreCameraPosition,
53
+ deleteCameraPosition,
54
+ renameCameraPosition,
55
+ };
56
+ });
@@ -1,17 +1,13 @@
1
1
  import {
2
2
  ACTOR_COLOR,
3
- ALIGNMENT_THRESHOLD,
4
3
  BACKGROUND_COLOR,
5
- BUMP_MULTIPLIER,
6
- EASE_EXPONENT,
7
- LONG_ANIMATION_DURATION,
8
- SHORT_ANIMATION_DURATION,
9
4
  WHEEL_TIME_OUT_MS,
10
5
  applySnapshot,
11
6
  computeAverageBrightness,
12
7
  getCameraOptions,
13
8
  performCameraOrientation,
14
9
  performClickPicking,
10
+ performSetCamera,
15
11
  } from "@ogw_internal/stores/hybrid_viewer";
16
12
  import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor";
17
13
  import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
@@ -26,16 +22,17 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
26
22
 
27
23
  export const useHybridViewerStore = defineStore("hybridViewer", () => {
28
24
  const dataStore = useDataStore();
29
- const viewerStore = useViewerStore();
30
25
  const hybridDb = reactive({});
31
- const status = ref(Status.NOT_CREATED);
26
+ const viewerStore = useViewerStore();
32
27
  const camera_options = reactive({});
33
28
  const genericRenderWindow = reactive({});
29
+ const status = ref(Status.NOT_CREATED);
34
30
  const is_moving = ref(false);
35
31
  const is_picking = ref(false);
36
32
  const zScale = ref(1);
37
- let viewStream = undefined;
38
33
  let imageStyle = undefined;
34
+ let viewStream = undefined;
35
+ let wheelEventEndTimeout = undefined;
39
36
  const gridActor = undefined;
40
37
 
41
38
  watch(is_picking, (value) => {
@@ -63,12 +60,9 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
63
60
  background: BACKGROUND_COLOR,
64
61
  listenWindowResize: false,
65
62
  });
66
-
67
63
  const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow();
68
64
  imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style;
69
- imageStyle.transition = "opacity 0.1s ease-in";
70
- imageStyle.zIndex = 1;
71
-
65
+ Object.assign(imageStyle, { transition: "opacity 0.1s ease-in", zIndex: 1 });
72
66
  await viewerStore.ws_connect();
73
67
  viewStream = viewerStore.client.getImageStream().createViewStream("-1");
74
68
  viewStream.onImageReady((event) => {
@@ -79,13 +73,11 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
79
73
  webGLRenderWindow.setBackgroundImage(event.image);
80
74
  imageStyle.opacity = 1;
81
75
  });
82
-
83
- const renderer = genericRenderWindow.value.getRenderer();
84
- const camera = renderer.getActiveCamera();
76
+ const camera = genericRenderWindow.value.getRenderer().getActiveCamera();
77
+ Object.assign(camera_options, getCameraOptions(camera));
85
78
  camera.onModified(() => {
86
79
  Object.assign(camera_options, getCameraOptions(camera));
87
80
  });
88
-
89
81
  status.value = Status.CREATED;
90
82
  }
91
83
 
@@ -93,14 +85,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
93
85
  if (!genericRenderWindow.value) {
94
86
  return;
95
87
  }
96
- const value = await dataStore.item(id);
97
88
  const reader = vtkXMLPolyDataReader();
98
- const textEncoder = new TextEncoder();
99
- await reader.parseAsArrayBuffer(textEncoder.encode(value.binary_light_viewable));
100
- const polydata = reader.getOutputData(0);
89
+ const value = await dataStore.item(id);
90
+ await reader.parseAsArrayBuffer(new TextEncoder().encode(value.binary_light_viewable));
91
+ const actor = vtkActor();
101
92
  const mapper = vtkMapper();
93
+ const polydata = reader.getOutputData(0);
102
94
  mapper.setInputData(polydata);
103
- const actor = vtkActor();
104
95
  actor.getProperty().setColor(ACTOR_COLOR);
105
96
  actor.setMapper(mapper);
106
97
  const renderer = genericRenderWindow.value.getRenderer();
@@ -116,8 +107,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
116
107
  if (!hybridDb[id]) {
117
108
  return;
118
109
  }
119
- const renderer = genericRenderWindow.value.getRenderer();
120
- renderer.removeActor(hybridDb[id].actor);
110
+ genericRenderWindow.value.getRenderer().removeActor(hybridDb[id].actor);
121
111
  genericRenderWindow.value.getRenderWindow().render();
122
112
  delete hybridDb[id];
123
113
  }
@@ -127,14 +117,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
127
117
  return;
128
118
  }
129
119
  hybridDb[id].actor.setVisibility(visibility);
130
- const renderWindow = genericRenderWindow.value.getRenderWindow();
131
- renderWindow.render();
120
+ genericRenderWindow.value.getRenderWindow().render();
132
121
  }
122
+
133
123
  async function setZScaling(z_scale) {
134
124
  zScale.value = z_scale;
135
125
  const renderer = genericRenderWindow.value.getRenderer();
136
- const actors = renderer.getActors();
137
- for (const actor of actors) {
126
+ for (const actor of renderer.getActors()) {
138
127
  if (actor !== gridActor) {
139
128
  const scale = actor.getScale();
140
129
  actor.setScale(scale[0], scale[1], z_scale);
@@ -143,48 +132,49 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
143
132
  renderer.resetCamera();
144
133
  genericRenderWindow.value.getRenderWindow().render();
145
134
  const schema = viewer_schemas?.opengeodeweb_viewer?.viewer?.set_z_scaling;
146
- if (!schema) {
147
- return;
135
+ if (schema) {
136
+ await viewerStore.request(schema, { z_scale });
148
137
  }
149
- await viewerStore.request(schema, {
150
- z_scale,
151
- });
152
138
  remoteRender();
153
139
  }
154
140
 
155
141
  function resetCamera() {
156
- const renderer = genericRenderWindow.value.getRenderer();
157
- renderer.resetCamera();
142
+ genericRenderWindow.value.getRenderer().resetCamera();
158
143
  genericRenderWindow.value.getRenderWindow().render();
159
144
  syncRemoteCamera();
160
145
  }
161
146
 
147
+ function setCamera(new_camera_options) {
148
+ performSetCamera(new_camera_options, {
149
+ genericRenderWindow: genericRenderWindow.value,
150
+ is_moving,
151
+ imageStyle,
152
+ syncRemoteCamera,
153
+ });
154
+ }
155
+
162
156
  function setCameraOrientation(orientation) {
163
157
  performCameraOrientation(orientation, {
164
158
  genericRenderWindow: genericRenderWindow.value,
165
159
  is_moving,
166
160
  imageStyle,
167
161
  syncRemoteCamera,
168
- constants: {
169
- ALIGNMENT_THRESHOLD,
170
- BUMP_MULTIPLIER,
171
- EASE_EXPONENT,
172
- LONG_ANIMATION_DURATION,
173
- SHORT_ANIMATION_DURATION,
174
- },
175
162
  });
176
163
  }
177
164
 
178
165
  function syncRemoteCamera() {
179
- const renderer = genericRenderWindow.value.getRenderer();
180
- const camera = renderer.getActiveCamera();
181
- const params = { camera_options: getCameraOptions(camera) };
182
- viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.update_camera, params, {
183
- response_function: () => {
184
- remoteRender();
185
- Object.assign(camera_options, params.camera_options);
166
+ const camera = genericRenderWindow.value.getRenderer().getActiveCamera();
167
+ const options = getCameraOptions(camera);
168
+ viewerStore.request(
169
+ viewer_schemas.opengeodeweb_viewer.viewer.update_camera,
170
+ { camera_options: options },
171
+ {
172
+ response_function: () => {
173
+ remoteRender();
174
+ Object.assign(camera_options, options);
175
+ },
186
176
  },
187
- });
177
+ );
188
178
  }
189
179
 
190
180
  function remoteRender() {
@@ -199,10 +189,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
199
189
  const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow();
200
190
  webGLRenderWindow.setUseBackgroundImage(true);
201
191
  imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style;
202
- imageStyle.transition = "opacity 0.1s ease-in";
203
- imageStyle.zIndex = 1;
192
+ Object.assign(imageStyle, { transition: "opacity 0.1s ease-in", zIndex: 1 });
204
193
  resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight);
205
-
206
194
  useMousePressed({
207
195
  target: container,
208
196
  onPressed: (event) => {
@@ -225,18 +213,17 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
225
213
  imageStyle.opacity = 0;
226
214
  },
227
215
  onReleased: () => {
228
- if (!is_moving.value) {
229
- return;
216
+ if (is_moving.value) {
217
+ is_moving.value = false;
218
+ syncRemoteCamera();
230
219
  }
231
- is_moving.value = false;
232
- syncRemoteCamera();
233
220
  },
234
221
  });
235
-
236
- let wheelEventEndTimeout = undefined;
237
222
  useEventListener(container, "wheel", () => {
238
223
  is_moving.value = true;
239
- imageStyle.opacity = 0;
224
+ if (imageStyle) {
225
+ imageStyle.opacity = 0;
226
+ }
240
227
  clearTimeout(wheelEventEndTimeout);
241
228
  wheelEventEndTimeout = setTimeout(() => {
242
229
  is_moving.value = false;
@@ -256,8 +243,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
256
243
  await nextTick();
257
244
  webGLRenderWindow.setSize(width, height);
258
245
  viewStream.setSize(width, height);
259
- const renderWindow = genericRenderWindow.value.getRenderWindow();
260
- renderWindow.render();
246
+ genericRenderWindow.value.getRenderWindow().render();
261
247
  remoteRender();
262
248
  }
263
249
 
@@ -271,10 +257,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
271
257
  }
272
258
 
273
259
  function exportStores() {
274
- const renderer = genericRenderWindow.value.getRenderer();
275
- const camera = renderer.getActiveCamera();
276
- const cameraSnapshot = getCameraOptions(camera) || camera_options;
277
- return { zScale: zScale.value, camera_options: cameraSnapshot };
260
+ const camera = genericRenderWindow.value.getRenderer().getActiveCamera();
261
+ return { zScale: zScale.value, camera_options: getCameraOptions(camera) || camera_options };
278
262
  }
279
263
 
280
264
  async function importStores(snapshot) {
@@ -282,13 +266,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
282
266
  genericRenderWindow: genericRenderWindow.value,
283
267
  setZScaling,
284
268
  syncRemoteCamera,
269
+ setCamera,
285
270
  });
286
271
  }
287
272
 
288
273
  function clear() {
289
274
  const renderer = genericRenderWindow.value.getRenderer();
290
- const actors = renderer.getActors();
291
- for (const actor of actors) {
275
+ for (const actor of renderer.getActors()) {
292
276
  renderer.removeActor(actor);
293
277
  }
294
278
  genericRenderWindow.value.getRenderWindow().render();
@@ -305,6 +289,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
305
289
  setVisibility,
306
290
  setZScaling,
307
291
  syncRemoteCamera,
292
+ setCamera,
308
293
  initHybridViewer,
309
294
  remoteRender,
310
295
  resize,
@@ -49,7 +49,7 @@ async function runScript(
49
49
  child.on("kill", () => {
50
50
  console.log(`[${execName}] process killed`);
51
51
  });
52
- child.name = command.replace(/^.*[\\/]/, "");
52
+ child.name = command.replace(/^.*[\\/]/u, "");
53
53
 
54
54
  try {
55
55
  return await pTimeout(waitForReady(child, expectedResponse), {
@@ -27,7 +27,7 @@ async function waitNuxt(nuxtProcess) {
27
27
  for await (const [data] of on(nuxtProcess.stdout, "data")) {
28
28
  const output = data.toString();
29
29
  console.log("Nuxt:", output);
30
- const portMatch = output.match(/Listening on http:\/\/\[::\]:(\d+)/);
30
+ const portMatch = output.match(/Listening on http:\/\/\[::\]:(\d+)/u);
31
31
  if (portMatch) {
32
32
  const [, nuxtPort] = portMatch;
33
33
 
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  // Third party imports
6
6
  import JSZip from "jszip";
7
7
 
8
- async function unzipFile(zipFilePath, outputDir = zipFilePath.replace(/\.[^/.]+$/, "")) {
8
+ async function unzipFile(zipFilePath, outputDir = zipFilePath.replace(/\.[^/.]+$/u, "")) {
9
9
  console.log("Unzipping file...", zipFilePath, outputDir);
10
10
  try {
11
11
  const data = await fs.promises.readFile(zipFilePath);
@@ -1,4 +1,5 @@
1
1
  import { Dexie } from "dexie";
2
+ import { cameraPositionsTable } from "./tables/camera_positions";
2
3
  import { dataStyleTable } from "./tables/data_style";
3
4
  import { dataTable } from "./tables/data";
4
5
  import { modelComponentDataStyleTable } from "./tables/model_component_datastyle";
@@ -15,6 +16,7 @@ export class BaseDatabase extends Dexie {
15
16
  [modelComponentDataStyleTable.name]: modelComponentDataStyleTable.schema,
16
17
  [modelComponentTypeDataStyleTable.name]: modelComponentTypeDataStyleTable.schema,
17
18
  [modelComponentsRelationTable.name]: modelComponentsRelationTable.schema,
19
+ [cameraPositionsTable.name]: cameraPositionsTable.schema,
18
20
  treeview_config: "id",
19
21
  };
20
22
  }
@@ -6,7 +6,7 @@ class Database extends BaseDatabase {
6
6
  constructor() {
7
7
  super("Database");
8
8
 
9
- this.version(1).stores(BaseDatabase.initialStores);
9
+ this.version(3).stores(BaseDatabase.initialStores);
10
10
  }
11
11
 
12
12
  static async addTable(tableName, schemaDefinition) {
@@ -0,0 +1,4 @@
1
+ export const cameraPositionsTable = {
2
+ name: "camera_positions",
3
+ schema: "++id, name",
4
+ };
@@ -1,3 +1,9 @@
1
+ import {
2
+ LONG_ANIMATION_DURATION,
3
+ SHORT_ANIMATION_DURATION,
4
+ animateCamera,
5
+ computeAnimationDuration,
6
+ } from "@ogw_internal/stores/hybrid_viewer_camera_animation";
1
7
  import { dot } from "@kitware/vtk.js/Common/Core/Math";
2
8
 
3
9
  const RGB_MAX = 255;
@@ -17,8 +23,6 @@ const ACTOR_COLOR = [
17
23
  const WHEEL_TIME_OUT_MS = 600;
18
24
  const BUMP_MULTIPLIER = 0.2;
19
25
  const ALIGNMENT_THRESHOLD = 0.9;
20
- const LONG_ANIMATION_DURATION = 1000;
21
- const SHORT_ANIMATION_DURATION = 500;
22
26
  const EASE_EXPONENT = 1.1;
23
27
 
24
28
  const SAMPLE_SIZE = 10;
@@ -76,17 +80,13 @@ function computeAverageBrightness(rect, options) {
76
80
  if (!latestImage || !offscreenCtx || !offscreenCanvas || !genericRenderWindow) {
77
81
  return BACKGROUND_GREY_VALUE / RGB_MAX;
78
82
  }
79
-
80
83
  const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
81
84
  if (!canvas) {
82
85
  return BACKGROUND_GREY_VALUE / RGB_MAX;
83
86
  }
84
-
85
87
  const { relX, relY, relW, relH } = mapRect(rect, latestImage, canvas.getBoundingClientRect());
86
-
87
88
  offscreenCanvas.width = SAMPLE_SIZE;
88
89
  offscreenCanvas.height = SAMPLE_SIZE;
89
-
90
90
  try {
91
91
  offscreenCtx.drawImage(
92
92
  latestImage,
@@ -100,7 +100,6 @@ function computeAverageBrightness(rect, options) {
100
100
  SAMPLE_SIZE,
101
101
  );
102
102
  const { data } = offscreenCtx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE);
103
-
104
103
  let minBrightness = 1;
105
104
  for (let i = 0; i < TOTAL_CHANNELS; i += RGBA_CHANNELS) {
106
105
  const brightness = (data[i] + data[i + 1] + data[i + 2]) / (3 * RGB_MAX);
@@ -108,7 +107,6 @@ function computeAverageBrightness(rect, options) {
108
107
  minBrightness = brightness;
109
108
  }
110
109
  }
111
-
112
110
  return minBrightness;
113
111
  } catch {
114
112
  return BACKGROUND_GREY_VALUE / RGB_MAX;
@@ -150,54 +148,8 @@ function performClickPicking(event, options) {
150
148
  },
151
149
  );
152
150
  }
153
-
154
- function animateCamera(options) {
155
- const {
156
- camera,
157
- startState,
158
- targetState,
159
- duration,
160
- bumpMultiplier,
161
- easeExponent,
162
- onUpdate,
163
- onEnd,
164
- } = options;
165
- const startTime = performance.now();
166
-
167
- function animate(currentTime) {
168
- const progress = Math.min((currentTime - startTime) / duration, 1);
169
- const ease =
170
- duration > SHORT_ANIMATION_DURATION
171
- ? 1 - (1 - progress) ** easeExponent
172
- : progress * (2 - progress);
173
- const bump = bumpMultiplier * Math.sin(Math.PI * progress);
174
-
175
- camera.set({
176
- position: startState.position.map(
177
- (startValue, index) =>
178
- startValue + (targetState.position[index] - startValue) * ease + bump,
179
- ),
180
- viewUp: startState.view_up.map(
181
- (startValue, index) => startValue + (targetState.view_up[index] - startValue) * ease,
182
- ),
183
- focalPoint: startState.focal_point.map(
184
- (startValue, index) => startValue + (targetState.focal_point[index] - startValue) * ease,
185
- ),
186
- });
187
-
188
- onUpdate();
189
-
190
- if (progress < 1) {
191
- requestAnimationFrame(animate);
192
- } else {
193
- onEnd();
194
- }
195
- }
196
- requestAnimationFrame(animate);
197
- }
198
-
199
151
  async function applySnapshot(snapshot, options) {
200
- const { genericRenderWindow, setZScaling, syncRemoteCamera } = options;
152
+ const { genericRenderWindow, setZScaling, syncRemoteCamera, setCamera } = options;
201
153
  if (!snapshot) {
202
154
  return;
203
155
  }
@@ -207,17 +159,47 @@ async function applySnapshot(snapshot, options) {
207
159
  }
208
160
  const { camera_options: snapshot_camera_options } = snapshot;
209
161
  if (snapshot_camera_options) {
210
- applyCameraOptions(
211
- genericRenderWindow.getRenderer().getActiveCamera(),
212
- snapshot_camera_options,
213
- );
214
- genericRenderWindow.getRenderWindow().render();
215
- syncRemoteCamera();
162
+ if (setCamera) {
163
+ setCamera(snapshot_camera_options);
164
+ } else {
165
+ applyCameraOptions(
166
+ genericRenderWindow.getRenderer().getActiveCamera(),
167
+ snapshot_camera_options,
168
+ );
169
+ genericRenderWindow.getRenderWindow().render();
170
+ syncRemoteCamera();
171
+ }
216
172
  }
217
173
  }
218
174
 
175
+ function performSetCamera(targetCameraOptions, options) {
176
+ const { genericRenderWindow, is_moving, imageStyle, syncRemoteCamera } = options;
177
+ const camera = genericRenderWindow.getRenderer().getActiveCamera();
178
+ const startState = getCameraOptions(camera);
179
+ const duration = computeAnimationDuration(startState, targetCameraOptions);
180
+ is_moving.value = true;
181
+ if (imageStyle) {
182
+ imageStyle.opacity = 0;
183
+ }
184
+ animateCamera({
185
+ camera,
186
+ startState,
187
+ targetState: targetCameraOptions,
188
+ duration,
189
+ bumpMultiplier: 0,
190
+ easeExponent: EASE_EXPONENT,
191
+ onUpdate: () => genericRenderWindow.getRenderWindow().render(),
192
+ onEnd: () => {
193
+ applyCameraOptions(camera, targetCameraOptions);
194
+ genericRenderWindow.getRenderWindow().render();
195
+ is_moving.value = false;
196
+ syncRemoteCamera();
197
+ },
198
+ });
199
+ }
200
+
219
201
  function performCameraOrientation(orientation, options) {
220
- const { genericRenderWindow, is_moving, imageStyle, syncRemoteCamera, constants } = options;
202
+ const { genericRenderWindow, is_moving, imageStyle, syncRemoteCamera } = options;
221
203
  const config = ORIENTATIONS[orientation.toLowerCase()];
222
204
  const renderer = genericRenderWindow.getRenderer();
223
205
  const camera = renderer.getActiveCamera();
@@ -234,9 +216,7 @@ function performCameraOrientation(orientation, options) {
234
216
 
235
217
  const alignment = dot(camera.getDirectionOfProjection(), config.position);
236
218
  const duration =
237
- alignment > constants.ALIGNMENT_THRESHOLD
238
- ? constants.LONG_ANIMATION_DURATION
239
- : constants.SHORT_ANIMATION_DURATION;
219
+ alignment > ALIGNMENT_THRESHOLD ? LONG_ANIMATION_DURATION : SHORT_ANIMATION_DURATION;
240
220
  is_moving.value = true;
241
221
  imageStyle.opacity = 0;
242
222
 
@@ -245,8 +225,8 @@ function performCameraOrientation(orientation, options) {
245
225
  startState,
246
226
  targetState,
247
227
  duration,
248
- bumpMultiplier: constants.BUMP_MULTIPLIER,
249
- easeExponent: constants.EASE_EXPONENT,
228
+ bumpMultiplier: BUMP_MULTIPLIER,
229
+ easeExponent: EASE_EXPONENT,
250
230
  onUpdate: () => genericRenderWindow.getRenderWindow().render(),
251
231
  onEnd: () => {
252
232
  is_moving.value = false;
@@ -261,11 +241,8 @@ export {
261
241
  WHEEL_TIME_OUT_MS,
262
242
  BUMP_MULTIPLIER,
263
243
  ALIGNMENT_THRESHOLD,
264
- LONG_ANIMATION_DURATION,
265
- SHORT_ANIMATION_DURATION,
266
244
  EASE_EXPONENT,
267
245
  ORIENTATIONS,
268
- animateCamera,
269
246
  applyCameraOptions,
270
247
  applySnapshot,
271
248
  centerCameraOnPosition,
@@ -273,4 +250,5 @@ export {
273
250
  getCameraOptions,
274
251
  performCameraOrientation,
275
252
  performClickPicking,
253
+ performSetCamera,
276
254
  };
@@ -0,0 +1,108 @@
1
+ import { dot } from "@kitware/vtk.js/Common/Core/Math";
2
+
3
+ const NEAR_ZERO_THRESHOLD = 1e-10;
4
+ const SLERP_LINEAR_THRESHOLD = 0.9995;
5
+ const LONG_ANIMATION_DURATION = 1000;
6
+ const SHORT_ANIMATION_DURATION = 500;
7
+
8
+ function vecSub(vector, other) {
9
+ return [vector[0] - other[0], vector[1] - other[1], vector[2] - other[2]];
10
+ }
11
+
12
+ function vecLength(vector) {
13
+ return Math.hypot(vector[0], vector[1], vector[2]);
14
+ }
15
+
16
+ function vecNormalize(vector) {
17
+ const len = vecLength(vector);
18
+ if (len < NEAR_ZERO_THRESHOLD) {
19
+ return [0, 0, 1];
20
+ }
21
+ return [vector[0] / len, vector[1] / len, vector[2] / len];
22
+ }
23
+
24
+ function slerp(from, target, ratio) {
25
+ const normFrom = vecNormalize(from);
26
+ const normTarget = vecNormalize(target);
27
+ let dotProduct =
28
+ normFrom[0] * normTarget[0] + normFrom[1] * normTarget[1] + normFrom[2] * normTarget[2];
29
+ dotProduct = Math.max(-1, Math.min(1, dotProduct));
30
+ if (dotProduct > SLERP_LINEAR_THRESHOLD) {
31
+ return vecNormalize([
32
+ normFrom[0] + (normTarget[0] - normFrom[0]) * ratio,
33
+ normFrom[1] + (normTarget[1] - normFrom[1]) * ratio,
34
+ normFrom[2] + (normTarget[2] - normFrom[2]) * ratio,
35
+ ]);
36
+ }
37
+ const theta = Math.acos(dotProduct);
38
+ const sinTheta = Math.sin(theta);
39
+ const weightFrom = Math.sin((1 - ratio) * theta) / sinTheta;
40
+ const weightTarget = Math.sin(ratio * theta) / sinTheta;
41
+ return [
42
+ normFrom[0] * weightFrom + normTarget[0] * weightTarget,
43
+ normFrom[1] * weightFrom + normTarget[1] * weightTarget,
44
+ normFrom[2] * weightFrom + normTarget[2] * weightTarget,
45
+ ];
46
+ }
47
+
48
+ function computeAnimationDuration(startState, targetState) {
49
+ const startDir = vecNormalize(vecSub(startState.position, startState.focal_point));
50
+ const targetDir = vecNormalize(vecSub(targetState.position, targetState.focal_point));
51
+ const dotProduct = Math.max(-1, Math.min(1, dot(startDir, targetDir)));
52
+ const angle = Math.acos(dotProduct);
53
+ const angleRatio = angle / Math.PI;
54
+ return (
55
+ SHORT_ANIMATION_DURATION + (LONG_ANIMATION_DURATION - SHORT_ANIMATION_DURATION) * angleRatio
56
+ );
57
+ }
58
+
59
+ function animateCamera(options) {
60
+ const {
61
+ camera,
62
+ startState,
63
+ targetState,
64
+ duration,
65
+ bumpMultiplier,
66
+ easeExponent,
67
+ onUpdate,
68
+ onEnd,
69
+ } = options;
70
+ const startDir = vecSub(startState.position, startState.focal_point);
71
+ const targetDir = vecSub(targetState.position, targetState.focal_point);
72
+ const startDist = vecLength(startDir);
73
+ const targetDist = vecLength(targetDir);
74
+ const startTime = performance.now();
75
+ function animate(currentTime) {
76
+ const progress = Math.min((currentTime - startTime) / duration, 1);
77
+ const ease =
78
+ duration > SHORT_ANIMATION_DURATION
79
+ ? 1 - (1 - progress) ** easeExponent
80
+ : progress * (2 - progress);
81
+ const bump = bumpMultiplier * Math.sin(Math.PI * progress);
82
+ const dir = slerp(startDir, targetDir, ease);
83
+ const dist = startDist + (targetDist - startDist) * ease + bump;
84
+ const focalPoint = startState.focal_point.map(
85
+ (startValue, index) => startValue + (targetState.focal_point[index] - startValue) * ease,
86
+ );
87
+ const viewUp = slerp(startState.view_up, targetState.view_up, ease);
88
+ camera.set({
89
+ position: focalPoint.map((focalCoord, index) => focalCoord + dir[index] * dist),
90
+ viewUp,
91
+ focalPoint,
92
+ });
93
+ onUpdate();
94
+ if (progress < 1) {
95
+ requestAnimationFrame(animate);
96
+ } else {
97
+ onEnd();
98
+ }
99
+ }
100
+ requestAnimationFrame(animate);
101
+ }
102
+
103
+ export {
104
+ LONG_ANIMATION_DURATION,
105
+ SHORT_ANIMATION_DURATION,
106
+ animateCamera,
107
+ computeAnimationDuration,
108
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.16.1",
3
+ "version": "10.17.0",
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": {
@@ -36,7 +36,7 @@ export default defineEventHandler(async (event) => {
36
36
  console.log("Service URL created:", response.uri);
37
37
  return {
38
38
  statusCode: 200,
39
- url: response.uri.replace(/^https?:\/\//i, ""),
39
+ url: response.uri.replace(/^https?:\/\//iu, ""),
40
40
  };
41
41
  } catch (error) {
42
42
  console.log(error);