@geode/opengeodeweb-front 10.41.0 → 10.42.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.
- package/app/components/Screenshot.vue +148 -25
- package/app/components/Viewer/Generic/Mesh/EdgesOptions.vue +8 -1
- package/app/components/Viewer/Generic/Model/ComponentEdgesOptions.vue +223 -0
- package/app/components/Viewer/Generic/Model/EdgesOptions.vue +2 -27
- package/app/components/Viewer/Generic/Model/ModelEdgesCard.vue +159 -0
- package/app/components/Viewer/Grid/2D/EdgesOptions.vue +5 -1
- package/app/components/Viewer/Grid/3D/EdgesOptions.vue +5 -1
- package/app/components/Viewer/Solid/EdgesOptions.vue +5 -1
- package/app/components/Viewer/Surface/EdgesOptions.vue +5 -1
- package/internal/stores/data_style/model/blocks/edges.ts +51 -0
- package/internal/stores/data_style/model/blocks/index.ts +4 -0
- package/internal/stores/data_style/model/component_style.ts +27 -0
- package/internal/stores/data_style/model/edges/component.ts +406 -0
- package/internal/stores/data_style/model/edges/index.ts +5 -1
- package/internal/stores/data_style/model/edges/visibility.ts +24 -3
- package/internal/stores/data_style/model/edges/width.ts +56 -0
- package/internal/stores/data_style/model/index.ts +3 -1
- package/internal/stores/data_style/model/points/component.ts +5 -23
- package/internal/stores/data_style/model/surfaces/edges.ts +51 -0
- package/internal/stores/data_style/model/surfaces/index.ts +4 -0
- package/internal/stores/hybrid_viewer/scene.ts +23 -2
- package/package.json +4 -4
- package/server/api/microservice/app/set_is_app_ready.post.ts +0 -4
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
import viewer_schemas, {
|
|
3
3
|
type ViewerTakeScreenshotParams,
|
|
4
4
|
} from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
|
|
5
|
+
import { BACKGROUND_GREY_VALUE } from "@ogw_internal/stores/hybrid_viewer/constants";
|
|
6
|
+
import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
|
|
5
7
|
import ToolPanel from "@ogw_front/components/ToolPanel.vue";
|
|
8
|
+
import ViewerOptionsColorPicker from "@ogw_front/components/Viewer/Options/ColorPicker.vue";
|
|
6
9
|
import fileDownload from "js-file-download";
|
|
7
10
|
import { useClipboardItems } from "@vueuse/core";
|
|
8
11
|
import { useFeedbackStore } from "@ogw_front/stores/feedback";
|
|
12
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
9
13
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
10
14
|
|
|
11
15
|
const DEFAULT_PANEL_WIDTH = 260;
|
|
@@ -24,14 +28,54 @@ const output_extensions =
|
|
|
24
28
|
const filename = ref<string>("");
|
|
25
29
|
const output_extension = ref<ViewerTakeScreenshotParams["output_extension"]>("png");
|
|
26
30
|
const include_background = ref<boolean>(true);
|
|
31
|
+
const include_ui = ref<boolean>(false);
|
|
27
32
|
const screenshot_type = ref<string>("file");
|
|
33
|
+
const DEFAULT_BACKGROUND_COLOR: RGBAColor = {
|
|
34
|
+
red: BACKGROUND_GREY_VALUE,
|
|
35
|
+
green: BACKGROUND_GREY_VALUE,
|
|
36
|
+
blue: BACKGROUND_GREY_VALUE,
|
|
37
|
+
alpha: 1,
|
|
38
|
+
};
|
|
39
|
+
const background_color = ref<RGBAColor>({ ...DEFAULT_BACKGROUND_COLOR });
|
|
40
|
+
let is_capturing = false;
|
|
28
41
|
|
|
29
42
|
const { copy } = useClipboardItems();
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
const
|
|
44
|
+
function applyBackgroundColor(color: RGBAColor): Promise<void> {
|
|
45
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
46
|
+
// oxlint-disable-next-line id-length -- the viewer schema requires { r, g, b }
|
|
47
|
+
return hybridViewerStore.setBackgroundColor({ r: color.red, g: color.green, b: color.blue });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function restoreBackgroundColor(): Promise<void> {
|
|
51
|
+
background_color.value = { ...DEFAULT_BACKGROUND_COLOR };
|
|
52
|
+
await applyBackgroundColor(DEFAULT_BACKGROUND_COLOR);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function deliverScreenshot(blob: Blob | Uint8Array, current_filename: string): Promise<void> {
|
|
33
56
|
const feedbackStore = useFeedbackStore();
|
|
34
|
-
|
|
57
|
+
if (screenshot_type.value === "file") {
|
|
58
|
+
fileDownload(blob, `${current_filename}.${output_extension.value}`);
|
|
59
|
+
feedbackStore.add_success("Screenshot downloaded");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const pngBlob = new Blob([blob], { type: "image/png" });
|
|
64
|
+
await copy([new ClipboardItem({ "image/png": pngBlob })]);
|
|
65
|
+
feedbackStore.add_success("Screenshot copied to clipboard");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
feedbackStore.add_error(
|
|
69
|
+
0,
|
|
70
|
+
"",
|
|
71
|
+
"Clipboard Error",
|
|
72
|
+
`Failed to copy screenshot to clipboard: ${message}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function takeSceneScreenshot(current_filename: string): Promise<void> {
|
|
78
|
+
const viewerStore = useViewerStore();
|
|
35
79
|
const schema = viewer_schemas.opengeodeweb_viewer.viewer.take_screenshot;
|
|
36
80
|
const params = {
|
|
37
81
|
filename: current_filename,
|
|
@@ -46,32 +90,92 @@ async function takeScreenshot(): Promise<void> {
|
|
|
46
90
|
{
|
|
47
91
|
// `blob` is the image file content, received as binary (Uint8Array) through wslink
|
|
48
92
|
response_function: async ({ blob }) => {
|
|
49
|
-
|
|
50
|
-
fileDownload(blob, `${current_filename}.${output_extension.value}`);
|
|
51
|
-
feedbackStore.add_success("Screenshot downloaded");
|
|
52
|
-
} else {
|
|
53
|
-
try {
|
|
54
|
-
const pngBlob = new Blob([blob], { type: "image/png" });
|
|
55
|
-
await copy([new ClipboardItem({ "image/png": pngBlob })]);
|
|
56
|
-
feedbackStore.add_success("Screenshot copied to clipboard");
|
|
57
|
-
} catch (error) {
|
|
58
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
-
feedbackStore.add_error(
|
|
60
|
-
0,
|
|
61
|
-
"",
|
|
62
|
-
"Clipboard Error",
|
|
63
|
-
`Failed to copy screenshot to clipboard: ${message}`,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
93
|
+
await deliverScreenshot(blob, current_filename);
|
|
67
94
|
},
|
|
68
95
|
},
|
|
69
96
|
);
|
|
70
|
-
show.value = false;
|
|
71
97
|
}
|
|
72
98
|
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
// `preferCurrentTab` is not yet part of the TypeScript DOM lib
|
|
100
|
+
interface TabCaptureOptions extends DisplayMediaStreamOptions {
|
|
101
|
+
preferCurrentTab: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Captures the whole browser tab (scene and web UI) through the native screen capture API
|
|
105
|
+
async function captureTab(mime_type: string): Promise<Blob> {
|
|
106
|
+
const options: TabCaptureOptions = { video: true, audio: false, preferCurrentTab: true };
|
|
107
|
+
const stream = await navigator.mediaDevices.getDisplayMedia(options);
|
|
108
|
+
try {
|
|
109
|
+
const video = document.createElement("video");
|
|
110
|
+
video.srcObject = stream;
|
|
111
|
+
video.muted = true;
|
|
112
|
+
await video.play();
|
|
113
|
+
const canvas = document.createElement("canvas");
|
|
114
|
+
canvas.width = video.videoWidth;
|
|
115
|
+
canvas.height = video.videoHeight;
|
|
116
|
+
canvas.getContext("2d")?.drawImage(video, 0, 0);
|
|
117
|
+
// oxlint-disable-next-line promise/avoid-new
|
|
118
|
+
return await new Promise((resolve, reject) => {
|
|
119
|
+
canvas.toBlob((blob) => {
|
|
120
|
+
if (blob) {
|
|
121
|
+
resolve(blob);
|
|
122
|
+
} else {
|
|
123
|
+
reject(new Error("Failed to encode the captured frame"));
|
|
124
|
+
}
|
|
125
|
+
}, mime_type);
|
|
126
|
+
});
|
|
127
|
+
} finally {
|
|
128
|
+
for (const track of stream.getTracks()) {
|
|
129
|
+
track.stop();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function takeUiScreenshot(current_filename: string): Promise<void> {
|
|
135
|
+
const feedbackStore = useFeedbackStore();
|
|
136
|
+
const mime_type = output_extension.value === "jpg" ? "image/jpeg" : "image/png";
|
|
137
|
+
try {
|
|
138
|
+
const blob = await captureTab(mime_type);
|
|
139
|
+
await deliverScreenshot(blob, current_filename);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
142
|
+
feedbackStore.add_error(0, "", "Screenshot Error", `Failed to capture the page: ${message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function takeScreenshot(): Promise<void> {
|
|
147
|
+
const current_filename = screenshot_type.value === "file" ? filename.value : "screenshot";
|
|
148
|
+
is_capturing = true;
|
|
149
|
+
try {
|
|
150
|
+
if (include_ui.value) {
|
|
151
|
+
// Close the panel first so it does not appear on the captured page
|
|
152
|
+
show.value = false;
|
|
153
|
+
await nextTick();
|
|
154
|
+
await takeUiScreenshot(current_filename);
|
|
155
|
+
} else {
|
|
156
|
+
await takeSceneScreenshot(current_filename);
|
|
157
|
+
show.value = false;
|
|
158
|
+
}
|
|
159
|
+
} finally {
|
|
160
|
+
await restoreBackgroundColor();
|
|
161
|
+
is_capturing = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
watch(background_color, async (color) => {
|
|
166
|
+
if (show.value) {
|
|
167
|
+
await applyBackgroundColor(color);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
watch(show, async (value) => {
|
|
172
|
+
if (!value && !is_capturing) {
|
|
173
|
+
await restoreBackgroundColor();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
watch([output_extension, include_ui], ([extension, with_ui]) => {
|
|
178
|
+
if (extension !== "png" || with_ui) {
|
|
75
179
|
include_background.value = true;
|
|
76
180
|
}
|
|
77
181
|
});
|
|
@@ -161,11 +265,22 @@ function handleClose(): void {
|
|
|
161
265
|
</v-row>
|
|
162
266
|
|
|
163
267
|
<v-row dense>
|
|
268
|
+
<v-col cols="12" class="py-1">
|
|
269
|
+
<v-switch
|
|
270
|
+
data-testid="screenshotIncludeUiSwitch"
|
|
271
|
+
v-model="include_ui"
|
|
272
|
+
label="Include UI"
|
|
273
|
+
density="compact"
|
|
274
|
+
hide-details
|
|
275
|
+
inset
|
|
276
|
+
class="text-caption"
|
|
277
|
+
></v-switch>
|
|
278
|
+
</v-col>
|
|
164
279
|
<v-col cols="12" class="py-1">
|
|
165
280
|
<v-switch
|
|
166
281
|
data-testid="screenshotIncludeBackgroundSwitch"
|
|
167
282
|
v-model="include_background"
|
|
168
|
-
:disabled="screenshot_type === 'file' && output_extension !== 'png'"
|
|
283
|
+
:disabled="(screenshot_type === 'file' && output_extension !== 'png') || include_ui"
|
|
169
284
|
label="Include background"
|
|
170
285
|
density="compact"
|
|
171
286
|
hide-details
|
|
@@ -173,6 +288,14 @@ function handleClose(): void {
|
|
|
173
288
|
class="text-caption"
|
|
174
289
|
></v-switch>
|
|
175
290
|
</v-col>
|
|
291
|
+
<v-col v-if="include_background" cols="12" class="py-1">
|
|
292
|
+
<span class="text-caption">Background color</span>
|
|
293
|
+
<ViewerOptionsColorPicker
|
|
294
|
+
data-testid="screenshotBackgroundColorPicker"
|
|
295
|
+
v-model="background_color"
|
|
296
|
+
disabled-alpha
|
|
297
|
+
/>
|
|
298
|
+
</v-col>
|
|
176
299
|
</v-row>
|
|
177
300
|
</v-container>
|
|
178
301
|
|
|
@@ -18,13 +18,19 @@ const dataStyleStore = useDataStyleStore();
|
|
|
18
18
|
const hybridViewerStore = useHybridViewerStore();
|
|
19
19
|
const { applyBatchStyle } = useBatchStyle();
|
|
20
20
|
|
|
21
|
+
interface CapabilityConfig {
|
|
22
|
+
available?: boolean;
|
|
23
|
+
hasColorMap?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
interface Props {
|
|
22
27
|
itemProps: ItemProps & { index?: number };
|
|
23
28
|
btnImage: string;
|
|
24
29
|
tooltip?: string;
|
|
30
|
+
capabilities?: Record<string, CapabilityConfig>;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
const { itemProps, btnImage, tooltip = "Edges options" } = defineProps<Props>();
|
|
33
|
+
const { itemProps, btnImage, tooltip = "Edges options", capabilities = {} } = defineProps<Props>();
|
|
28
34
|
|
|
29
35
|
const id = toRef(() => itemProps.id);
|
|
30
36
|
|
|
@@ -251,6 +257,7 @@ async function setEdgeAttributeRangesPerData(ranges: RangesPerData): Promise<voi
|
|
|
251
257
|
v-model:edge_attribute_color_map="edge_attribute_color_map"
|
|
252
258
|
v-model:edge_attribute_no_data_color="edge_attribute_no_data_color"
|
|
253
259
|
v-model:edge_attribute_time_step="edge_attribute_time_step"
|
|
260
|
+
:capabilities="capabilities"
|
|
254
261
|
/>
|
|
255
262
|
</template>
|
|
256
263
|
</template>
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
|
|
3
|
+
import ViewerOptionsColoringTypeSelector from "@ogw_front/components/Viewer/Options/ColoringTypeSelector.vue";
|
|
4
|
+
import ViewerOptionsWidthSlider from "@ogw_front/components/Viewer/Options/Sliders/Width.vue";
|
|
5
|
+
import VisibilitySwitch from "@ogw_front/components/Viewer/Options/VisibilitySwitch.vue";
|
|
6
|
+
import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
|
|
7
|
+
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
8
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
9
|
+
|
|
10
|
+
interface EdgesTarget {
|
|
11
|
+
componentType: "Surface" | "Block";
|
|
12
|
+
componentIds: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
modelId: string;
|
|
17
|
+
targets: EdgesTarget[];
|
|
18
|
+
referenceComponentId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { modelId, targets, referenceComponentId = undefined } = defineProps<Props>();
|
|
22
|
+
|
|
23
|
+
const dataStyleStore = useDataStyleStore();
|
|
24
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
25
|
+
|
|
26
|
+
// Each component type exposes its own edges functions in the data style store
|
|
27
|
+
const EDGES_STYLES = {
|
|
28
|
+
Surface: {
|
|
29
|
+
visibility: dataStyleStore.modelSurfaceEdgesVisibility,
|
|
30
|
+
width: dataStyleStore.modelSurfaceEdgesWidth,
|
|
31
|
+
color: dataStyleStore.modelSurfaceEdgesColor,
|
|
32
|
+
activeColoring: dataStyleStore.modelSurfaceEdgesActiveColoring,
|
|
33
|
+
vertexAttributeName: dataStyleStore.modelSurfaceEdgesVertexAttributeName,
|
|
34
|
+
vertexAttributeItem: dataStyleStore.modelSurfaceEdgesVertexAttributeItem,
|
|
35
|
+
vertexAttributeRange: dataStyleStore.modelSurfaceEdgesVertexAttributeRange,
|
|
36
|
+
vertexAttributeColorMap: dataStyleStore.modelSurfaceEdgesVertexAttributeColorMap,
|
|
37
|
+
vertexAttributeNoDataColor: dataStyleStore.modelSurfaceEdgesVertexAttributeNoDataColor,
|
|
38
|
+
setVisibility: dataStyleStore.setModelSurfacesEdgesVisibility,
|
|
39
|
+
setWidth: dataStyleStore.setModelSurfacesEdgesWidth,
|
|
40
|
+
setColor: dataStyleStore.setModelSurfacesEdgesColor,
|
|
41
|
+
setActiveColoring: dataStyleStore.setModelSurfacesEdgesActiveColoring,
|
|
42
|
+
setVertexAttributeName: dataStyleStore.setModelSurfacesEdgesVertexAttributeName,
|
|
43
|
+
setVertexAttributeItem: dataStyleStore.setModelSurfacesEdgesVertexAttributeItem,
|
|
44
|
+
setVertexAttributeRange: dataStyleStore.setModelSurfacesEdgesVertexAttributeRange,
|
|
45
|
+
setVertexAttributeColorMap: dataStyleStore.setModelSurfacesEdgesVertexAttributeColorMap,
|
|
46
|
+
setVertexAttributeNoDataColor: dataStyleStore.setModelSurfacesEdgesVertexAttributeNoDataColor,
|
|
47
|
+
},
|
|
48
|
+
Block: {
|
|
49
|
+
visibility: dataStyleStore.modelBlockEdgesVisibility,
|
|
50
|
+
width: dataStyleStore.modelBlockEdgesWidth,
|
|
51
|
+
color: dataStyleStore.modelBlockEdgesColor,
|
|
52
|
+
activeColoring: dataStyleStore.modelBlockEdgesActiveColoring,
|
|
53
|
+
vertexAttributeName: dataStyleStore.modelBlockEdgesVertexAttributeName,
|
|
54
|
+
vertexAttributeItem: dataStyleStore.modelBlockEdgesVertexAttributeItem,
|
|
55
|
+
vertexAttributeRange: dataStyleStore.modelBlockEdgesVertexAttributeRange,
|
|
56
|
+
vertexAttributeColorMap: dataStyleStore.modelBlockEdgesVertexAttributeColorMap,
|
|
57
|
+
vertexAttributeNoDataColor: dataStyleStore.modelBlockEdgesVertexAttributeNoDataColor,
|
|
58
|
+
setVisibility: dataStyleStore.setModelBlocksEdgesVisibility,
|
|
59
|
+
setWidth: dataStyleStore.setModelBlocksEdgesWidth,
|
|
60
|
+
setColor: dataStyleStore.setModelBlocksEdgesColor,
|
|
61
|
+
setActiveColoring: dataStyleStore.setModelBlocksEdgesActiveColoring,
|
|
62
|
+
setVertexAttributeName: dataStyleStore.setModelBlocksEdgesVertexAttributeName,
|
|
63
|
+
setVertexAttributeItem: dataStyleStore.setModelBlocksEdgesVertexAttributeItem,
|
|
64
|
+
setVertexAttributeRange: dataStyleStore.setModelBlocksEdgesVertexAttributeRange,
|
|
65
|
+
setVertexAttributeColorMap: dataStyleStore.setModelBlocksEdgesVertexAttributeColorMap,
|
|
66
|
+
setVertexAttributeNoDataColor: dataStyleStore.setModelBlocksEdgesVertexAttributeNoDataColor,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
type EdgesStyle = (typeof EDGES_STYLES)[EdgesTarget["componentType"]];
|
|
70
|
+
|
|
71
|
+
// Values are read from the first target, changes are applied to every target
|
|
72
|
+
const edgesStyle = computed<EdgesStyle | undefined>(() => {
|
|
73
|
+
const [reference] = targets;
|
|
74
|
+
return reference === undefined ? undefined : EDGES_STYLES[reference.componentType];
|
|
75
|
+
});
|
|
76
|
+
const allComponentIds = computed<string[]>(() => targets.flatMap((target) => target.componentIds));
|
|
77
|
+
|
|
78
|
+
async function applyEdgesStyle(
|
|
79
|
+
setter: (style: EdgesStyle, componentIds: string[]) => Promise<unknown>,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
await Promise.all(
|
|
82
|
+
targets.map(async (target) => {
|
|
83
|
+
await setter(EDGES_STYLES[target.componentType], target.componentIds);
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
hybridViewerStore.remoteRender();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const visibility = computed<boolean | undefined>({
|
|
90
|
+
get: () => edgesStyle.value?.visibility(modelId, referenceComponentId),
|
|
91
|
+
set: async (newValue) => {
|
|
92
|
+
if (newValue === undefined) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await applyEdgesStyle((style, ids) => style.setVisibility(modelId, ids, newValue));
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const width = computed<number | undefined>({
|
|
100
|
+
get: () => edgesStyle.value?.width(modelId, referenceComponentId),
|
|
101
|
+
set: async (newValue) => {
|
|
102
|
+
if (newValue === undefined) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await applyEdgesStyle((style, ids) => style.setWidth(modelId, ids, newValue));
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const activeColoring = computed<string | undefined>({
|
|
110
|
+
get: () => edgesStyle.value?.activeColoring(modelId, referenceComponentId),
|
|
111
|
+
set: async (newValue) => {
|
|
112
|
+
if (newValue === undefined) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await applyEdgesStyle((style, ids) => style.setActiveColoring(modelId, ids, newValue));
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const color = computed<RGBAColor | undefined>({
|
|
120
|
+
get: () => edgesStyle.value?.color(modelId, referenceComponentId),
|
|
121
|
+
set: async (newValue) => {
|
|
122
|
+
if (newValue === undefined) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await applyEdgesStyle((style, ids) => style.setColor(modelId, ids, newValue));
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const vertexAttributeName = computed<string | undefined>({
|
|
130
|
+
get: () => edgesStyle.value?.vertexAttributeName(modelId, referenceComponentId),
|
|
131
|
+
set: async (newValue) => {
|
|
132
|
+
if (newValue === undefined) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await applyEdgesStyle((style, ids) => style.setVertexAttributeName(modelId, ids, newValue));
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const vertexAttributeItem = computed<number | undefined>({
|
|
140
|
+
get: () => edgesStyle.value?.vertexAttributeItem(modelId, referenceComponentId),
|
|
141
|
+
set: async (newValue) => {
|
|
142
|
+
if (newValue === undefined) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
await applyEdgesStyle((style, ids) => style.setVertexAttributeItem(modelId, ids, newValue));
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const vertexAttributeRange = computed<(number | undefined)[] | undefined>({
|
|
150
|
+
get: () => edgesStyle.value?.vertexAttributeRange(modelId, referenceComponentId),
|
|
151
|
+
set: async (newValue) => {
|
|
152
|
+
if (newValue === undefined) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const [minimum, maximum] = newValue;
|
|
156
|
+
if (minimum === undefined || maximum === undefined) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await applyEdgesStyle((style, ids) =>
|
|
160
|
+
style.setVertexAttributeRange(modelId, ids, minimum, maximum),
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const vertexAttributeColorMap = computed<string | undefined>({
|
|
166
|
+
get: () => edgesStyle.value?.vertexAttributeColorMap(modelId, referenceComponentId),
|
|
167
|
+
set: async (newValue) => {
|
|
168
|
+
await applyEdgesStyle((style, ids) => style.setVertexAttributeColorMap(modelId, ids, newValue));
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const vertexAttributeNoDataColor = computed<RGBAColor | undefined>({
|
|
173
|
+
get: () => edgesStyle.value?.vertexAttributeNoDataColor(modelId, referenceComponentId),
|
|
174
|
+
set: async (newValue) => {
|
|
175
|
+
if (newValue === undefined) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
await applyEdgesStyle((style, ids) =>
|
|
179
|
+
style.setVertexAttributeNoDataColor(modelId, ids, newValue),
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const capabilities = {
|
|
185
|
+
color: { available: true },
|
|
186
|
+
textures: { available: false },
|
|
187
|
+
vertex: { available: true },
|
|
188
|
+
edge: { available: false },
|
|
189
|
+
cell: { available: false },
|
|
190
|
+
polygon: { available: false },
|
|
191
|
+
polyhedron: { available: false },
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const backSchemas = back_schemas.opengeodeweb_back;
|
|
195
|
+
const attributeSchemas = {
|
|
196
|
+
vertex: {
|
|
197
|
+
names: backSchemas.model_component_vertex_attribute_names,
|
|
198
|
+
range: backSchemas.model_component_vertex_attribute_range,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<template>
|
|
204
|
+
<div class="mt-2" data-testid="modelComponentEdgesOptions">
|
|
205
|
+
<VisibilitySwitch data-testid="modelComponentEdgesVisibilitySwitch" v-model="visibility" />
|
|
206
|
+
<template v-if="visibility">
|
|
207
|
+
<ViewerOptionsWidthSlider data-testid="modelComponentEdgesWidthSlider" v-model="width" />
|
|
208
|
+
<ViewerOptionsColoringTypeSelector
|
|
209
|
+
:id="modelId"
|
|
210
|
+
:componentIds="allComponentIds"
|
|
211
|
+
v-model:coloring_style_key="activeColoring"
|
|
212
|
+
v-model:color="color"
|
|
213
|
+
v-model:vertex_attribute_name="vertexAttributeName"
|
|
214
|
+
v-model:vertex_attribute_item="vertexAttributeItem"
|
|
215
|
+
v-model:vertex_attribute_range="vertexAttributeRange"
|
|
216
|
+
v-model:vertex_attribute_color_map="vertexAttributeColorMap"
|
|
217
|
+
v-model:vertex_attribute_no_data_color="vertexAttributeNoDataColor"
|
|
218
|
+
:capabilities="capabilities"
|
|
219
|
+
:schemas="attributeSchemas"
|
|
220
|
+
/>
|
|
221
|
+
</template>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|
|
@@ -2,36 +2,14 @@
|
|
|
2
2
|
import ViewerContextMenuItem, {
|
|
3
3
|
type ItemProps,
|
|
4
4
|
} from "@ogw_front/components/Viewer/ContextMenu/ContextMenuItem.vue";
|
|
5
|
+
import ModelEdgesCard from "./ModelEdgesCard.vue";
|
|
5
6
|
import SurfaceEdges from "@ogw_front/assets/viewer_svgs/surface_edges.svg";
|
|
6
|
-
import ViewerOptionsVisibilitySwitch from "@ogw_front/components/Viewer/Options/VisibilitySwitch.vue";
|
|
7
|
-
import { useBatchStyle } from "@ogw_front/composables/batch_style";
|
|
8
|
-
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
9
|
-
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
10
|
-
|
|
11
|
-
const dataStyleStore = useDataStyleStore();
|
|
12
|
-
const hybridViewerStore = useHybridViewerStore();
|
|
13
|
-
const { applyBatchStyle } = useBatchStyle();
|
|
14
7
|
|
|
15
8
|
interface Props {
|
|
16
9
|
itemProps: ItemProps & { index: number };
|
|
17
10
|
}
|
|
18
11
|
|
|
19
12
|
const { itemProps } = defineProps<Props>();
|
|
20
|
-
|
|
21
|
-
const id = computed(() => (itemProps.meta_data.modelId as string | undefined) || itemProps.id);
|
|
22
|
-
|
|
23
|
-
const visibility = computed({
|
|
24
|
-
get: () => dataStyleStore.modelEdgesVisibility(id.value),
|
|
25
|
-
set: async (newValue) => {
|
|
26
|
-
if (newValue === undefined) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
await applyBatchStyle(id.value, (targetId: string) =>
|
|
30
|
-
dataStyleStore.setModelEdgesVisibility(targetId, newValue),
|
|
31
|
-
);
|
|
32
|
-
hybridViewerStore.remoteRender();
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
13
|
</script>
|
|
36
14
|
|
|
37
15
|
<template>
|
|
@@ -43,10 +21,7 @@ const visibility = computed({
|
|
|
43
21
|
:btnImage="SurfaceEdges"
|
|
44
22
|
>
|
|
45
23
|
<template #options>
|
|
46
|
-
<
|
|
47
|
-
data-testid="modelEdgesVisibilitySwitch"
|
|
48
|
-
v-model="visibility"
|
|
49
|
-
/>
|
|
24
|
+
<ModelEdgesCard :itemProps="itemProps" />
|
|
50
25
|
</template>
|
|
51
26
|
</ViewerContextMenuItem>
|
|
52
27
|
</template>
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
type ModelMenuItemProps,
|
|
4
|
+
useModelMenuContext,
|
|
5
|
+
} from "@ogw_front/composables/model_menu_context";
|
|
6
|
+
import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
|
|
7
|
+
import ViewerGenericModelComponentEdgesOptions from "@ogw_front/components/Viewer/Generic/Model/ComponentEdgesOptions.vue";
|
|
8
|
+
import ViewerOptionsWidthSlider from "@ogw_front/components/Viewer/Options/Sliders/Width.vue";
|
|
9
|
+
import VisibilitySwitch from "@ogw_front/components/Viewer/Options/VisibilitySwitch.vue";
|
|
10
|
+
import { useBatchStyle } from "@ogw_front/composables/batch_style";
|
|
11
|
+
import { useDataStore } from "@ogw_front/stores/data";
|
|
12
|
+
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
13
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
14
|
+
|
|
15
|
+
// Lines are edges themselves and corners have none: no edges options for them
|
|
16
|
+
const EDGES_TYPE_TITLES = {
|
|
17
|
+
Surface: "Surfaces Edges Options",
|
|
18
|
+
Block: "Blocks Edges Options",
|
|
19
|
+
} as const;
|
|
20
|
+
type EdgesComponentType = keyof typeof EDGES_TYPE_TITLES;
|
|
21
|
+
const EDGES_COMPONENT_TYPES: EdgesComponentType[] = ["Surface", "Block"];
|
|
22
|
+
|
|
23
|
+
function isEdgesComponentType(type: string | undefined): type is EdgesComponentType {
|
|
24
|
+
return type !== undefined && type in EDGES_TYPE_TITLES;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
itemProps: ModelMenuItemProps;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { itemProps } = defineProps<Props>();
|
|
32
|
+
|
|
33
|
+
const dataStore = useDataStore();
|
|
34
|
+
const dataStyleStore = useDataStyleStore();
|
|
35
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
36
|
+
const { applyBatchStyle } = useBatchStyle();
|
|
37
|
+
|
|
38
|
+
const { modelId, componentId, componentType, collections, targetComponentIds } =
|
|
39
|
+
useModelMenuContext(() => itemProps);
|
|
40
|
+
|
|
41
|
+
const modelEdgesVisibility = computed<boolean | undefined>({
|
|
42
|
+
get: () => dataStyleStore.modelEdgesVisibility(modelId.value),
|
|
43
|
+
set: async (newValue) => {
|
|
44
|
+
if (newValue === undefined) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await applyBatchStyle(modelId.value, (targetId: string) =>
|
|
48
|
+
dataStyleStore.setModelEdgesVisibility(targetId, newValue),
|
|
49
|
+
);
|
|
50
|
+
hybridViewerStore.remoteRender();
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const modelEdgesWidth = computed<number | undefined>({
|
|
55
|
+
get: () => dataStyleStore.modelEdgesWidth(modelId.value),
|
|
56
|
+
set: async (newValue) => {
|
|
57
|
+
if (newValue === undefined) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await applyBatchStyle(modelId.value, (targetId: string) =>
|
|
61
|
+
dataStyleStore.setModelEdgesWidth(targetId, newValue),
|
|
62
|
+
);
|
|
63
|
+
hybridViewerStore.remoteRender();
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const edgesComponentType = computed<EdgesComponentType | undefined>(() =>
|
|
68
|
+
isEdgesComponentType(componentType.value) ? componentType.value : undefined,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const allComponentsTargets = ref<{ componentType: EdgesComponentType; componentIds: string[] }[]>(
|
|
72
|
+
[],
|
|
73
|
+
);
|
|
74
|
+
watch(
|
|
75
|
+
modelId,
|
|
76
|
+
async (currentModelId) => {
|
|
77
|
+
const targets = await Promise.all(
|
|
78
|
+
EDGES_COMPONENT_TYPES.map(async (type) => ({
|
|
79
|
+
componentType: type,
|
|
80
|
+
componentIds: await dataStore.getMeshComponentGeodeIds(currentModelId, type),
|
|
81
|
+
})),
|
|
82
|
+
);
|
|
83
|
+
if (modelId.value === currentModelId) {
|
|
84
|
+
allComponentsTargets.value = targets.filter((target) => target.componentIds.length > 0);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{ immediate: true },
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const typeTargets = computed(() =>
|
|
91
|
+
edgesComponentType.value === undefined
|
|
92
|
+
? []
|
|
93
|
+
: [{ componentType: edgesComponentType.value, componentIds: targetComponentIds.value }],
|
|
94
|
+
);
|
|
95
|
+
const typeReferenceId = computed<string | undefined>(() =>
|
|
96
|
+
collections.value ? targetComponentIds.value[0] : undefined,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const componentTargets = computed(() =>
|
|
100
|
+
edgesComponentType.value === undefined || componentId.value === undefined
|
|
101
|
+
? []
|
|
102
|
+
: [{ componentType: edgesComponentType.value, componentIds: [componentId.value] }],
|
|
103
|
+
);
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<v-sheet class="model-edges-card" color="transparent">
|
|
108
|
+
<OptionsSection title="Model Edges Options">
|
|
109
|
+
<VisibilitySwitch data-testid="modelEdgesVisibilitySwitch" v-model="modelEdgesVisibility" />
|
|
110
|
+
<ViewerOptionsWidthSlider
|
|
111
|
+
v-if="modelEdgesVisibility"
|
|
112
|
+
data-testid="modelEdgesWidthSlider"
|
|
113
|
+
v-model="modelEdgesWidth"
|
|
114
|
+
/>
|
|
115
|
+
</OptionsSection>
|
|
116
|
+
|
|
117
|
+
<OptionsSection
|
|
118
|
+
v-if="!componentType && !componentId && allComponentsTargets.length > 0"
|
|
119
|
+
title="Components Edges Options"
|
|
120
|
+
data-testid="modelEdgesComponentsOptions"
|
|
121
|
+
class="mt-4"
|
|
122
|
+
>
|
|
123
|
+
<ViewerGenericModelComponentEdgesOptions :modelId="modelId" :targets="allComponentsTargets" />
|
|
124
|
+
</OptionsSection>
|
|
125
|
+
|
|
126
|
+
<OptionsSection
|
|
127
|
+
v-if="edgesComponentType"
|
|
128
|
+
:title="EDGES_TYPE_TITLES[edgesComponentType]"
|
|
129
|
+
data-testid="modelEdgesComponentTypeOptions"
|
|
130
|
+
class="mt-4"
|
|
131
|
+
>
|
|
132
|
+
<ViewerGenericModelComponentEdgesOptions
|
|
133
|
+
:modelId="modelId"
|
|
134
|
+
:targets="typeTargets"
|
|
135
|
+
:referenceComponentId="typeReferenceId"
|
|
136
|
+
/>
|
|
137
|
+
</OptionsSection>
|
|
138
|
+
|
|
139
|
+
<OptionsSection
|
|
140
|
+
v-if="edgesComponentType && componentId"
|
|
141
|
+
title="Component Edges Options"
|
|
142
|
+
data-testid="modelEdgesComponentOptions"
|
|
143
|
+
class="mt-4"
|
|
144
|
+
>
|
|
145
|
+
<ViewerGenericModelComponentEdgesOptions
|
|
146
|
+
:modelId="modelId"
|
|
147
|
+
:targets="componentTargets"
|
|
148
|
+
:referenceComponentId="componentId"
|
|
149
|
+
/>
|
|
150
|
+
</OptionsSection>
|
|
151
|
+
</v-sheet>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<style scoped>
|
|
155
|
+
.model-edges-card {
|
|
156
|
+
padding-top: 12px;
|
|
157
|
+
overflow-x: hidden;
|
|
158
|
+
}
|
|
159
|
+
</style>
|