@geode/opengeodeweb-front 10.14.2-rc.2 → 10.15.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,273 @@
1
+ <script setup>
2
+ import ToolPanel from "@ogw_front/components/ToolPanel";
3
+ import { applyCameraOptions } from "@ogw_front/utils/hybrid_viewer";
4
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
5
+ import { newInstance as vtkAnnotatedCubeActor } from "@kitware/vtk.js/Rendering/Core/AnnotatedCubeActor";
6
+ import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
7
+
8
+ const { panel, width } = defineProps({
9
+ panel: { type: Boolean, default: false },
10
+ width: { type: Number, default: 400 },
11
+ });
12
+
13
+ const show = defineModel("show", { type: Boolean, default: false });
14
+ const emit = defineEmits(["select"]);
15
+
16
+ const orientations = [
17
+ {
18
+ label: "Z+",
19
+ value: "zplus",
20
+ face: "top",
21
+ vtkKey: "ZPlus",
22
+ rotation: 0,
23
+ position: { top: "15%", left: "50%" },
24
+ },
25
+ {
26
+ label: "Z-",
27
+ value: "zminus",
28
+ face: "bottom",
29
+ vtkKey: "ZMinus",
30
+ rotation: 0,
31
+ position: { top: "85%", left: "50%" },
32
+ },
33
+ {
34
+ label: "Y+",
35
+ value: "yplus",
36
+ face: "front",
37
+ vtkKey: "YPlus",
38
+ rotation: 180,
39
+ position: { top: "35%", left: "20%" },
40
+ },
41
+ {
42
+ label: "Y-",
43
+ value: "yminus",
44
+ face: "back",
45
+ vtkKey: "YMinus",
46
+ rotation: 0,
47
+ position: { top: "65%", left: "80%" },
48
+ },
49
+ {
50
+ label: "X+",
51
+ value: "xplus",
52
+ face: "right",
53
+ vtkKey: "XPlus",
54
+ rotation: 90,
55
+ position: { top: "35%", left: "80%" },
56
+ },
57
+ {
58
+ label: "X-",
59
+ value: "xminus",
60
+ face: "left",
61
+ vtkKey: "XMinus",
62
+ rotation: -90,
63
+ position: { top: "65%", left: "20%" },
64
+ },
65
+ ];
66
+
67
+ const hoveredFace = ref(undefined);
68
+ const hybridViewerStore = useHybridViewerStore();
69
+ const cubeContainer = useTemplateRef("cubeContainer");
70
+
71
+ let genericRenderWindow = undefined;
72
+ let cubeActor = undefined;
73
+ let isInteracting = false;
74
+
75
+ function initVTK() {
76
+ if (genericRenderWindow) {
77
+ return;
78
+ }
79
+ genericRenderWindow = vtkGenericRenderWindow({
80
+ background: [0, 0, 0, 0],
81
+ listenWindowResize: false,
82
+ });
83
+
84
+ const interactor = genericRenderWindow.getInteractor();
85
+ interactor.onStartAnimation(() => {
86
+ isInteracting = true;
87
+ });
88
+
89
+ interactor.onEndAnimation(() => {
90
+ isInteracting = false;
91
+ });
92
+
93
+ cubeActor = vtkAnnotatedCubeActor();
94
+ cubeActor.setDefaultStyle({
95
+ fontFamily: "sans-serif",
96
+ fontStyle: "bold",
97
+ faceColor: "rgba(60, 60, 60, 1)",
98
+ fontColor: "white",
99
+ edgeColor: "rgba(255, 255, 255, 0.4)",
100
+ edgeThickness: 0.1,
101
+ resolution: 400,
102
+ fontSizeScale: (resolution) => resolution / 4,
103
+ });
104
+
105
+ for (const orientation of orientations) {
106
+ cubeActor[`set${orientation.vtkKey}FaceProperty`]({
107
+ text: orientation.label,
108
+ faceRotation: orientation.rotation,
109
+ });
110
+ }
111
+
112
+ cubeActor.getProperty().setBackfaceCulling(true);
113
+
114
+ const renderer = genericRenderWindow.getRenderer();
115
+
116
+ renderer.addActor(cubeActor);
117
+ renderer.resetCamera();
118
+ }
119
+
120
+ function syncCubeCamera() {
121
+ const options = hybridViewerStore.camera_options;
122
+ if (!genericRenderWindow || isInteracting || !options.position) {
123
+ return;
124
+ }
125
+ const camera = genericRenderWindow.getRenderer().getActiveCamera();
126
+ applyCameraOptions(camera, options);
127
+ genericRenderWindow.getRenderer().resetCamera();
128
+ genericRenderWindow.getRenderWindow().render();
129
+ }
130
+
131
+ watch(cubeContainer, (newContainer) => {
132
+ if (!newContainer || !import.meta.client) {
133
+ return;
134
+ }
135
+ initVTK();
136
+ genericRenderWindow.setContainer(newContainer);
137
+ const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
138
+ canvas.style.width = "100%";
139
+ canvas.style.height = "100%";
140
+ canvas.style.background = "transparent";
141
+ genericRenderWindow.resize();
142
+ syncCubeCamera();
143
+ });
144
+
145
+ onBeforeUnmount(() => genericRenderWindow?.delete());
146
+
147
+ watch(() => hybridViewerStore.camera_options, syncCubeCamera, { deep: true });
148
+
149
+ watch(hoveredFace, (newFace, oldFace) => {
150
+ if (!cubeActor) {
151
+ return;
152
+ }
153
+ function updateFace(face, active) {
154
+ const config = orientations.find((orientation) => orientation.face === face);
155
+ if (config) {
156
+ cubeActor[`set${config.vtkKey}FaceProperty`]({
157
+ faceColor: active ? "rgba(255, 255, 255, 0.95)" : "rgba(60, 60, 60, 1)",
158
+ fontColor: active ? "black" : "white",
159
+ });
160
+ }
161
+ }
162
+
163
+ updateFace(oldFace, false);
164
+ updateFace(newFace, true);
165
+ genericRenderWindow.getRenderWindow().render();
166
+ });
167
+ </script>
168
+
169
+ <template>
170
+ <ToolPanel
171
+ v-if="panel"
172
+ v-model="show"
173
+ title="Camera Orientations"
174
+ :width="width"
175
+ style="z-index: 2; top: 90px; right: 55px"
176
+ >
177
+ <div
178
+ class="pa-0 overflow-hidden position-relative"
179
+ style="
180
+ height: 320px;
181
+ background: radial-gradient(circle at center, rgba(255, 255, 255, 0.05), transparent 70%);
182
+ "
183
+ >
184
+ <svg class="position-absolute fill-height w-100" style="pointer-events: none">
185
+ <line
186
+ v-for="orientation in orientations"
187
+ :key="orientation.value"
188
+ x1="50%"
189
+ y1="50%"
190
+ :x2="orientation.position.left"
191
+ :y2="orientation.position.top"
192
+ :stroke="hoveredFace === orientation.face ? 'white' : 'rgba(255,255,255,0.1)'"
193
+ :stroke-width="hoveredFace === orientation.face ? 2 : 1"
194
+ class="transition-all"
195
+ style="filter: drop-shadow(0 0 3px white)"
196
+ />
197
+ </svg>
198
+
199
+ <div
200
+ class="position-absolute d-flex align-center justify-center"
201
+ style="top: 50%; left: 50%; transform: translate(-50%, -50%)"
202
+ >
203
+ <div ref="cubeContainer" style="width: 100px; height: 100px; pointer-events: none" />
204
+ </div>
205
+
206
+ <v-btn
207
+ v-for="orientation in orientations"
208
+ :key="orientation.value"
209
+ icon
210
+ variant="tonal"
211
+ size="44"
212
+ class="satellite-node position-absolute"
213
+ :style="orientation.position"
214
+ @mouseenter="hoveredFace = orientation.face"
215
+ @mouseleave="hoveredFace = undefined"
216
+ @click.stop="emit('select', orientation.value)"
217
+ >
218
+ <v-tooltip activator="parent" location="top">{{ orientation.value }} View</v-tooltip>
219
+ <span class="text-caption font-weight-black">{{ orientation.label }}</span>
220
+ </v-btn>
221
+ </div>
222
+ </ToolPanel>
223
+
224
+ <v-list v-else density="compact" class="pa-4 orientation-menu rounded-lg" elevation="8">
225
+ <div class="d-flex flex-column align-center" style="gap: 16px">
226
+ <div
227
+ class="d-flex align-center justify-center"
228
+ style="width: 60px; height: 60px; border-radius: 8px; overflow: hidden"
229
+ >
230
+ <div ref="cubeContainer" class="w-100 h-100" style="pointer-events: none" />
231
+ </div>
232
+ <v-divider class="w-100" />
233
+ <div class="d-flex flex-wrap justify-center" style="max-width: 140px">
234
+ <v-btn
235
+ v-for="orientation in orientations"
236
+ :key="orientation.value"
237
+ icon
238
+ size="32"
239
+ variant="text"
240
+ class="ma-1"
241
+ @mouseenter="hoveredFace = orientation.face"
242
+ @mouseleave="hoveredFace = undefined"
243
+ @click.stop="emit('select', orientation.value)"
244
+ >
245
+ <v-tooltip activator="parent" location="top">{{ orientation.label }}</v-tooltip>
246
+ <span class="text-caption font-weight-black">{{ orientation.label }}</span>
247
+ </v-btn>
248
+ </div>
249
+ </div>
250
+ </v-list>
251
+ </template>
252
+
253
+ <style scoped>
254
+ .satellite-node {
255
+ transform: translate(-50%, -50%);
256
+ background: rgba(0, 0, 0, 0.2) !important;
257
+ backdrop-filter: blur(8px);
258
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
259
+ transition: all 0.2s ease;
260
+ }
261
+ .satellite-node:hover {
262
+ transform: translate(-50%, -50%) scale(1.1);
263
+ border-color: white !important;
264
+ background: rgba(255, 255, 255, 0.1) !important;
265
+ }
266
+ .transition-all {
267
+ transition: all 0.4s ease;
268
+ }
269
+ .orientation-menu {
270
+ min-width: 180px;
271
+ background: rgb(var(--v-theme-surface)) !important;
272
+ }
273
+ </style>
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import VeaseViewToolbar from "@ogw_front/components/VeaseViewToolbar";
2
+ import ViewToolbar from "@ogw_front/components/ViewToolbar";
3
3
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
4
4
  import { useViewerStore } from "@ogw_front/stores/viewer";
