@vcmap/ui 5.0.0-rc.10 → 5.0.0-rc.13

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.
Files changed (165) hide show
  1. package/README.md +12 -5
  2. package/build/build.js +6 -3
  3. package/build/buildHelpers.js +12 -4
  4. package/build/buildPreview.js +7 -0
  5. package/build/getPluginProxies.js +4 -0
  6. package/config/aerowest.config.json +13 -3
  7. package/config/base.config.json +398 -219
  8. package/config/codes.config.json +397 -0
  9. package/config/dev.config.json +375 -1
  10. package/config/graphFeatureInfo.config.json +100 -0
  11. package/config/www.config.json +1232 -0
  12. package/dist/assets/{cesium.eb5667.js → cesium.21663e.js} +0 -0
  13. package/dist/assets/cesium.js +1 -1
  14. package/dist/assets/core.63242d.js +4 -0
  15. package/dist/assets/core.js +1 -1
  16. package/dist/assets/font/OFL.txt +93 -0
  17. package/dist/assets/font/TitilliumWeb-Regular.woff2 +0 -0
  18. package/dist/assets/{index.4ccd4433.js → index.44b91cfe.js} +1 -1
  19. package/dist/assets/{ol.ef03b1.js → ol.88ba9d.js} +0 -0
  20. package/dist/assets/ol.js +1 -1
  21. package/dist/assets/ui.3c2933.css +1 -0
  22. package/dist/assets/ui.3c2933.js +71 -0
  23. package/dist/assets/ui.js +1 -1
  24. package/dist/assets/vue.c897fc.js +9 -0
  25. package/dist/assets/vue.js +2 -1
  26. package/dist/assets/{vuetify.401a29.css → vuetify.147c3a.css} +1 -1
  27. package/dist/assets/{vuetify.401a29.js → vuetify.147c3a.js} +72 -72
  28. package/dist/assets/vuetify.js +2 -2
  29. package/dist/index.html +1 -5
  30. package/index.js +39 -5
  31. package/lib/vue.js +1 -0
  32. package/map.config.json +15 -6
  33. package/package.json +17 -8
  34. package/plugins/@vcmap/create-link/fallbackCreateLink.vue +71 -0
  35. package/plugins/@vcmap/create-link/index.js +83 -0
  36. package/plugins/@vcmap/create-link/package.json +6 -0
  37. package/plugins/@vcmap/pluginExample/index.js +2 -2
  38. package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +20 -3
  39. package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +1 -1
  40. package/plugins/@vcmap/project-selector/index.js +1 -1
  41. package/plugins/@vcmap/project-selector/package.json +1 -2
  42. package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +1 -1
  43. package/plugins/@vcmap/theme-changer/index.js +1 -1
  44. package/plugins/@vcmap/theme-changer/package.json +1 -2
  45. package/plugins/categoryTest/Categories.vue +89 -1
  46. package/plugins/categoryTest/Category.vue +1 -1
  47. package/plugins/example/index.js +10 -23
  48. package/plugins/simple-graph/README.md +51 -0
  49. package/plugins/simple-graph/SimpleGraphComponent.vue +70 -0
  50. package/plugins/simple-graph/index.js +17 -0
  51. package/plugins/simple-graph/package.json +11 -0
  52. package/plugins/simple-graph/simpleGraphView.js +76 -0
  53. package/plugins/test/editor.vue +1 -1
  54. package/plugins/test/index.js +76 -9
  55. package/plugins/test/toolbox-data.js +82 -57
  56. package/plugins/test/windowManagerExample.vue +1 -1
  57. package/src/actions/stateRefAction.js +2 -2
  58. package/src/actions/styleSelector.vue +1 -1
  59. package/src/application/Navbar.vue +13 -2
  60. package/src/application/VcsApp.vue +301 -116
  61. package/src/application/VcsMap.vue +1 -1
  62. package/src/application/VcsSettings.vue +1 -1
  63. package/src/application/vcsAppWrapper.vue +1 -0
  64. package/src/assets/font/OFL.txt +93 -0
  65. package/src/assets/font/TitilliumWeb-Regular.woff2 +0 -0
  66. package/src/components/form-inputs-controls/VcsCheckbox.vue +13 -0
  67. package/src/components/form-inputs-controls/VcsColorPicker.vue +1 -1
  68. package/src/components/form-inputs-controls/VcsRadio.vue +123 -0
  69. package/src/components/form-output/VcsFormattedNumber.vue +1 -1
  70. package/src/components/lists/VcsActionList.vue +22 -7
  71. package/src/components/lists/VcsTreeview.vue +4 -4
  72. package/src/components/lists/VcsTreeviewLeaf.vue +10 -3
  73. package/src/components/lists/VcsTreeviewSearchbar.vue +1 -2
  74. package/src/components/tables/VcsTable.vue +245 -0
  75. package/src/contentTree/LayerTree.vue +1 -1
  76. package/src/contentTree/contentTreeCollection.js +4 -4
  77. package/src/contentTree/contentTreeItem.js +9 -9
  78. package/src/contentTree/groupContentTreeItem.js +1 -1
  79. package/src/contentTree/layerContentTreeItem.js +15 -1
  80. package/src/contentTree/layerGroupContentTreeItem.js +21 -1
  81. package/src/contentTree/nodeContentTreeItem.js +1 -1
  82. package/src/featureInfo/AddressBalloonComponent.vue +47 -0
  83. package/src/featureInfo/BalloonComponent.vue +140 -0
  84. package/src/featureInfo/abstractFeatureInfoView.js +313 -0
  85. package/src/featureInfo/addressBalloonFeatureInfoView.js +118 -0
  86. package/src/featureInfo/balloonFeatureInfoView.js +151 -0
  87. package/src/featureInfo/balloonHelper.js +132 -0
  88. package/src/featureInfo/featureInfo.js +457 -0
  89. package/src/featureInfo/featureInfoInteraction.js +42 -0
  90. package/src/featureInfo/iframeFeatureInfoView.js +95 -0
  91. package/src/featureInfo/tableFeatureInfoView.js +106 -0
  92. package/src/i18n/de.js +26 -0
  93. package/src/i18n/en.js +26 -0
  94. package/src/i18n/i18nCollection.js +17 -0
  95. package/src/icons/+all.js +80 -0
  96. package/src/icons/ClippingHorizontalIcon.vue +7 -0
  97. package/src/icons/ClippingIcon.vue +7 -0
  98. package/src/icons/ClippingVerticalIcon.vue +7 -0
  99. package/src/icons/ColorPickerIcon.vue +7 -0
  100. package/src/icons/ComponentsIcon.vue +2 -2
  101. package/src/icons/DimensionsHouseIcon.vue +11 -9
  102. package/src/icons/EditIcon.vue +7 -0
  103. package/src/icons/GlobalTerrainIcon.vue +9 -0
  104. package/src/icons/GroundIcon.vue +18 -0
  105. package/src/icons/HideIcon.vue +12 -0
  106. package/src/icons/LogoutIcon.vue +7 -0
  107. package/src/icons/ObjectAttributeIcon.vue +2 -13
  108. package/src/icons/PedestrianIcon.vue +2 -3
  109. package/src/icons/PenIcon.vue +2 -9
  110. package/src/icons/PoiIcon.vue +5 -2
  111. package/src/icons/PointSelectIcon.vue +4 -2
  112. package/src/icons/QueryIcon.vue +6 -7
  113. package/src/icons/ScreenshotIcon.vue +16 -0
  114. package/src/icons/ShareIcon.vue +4 -16
  115. package/src/icons/SkipNextIcon.vue +3 -1
  116. package/src/icons/TerrainBoxIcon.vue +9 -0
  117. package/src/icons/ToolsIcon.vue +4 -30
  118. package/src/icons/UploadIcon.vue +2 -9
  119. package/src/icons/UserProfileIcon.vue +7 -0
  120. package/src/icons/UserShareIcon.vue +7 -0
  121. package/src/icons/VideoRecorderIcon.vue +5 -9
  122. package/src/icons/ViewpointFlightIcon.vue +11 -0
  123. package/src/icons/ViewpointIcon.vue +11 -0
  124. package/src/icons/Viewshed360Icon.vue +7 -0
  125. package/src/icons/ViewshedConeIcon.vue +7 -0
  126. package/src/icons/ViewshedIcon.vue +7 -0
  127. package/src/icons/WallIcon.vue +4 -9
  128. package/src/legend/legendHelper.js +193 -0
  129. package/src/legend/styleLegendItem.vue +129 -0
  130. package/src/legend/vcsLegend.vue +92 -0
  131. package/src/manager/buttonManager.js +7 -12
  132. package/src/manager/categoryManager/ComponentsManager.vue +30 -0
  133. package/src/manager/categoryManager/categoryManager.js +500 -0
  134. package/src/manager/contextMenu/contextMenuComponent.vue +43 -0
  135. package/src/manager/contextMenu/contextMenuInteraction.js +42 -0
  136. package/src/manager/contextMenu/contextMenuManager.js +197 -0
  137. package/src/manager/navbarManager.js +9 -9
  138. package/src/manager/toolbox/GroupToolboxComponent.vue +118 -0
  139. package/src/manager/toolbox/SelectToolboxComponent.vue +128 -0
  140. package/src/manager/toolbox/ToolboxManager.vue +116 -98
  141. package/src/manager/toolbox/toolboxManager.js +235 -86
  142. package/src/manager/window/WindowComponent.vue +1 -1
  143. package/src/manager/window/WindowManager.vue +5 -3
  144. package/src/manager/window/windowManager.js +118 -14
  145. package/src/navigation/mapNavigation.vue +3 -5
  146. package/src/navigation/overviewMap.js +28 -5
  147. package/src/navigation/vcsCompass.vue +1 -1
  148. package/src/pluginHelper.js +42 -10
  149. package/src/setup.js +0 -2
  150. package/src/state.js +256 -0
  151. package/src/styles/_theming.scss +0 -5
  152. package/src/styles/variables.scss +7 -0
  153. package/src/styles/vcsFont.scss +17 -0
  154. package/src/uiConfig.js +79 -0
  155. package/src/vcsUiApp.js +213 -22
  156. package/src/vuePlugins/vuetify.js +14 -4
  157. package/config/berlin.config.json +0 -510
  158. package/dist/assets/core.216494.js +0 -4
  159. package/dist/assets/ui.99a1a7.css +0 -1
  160. package/dist/assets/ui.99a1a7.js +0 -70
  161. package/dist/assets/vue-composition-api.c5aca1.js +0 -14
  162. package/dist/assets/vue-composition-api.js +0 -2
  163. package/dist/assets/vue.762edd.js +0 -9
  164. package/lib/vue-composition-api.js +0 -2
  165. package/src/manager/toolbox/ToolboxGroupComponent.vue +0 -128
