@vcmap/ui 6.3.1 → 6.3.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/dist/assets/ui.js CHANGED
@@ -1 +1 @@
1
- export * from "./ui-e52e6b43.js";
1
+ export * from "./ui-a0e9dbf6.js";
@@ -1 +1 @@
1
- export * from "./vue-93e3a003.js";
1
+ export * from "./vue-f6681844.js";
@@ -10,7 +10,7 @@ function loadCss(href) {
10
10
  elem.onerror = reject;
11
11
  document.head.appendChild(elem);
12
12
  });
13
- } await loadCss('./assets/vuetify-a61a435f.css');import { watch as Q, onScopeDispose as Ze, effectScope as Zl, shallowRef as K, reactive as it, computed as b, watchEffect as Fe, toRefs as Yt, capitalize as Nn, unref as ot, Fragment as ie, isVNode as Rc, Comment as Nc, warn as ja, getCurrentInstance as Hc, ref as j, provide as De, inject as ye, defineComponent as zc, h as Gt, camelize as Ir, toRaw as Be, createVNode as r, mergeProps as O, onBeforeUnmount as Qe, readonly as Ql, onMounted as Ye, onDeactivated as _r, onActivated as Wc, nextTick as we, TransitionGroup as Jl, Transition as jt, isRef as Tn, toRef as $, onBeforeMount as ra, withDirectives as $e, resolveDirective as gt, vShow as Ct, onUpdated as jc, Text as Uc, resolveDynamicComponent as Yc, markRaw as Gc, Teleport as Kc, cloneVNode as qc, createTextVNode as Tt, onUnmounted as Tr, onBeforeUpdate as Xc, withModifiers as Tl, toDisplayString as Zc, vModelText as Qc, resolveComponent as Jc, render as Ar } from "./vue-93e3a003.js";
13
+ } await loadCss('./assets/vuetify-08553f7d.css');import { watch as Q, onScopeDispose as Ze, effectScope as Zl, shallowRef as K, reactive as it, computed as b, watchEffect as Fe, toRefs as Yt, capitalize as Nn, unref as ot, Fragment as ie, isVNode as Rc, Comment as Nc, warn as ja, getCurrentInstance as Hc, ref as j, provide as De, inject as ye, defineComponent as zc, h as Gt, camelize as Ir, toRaw as Be, createVNode as r, mergeProps as O, onBeforeUnmount as Qe, readonly as Ql, onMounted as Ye, onDeactivated as _r, onActivated as Wc, nextTick as we, TransitionGroup as Jl, Transition as jt, isRef as Tn, toRef as $, onBeforeMount as ra, withDirectives as $e, resolveDirective as gt, vShow as Ct, onUpdated as jc, Text as Uc, resolveDynamicComponent as Yc, markRaw as Gc, Teleport as Kc, cloneVNode as qc, createTextVNode as Tt, onUnmounted as Tr, onBeforeUpdate as Xc, withModifiers as Tl, toDisplayString as Zc, vModelText as Qc, resolveComponent as Jc, render as Ar } from "./vue-f6681844.js";
14
14
  function rt(e, n) {
15
15
  let t;
16
16
  function a() {
@@ -1 +1 @@
1
- export * from "./vuetify-a61a435f.js";
1
+ export * from "./vuetify-08553f7d.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcmap/ui",
3
- "version": "6.3.1",
3
+ "version": "6.3.2",
4
4
  "author": "Virtual City Systems",
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@vcmap-cesium/engine": "^11.0.5",
61
- "@vcmap/core": "^6.3.2",
61
+ "@vcmap/core": "^6.3.3",
62
62
  "ol": "^10.4.0",
63
63
  "vue": "~3.4.38",
64
64
  "vuetify": "~3.7.14"
@@ -299,6 +299,9 @@ export function createDeepPickingAction(app, layer, collectFeatures, event) {
299
299
  app,
300
300
  features.filter((f) => featurePredicate(app, f)),
301
301
  event.position,
302
+ () => {
303
+ app.windowManager.remove(deepPickingWindowId);
304
+ },
302
305
  );
303
306
 
304
307
  if (app.windowManager.has(deepPickingWindowId)) {
@@ -87,11 +87,12 @@ export function applyDoubleUnderscoreFilter(attributes: {
87
87
  /**
88
88
  * Filters attributes having an empty object as value
89
89
  * @param {Object<string, unknown>} attributes
90
+ * @param {boolean} removeAllEmpty - if true, will remove all empty attributes, otherwise only empty objects
90
91
  * @returns {Object}
91
92
  */
92
93
  export function applyEmptyAttributesFilter(attributes: {
93
94
  [x: string]: unknown;
94
- }): Object;
95
+ }, removeAllEmpty?: boolean): Object;
95
96
  export default AbstractFeatureInfoView;
96
97
  export type FeatureInfoProps = {
97
98
  featureId: string;
@@ -111,6 +112,8 @@ export type FeatureInfoViewOptions = import("@vcmap/core").VcsObjectOptions & {
111
112
  attributeKeys?: string[];
112
113
  keyMapping?: Record<string, string>;
113
114
  valueMapping?: Record<string, string | Record<string, string>>;
115
+ mergeParentAttributes?: boolean;
116
+ removeNoDataAttributes?: boolean;
114
117
  tags?: Record<string, HTMLTagOptions>;
115
118
  window?: Pick<import("../manager/window/windowManager.js").WindowComponentOptions, 'state' | 'slot' | 'position'>;
116
119
  };
@@ -147,6 +150,14 @@ declare class AbstractFeatureInfoView extends VcsObject {
147
150
  [x: string]: string;
148
151
  };
149
152
  };
153
+ /**
154
+ * @type {boolean}
155
+ */
156
+ mergeParentAttributes: boolean;
157
+ /**
158
+ * @type {boolean}
159
+ */
160
+ removeNoDataAttributes: boolean;
150
161
  /**
151
162
  * @type {null|Object<string,HTMLTagOptions>}
152
163
  */
@@ -1,3 +1,4 @@
1
+ import { parseBoolean } from '@vcsuite/parsers';
1
2
  import { renderTemplate, VcsObject } from '@vcmap/core';
2
3
  import { WindowSlot } from '../manager/window/windowManager.js';
3
4
  import { defaultTagOptions } from '../components/tables/VcsTable.vue';
@@ -22,12 +23,16 @@ import { defaultTagOptions } from '../components/tables/VcsTable.vue';
22
23
  * attributeKeys?: string[],
23
24
  * keyMapping?: Record<string,string>,
24
25
  * valueMapping?: Record<string, string|Record<string,string>>,
26
+ * mergeParentAttributes?: boolean,
27
+ * removeNoDataAttributes?: boolean,
25
28
  * tags?: Record<string, HTMLTagOptions>,
26
29
  * window?: Pick<import("../manager/window/windowManager.js").WindowComponentOptions,'state'|'slot'|'position'>
27
30
  * }} FeatureInfoViewOptions
28
31
  * @property {Array<string>} [attributeKeys] - list of keys to filter attributes of selected feature
29
32
  * @property {Object<string,string>} [keyMapping] - object providing text replacements or i18n strings for attribute keys
30
33
  * @property {Object<string, string|Object<string,string>>} [valueMapping] - object providing text replacements or i18n strings for attribute values
34
+ * @property {boolean} [mergeParentAttributes] - if true, will merge attributes from parent features, if __PARENT_FEATURE is set on the feature. Child attributes will overwrite parent attributes in case of identical keys.
35
+ * @property {boolean} [removeNoDataAttributes] - if true, will remove all attributes with no data values
31
36
  * @property {Object<string,HTMLTagOptions>} [tags] - object with keys rendered as special html element. Value contains html options
32
37
  * @property {Pick<import("../manager/window/windowManager.js").WindowComponentOptions,'state'|'slot'|'position'>} [window] - state, slot, position can be set. Other options are predefined. headerTitle of window state can be a template string, e.g. "{{myAttribute}}" or ["{{layerName}}", " - ", "{{myAttribute}}"]
33
38
  */
@@ -238,14 +243,17 @@ export function applyDoubleUnderscoreFilter(attributes, keys = []) {
238
243
  /**
239
244
  * Filters attributes having an empty object as value
240
245
  * @param {Object<string, unknown>} attributes
246
+ * @param {boolean} removeAllEmpty - if true, will remove all empty attributes, otherwise only empty objects
241
247
  * @returns {Object}
242
248
  */
243
- export function applyEmptyAttributesFilter(attributes) {
249
+ export function applyEmptyAttributesFilter(attributes, removeAllEmpty = false) {
244
250
  return Object.keys(attributes).reduce((obj, key) => {
245
251
  if (
246
- attributes[key] !== null &&
247
- typeof attributes[key] === 'object' &&
248
- Object.keys(attributes[key]).length === 0
252
+ (attributes[key] !== null &&
253
+ typeof attributes[key] === 'object' &&
254
+ Object.keys(attributes[key]).length === 0) ||
255
+ (removeAllEmpty &&
256
+ (attributes[key] === undefined || attributes[key] === null))
249
257
  ) {
250
258
  return obj;
251
259
  }
@@ -278,6 +286,41 @@ function getWindowState(app, state, attributes) {
278
286
  };
279
287
  }
280
288
 
289
+ /**
290
+ * Recursively searches for parent feature attributes, if __PARENT_FEATURE property is set. Parent feature is searched in the batchTable of the content of the given feature.
291
+ * @param {import("ol").Feature|import("@vcmap-cesium/engine").Cesium3DTileFeature|import("@vcmap-cesium/engine").Cesium3DTilePointFeature} feature
292
+ * @param {Array<object>} records - array of parent feature attributes
293
+ * @returns {Array<object>} record of parent feature ids and their attributes
294
+ */
295
+ function getParentFeatureAttributes(feature, records = []) {
296
+ const parentId = feature.getProperty('__PARENT_FEATURE');
297
+ if (parentId !== 'null' && feature.content?.batchTable) {
298
+ for (let i = 0; i < feature.content.batchTable.featuresLength; i++) {
299
+ const batchTableFeature = feature.content.batchTable.getFeature(i);
300
+ if (batchTableFeature.getProperty('id') === parentId) {
301
+ records.push(batchTableFeature.getAttributes());
302
+ return getParentFeatureAttributes(batchTableFeature, records);
303
+ }
304
+ }
305
+ }
306
+ return records;
307
+ }
308
+
309
+ /**
310
+ * Merges two objects by preventing the overwriting of existing keys with undefined values.
311
+ * @param {Object} target
312
+ * @param {Object} source
313
+ * @returns {Object}
314
+ */
315
+ function safeMerge(target, source) {
316
+ Object.entries(source).forEach(([key, value]) => {
317
+ if (value !== undefined || !(key in target)) {
318
+ target[key] = value;
319
+ }
320
+ });
321
+ return target;
322
+ }
323
+
281
324
  /**
282
325
  * Abstract class to be extended by FeatureInfoView classes
283
326
  * Subclasses must always provide a component and may overwrite class methods.
@@ -300,6 +343,8 @@ class AbstractFeatureInfoView extends VcsObject {
300
343
  keyMapping: undefined,
301
344
  valueMapping: undefined,
302
345
  tags: undefined,
346
+ mergeParentAttributes: true,
347
+ removeNoDataAttributes: false,
303
348
  window: {},
304
349
  };
305
350
  }
@@ -323,6 +368,20 @@ class AbstractFeatureInfoView extends VcsObject {
323
368
  * @type {null|Object<string, string|Object<string,string>>}
324
369
  */
325
370
  this.valueMapping = options.valueMapping || defaultOptions.valueMapping;
371
+ /**
372
+ * @type {boolean}
373
+ */
374
+ this.mergeParentAttributes = parseBoolean(
375
+ options.mergeParentAttributes,
376
+ defaultOptions.mergeParentAttributes,
377
+ );
378
+ /**
379
+ * @type {boolean}
380
+ */
381
+ this.removeNoDataAttributes = parseBoolean(
382
+ options.removeNoDataAttributes,
383
+ defaultOptions.removeNoDataAttributes,
384
+ );
326
385
  /**
327
386
  * @type {null|Object<string,HTMLTagOptions>}
328
387
  */
@@ -360,9 +419,17 @@ class AbstractFeatureInfoView extends VcsObject {
360
419
  * @returns {Object}
361
420
  * @protected
362
421
  */
363
- // eslint-disable-next-line class-methods-use-this
364
422
  _getAttributesFromFeature(feature) {
365
- return feature?.getAttributes() || {};
423
+ const attributes = feature?.getAttributes() || {};
424
+ if (this.mergeParentAttributes) {
425
+ const parentAttributes = getParentFeatureAttributes(feature);
426
+ // Merge parent with child (child overwrites parent)
427
+ return [...parentAttributes, attributes].reduce(
428
+ (acc, source) => safeMerge(acc, source),
429
+ {},
430
+ );
431
+ }
432
+ return attributes;
366
433
  }
367
434
 
368
435
  /**
@@ -386,7 +453,7 @@ class AbstractFeatureInfoView extends VcsObject {
386
453
  }
387
454
  attributes = applyOlcsAttributeFilter(attributes, this.attributeKeys);
388
455
  attributes = applyDoubleUnderscoreFilter(attributes, this.attributeKeys);
389
- return applyEmptyAttributesFilter(attributes);
456
+ return applyEmptyAttributesFilter(attributes, this.removeNoDataAttributes);
390
457
  }
391
458
 
392
459
  /**
@@ -463,6 +530,7 @@ class AbstractFeatureInfoView extends VcsObject {
463
530
  */
464
531
  toJSON() {
465
532
  const config = super.toJSON();
533
+ const defaultOptions = AbstractFeatureInfoView.getDefaultOptions();
466
534
  if (this.attributeKeys.length > 0) {
467
535
  config.attributeKeys = this.attributeKeys.slice(0);
468
536
  }
@@ -472,6 +540,12 @@ class AbstractFeatureInfoView extends VcsObject {
472
540
  if (this.valueMapping) {
473
541
  config.valueMapping = JSON.parse(JSON.stringify(this.valueMapping));
474
542
  }
543
+ if (this.mergeParentAttributes !== defaultOptions.mergeParentAttributes) {
544
+ config.mergeParentAttributes = this.mergeParentAttributes;
545
+ }
546
+ if (this.removeNoDataAttributes !== defaultOptions.removeNoDataAttributes) {
547
+ config.removeNoDataAttributes = this.removeNoDataAttributes;
548
+ }
475
549
  if (this.tags) {
476
550
  config.tags = JSON.parse(JSON.stringify(this.tags));
477
551
  }
@@ -31,12 +31,13 @@ export function getFeatureInfoViewForFeature(app: import("../vcsUiApp.js").defau
31
31
  * @param {import("../vcsUiApp.js").default} app
32
32
  * @param {import("@vcmap/core").EventFeature[]} features
33
33
  * @param {import("ol/coordinate.js").Coordinate?} position
34
+ * @param {() => void} [close] function to close the window or selection
34
35
  * @returns {{
35
36
  * groups: import("../components/lists/VcsGroupedList.vue").VcsListGroup,
36
37
  * items: import("../components/lists/VcsGroupedList.vue").VcsGroupedListItem,
37
38
  * }}
38
39
  */
39
- export function getGroupedFeatureList(app: import("../vcsUiApp.js").default, features: import("@vcmap/core").EventFeature[], position?: import("ol/coordinate.js").Coordinate | null): {
40
+ export function getGroupedFeatureList(app: import("../vcsUiApp.js").default, features: import("@vcmap/core").EventFeature[], position?: import("ol/coordinate.js").Coordinate | null, close?: (() => void) | undefined): {
40
41
  groups: import("../components/lists/VcsGroupedList.vue").VcsListGroup;
41
42
  items: import("../components/lists/VcsGroupedList.vue").VcsGroupedListItem;
42
43
  };
@@ -21,6 +21,8 @@ import {
21
21
  mercatorToCartesian,
22
22
  cartesianToMercator,
23
23
  CesiumMap,
24
+ panoramaFeature,
25
+ PanoramaMap,
24
26
  } from '@vcmap/core';
25
27
  import { getLogger as getLoggerByName } from '@vcsuite/logger';
26
28
  import {
@@ -187,12 +189,18 @@ export function getFeatureInfoViewForFeature(app, feature) {
187
189
  * @param {import("../vcsUiApp.js").default} app
188
190
  * @param {import("@vcmap/core").EventFeature[]} features
189
191
  * @param {import("ol/coordinate.js").Coordinate?} position
192
+ * @param {() => void} [close] function to close the window or selection
190
193
  * @returns {{
191
194
  * groups: import("../components/lists/VcsGroupedList.vue").VcsListGroup,
192
195
  * items: import("../components/lists/VcsGroupedList.vue").VcsGroupedListItem,
193
196
  * }}
194
197
  */
195
- export function getGroupedFeatureList(app, features, position = undefined) {
198
+ export function getGroupedFeatureList(
199
+ app,
200
+ features,
201
+ position = undefined,
202
+ close = undefined,
203
+ ) {
196
204
  const groups = {};
197
205
  const items = features.map((f) => {
198
206
  let actions;
@@ -211,26 +219,62 @@ export function getGroupedFeatureList(app, features, position = undefined) {
211
219
  ),
212
220
  ];
213
221
  }
222
+
214
223
  /** @type {import("../components/lists/VcsListItemComponent.vue").VcsListItem} */
215
- const listItem = reactive({
216
- name: oFeature.getId(),
217
- title:
218
- attributes?.[titlePropName] ||
219
- attributes?.title ||
220
- attributes?.name ||
221
- oFeature.getId(),
222
- disabled: !getFeatureInfoViewForFeature(app, oFeature),
223
- selectionChanged: (value) => {
224
- if (value) {
225
- app.featureInfo
226
- .selectFeature(oFeature, position)
227
- .catch((e) => getLogger().error(e));
228
- } else {
229
- app.featureInfo.clearFeature();
230
- }
231
- },
232
- actions,
233
- });
224
+ let listItem;
225
+
226
+ if (oFeature[panoramaFeature]) {
227
+ listItem = reactive({
228
+ name: oFeature.getId(),
229
+ title:
230
+ attributes?.[titlePropName] ||
231
+ attributes?.title ||
232
+ attributes?.name ||
233
+ oFeature.getId(),
234
+ clickedCallbacks: [
235
+ async () => {
236
+ close?.();
237
+ const { dataset, name, time } = oFeature[panoramaFeature];
238
+ const panoramaImage = await dataset.createPanoramaImage(name, time);
239
+ if (app.maps.activeMap instanceof PanoramaMap) {
240
+ app.maps.activeMap.setCurrentImage(panoramaImage);
241
+ } else {
242
+ const firstPanoramaMap = app.maps.getByType(
243
+ PanoramaMap.className,
244
+ )[0];
245
+ if (firstPanoramaMap) {
246
+ await app.maps.activatePanoramaMap(
247
+ firstPanoramaMap,
248
+ panoramaImage,
249
+ );
250
+ }
251
+ }
252
+ },
253
+ ],
254
+ actions,
255
+ });
256
+ } else {
257
+ listItem = reactive({
258
+ name: oFeature.getId(),
259
+ title:
260
+ attributes?.[titlePropName] ||
261
+ attributes?.title ||
262
+ attributes?.name ||
263
+ oFeature.getId(),
264
+ disabled: !getFeatureInfoViewForFeature(app, oFeature),
265
+ selectionChanged: (value) => {
266
+ if (value) {
267
+ app.featureInfo
268
+ .selectFeature(oFeature, position)
269
+ .catch((e) => getLogger().error(e));
270
+ } else {
271
+ app.featureInfo.clearFeature();
272
+ }
273
+ },
274
+ actions,
275
+ });
276
+ }
277
+
234
278
  if (layerName) {
235
279
  if (!groups[layerName]) {
236
280
  const title = layer?.properties?.title;
@@ -240,6 +284,7 @@ export function getGroupedFeatureList(app, features, position = undefined) {
240
284
  }
241
285
  return listItem;
242
286
  });
287
+
243
288
  return { groups: Object.values(groups), items };
244
289
  }
245
290
 
@@ -813,6 +858,7 @@ class FeatureInfo extends Collection {
813
858
  this._app,
814
859
  features,
815
860
  position,
861
+ () => this.clearCluster(),
816
862
  );
817
863
 
818
864
  this._clusterWindowId = id;
File without changes