@geode/opengeodeweb-front 10.38.0-rc.4 → 10.38.0-rc.5

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,34 @@
1
+ <script setup lang="ts">
2
+ import FactorFilterPanel from "@ogw_front/components/FactorFilterPanel.vue";
3
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
4
+
5
+ const DEFAULT_EXPLODE_VALUE = 0.2;
6
+ const MIN_EXPLODE_VALUE = 0;
7
+ const MAX_EXPLODE_VALUE = 1;
8
+
9
+ interface Props {
10
+ escapeFunction?: () => void;
11
+ }
12
+
13
+ const { escapeFunction = undefined } = defineProps<Props>();
14
+
15
+ const show = defineModel<boolean>("show", { default: false });
16
+ const hybridViewerStore = useHybridViewerStore();
17
+ </script>
18
+
19
+ <template>
20
+ <FactorFilterPanel
21
+ v-model:show="show"
22
+ title="Exploded View"
23
+ label="Explode Factor"
24
+ test-id-prefix="explode"
25
+ :set-factor="hybridViewerStore.setExplode"
26
+ :factor-range="{
27
+ min: MIN_EXPLODE_VALUE,
28
+ max: MAX_EXPLODE_VALUE,
29
+ defaultValue: DEFAULT_EXPLODE_VALUE,
30
+ neutralValue: MIN_EXPLODE_VALUE,
31
+ }"
32
+ :escapeFunction="escapeFunction"
33
+ />
34
+ </template>
@@ -0,0 +1,211 @@
1
+ <script setup lang="ts">
2
+ import ToolPanel from "@ogw_front/components/ToolPanel.vue";
3
+ import { useDataStore } from "@ogw_front/stores/data";
4
+ import { useDebounceFn } from "@vueuse/core";
5
+ import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
6
+
7
+ const DEBOUNCE_DELAY = 100;
8
+
9
+ interface FactorRange {
10
+ min: number;
11
+ max: number;
12
+ defaultValue: number;
13
+ neutralValue: number;
14
+ }
15
+
16
+ interface Props {
17
+ title: string;
18
+ label: string;
19
+ testIdPrefix: string;
20
+ setFactor: (ids: string[], factor: number) => Promise<void>;
21
+ factorRange: FactorRange;
22
+ formatValue?: (value: number) => string;
23
+ escapeFunction?: () => void;
24
+ }
25
+
26
+ const {
27
+ title,
28
+ label,
29
+ testIdPrefix,
30
+ setFactor,
31
+ factorRange,
32
+ formatValue = (value: number): string => value.toFixed(2),
33
+ escapeFunction = undefined,
34
+ } = defineProps<Props>();
35
+
36
+ const capitalizedPrefix = computed<string>(
37
+ () => `${testIdPrefix.charAt(0).toUpperCase()}${testIdPrefix.slice(1)}`,
38
+ );
39
+ const show = defineModel<boolean>("show", { default: false });
40
+ const dataStore = useDataStore();
41
+ const hybridViewerStore = useHybridViewerStore();
42
+ const targetAllVisible = ref<boolean>(true);
43
+ const selectedDatasetIds = ref<string[]>([]);
44
+ const factor = ref<number>(factorRange.defaultValue);
45
+
46
+ const allItems = dataStore.refAllItems();
47
+ const availableDatasets = computed<{ title: string; value: string }[]>(() =>
48
+ allItems.value.map((item) => ({
49
+ title: item.name || item.id,
50
+ value: item.id,
51
+ })),
52
+ );
53
+
54
+ let appliedFactor: number | undefined = undefined;
55
+
56
+ async function applyFactor(): Promise<void> {
57
+ appliedFactor = factor.value;
58
+ const allIds = allItems.value.map((item) => item.id);
59
+ if (allIds.length === 0) {
60
+ return;
61
+ }
62
+ const targetIds = targetAllVisible.value ? allIds : selectedDatasetIds.value;
63
+ const untargetedIds = allIds.filter((id) => !targetIds.includes(id));
64
+
65
+ if (targetIds.length > 0) {
66
+ await setFactor(targetIds, Number(factor.value));
67
+ }
68
+ if (untargetedIds.length > 0) {
69
+ await setFactor(untargetedIds, factorRange.neutralValue);
70
+ }
71
+ }
72
+
73
+ const debouncedApply = useDebounceFn(() => applyFactor(), DEBOUNCE_DELAY);
74
+
75
+ async function resetFactor(): Promise<void> {
76
+ factor.value = factorRange.defaultValue;
77
+ await applyFactor();
78
+ }
79
+
80
+ async function removeFactor(): Promise<void> {
81
+ factor.value = factorRange.neutralValue;
82
+ await applyFactor();
83
+ }
84
+
85
+ watch(factor, (value) => {
86
+ if (value !== appliedFactor) {
87
+ debouncedApply();
88
+ }
89
+ });
90
+
91
+ watch(show, (visible) => {
92
+ if (visible) {
93
+ applyFactor();
94
+ }
95
+ });
96
+
97
+ watch(
98
+ [targetAllVisible, selectedDatasetIds],
99
+ () => {
100
+ if (show.value) {
101
+ applyFactor();
102
+ }
103
+ },
104
+ { deep: true },
105
+ );
106
+
107
+ watch(allItems, () => {
108
+ if (show.value) {
109
+ applyFactor();
110
+ }
111
+ });
112
+
113
+ watch(
114
+ () => Object.values(hybridViewerStore.hybridDb).filter((entry) => entry && entry.actor).length,
115
+ (actorCount) => {
116
+ if (show.value && actorCount > 0) {
117
+ applyFactor();
118
+ }
119
+ },
120
+ );
121
+ </script>
122
+
123
+ <template>
124
+ <ToolPanel
125
+ v-model="show"
126
+ :title="title"
127
+ :width="340"
128
+ :click-outside="false"
129
+ :escapeFunction="escapeFunction"
130
+ >
131
+ <v-card-text class="pa-3 max-panel-height overflow-y-auto overflow-x-hidden">
132
+ <v-switch
133
+ v-model="targetAllVisible"
134
+ :data-testid="`${testIdPrefix}TargetAllVisibleSwitch`"
135
+ label="Apply to all visible datasets"
136
+ color="primary"
137
+ density="compact"
138
+ hide-details
139
+ class="mb-2 text-caption"
140
+ />
141
+
142
+ <v-select
143
+ v-if="!targetAllVisible"
144
+ v-model="selectedDatasetIds"
145
+ :data-testid="`${testIdPrefix}SelectedDatasetsSelect`"
146
+ :items="availableDatasets"
147
+ label="Select datasets"
148
+ multiple
149
+ chips
150
+ closable-chips
151
+ variant="outlined"
152
+ density="compact"
153
+ hide-details
154
+ class="mb-3 text-caption"
155
+ />
156
+
157
+ <v-divider class="my-3" />
158
+
159
+ <div class="d-flex align-center justify-space-between mb-1">
160
+ <span class="text-caption font-weight-bold">{{ label }}</span>
161
+ <span class="text-caption text-primary font-weight-bold">
162
+ {{ formatValue(factor) }}
163
+ </span>
164
+ </div>
165
+
166
+ <v-slider
167
+ v-model="factor"
168
+ :data-testid="`${testIdPrefix}FactorSlider`"
169
+ :min="factorRange.min"
170
+ :max="factorRange.max"
171
+ step="0.01"
172
+ color="primary"
173
+ track-color="grey-lighten-2"
174
+ density="compact"
175
+ hide-details
176
+ class="my-2 px-1"
177
+ />
178
+ </v-card-text>
179
+
180
+ <template #actions>
181
+ <v-card-actions class="justify-space-between px-3 pb-3 pt-0">
182
+ <v-btn
183
+ :data-testid="`remove${capitalizedPrefix}Button`"
184
+ variant="text"
185
+ size="small"
186
+ color="error"
187
+ class="text-caption text-none"
188
+ @click="removeFactor"
189
+ >
190
+ Remove {{ capitalizedPrefix }}
191
+ </v-btn>
192
+ <v-btn
193
+ :data-testid="`reset${capitalizedPrefix}Button`"
194
+ variant="tonal"
195
+ size="small"
196
+ color="secondary"
197
+ class="text-caption text-none"
198
+ @click="resetFactor"
199
+ >
200
+ Reset
201
+ </v-btn>
202
+ </v-card-actions>
203
+ </template>
204
+ </ToolPanel>
205
+ </template>
206
+
207
+ <style scoped>
208
+ .max-panel-height {
209
+ max-height: 520px;
210
+ }
211
+ </style>
@@ -1,12 +1,11 @@
1
1
  <script setup lang="ts">
