@geode/opengeodeweb-front 10.40.1-rc.1 → 10.41.0-rc.2
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/.build/shared/utils/file.js +1 -1
- package/.build/shared/utils/response_handlers/load.js +19 -1
- package/app/components/MissingFilesSelector.vue +84 -11
- package/app/components/ObjectSelector.vue +147 -13
- package/app/components/Viewer/Generic/Mesh/PointsOptions.vue +0 -1
- package/app/components/Viewer/Generic/Model/ComponentPointsOptions.vue +245 -0
- package/app/components/Viewer/Generic/Model/ModelPointsCard.vue +141 -0
- package/app/components/Viewer/Generic/Model/ModelStyleCard.vue +7 -67
- package/app/components/Viewer/Generic/Model/PointsOptions.vue +2 -40
- package/app/components/csv-preview/CsvSettings.vue +1 -1
- package/app/composables/model_menu_context.ts +101 -0
- package/app/utils/import_workflow.ts +13 -2
- package/internal/stores/data_style/model/blocks/index.ts +4 -0
- package/internal/stores/data_style/model/blocks/points.ts +51 -0
- package/internal/stores/data_style/model/index.ts +6 -0
- package/internal/stores/data_style/model/lines/index.ts +4 -0
- package/internal/stores/data_style/model/lines/points.ts +51 -0
- package/internal/stores/data_style/model/points/component.ts +423 -0
- package/internal/stores/data_style/model/points/size.ts +13 -1
- package/internal/stores/data_style/model/points/visibility.ts +13 -3
- package/internal/stores/data_style/model/surfaces/index.ts +4 -0
- package/internal/stores/data_style/model/surfaces/points.ts +51 -0
- package/internal/utils/upload_file.ts +1 -1
- package/package.json +1 -1
- package/shared/utils/file.ts +1 -1
- package/shared/utils/response_handlers/load.ts +37 -1
|
@@ -5,7 +5,7 @@ const BYTES_PER_KIBIBYTE = 1024;
|
|
|
5
5
|
const CHUNK_SIZE_BYTES = 8192 * BYTES_PER_KIBIBYTE;
|
|
6
6
|
function assertFile(file) {
|
|
7
7
|
consola.debug("[ASSERT_FILE] Asserting file", { file });
|
|
8
|
-
if (!(file instanceof File)) throw new
|
|
8
|
+
if (!(file instanceof File)) throw new TypeError("file must be an instance of File");
|
|
9
9
|
}
|
|
10
10
|
function hasBody(params) {
|
|
11
11
|
if (params instanceof FormData || params instanceof Blob) return true;
|
|
@@ -46,5 +46,23 @@ function resolveAllowedObjects(filenames, allowedObjectsList) {
|
|
|
46
46
|
selectedGeodeObject: selectGeodeObject(mergedAllowedObjects)
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
function sortCandidates(objectMap) {
|
|
50
|
+
return Object.keys(objectMap).filter((key) => getObject(objectMap, key).is_loadable > 0).toSorted((keyA, keyB) => {
|
|
51
|
+
const objectA = getObject(objectMap, keyA);
|
|
52
|
+
const objectB = getObject(objectMap, keyB);
|
|
53
|
+
if (objectA.is_loadable !== objectB.is_loadable) return objectB.is_loadable - objectA.is_loadable;
|
|
54
|
+
return (objectB.object_priority ?? -Infinity) - (objectA.object_priority ?? -Infinity);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function resolveAllowedObjectsPerFile(filenames, allowedObjectsList) {
|
|
58
|
+
return filenames.map((filename, index) => {
|
|
59
|
+
const objectMap = allowedObjectsList[index] ?? {};
|
|
60
|
+
return {
|
|
61
|
+
filename,
|
|
62
|
+
candidates: sortCandidates(objectMap),
|
|
63
|
+
selectedGeodeObject: selectGeodeObject(objectMap)
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
49
67
|
//#endregion
|
|
50
|
-
export { getFileExtension, resolveAllowedObjects };
|
|
68
|
+
export { getFileExtension, resolveAllowedObjects, resolveAllowedObjectsPerFile };
|
|
@@ -15,12 +15,17 @@ interface FilePlan {
|
|
|
15
15
|
mandatory_files: string[];
|
|
16
16
|
additional_files: string[];
|
|
17
17
|
}
|
|
18
|
+
interface FileMissingFiles {
|
|
19
|
+
filename: string;
|
|
20
|
+
mandatory_files: string[];
|
|
21
|
+
additional_files: string[];
|
|
22
|
+
}
|
|
18
23
|
|
|
19
24
|
const schema = schemas.opengeodeweb_back.data_missing_files;
|
|
20
25
|
const time_series_schema = schemas.opengeodeweb_back.time_series_missing_files;
|
|
21
26
|
|
|
22
27
|
interface Emits {
|
|
23
|
-
update_values: [value: { additional_files
|
|
28
|
+
update_values: [value: { additional_files?: UploadFile[]; skipped_files?: string[] }];
|
|
24
29
|
increment_step: [];
|
|
25
30
|
decrement_step: [];
|
|
26
31
|
}
|
|
@@ -29,7 +34,8 @@ const emit = defineEmits<Emits>();
|
|
|
29
34
|
|
|
30
35
|
interface Props {
|
|
31
36
|
multiple: boolean;
|
|
32
|
-
|
|
37
|
+
// Filename -> geode object type, each file is checked with its own type (unused for time series)
|
|
38
|
+
geodeObjectTypes?: Record<string, string>;
|
|
33
39
|
filenames: string[];
|
|
34
40
|
files?: UploadFile[];
|
|
35
41
|
timeSeries?: boolean;
|
|
@@ -37,7 +43,7 @@ interface Props {
|
|
|
37
43
|
|
|
38
44
|
const {
|
|
39
45
|
multiple,
|
|
40
|
-
|
|
46
|
+
geodeObjectTypes = {},
|
|
41
47
|
filenames,
|
|
42
48
|
files = [],
|
|
43
49
|
timeSeries = false,
|
|
@@ -48,11 +54,36 @@ const loading = ref<boolean>(false);
|
|
|
48
54
|
const has_missing_files = ref<boolean>(false);
|
|
49
55
|
const mandatory_files = ref<string[]>([]);
|
|
50
56
|
const additional_files = ref<string[]>([]);
|
|
57
|
+
const missing_by_file = ref<FileMissingFiles[]>([]);
|
|
58
|
+
// Files left out at the data type step are not checked
|
|
59
|
+
const checked_filenames = computed(() =>
|
|
60
|
+
timeSeries ? filenames : filenames.filter((filename) => filename in geodeObjectTypes),
|
|
61
|
+
);
|
|
62
|
+
// Files whose mandatory files are missing are skipped when the step is skipped
|
|
63
|
+
const files_without_mandatory = computed(() =>
|
|
64
|
+
checked_filenames.value.filter(
|
|
65
|
+
(filename) =>
|
|
66
|
+
!missing_by_file.value.some(
|
|
67
|
+
(missing) => missing.filename === filename && missing.mandatory_files.length > 0,
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
const can_skip = computed(
|
|
72
|
+
() => missing_by_file.value.length > 0 && files_without_mandatory.value.length > 0,
|
|
73
|
+
);
|
|
51
74
|
const toggle_loading = useToggle(loading);
|
|
52
75
|
const uploading = ref<boolean>(false);
|
|
53
76
|
|
|
54
77
|
function files_uploaded_event(value: UploadFile[]): void {
|
|
55
|
-
emit("update_values", { additional_files: value });
|
|
78
|
+
emit("update_values", { additional_files: value, skipped_files: [] });
|
|
79
|
+
emit("increment_step");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function skip_step(): void {
|
|
83
|
+
const skipped_files = checked_filenames.value.filter(
|
|
84
|
+
(filename) => !files_without_mandatory.value.includes(filename),
|
|
85
|
+
);
|
|
86
|
+
emit("update_values", { skipped_files });
|
|
56
87
|
emit("increment_step");
|
|
57
88
|
}
|
|
58
89
|
|
|
@@ -65,9 +96,10 @@ async function missing_files(): Promise<void> {
|
|
|
65
96
|
has_missing_files.value = false;
|
|
66
97
|
mandatory_files.value = [];
|
|
67
98
|
additional_files.value = [];
|
|
99
|
+
missing_by_file.value = [];
|
|
68
100
|
const backStore = useBackStore();
|
|
69
101
|
|
|
70
|
-
const promise_array
|
|
102
|
+
const promise_array = checked_filenames.value.map((filename): Promise<FilePlan> => {
|
|
71
103
|
if (isCsvFile(filename)) {
|
|
72
104
|
return Promise.resolve({
|
|
73
105
|
has_missing_files: false,
|
|
@@ -78,13 +110,18 @@ async function missing_files(): Promise<void> {
|
|
|
78
110
|
if (timeSeries) {
|
|
79
111
|
return backStore.request({ schema: time_series_schema, params: { filename } });
|
|
80
112
|
}
|
|
81
|
-
const params = { geode_object_type:
|
|
113
|
+
const params = { geode_object_type: geodeObjectTypes[filename] ?? "", filename };
|
|
82
114
|
return backStore.request({ schema, params });
|
|
83
115
|
});
|
|
84
116
|
const values = await Promise.all(promise_array);
|
|
85
|
-
for (const value of values) {
|
|
117
|
+
for (const [index, value] of values.entries()) {
|
|
86
118
|
if (value.has_missing_files) {
|
|
87
119
|
has_missing_files.value = true;
|
|
120
|
+
missing_by_file.value.push({
|
|
121
|
+
filename: checked_filenames.value[index] ?? "",
|
|
122
|
+
mandatory_files: value.mandatory_files,
|
|
123
|
+
additional_files: value.additional_files,
|
|
124
|
+
});
|
|
88
125
|
}
|
|
89
126
|
mandatory_files.value = [...mandatory_files.value, ...value.mandatory_files];
|
|
90
127
|
additional_files.value = [...additional_files.value, ...value.additional_files];
|
|
@@ -98,6 +135,7 @@ async function missing_files(): Promise<void> {
|
|
|
98
135
|
}
|
|
99
136
|
|
|
100
137
|
if (!has_missing_files.value) {
|
|
138
|
+
emit("update_values", { skipped_files: [] });
|
|
101
139
|
emit("increment_step");
|
|
102
140
|
}
|
|
103
141
|
toggle_loading();
|
|
@@ -159,7 +197,40 @@ await missing_files();
|
|
|
159
197
|
<template>
|
|
160
198
|
<FetchingData v-if="loading || uploading" />
|
|
161
199
|
<v-container v-else-if="has_missing_files">
|
|
162
|
-
<
|
|
200
|
+
<template v-if="checked_filenames.length > 1">
|
|
201
|
+
<div v-for="missing in missing_by_file" :key="missing.filename" class="mb-2">
|
|
202
|
+
<v-row align="center">
|
|
203
|
+
<p class="pa-1 font-weight-bold">{{ missing.filename }}</p>
|
|
204
|
+
<v-chip
|
|
205
|
+
v-if="missing.mandatory_files.length && files_without_mandatory.length"
|
|
206
|
+
class="ma-1"
|
|
207
|
+
color="warning"
|
|
208
|
+
size="small"
|
|
209
|
+
>
|
|
210
|
+
Skipped if mandatory files are not added
|
|
211
|
+
</v-chip>
|
|
212
|
+
</v-row>
|
|
213
|
+
<v-row v-if="missing.mandatory_files.length" align="center">
|
|
214
|
+
<v-col cols="auto" class="pa-0">
|
|
215
|
+
<v-icon color="warning" icon="mdi-file-document-alert-outline" />
|
|
216
|
+
</v-col>
|
|
217
|
+
<p class="pa-1">Mandatory files:</p>
|
|
218
|
+
<v-col v-for="mandatory_file in missing.mandatory_files" cols="auto" class="pa-0">
|
|
219
|
+
<v-chip>{{ mandatory_file }}</v-chip>
|
|
220
|
+
</v-col>
|
|
221
|
+
</v-row>
|
|
222
|
+
<v-row v-if="missing.additional_files.length" align="center">
|
|
223
|
+
<v-col cols="auto" class="pa-0">
|
|
224
|
+
<v-icon color="accent" icon="mdi-file-document-plus-outline" />
|
|
225
|
+
</v-col>
|
|
226
|
+
<p class="pa-1">Additional files:</p>
|
|
227
|
+
<v-col v-for="additional_file in missing.additional_files" cols="auto" class="pa-0">
|
|
228
|
+
<v-chip>{{ additional_file }}</v-chip>
|
|
229
|
+
</v-col>
|
|
230
|
+
</v-row>
|
|
231
|
+
</div>
|
|
232
|
+
</template>
|
|
233
|
+
<v-row v-else-if="mandatory_files.length" align="center">
|
|
163
234
|
<v-col cols="auto" class="pa-0">
|
|
164
235
|
<v-icon color="warning" icon="mdi-file-document-alert-outline" />
|
|
165
236
|
</v-col>
|
|
@@ -168,7 +239,7 @@ await missing_files();
|
|
|
168
239
|
<v-chip>{{ mandatory_file }}</v-chip>
|
|
169
240
|
</v-col>
|
|
170
241
|
</v-row>
|
|
171
|
-
<v-row v-if="additional_files.length" align="center">
|
|
242
|
+
<v-row v-if="!(checked_filenames.length > 1) && additional_files.length" align="center">
|
|
172
243
|
<v-col cols="auto" class="pa-0">
|
|
173
244
|
<v-icon color="accent" icon="mdi-file-document-plus-outline" />
|
|
174
245
|
</v-col>
|
|
@@ -194,8 +265,10 @@ await missing_files();
|
|
|
194
265
|
</v-col>
|
|
195
266
|
</v-row>
|
|
196
267
|
<v-row>
|
|
197
|
-
<v-col v-if="
|
|
198
|
-
<v-btn @click="
|
|
268
|
+
<v-col v-if="can_skip" cols="auto">
|
|
269
|
+
<v-btn @click="skip_step" color="warning" data-testid="skipMissingFilesButton">
|
|
270
|
+
Skip step
|
|
271
|
+
</v-btn>
|
|
199
272
|
</v-col>
|
|
200
273
|
</v-row>
|
|
201
274
|
</v-container>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
type FileAllowedObjects,
|
|
4
|
+
resolveAllowedObjects,
|
|
5
|
+
resolveAllowedObjectsPerFile,
|
|
6
|
+
} from "@ogw_shared/utils/response_handlers/load.js";
|
|
2
7
|
import FetchingData from "@ogw_front/components/FetchingData.vue";
|
|
3
8
|
import { geode_objects } from "@ogw_front/assets/geode_objects";
|
|
4
|
-
import { resolveAllowedObjects } from "@ogw_shared/utils/response_handlers/load.js";
|
|
5
9
|
import schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
|
|
6
10
|
import { useBackStore } from "@ogw_front/stores/back";
|
|
7
11
|
|
|
@@ -14,8 +18,15 @@ type AllowedObjectMap = Record<string, AllowedObject>;
|
|
|
14
18
|
|
|
15
19
|
const schema = schemas.opengeodeweb_back.allowed_objects;
|
|
16
20
|
|
|
21
|
+
interface PendingGroup {
|
|
22
|
+
filenames: string[];
|
|
23
|
+
candidates: string[];
|
|
24
|
+
selected: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
const emit = defineEmits<{
|
|
18
|
-
|
|
28
|
+
// Filename -> geode object type, files that cannot be loaded are left out
|
|
29
|
+
update_values: [value: { geode_object_types: Record<string, string> }];
|
|
19
30
|
increment_step: [];
|
|
20
31
|
}>();
|
|
21
32
|
|
|
@@ -31,6 +42,14 @@ const loading = ref<boolean>(false);
|
|
|
31
42
|
const allowedGeodeObjects = ref<AllowedObjectMap>({});
|
|
32
43
|
const toggleLoading = useToggle(loading);
|
|
33
44
|
const multipleFilesNoCommon = ref<boolean>(false);
|
|
45
|
+
// Types already known, files left to choose for, and files that cannot be loaded
|
|
46
|
+
const resolvedTypes = ref<Record<string, string>>({});
|
|
47
|
+
const pendingFilenames = ref<string[]>([]);
|
|
48
|
+
const pendingGroups = ref<PendingGroup[]>([]);
|
|
49
|
+
const skippedFiles = ref<string[]>([]);
|
|
50
|
+
const showPerFile = ref<boolean>(false);
|
|
51
|
+
// Every file got its type without the user, nothing to display
|
|
52
|
+
const autoResolved = ref<boolean>(false);
|
|
34
53
|
|
|
35
54
|
async function fetchAllowedObjectsList(): Promise<AllowedObjectMap[]> {
|
|
36
55
|
const promiseArray = filenames.map((filename): Promise<{ allowed_objects: AllowedObjectMap }> => {
|
|
@@ -41,11 +60,43 @@ async function fetchAllowedObjectsList(): Promise<AllowedObjectMap[]> {
|
|
|
41
60
|
return responses.map((response) => response.allowed_objects);
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
function emitGeodeObjectTypes(geode_object_types: Record<string, string>): void {
|
|
64
|
+
emit("update_values", { geode_object_types });
|
|
65
|
+
emit("increment_step");
|
|
66
|
+
}
|
|
67
|
+
|
|
44
68
|
function setGeodeObject(geode_object_type: string | undefined): void {
|
|
45
|
-
if (geode_object_type) {
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
if (!geode_object_type) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const chosen = pendingFilenames.value.map((filename) => [filename, geode_object_type]);
|
|
73
|
+
emitGeodeObjectTypes({ ...resolvedTypes.value, ...Object.fromEntries(chosen) });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function confirmPerFile(): void {
|
|
77
|
+
const chosen = pendingGroups.value.flatMap((group) =>
|
|
78
|
+
group.filenames.map((filename) => [filename, group.selected]),
|
|
79
|
+
);
|
|
80
|
+
emitGeodeObjectTypes({ ...resolvedTypes.value, ...Object.fromEntries(chosen) });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Files with the same candidate types share one choice
|
|
84
|
+
function groupPendingFiles(pending: readonly FileAllowedObjects[]): PendingGroup[] {
|
|
85
|
+
const groups = new Map<string, PendingGroup>();
|
|
86
|
+
for (const file of pending) {
|
|
87
|
+
const key = file.candidates.join(",");
|
|
88
|
+
const group = groups.get(key);
|
|
89
|
+
if (group) {
|
|
90
|
+
group.filenames.push(file.filename);
|
|
91
|
+
} else {
|
|
92
|
+
groups.set(key, {
|
|
93
|
+
filenames: [file.filename],
|
|
94
|
+
candidates: file.candidates,
|
|
95
|
+
selected: file.candidates[0] ?? "",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
48
98
|
}
|
|
99
|
+
return [...groups.values()];
|
|
49
100
|
}
|
|
50
101
|
|
|
51
102
|
// The geode_objects lookup is fixed and keyed by known object type names; a missing entry only happens if the backend reports a type this table doesn't know about.
|
|
@@ -59,19 +110,63 @@ function geodeObjectImage(key: string): string | undefined {
|
|
|
59
110
|
return geode_objects[key]?.image;
|
|
60
111
|
}
|
|
61
112
|
|
|
113
|
+
function showCommonGeodeObjects(
|
|
114
|
+
pendingNames: readonly string[],
|
|
115
|
+
allowedObjectsList: readonly AllowedObjectMap[],
|
|
116
|
+
): string | undefined {
|
|
117
|
+
const resolved = resolveAllowedObjects(pendingNames, allowedObjectsList);
|
|
118
|
+
allowedGeodeObjects.value = resolved.mergedAllowedObjects;
|
|
119
|
+
multipleFilesNoCommon.value = resolved.multipleFilesNoCommon;
|
|
120
|
+
return resolved.selectedGeodeObject;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Each file gets its own type. Returns true when the per file panel is needed, i.e. the files cannot be handled by a single choice
|
|
124
|
+
function resolvePerFile(allowedObjectsList: readonly AllowedObjectMap[]): boolean {
|
|
125
|
+
const files = resolveAllowedObjectsPerFile(filenames, allowedObjectsList);
|
|
126
|
+
const loadable = files.filter((file) => file.candidates.length > 0);
|
|
127
|
+
if (loadable.length === 0) {
|
|
128
|
+
// Nothing can be loaded: show why with the common types
|
|
129
|
+
showCommonGeodeObjects(filenames, allowedObjectsList);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const pending = loadable.filter((file) => file.selectedGeodeObject === undefined);
|
|
133
|
+
resolvedTypes.value = Object.fromEntries(
|
|
134
|
+
loadable.flatMap((file) =>
|
|
135
|
+
file.selectedGeodeObject === undefined ? [] : [[file.filename, file.selectedGeodeObject]],
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
pendingFilenames.value = pending.map((file) => file.filename);
|
|
139
|
+
skippedFiles.value = files
|
|
140
|
+
.filter((file) => file.candidates.length === 0)
|
|
141
|
+
.map((file) => file.filename);
|
|
142
|
+
if (skippedFiles.value.length === 0) {
|
|
143
|
+
if (pending.length === 0) {
|
|
144
|
+
autoResolved.value = true;
|
|
145
|
+
emitGeodeObjectTypes(resolvedTypes.value);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const pendingAllowedObjects = pending.map(
|
|
149
|
+
(file) => allowedObjectsList[filenames.indexOf(file.filename)] ?? {},
|
|
150
|
+
);
|
|
151
|
+
// Pending files sharing a loadable type: one choice for all of them
|
|
152
|
+
const selected = showCommonGeodeObjects(pendingFilenames.value, pendingAllowedObjects);
|
|
153
|
+
if (Object.values(allowedGeodeObjects.value).some((object) => object.is_loadable > 0)) {
|
|
154
|
+
setGeodeObject(selected);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
pendingGroups.value = groupPendingFiles(pending);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
62
162
|
async function getAllowedGeodeObjects(): Promise<void> {
|
|
63
163
|
toggleLoading();
|
|
64
164
|
allowedGeodeObjects.value = {};
|
|
65
165
|
multipleFilesNoCommon.value = false;
|
|
166
|
+
showPerFile.value = false;
|
|
66
167
|
|
|
67
168
|
const allowedGeodeObjectsList = await fetchAllowedObjectsList();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
allowedGeodeObjects.value = resolved.mergedAllowedObjects;
|
|
71
|
-
multipleFilesNoCommon.value = resolved.multipleFilesNoCommon;
|
|
72
|
-
if (resolved.selectedGeodeObject) {
|
|
73
|
-
setGeodeObject(resolved.selectedGeodeObject);
|
|
74
|
-
}
|
|
169
|
+
showPerFile.value = resolvePerFile(allowedGeodeObjectsList);
|
|
75
170
|
toggleLoading();
|
|
76
171
|
}
|
|
77
172
|
// oxlint-disable-next-line no-top-level-await
|
|
@@ -80,7 +175,46 @@ await getAllowedGeodeObjects();
|
|
|
80
175
|
|
|
81
176
|
<template>
|
|
82
177
|
<FetchingData v-if="loading" />
|
|
178
|
+
<v-container v-else-if="showPerFile" data-testid="perFileObjectSelector">
|
|
179
|
+
<v-row v-for="group in pendingGroups" :key="group.filenames.join()" align="center">
|
|
180
|
+
<v-col cols="auto" class="pa-1">
|
|
181
|
+
<v-chip v-for="filename in group.filenames" :key="filename" class="ma-1" size="small">
|
|
182
|
+
{{ filename }}
|
|
183
|
+
</v-chip>
|
|
184
|
+
</v-col>
|
|
185
|
+
<v-col class="pa-1">
|
|
186
|
+
<v-select
|
|
187
|
+
v-model="group.selected"
|
|
188
|
+
:items="group.candidates"
|
|
189
|
+
density="compact"
|
|
190
|
+
hide-details
|
|
191
|
+
variant="outlined"
|
|
192
|
+
/>
|
|
193
|
+
</v-col>
|
|
194
|
+
</v-row>
|
|
195
|
+
<v-row v-for="(type, filename) in resolvedTypes" :key="filename" align="center">
|
|
196
|
+
<v-chip class="ma-1" size="small">{{ filename }}</v-chip>
|
|
197
|
+
<v-chip class="ma-1" size="small" variant="tonal">{{ type }}</v-chip>
|
|
198
|
+
</v-row>
|
|
199
|
+
<v-row v-if="skippedFiles.length" align="center">
|
|
200
|
+
<v-icon color="warning" icon="mdi-file-alert-outline" class="ma-1" />
|
|
201
|
+
<p class="pa-1">Not supported, will be skipped:</p>
|
|
202
|
+
<v-chip v-for="filename in skippedFiles" :key="filename" class="ma-1" size="small">
|
|
203
|
+
{{ filename }}
|
|
204
|
+
</v-chip>
|
|
205
|
+
</v-row>
|
|
206
|
+
<v-row>
|
|
207
|
+
<v-col cols="auto">
|
|
208
|
+
<v-btn color="primary" data-testid="confirmDataTypesButton" @click="confirmPerFile">
|
|
209
|
+
Continue
|
|
210
|
+
</v-btn>
|
|
211
|
+
</v-col>
|
|
212
|
+
</v-row>
|
|
213
|
+
</v-container>
|
|
83
214
|
<v-row v-else-if="Object.keys(allowedGeodeObjects).length" class="justify-left">
|
|
215
|
+
<v-col v-if="pendingFilenames.length && pendingFilenames.length < filenames.length" cols="12">
|
|
216
|
+
Choose the data type for: {{ pendingFilenames.join(", ") }}
|
|
217
|
+
</v-col>
|
|
84
218
|
<v-col v-for="(value, key) in allowedGeodeObjects" :key="key" cols="3" md="4">
|
|
85
219
|
<v-tooltip :text="geodeObjectTooltip(key, Boolean(value['is_loadable']))" location="bottom">
|
|
86
220
|
<template v-slot:activator="{ props }">
|
|
@@ -112,7 +246,7 @@ await getAllowedGeodeObjects();
|
|
|
112
246
|
</v-card-text>
|
|
113
247
|
</v-card>
|
|
114
248
|
</v-row>
|
|
115
|
-
<v-row v-else class="pa-5">
|
|
249
|
+
<v-row v-else-if="!autoResolved" class="pa-5">
|
|
116
250
|
<v-card class="card" variant="tonal" rounded>
|
|
117
251
|
<v-card-text>
|
|
118
252
|
This file format isn't supported! Please check the
|
|
@@ -172,7 +172,6 @@ async function setVertexAttributeRangesPerData(ranges: RangesPerData): Promise<v
|
|
|
172
172
|
v-model:vertex_attribute_color_map="vertex_attribute_color_map"
|
|
173
173
|
v-model:vertex_attribute_no_data_color="vertex_attribute_no_data_color"
|
|
174
174
|
v-model:vertex_attribute_time_step="vertex_attribute_time_step"
|
|
175
|
-
:capabilities="{ vertex: { available: false } }"
|
|
176
175
|
/>
|
|
177
176
|
</template>
|
|
178
177
|
</template>
|
|
@@ -0,0 +1,245 @@
|
|
|
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 ViewerOptionsSizeSlider from "@ogw_front/components/Viewer/Options/Sliders/Size.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 PointsTarget {
|
|
11
|
+
componentType: "Surface" | "Line" | "Block";
|
|
12
|
+
componentIds: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
modelId: string;
|
|
17
|
+
targets: PointsTarget[];
|
|
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 points functions in the data style store
|
|
27
|
+
const POINTS_STYLES = {
|
|
28
|
+
Surface: {
|
|
29
|
+
visibility: dataStyleStore.modelSurfacePointsVisibility,
|
|
30
|
+
size: dataStyleStore.modelSurfacePointsSize,
|
|
31
|
+
color: dataStyleStore.modelSurfacePointsColor,
|
|
32
|
+
activeColoring: dataStyleStore.modelSurfacePointsActiveColoring,
|
|
33
|
+
vertexAttributeName: dataStyleStore.modelSurfacePointsVertexAttributeName,
|
|
34
|
+
vertexAttributeItem: dataStyleStore.modelSurfacePointsVertexAttributeItem,
|
|
35
|
+
vertexAttributeRange: dataStyleStore.modelSurfacePointsVertexAttributeRange,
|
|
36
|
+
vertexAttributeColorMap: dataStyleStore.modelSurfacePointsVertexAttributeColorMap,
|
|
37
|
+
vertexAttributeNoDataColor: dataStyleStore.modelSurfacePointsVertexAttributeNoDataColor,
|
|
38
|
+
setVisibility: dataStyleStore.setModelSurfacesPointsVisibility,
|
|
39
|
+
setSize: dataStyleStore.setModelSurfacesPointsSize,
|
|
40
|
+
setColor: dataStyleStore.setModelSurfacesPointsColor,
|
|
41
|
+
setActiveColoring: dataStyleStore.setModelSurfacesPointsActiveColoring,
|
|
42
|
+
setVertexAttributeName: dataStyleStore.setModelSurfacesPointsVertexAttributeName,
|
|
43
|
+
setVertexAttributeItem: dataStyleStore.setModelSurfacesPointsVertexAttributeItem,
|
|
44
|
+
setVertexAttributeRange: dataStyleStore.setModelSurfacesPointsVertexAttributeRange,
|
|
45
|
+
setVertexAttributeColorMap: dataStyleStore.setModelSurfacesPointsVertexAttributeColorMap,
|
|
46
|
+
setVertexAttributeNoDataColor: dataStyleStore.setModelSurfacesPointsVertexAttributeNoDataColor,
|
|
47
|
+
},
|
|
48
|
+
Line: {
|
|
49
|
+
visibility: dataStyleStore.modelLinePointsVisibility,
|
|
50
|
+
size: dataStyleStore.modelLinePointsSize,
|
|
51
|
+
color: dataStyleStore.modelLinePointsColor,
|
|
52
|
+
activeColoring: dataStyleStore.modelLinePointsActiveColoring,
|
|
53
|
+
vertexAttributeName: dataStyleStore.modelLinePointsVertexAttributeName,
|
|
54
|
+
vertexAttributeItem: dataStyleStore.modelLinePointsVertexAttributeItem,
|
|
55
|
+
vertexAttributeRange: dataStyleStore.modelLinePointsVertexAttributeRange,
|
|
56
|
+
vertexAttributeColorMap: dataStyleStore.modelLinePointsVertexAttributeColorMap,
|
|
57
|
+
vertexAttributeNoDataColor: dataStyleStore.modelLinePointsVertexAttributeNoDataColor,
|
|
58
|
+
setVisibility: dataStyleStore.setModelLinesPointsVisibility,
|
|
59
|
+
setSize: dataStyleStore.setModelLinesPointsSize,
|
|
60
|
+
setColor: dataStyleStore.setModelLinesPointsColor,
|
|
61
|
+
setActiveColoring: dataStyleStore.setModelLinesPointsActiveColoring,
|
|
62
|
+
setVertexAttributeName: dataStyleStore.setModelLinesPointsVertexAttributeName,
|
|
63
|
+
setVertexAttributeItem: dataStyleStore.setModelLinesPointsVertexAttributeItem,
|
|
64
|
+
setVertexAttributeRange: dataStyleStore.setModelLinesPointsVertexAttributeRange,
|
|
65
|
+
setVertexAttributeColorMap: dataStyleStore.setModelLinesPointsVertexAttributeColorMap,
|
|
66
|
+
setVertexAttributeNoDataColor: dataStyleStore.setModelLinesPointsVertexAttributeNoDataColor,
|
|
67
|
+
},
|
|
68
|
+
Block: {
|
|
69
|
+
visibility: dataStyleStore.modelBlockPointsVisibility,
|
|
70
|
+
size: dataStyleStore.modelBlockPointsSize,
|
|
71
|
+
color: dataStyleStore.modelBlockPointsColor,
|
|
72
|
+
activeColoring: dataStyleStore.modelBlockPointsActiveColoring,
|
|
73
|
+
vertexAttributeName: dataStyleStore.modelBlockPointsVertexAttributeName,
|
|
74
|
+
vertexAttributeItem: dataStyleStore.modelBlockPointsVertexAttributeItem,
|
|
75
|
+
vertexAttributeRange: dataStyleStore.modelBlockPointsVertexAttributeRange,
|
|
76
|
+
vertexAttributeColorMap: dataStyleStore.modelBlockPointsVertexAttributeColorMap,
|
|
77
|
+
vertexAttributeNoDataColor: dataStyleStore.modelBlockPointsVertexAttributeNoDataColor,
|
|
78
|
+
setVisibility: dataStyleStore.setModelBlocksPointsVisibility,
|
|
79
|
+
setSize: dataStyleStore.setModelBlocksPointsSize,
|
|
80
|
+
setColor: dataStyleStore.setModelBlocksPointsColor,
|
|
81
|
+
setActiveColoring: dataStyleStore.setModelBlocksPointsActiveColoring,
|
|
82
|
+
setVertexAttributeName: dataStyleStore.setModelBlocksPointsVertexAttributeName,
|
|
83
|
+
setVertexAttributeItem: dataStyleStore.setModelBlocksPointsVertexAttributeItem,
|
|
84
|
+
setVertexAttributeRange: dataStyleStore.setModelBlocksPointsVertexAttributeRange,
|
|
85
|
+
setVertexAttributeColorMap: dataStyleStore.setModelBlocksPointsVertexAttributeColorMap,
|
|
86
|
+
setVertexAttributeNoDataColor: dataStyleStore.setModelBlocksPointsVertexAttributeNoDataColor,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
type PointsStyle = (typeof POINTS_STYLES)[PointsTarget["componentType"]];
|
|
90
|
+
|
|
91
|
+
// Values are read from the first target, changes are applied to every target
|
|
92
|
+
const pointsStyle = computed<PointsStyle | undefined>(() => {
|
|
93
|
+
const [reference] = targets;
|
|
94
|
+
return reference === undefined ? undefined : POINTS_STYLES[reference.componentType];
|
|
95
|
+
});
|
|
96
|
+
const allComponentIds = computed<string[]>(() => targets.flatMap((target) => target.componentIds));
|
|
97
|
+
|
|
98
|
+
async function applyPointsStyle(
|
|
99
|
+
setter: (style: PointsStyle, componentIds: string[]) => Promise<unknown>,
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
await Promise.all(
|
|
102
|
+
targets.map(async (target) => {
|
|
103
|
+
await setter(POINTS_STYLES[target.componentType], target.componentIds);
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
hybridViewerStore.remoteRender();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const visibility = computed<boolean | undefined>({
|
|
110
|
+
get: () => pointsStyle.value?.visibility(modelId, referenceComponentId),
|
|
111
|
+
set: async (newValue) => {
|
|
112
|
+
if (newValue === undefined) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await applyPointsStyle((style, ids) => style.setVisibility(modelId, ids, newValue));
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const size = computed<number | undefined>({
|
|
120
|
+
get: () => pointsStyle.value?.size(modelId, referenceComponentId),
|
|
121
|
+
set: async (newValue) => {
|
|
122
|
+
if (newValue === undefined) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await applyPointsStyle((style, ids) => style.setSize(modelId, ids, newValue));
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const activeColoring = computed<string | undefined>({
|
|
130
|
+
get: () => pointsStyle.value?.activeColoring(modelId, referenceComponentId),
|
|
131
|
+
set: async (newValue) => {
|
|
132
|
+
if (newValue === undefined) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await applyPointsStyle((style, ids) => style.setActiveColoring(modelId, ids, newValue));
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const color = computed<RGBAColor | undefined>({
|
|
140
|
+
get: () => pointsStyle.value?.color(modelId, referenceComponentId),
|
|
141
|
+
set: async (newValue) => {
|
|
142
|
+
if (newValue === undefined) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
await applyPointsStyle((style, ids) => style.setColor(modelId, ids, newValue));
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const vertexAttributeName = computed<string | undefined>({
|
|
150
|
+
get: () => pointsStyle.value?.vertexAttributeName(modelId, referenceComponentId),
|
|
151
|
+
set: async (newValue) => {
|
|
152
|
+
if (newValue === undefined) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
await applyPointsStyle((style, ids) => style.setVertexAttributeName(modelId, ids, newValue));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const vertexAttributeItem = computed<number | undefined>({
|
|
160
|
+
get: () => pointsStyle.value?.vertexAttributeItem(modelId, referenceComponentId),
|
|
161
|
+
set: async (newValue) => {
|
|
162
|
+
if (newValue === undefined) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await applyPointsStyle((style, ids) => style.setVertexAttributeItem(modelId, ids, newValue));
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const vertexAttributeRange = computed<(number | undefined)[] | undefined>({
|
|
170
|
+
get: () => pointsStyle.value?.vertexAttributeRange(modelId, referenceComponentId),
|
|
171
|
+
set: async (newValue) => {
|
|
172
|
+
if (newValue === undefined) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const [minimum, maximum] = newValue;
|
|
176
|
+
if (minimum === undefined || maximum === undefined) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
await applyPointsStyle((style, ids) =>
|
|
180
|
+
style.setVertexAttributeRange(modelId, ids, minimum, maximum),
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const vertexAttributeColorMap = computed<string | undefined>({
|
|
186
|
+
get: () => pointsStyle.value?.vertexAttributeColorMap(modelId, referenceComponentId),
|
|
187
|
+
set: async (newValue) => {
|
|
188
|
+
await applyPointsStyle((style, ids) =>
|
|
189
|
+
style.setVertexAttributeColorMap(modelId, ids, newValue),
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const vertexAttributeNoDataColor = computed<RGBAColor | undefined>({
|
|
195
|
+
get: () => pointsStyle.value?.vertexAttributeNoDataColor(modelId, referenceComponentId),
|
|
196
|
+
set: async (newValue) => {
|
|
197
|
+
if (newValue === undefined) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await applyPointsStyle((style, ids) =>
|
|
201
|
+
style.setVertexAttributeNoDataColor(modelId, ids, newValue),
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const capabilities = {
|
|
207
|
+
color: { available: true },
|
|
208
|
+
textures: { available: false },
|
|
209
|
+
vertex: { available: true },
|
|
210
|
+
edge: { available: false },
|
|
211
|
+
cell: { available: false },
|
|
212
|
+
polygon: { available: false },
|
|
213
|
+
polyhedron: { available: false },
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const backSchemas = back_schemas.opengeodeweb_back;
|
|
217
|
+
const attributeSchemas = {
|
|
218
|
+
vertex: {
|
|
219
|
+
names: backSchemas.model_component_vertex_attribute_names,
|
|
220
|
+
range: backSchemas.model_component_vertex_attribute_range,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
</script>
|
|
224
|
+
|
|
225
|
+
<template>
|
|
226
|
+
<div class="mt-2" data-testid="modelComponentPointsOptions">
|
|
227
|
+
<VisibilitySwitch data-testid="modelComponentPointsVisibilitySwitch" v-model="visibility" />
|
|
228
|
+
<template v-if="visibility">
|
|
229
|
+
<ViewerOptionsSizeSlider data-testid="modelComponentPointsSizeSlider" v-model="size" />
|
|
230
|
+
<ViewerOptionsColoringTypeSelector
|
|
231
|
+
:id="modelId"
|
|
232
|
+
:componentIds="allComponentIds"
|
|
233
|
+
v-model:coloring_style_key="activeColoring"
|
|
234
|
+
v-model:color="color"
|
|
235
|
+
v-model:vertex_attribute_name="vertexAttributeName"
|
|
236
|
+
v-model:vertex_attribute_item="vertexAttributeItem"
|
|
237
|
+
v-model:vertex_attribute_range="vertexAttributeRange"
|
|
238
|
+
v-model:vertex_attribute_color_map="vertexAttributeColorMap"
|
|
239
|
+
v-model:vertex_attribute_no_data_color="vertexAttributeNoDataColor"
|
|
240
|
+
:capabilities="capabilities"
|
|
241
|
+
:schemas="attributeSchemas"
|
|
242
|
+
/>
|
|
243
|
+
</template>
|
|
244
|
+
</div>
|
|
245
|
+
</template>
|