5
5
 
@@ -47,7 +47,7 @@ function debounce(func, wait) {
47
47
  <template>
48
48
  <ClientOnly>
49
49
  <div data-testid="hybridViewer" class="fill-height" style="position: relative; height: 100%">
50
- <VeaseViewToolbar />
50
+ <ViewToolbar />
51
51
  <slot name="ui"></slot>
52
52
  <v-col
53
53
  class="pa-0"
@@ -1,15 +1,13 @@
1
1
  <script setup>
2
- import GlassCard from "@ogw_front/components/GlassCard";
2
+ import ToolPanel from "@ogw_front/components/ToolPanel";
3
3
  import fileDownload from "js-file-download";
4
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
5
-
6
4
  import { useViewerStore } from "@ogw_front/stores/viewer";
5
+ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
7
6
 
8
- const emit = defineEmits(["close"]);
7
+ const show = defineModel({ type: Boolean, default: false });
9
8
 
10
- const { show_dialog, width } = defineProps({
11
- show_dialog: { type: Boolean, required: true },
12
- width: { type: Number, required: false, default: 400 },
9
+ const { width } = defineProps({
10
+ width: { type: Number, default: 400 },
13
11
  });
14
12
 
15
13
  const output_extensions =
@@ -33,7 +31,7 @@ async function takeScreenshot() {
33
31
  },
34
32
  },
35
33
  );
36
- emit("close");
34
+ show.value = false;
37
35
  }
