@geode/opengeodeweb-front 10.40.0-rc.2 → 10.40.1-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.
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { type AttributeSchemas, fetchAttributeRange } from "@ogw_front/utils/attribute_range";
2
3
  import {
3
4
  MESH_ELEMENT_KINDS,
4
5
  MODEL_COMPONENT_ATTRIBUTE_SCHEMAS,
@@ -26,9 +27,9 @@ const { escapeFunction = undefined } = defineProps<Props>();
26
27
 
27
28
  interface AttributeSource {
28
29
  title: string;
29
- schema: JsonRpcSchema;
30
+ schemas: AttributeSchemas;
30
31
  timeStepKey: string;
31
- params: Record<string, unknown>;
32
+ params: { id: string; component_ids?: string[] };
32
33
  location: "point" | "cell";
33
34
  }
34
35
 
@@ -86,23 +87,24 @@ const itemOptions = computed<{ title: string; value: number }[]>(() =>
86
87
  })),
87
88
  );
88
89
 
89
- function attributeSchema(kind: string, isModel: boolean): JsonRpcSchema {
90
+ function attributeSchema(kind: string, isModel: boolean, route: "names" | "range"): JsonRpcSchema {
90
91
  const prefix = isModel ? "model_component_" : "";
91
92
  return back_schemas.opengeodeweb_back[
92
- `${prefix}${kind}_attribute_names` as keyof typeof back_schemas.opengeodeweb_back
93
+ `${prefix}${kind}_attribute_${route}` as keyof typeof back_schemas.opengeodeweb_back
93
94
  ] as JsonRpcSchema;
94
95
  }
95
96
 
96
97
  function attributeSource(
97
98
  kind: string,
98
99
  title: string,
99
- params: Record<string, unknown>,
100
+ params: { id: string; component_ids?: string[] },
100
101
  modelType?: string,
101
102
  ): AttributeSource {
102
- const schema = attributeSchema(kind, modelType !== undefined);
103
+ const isModel = modelType !== undefined;
104
+ const schema = attributeSchema(kind, isModel, "names");
103
105
  return {
104
106
  title: `${title}${kind} attribute`,
105
- schema,
107
+ schemas: { names: schema, range: attributeSchema(kind, isModel, "range") },
106
108
  timeStepKey:
107
109
  modelType === undefined
108
110
  ? schema.$id
@@ -154,11 +156,17 @@ async function applyThreshold(): Promise<void> {
154
156
 
155
157
  const debouncedApply = useDebounceFn(() => applyThreshold(), DEBOUNCE_DELAY);
156
158
 
157
- function resetRange(): void {
158
- const { min, max } = getAttributeRange(
159
- currentAttribute.value as Parameters<typeof getAttributeRange>[0],
160
- attributeItem.value,
161
- );
159
+ async function resetRange(): Promise<void> {
160
+ const source = sources.value[selectedSourceIndex.value ?? -1];
161
+ const name = attributeName.value;
162
+ if (!source || name === undefined) {
163
+ return;
164
+ }
165
+ const range = await fetchAttributeRange(source.schemas.range, source.params, name);
166
+ if (name !== attributeName.value) {
167
+ return;
168
+ }
169
+ const { min, max } = getAttributeRange(range, attributeItem.value);
162
170
  minimum.value = min;
163
171
  maximum.value = max;
164
172
  }
@@ -186,19 +194,19 @@ watch(selectedSourceIndex, async (index) => {
186
194
  return;
187
195
  }
188
196
  const response = (await backStore.request({
189
- schema: source.schema,
197
+ schema: source.schemas.names,
190
198
  params: source.params,
191
199
  })) as { attributes: AttributeInfo[] };
192
200
  attributes.value = response.attributes;
193
201
  });
194
202
 
195
- watch(attributeName, () => {
203
+ watch(attributeName, async () => {
196
204
  attributeItem.value = 0;
197
- resetRange();
205
+ await resetRange();
198
206
  });
199
207
 
200
- watch(attributeItem, () => {
201
- resetRange();
208
+ watch(attributeItem, async () => {
209
+ await resetRange();
202
210
  });
203
211
 
204
212
  watch(currentTimeStep, () => {
@@ -30,6 +30,8 @@ const referenceBlockId = computed<string | undefined>(() =>
30
30
  collections ? targetBlockIds[0] : undefined,
31
31
  );
32
32
 
33
+ const blockComponentIds = computed<string[]>(() => (blockId === undefined ? [] : [blockId]));
34
+
33
35
  // Visibility
34
36
  const blocksVisibility = computed<boolean>({
35
37
  get: () =>
@@ -184,8 +186,14 @@ const blocksPolyhedronAttributeName = computed<string | undefined>({
184
186
  if (newValue === undefined) {
185
187
  return;
186
188
  }
187
- await dataStyleStore.setModelBlocksPolyhedronAttributeName(modelId, targetBlockIds, newValue);
188
- hybridViewerStore.remoteRender();
189
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeName(
190
+ modelId,
191
+ targetBlockIds,
192
+ newValue,
193
+ );
194
+ if (applied) {
195
+ hybridViewerStore.remoteRender();
196
+ }
189
197
  },
190
198
  });
191
199
 
@@ -195,20 +203,28 @@ const blocksPolyhedronAttributeTimeStep = computed<number | undefined>({
195
203
  if (newValue === undefined) {
196
204
  return;
197
205
  }
198
- await dataStyleStore.setModelBlocksPolyhedronAttributeTimeStep(
206
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeTimeStep(
199
207
  modelId,
200
208
  targetBlockIds,
201
209
  newValue,
202
210
  );
203
- hybridViewerStore.remoteRender();
211
+ if (applied) {
212
+ hybridViewerStore.remoteRender();
213
+ }
204
214
  },
205
215
  });
206
216
 
207
217
  const blocksPolyhedronAttributeItem = computed<string | undefined>({
208
218
  get: () => dataStyleStore.modelBlocksPolyhedronAttributeItem(modelId, referenceBlockId.value),
209
219
  set: async (newValue) => {
210
- await dataStyleStore.setModelBlocksPolyhedronAttributeItem(modelId, targetBlockIds, newValue);
211
- hybridViewerStore.remoteRender();
220
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeItem(
221
+ modelId,
222
+ targetBlockIds,
223
+ newValue,
224
+ );
225
+ if (applied) {
226
+ hybridViewerStore.remoteRender();
227
+ }
212
228
  },
213
229
  });
214
230
 
@@ -219,25 +235,29 @@ const blocksPolyhedronAttributeRange = computed<[number, number] | undefined>({
219
235
  if (minimum === undefined || maximum === undefined) {
220
236
  return;
221
237
  }
222
- await dataStyleStore.setModelBlocksPolyhedronAttributeRange(
238
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeRange(
223
239
  modelId,
224
240
  targetBlockIds,
225
241
  minimum,
226
242
  maximum,
227
243
  );
228
- hybridViewerStore.remoteRender();
244
+ if (applied) {
245
+ hybridViewerStore.remoteRender();
246
+ }
229
247
  },
230
248
  });
231
249
 
232
250
  const blocksPolyhedronAttributeColorMap = computed<Map<string, RGBAColor> | undefined>({
233
251
  get: () => dataStyleStore.modelBlocksPolyhedronAttributeColorMap(modelId, referenceBlockId.value),
234
252
  set: async (newValue) => {
235
- await dataStyleStore.setModelBlocksPolyhedronAttributeColorMap(
253
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeColorMap(
236
254
  modelId,
237
255
  targetBlockIds,
238
256
  newValue,
239
257
  );
240
- hybridViewerStore.remoteRender();
258
+ if (applied) {
259
+ hybridViewerStore.remoteRender();
260
+ }
241
261
  },
242
262
  });
243
263
 
@@ -245,12 +265,14 @@ const blocksPolyhedronAttributeNoDataColor = computed<RGBAColor>({
245
265
  get: () =>
246
266
  dataStyleStore.modelBlocksPolyhedronAttributeNoDataColor(modelId, referenceBlockId.value),
247
267
  set: async (newValue) => {
248
- await dataStyleStore.setModelBlocksPolyhedronAttributeNoDataColor(
268
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeNoDataColor(
249
269
  modelId,
250
270
  targetBlockIds,
251
271
  newValue,
252
272
  );
253
- hybridViewerStore.remoteRender();
273
+ if (applied) {
274
+ hybridViewerStore.remoteRender();
275
+ }
254
276
  },
255
277
  });
256
278
 
@@ -328,8 +350,14 @@ const polyhedronAttributeName = computed<string | undefined>({
328
350
  if (blockId === undefined || newValue === undefined) {
329
351
  return;
330
352
  }
331
- await dataStyleStore.setModelBlocksPolyhedronAttributeName(modelId, [blockId], newValue);
332
- hybridViewerStore.remoteRender();
353
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeName(
354
+ modelId,
355
+ [blockId],
356
+ newValue,
357
+ );
358
+ if (applied) {
359
+ hybridViewerStore.remoteRender();
360
+ }
333
361
  },
334
362
  });
335
363
 
@@ -339,8 +367,14 @@ const polyhedronAttributeTimeStep = computed<number | undefined>({
339
367
  if (blockId === undefined || newValue === undefined) {
340
368
  return;
341
369
  }
342
- await dataStyleStore.setModelBlocksPolyhedronAttributeTimeStep(modelId, [blockId], newValue);
343
- hybridViewerStore.remoteRender();
370
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeTimeStep(
371
+ modelId,
372
+ [blockId],
373
+ newValue,
374
+ );
375
+ if (applied) {
376
+ hybridViewerStore.remoteRender();
377
+ }
344
378
  },
345
379
  });
346
380
 
@@ -350,8 +384,14 @@ const polyhedronAttributeItem = computed<string | undefined>({
350
384
  if (blockId === undefined) {
351
385
  return;
352
386
  }
353
- await dataStyleStore.setModelBlocksPolyhedronAttributeItem(modelId, [blockId], newValue);
354
- hybridViewerStore.remoteRender();
387
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeItem(
388
+ modelId,
389
+ [blockId],
390
+ newValue,
391
+ );
392
+ if (applied) {
393
+ hybridViewerStore.remoteRender();
394
+ }
355
395
  },
356
396
  });
357
397
 
@@ -362,13 +402,15 @@ const polyhedronAttributeRange = computed<[number, number] | undefined>({
362
402
  if (blockId === undefined || minimum === undefined || maximum === undefined) {
363
403
  return;
364
404
  }
365
- await dataStyleStore.setModelBlocksPolyhedronAttributeRange(
405
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeRange(
366
406
  modelId,
367
407
  [blockId],
368
408
  minimum,
369
409
  maximum,
370
410
  );
371
- hybridViewerStore.remoteRender();
411
+ if (applied) {
412
+ hybridViewerStore.remoteRender();
413
+ }
372
414
  },
373
415
  });
374
416
 
@@ -378,8 +420,14 @@ const polyhedronAttributeColorMap = computed<Map<string, RGBAColor> | undefined>
378
420
  if (blockId === undefined) {
379
421
  return;
380
422
  }
381
- await dataStyleStore.setModelBlocksPolyhedronAttributeColorMap(modelId, [blockId], newValue);
382
- hybridViewerStore.remoteRender();
423
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeColorMap(
424
+ modelId,
425
+ [blockId],
426
+ newValue,
427
+ );
428
+ if (applied) {
429
+ hybridViewerStore.remoteRender();
430
+ }
383
431
  },
384
432
  });
385
433
 
@@ -389,8 +437,14 @@ const polyhedronAttributeNoDataColor = computed<RGBAColor>({
389
437
  if (blockId === undefined) {
390
438
  return;
391
439
  }
392
- await dataStyleStore.setModelBlocksPolyhedronAttributeNoDataColor(modelId, [blockId], newValue);
393
- hybridViewerStore.remoteRender();
440
+ const applied = await dataStyleStore.setModelBlocksPolyhedronAttributeNoDataColor(
441
+ modelId,
442
+ [blockId],
443
+ newValue,
444
+ );
445
+ if (applied) {
446
+ hybridViewerStore.remoteRender();
447
+ }
394
448
  },
395
449
  });
396
450
 
@@ -404,8 +458,17 @@ const capabilities = {
404
458
  polyhedron: { available: true },
405
459
  };
406
460
 
407
- const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attribute_names;
408
- const polyhedronSchema = back_schemas.opengeodeweb_back.model_component_polyhedron_attribute_names;
461
+ const backSchemas = back_schemas.opengeodeweb_back;
462
+ const attributeSchemas = {
463
+ vertex: {
464
+ names: backSchemas.model_component_vertex_attribute_names,
465
+ range: backSchemas.model_component_vertex_attribute_range,
466
+ },
467
+ polyhedron: {
468
+ names: backSchemas.model_component_polyhedron_attribute_names,
469
+ range: backSchemas.model_component_polyhedron_attribute_range,
470
+ },
471
+ };
409
472
  </script>
410
473
 
411
474
  <template>
@@ -429,7 +492,7 @@ const polyhedronSchema = back_schemas.opengeodeweb_back.model_component_polyhedr
429
492
  v-model:polyhedron_attribute_no_data_color="blocksPolyhedronAttributeNoDataColor"
430
493
  v-model:polyhedron_attribute_time_step="blocksPolyhedronAttributeTimeStep"
431
494
  :capabilities="capabilities"
432
- :schemas="{ vertex: vertexSchema, polyhedron: polyhedronSchema }"
495
+ :schemas="attributeSchemas"
433
496
  :allowRandom="true"
434
497
  />
435
498
  </OptionsSection>
@@ -443,7 +506,7 @@ const polyhedronSchema = back_schemas.opengeodeweb_back.model_component_polyhedr
443
506
  <VisibilitySwitch v-model="blockVisibility" />
444
507
  <ViewerOptionsColoringTypeSelector
445
508
  :id="modelId"
446
- :componentIds="[blockId]"
509
+ :componentIds="blockComponentIds"
447
510
  v-model:coloring_style_key="blockActiveColoring"
448
511
  v-model:color="blockColor"
449
512
  v-model:vertex_attribute_name="vertexAttributeName"
@@ -459,7 +522,7 @@ const polyhedronSchema = back_schemas.opengeodeweb_back.model_component_polyhedr
459
522
  v-model:polyhedron_attribute_no_data_color="polyhedronAttributeNoDataColor"
460
523
  v-model:polyhedron_attribute_time_step="polyhedronAttributeTimeStep"
461
524
  :capabilities="capabilities"
462
- :schemas="{ vertex: vertexSchema, polyhedron: polyhedronSchema }"
525
+ :schemas="attributeSchemas"
463
526
  :allowRandom="true"
464
527
  />
465
528
  </OptionsSection>
@@ -29,6 +29,8 @@ const referenceCornerId = computed<string | undefined>(() =>
29
29
  collections ? targetCornerIds[0] : undefined,
30
30
  );
31
31
 
32
+ const cornerComponentIds = computed<string[]>(() => (cornerId === undefined ? [] : [cornerId]));
33
+
32
34
  // Visibility
33
35
  const cornersVisibility = computed<boolean>({
34
36
  get: () =>
@@ -258,7 +260,13 @@ const capabilities = {
258
260
  polyhedron: { available: false },
259
261
  };
260
262
 
261
- const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attribute_names;
263
+ const backSchemas = back_schemas.opengeodeweb_back;
264
+ const attributeSchemas = {
265
+ vertex: {
266
+ names: backSchemas.model_component_vertex_attribute_names,
267
+ range: backSchemas.model_component_vertex_attribute_range,
268
+ },
269
+ };
262
270
  </script>
263
271
 
264
272
  <template>
@@ -276,7 +284,7 @@ const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attri
276
284
  v-model:vertex_attribute_no_data_color="cornersVertexAttributeNoDataColor"
277
285
  v-model:vertex_attribute_time_step="cornersVertexAttributeTimeStep"
278
286
  :capabilities="capabilities"
279
- :schemas="{ vertex: vertexSchema }"
287
+ :schemas="attributeSchemas"
280
288
  :allowRandom="true"
281
289
  />
282
290
  </OptionsSection>
@@ -290,7 +298,7 @@ const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attri
290
298
  <VisibilitySwitch v-model="cornerVisibility" />
291
299
  <ViewerOptionsColoringTypeSelector
292
300
  :id="modelId"
293
- :componentIds="[cornerId]"
301
+ :componentIds="cornerComponentIds"
294
302
  v-model:coloring_style_key="cornerActiveColoring"
295
303
  v-model:color="cornerColor"
296
304
  v-model:vertex_attribute_name="vertexAttributeName"
@@ -300,7 +308,7 @@ const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attri
300
308
  v-model:vertex_attribute_no_data_color="vertexAttributeNoDataColor"
301
309
  v-model:vertex_attribute_time_step="vertexAttributeTimeStep"
302
310
  :capabilities="capabilities"
303
- :schemas="{ vertex: vertexSchema }"
311
+ :schemas="attributeSchemas"
304
312
  :allowRandom="true"
305
313
  />
306
314
  </OptionsSection>
@@ -29,6 +29,8 @@ const referenceLineId = computed<string | undefined>(() =>
29
29
  collections ? targetLineIds[0] : undefined,
30
30
  );
31
31
 
32
+ const lineComponentIds = computed<string[]>(() => (lineId === undefined ? [] : [lineId]));
33
+
32
34
  // Visibility
33
35
  const linesVisibility = computed<boolean>({
34
36
  get: () =>
@@ -375,8 +377,17 @@ const capabilities = {
375
377
  polyhedron: { available: false },
376
378
  };
377
379
 
378
- const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attribute_names;
379
- const edgeSchema = back_schemas.opengeodeweb_back.model_component_edge_attribute_names;
380
+ const backSchemas = back_schemas.opengeodeweb_back;
381
+ const attributeSchemas = {
382
+ vertex: {
383
+ names: backSchemas.model_component_vertex_attribute_names,
384
+ range: backSchemas.model_component_vertex_attribute_range,
385
+ },
386
+ edge: {
387
+ names: backSchemas.model_component_edge_attribute_names,
388
+ range: backSchemas.model_component_edge_attribute_range,
389
+ },
390
+ };
380
391
  </script>
381
392
 
382
393
  <template>
@@ -400,7 +411,7 @@ const edgeSchema = back_schemas.opengeodeweb_back.model_component_edge_attribute
400
411
  v-model:edge_attribute_no_data_color="linesEdgeAttributeNoDataColor"
401
412
  v-model:edge_attribute_time_step="linesEdgeAttributeTimeStep"
402
413
  :capabilities="capabilities"
403
- :schemas="{ vertex: vertexSchema, edge: edgeSchema }"
414
+ :schemas="attributeSchemas"
404
415
  :allowRandom="true"
405
416
  />
406
417
  </OptionsSection>
@@ -414,7 +425,7 @@ const edgeSchema = back_schemas.opengeodeweb_back.model_component_edge_attribute
414
425
  <VisibilitySwitch v-model="lineVisibility" />
415
426
  <ViewerOptionsColoringTypeSelector
416
427
  :id="modelId"
417
- :componentIds="[lineId]"
428
+ :componentIds="lineComponentIds"
418
429
  v-model:coloring_style_key="lineActiveColoring"
419
430
  v-model:color="lineColor"
420
431
  v-model:vertex_attribute_name="vertexAttributeName"
@@ -430,7 +441,7 @@ const edgeSchema = back_schemas.opengeodeweb_back.model_component_edge_attribute
430
441
  v-model:edge_attribute_no_data_color="edgeAttributeNoDataColor"
431
442
  v-model:edge_attribute_time_step="edgeAttributeTimeStep"
432
443
  :capabilities="capabilities"
433
- :schemas="{ vertex: vertexSchema, edge: edgeSchema }"
444
+ :schemas="attributeSchemas"
434
445
  :allowRandom="true"
435
446
  />
436
447
  </OptionsSection>
@@ -30,6 +30,8 @@ const referenceSurfaceId = computed<string | undefined>(() =>
30
30
  collections ? targetSurfaceIds[0] : undefined,
31
31
  );
32
32
 
33
+ const surfaceComponentIds = computed<string[]>(() => (surfaceId === undefined ? [] : [surfaceId]));
34
+
33
35
  // Visibility
34
36
  const surfacesVisibility = computed<boolean>({
35
37
  get: () =>
@@ -426,8 +428,17 @@ const capabilities = {
426
428
  polyhedron: { available: false },
427
429
  };
428
430
 
429
- const vertexSchema = back_schemas.opengeodeweb_back.model_component_vertex_attribute_names;
430
- const polygonSchema = back_schemas.opengeodeweb_back.model_component_polygon_attribute_names;
431
+ const backSchemas = back_schemas.opengeodeweb_back;
432
+ const attributeSchemas = {
433
+ vertex: {
434
+ names: backSchemas.model_component_vertex_attribute_names,
435
+ range: backSchemas.model_component_vertex_attribute_range,
436
+ },
437
+ polygon: {
438
+ names: backSchemas.model_component_polygon_attribute_names,
439
+ range: backSchemas.model_component_polygon_attribute_range,
440
+ },
441
+ };
431
442
  </script>
432
443
 
433
444
  <template>
@@ -451,7 +462,7 @@ const polygonSchema = back_schemas.opengeodeweb_back.model_component_polygon_att
451
462
  v-model:polygon_attribute_no_data_color="surfacesPolygonAttributeNoDataColor"
452
463
  v-model:polygon_attribute_time_step="surfacesPolygonAttributeTimeStep"
453
464
  :capabilities="capabilities"
454
- :schemas="{ vertex: vertexSchema, polygon: polygonSchema }"
465
+ :schemas="attributeSchemas"
455
466
  :allowRandom="true"
456
467
  />
457
468
  </OptionsSection>
@@ -465,7 +476,7 @@ const polygonSchema = back_schemas.opengeodeweb_back.model_component_polygon_att
465
476
  <VisibilitySwitch v-model="surfaceVisibility" />
466
477
  <ViewerOptionsColoringTypeSelector
467
478
  :id="modelId"
468
- :componentIds="[surfaceId]"
479
+ :componentIds="surfaceComponentIds"
469
480
  v-model:coloring_style_key="surfaceActiveColoring"
470
481
  v-model:color="surfaceColor"
471
482
  v-model:vertex_attribute_name="vertexAttributeName"
@@ -481,7 +492,7 @@ const polygonSchema = back_schemas.opengeodeweb_back.model_component_polygon_att
481
492
  v-model:polygon_attribute_no_data_color="polygonAttributeNoDataColor"
482
493
  v-model:polygon_attribute_time_step="polygonAttributeTimeStep"
483
494
  :capabilities="capabilities"
484
- :schemas="{ vertex: vertexSchema, polygon: polygonSchema }"
495
+ :schemas="attributeSchemas"
485
496
  :allowRandom="true"
486
497
  />
487
498
  </OptionsSection>
@@ -7,6 +7,40 @@ const emit = defineEmits<Emits>();
7
7
 
8
8
  const minimum = defineModel<number>("minimum");
9
9
  const maximum = defineModel<number>("maximum");
10
+
11
+ const minimumText = ref<string>("");
12
+ const maximumText = ref<string>("");
13
+
14
+ watch(minimum, (value) => (minimumText.value = value === undefined ? "" : String(value)), {
15
+ immediate: true,
16
+ });
17
+ watch(maximum, (value) => (maximumText.value = value === undefined ? "" : String(value)), {
18
+ immediate: true,
19
+ });
20
+
21
+ // Text inputs (not type="number") so scientific notation like 1.5e7 can be typed, committed on enter or blur.
22
+ function parseValue(text: string): number | undefined {
23
+ const value = Number(text.trim());
24
+ return text.trim() === "" || !Number.isFinite(value) ? undefined : value;
25
+ }
26
+
27
+ function commitMinimum(): void {
28
+ const value = parseValue(minimumText.value);
29
+ if (value === undefined) {
30
+ minimumText.value = minimum.value === undefined ? "" : String(minimum.value);
31
+ return;
32
+ }
33
+ minimum.value = value;
34
+ }
35
+
36
+ function commitMaximum(): void {
37
+ const value = parseValue(maximumText.value);
38
+ if (value === undefined) {
39
+ maximumText.value = maximum.value === undefined ? "" : String(maximum.value);
40
+ return;
41
+ }
42
+ maximum.value = value;
43
+ }
10
44
  </script>
11
45
 
12
46
  <template>
@@ -14,11 +48,11 @@ const maximum = defineModel<number>("maximum");
14
48
  <v-col cols="5" class="pe-1">
15
49
  <v-text-field
16
50
  data-testid="attributeMinInput"
17
- :model-value="minimum"
18
- @update:model-value="(value) => (minimum = Number(value))"
51
+ v-model="minimumText"
52
+ @keydown.enter="commitMinimum"
53
+ @blur="commitMinimum"
19
54
  label="Min"
20
- type="number"
21
- :max="maximum"
55
+ inputmode="decimal"
22
56
  density="compact"
23
57
  hide-details
24
58
  variant="outlined"
@@ -38,11 +72,11 @@ const maximum = defineModel<number>("maximum");
38
72
  <v-col cols="5" class="ps-1">
39
73
  <v-text-field
40
74
  data-testid="attributeMaxInput"
41
- :model-value="maximum"
42
- @update:model-value="(value) => (maximum = Number(value))"
75
+ v-model="maximumText"
76
+ @keydown.enter="commitMaximum"
77
+ @blur="commitMaximum"
43
78
  label="Max"
44
- type="number"
45
- :min="minimum"
79
+ inputmode="decimal"
46
80
  density="compact"
47
81
  hide-details
48
82
  variant="outlined"
@@ -4,9 +4,13 @@ import {
4
4
  type RangesPerData,
5
5
  useBatchGroup,
6
6
  } from "@ogw_front/composables/batch_style";
7
+ import {
8
+ type AttributeRangeInfo,
9
+ type AttributeSchemas,
10
+ fetchAttributeRange,
11
+ } from "@ogw_front/utils/attribute_range";
7
12
  import { getAttributeRange, intersectAttributes } from "@ogw_front/utils/attributes";
8
13
  import { DEFAULT_NO_DATA_COLOR } from "@ogw_front/utils/default_styles/constants";
9
- import type { JsonRpcSchema } from "@ogw_shared/utils/types.js";
10
14
  import ViewerOptionsAttributeColorBar from "@ogw_front/components/Viewer/Options/AttributeColorBar.vue";
11
15
  import ViewerOptionsColorPicker from "@ogw_front/components/Viewer/Options/ColorPicker.vue";
12
16
  import ViewerOptionsTimeStepSlider from "@ogw_front/components/Viewer/Options/TimeStepSlider.vue";
@@ -27,10 +31,10 @@ const attributeTimeStep = defineModel<number>("attributeTimeStep");
27
31
  interface Props {
28
32
  id: string;
29
33
  componentIds?: string[];
30
- schema: JsonRpcSchema;
34
+ schemas: AttributeSchemas;
31
35
  }
32
36
 
33
- const { id, componentIds = undefined, schema } = defineProps<Props>();
37
+ const { id, componentIds = undefined, schemas } = defineProps<Props>();
34
38
 
35
39
  interface Emits {
36
40
  "update:attributeColorMap": [colorMap: string];
@@ -43,13 +47,15 @@ const emit = defineEmits<Emits>();
43
47
  interface AttributeInfo {
44
48
  attribute_name: string;
45
49
  nb_items: number;
46
- no_data?: boolean;
47
50
  time_steps?: number[];
48
51
  [key: string]: unknown;
49
52
  }
50
53
 
51
54
  const attributes = ref<AttributeInfo[]>([]);
52
55
  let attributesPerTarget = new Map<string, AttributeInfo[]>();
56
+ const rangesPerTarget = ref(new Map<string, AttributeRangeInfo>());
57
+ let rangesName: string | undefined = undefined;
58
+ let rangesLoading: Promise<void> = Promise.resolve();
53
59
 
54
60
  const groupTargetIds = useBatchGroup(() => id);
55
61
 
@@ -57,6 +63,9 @@ const currentAttribute = computed<AttributeInfo | undefined>(() =>
57
63
  attributes.value.find((attr) => attr.attribute_name === attributeName.value),
58
64
  );
59
65
  const timeSteps = computed<number[]>(() => currentAttribute.value?.time_steps ?? []);
66
+ const noData = computed<boolean>(() =>
67
+ [...rangesPerTarget.value.values()].some((range) => range.no_data),
68
+ );
60
69
 
61
70
  let committedSeries: string | undefined = undefined;
62
71
 
@@ -72,7 +81,10 @@ function commitSeriesTimeStep(): void {
72
81
  committedSeries = name;
73
82
  const current = attributeTimeStep.value;
74
83
  const valid = current !== undefined && current >= 0 && current < timeSteps.value.length;
75
- emit("update:attributeTimeStep", valid ? current : 0);
84
+ // A valid step is already stored and applied with the series: emitting it again re-applies the attribute
85
+ if (!valid) {
86
+ emit("update:attributeTimeStep", 0);
87
+ }
76
88
  }
77
89
 
78
90
  const cssNoDataColor = computed<string>(() => {
@@ -118,68 +130,105 @@ const componentItems = computed<{ title: string; value: number }[]>(() => {
118
130
  }));
119
131
  });
120
132
 
121
- function resetRange(): void {
122
- if (currentAttribute.value) {
123
- const comp = attributeItem.value ?? 0;
124
- // GetAttributeRange's parameter type (AttributeRangeSource) isn't exported;
125
- // AttributeInfo's index signature covers its optional min/max fields at
126
- // Runtime (they come from the same backend attribute response shape).
127
- const { min, max } = getAttributeRange(
128
- currentAttribute.value as unknown as Parameters<typeof getAttributeRange>[0],
129
- comp,
130
- );
131
- attributeRange.value = [min, max];
133
+ function hasSelectedComponent(components: unknown): boolean {
134
+ return Array.isArray(components) && components.length > 0;
135
+ }
136
+
137
+ function requestParams(): { id: string; component_ids?: string[] } | undefined {
138
+ const schemaProperties = schemas.names.properties as Record<string, unknown> | undefined;
139
+ if (schemaProperties?.component_ids === undefined) {
140
+ return { id };
132
141
  }
142
+ return hasSelectedComponent(componentIds) ? { id, component_ids: componentIds } : undefined;
133
143
  }
134
144
 
135
- function hasSelectedComponent(components: unknown): boolean {
136
- return Array.isArray(components) && components.length > 0;
145
+ // Ranges are computed by the back for the selected attribute only, as scanning every step of every series is slow
146
+ async function requestRanges(name: string): Promise<void> {
147
+ const targets: [string, { id: string; component_ids?: string[] } | undefined][] =
148
+ groupTargetIds.value
149
+ ? [...attributesPerTarget]
150
+ .filter(([, targetAttributes]) =>
151
+ targetAttributes.some((attribute) => attribute.attribute_name === name),
152
+ )
153
+ .map(([targetId]) => [targetId, { id: targetId }])
154
+ : [[id, requestParams()]];
155
+ const entries = await Promise.all(
156
+ targets.map(async ([targetId, params]): Promise<[string, AttributeRangeInfo | undefined]> => [
157
+ targetId,
158
+ params ? await fetchAttributeRange(schemas.range, params, name) : undefined,
159
+ ]),
160
+ );
161
+ if (rangesName !== name) {
162
+ return;
163
+ }
164
+ rangesPerTarget.value = new Map(
165
+ entries.filter((entry): entry is [string, AttributeRangeInfo] => entry[1] !== undefined),
166
+ );
167
+ }
168
+
169
+ async function loadRanges(name: string): Promise<void> {
170
+ if (name !== rangesName) {
171
+ rangesName = name;
172
+ rangesPerTarget.value = new Map();
173
+ rangesLoading = requestRanges(name);
174
+ }
175
+ await rangesLoading;
176
+ }
177
+
178
+ function applyRange(): void {
179
+ const { min, max } = getAttributeRange(rangesPerTarget.value.get(id), attributeItem.value ?? 0);
180
+ attributeRange.value = [min, max];
181
+ }
182
+
183
+ async function resetRange(): Promise<void> {
184
+ if (currentAttribute.value) {
185
+ await loadRanges(currentAttribute.value.attribute_name);
186
+ applyRange();
187
+ }
137
188
  }
138
189
 
139
190
  async function getGroupAttributes(targetIds: string[]): Promise<void> {
140
- const responses = await requestForTargets<{ attributes: AttributeInfo[] }>(schema, targetIds, {
141
- attributes: [],
142
- });
191
+ const responses = await requestForTargets<{ attributes: AttributeInfo[] }>(
192
+ schemas.names,
193
+ targetIds,
194
+ {
195
+ attributes: [],
196
+ },
197
+ );
143
198
  attributesPerTarget = new Map(
144
199
  responses.map(([targetId, response]) => [targetId, response.attributes]),
145
200
  );
146
201
  attributes.value = intersectAttributes([...attributesPerTarget.values()]);
147
202
  }
148
203
 
149
- function initGroupAttribute(name: string, item: number): void {
204
+ async function initGroupAttribute(name: string, item: number): Promise<void> {
205
+ await loadRanges(name);
206
+ if (attributeName.value !== name) {
207
+ return;
208
+ }
150
209
  const ranges = new Map<string, AttributeRange>();
151
- for (const [targetId, targetAttributes] of attributesPerTarget) {
152
- const attribute = targetAttributes.find((candidate) => candidate.attribute_name === name);
153
- if (attribute) {
154
- const { min, max } = getAttributeRange(
155
- attribute as unknown as Parameters<typeof getAttributeRange>[0],
156
- item,
157
- );
158
- ranges.set(targetId, [min, max]);
159
- }
210
+ for (const [targetId, range] of rangesPerTarget.value) {
211
+ const { min, max } = getAttributeRange(range, item);
212
+ ranges.set(targetId, [min, max]);
160
213
  }
161
214
  emit("update:attributeColorMap", attributeColorMap.value ?? "batlow");
162
215
  emit("ranges_per_data", ranges);
163
216
  }
164
217
 
165
218
  async function getAttributes(): Promise<void> {
219
+ rangesName = undefined;
220
+ rangesPerTarget.value = new Map();
166
221
  if (groupTargetIds.value) {
167
222
  await getGroupAttributes(groupTargetIds.value);
168
223
  return;
169
224
  }
170
- const schemaProperties = schema.properties as Record<string, unknown> | undefined;
171
- const requiresComponent = schemaProperties?.component_ids !== undefined;
172
- if (requiresComponent && !hasSelectedComponent(componentIds)) {
225
+ const params = requestParams();
226
+ if (!params) {
173
227
  return;
174
228
  }
175
229
 
176
- const params: { id: string; component_ids?: unknown } = { id };
177
- if (requiresComponent) {
178
- params.component_ids = componentIds;
179
- }
180
-
181
230
  await backStore.request(
182
- { schema, params },
231
+ { schema: schemas.names, params },
183
232
  {
184
233
  response_function: (response: unknown) => {
185
234
  attributes.value = (response as { attributes: AttributeInfo[] }).attributes;
@@ -193,19 +242,19 @@ onMounted(async () => {
193
242
  });
194
243
 
195
244
  watch(
196
- () => [id, componentIds, schema],
245
+ [(): string => id, (): string[] | undefined => componentIds, (): AttributeSchemas => schemas],
197
246
  async () => {
198
247
  await getAttributes();
199
248
  },
200
249
  );
201
250
 
202
- watch([attributeName, attributeItem], ([name, item]) => {
251
+ watch([attributeName, attributeItem], async ([name, item]) => {
203
252
  if (groupTargetIds.value && name !== undefined) {
204
- initGroupAttribute(name, item ?? 0);
253
+ await initGroupAttribute(name, item ?? 0);
205
254
  }
206
255
  });
207
256
 
208
- watch([attributeName, attributeItem, currentAttribute], () => {
257
+ watch([attributeName, attributeItem, currentAttribute], async () => {
209
258
  commitSeriesTimeStep();
210
259
  if (groupTargetIds.value) {
211
260
  return;
@@ -216,8 +265,13 @@ watch([attributeName, attributeItem, currentAttribute], () => {
216
265
  if (attributeNoDataColor.value === undefined) {
217
266
  attributeNoDataColor.value = DEFAULT_NO_DATA_COLOR;
218
267
  }
219
- if (attributeRange.value[0] === undefined) {
220
- resetRange();
268
+ const name = currentAttribute.value?.attribute_name;
269
+ if (name === undefined) {
270
+ return;
271
+ }
272
+ await loadRanges(name);
273
+ if (name === currentAttribute.value?.attribute_name && attributeRange.value[0] === undefined) {
274
+ applyRange();
221
275
  }
222
276
  });
223
277
  </script>
@@ -247,7 +301,7 @@ watch([attributeName, attributeItem, currentAttribute], () => {
247
301
  />
248
302
  <ViewerOptionsTimeStepSlider v-model="attributeTimeStep" :time-steps="timeSteps" />
249
303
  <div
250
- v-if="currentAttribute && currentAttribute.no_data"
304
+ v-if="currentAttribute && noData"
251
305
  class="text-caption text-high-emphasis mt-1 d-flex align-center ga-1"
252
306
  data-testid="noDataInfo"
253
307
  >
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { JsonRpcSchema } from "@ogw_shared/utils/types.js";
2
+ import type { AttributeSchemas } from "@ogw_front/utils/attribute_range";
3
3
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
4
4
  import type { RangesPerData } from "@ogw_front/composables/batch_style";
5
5
  import ViewerOptionsAttributeSelector from "@ogw_front/components/Viewer/Options/AttributeSelector.vue";
@@ -18,11 +18,11 @@ interface CapabilityConfig {
18
18
  }
19
19
 
20
20
  interface Schemas {
21
- vertex?: JsonRpcSchema;
22
- edge?: JsonRpcSchema;
23
- cell?: JsonRpcSchema;
24
- polygon?: JsonRpcSchema;
25
- polyhedron?: JsonRpcSchema;
21
+ vertex?: AttributeSchemas;
22
+ edge?: AttributeSchemas;
23
+ cell?: AttributeSchemas;
24
+ polygon?: AttributeSchemas;
25
+ polyhedron?: AttributeSchemas;
26
26
  }
27
27
 
28
28
  const coloring_style_key = defineModel<string>("coloring_style_key");
@@ -95,12 +95,27 @@ const {
95
95
  allowRandom = false,
96
96
  } = defineProps<Props>();
97
97
 
98
- const vertexSchema = schemas.vertex || back_schemas.opengeodeweb_back.vertex_attribute_names;
99
- const edgeSchema = schemas.edge || back_schemas.opengeodeweb_back.edge_attribute_names;
100
- const cellSchema = schemas.cell || back_schemas.opengeodeweb_back.cell_attribute_names;
101
- const polygonSchema = schemas.polygon || back_schemas.opengeodeweb_back.polygon_attribute_names;
102
- const polyhedronSchema =
103
- schemas.polyhedron || back_schemas.opengeodeweb_back.polyhedron_attribute_names;
98
+ const backSchemas = back_schemas.opengeodeweb_back;
99
+ const vertexSchemas = schemas.vertex ?? {
100
+ names: backSchemas.vertex_attribute_names,
101
+ range: backSchemas.vertex_attribute_range,
102
+ };
103
+ const edgeSchemas = schemas.edge ?? {
104
+ names: backSchemas.edge_attribute_names,
105
+ range: backSchemas.edge_attribute_range,
106
+ };
107
+ const cellSchemas = schemas.cell ?? {
108
+ names: backSchemas.cell_attribute_names,
109
+ range: backSchemas.cell_attribute_range,
110
+ };
111
+ const polygonSchemas = schemas.polygon ?? {
112
+ names: backSchemas.polygon_attribute_names,
113
+ range: backSchemas.polygon_attribute_range,
114
+ };
115
+ const polyhedronSchemas = schemas.polyhedron ?? {
116
+ names: backSchemas.polyhedron_attribute_names,
117
+ range: backSchemas.polyhedron_attribute_range,
118
+ };
104
119
 
105
120
  function isAvailable(key: string): boolean {
106
121
  if (capabilities[key] && capabilities[key].available === false) {
@@ -266,7 +281,7 @@ watch(
266
281
  @ranges_per_data="emit('vertex_attribute_ranges_per_data', $event)"
267
282
  :id="id"
268
283
  :componentIds="componentIds"
269
- :schema="vertexSchema"
284
+ :schemas="vertexSchemas"
270
285
  />
271
286
  </template>
272
287
  <template v-if="active_key === edge_dict['value'] && hasColorMap('edge')">
@@ -280,7 +295,7 @@ watch(
280
295
  @ranges_per_data="emit('edge_attribute_ranges_per_data', $event)"
281
296
  :id="id"
282
297
  :componentIds="componentIds"
283
- :schema="edgeSchema"
298
+ :schemas="edgeSchemas"
284
299
  />
285
300
  </template>
286
301
  <template v-if="active_key === cell_dict['value'] && hasColorMap('cell')">
@@ -294,7 +309,7 @@ watch(
294
309
  @ranges_per_data="emit('cell_attribute_ranges_per_data', $event)"
295
310
  :id="id"
296
311
  :componentIds="componentIds"
297
- :schema="cellSchema"
312
+ :schemas="cellSchemas"
298
313
  />
299
314
  </template>
300
315
  <template v-if="active_key === polygon_dict['value'] && hasColorMap('polygon')">
@@ -308,7 +323,7 @@ watch(
308
323
  @ranges_per_data="emit('polygon_attribute_ranges_per_data', $event)"
309
324
  :id="id"
310
325
  :componentIds="componentIds"
311
- :schema="polygonSchema"
326
+ :schemas="polygonSchemas"
312
327
  />
313
328
  </template>
314
329
  <template v-if="active_key === polyhedron_dict['value'] && hasColorMap('polyhedron')">
@@ -322,7 +337,7 @@ watch(
322
337
  @ranges_per_data="emit('polyhedron_attribute_ranges_per_data', $event)"
323
338
  :id="id"
324
339
  :componentIds="componentIds"
325
- :schema="polyhedronSchema"
340
+ :schemas="polyhedronSchemas"
326
341
  />
327
342
  </template>
328
343
  </v-col>
@@ -1,5 +1,6 @@
1
1
  import type { JsonRpcSchema } from "@ogw_shared/utils/types";
2
2
  import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
3
+ import { fetchAttributeRange } from "@ogw_front/utils/attribute_range";
3
4
  import { getAttributeRange } from "@ogw_front/utils/attributes";
4
5
  import { useBackStore } from "@ogw_front/stores/back";
5
6
  import { useDataStyleStore } from "@ogw_front/stores/data_style";
@@ -35,10 +36,6 @@ interface AttributeStyleComponent {
35
36
  interface AttributeResponse {
36
37
  readonly attributes?: readonly {
37
38
  readonly attribute_name?: string;
38
- readonly min_values?: readonly number[];
39
- readonly max_values?: readonly number[];
40
- readonly min_value?: number;
41
- readonly max_value?: number;
42
39
  readonly time_steps?: readonly number[];
43
40
  }[];
44
41
  }
@@ -259,39 +256,29 @@ export function useGlobalAttributeStyle(dataIdRef: Ref<string | undefined>): {
259
256
  continue;
260
257
  }
261
258
 
262
- const schemaName = `${activeColoring}_attribute_names`;
263
- const backSchemas = back_schemas.opengeodeweb_back as Record<string, unknown>;
264
- const schema = backSchemas[schemaName];
265
- if (schema === undefined || !isJsonRpcSchema(schema)) {
259
+ const rangeSchema = (back_schemas.opengeodeweb_back as Record<string, unknown>)[
260
+ `${activeColoring}_attribute_range`
261
+ ];
262
+ if (!isJsonRpcSchema(rangeSchema)) {
266
263
  continue;
267
264
  }
268
265
 
269
266
  requestPromises.push(
270
- backStore.request(
271
- {
272
- schema,
273
- params: { id: targetId },
274
- },
275
- {
276
- response_function: async (response: unknown): Promise<void> => {
277
- if (!isAttributeResponse(response)) {
278
- return;
279
- }
280
- const attributes = response.attributes ?? [];
281
- const currentAttribute = attributes.find((attr) => attr.attribute_name === attrName);
282
- if (currentAttribute) {
283
- const { min, max } = getAttributeRange(currentAttribute, attrItem);
284
-
285
- const setterName = `set${setterKey}${attributeType}Range`;
286
- const setter = getDynamicStoreMethod(dataStyleStore, setterName);
287
- if (setter) {
288
- setter(targetId, min, max);
289
- await hybridViewerStore.remoteRender();
290
- }
291
- }
292
- },
293
- },
294
- ),
267
+ (async (): Promise<void> => {
268
+ const range = await fetchAttributeRange(rangeSchema, { id: targetId }, attrName);
269
+ if (!range) {
270
+ return;
271
+ }
272
+ const { min, max } = getAttributeRange(range, attrItem);
273
+ const setter = getDynamicStoreMethod(
274
+ dataStyleStore,
275
+ `set${setterKey}${attributeType}Range`,
276
+ );
277
+ if (setter) {
278
+ setter(targetId, min, max);
279
+ await hybridViewerStore.remoteRender();
280
+ }
281
+ })(),
295
282
  );
296
283
  }
297
284
 
@@ -0,0 +1,56 @@
1
+ import type { JsonRpcSchema } from "@ogw_shared/utils/types.js";
2
+ import { useBackStore } from "@ogw_front/stores/back";
3
+
4
+ interface AttributeRangeInfo {
5
+ min_values: number[];
6
+ max_values: number[];
7
+ no_data: boolean;
8
+ }
9
+
10
+ // The *_attribute_names route listing the attributes, and its *_attribute_range route
11
+ interface AttributeSchemas {
12
+ names: JsonRpcSchema;
13
+ range: JsonRpcSchema;
14
+ }
15
+
16
+ interface AttributeRangeTarget {
17
+ id: string;
18
+ component_ids?: string[];
19
+ }
20
+
21
+ // A data never changes once imported: selectors showing the same attribute share one request
22
+ const rangeRequests = new Map<string, Promise<AttributeRangeInfo | undefined>>();
23
+
24
+ async function requestAttributeRange(
25
+ rangeSchema: JsonRpcSchema,
26
+ params: Record<string, unknown>,
27
+ key: string,
28
+ ): Promise<AttributeRangeInfo | undefined> {
29
+ const backStore = useBackStore();
30
+ try {
31
+ const range = await backStore.request({ schema: rangeSchema, params });
32
+ // oxlint-disable-next-line no-unsafe-type-assertion -- every *_attribute_range schema shares this response.
33
+ return range as AttributeRangeInfo;
34
+ } catch {
35
+ rangeRequests.delete(key);
36
+ return undefined;
37
+ }
38
+ }
39
+
40
+ async function fetchAttributeRange(
41
+ rangeSchema: JsonRpcSchema,
42
+ target: AttributeRangeTarget,
43
+ attributeName: string,
44
+ ): Promise<AttributeRangeInfo | undefined> {
45
+ const params = { ...target, attribute_name: attributeName };
46
+ const key = JSON.stringify([rangeSchema.$id, params]);
47
+ let request = rangeRequests.get(key);
48
+ if (request === undefined) {
49
+ request = requestAttributeRange(rangeSchema, params, key);
50
+ rangeRequests.set(key, request);
51
+ }
52
+ const range = await request;
53
+ return range;
54
+ }
55
+
56
+ export { type AttributeRangeInfo, type AttributeSchemas, fetchAttributeRange };
@@ -72,7 +72,7 @@ interface UseModelBlocksPolyhedronAttributeReturn {
72
72
  modelId: string,
73
73
  blockIds: string[],
74
74
  timeStep: number,
75
- ) => Promise<void>;
75
+ ) => Promise<boolean>;
76
76
  modelBlocksPolyhedronAttributeColorMap: (modelId: string, blockId?: string) => string | undefined;
77
77
  modelBlocksPolyhedronAttributeStoredConfig: (
78
78
  modelId: string,
@@ -89,29 +89,29 @@ interface UseModelBlocksPolyhedronAttributeReturn {
89
89
  modelId: string,
90
90
  blockIds: string[],
91
91
  name: string,
92
- ) => Promise<unknown>;
92
+ ) => Promise<boolean>;
93
93
  setModelBlocksPolyhedronAttributeItem: (
94
94
  modelId: string,
95
95
  blockIds: string[],
96
96
  item: number,
97
- ) => Promise<unknown>;
97
+ ) => Promise<boolean>;
98
98
  setModelBlocksPolyhedronAttributeRange: (
99
99
  modelId: string,
100
100
  blockIds: string[],
101
101
  minimum: number,
102
102
  maximum: number,
103
- ) => Promise<unknown>;
103
+ ) => Promise<boolean>;
104
104
  setModelBlocksPolyhedronAttributeColorMap: (
105
105
  modelId: string,
106
106
  blockIds: string[],
107
107
  colorMap: string | undefined,
108
- ) => Promise<unknown>;
108
+ ) => Promise<boolean>;
109
109
  modelBlocksPolyhedronAttributeNoDataColor: (modelId: string, blockId?: string) => RGBAColor;
110
110
  setModelBlocksPolyhedronAttributeNoDataColor: (
111
111
  modelId: string,
112
112
  blockIds: string[],
113
113
  no_data_color: RGBAColor,
114
- ) => Promise<unknown>;
114
+ ) => Promise<boolean>;
115
115
  }
116
116
 
117
117
  // oxlint-disable-next-line max-lines-per-function
@@ -281,7 +281,8 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
281
281
  params,
282
282
  });
283
283
  }
284
- async function applyPolyhedronAttribute(modelId: string, blockIds: string[]): Promise<unknown> {
284
+ // True when the attribute was sent to the viewer, so the caller knows a render is needed
285
+ async function applyPolyhedronAttribute(modelId: string, blockIds: string[]): Promise<boolean> {
285
286
  const name = modelBlocksPolyhedronAttributeName(modelId, blockIds[0]);
286
287
  const item = modelBlocksPolyhedronAttributeItem(modelId, blockIds[0]);
287
288
  const storedConfig = modelBlocksPolyhedronAttributeStoredConfig(
@@ -298,17 +299,17 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
298
299
  colorMap: storedConfig.colorMap,
299
300
  no_data_color: storedConfig.no_data_color,
300
301
  };
301
- if (isModelBlocksPolyhedronAttributeValid(attribute)) {
302
- const result = await setModelBlocksPolyhedronAttribute(modelId, blockIds, attribute);
303
- return result;
302
+ if (!isModelBlocksPolyhedronAttributeValid(attribute)) {
303
+ return false;
304
304
  }
305
- return undefined;
305
+ await setModelBlocksPolyhedronAttribute(modelId, blockIds, attribute);
306
+ return true;
306
307
  }
307
308
  async function setModelBlocksPolyhedronAttributeName(
308
309
  modelId: string,
309
310
  blockIds: string[],
310
311
  name: string,
311
- ): Promise<unknown> {
312
+ ): Promise<boolean> {
312
313
  const item = modelBlocksPolyhedronAttributeLastItem(modelId, blockIds[0], name);
313
314
  await mutateModelBlocksPolyhedronStyle(modelId, blockIds, {
314
315
  name,
@@ -320,7 +321,7 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
320
321
  modelId: string,
321
322
  blockIds: string[],
322
323
  item: number,
323
- ): Promise<unknown> {
324
+ ): Promise<boolean> {
324
325
  await mutateModelBlocksPolyhedronStyle(modelId, blockIds, {
325
326
  item,
326
327
  });
@@ -330,13 +331,14 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
330
331
  modelId: string,
331
332
  blockIds: string[],
332
333
  timeStep: number,
333
- ): Promise<void> {
334
+ ): Promise<boolean> {
334
335
  const name = modelBlocksPolyhedronAttributeName(modelId, blockIds[0]);
335
336
  if (name === undefined) {
336
- return;
337
+ return false;
337
338
  }
338
339
  await setAttributeTimeStep(modelId, modelBlockPolyhedronAttributeSchema.$id, name, timeStep);
339
- await Promise.all([
340
+ let reapplied = false;
341
+ const [applied] = await Promise.all([
340
342
  applyPolyhedronAttribute(modelId, blockIds),
341
343
  reapplySameSeries(
342
344
  await dataStore.getBlocksGeodeIds(modelId),
@@ -352,17 +354,18 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
352
354
  ].join("|")
353
355
  : undefined,
354
356
  async (ids) => {
355
- await applyPolyhedronAttribute(modelId, ids);
357
+ reapplied = (await applyPolyhedronAttribute(modelId, ids)) || reapplied;
356
358
  },
357
359
  ),
358
360
  ]);
361
+ return applied || reapplied;
359
362
  }
360
363
  async function setModelBlocksPolyhedronAttributeRange(
361
364
  modelId: string,
362
365
  blockIds: string[],
363
366
  minimum: number,
364
367
  maximum: number,
365
- ): Promise<unknown> {
368
+ ): Promise<boolean> {
366
369
  const name = modelBlocksPolyhedronAttributeName(modelId, blockIds[0]);
367
370
  const item = modelBlocksPolyhedronAttributeItem(modelId, blockIds[0]);
368
371
  await setModelBlocksPolyhedronAttributeStoredConfig(modelId, blockIds, name, item, {
@@ -375,7 +378,7 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
375
378
  modelId: string,
376
379
  blockIds: string[],
377
380
  colorMap: string | undefined,
378
- ): Promise<unknown> {
381
+ ): Promise<boolean> {
379
382
  const name = modelBlocksPolyhedronAttributeName(modelId, blockIds[0]);
380
383
  const item = modelBlocksPolyhedronAttributeItem(modelId, blockIds[0]);
381
384
  await setModelBlocksPolyhedronAttributeStoredConfig(modelId, blockIds, name, item, {
@@ -387,7 +390,7 @@ function useModelBlocksPolyhedronAttribute(): UseModelBlocksPolyhedronAttributeR
387
390
  modelId: string,
388
391
  blockIds: string[],
389
392
  no_data_color: RGBAColor,
390
- ): Promise<unknown> {
393
+ ): Promise<boolean> {
391
394
  const name = modelBlocksPolyhedronAttributeName(modelId, blockIds[0]);
392
395
  const item = modelBlocksPolyhedronAttributeItem(modelId, blockIds[0]);
393
396
  const storedConfig = modelBlocksPolyhedronAttributeStoredConfig(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.40.0-rc.2",
3
+ "version": "10.40.1-rc.1",
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": {