2
- import ToolPanel from "@ogw_front/components/ToolPanel.vue";
3
- import { useDataStore } from "@ogw_front/stores/data";
4
- import { useDebounceFn } from "@vueuse/core";
2
+ import FactorFilterPanel from "@ogw_front/components/FactorFilterPanel.vue";
5
3
  import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
6
4
 
7
5
  const DEFAULT_SHRINK_VALUE = 0.8;
6
+ const MIN_SHRINK_VALUE = 0;
8
7
  const MAX_SHRINK_VALUE = 1;
9
- const DEBOUNCE_DELAY = 100;
8
+ const PERCENT = 100;
10
9
 
11
10
  interface Props {
12
11
  escapeFunction?: () => void;
@@ -15,173 +14,27 @@ interface Props {
15
14
  const { escapeFunction = undefined } = defineProps<Props>();
16
15
 
17
16
  const show = defineModel<boolean>("show", { default: false });
18
- const dataStore = useDataStore();
19
17
  const hybridViewerStore = useHybridViewerStore();
20
- const targetAllVisible = ref<boolean>(true);
21
- const selectedDatasetIds = ref<string[]>([]);
22
- const shrinkFactor = ref<number>(DEFAULT_SHRINK_VALUE);
23
18
 
24
- const allItems = dataStore.refAllItems();
25
- const availableDatasets = computed<{ title: string; value: string }[]>(() =>
26
- allItems.value.map((item) => ({
27
- title: item.name || item.id,
28
- value: item.id,
29
- })),
30
- );
31
-
32
- async function applyShrink(): Promise<void> {
33
- const allIds = allItems.value.map((item) => item.id);
34
- if (allIds.length === 0) {
35
- return;
36
- }
37
- const targetIds = targetAllVisible.value ? allIds : selectedDatasetIds.value;
38
- const untargetedIds = allIds.filter((id) => !targetIds.includes(id));
39
-
40
- if (targetIds.length > 0) {
41
- await hybridViewerStore.setShrink(targetIds, Number(shrinkFactor.value));
42
- }
43
- if (untargetedIds.length > 0) {
44
- await hybridViewerStore.setShrink(untargetedIds, MAX_SHRINK_VALUE);
45
- }
46
- }
47
-
48
- const debouncedApply = useDebounceFn(() => applyShrink(), DEBOUNCE_DELAY);
49
-
50
- async function resetShrink(): Promise<void> {
51
- shrinkFactor.value = DEFAULT_SHRINK_VALUE;
52
- await applyShrink();
19
+ function formatShrinkFactor(value: number): string {
20
+ return `${(value * PERCENT).toFixed(0)}% (${value.toFixed(2)})`;
53
21
  }
54
-
55
- async function removeShrink(): Promise<void> {
56
- shrinkFactor.value = MAX_SHRINK_VALUE;
57
- const allIds = allItems.value.map((item) => item.id);
58
- if (allIds.length > 0) {
59
- await hybridViewerStore.setShrink(allIds, MAX_SHRINK_VALUE);
60
- }
61
- }
62
-
63
- watch(shrinkFactor, () => {
64
- debouncedApply();
65
- });
66
-
67
- watch(show, (visible) => {
68
- if (visible) {
69
- applyShrink();
70
- }
71
- });
72
-
73
- watch(
74
- [targetAllVisible, selectedDatasetIds],
75
- () => {
76
- if (show.value) {
77
- applyShrink();
78
- }
79
- },
80
- { deep: true },
81
- );
82
-
83
- watch(allItems, () => {
84
- if (show.value) {
85
- applyShrink();
86
- }
87
- });
88
-
89
- watch(
90
- () => Object.values(hybridViewerStore.hybridDb).filter((entry) => entry && entry.actor).length,
91
- (actorCount) => {
92
- if (show.value && actorCount > 0) {
93
- applyShrink();
94
- }
95
- },
96
- );
97
22
  </script>
98
23
 
99
24
  <template>
100
- <ToolPanel
101
- v-model="show"
25
+ <FactorFilterPanel
26
+ v-model:show="show"
102
27
  title="Shrink Filter"
103
- :width="340"
104
- :click-outside="false"
28
+ label="Shrink Factor"
29
+ test-id-prefix="shrink"
30
+ :set-factor="hybridViewerStore.setShrink"
31
+ :factor-range="{
32
+ min: MIN_SHRINK_VALUE,
33
+ max: MAX_SHRINK_VALUE,
34
+ defaultValue: DEFAULT_SHRINK_VALUE,
35
+ neutralValue: MAX_SHRINK_VALUE,
36
+ }"
37
+ :format-value="formatShrinkFactor"
105
38
  :escapeFunction="escapeFunction"
106
- >
107
- <v-card-text class="pa-3 max-panel-height overflow-y-auto overflow-x-hidden">
108
- <v-switch
109
- v-model="targetAllVisible"
110
- data-testid="shrinkTargetAllVisibleSwitch"
111
- label="Apply to all visible datasets"
112
- color="primary"
113
- density="compact"
114
- hide-details
115
- class="mb-2 text-caption"
116
- />
117
-
118
- <v-select
119
- v-if="!targetAllVisible"
120
- v-model="selectedDatasetIds"
121
- data-testid="shrinkSelectedDatasetsSelect"
122
- :items="availableDatasets"
123
- label="Select datasets"
124
- multiple
125
- chips
126
- closable-chips
127
- variant="outlined"
128
- density="compact"
129
- hide-details
130
- class="mb-3 text-caption"
131
- />
132
-
133
- <v-divider class="my-3" />
134
-
135
- <div class="d-flex align-center justify-space-between mb-1">
136
- <span class="text-caption font-weight-bold">Shrink Factor</span>
137
- <span class="text-caption text-primary font-weight-bold">
138
- {{ (shrinkFactor * 100).toFixed(0) }}% ({{ shrinkFactor.toFixed(2) }})
139
- </span>
140
- </div>
141
-
142
- <v-slider
143
- v-model="shrinkFactor"
144
- data-testid="shrinkFactorSlider"
145
- min="0.0"
146
- max="1.0"
147
- step="0.01"
148
- color="primary"
149
- track-color="grey-lighten-2"
150
- density="compact"
151
- hide-details
152
- class="my-2 px-1"
153
- />
154
- </v-card-text>
155
-
156
- <template #actions>
157
- <v-card-actions class="justify-space-between px-3 pb-3 pt-0">
158
- <v-btn
159
- data-testid="removeShrinkButton"
160
- variant="text"
161
- size="small"
162
- color="error"
163
- class="text-caption text-none"
164
- @click="removeShrink"
165
- >
166
- Remove Shrink
167
- </v-btn>
168
- <v-btn
169
- data-testid="resetShrinkButton"
170
- variant="tonal"
171
- size="small"
172
- color="secondary"
173
- class="text-caption text-none"
174
- @click="resetShrink"
175
- >
176
- Reset
177
- </v-btn>
178
- </v-card-actions>
179
- </template>
180
- </ToolPanel>
39
+ />
181
40
  </template>
182
-
183
- <style scoped>
184
- .max-panel-height {
185
- max-height: 520px;
186
- }
187
- </style>
@@ -7,6 +7,7 @@ import CameraBookmarkIcon from "@ogw_front/assets/viewer_svgs/camera-bookmark.sv
7
7
  import CameraManager from "@ogw_front/components/CameraManager.vue";
8
8
  import CameraOrientation from "@ogw_front/components/CameraOrientation.vue";
9
9
  import ClippingPlanes from "@ogw_front/components/ClippingPlanes.vue";
10
+ import ExplodeFilter from "@ogw_front/components/ExplodeFilter.vue";
10
11
  import Ruler from "@ogw_front/components/Ruler.vue";
11
12
  import Screenshot from "@ogw_front/components/Screenshot.vue";
12
13
  import ShrinkFilter from "@ogw_front/components/ShrinkFilter.vue";
@@ -26,6 +27,7 @@ const showCameraOrientation = ref<boolean>(false);
26
27
  const showZScaling = ref<boolean>(false);
27
28
  const showClippingPlanes = ref<boolean>(false);
28
29
  const showShrinkFilter = ref<boolean>(false);
30
+ const showExplodeFilter = ref<boolean>(false);
29
31
  const showThresholdFilter = ref<boolean>(false);
30
32
  const showRuler = ref<boolean>(false);
31
33
  const gridScale = ref<boolean>(false);
@@ -77,6 +79,7 @@ function closeAllToolsExcept(toolRef: Ref<boolean>): void {
77
79
  showZScaling,
78
80
  showClippingPlanes,
79
81
  showShrinkFilter,
82
+ showExplodeFilter,
80
83
  showThresholdFilter,
81
84
  showRuler,
82
85
  is_picking,
@@ -236,6 +239,15 @@ const camera_options = computed<CameraOptionAction[]>(() => [
236
239
  toggleTool(showShrinkFilter);
237
240
  },
238
241
  },
242
+ {
243
+ testId: "explodeFilterButton",
244
+ tooltip: "Exploded View",
245
+ icon: "mdi-arrow-expand-all",
246
+ color: showExplodeFilter.value ? "primary" : undefined,
247
+ action: (): void => {
248
+ toggleTool(showExplodeFilter);
249
+ },
250
+ },
239
251
  {
240
252
  testId: "thresholdFilterButton",
241
253
  tooltip: "Threshold Filter",
@@ -334,6 +346,10 @@ const camera_options = computed<CameraOptionAction[]>(() => [
334
346
  v-model:show="showShrinkFilter"
335
347
  :escapeFunction="() => (showShrinkFilter = false)"
336
348
  />
349
+ <ExplodeFilter
350
+ v-model:show="showExplodeFilter"
351
+ :escapeFunction="() => (showExplodeFilter = false)"
352
+ />
337
353
  <ThresholdFilter
338
354
  v-model:show="showThresholdFilter"
339
355
  :escapeFunction="() => (showThresholdFilter = false)"
@@ -1,3 +1,4 @@
1
+ import type { JsonRpcSchema, ParamsOf, ResponseOf } from "@ogw_shared/utils/types.js";
1
2
  import viewer_schemas, {
2
3
  type ViewerClippingPlanesParams,
3
4
  } from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
@@ -5,55 +6,51 @@ import type { SliceAxis } from "@ogw_front/utils/slice";
5
6
  import { useHybridViewerCore } from "./core";
6
7
  import { useViewerStore } from "@ogw_front/stores/viewer";
7
8
 
9
+ async function requestAndRender<Schema extends JsonRpcSchema>(
10
+ schema: Schema,
11
+ params: ParamsOf<Schema>,
12
+ ): Promise<ResponseOf<Schema>> {
13
+ const viewerStore = useViewerStore();
14
+ const { remoteRender } = useHybridViewerCore();
15
+ const response = await viewerStore.request({
16
+ schema,
17
+ params,
18
+ });
19
+ await remoteRender();
20
+ return response;
21
+ }
22
+
8
23
  async function setClippingPlanes(
9
24
  ids: string[],
10
25
  planes: ViewerClippingPlanesParams["planes"],
11
26
  ): Promise<void> {
12
- const viewerStore = useViewerStore();
13
- const { remoteRender } = useHybridViewerCore();
14
- const schema = viewer_schemas.opengeodeweb_viewer.viewer.clipping_planes;
15
- const params = {
27
+ await requestAndRender(viewer_schemas.opengeodeweb_viewer.viewer.clipping_planes, {
16
28
  ids,
17
29
  planes,
18
- };
19
- await viewerStore.request({
20
- schema,
21
- params,
22
30
  });
23
- await remoteRender();
24
31
  }
25
32
  async function setShrink(ids: string[], shrink_factor: number): Promise<void> {
26
- const viewerStore = useViewerStore();
27
- const { remoteRender } = useHybridViewerCore();
28
- const schema = viewer_schemas.opengeodeweb_viewer.viewer.shrink;
29
- const params = {
33
+ await requestAndRender(viewer_schemas.opengeodeweb_viewer.viewer.shrink, {
30
34
  ids,
31
35
  shrink_factor,
32
- };
33
- await viewerStore.request({
34
- schema,
35
- params,
36
36
  });
37
- await remoteRender();
37
+ }
38
+ async function setExplode(ids: string[], explode_factor: number): Promise<void> {
39
+ await requestAndRender(viewer_schemas.opengeodeweb_viewer.viewer.explode, {
40
+ ids,
41
+ explode_factor,
42
+ });
38
43
  }
39
44
  async function setSlice(
40
45
  ids: string[],
41
46
  slices: { axis: SliceAxis; index: number }[],
42
47
  ): Promise<[number, number, number]> {
43
- const viewerStore = useViewerStore();
44
- const { remoteRender } = useHybridViewerCore();
45
- const schema = viewer_schemas.opengeodeweb_viewer.viewer.slice;
46
- const params = {
47
- ids,
48
- slices,
49
- };
50
48
  const {
51
49
  max_indices: [max_i = 0, max_j = 0, max_k = 0],
52
- } = await viewerStore.request({
53
- schema,
54
- params,
50
+ } = await requestAndRender(viewer_schemas.opengeodeweb_viewer.viewer.slice, {
51
+ ids,
52
+ slices,
55
53
  });
56
- await remoteRender();
57
54
  return [max_i, max_j, max_k];
58
55
  }
59
56
  interface ThresholdAttribute {
@@ -64,22 +61,15 @@ interface ThresholdAttribute {
64
61
  maximum: number;
65
62
  }
66
63
  async function setThreshold(ids: string[], attribute?: ThresholdAttribute): Promise<void> {
67
- const viewerStore = useViewerStore();
68
- const { remoteRender } = useHybridViewerCore();
69
- const schema = viewer_schemas.opengeodeweb_viewer.viewer.threshold;
70
- const params = {
64
+ await requestAndRender(viewer_schemas.opengeodeweb_viewer.viewer.threshold, {
71
65
  ids,
72
66
  attribute,
73
- };
74
- await viewerStore.request({
75
- schema,
76
- params,
77
67
  });
78
- await remoteRender();
79
68
  }
80
69
  function useHybridViewerFilters(): {
81
70
  setClippingPlanes: (ids: string[], planes: ViewerClippingPlanesParams["planes"]) => Promise<void>;
82
71
  setShrink: (ids: string[], shrink_factor: number) => Promise<void>;
72
+ setExplode: (ids: string[], explode_factor: number) => Promise<void>;
83
73
  setSlice: (
84
74
  ids: string[],
85
75
  slices: { axis: SliceAxis; index: number }[],
@@ -89,6 +79,7 @@ function useHybridViewerFilters(): {
89
79
  return {
90
80
  setClippingPlanes,
91
81
  setShrink,
82
+ setExplode,
92
83
  setSlice,
93
84
  setThreshold,
94
85
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.38.0-rc.4",
3
+ "version": "10.38.0-rc.5",
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": {