@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.
- package/README.md +33 -31
- package/build/build.js +9 -0
- package/build/buildHelpers.js +12 -10
- package/build/commonViteConfig.js +3 -10
- package/config/base.config.json +30 -24
- package/config/dev.config.json +13 -1
- package/config/www.config.json +104 -17
- package/dist/assets/cesium.430460.js +137226 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.5089ba.js +16024 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/index.854f8e2b.js +1 -0
- package/dist/assets/ol.9be53a.js +44279 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/{ui.15ef6a.css → ui.49010a.css} +1 -1
- package/dist/assets/ui.49010a.js +16776 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.247c1c.js +4675 -0
- package/dist/assets/vue.js +5 -2
- package/dist/assets/{vuetify.202322.css → vuetify.735e58.css} +1 -1
- package/dist/assets/vuetify.735e58.js +21019 -0
- package/dist/assets/vuetify.js +5 -2
- package/dist/index.html +1 -1
- package/index.html +77 -0
- package/index.js +8 -1
- package/package.json +12 -10
- package/plugins/@vcmap/create-link/fallbackCreateLink.vue +4 -1
- package/plugins/@vcmap/create-link/index.js +4 -1
- package/plugins/@vcmap/pluginExample/exampleActions.js +45 -0
- package/plugins/@vcmap/pluginExample/index.js +38 -1
- package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +152 -98
- package/plugins/@vcmap/project-selector/ContextsListComponent.vue +8 -1
- package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +27 -1
- package/plugins/@vcmap/search-nominatim/LICENSE.md +14 -0
- package/plugins/@vcmap/search-nominatim/README.md +2 -0
- package/plugins/@vcmap/search-nominatim/config.json +4 -0
- package/plugins/@vcmap/search-nominatim/index.js +26 -0
- package/plugins/@vcmap/search-nominatim/nominatim.js +170 -0
- package/plugins/@vcmap/search-nominatim/package.json +43 -0
- package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +26 -0
- package/plugins/buttonExamples/ButtonExamples.vue +28 -1
- package/plugins/categoryTest/Categories.vue +16 -0
- package/plugins/categoryTest/Category.vue +30 -4
- package/plugins/example/mySuperComponent.vue +12 -1
- package/plugins/notifier/index.js +31 -0
- package/plugins/notifier/notifierTester.vue +88 -0
- package/plugins/package.json +2 -1
- package/plugins/simple-graph/SimpleGraphComponent.vue +5 -11
- package/plugins/test/allIconsComponent.vue +16 -0
- package/plugins/test/editor.vue +3 -0
- package/plugins/test/emptyComponent.vue +3 -0
- package/plugins/test/index.js +22 -0
- package/plugins/test/myCustomHeader.vue +9 -1
- package/plugins/test/testList.vue +287 -0
- package/plugins/test/vcsContent.vue +3 -0
- package/plugins/test/windowManagerExample.vue +3 -0
- package/plugins/wizardExample/index.js +41 -0
- package/plugins/wizardExample/wizardExample.vue +77 -0
- package/src/actions/actionHelper.js +103 -2
- package/src/actions/styleSelector.vue +9 -0
- package/src/application/VcsApp.vue +95 -17
- package/src/application/VcsAttributions.vue +63 -0
- package/src/application/VcsAttributionsFooter.vue +87 -0
- package/src/application/{Navbar.vue → VcsNavbar.vue} +35 -2
- package/src/application/VcsSettings.vue +4 -0
- package/src/application/attributionsHelper.js +150 -0
- package/src/application/vcsAppWrapper.vue +5 -1
- package/src/components/buttons/VcsActionButtonList.vue +8 -1
- package/src/components/buttons/VcsButton.vue +7 -1
- package/src/components/form-inputs-controls/VcsCheckbox.vue +7 -2
- package/src/components/form-inputs-controls/VcsColorPicker.vue +4 -0
- package/src/components/form-inputs-controls/VcsFormSection.vue +55 -9
- package/src/components/form-inputs-controls/VcsRadio.vue +7 -1
- package/src/components/form-inputs-controls/VcsSelect.vue +38 -2
- package/src/components/form-inputs-controls/VcsTextArea.vue +2 -0
- package/src/components/form-inputs-controls/VcsTextField.vue +16 -4
- package/src/components/form-inputs-controls/VcsWizard.vue +133 -0
- package/src/components/imageElementInjector.vue +22 -0
- package/src/components/lists/VcsActionList.vue +12 -1
- package/src/components/lists/VcsList.vue +466 -0
- package/src/components/lists/VcsTreeview.vue +7 -3
- package/src/components/lists/VcsTreeviewLeaf.vue +23 -51
- package/src/components/lists/VcsTreeviewSearchbar.vue +6 -23
- package/src/components/notification/VcsTooltip.vue +14 -9
- package/src/components/tables/VcsTable.vue +129 -38
- package/src/contentTree/LayerTree.vue +1 -1
- package/src/contentTree/contentTreeItem.js +13 -13
- package/src/contentTree/subContentTreeItem.js +1 -1
- package/src/contentTree/vcsObjectContentTreeItem.js +1 -1
- package/src/featureInfo/AddressBalloonComponent.vue +17 -1
- package/src/featureInfo/BalloonComponent.vue +63 -27
- package/src/featureInfo/balloonFeatureInfoView.js +14 -14
- package/src/featureInfo/balloonHelper.js +4 -0
- package/src/featureInfo/featureInfo.js +23 -2
- package/src/featureInfo/featureInfoInteraction.js +1 -1
- package/src/i18n/de.js +22 -0
- package/src/i18n/en.js +22 -0
- package/src/icons/+all.js +4 -0
- package/src/icons/WandIcon.vue +63 -0
- package/src/legend/legendHelper.js +18 -12
- package/src/legend/styleLegendItem.vue +20 -1
- package/src/legend/vcsLegend.vue +29 -3
- package/src/manager/toolbox/GroupToolboxComponent.vue +13 -1
- package/src/manager/toolbox/SelectToolboxComponent.vue +13 -1
- package/src/manager/toolbox/ToolboxManager.vue +3 -0
- package/src/manager/window/WindowComponent.vue +15 -2
- package/src/manager/window/WindowComponentHeader.vue +38 -7
- package/src/manager/window/WindowManager.vue +1 -0
- package/src/manager/window/windowManager.js +11 -1
- package/src/navigation/mapNavigation.vue +15 -36
- package/src/navigation/orientationToolsButton.vue +6 -1
- package/src/navigation/overviewMap.js +19 -47
- package/src/navigation/tiltSlider.vue +3 -0
- package/src/navigation/vcsCompass.vue +2 -0
- package/src/notifier/notifier.js +121 -0
- package/src/notifier/notifierComponent.vue +84 -0
- package/src/search/resultItem.vue +89 -0
- package/src/search/resultsComponent.vue +98 -0
- package/src/search/search.js +326 -0
- package/src/search/searchComponent.vue +90 -0
- package/src/styles/_typography.scss +3 -0
- package/src/styles/utils/_cursor.scss +4 -0
- package/src/styles/variables.scss +23 -4
- package/src/vcsUiApp.js +35 -1
- package/src/vuePlugins/vuetify.js +2 -0
- package/dist/assets/cesium.9489f8.js +0 -8699
- package/dist/assets/core.aa346a.js +0 -4
- package/dist/assets/index.3cd4fffa.js +0 -1
- package/dist/assets/ol.39651b.js +0 -439
- package/dist/assets/ui.15ef6a.js +0 -71
- package/dist/assets/vue.cbe9d8.js +0 -9
- 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>
|
@@ -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
|
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
|
-
|
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();
|