38
36
 
39
37
  watch(output_extension, (value) => {
@@ -42,58 +40,41 @@ watch(output_extension, (value) => {
42
40
  }
43
41
  });
44
42
  </script>
43
+
45
44
  <template>
46
- <GlassCard
47
- v-if="show_dialog"
48
- @click.stop
45
+ <ToolPanel
46
+ v-model="show"
49
47
  title="Take a screenshot"
50
48
  :width="width"
51
- :ripple="false"
52
- variant="panel"
53
- padding="pa-0"
54
- class="position-absolute elevation-24"
55
- style="z-index: 2; top: 90px; right: 55px"
49
+ close-label="Cancel"
50
+ action-label="Screenshot"
51
+ @action="takeScreenshot"
56
52
  >
57
- <v-card-text class="pa-5">
58
- <v-container>
59
- <v-row>
60
- <v-col cols="8" class="py-0">
61
- <v-text-field v-model="filename" label="File name"></v-text-field>
62
- </v-col>
63
- <v-col cols="4" class="py-0">
64
- <v-select
65
- v-model="output_extension"
66
- :items="output_extensions"
67
- label="Extension"
68
- required
69
- />
70
- </v-col>
71
- </v-row>
72
-
73
- <v-row>
74
- <v-col cols="12" class="py-0">
75
- <v-switch
76
- v-model="include_background"
77
- :disabled="output_extension !== 'png'"
78
- label="Include background"
79
- inset
80
- ></v-switch>
81
- </v-col>
82
- </v-row>
83
- </v-container>
84
- </v-card-text>
53
+ <v-container class="pa-5">
54
+ <v-row>
55
+ <v-col cols="8" class="py-0">
56
+ <v-text-field v-model="filename" label="File name"></v-text-field>
57
+ </v-col>
58
+ <v-col cols="4" class="py-0">
59
+ <v-select
60
+ v-model="output_extension"
61
+ :items="output_extensions"
62
+ label="Extension"
63
+ required
64
+ />
65
+ </v-col>
66
+ </v-row>
85
67
 
86
- <template #actions>
87
- <v-card-actions class="justify-center pb-4">
88
- <v-btn variant="text" color="primary" @click="emit('close')">Close</v-btn>
89
- <v-btn
90
- variant="outlined"
91
- :disabled="!filename || !output_extension"
92
- color="primary"
93
- @click="takeScreenshot()"
94
- >Screenshot</v-btn
95
- >
96
- </v-card-actions>
97
- </template>
98
- </GlassCard>
68
+ <v-row>
69
+ <v-col cols="12" class="py-0">
70
+ <v-switch
71
+ v-model="include_background"
72
+ :disabled="output_extension !== 'png'"
73
+ label="Include background"
74
+ inset
75
+ ></v-switch>
76
+ </v-col>
77
+ </v-row>
78
+ </v-container>
79
+ </ToolPanel>
99
80
  </template>
@@ -0,0 +1,62 @@
1
+ <script setup>
2
+ import GlassCard from "@ogw_front/components/GlassCard";
3
+
4
+ const { title, width, closeLabel, actionLabel } = defineProps({
5
+ title: { type: String, default: "" },
6
+ width: { type: Number, default: 400 },
7
+ closeLabel: { type: String, default: "Close" },
8
+ actionLabel: { type: String, default: undefined },
9
+ });
10
+
11
+ const model = defineModel({ type: Boolean, default: false });
12
+ const emit = defineEmits(["action"]);
13
+
14
+ function close() {
15
+ model.value = false;
16
+ }
17
+ </script>
18
+
19
+ <template>
20
+ <GlassCard
21
+ v-if="model"
22
+ v-click-outside="close"
23
+ :title="title"
24
+ :width="width"
25
+ :ripple="false"
26
+ variant="panel"
27
+ padding="pa-0"
28
+ class="position-absolute rounded-xl elevation-24 tool-panel"
29
+ v-bind="$attrs"
30
+ >
31
+ <v-card-text class="pa-0 overflow-hidden position-relative">
32
+ <slot />
33
+ </v-card-text>
34
+
35
+ <template #actions>
36
+ <slot name="actions">
37
+ <v-card-actions class="justify-center pb-6" style="gap: 12px">
38
+ <v-btn variant="text" size="small" color="white" @click="close">
39
+ {{ closeLabel }}
40
+ </v-btn>
41
+ <v-btn
42
+ v-if="actionLabel"
43
+ variant="outlined"
44
+ size="small"
45
+ color="white"
46
+ @click="emit('action')"
47
+ >
48
+ {{ actionLabel }}
49
+ </v-btn>
50
+ </v-card-actions>
51
+ </slot>
52
+ </template>
53
+ </GlassCard>
54
+ </template>
55
+
56
+ <style scoped>
57
+ .tool-panel {
58
+ z-index: 2;
59
+ top: 90px;
60
+ right: 55px;
61
+ }
62
+ </style>
@@ -2,19 +2,46 @@
2
2
  import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
3
3
 
4
4
  import ActionButton from "@ogw_front/components/ActionButton.vue";
5
+ import CameraOrientation from "@ogw_front/components/CameraOrientation.vue";
5
6
  import Screenshot from "@ogw_front/components/Screenshot";
7
+ import ZScaling from "@ogw_front/components/ZScaling";
8
+
9
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
6
10
  import { useViewerStore } from "@ogw_front/stores/viewer";
7
11
 
12
+ const hybridViewerStore = useHybridViewerStore();
8
13
  const viewerStore = useViewerStore();
9
14
  const take_screenshot = ref(false);
15
+ const showCameraOrientation = ref(false);
16
+ const showZScaling = ref(false);
10
17
  const grid_scale = ref(false);
18
+ const zScale = ref(hybridViewerStore.zScale);
19
+
20
+ watch(
21
+ () => hybridViewerStore.zScale,
22
+ (newVal) => {
23
+ zScale.value = newVal;
24
+ },
25
+ );
26
+
27
+ async function handleZScalingClose() {
28
+ await hybridViewerStore.setZScaling(zScale.value);
29
+ showZScaling.value = false;
30
+ }
11
31
 
12
32
  const camera_options = [
13
33
  {
14
34
  tooltip: "Reset camera",
15
35
  icon: "mdi-cube-scan",
16
36
  action: () => {
17
- viewerStore.request(schemas.opengeodeweb_viewer.viewer.reset_camera);
37
+ hybridViewerStore.resetCamera();
38
+ },
39
+ },
40
+ {
41
+ tooltip: "Camera orientation",
42
+ icon: "mdi-rotate-3d",
43
+ action: () => {
44
+ showCameraOrientation.value = !showCameraOrientation.value;
18
45
  },
19
46
  },
20
47
  {
@@ -34,11 +61,19 @@ const camera_options = [
34
61
  {
35
62
  response_function: () => {
36
63
  grid_scale.value = !grid_scale.value;
64
+ hybridViewerStore.remoteRender();
37
65
  },
38
66
  },
39
67
  );
40
68
  },
41
69
  },
70
+ {
71
+ tooltip: "Z Scaling Control",
72
+ icon: "mdi-sort",
73
+ action: () => {
74
+ showZScaling.value = !showZScaling.value;
75
+ },
76
+ },
42
77
  ];
