@vcmap/ui 5.0.0-rc.14 → 5.0.0-rc.15

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 (105) hide show
  1. package/README.md +33 -31
  2. package/build/build.js +9 -0
  3. package/build/buildHelpers.js +12 -10
  4. package/build/commonViteConfig.js +3 -10
  5. package/config/base.config.json +26 -24
  6. package/config/dev.config.json +8 -0
  7. package/config/www.config.json +102 -17
  8. package/dist/assets/cesium.2e288a.js +137226 -0
  9. package/dist/assets/cesium.js +1 -1
  10. package/dist/assets/core.8014d3.js +14473 -0
  11. package/dist/assets/core.js +1 -1
  12. package/dist/assets/index.3f74fa92.js +1 -0
  13. package/dist/assets/ol.31c3a5.js +44279 -0
  14. package/dist/assets/ol.js +1 -1
  15. package/dist/assets/ui.36f84f.css +1 -0
  16. package/dist/assets/ui.36f84f.js +16101 -0
  17. package/dist/assets/ui.js +1 -1
  18. package/dist/assets/vue.a39c10.js +4675 -0
  19. package/dist/assets/vue.js +5 -2
  20. package/dist/assets/{vuetify.202322.css → vuetify.378637.css} +1 -1
  21. package/dist/assets/vuetify.378637.js +21019 -0
  22. package/dist/assets/vuetify.js +5 -2
  23. package/dist/index.html +1 -1
  24. package/index.js +4 -1
  25. package/package.json +10 -10
  26. package/plugins/@vcmap/pluginExample/index.js +14 -0
  27. package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +93 -67
  28. package/plugins/@vcmap/project-selector/ContextsListComponent.vue +8 -1
  29. package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +27 -1
  30. package/plugins/@vcmap/search-nominatim/LICENSE.md +14 -0
  31. package/plugins/@vcmap/search-nominatim/README.md +2 -0
  32. package/plugins/@vcmap/search-nominatim/config.json +4 -0
  33. package/plugins/@vcmap/search-nominatim/index.js +26 -0
  34. package/plugins/@vcmap/search-nominatim/nominatim.js +170 -0
  35. package/plugins/@vcmap/search-nominatim/package.json +43 -0
  36. package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +24 -0
  37. package/plugins/buttonExamples/ButtonExamples.vue +28 -1
  38. package/plugins/categoryTest/Categories.vue +16 -0
  39. package/plugins/categoryTest/Category.vue +30 -4
  40. package/plugins/example/mySuperComponent.vue +12 -1
  41. package/plugins/package.json +2 -1
  42. package/plugins/simple-graph/SimpleGraphComponent.vue +5 -11
  43. package/plugins/test/allIconsComponent.vue +16 -0
  44. package/plugins/test/editor.vue +3 -0
  45. package/plugins/test/emptyComponent.vue +3 -0
  46. package/plugins/test/vcsContent.vue +3 -0
  47. package/src/actions/actionHelper.js +103 -2
  48. package/src/actions/styleSelector.vue +9 -0
  49. package/src/application/VcsApp.vue +77 -9
  50. package/src/application/VcsAttributions.vue +63 -0
  51. package/src/application/VcsAttributionsFooter.vue +87 -0
  52. package/src/application/{Navbar.vue → VcsNavbar.vue} +35 -2
  53. package/src/application/VcsSettings.vue +4 -0
  54. package/src/application/attributionsHelper.js +150 -0
  55. package/src/application/vcsAppWrapper.vue +5 -1
  56. package/src/components/buttons/VcsActionButtonList.vue +8 -1
  57. package/src/components/buttons/VcsButton.vue +7 -1
  58. package/src/components/form-inputs-controls/VcsCheckbox.vue +7 -2
  59. package/src/components/form-inputs-controls/VcsColorPicker.vue +4 -0
  60. package/src/components/form-inputs-controls/VcsFormSection.vue +48 -2
  61. package/src/components/form-inputs-controls/VcsRadio.vue +7 -1
  62. package/src/components/form-inputs-controls/VcsSelect.vue +5 -1
  63. package/src/components/form-inputs-controls/VcsTextArea.vue +2 -0
  64. package/src/components/form-inputs-controls/VcsTextField.vue +6 -2
  65. package/src/components/lists/VcsActionList.vue +12 -1
  66. package/src/components/lists/VcsTreeview.vue +6 -1
  67. package/src/components/lists/VcsTreeviewLeaf.vue +5 -1
  68. package/src/components/lists/VcsTreeviewSearchbar.vue +5 -0
  69. package/src/components/notification/VcsTooltip.vue +14 -9
  70. package/src/components/tables/VcsTable.vue +117 -38
  71. package/src/featureInfo/AddressBalloonComponent.vue +17 -1
  72. package/src/featureInfo/BalloonComponent.vue +57 -23
  73. package/src/featureInfo/balloonFeatureInfoView.js +2 -2
  74. package/src/featureInfo/featureInfo.js +10 -2
  75. package/src/i18n/de.js +15 -0
  76. package/src/i18n/en.js +15 -0
  77. package/src/legend/legendHelper.js +18 -12
  78. package/src/legend/styleLegendItem.vue +20 -1
  79. package/src/legend/vcsLegend.vue +29 -3
  80. package/src/manager/toolbox/GroupToolboxComponent.vue +13 -1
  81. package/src/manager/toolbox/SelectToolboxComponent.vue +13 -1
  82. package/src/manager/toolbox/ToolboxManager.vue +3 -0
  83. package/src/manager/window/WindowComponent.vue +6 -0
  84. package/src/manager/window/WindowComponentHeader.vue +6 -1
  85. package/src/navigation/mapNavigation.vue +15 -36
  86. package/src/navigation/orientationToolsButton.vue +6 -1
  87. package/src/navigation/overviewMap.js +10 -39
  88. package/src/navigation/tiltSlider.vue +3 -0
  89. package/src/navigation/vcsCompass.vue +2 -0
  90. package/src/search/resultItem.vue +89 -0
  91. package/src/search/resultsComponent.vue +98 -0
  92. package/src/search/search.js +326 -0
  93. package/src/search/searchComponent.vue +90 -0
  94. package/src/styles/_typography.scss +3 -0
  95. package/src/styles/utils/_cursor.scss +4 -0
  96. package/src/styles/variables.scss +4 -1
  97. package/src/vcsUiApp.js +16 -0
  98. package/dist/assets/cesium.9489f8.js +0 -8699
  99. package/dist/assets/core.aa346a.js +0 -4
  100. package/dist/assets/index.3cd4fffa.js +0 -1
  101. package/dist/assets/ol.39651b.js +0 -439
  102. package/dist/assets/ui.15ef6a.css +0 -1
  103. package/dist/assets/ui.15ef6a.js +0 -71
  104. package/dist/assets/vue.cbe9d8.js +0 -9
  105. package/dist/assets/vuetify.202322.js +0 -148