@@ -4,7 +4,8 @@ import { StateActionState } from '../actions/stateRefAction.js';
4
4
 
5
5
  /**
6
6
  * @typedef {ContentTreeItemOptions} LayerGroupContentTreeItemOptions
7
- * @property {Array<string>} layerNames
7
+ * @property {Array<string>} layerNames list of LayerNames which should be activated on click
8
+ * @property {Array<string>} layerNamesToDeactivate list of LayerNames which should be deactivated on click if the click activates the layer in layerNames
8
9
  * @property {string} [defaultViewpoint] - the name of an optional default viewpoint
9
10
  */
10
11
 
@@ -55,6 +56,14 @@ class LayerGroupContentTreeItem extends ContentTreeItem {
55
56
  options.layerNames.slice() :
56
57
  [];
57
58
 
59
+ /**
60
+ * @type {Array<string>}
61
+ * @private
62
+ */
63
+ this._layerNamesToDeactivate = Array.isArray(options.layerNamesToDeactivate) ?
64
+ options.layerNamesToDeactivate.slice() :
65
+ [];
66
+
58
67
  /**
59
68
  * @type {Array<function():void>}
60
69
  * @private
@@ -122,11 +131,18 @@ class LayerGroupContentTreeItem extends ContentTreeItem {
122
131
  }));
123
132
  }
124
133
 
134
+ /**
135
+ * @returns {Promise<void>}
136
+ */
125
137
  async clicked() {
126
138
  const layers = this._layers;
127
139
  const activate = layers.some(l => !(l.active || l.loading));
128
140
  if (activate) {
129
141
  await Promise.all(layers.map(l => l.activate()));
142
+ this._layerNamesToDeactivate
143
+ .map(n => this._app.layers.getByKey(n))
144
+ .filter(l => l)
145
+ .forEach(l => l.deactivate());
130
146
  } else {
131
147
  layers.forEach((l) => { l.deactivate(); });
132
148
  }
@@ -138,12 +154,16 @@ class LayerGroupContentTreeItem extends ContentTreeItem {
138
154
  toJSON() {
139
155
  const config = super.toJSON();
140
156
  config.layerNames = this._layerNames.slice();
157
+ config.layerNamesToDeactivate = this._layerNamesToDeactivate.slice();
141
158
  if (this._defaultViewpoint) {
142
159
  config.defaultViewpoint = this._defaultViewpoint;
143
160
  }
144
161
  return config;
145
162
  }
146
163
 
164
+ /**
165
+ * @inheritDoc
166
+ */
147
167
  destroy() {
148
168
  this._clearListeners();
149
169
  super.destroy();
@@ -1,4 +1,4 @@
1
- import { watch } from '@vue/composition-api';
1
+ import { watch } from 'vue';
2
2
  import ContentTreeItem, { contentTreeClassRegistry } from './contentTreeItem.js';
3
3
 
4
4
  /**
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <BalloonComponent
3
+ v-bind="{...$attrs}"
4
+ >
5
+ <template #default="{ attrs }">
6
+ <v-list-item two-line v-if="Object.values(attrs.attributes).length > 0">
7
+ <v-list-item-avatar
8
+ tile
9
+ size="40"
10
+ >
11
+ <v-icon size="20" color="secondary">
12
+ $vcsHomePoint
13
+ </v-icon>
14
+ </v-list-item-avatar>
15
+ <v-list-item-content>
16
+ <v-list-item-title v-if="attrs.attributes.addressName">
17
+ {{ attrs.attributes.addressName }}
18
+ </v-list-item-title>
19
+ <v-list-item-title v-else>
20
+ {{ attrs.attributes.street }} {{ attrs.attributes.number }}
21
+ </v-list-item-title>
22
+ <v-list-item-subtitle v-if="attrs.attributes.name">
23
+ {{ attrs.attributes.street }} {{ attrs.attributes.number }}
24
+ </v-list-item-subtitle>
25
+ <v-list-item-subtitle>{{ attrs.attributes.zip }} {{ attrs.attributes.city }}</v-list-item-subtitle>
26
+ <v-list-item-subtitle>{{ attrs.attributes.country }}</v-list-item-subtitle>
27
+ </v-list-item-content>
28
+ </v-list-item>
29
+ </template>
30
+ </BalloonComponent>
31
+ </template>
32
+ <script>
33
+
34
+ import BalloonComponent from './BalloonComponent.vue';
35
+
36
+ /**
37
+ * @description A balloon viewing address information
38
+ */
39
+ export default {
40
+ name: 'AddressBalloonComponent',
41
+ components: { BalloonComponent },
42
+ };
43
+ </script>
44
+
45
+ <style>
46
+
47
+ </style>
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <v-card
3
+ class="mx-auto"
4
+ max-width="400"
5
+ v-if="position"
6
+ >
7
+ <slot name="balloon-header" :attrs="{...$props, ...$attrs}">
8
+ <v-list-item two-line>
9
+ <v-list-item-avatar
10
+ tile
11
+ size="40"
12
+ >
13
+ <v-icon color="primary">
14
+ $vcsInfo
15
+ </v-icon>
16
+ </v-list-item-avatar>
17
+ <v-list-item-content>
18
+ <v-list-item-title class="text-h5">
19
+ {{ title }}
20
+ </v-list-item-title>
21
+ <v-list-item-subtitle>{{ subtitle }}</v-list-item-subtitle>
22
+ </v-list-item-content>
23
+ <VcsButton
24
+ @click.stop="close"
25
+ small
26
+ icon="mdi-close-thick"
27
+ class="mb-8"
28
+ />
29
+ </v-list-item>
30
+ </slot>
31
+
32
+ <v-divider />
33
+
34
+ <v-card class="overflow-y-auto" max-height="250">
35
+ <slot :attrs="{...$props, ...$attrs}">
36
+ <v-list v-for="(value, name, index) in attributes" :key="`attribute-${index}`">
37
+ <v-list-item>
38
+ <v-list-item-content>
39
+ <v-list-item-title>
40
+ {{ name }}
41
+ </v-list-item-title>
42
+ <v-list-item-subtitle>{{ value }}</v-list-item-subtitle>
43
+ </v-list-item-content>
44
+ </v-list-item>
45
+ </v-list>
46
+ </slot>
47
+ </v-card>
48
+ </v-card>
49
+ </template>
50
+ <script>
51
+
52
+ import { inject, onMounted, onUnmounted, watch } from 'vue';
53
+ import { setupBalloonPositionListener } from './balloonHelper.js';
54
+ import VcsButton from '../components/buttons/VcsButton.vue';
55
+
56
+ /**
57
+ * @description A balloon viewing feature attributes. Size dynamic dependent on number of attributes.
58
+ * Scrollable, if more than 6 attributes are provided.
59
+ * @vue-prop {string} featureId - feature's id
60
+ * @vue-prop {string} title - balloon title
61
+ * @vue-prop {string} subtitle - balloon subtitle
62
+ * @vue-prop {Object} attributes - feature's attributes
63
+ * @vue-prop {Array<import("ol/coordinate").Coordinate>} position - clicked position balloon is rendered at
64
+ * @vue-data {slot} [#balloon-header] - slot to override balloon header, $props and $attrs are passed to `attrs`
65
+ * @vue-data {slot} [#default] - slot to override balloon content, $props and $attrs are passed to `attrs`
66
+ */
67
+ export default {
68
+ name: 'BalloonComponent',
69
+ components: { VcsButton },
70
+ props: {
71
+ featureId: {
72
+ type: String,
73
+ required: true,
74
+ },
75
+ title: {
76
+ type: String,
77
+ required: true,
78
+ },
79
+ subtitle: {
80
+ type: String,
81
+ required: true,
82
+ },
83
+ attributes: {
84
+ type: Object,
85
+ required: true,
86
+ },
87
+ position: {
88
+ type: Array,
89
+ default: null,
90
+ },
91
+ },
92
+ setup(props, { attrs }) {
93
+ const app = inject('vcsApp');
94
+ const windowId = attrs['window-state'].id;
95
+
96
+ let balloonPositionListener = null;
97
+ const destroyListener = () => {
98
+ if (balloonPositionListener) {
99
+ balloonPositionListener();
100
+ }
101
+ };
102
+
103
+ onMounted(async () => {
104
+ balloonPositionListener = await setupBalloonPositionListener(app, windowId, props.position);
105
+ });
106
+
107
+ watch(() => props.featureId, async () => {
108
+ destroyListener();
109
+ balloonPositionListener = await setupBalloonPositionListener(app, windowId, props.position);
110
+ });
111
+
112
+ onUnmounted(() => {
113
+ destroyListener();
114
+ });
115
+
116
+ const close = () => {
117
+ app.windowManager.remove(attrs['window-state'].id);
118
+ destroyListener();
119
+ };
120
+
121
+ return {
122
+ close,
123
+ };
124
+ },
125
+ };
126
+ </script>
127
+
128
+ <style>
129
+ .balloon:before {
130
+ content: "";
131
+ position: absolute;
132
+ bottom: -20px;
133
+ left: 40px;
134
+ border-width: 20px 20px 0;
135
+ border-style: solid;
136
+ border-color: white transparent;
137
+ display: block;
138
+ width: 0;
139
+ }
140
+ </style>
@@ -0,0 +1,313 @@
1
+ import { VcsObject } from '@vcmap/core';
2
+ import { WindowSlot } from '../manager/window/windowManager.js';
3
+
4
+ /**
5
+ * @typedef {Object} FeatureInfoProps
6
+ * @property {string} featureId
7
+ * @property {string} layerName
8
+ * @property {Object} layerProperties
9
+ * @property {Object} attributes
10
+ */
11
+
12
+ /**
13
+ * @typedef {import("@vcmap/core").VcsObjectOptions} FeatureInfoViewOptions
14
+ * @property {Array<string>} [attributeKeys] - list of keys to filter attributes of selected feature
15
+ * @property {Object<string,string>} [keyMapping] - object providing text replacements or i18n strings for attribute keys
16
+ * @property {Object<string, string|Object<string,string>>} [valueMapping] - object providing text replacements or i18n strings for attribute values
17
+ * @property {WindowComponentOptions} [window] - state, slot, position can be set. Other options are predefined.
18
+ */
19
+
20
+ /**
21
+ * @param {string|Object<string,string>} mappedValue
22
+ * @param {string} value
23
+ * @returns {string}
24
+ */
25
+ function getMappedValue(mappedValue, value) {
26
+ if (typeof mappedValue === 'string') {
27
+ return mappedValue.replace(/\${value}/g, value);
28
+ }
29
+ return mappedValue[value] ?? value;
30
+ }
31
+
32
+ /**
33
+ * Replaces values by new values according to mapping table. Nested keys are represented by a "."
34
+ * @param {Object<string, *>} attributes
35
+ * @param {Object<string, string|Object<string,string>>} mapping - value mapping
36
+ */
37
+ export function applyValueMapping(attributes, mapping) {
38
+ Object.keys(mapping)
39
+ .sort((a, b) => {
40
+ const aLen = a.split('.').length;
41
+ const bLen = b.split('.').length;
42
+ if (aLen > bLen) {
43
+ return -1;
44
+ }
45
+ if (bLen > aLen) {
46
+ return 1;
47
+ }
48
+ return 0;
49
+ })
50
+ .forEach((mappingKey) => {
51
+ if (Object.hasOwn(attributes, mappingKey)) {
52
+ attributes[mappingKey] = getMappedValue(mapping[mappingKey], attributes[mappingKey]);
53
+ } else {
54
+ const mappingKeys = mappingKey.split('.');
55
+ mappingKeys.reduce((obj, key, index) => {
56
+ if (obj && Object.hasOwn(obj, key) && index === mappingKeys.length - 1) {
57
+ obj[key] = getMappedValue(mapping[mappingKey], obj[key]);
58
+ }
59
+ return obj?.[key];
60
+ }, attributes);
61
+ }
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Replaces keys by new keys according to mapping table.
67
+ * Nested keys to replace are represented by a ".". Keys will
68
+ * be replaced by the given string literal. This will always lead
69
+ * to a new top level key or an overwritting of an existing key.
70
+ * Deletes old keys!
71
+ * @example
72
+ * const getAttrs = () => { foo: { bar: true }, 'foo.baz': true };
73
+ * const nestedMapping = { 'foo.bar': 'bar' };
74
+ * const nestedMappingWithPeriod = { 'foo.bar': 'bar.foo' };
75
+ * const flatMapping = { 'foo': 'bar' };
76
+ * const periodMapping = { 'foo.baz': 'foo' };
77
+ * // apply nested key mapping will replace nested key with top level key
78
+ * const nestedAttrs = applyKeyMapping(getAttrs(), nestedMapping);
79
+ * assert(nestedAttrs.bar === true);
80
+ * // apply nested key mapping with a new key with a period. will replace nested key with a top level key.
81
+ * const nestedAttrsWithPeriod = applyKeyMapping(getAttrs(), nestedMappingWithPeriod);
82
+ * assert(nestedAttrsWithPeriod['bar.foo'] === true);
83
+ * // apply flat mapping: will replace top level key with another top level key
84
+ * const flatAttrs = applyKeyMapping(getAttrs(), flatMapping);
85
+ * assert(flatAttrs.bar.bar === true);
86
+ * // apply flat mapping of a key with a period. this will overwrite an existing _other_ key
87
+ * const periodAttrs = applyKeyMapping(getAttrs(), periodMapping);
88
+ * assert(periodAttrs.foo === true);
89
+ * @param {Object<string, *>} attributes
90
+ * @param {Object<string,string>} mapping - key mapping
91
+ */
92
+ export function applyKeyMapping(attributes, mapping) {
93
+ Object.keys(mapping)
94
+ .sort((a, b) => {
95
+ const aLen = a.split('.').length;
96
+ const bLen = b.split('.').length;
97
+ if (aLen > bLen) {
98
+ return -1;
99
+ }
100
+ if (bLen > aLen) {
101
+ return 1;
102
+ }
103
+ return 0;
104
+ })
105
+ .forEach((mappingKey) => {
106
+ if (Object.hasOwn(attributes, mappingKey)) {
107
+ attributes[mapping[mappingKey]] = attributes[mappingKey];
108
+ delete attributes[mappingKey];
109
+ } else {
110
+ const mappingKeys = mappingKey.split('.');
111
+ mappingKeys.reduce((obj, key, index) => {
112
+ if (obj && Object.hasOwn(obj, key) && index === mappingKeys.length - 1) {
113
+ attributes[mapping[mappingKey]] = obj[key];
114
+ delete obj[key];
115
+ }
116
+ return obj?.[key];
117
+ }, attributes);
118
+ }
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Applies an attribute filtering. Nested attributes are represented by a ".".
124
+ * @example
125
+ * const attrs = { foo: { bar: true, baz: false }, bar: true, baz: true, foobar: { foo: true, bar: true } };
126
+ * const filter = ["foo.bar", "baz", "foobar"];
127
+ * const filtered = applyAttributeFilter(attrs, filter)
128
+ * // nested keys will also filter for their parent
129
+ * assert(filtered.foo.bar === true);
130
+ * // only keys filtered will be present
131
+ * assert(filtered.foo.baz === undefined);
132
+ * assert(filtered.bar === undefined);
133
+ * assert(filtered.baz === true);
134
+ * // if filtering parent top level keys, will pass on a reference of the actual value and its children.
135
+ * assert(deepEquals(filtered.foobar, attrs.foobar));
136
+ * @param {Object<string, *>} attributes
137
+ * @param {Array<string>} keys
138
+ * @param {Object<string, *>=} result
139
+ * @returns {Object<string, *>}
140
+ */
141
+ export function applyAttributeFilter(attributes, keys, result = {}) {
142
+ const nestedKeys = {};
143
+ keys.forEach((k) => {
144
+ if (Object.hasOwn(attributes, k)) {
145
+ result[k] = attributes[k];
146
+ } else if (k.includes('.')) {
147
+ const [parent, ...rest] = k.split('.');
148
+ if (!nestedKeys[parent]) {
149
+ nestedKeys[parent] = [];
150
+ }
151
+ nestedKeys[parent].push(rest.join('.'));
152
+ }
153
+ });
154
+
155
+ Object.entries(nestedKeys)
156
+ .forEach(([parent, pKs]) => {
157
+ if (typeof attributes[parent] === 'object') {
158
+ result[parent] = {};
159
+ applyAttributeFilter(attributes[parent], pKs, result[parent]);
160
+ }
161
+ });
162
+ return result;
163
+ }
164
+
165
+ /**
166
+ * Abstract class to be extended by FeatureInfoView classes
167
+ * Subclasses must always provide a component and may overwrite class methods.
168
+ * @abstract
169
+ * @class
170
+ * @extends {VcsObject}
171
+ */
172
+ class AbstractFeatureInfoView extends VcsObject {
173
+ /**
174
+ * @type {string}
175
+ */
176
+ static get className() { return 'AbstractFeatureInfoView'; }
177
+
178
+ /** @returns {FeatureInfoViewOptions} */
179
+ static getDefaultOptions() {
180
+ return {
181
+ attributeKeys: [],
182
+ keyMapping: undefined,
183
+ valueMapping: undefined,
184
+ window: {},
185
+ };
186
+ }
187
+
188
+ /**
189
+ * @param {FeatureInfoViewOptions} options
190
+ * @param {import("vue").Component} component
191
+ */
192
+ constructor(options, component) {
193
+ super(options);
194
+ const defaultOptions = AbstractFeatureInfoView.getDefaultOptions();
195
+ /**
196
+ * @type {string[]}
197
+ */
198
+ this.attributeKeys = options.attributeKeys || defaultOptions.attributeKeys;
199
+ /**
200
+ * @type {null|Object<string,string>}
201
+ */
202
+ this.keyMapping = options.keyMapping || defaultOptions.keyMapping;
203
+ /**
204
+ * @type {null|Object<string, string|Object<string,string>>}
205
+ */
206
+ this.valueMapping = options.valueMapping || defaultOptions.valueMapping;
207
+ /**
208
+ * @type {WindowComponentOptions|Object}
209
+ * @private
210
+ */
211
+ this._window = options.window || defaultOptions.window;
212
+ /**
213
+ * @type {import("vue").Component|undefined}
214
+ * @private
215
+ */
216
+ this._component = component;
217
+ }
218
+
219
+ /**
220
+ * window options, configured in a context, used only internally by AbstractFeatureInfoView or subclass
221
+ * @type {WindowComponentOptions|Object}
222
+ * @readonly
223
+ */
224
+ get window() { return this._window; }
225
+
226
+ /**
227
+ * component provided by a FeatureInfoView class, passed to featureInfo via `getWindowComponentOptions()`
228
+ * @type {import("vue").Component|undefined}
229
+ * @readonly
230
+ */
231
+ get component() { return this._component; }
232
+
233
+ /**
234
+ * This method returns all relevant attributes for this view.
235
+ * Called by `getProperties()` to pass attributes as props object to the VueComponent of this view.
236
+ * May be overwritten by classes extending AbstractFeatureInfoView.
237
+ * It filters attributes of the feature by keys, performs value and key mapping, if provided.
238
+ * @param {undefined|import("ol").Feature<import("ol/geom/Geometry").default>|import("@vcmap/cesium").Cesium3DTileFeature|import("@vcmap/cesium").Cesium3DTilePointFeature} feature
239
+ * @returns {Object}
240
+ */
241
+ getAttributes(feature) {
242
+ let attributes = feature.getProperty('attributes') || {};
243
+ if (this.attributeKeys.length > 0) {
244
+ attributes = applyAttributeFilter(attributes, this.attributeKeys);
245
+ }
246
+ if (this.valueMapping) {
247
+ applyValueMapping(attributes, this.valueMapping);
248
+ }
249
+ if (this.keyMapping) {
250
+ applyKeyMapping(attributes, this.keyMapping);
251
+ }
252
+ return attributes;
253
+ }
254
+
255
+ /**
256
+ * This method returns all relevant properties passed to the VueComponent of this view.
257
+ * May be overwritten by classes extending AbstractFeatureInfoView.
258
+ * Called by `getWindowComponentOptions()`.
259
+ * @param {FeatureInfoEvent} featureInfo
260
+ * @param {import("@vcmap/core").Layer} layer
261
+ * @returns {FeatureInfoProps}
262
+ */
263
+ getProperties({ feature }, layer) {
264
+ return {
265
+ featureId: feature.getId(),
266
+ layerName: layer.name,
267
+ layerProperties: layer.properties,
268
+ attributes: this.getAttributes(feature),
269
+ };
270
+ }
271
+
272
+ /**
273
+ * This method is being called by featureInfo, whenever a new window is created (added to the windowManager).
274
+ * May be overwritten by classes extending AbstractFeatureInfoView.
275
+ * @param {FeatureInfoEvent} featureInfo
276
+ * @param {import("@vcmap/core").Layer} layer
277
+ * @returns {WindowComponentOptions}
278
+ */
279
+ getWindowComponentOptions(featureInfo, layer) {
280
+ return {
281
+ state: this.window.state ?? {
282
+ headerTitle: layer.properties?.title || layer.name,
283
+ headerIcon: '$vcsInfo',
284
+ },
285
+ slot: this.window.slot ?? WindowSlot.DYNAMIC_LEFT,
286
+ component: this.component,
287
+ position: this.window.position,
288
+ props: this.getProperties(featureInfo, layer),
289
+ };
290
+ }
291
+
292
+ /**
293
+ * @returns {FeatureInfoViewOptions}
294
+ */
295
+ toJSON() {
296
+ const config = super.toJSON();
297
+ if (this.attributeKeys.length > 0) {
298
+ config.attributeKeys = this.attributeKeys.slice(0);
299
+ }
300
+ if (this.keyMapping) {
301
+ config.keyMapping = { ...this.keyMapping };
302
+ }
303
+ if (this.valueMapping) {
304
+ config.valueMapping = JSON.parse(JSON.stringify(this.valueMapping));
305
+ }
306
+ if (Object.keys(this._window).length > 0) {
307
+ config.window = { ...this._window };
308
+ }
309
+ return config;
310
+ }
311
+ }
312
+
313
+ export default AbstractFeatureInfoView;
@@ -0,0 +1,118 @@
1
+ import BalloonFeatureInfoView, { extractNestedKey } from './balloonFeatureInfoView.js';
2
+ import AddressBalloonComponent from './AddressBalloonComponent.vue';
3
+
4
+ /**
5
+ * @typedef {FeatureInfoViewOptions} AddressBalloonFeatureInfoViewOptions
6
+ * @property {string|null} [addressName='gml:name'] key to evaluate for name. Use null to suppress
7
+ * @property {string|null} [street='Address.Street'] key to evaluate for street. Use null to suppress
8
+ * @property {string|null} [number='Address.HouseNumber'] key to evaluate for house number. Use null to suppress
9
+ * @property {string|null} [city='Address.City'] key to evaluate for city. Use null to suppress
10
+ * @property {string|null} [zip='Address.ZipCode'] key to evaluate for zip code. Use null to suppress
11
+ * @property {string|null} [country='Address.Country'] key to evaluate for country. Use null to suppress
12
+ */
13
+
14
+ /**
15
+ * @class
16
+ * @description An balloon view.
17
+ * @extends {BalloonFeatureInfoView}
18
+ */
19
+ class AddressBalloonFeatureInfoView extends BalloonFeatureInfoView {
20
+ /**
21
+ * @type {string}
22
+ */
23
+ static get className() { return 'AddressBalloonFeatureInfoView'; }
24
+
25
+ /** @returns {AddressBalloonFeatureInfoViewOptions} */
26
+ static getDefaultOptions() {
27
+ return {
28
+ addressName: 'gml:name',
29
+ street: 'Address.Street',
30
+ number: 'Address.HouseNumber',
31
+ city: 'Address.City',
32
+ zip: 'Address.ZipCode',
33
+ country: 'Address.Country',
34
+ };
35
+ }
36
+
37
+ /**
38
+ * @param {AddressBalloonFeatureInfoViewOptions} options
39
+ */
40
+ constructor(options) {
41
+ super(options, AddressBalloonComponent);
42
+ const defaultOptions = AddressBalloonFeatureInfoView.getDefaultOptions();
43
+
44
+ /**
45
+ * @type {string|null}
46
+ */
47
+ this.addressName = options.addressName !== undefined ? options.addressName : defaultOptions.addressName;
48
+ /**
49
+ * @type {string|null}
50
+ */
51
+ this.street = options.street !== undefined ? options.street : defaultOptions.street;
52
+ /**
53
+ * @type {string|null}
54
+ */
55
+ this.number = options.number !== undefined ? options.number : defaultOptions.number;
56
+ /**
57
+ * @type {string|null}
58
+ */
59
+ this.city = options.city !== undefined ? options.city : defaultOptions.city;
60
+ /**
61
+ * @type {string|null}
62
+ */
63
+ this.zip = options.zip !== undefined ? options.zip : defaultOptions.zip;
64
+ /**
65
+ * @type {string|null}
66
+ */
67
+ this.country = options.country !== undefined ? options.country : defaultOptions.country;
68
+ }
69
+
70
+ /**
71
+ * derives address attributes from addressKeys
72
+ * @param {undefined|import("ol").Feature<import("ol/geom/Geometry").default>|import("@vcmap/cesium").Cesium3DTileFeature|import("@vcmap/cesium").Cesium3DTilePointFeature} feature
73
+ * @returns {Object}
74
+ */
75
+ getAttributes(feature) {
76
+ const attributes = super.getAttributes(feature);
77
+ const obj = {};
78
+ const applyAddressKeys = (key) => {
79
+ if (this[key]) {
80
+ const derivedValue = extractNestedKey(this[key], attributes);
81
+ if (derivedValue) {
82
+ obj[key] = derivedValue;
83
+ }
84
+ }
85
+ };
86
+ Object.keys(AddressBalloonFeatureInfoView.getDefaultOptions()).forEach(key => applyAddressKeys(key));
87
+ return obj;
88
+ }
89
+
90
+ /**
91
+ * @returns {AddressBalloonFeatureInfoViewOptions}
92
+ */
93
+ toJSON() {
94
+ const config = super.toJSON();
95
+ const defaultOptions = AddressBalloonFeatureInfoView.getDefaultOptions();
96
+ if (this.addressName !== defaultOptions.addressName) {
97
+ config.addressName = this.addressName;
98
+ }
99
+ if (this.street !== defaultOptions.street) {
100
+ config.street = this.street;
101
+ }
102
+ if (this.number !== defaultOptions.number) {
103
+ config.number = this.number;
104
+ }
105
+ if (this.city !== defaultOptions.city) {
106
+ config.city = this.city;
107
+ }
108
+ if (this.zip !== defaultOptions.zip) {
109
+ config.zip = this.zip;
110
+ }
111
+ if (this.country !== defaultOptions.country) {
112
+ config.country = this.country;
113
+ }
114
+ return config;
115
+ }
116
+ }
117
+
118
+ export default AddressBalloonFeatureInfoView;