43
78
  </script>
44
79
 
@@ -47,15 +82,26 @@ const camera_options = [
47
82
  <v-row v-for="camera_option in camera_options" :key="camera_option.icon" dense>
48
83
  <v-col>
49
84
  <ActionButton
50
- :tooltip="camera_option.tooltip"
51
85
  :icon="camera_option.icon"
86
+ :tooltip="camera_option.tooltip"
52
87
  tooltip-location="left"
53
- @click="camera_option.action"
88
+ @click.stop="camera_option.action"
54
89
  />
55
90
  </v-col>
56
91
  </v-row>
57
92
  </v-container>
58
- <Screenshot :show_dialog="take_screenshot" @close="take_screenshot = false" />
93
+ <CameraOrientation
94
+ v-model:show="showCameraOrientation"
95
+ panel
96
+ @select="hybridViewerStore.setCameraOrientation"
97
+ />
98
+ <Screenshot v-model="take_screenshot" />
99
+ <ZScaling
100
+ v-model:show="showZScaling"
101
+ v-model="zScale"
102
+ :width="400"
103
+ @apply="handleZScalingClose"
104
+ />
59
105
  </template>
60
106
 
61
107
  <style module>
@@ -46,48 +46,6 @@ const color = computed({
46
46
  hybridViewerStore.remoteRender();
47
47
  },
48
48
  });
