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

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 (132) 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 +30 -24
  6. package/config/dev.config.json +13 -1
  7. package/config/www.config.json +104 -17
  8. package/dist/assets/cesium.430460.js +137226 -0
  9. package/dist/assets/cesium.js +1 -1
  10. package/dist/assets/core.5089ba.js +16024 -0
  11. package/dist/assets/core.js +1 -1
  12. package/dist/assets/index.854f8e2b.js +1 -0
  13. package/dist/assets/ol.9be53a.js +44279 -0
  14. package/dist/assets/ol.js +1 -1
  15. package/dist/assets/{ui.15ef6a.css → ui.49010a.css} +1 -1
  16. package/dist/assets/ui.49010a.js +16776 -0
  17. package/dist/assets/ui.js +1 -1
  18. package/dist/assets/vue.247c1c.js +4675 -0
  19. package/dist/assets/vue.js +5 -2
  20. package/dist/assets/{vuetify.202322.css → vuetify.735e58.css} +1 -1
  21. package/dist/assets/vuetify.735e58.js +21019 -0
  22. package/dist/assets/vuetify.js +5 -2
  23. package/dist/index.html +1 -1
  24. package/index.html +77 -0
  25. package/index.js +8 -1
  26. package/package.json +12 -10
  27. package/plugins/@vcmap/create-link/fallbackCreateLink.vue +4 -1
  28. package/plugins/@vcmap/create-link/index.js +4 -1
  29. package/plugins/@vcmap/pluginExample/exampleActions.js +45 -0
  30. package/plugins/@vcmap/pluginExample/index.js +38 -1
  31. package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +152 -98
  32. package/plugins/@vcmap/project-selector/ContextsListComponent.vue +8 -1
  33. package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +27 -1
  34. package/plugins/@vcmap/search-nominatim/LICENSE.md +14 -0
  35. package/plugins/@vcmap/search-nominatim/README.md +2 -0
  36. package/plugins/@vcmap/search-nominatim/config.json +4 -0
  37. package/plugins/@vcmap/search-nominatim/index.js +26 -0
  38. package/plugins/@vcmap/search-nominatim/nominatim.js +170 -0
  39. package/plugins/@vcmap/search-nominatim/package.json +43 -0
  40. package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +26 -0
  41. package/plugins/buttonExamples/ButtonExamples.vue +28 -1
  42. package/plugins/categoryTest/Categories.vue +16 -0
  43. package/plugins/categoryTest/Category.vue +30 -4
  44. package/plugins/example/mySuperComponent.vue +12 -1
  45. package/plugins/notifier/index.js +31 -0
  46. package/plugins/notifier/notifierTester.vue +88 -0
  47. package/plugins/package.json +2 -1
  48. package/plugins/simple-graph/SimpleGraphComponent.vue +5 -11
  49. package/plugins/test/allIconsComponent.vue +16 -0
  50. package/plugins/test/editor.vue +3 -0
  51. package/plugins/test/emptyComponent.vue +3 -0
  52. package/plugins/test/index.js +22 -0
  53. package/plugins/test/myCustomHeader.vue +9 -1
  54. package/plugins/test/testList.vue +287 -0
  55. package/plugins/test/vcsContent.vue +3 -0
  56. package/plugins/test/windowManagerExample.vue +3 -0
  57. package/plugins/wizardExample/index.js +41 -0
  58. package/plugins/wizardExample/wizardExample.vue +77 -0
  59. package/src/actions/actionHelper.js +103 -2
  60. package/src/actions/styleSelector.vue +9 -0
  61. package/src/application/VcsApp.vue +95 -17
  62. package/src/application/VcsAttributions.vue +63 -0
  63. package/src/application/VcsAttributionsFooter.vue +87 -0
  64. package/src/application/{Navbar.vue → VcsNavbar.vue} +35 -2
  65. package/src/application/VcsSettings.vue +4 -0
  66. package/src/application/attributionsHelper.js +150 -0
  67. package/src/application/vcsAppWrapper.vue +5 -1
  68. package/src/components/buttons/VcsActionButtonList.vue +8 -1
  69. package/src/components/buttons/VcsButton.vue +7 -1
  70. package/src/components/form-inputs-controls/VcsCheckbox.vue +7 -2
  71. package/src/components/form-inputs-controls/VcsColorPicker.vue +4 -0
  72. package/src/components/form-inputs-controls/VcsFormSection.vue +55 -9
  73. package/src/components/form-inputs-controls/VcsRadio.vue +7 -1
  74. package/src/components/form-inputs-controls/VcsSelect.vue +38 -2
  75. package/src/components/form-inputs-controls/VcsTextArea.vue +2 -0
  76. package/src/components/form-inputs-controls/VcsTextField.vue +16 -4
  77. package/src/components/form-inputs-controls/VcsWizard.vue +133 -0
  78. package/src/components/imageElementInjector.vue +22 -0
  79. package/src/components/lists/VcsActionList.vue +12 -1
  80. package/src/components/lists/VcsList.vue +466 -0
  81. package/src/components/lists/VcsTreeview.vue +7 -3
  82. package/src/components/lists/VcsTreeviewLeaf.vue +23 -51
  83. package/src/components/lists/VcsTreeviewSearchbar.vue +6 -23
  84. package/src/components/notification/VcsTooltip.vue +14 -9
  85. package/src/components/tables/VcsTable.vue +129 -38
  86. package/src/contentTree/LayerTree.vue +1 -1
  87. package/src/contentTree/contentTreeItem.js +13 -13
  88. package/src/contentTree/subContentTreeItem.js +1 -1
  89. package/src/contentTree/vcsObjectContentTreeItem.js +1 -1
  90. package/src/featureInfo/AddressBalloonComponent.vue +17 -1
  91. package/src/featureInfo/BalloonComponent.vue +63 -27
  92. package/src/featureInfo/balloonFeatureInfoView.js +14 -14
  93. package/src/featureInfo/balloonHelper.js +4 -0
  94. package/src/featureInfo/featureInfo.js +23 -2
  95. package/src/featureInfo/featureInfoInteraction.js +1 -1
  96. package/src/i18n/de.js +22 -0
  97. package/src/i18n/en.js +22 -0
  98. package/src/icons/+all.js +4 -0
  99. package/src/icons/WandIcon.vue +63 -0
  100. package/src/legend/legendHelper.js +18 -12
  101. package/src/legend/styleLegendItem.vue +20 -1
  102. package/src/legend/vcsLegend.vue +29 -3
  103. package/src/manager/toolbox/GroupToolboxComponent.vue +13 -1
  104. package/src/manager/toolbox/SelectToolboxComponent.vue +13 -1
  105. package/src/manager/toolbox/ToolboxManager.vue +3 -0
  106. package/src/manager/window/WindowComponent.vue +15 -2
  107. package/src/manager/window/WindowComponentHeader.vue +38 -7
  108. package/src/manager/window/WindowManager.vue +1 -0
  109. package/src/manager/window/windowManager.js +11 -1
  110. package/src/navigation/mapNavigation.vue +15 -36
  111. package/src/navigation/orientationToolsButton.vue +6 -1
  112. package/src/navigation/overviewMap.js +19 -47
  113. package/src/navigation/tiltSlider.vue +3 -0
  114. package/src/navigation/vcsCompass.vue +2 -0
  115. package/src/notifier/notifier.js +121 -0
  116. package/src/notifier/notifierComponent.vue +84 -0
  117. package/src/search/resultItem.vue +89 -0
  118. package/src/search/resultsComponent.vue +98 -0
  119. package/src/search/search.js +326 -0
  120. package/src/search/searchComponent.vue +90 -0
  121. package/src/styles/_typography.scss +3 -0
  122. package/src/styles/utils/_cursor.scss +4 -0
  123. package/src/styles/variables.scss +23 -4
  124. package/src/vcsUiApp.js +35 -1
  125. package/src/vuePlugins/vuetify.js +2 -0
  126. package/dist/assets/cesium.9489f8.js +0 -8699
  127. package/dist/assets/core.aa346a.js +0 -4
  128. package/dist/assets/index.3cd4fffa.js +0 -1
  129. package/dist/assets/ol.39651b.js +0 -439
  130. package/dist/assets/ui.15ef6a.js +0 -71
  131. package/dist/assets/vue.cbe9d8.js +0 -9
  132. package/dist/assets/vuetify.202322.js +0 -148
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <v-list dense class="ma-0 overflow-y-auto vcs-search-results">
3
+ <v-list-item-group
4
+ v-model="highlighted"
5
+ >
6
+ <v-list-item
7
+ v-for="(item, index) in results"
8
+ :key="index"
9
+ color="secondary"
10
+ class="px-0"
11
+ >
12
+ <v-list-item-content>
13
+ <ResultItem
14
+ :item="item"
15
+ :query="query"
16
+ class="cursor-pointer"
17
+ />
18
+ <v-divider
19
+ v-if="index < results.length - 1"
20
+ :key="index"
21
+ />
22
+ </v-list-item-content>
23
+ </v-list-item>
24
+ </v-list-item-group>
25
+ </v-list>
26
+ </template>
27
+
28
+ <script>
29
+ import { inject, onUnmounted, ref, computed } from 'vue';
30
+ import { VDivider, VList, VListItem, VListItemContent, VListItemGroup } from 'vuetify/lib';
31
+ import ResultItem from './resultItem.vue';
32
+
33
+ /**
34
+ * ResultsComponent listing all available result items in a scrollable list
35
+ * @vue-prop {string} query - The query string forwarded to mark results within resultItem component.
36
+ * @vue-prop {Array<ResultItem>} results - Array of results.
37
+ * @vue-computed {import("vue").Ref<string>} highlighted - The highlighted result item. Updates also on feature select.
38
+ */
39
+ export default {
40
+ name: 'ResultsComponent',
41
+ components: {
42
+ ResultItem,
43
+ VList,
44
+ VListItemGroup,
45
+ VListItem,
46
+ VListItemContent,
47
+ VDivider,
48
+ },
49
+ props: {
50
+ query: {
51
+ type: String,
52
+ default: '',
53
+ },
54
+ results: {
55
+ type: Array,
56
+ required: true,
57
+ },
58
+ },
59
+ setup(props) {
60
+ const highlightedRef = ref(-1);
61
+ /** @type {VcsUiApp} */
62
+ const app = inject('vcsApp');
63
+ const selectedListener = app.featureInfo.featureChanged.addEventListener((feature) => {
64
+ if (highlightedRef.value >= 0) {
65
+ if (feature && props.results[highlightedRef.value].feature === feature) {
66
+ return;
67
+ }
68
+ highlightedRef.value = -1;
69
+ } else if (feature) {
70
+ highlightedRef.value = props.results.findIndex(r => r.feature === feature);
71
+ }
72
+ });
73
+
74
+ onUnmounted(() => {
75
+ selectedListener();
76
+ });
77
+
78
+ return {
79
+ highlighted: computed({
80
+ get() { return highlightedRef.value; },
81
+ set(value) {
82
+ highlightedRef.value = value;
83
+ if (value >= 0) {
84
+ const item = props.results[value];
85
+ item.clicked();
86
+ }
87
+ },
88
+ }),
89
+ };
90
+ },
91
+ };
92
+ </script>
93
+
94
+ <style scoped>
95
+ .vcs-search-results {
96
+ max-height: 400px;
97
+ }
98
+ </style>
@@ -0,0 +1,326 @@
1
+ import {
2
+ IndexedCollection,
3
+ markVolatile,
4
+ maxZIndex,
5
+ mercatorProjection,
6
+ VcsEvent,
7
+ vcsLayerName,
8
+ VectorLayer,
9
+ VectorStyleItem, Viewpoint,
10
+ } from '@vcmap/core';
11
+ import { shallowRef } from 'vue';
12
+ import { check } from '@vcsuite/check';
13
+ import { Icon } from 'ol/style.js';
14
+ import { getLogger } from '@vcsuite/logger';
15
+ import { vcsAppSymbol } from '../pluginHelper.js';
16
+ import { defaultPrimaryColor } from '../vuePlugins/vuetify.js';
17
+ import { getViewpointFromFeature } from '../actions/actionHelper.js';
18
+
19
+ /**
20
+ * A readonly rendering interface of a ResultItem.
21
+ * A ResultItem must provide either a feature, a clicked handler or both.
22
+ * @typedef {Object} ResultItem
23
+ * @property {string} title
24
+ * @property {string} [icon] An optional icon
25
+ * @property {Array<VcsAction>} [actions]
26
+ * @property {function():Promise<void>} [clicked] Obligatory, if no feature is provided. Can overwrite default zoom to feature behaviour.
27
+ * @property {import("ol").Feature|undefined} [feature] If a feature is provided, the feature is added to the result layer and search zooms to the layer's extent. Default clicked handler is zoom to feature, highlight feature and select feature, if feature has a FeatureInfoView.
28
+ */
29
+
30
+
31
+ /**
32
+ * @interface SearchImpl
33
+ * @property {string} name Name of the implementation. Must be the name of the plugin the SearchImpl is owned by
34
+ * @property {function(q:string):Array<ResultItem>} search
35
+ * @property {function(q:string):Array<string>} [suggest] // XXX currently not implemented in UI at Beta state
36
+ * @property{function():void} abort - should abort any ongoing requests to search or suggest without throwing an error
37
+ * @property {function():void} destroy
38
+ */
39
+
40
+ /**
41
+ * @param {string} color
42
+ * @returns {import("ol/style/Icon").Options}
43
+ */
44
+ function getPointResultIcon(color) {
45
+ return {
46
+ src: `data:image/svg+xml,%3Csvg xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns%23' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns%23' xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='icon_24_poi' width='24' height='23.994' viewBox='0 0 24 23.994' sodipodi:docname='mapIcon.svg'%3E%3Cg id='Gruppe_1972' transform='translate(-571 -609.477)'%3E%3Cpath id='Pfad_773' d='M583,611a8.009,8.009,0,0,0-8,8c0,5.314,6.952,13.32,7.248,13.658a1,1,0,0,0,1.5,0c.3-.338,7.248-8.344,7.248-13.658A8.009,8.009,0,0,0,583,611Zm0,19.444c-2.18-2.685-6-8.09-6-11.444a6,6,0,0,1,12,0C589,622.354,585.18,627.759,583,630.444Z' fill='currentColor' /%3E%3Cpath id='Pfad_774' d='M583,615a4,4,0,1,0,4,4A4,4,0,0,0,583,615Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,583,621Z' fill='currentColor' /%3E%3C/g%3E%3Cpath fill='${encodeURIComponent(color)}' d='M 11.672998,20.526286 C 8.5115524,16.526958 6.4310003,12.714969 6.0702695,10.260963 6.0109099,9.8571482 6.0115821,9.1201807 6.0716855,8.7084104 6.4424582,6.1682348 8.3335069,4.1603103 10.828528,3.6575721 c 1.904966,-0.383844 3.881822,0.1903514 5.289639,1.5364231 0.993092,0.9495349 1.610829,2.1488769 1.810148,3.5144152 0.0601,0.4117703 0.06077,1.1487378 0.0014,1.5525526 -0.357076,2.429138 -2.337816,6.081898 -5.487559,10.119822 -0.224045,0.287223 -0.415188,0.530536 -0.424763,0.540696 -0.0096,0.01016 -0.16456,-0.167678 -0.344411,-0.395195 z m 0.990366,-7.047968 c 0.894914,-0.146674 1.762065,-0.627065 2.349286,-1.301476 0.86707,-0.995812 1.194989,-2.3427819 0.880571,-3.6170541 -0.379849,-1.5394474 -1.596396,-2.6842781 -3.173401,-2.9863277 -0.368703,-0.070619 -1.070937,-0.070619 -1.43964,0 C 9.7056173,5.875042 8.48604,7.0227247 8.1067793,8.5597879 7.8410265,9.6368274 8.0329903,10.787029 8.6317551,11.705317 c 0.5717674,0.876885 1.4205679,1.474277 2.4457369,1.721329 0.47704,0.114961 1.079877,0.134602 1.585872,0.05167 z' id='path1432' /%3E%3C/svg%3E`,
47
+ scale: 1,
48
+ color,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * sets up result layer for displaying search results
54
+ * @param {VcsUiApp} app
55
+ * @returns {{ resultLayer: import("@vcmap/core").VectorLayer, destroy: (function(): void)}}
56
+ */
57
+ function setupSearchResultLayer(app) {
58
+ const resultLayer = new VectorLayer({
59
+ projection: mercatorProjection.toJSON(),
60
+ vectorProperties: {
61
+ altitudeMode: 'clampToGround',
62
+ classificationType: 'both',
63
+ },
64
+ properties: {
65
+ title: 'search.title',
66
+ },
67
+ zIndex: maxZIndex,
68
+ });
69
+ markVolatile(resultLayer);
70
+ app.layers.add(resultLayer);
71
+
72
+ const style = new VectorStyleItem({
73
+ image: getPointResultIcon(defaultPrimaryColor),
74
+ fill: {
75
+ color: 'rgba(237, 237, 237, 0.1)',
76
+ },
77
+ stroke: {
78
+ color: defaultPrimaryColor,
79
+ width: 5,
80
+ },
81
+ });
82
+ resultLayer.setStyle(style);
83
+
84
+ function setResultColor(color) {
85
+ style.stroke?.setColor(color);
86
+ style.image = new Icon(getPointResultIcon(color));
87
+ resultLayer.forceRedraw();
88
+ }
89
+
90
+ const listeners = [
91
+ app.uiConfig.added.addEventListener((item) => {
92
+ if (item.name === 'primaryColor') {
93
+ setResultColor(item.value);
94
+ }
95
+ }),
96
+ app.uiConfig.removed.addEventListener((item) => {
97
+ if (item.name === 'primaryColor') {
98
+ setResultColor(defaultPrimaryColor);
99
+ }
100
+ }),
101
+ ];
102
+
103
+ const destroy = () => {
104
+ resultLayer.destroy();
105
+ listeners.forEach(cb => cb());
106
+ };
107
+
108
+ return { resultLayer, destroy };
109
+ }
110
+
111
+ /**
112
+ * Symbol added to search implementations to specify the implementation's owner
113
+ * @type {symbol}
114
+ */
115
+ const searchImplOwnerSymbol = Symbol('featureInfoView');
116
+
117
+
118
+ /**
119
+ * Collection of SearchImpl
120
+ * @extends {IndexedCollection<SearchImpl<ResultItem>>}
121
+ */
122
+ class Search extends IndexedCollection {
123
+ /**
124
+ * @param {VcsUiApp} app
125
+ */
126
+ constructor(app) {
127
+ super();
128
+
129
+ /**
130
+ * @type {VcsUiApp}
131
+ * @private
132
+ */
133
+ this._app = app;
134
+
135
+ /**
136
+ * An event triggered every time the currentResults array changes,
137
+ * either by a new search providing the new results or
138
+ * on clearing, if the results array has not been empty already.
139
+ * @type {import("@vcmap/core").VcsEvent<Array<ResultItem>>}
140
+ * @private
141
+ */
142
+ this._resultsChanged = new VcsEvent();
143
+
144
+ /**
145
+ * @type {import("vue").Ref<Array<ResultItem>>}
146
+ * @private
147
+ */
148
+ this._currentResults = shallowRef([]);
149
+ const { resultLayer, destroy } = setupSearchResultLayer(app);
150
+ /**
151
+ * @type {import("@vcmap/core").VectorLayer}
152
+ * @private
153
+ */
154
+ this._resultLayer = resultLayer;
155
+ /**
156
+ * @type {function():void}
157
+ * @private
158
+ */
159
+ this._destroyResultLayer = destroy;
160
+ }
161
+
162
+ /**
163
+ * An event triggered every time the currentResults array changes,
164
+ * either by a new search providing the new results or
165
+ * on clearing, if the results array has not been empty already.
166
+ * @type {import("@vcmap/core").VcsEvent<Array<ResultItem>>}
167
+ * @readonly
168
+ */
169
+ get resultsChanged() {
170
+ return this._resultsChanged;
171
+ }
172
+
173
+ /**
174
+ * @type {import("vue").Ref<Array<ResultItem>>}
175
+ * @readonly
176
+ */
177
+ get currentResults() {
178
+ return this._currentResults;
179
+ }
180
+
181
+ /**
182
+ * @type {VectorLayer}
183
+ * @readonly
184
+ */
185
+ get resultLayer() {
186
+ return this._resultLayer;
187
+ }
188
+
189
+ /**
190
+ * @param {SearchImpl} item
191
+ * @param {string|symbol} owner pluginName or vcsAppSymbol
192
+ * @param {number=} index
193
+ */
194
+ add(item, owner, index) {
195
+ check(owner, [String, vcsAppSymbol]);
196
+ check(item.search, Function);
197
+ if (item.name !== owner) {
198
+ getLogger('Search').warning('SearchImplementations must be named as the plugin they are owned by.');
199
+ }
200
+ item[searchImplOwnerSymbol] = owner;
201
+ super.add(item, index);
202
+ }
203
+
204
+ /**
205
+ * removes all search implementations of a specific owner (plugin) and fires removed Events
206
+ * @param {string|vcsAppSymbol} owner
207
+ */
208
+ removeOwner(owner) {
209
+ this._array.forEach((impl) => {
210
+ if (impl[searchImplOwnerSymbol] === owner) {
211
+ super.remove(impl);
212
+ }
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Get the results for a given query string.
218
+ * Available features are added to results layer and map is zoomed to all results (extent of result layer).
219
+ * Adds default clicked handler to result items with feature, which zooms to and highlights said feature. If feature has FeatureInfoView, feature is selected by featureInfo.
220
+ * @param {string} q
221
+ * @returns {Promise<Array<ResultItem>>}
222
+ */
223
+ async search(q) {
224
+ this.clearResults();
225
+ const promises = await Promise.allSettled([...this._array].map(impl => impl.search(q)));
226
+ const isAborted = promises.some(r => r.status === 'rejected' && r.reason?.name === 'AbortError');
227
+ if (!isAborted) {
228
+ const results = promises
229
+ .map((o) => {
230
+ if (o.status === 'rejected') {
231
+ getLogger('Search').warning(o.reason);
232
+ return [];
233
+ }
234
+ return o.value;
235
+ })
236
+ .flat();
237
+
238
+ this._currentResults.value = results
239
+ .filter(r => r.feature || r.clicked)
240
+ .map((item) => {
241
+ if (item.feature) {
242
+ this._resultLayer.addFeatures([item.feature]);
243
+ if (!item.clicked) {
244
+ const viewpoint = getViewpointFromFeature(item.feature);
245
+ item.clicked = () => {
246
+ this._app.maps.activeMap.gotoViewpoint(viewpoint);
247
+ return this._app.featureInfo.selectFeature(item.feature);
248
+ };
249
+ }
250
+ }
251
+ return item;
252
+ });
253
+ if (this._currentResults.value.length > 0) {
254
+ await this._resultLayer.activate();
255
+ const extent = this._resultLayer.getZoomToExtent();
256
+ const viewpoint = Viewpoint.createViewpointFromExtent(extent);
257
+ await this._app.maps.activeMap.gotoViewpoint(viewpoint);
258
+ this.resultsChanged.raiseEvent(this._currentResults.value.slice(0));
259
+ }
260
+ }
261
+ return this._currentResults.value;
262
+ }
263
+
264
+ /**
265
+ * Get the suggestions for a given query string
266
+ * @param {string} q
267
+ * @returns {Promise<Array<string>>}
268
+ */
269
+ async suggest(q) {
270
+ const promises = await Promise.allSettled([...this._array].map((impl) => {
271
+ if (impl.suggest) {
272
+ return impl.suggest(q);
273
+ }
274
+ return Promise.resolve([]);
275
+ }));
276
+ const isAborted = promises.some(r => r.status === 'rejected' && r.reason?.name === 'AbortError');
277
+ if (isAborted) {
278
+ return [];
279
+ }
280
+ const suggestions = promises
281
+ .map((o) => {
282
+ if (o.status === 'rejected') {
283
+ getLogger('Search').warning(o.reason);
284
+ return [];
285
+ }
286
+ return o.value;
287
+ })
288
+ .flat();
289
+ return suggestions;
290
+ }
291
+
292
+ /**
293
+ * Aborting any ongoing request
294
+ */
295
+ abort() {
296
+ [...this._array].forEach(impl => impl.abort?.());
297
+ }
298
+
299
+ /**
300
+ * Clears the results and aborts running request
301
+ */
302
+ clearResults() {
303
+ this.abort();
304
+ if (this._currentResults.value.length > 0) {
305
+ this._currentResults.value = [];
306
+ this.resultsChanged.raiseEvent(this._currentResults.value.slice(0));
307
+ }
308
+ this._resultLayer.removeAllFeatures();
309
+ this._resultLayer.deactivate();
310
+ if (this._app.featureInfo.selectedFeature?.[vcsLayerName] === this._resultLayer.name) {
311
+ this._app.featureInfo.clear();
312
+ }
313
+ }
314
+
315
+ /**
316
+ * @inheritDoc
317
+ */
318
+ destroy() {
319
+ [...this._array].forEach(impl => impl.destroy());
320
+ this.resultsChanged.destroy();
321
+ this._destroyResultLayer();
322
+ super.destroy();
323
+ }
324
+ }
325
+
326
+ export default Search;
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <v-card>
3
+ <span class="d-flex justify-space-between align-center ma-1">
4
+ <v-icon
5
+ class="mx-2"
6
+ >
7
+ $vcsSearch
8
+ </v-icon>
9
+ <VcsTextField
10
+ class="font-size-14 d-inline-block user-select-none w-full mx-2"
11
+ autofocus
12
+ :loading="searching"
13
+ clearable
14
+ dense
15
+ :placeholder="$t('search.placeholder')"
16
+ v-model.trim="query"
17
+ @keydown.enter="search"
18
+ @keydown.esc="clear"
19
+ @input="reset"
20
+ />
21
+ </span>
22
+ <v-divider />
23
+ <ResultsComponent :query="query" :results="results" />
24
+ </v-card>
25
+ </template>
26
+
27
+ <style>
28
+
29
+ </style>
30
+
31
+ <script>
32
+ import { inject, onUnmounted, ref } from 'vue';
33
+ import { getLogger } from '@vcsuite/logger';
34
+ import { VCard, VDivider, VIcon } from 'vuetify/lib';
35
+ import VcsTextField from '../components/form-inputs-controls/VcsTextField.vue';
36
+ import ResultsComponent from './resultsComponent.vue';
37
+
38
+ export default {
39
+ components: {
40
+ ResultsComponent,
41
+ VcsTextField,
42
+ VCard,
43
+ VIcon,
44
+ VDivider,
45
+ },
46
+ setup() {
47
+ /** @type {VcsUiApp} */
48
+ const app = inject('vcsApp');
49
+ const searching = ref(false);
50
+ const query = ref(null);
51
+ const suggestions = ref([]);
52
+ const results = app.search.currentResults;
53
+
54
+ const reset = () => {
55
+ app.search.clearResults();
56
+ suggestions.value = [];
57
+ };
58
+
59
+ const clear = () => {
60
+ reset();
61
+ searching.value = false;
62
+ query.value = null;
63
+ };
64
+
65
+ const search = async () => {
66
+ reset();
67
+ searching.value = true;
68
+ try {
69
+ await app.search.search(query.value);
70
+ } catch (e) {
71
+ getLogger('Search').error(e);
72
+ }
73
+ searching.value = false;
74
+ };
75
+
76
+ onUnmounted(() => {
77
+ clear();
78
+ });
79
+
80
+ return {
81
+ query,
82
+ searching,
83
+ results,
84
+ reset,
85
+ clear,
86
+ search,
87
+ };
88
+ },
89
+ };
90
+ </script>
@@ -1,3 +1,5 @@
1
+ @use 'variables' as *;
2
+
1
3
  .v-application {
2
4
  h1,
3
5
  h2,
@@ -12,6 +14,7 @@
12
14
  .caption,
13
15
  .tooltip,
14
16
  p {
17
+ font-size: $font-size;
15
18
  &:not(.v-icon) {
16
19
  font-family: 'Titillium Web', sans-serif !important;
17
20
  }
@@ -6,6 +6,10 @@
6
6
  cursor: pointer;
7
7
  }
8
8
 
9
+ .cursor-auto {
10
+ cursor: auto;
11
+ }
12
+
9
13
  .cursor-not-allowed {
10
14
  cursor: not-allowed !important;
11
15
  }
@@ -47,7 +47,6 @@ $icon-size: 20px; // 24px !default;
47
47
  $icon-size-dense: 16px; // 20px !default;
48
48
 
49
49
  /** VcsSelect **/
50
-
51
50
  $select-dense-selections-margin: 4px 4px 3px 0; // 5px 4px 3px 0 !default;
52
51
 
53
52
  // list box
@@ -61,18 +60,38 @@ $list-item-min-height: 32px; // 48px !default;
61
60
  $list-dense-min-height: 24px; // 40px !default;
62
61
  $list-item-content-padding: 4px 0; // 12px 0 !default;
63
62
  $list-dense-content-padding: 0 0; // 8px 0 !default;
64
- $list-item-action-icon-margin: 0 0; //32px !default;
63
+ $list-item-action-icon-margin: 0; //32px !default;
65
64
  $list-item-action-margin: 0 0; // 12px 0 !default;
66
65
  $list-item-icon-margin: auto 0; // 16px 0 !default;
67
-
66
+ $list-item-content-children-margin-bottom: 0; // 2px !default;
68
67
 
69
68
  /** Treeview **/
70
69
  $treeview-node-level-width: 16px;
71
70
  $treeview-node-margin: 4px;
72
71
  $treeview-node-height: 32px; // 48px !default;
73
72
 
74
- /** Expansion Panel */
73
+ /** Expansion Panel **/
75
74
  $expansion-panel-header-min-height: 32px; // 48px !default;
76
75
  $expansion-panel-active-header-min-height: 32px; // 64px !default;
77
76
  $expansion-panel-header-padding: 6px 0; // 16px 24px !default;
78
77
  $expansion-panel-content-padding: 0 0 4px; // 0 24px 16px !default;
78
+
79
+ /** Footer **/
80
+ $footer-padding: 0 8px; // 6px 16px !default;
81
+
82
+ /** Wizard **/
83
+ $stepper-vertical-padding-bottom: 16px; // 36px !default;
84
+ $stepper-vertical-step-padding: 12px 8px;
85
+ $stepper-vertical-content-ltr-margin: -14px 0 -14px 13px; // -8px -36px -16px 36px !default;
86
+ $stepper-vertical-content-ltr-padding: 16px 8px 16px 16px; // 16px 23px 16px 60px !default;
87
+ $stepper-vertical-step-step-margin: 8px;
88
+ $stepper-step-step-min-width: 12px;
89
+ $stepper-step-step-width: 12px;
90
+ $stepper-step-step-height: 12px;
91
+
92
+ /** Snackbar **/
93
+ $snackbar-bottom: 36px;
94
+ $snackbar-background-color: rgba(0, 0, 0, 0.8);
95
+ $snackbar-content-padding: 8px;
96
+ $snackbar-action-margin: 8px;
97
+ $snackbar-wrapper-min-height: 36px;
package/src/vcsUiApp.js CHANGED
@@ -30,6 +30,8 @@ import FeatureInfo from './featureInfo/featureInfo.js';
30
30
  import UiConfig from './uiConfig.js';
31
31
  import { createEmptyState, getStateFromURL } from './state.js';
32
32
  import { version } from '../package.json';
33
+ import Search from './search/search.js';
34
+ import Notifier from './notifier/notifier.js';
33
35
 
34
36
  /**
35
37
  * @typedef {import("@vcmap/core").VcsAppConfig} VcsUiAppConfig
@@ -125,6 +127,7 @@ class VcsUiApp extends VcsApp {
125
127
  this._toolboxManager.removeOwner(plugin.name);
126
128
  this._categoryManager.removeOwner(plugin.name);
127
129
  this._contextMenuManager.removeOwner(plugin.name);
130
+ this._search.removeOwner(plugin.name);
128
131
  if (plugin.i18n) {
129
132
  this.i18n.addPluginMessages(plugin.name, plugin[contextIdSymbol], plugin.i18n);
130
133
  }
@@ -133,7 +136,11 @@ class VcsUiApp extends VcsApp {
133
136
  if (this._cachedAppState.contextIds.includes(plugin[contextIdSymbol])) {
134
137
  state = this._cachedAppState.plugins.find(s => s.name === plugin.name);
135
138
  }
136
- plugin.initialize(this, state?.state);
139
+ try {
140
+ plugin.initialize(this, state?.state);
141
+ } catch (e) {
142
+ getLogger().error(`Error in plugin ${plugin.name} initialize hook`, e);
143
+ }
137
144
  }
138
145
  }),
139
146
  this._plugins.removed.addEventListener(async (plugin) => {
@@ -142,6 +149,7 @@ class VcsUiApp extends VcsApp {
142
149
  this._toolboxManager.removeOwner(plugin.name);
143
150
  this._categoryManager.removeOwner(plugin.name);
144
151
  this._contextMenuManager.removeOwner(plugin.name);
152
+ this._search.removeOwner(plugin.name);
145
153
  this.i18n.removePluginMessages(plugin.name, plugin[contextIdSymbol]);
146
154
  }),
147
155
  ];
@@ -210,6 +218,18 @@ class VcsUiApp extends VcsApp {
210
218
  */
211
219
  this._contextMenuManager = new ContextMenuManager(this);
212
220
 
221
+ /**
222
+ * @type {Search}
223
+ * @private
224
+ */
225
+ this._search = new Search(this);
226
+
227
+ /**
228
+ * @type {Notifier}
229
+ * @private
230
+ */
231
+ this._notifier = new Notifier();
232
+
213
233
  /**
214
234
  * @type {AppState}
215
235
  * @private
@@ -283,12 +303,24 @@ class VcsUiApp extends VcsApp {
283
303
  */
284
304
  get contextMenuManager() { return this._contextMenuManager; }
285
305
 
306
+ /**
307
+ * @type {Search}
308
+ * @readonly
309
+ */
310
+ get search() { return this._search; }
311
+
286
312
  /**
287
313
  * @type {UiConfig}
288
314
  * @readonly
289
315
  */
290
316
  get uiConfig() { return this._uiConfig; }
291
317
 
318
+ /**
319
+ * @type {Notifier}
320
+ * @readonly
321
+ */
322
+ get notifier() { return this._notifier; }
323
+
292
324
  /**
293
325
  * Get the state of the application. When passed the forUrl flag, only a minimal set of states shall be provided for a sharable link to the current state (to ensure
294
326
  * the maximum URL length is not exceeded). This includes: layer active state & styling, active map, active viewpoint,
@@ -320,6 +352,7 @@ class VcsUiApp extends VcsApp {
320
352
  active: l.active || l.loading,
321
353
  };
322
354
  if (
355
+ l.style &&
323
356
  l.style.name !== l.defaultStyle.name &&
324
357
  this.styles.has(l.style) &&
325
358
  l.style[contextIdSymbol] !== defaultDynamicContextId &&
@@ -447,6 +480,7 @@ class VcsUiApp extends VcsApp {
447
480
  destroyCollection(this._plugins);
448
481
  destroyCollection(this._contentTree);
449
482
  destroyCollection(this._i18n);
483
+ destroyCollection(this._search);
450
484
  this._contentTreeClassRegistry.destroy();
451
485
  this._featureInfo.destroy();
452
486
  this._uiConfig.destroy();
@@ -46,6 +46,8 @@ export function createVuetify() {
46
46
  accent: '#757575',
47
47
  warning: '#FFCE00',
48
48
  error: '#FF5252',
49
+ info: '#2196F3',
50
+ success: '#4CAF50',
49
51
  },
50
52
  },
51
53
  },