@@ -65,9 +65,33 @@
65
65
  </template>
66
66
  <script>
67
67
  import { inject } from 'vue';
68
+ import {
69
+ VCard,
70
+ VCardText,
71
+ VChip,
72
+ VListItem,
73
+ VListItemContent,
74
+ VListItemTitle,
75
+ VListItemAction,
76
+ VAvatar,
77
+ VIcon,
78
+ VDivider,
79
+ } from 'vuetify/lib';
68
80
 
69
81
  export default {
70
82
  name: 'ThemeChanger',
83
+ components: [
84
+ VCard,
85
+ VCardText,
86
+ VChip,
87
+ VListItem,
88
+ VListItemContent,
89
+ VListItemTitle,
90
+ VListItemAction,
91
+ VAvatar,
92
+ VIcon,
93
+ VDivider,
94
+ ],
71
95
  setup() {
72
96
  const app = inject('vcsApp');
73
97
  const plugin = app.plugins.getByKey('@vcmap/theme-changer');
@@ -172,10 +172,37 @@
172
172
  <script>
173
173
  import { VcsButton, VcsActionButtonList } from '@vcmap/ui';
174
174
  import { ref } from 'vue';
175
+ import {
176
+ VCard,
177
+ VListItem,
178
+ VListItemContent,
179
+ VListItemTitle,
180
+ VListItemAction,
181
+ VSwitch,
182
+ VDivider,
183
+ VRow,
184
+ VCol,
185
+ VCardTitle,
186
+ VCardActions,
187
+ } from 'vuetify/lib';
175
188
 
176
189
  export default {
177
190
  name: 'ButtonExamples',
178
- components: { VcsButton, VcsActionButtonList },
191
+ components: {
192
+ VcsButton,
193
+ VcsActionButtonList,
194
+ VCard,
195
+ VListItem,
196
+ VListItemContent,
197
+ VListItemTitle,
198
+ VListItemAction,
199
+ VSwitch,
200
+ VDivider,
201
+ VRow,
202
+ VCol,
203
+ VCardTitle,
204
+ VCardActions,
205
+ },
179
206
  setup() {
180
207
  const active = ref(false);
181
208
  const disabled = ref(false);
@@ -60,6 +60,15 @@
60
60
  import { inject, ref, onUnmounted } from 'vue';
61
61
  import { VcsButton } from '@vcmap/ui';
62
62
  import { GeoJSONLayer } from '@vcmap/core';
63
+ import {
64
+ VDialog,
65
+ VCard,
66
+ VForm,
67
+ VSelect,
68
+ VTextField,
69
+ VBtn,
70
+ VTextarea,
71
+ } from 'vuetify/lib';
63
72
  import Category from './Category.vue';
64
73
 
65
74
  export default {
@@ -67,6 +76,13 @@
67
76
  components: {
68
77
  Category,
69
78
  VcsButton,
79
+ VDialog,
80
+ VCard,
81
+ VForm,
82
+ VSelect,
83
+ VTextField,
84
+ VBtn,
85
+ VTextarea,
70
86
  },
71
87
  setup() {
72
88
  const app = inject('vcsApp');
@@ -7,7 +7,7 @@
7
7
  <span class="float-right">
8
8
  <vcs-button icon="$vcsPlus" @click="dialog = true" />
9
9
  <vcs-button icon="mdi-download" @click="download" />
10
- <a :href="downloadLink" target="_blank" ref="link" download="category.json"/>
10
+ <a :href="downloadLink" target="_blank" ref="link" download="category.json" />
11
11
  </span>
12
12
  </span>
13
13
 
@@ -26,7 +26,9 @@
26
26
  class="mb-1"
27
27
  >
28
28
  <v-list-item-content>
29
- <v-list-item-title class="subtitle-1" >{{ item.name }}</v-list-item-title>
29
+ <v-list-item-title class="subtitle-1">
30
+ {{ item.name }}
31
+ </v-list-item-title>
30
32
  <v-list-item-subtitle>{{ item.type }}</v-list-item-subtitle>
31
33
  </v-list-item-content>
32
34
  </v-list-item>
@@ -40,7 +42,9 @@
40
42
  @submit.prevent="addItem"
41
43
  >
42
44
  <v-textarea v-model="jsonString" />
43
- <v-btn type="submit">Add</v-btn>
45
+ <v-btn type="submit">
46
+ Add
47
+ </v-btn>
44
48
  </v-form>
45
49
  </v-card>
46
50
  </v-dialog>
@@ -51,10 +55,32 @@
51
55
  import { inject, nextTick, ref } from 'vue';
52
56
  import { VcsButton } from '@vcmap/ui';
53
57
  import { getObjectFromClassRegistry } from '@vcmap/core';
58
+ import {
59
+ VVirtualScroll,
60
+ VListItem,
61
+ VListItemContent,
62
+ VListItemTitle,
63
+ VListItemSubtitle,
64
+ VDialog,
65
+ VCard,
66
+ VForm,
67
+ VTextarea,
68
+ } from 'vuetify/lib';
54
69
 
55
70
  export default {
56
71
  name: 'CategoryComponent',
57
- components: { VcsButton },
72
+ components: {
73
+ VcsButton,
74
+ VVirtualScroll,
75
+ VListItem,
76
+ VListItemContent,
77
+ VListItemTitle,
78
+ VListItemSubtitle,
79
+ VDialog,
80
+ VCard,
81
+ VForm,
82
+ VTextarea,
83
+ },
58
84
  props: {
59
85
  categoryName: {
60
86
  type: String,
@@ -107,11 +107,22 @@
107
107
  </template>
108
108
  <script>
109
109
 
110
- import Vue from 'vue';
110
+ import {
111
+ VSheet,
112
+ VContainer,
113
+ VRow,
114
+ VCol,
115
+ VTextField,
116
+ } from 'vuetify/lib';
111
117
 
112
118
  export default {
113
119
  name: 'MySuperComponent',
114
120
  components: {
121
+ VSheet,
122
+ VContainer,
123
+ VRow,
124
+ VCol,
125
+ VTextField,
115
126
  },
116
127
  data() {
117
128
  return {
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "dependencies": {
3
- "@vcmap/hello-world": "^1.0.0"
3
+ "@vcmap/hello-world": "^1.0.0",
4
+ "@vcmap/print": "^1.0.1"
4
5
  }
5
6
  }
@@ -14,19 +14,13 @@
14
14
  </template>
15
15
  <script>
16
16
 
17
- /**
18
- * @description A simple table view for feature attributes using {@link https://vuetifyjs.com/en/api/v-sparkline/#props|vuetify v-sparkline }
19
- * @vue-prop {Object} attributes - the feature's attributes
20
- * @vue-prop {string[]} [labels] - optional array of strings labeling all data points
21
- * @vue-prop {string} [graph='trend'] - Choose between a trendline or bars
22
- * @vue-prop {string} [color='primary'] - optional color of the sparkline of the graph
23
- * @vue-prop {string[]} [gradient] - optional array of colors to use as a linear-gradient
24
- * @vue-prop {boolean} [fill=false] - if true, filled area below sparkline
25
- * @vue-prop {boolean|number|string} [smooth=true] - optional number of px to use as a corner radius. true defaults to 8, false is 0
26
- * @vue-computed {number[]} values - numeric values for the graph derived from the attributeKeys
27
- */
17
+ import { VSparkline } from 'vuetify/lib';
18
+
28
19
  export default {
29
20
  name: 'SimpleGraphComponent',
21
+ components: [
22
+ VSparkline,
23
+ ],
30
24
  props: {
31
25
  attributes: {
32
26
  type: Object,
@@ -16,9 +16,25 @@
16
16
 
17
17
  <script>
18
18
  import { Icons } from '@vcmap/ui';
19
+ import {
20
+ VSheet,
21
+ VList,
22
+ VListItem,
23
+ VListItemIcon,
24
+ VIcon,
25
+ VListItemTitle,
26
+ } from 'vuetify/lib';
19
27
 
20
28
  export default {
21
29
  name: 'AllIconsComponent',
30
+ components: [
31
+ VSheet,
32
+ VList,
33
+ VListItem,
34
+ VListItemIcon,
35
+ VIcon,
36
+ VListItemTitle,
37
+ ],
22
38
  computed: {
23
39
  icons() {
24
40
  return Object.keys(Icons).map(n => `$${n}`);
@@ -21,6 +21,7 @@
21
21
  import { ref, inject } from 'vue';
22
22
  import { VcsButton } from '@vcmap/ui';
23
23
  import { Context } from '@vcmap/core';
24
+ import { VProgressCircular, VTextarea } from 'vuetify/lib';
24
25
 
25
26
  const contextId = 'foo';
26
27
 
@@ -28,6 +29,8 @@
28
29
  name: 'Editor',
29
30
  components: {
30
31
  VcsButton,
32
+ VTextarea,
33
+ VProgressCircular,
31
34
  },
32
35
  setup() {
33
36
  /** @type {VcsUiApp} */
@@ -7,7 +7,10 @@
7
7
 
8
8
  <script>
9
9
 
10
+ import { VSheet } from 'vuetify/lib';
11
+
10
12
  export default {
11
13
  name: 'EmptyComponent',
14
+ components: [VSheet],
12
15
  };
13
16
  </script>
@@ -14,8 +14,11 @@
14
14
 
15
15
  </style>
16
16
  <script>
17
+ import { VSheet } from 'vuetify/lib';
18
+
17
19
  export default {
18
20
  name: 'VcsContent',
21
+ components: [VSheet],
19
22
  setup() {
20
23
  return { };
21
24
  },
@@ -1,8 +1,11 @@
1
1
  import { v4 as uuid } from 'uuid';
2
2
  import { check } from '@vcsuite/check';
3
- import { Collection, MapCollection, Viewpoint } from '@vcmap/core';
3
+ import { Collection, Extent, MapCollection, mercatorProjection, Viewpoint } from '@vcmap/core';
4
+ import { Feature } from 'ol';
5
+ import { reactive, ref } from 'vue';
4
6
  import { vcsAppSymbol } from '../pluginHelper.js';
5
- import { getWindowPositionOptions } from '../manager/window/windowManager.js';
7
+ import { getWindowPositionOptions, WindowSlot } from '../manager/window/windowManager.js';
8
+ import SearchComponent from '../search/searchComponent.vue';
6
9
 
7
10
  /**
8
11
  * @typedef {Object} ActionOptions
@@ -88,6 +91,60 @@ export function createToggleAction(actionOptions, windowComponent, windowManager
88
91
  return { action, destroy };
89
92
  }
90
93
 
94
+ /**
95
+ * Creates a toggle button for the search tool, which is only available, if at least one search implementation is registered.
96
+ * @param {VcsUiApp} app
97
+ * @returns {{ searchAction: import("vue").Ref<import("vue").UnwrapRef<VcsAction>|null>, destroy: function():void }}
98
+ */
99
+ export function createSearchButtonAction(app) {
100
+ let destroyAction = () => {};
101
+ const searchAction = ref(null);
102
+ const determineAction = () => {
103
+ if (app.windowManager.has('searchId')) {
104
+ app.windowManager.remove('searchId');
105
+ }
106
+ if (app.search.size > 0 && searchAction.value === null) {
107
+ const action = createToggleAction(
108
+ {
109
+ name: 'search.title',
110
+ icon: '$vcsSearch',
111
+ title: 'search.tooltip',
112
+ },
113
+ {
114
+ id: 'searchId',
115
+ component: SearchComponent,
116
+ state: { hideHeader: true },
117
+ slot: WindowSlot.DETACHED,
118
+ position: {
119
+ right: 0,
120
+ top: 0,
121
+ width: 440,
122
+ },
123
+ },
124
+ app.windowManager,
125
+ vcsAppSymbol,
126
+ );
127
+ destroyAction = action.destroy;
128
+ searchAction.value = reactive(action.action);
129
+ } else if (searchAction.value !== null) {
130
+ destroyAction();
131
+ destroyAction = () => {};
132
+ searchAction.value = null;
133
+ }
134
+ };
135
+ determineAction();
136
+ const listeners = [
137
+ app.search.added.addEventListener(determineAction),
138
+ app.search.removed.addEventListener(determineAction),
139
+ ];
140
+ const destroy = () => {
141
+ destroyAction();
142
+ listeners.forEach((cb) => { cb(); });
143
+ };
144
+
145
+ return { searchAction, destroy };
146
+ }
147
+
91
148
  /**
92
149
  * Creates an action which will toggle the overview map (opening & closing the window and activating/ deactivating the overview map).
93
150
  * @param {OverviewMap} overviewMap
@@ -260,3 +317,47 @@ export function createGoToViewpointAction(actionOptions, viewpoint, viewpointCol
260
317
  },
261
318
  };
262
319
  }
320
+
321
+ /**
322
+ * calculates and returns a viewpoint using feature's extent
323
+ * @param {import("ol").Feature<import("ol/geom/Geometry").default>} feature
324
+ * @returns {Viewpoint|null}
325
+ */
326
+ export function getViewpointFromFeature(feature) {
327
+ const extent = new Extent({
328
+ coordinates: feature.getGeometry()?.getExtent?.(),
329
+ projection: mercatorProjection,
330
+ });
331
+
332
+ if (!extent || !extent.isValid()) {
333
+ return null;
334
+ }
335
+ return Viewpoint.createViewpointFromExtent(extent);
336
+ }
337
+
338
+ /**
339
+ * Creates an action, which when clicked, zooms to the provided feature
340
+ * @param {ActionOptions} actionOptions
341
+ * @param {import("ol").Feature<import("ol/geom/Geometry").default>} feature
342
+ * @param {import("@vcmap/core").MapCollection} mapCollection
343
+ * @returns {VcsAction|null} returns null if the feature does not have a geometry with a valid extent
344
+ */
345
+ export function createZoomToFeatureAction(actionOptions, feature, mapCollection) {
346
+ check(actionOptions, {
347
+ name: String,
348
+ icon: [undefined, String],
349
+ title: [undefined, String],
350
+ });
351
+ check(feature, Feature);
352
+ check(mapCollection, MapCollection);
353
+
354
+ const viewpoint = getViewpointFromFeature(feature);
355
+
356
+ return {
357
+ title: 'search.zoomToFeatureAction',
358
+ ...actionOptions,
359
+ async callback() {
360
+ await mapCollection.activeMap.gotoViewpoint(viewpoint);
361
+ },
362
+ };
363
+ }
@@ -20,12 +20,21 @@
20
20
 
21
21
  <script>
22
22
  import { computed, inject, onUnmounted, ref } from 'vue';
23
+ import {
24
+ VChip, VList, VListItem, VListItemContent, VListItemIcon, VSheet,
25
+ } from 'vuetify/lib';
23
26
  import VcsSelect from '../components/form-inputs-controls/VcsSelect.vue';
24
27
 
25
28
  export default {
26
29
  name: 'StyleSelector',
27
30
  components: {
28
31
  VcsSelect,
32
+ VSheet,
33
+ VList,
34
+ VListItem,
35
+ VListItemIcon,
36
+ VChip,
37
+ VListItemContent,
29
38
  },
30
39
  props: {
31
40
  availableStyles: {
@@ -1,18 +1,28 @@
1
1
  <template>
2
2
  <v-container class="fill-height pa-0" absolute fluid>
3
- <Navbar />
3
+ <VcsNavbar />
4
4
  <v-container class="vcs-main pa-0" :class="{ 'vcs-main-xs': $vuetify.breakpoint.xs }" fluid absolute>
5
5
  <template v-if="$vuetify.breakpoint.xs">
6
6
  <img v-if="mobileLogo" :src="mobileLogo" alt="Logo" draggable="false" class="mobile-logo">
7
7
  <div v-else class="company-logo-mobile mobile-logo" />
8
8
  </template>
9
+ <VcsButton
10
+ v-if="!$vuetify.breakpoint.smAndUp && $vuetify.breakpoint.mobile"
11
+ :key="attributionAction.name"
12
+ :tooltip="attributionAction.title"
13
+ :icon="attributionAction.icon"
14
+ :active="attributionAction.active"
15
+ @click.stop="attributionAction.callback($event)"
16
+ small
17
+ class="z-index-1 mobile-attribution-btn"
18
+ />
9
19
  <VcsMap :map-id="mapId" />
10
20
  <MapNavigation />
11
21
  <ToolboxManagerComponent />
12
22
  <WindowManagerComponent />
13
23
  </v-container>
14
- <v-footer absolute v-if="!$vuetify.breakpoint.xs">
15
- {{ $t('footer.title') }}
24
+ <v-footer absolute v-if="$vuetify.breakpoint.smAndUp" min-height="22px">
25
+ <VcsAttributionsFooter :entries="attributionEntries" :attribution-action="attributionAction" />
16
26
  </v-footer>
17
27
  </v-container>
18
28
  </template>
@@ -26,7 +36,7 @@
26
36
  top: 48px;
27
37
  left: 0;
28
38
  right: 0;
29
- bottom: 33px;
39
+ bottom: 22px;
30
40
  }
31
41
 
32
42
  .vcs-main-xs {
@@ -43,6 +53,12 @@
43
53
  z-index: 1;
44
54
  }
45
55
 
56
+ .mobile-attribution-btn{
57
+ position: fixed;
58
+ right: 2px;
59
+ bottom: 56px;
60
+ }
61
+
46
62
  </style>
47
63
 
48
64
  <script>
@@ -56,12 +72,13 @@
56
72
  watch,
57
73
  } from 'vue';
58
74
  import { getVcsAppById } from '@vcmap/core';
75
+ import { VContainer, VFooter } from 'vuetify/lib';
59
76
  import WindowManagerComponent from '../manager/window/WindowManager.vue';
60
77
  import ToolboxManagerComponent from '../manager/toolbox/ToolboxManager.vue';
61
78
  import { ButtonLocation } from '../manager/navbarManager.js';
62
79
  import { vcsAppSymbol } from '../pluginHelper.js';
63
80
  import VcsMap from './VcsMap.vue';
64
- import Navbar from './Navbar.vue';
81
+ import VcsNavbar from './VcsNavbar.vue';
65
82
  import { createMapButtonAction, createToggleAction } from '../actions/actionHelper.js';
66
83
  import MapNavigation from '../navigation/mapNavigation.vue';
67
84
  import VcsSettings from './VcsSettings.vue';
@@ -70,6 +87,10 @@
70
87
  import { defaultPrimaryColor } from '../vuePlugins/vuetify.js';
71
88
  import VcsLegend from '../legend/vcsLegend.vue';
72
89
  import { getLegendEntries } from '../legend/legendHelper.js';
90
+ import VcsAttributionsFooter from './VcsAttributionsFooter.vue';
91
+ import VcsButton from '../components/buttons/VcsButton.vue';
92
+ import VcsAttributions from './VcsAttributions.vue';
93
+ import { getAttributions } from './attributionsHelper.js';
73
94
 
74
95
  /**
75
96
  * You should call this function in the component providing the vcsUiApp to your
@@ -178,10 +199,10 @@
178
199
 
179
200
  /**
180
201
  * adds or removes the legend button, depending on the number of entries
181
- * @param {import("vue").Reactive<{string,LegendEntry}>} newEntries
202
+ * @param {Array<LegendEntry>} newEntries
182
203
  */
183
204
  const handleLegendButton = (newEntries) => {
184
- if (Object.keys(newEntries).length > 0) {
205
+ if (newEntries.length > 0) {
185
206
  if (!app.navbarManager.has('legend')) {
186
207
  app.navbarManager.add(
187
208
  {
@@ -197,7 +218,7 @@
197
218
  app.windowManager.remove('legend');
198
219
  }
199
220
  };
200
- handleLegendButton(entries);
221
+ handleLegendButton(entries.value);
201
222
 
202
223
  const stopWatching = watch(
203
224
  entries,
@@ -329,6 +350,45 @@
329
350
  };
330
351
  }
331
352
 
353
+ /**
354
+ * This helper gets attributions of all active maps, layers and oblique collections and returns an array of entries.
355
+ * It also returns a attributionAction to toggle the attributions window and a destroy function.
356
+ * @param {VcsUiApp} app
357
+ * @returns {{attributionEntries: import("vue").Ref<Array<AttributionEntry>>, attributionAction: VcsAction, destroyAttributions: function():void}}
358
+ */
359
+ export function setupAttributions(app) {
360
+ const { entries, destroy } = getAttributions(app);
361
+
362
+ const { action: attributionAction, destroy: attributionDestroy } = createToggleAction(
363
+ {
364
+ name: 'attributionToggle',
365
+ icon: 'mdi-chevron-double-right',
366
+ title: 'footer.attributions.tooltip',
367
+ },
368
+ {
369
+ id: 'attribution',
370
+ component: VcsAttributions,
371
+ state: {
372
+ headerTitle: 'footer.attributions.title',
373
+ headerIcon: 'mdi-copyright',
374
+ },
375
+ slot: WindowSlot.DYNAMIC_RIGHT,
376
+ props: { entries },
377
+ },
378
+ app.windowManager,
379
+ vcsAppSymbol,
380
+ );
381
+
382
+ return {
383
+ attributionEntries: entries,
384
+ attributionAction,
385
+ destroyAttributions: () => {
386
+ destroy();
387
+ attributionDestroy();
388
+ },
389
+ };
390
+ }
391
+
332
392
  /**
333
393
  * The base component to setup the entire application. To embed the VcsApp, use this component.
334
394
  * @vue-prop {string} appId - the id of the app to inject. this will setup listeners on the app to call vcsAppMounted on plugins
@@ -336,11 +396,15 @@
336
396
  */
337
397
  export default {
338
398
  components: {
399
+ VcsButton,
400
+ VcsAttributionsFooter,
339
401
  MapNavigation,
340
- Navbar,
402
+ VcsNavbar,
341
403
  VcsMap,
342
404
  WindowManagerComponent,
343
405
  ToolboxManagerComponent,
406
+ VContainer,
407
+ VFooter,
344
408
  },
345
409
  props: {
346
410
  appId: {
@@ -360,6 +424,7 @@
360
424
  const settingsDestroy = setupSettingsWindow(app);
361
425
  const destroyComponentsWindow = setupComponentsWindow(app);
362
426
  const destroyThemingListener = setupUiConfigTheming(app, getCurrentInstance().proxy.$vuetify);
427
+ const { attributionEntries, attributionAction, destroyAttributions } = setupAttributions(app);
363
428
 
364
429
  let pluginMountedListener;
365
430
  onMounted(() => {
@@ -376,11 +441,14 @@
376
441
  settingsDestroy();
377
442
  destroyComponentsWindow();
378
443
  destroyThemingListener();
444
+ destroyAttributions();
379
445
  });
380
446
 
381
447
  return {
382
448
  mapId,
383
449
  mobileLogo: computed(() => app.uiConfig.config.value.mobileLogo ?? app.uiConfig.config.value.logo),
450
+ attributionEntries,
451
+ attributionAction,
384
452
  };
385
453
  },
386
454
  };
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <v-list>
3
+ <v-list-item v-for="({key, title, attributions}) in entries" :key="key">
4
+ <v-list-item-content>
5
+ <v-list-item-title>{{ $t(title) }}</v-list-item-title>
6
+ <v-list-item-subtitle
7
+ v-for="attribution in attributions"
8
+ :key="attribution.provider"
9
+ :title="`${$t(attribution.provider)} ${attribution.year}`"
10
+ >
11
+ <a
12
+ :href="attribution.url"
13
+ target="_blank"
14
+ class="text--secondary"
15
+ >
16
+ {{ $t(attribution.provider) }} {{ attribution.year }}
17
+ </a>
18
+ </v-list-item-subtitle>
19
+ </v-list-item-content>
20
+ </v-list-item>
21
+ </v-list>
22
+ </template>
23
+
24
+ <style lang="scss" scoped>
25
+
26
+ ::v-deep {
27
+ a:before {
28
+ content: '\00a9\00a0'
29
+ }
30
+ }
31
+
32
+ </style>
33
+
34
+ <script >
35
+ import {
36
+ VList,
37
+ VListItem,
38
+ VListItemContent,
39
+ VListItemTitle,
40
+ VListItemSubtitle,
41
+ } from 'vuetify/lib';
42
+
43
+ /**
44
+ * Lists attributions of maps, layers and oblique collections
45
+ * @vue-prop {import("vue").Ref<Array<AttributionEntry>} entries - array with one entry per active VcsObject
46
+ */
47
+ export default {
48
+ name: 'VcsAttributions',
49
+ components: {
50
+ VList,
51
+ VListItem,
52
+ VListItemContent,
53
+ VListItemTitle,
54
+ VListItemSubtitle,
55
+ },
56
+ props: {
57
+ entries: {
58
+ type: Object,
59
+ default: () => {},
60
+ },
61
+ },
62
+ };
63
+ </script>