49
- const vertex_attribute_name = computed({
50
- get: () => dataStyleStore.meshEdgesVertexAttributeName(id.value),
51
- set: async (newValue) => {
52
- await dataStyleStore.setMeshEdgesVertexAttributeName(id.value, newValue);
53
- hybridViewerStore.remoteRender();
54
- },
55
- });
56
- const vertex_attribute_range = computed({
57
- get: () => dataStyleStore.meshEdgesVertexAttributeRange(id.value),
58
- set: async (newValue) => {
59
- await dataStyleStore.setMeshEdgesVertexAttributeRange(id.value, newValue[0], newValue[1]);
60
- hybridViewerStore.remoteRender();
61
- },
62
- });
63
- const vertex_attribute_color_map = computed({
64
- get: () => dataStyleStore.meshEdgesVertexAttributeColorMap(id.value),
65
- set: async (newValue) => {
66
- await dataStyleStore.setMeshEdgesVertexAttributeColorMap(id.value, newValue);
67
- hybridViewerStore.remoteRender();
68
- },
69
- });
70
- const edge_attribute_name = computed({
71
- get: () => dataStyleStore.meshEdgesEdgeAttributeName(id.value),
72
- set: async (newValue) => {
73
- await dataStyleStore.setMeshEdgesEdgeAttributeName(id.value, newValue);
74
- hybridViewerStore.remoteRender();
75
- },
76
- });
77
- const edge_attribute_range = computed({
78
- get: () => dataStyleStore.meshEdgesEdgeAttributeRange(id.value),
79
- set: async (newValue) => {
80
- await dataStyleStore.setMeshEdgesEdgeAttributeRange(id.value, newValue[0], newValue[1]);
81
- hybridViewerStore.remoteRender();
82
- },
83
- });
84
- const edge_attribute_color_map = computed({
85
- get: () => dataStyleStore.meshEdgesEdgeAttributeColorMap(id.value),
86
- set: async (newValue) => {
87
- await dataStyleStore.setMeshEdgesEdgeAttributeColorMap(id.value, newValue);
88
- hybridViewerStore.remoteRender();
89
- },
90
- });
91
49
  </script>
92
50
 
93
51
  <template>
@@ -110,12 +68,6 @@ const edge_attribute_color_map = computed({
110
68
  :id="id"
111
69
  v-model:coloring_style_key="coloring_style_key"
112
70
  v-model:color="color"
113
- v-model:vertex_attribute_name="vertex_attribute_name"
114
- v-model:vertex_attribute_range="vertex_attribute_range"
115
- v-model:vertex_attribute_color_map="vertex_attribute_color_map"
116
- v-model:edge_attribute_name="edge_attribute_name"
117
- v-model:edge_attribute_range="edge_attribute_range"
118
- v-model:edge_attribute_color_map="edge_attribute_color_map"
119
71
  />
120
72
  </v-col>
121
73
  </v-row>
@@ -95,6 +95,7 @@ const vertex_attribute_color_map = computed({
95
95
  v-model:vertex_attribute_name="vertex_attribute_name"
96
96
  v-model:vertex_attribute_range="vertex_attribute_range"
97
97
  v-model:vertex_attribute_color_map="vertex_attribute_color_map"
98
+ :capabilities="{ vertex: { available: false } }"
98
99
  />
99
100
  </v-col>
100
101
  </v-row>