@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.
- 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 +26 -24
- package/config/dev.config.json +8 -0
- package/config/www.config.json +102 -17
- package/dist/assets/cesium.2e288a.js +137226 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.8014d3.js +14473 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/index.3f74fa92.js +1 -0
- package/dist/assets/ol.31c3a5.js +44279 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui.36f84f.css +1 -0
- package/dist/assets/ui.36f84f.js +16101 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.a39c10.js +4675 -0
- package/dist/assets/vue.js +5 -2
- package/dist/assets/{vuetify.202322.css → vuetify.378637.css} +1 -1
- package/dist/assets/vuetify.378637.js +21019 -0
- package/dist/assets/vuetify.js +5 -2
- package/dist/index.html +1 -1
- package/index.js +4 -1
- package/package.json +10 -10
- package/plugins/@vcmap/pluginExample/index.js +14 -0
- package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +93 -67
- 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 +24 -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/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/vcsContent.vue +3 -0
- package/src/actions/actionHelper.js +103 -2
- package/src/actions/styleSelector.vue +9 -0
- package/src/application/VcsApp.vue +77 -9
- 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 +48 -2
- package/src/components/form-inputs-controls/VcsRadio.vue +7 -1
- package/src/components/form-inputs-controls/VcsSelect.vue +5 -1
- package/src/components/form-inputs-controls/VcsTextArea.vue +2 -0
- package/src/components/form-inputs-controls/VcsTextField.vue +6 -2
- package/src/components/lists/VcsActionList.vue +12 -1
- package/src/components/lists/VcsTreeview.vue +6 -1
- package/src/components/lists/VcsTreeviewLeaf.vue +5 -1
- package/src/components/lists/VcsTreeviewSearchbar.vue +5 -0
- package/src/components/notification/VcsTooltip.vue +14 -9
- package/src/components/tables/VcsTable.vue +117 -38
- package/src/featureInfo/AddressBalloonComponent.vue +17 -1
- package/src/featureInfo/BalloonComponent.vue +57 -23
- package/src/featureInfo/balloonFeatureInfoView.js +2 -2
- package/src/featureInfo/featureInfo.js +10 -2
- package/src/i18n/de.js +15 -0
- package/src/i18n/en.js +15 -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 +6 -0
- package/src/manager/window/WindowComponentHeader.vue +6 -1
- package/src/navigation/mapNavigation.vue +15 -36
- package/src/navigation/orientationToolsButton.vue +6 -1
- package/src/navigation/overviewMap.js +10 -39
- package/src/navigation/tiltSlider.vue +3 -0
- package/src/navigation/vcsCompass.vue +2 -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 +4 -1
- package/src/vcsUiApp.js +16 -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.css +0 -1
- package/dist/assets/ui.15ef6a.js +0 -71
- package/dist/assets/vue.cbe9d8.js +0 -9
- package/dist/assets/vuetify.202322.js +0 -148
@@ -92,6 +92,16 @@
|
|
92
92
|
<script>
|
93
93
|
|
94
94
|
import { getStringColor } from '@vcmap/core';
|
95
|
+
import {
|
96
|
+
VFlex,
|
97
|
+
VImg,
|
98
|
+
VLayout,
|
99
|
+
VList,
|
100
|
+
VListItem,
|
101
|
+
VListItemContent,
|
102
|
+
VListItemIcon,
|
103
|
+
VListItemTitle,
|
104
|
+
} from 'vuetify/lib';
|
95
105
|
import { StyleRowType, getImageSrcFromShape } from './legendHelper.js';
|
96
106
|
|
97
107
|
/**
|
@@ -100,7 +110,16 @@
|
|
100
110
|
*/
|
101
111
|
export default {
|
102
112
|
name: 'StyleLegendItem',
|
103
|
-
components: {
|
113
|
+
components: {
|
114
|
+
VList,
|
115
|
+
VLayout,
|
116
|
+
VFlex,
|
117
|
+
VListItem,
|
118
|
+
VListItemIcon,
|
119
|
+
VImg,
|
120
|
+
VListItemContent,
|
121
|
+
VListItemTitle,
|
122
|
+
},
|
104
123
|
props: {
|
105
124
|
item: {
|
106
125
|
type: Object,
|
package/src/legend/vcsLegend.vue
CHANGED
@@ -50,17 +50,38 @@
|
|
50
50
|
|
51
51
|
<script>
|
52
52
|
|
53
|
+
import {
|
54
|
+
VCard,
|
55
|
+
VExpansionPanels,
|
56
|
+
VExpansionPanel,
|
57
|
+
VExpansionPanelHeader,
|
58
|
+
VExpansionPanelContent,
|
59
|
+
VIcon,
|
60
|
+
VList,
|
61
|
+
VImg,
|
62
|
+
} from 'vuetify/lib';
|
53
63
|
import { LegendType } from './legendHelper.js';
|
54
64
|
import StyleLegendItem from './styleLegendItem.vue';
|
55
65
|
import VcsTreeviewLeaf from '../components/lists/VcsTreeviewLeaf.vue';
|
56
66
|
|
57
67
|
/**
|
58
68
|
* @description A component rendering configured legend information for active layers.
|
59
|
-
* @vue-prop {import("vue").
|
69
|
+
* @vue-prop {import("vue").Ref<Array<LegendEntry>>} entries - legend entries to be displayed
|
60
70
|
*/
|
61
71
|
export default {
|
62
72
|
name: 'VcsLegend',
|
63
|
-
components: {
|
73
|
+
components: {
|
74
|
+
VcsTreeviewLeaf,
|
75
|
+
StyleLegendItem,
|
76
|
+
VCard,
|
77
|
+
VExpansionPanels,
|
78
|
+
VExpansionPanel,
|
79
|
+
VExpansionPanelHeader,
|
80
|
+
VExpansionPanelContent,
|
81
|
+
VIcon,
|
82
|
+
VList,
|
83
|
+
VImg,
|
84
|
+
},
|
64
85
|
props: {
|
65
86
|
entries: {
|
66
87
|
type: Object,
|
@@ -85,8 +106,13 @@
|
|
85
106
|
};
|
86
107
|
</script>
|
87
108
|
|
88
|
-
<style scoped>
|
109
|
+
<style lang="scss" scoped>
|
89
110
|
.v-list-item--dense {
|
90
111
|
height: 32px;
|
91
112
|
}
|
113
|
+
::v-deep {
|
114
|
+
.treeview-label {
|
115
|
+
max-width: 189px;
|
116
|
+
}
|
117
|
+
}
|
92
118
|
</style>
|
@@ -62,6 +62,12 @@
|
|
62
62
|
</style>
|
63
63
|
<script>
|
64
64
|
import { computed, ref } from 'vue';
|
65
|
+
import {
|
66
|
+
VMenu,
|
67
|
+
VIcon,
|
68
|
+
VToolbar,
|
69
|
+
VToolbarItems,
|
70
|
+
} from 'vuetify/lib';
|
65
71
|
import VcsButton from '../../components/buttons/VcsButton.vue';
|
66
72
|
import { getComponentsByOrder } from './toolboxManager.js';
|
67
73
|
|
@@ -78,7 +84,13 @@
|
|
78
84
|
*/
|
79
85
|
export default {
|
80
86
|
name: 'ToolboxActionGroup',
|
81
|
-
components: {
|
87
|
+
components: {
|
88
|
+
VcsButton,
|
89
|
+
VMenu,
|
90
|
+
VIcon,
|
91
|
+
VToolbar,
|
92
|
+
VToolbarItems,
|
93
|
+
},
|
82
94
|
props: {
|
83
95
|
group: {
|
84
96
|
type: Object,
|
@@ -88,6 +88,12 @@
|
|
88
88
|
</style>
|
89
89
|
<script>
|
90
90
|
import { ref, computed } from 'vue';
|
91
|
+
import {
|
92
|
+
VMenu,
|
93
|
+
VIcon,
|
94
|
+
VToolbar,
|
95
|
+
VToolbarItems,
|
96
|
+
} from 'vuetify/lib';
|
91
97
|
import VcsButton from '../../components/buttons/VcsButton.vue';
|
92
98
|
|
93
99
|
/**
|
@@ -98,7 +104,13 @@
|
|
98
104
|
*/
|
99
105
|
export default {
|
100
106
|
name: 'ToolboxActionSelect',
|
101
|
-
components: {
|
107
|
+
components: {
|
108
|
+
VcsButton,
|
109
|
+
VMenu,
|
110
|
+
VIcon,
|
111
|
+
VToolbar,
|
112
|
+
VToolbarItems,
|
113
|
+
},
|
102
114
|
props: {
|
103
115
|
group: {
|
104
116
|
type: Object,
|
@@ -72,6 +72,7 @@
|
|
72
72
|
<script>
|
73
73
|
import { inject, ref, computed, watch, onUnmounted } from 'vue';
|
74
74
|
import { ButtonLocation, vcsAppSymbol } from '@vcmap/ui';
|
75
|
+
import { VToolbar, VToolbarItems } from 'vuetify/lib';
|
75
76
|
import { getComponentsByOrder, ToolboxType } from './toolboxManager.js';
|
76
77
|
import ToolboxActionSelect from './SelectToolboxComponent.vue';
|
77
78
|
import ToolboxActionGroup from './GroupToolboxComponent.vue';
|
@@ -104,6 +105,8 @@
|
|
104
105
|
ToolboxActionSelect,
|
105
106
|
ToolboxActionGroup,
|
106
107
|
VcsButton,
|
108
|
+
VToolbar,
|
109
|
+
VToolbarItems,
|
107
110
|
},
|
108
111
|
setup() {
|
109
112
|
const app = inject('vcsApp');
|
@@ -44,9 +44,15 @@
|
|
44
44
|
} from 'vue';
|
45
45
|
import { fromEvent } from 'rxjs';
|
46
46
|
import { switchMap, take, map, tap } from 'rxjs/operators';
|
47
|
+
import { VDivider, VSheet } from 'vuetify/lib';
|
47
48
|
import { WindowSlot } from './windowManager.js';
|
48
49
|
|
49
50
|
export default {
|
51
|
+
name: 'WindowComponent',
|
52
|
+
components: {
|
53
|
+
VSheet,
|
54
|
+
VDivider,
|
55
|
+
},
|
50
56
|
props: {
|
51
57
|
windowState: {
|
52
58
|
type: Object,
|
@@ -25,10 +25,15 @@
|
|
25
25
|
</style>
|
26
26
|
|
27
27
|
<script>
|
28
|
+
import { VIcon } from 'vuetify/lib';
|
28
29
|
import VcsButton from '../../components/buttons/VcsButton.vue';
|
29
30
|
|
30
31
|
export default {
|
31
|
-
|
32
|
+
name: 'WindowComponentHeader',
|
33
|
+
components: {
|
34
|
+
VcsButton,
|
35
|
+
VIcon,
|
36
|
+
},
|
32
37
|
props: {
|
33
38
|
windowState: {
|
34
39
|
type: Object,
|
@@ -37,8 +37,8 @@
|
|
37
37
|
|
38
38
|
<script>
|
39
39
|
import { computed, inject, ref, reactive, onUnmounted } from 'vue';
|
40
|
-
import { ObliqueMap, CesiumMap
|
41
|
-
import {
|
40
|
+
import { ObliqueMap, CesiumMap } from '@vcmap/core';
|
41
|
+
import { VContainer, VRow } from 'vuetify/lib';
|
42
42
|
import { createOverviewMapAction } from '../actions/actionHelper.js';
|
43
43
|
import { getWindowComponentOptions } from './overviewMap.js';
|
44
44
|
import VcsCompass from './vcsCompass.vue';
|
@@ -65,32 +65,6 @@
|
|
65
65
|
return OrientationToolsViewMode.TWO_D;
|
66
66
|
}
|
67
67
|
|
68
|
-
/**
|
69
|
-
* @param {VcsMap} map
|
70
|
-
* @param {Ref<number>} headingRef
|
71
|
-
* @param {Ref<number>} tiltRef
|
72
|
-
* @returns {function():void}
|
73
|
-
*/
|
74
|
-
function mapPostRender(map, headingRef, tiltRef) {
|
75
|
-
const handler = () => {
|
76
|
-
const vp = map.getViewpointSync();
|
77
|
-
if (vp) {
|
78
|
-
headingRef.value = vp.heading;
|
79
|
-
tiltRef.value = vp.pitch;
|
80
|
-
}
|
81
|
-
};
|
82
|
-
|
83
|
-
if (map instanceof CesiumMap) {
|
84
|
-
return map.getScene().postRender.addEventListener(handler);
|
85
|
-
} else if (map instanceof ObliqueMap || map instanceof OpenlayersMap) {
|
86
|
-
const key = map.olMap.on('postrender', handler);
|
87
|
-
return () => {
|
88
|
-
unByKey(key);
|
89
|
-
};
|
90
|
-
}
|
91
|
-
return () => {};
|
92
|
-
}
|
93
|
-
|
94
68
|
/**
|
95
69
|
* @param {VcsMap} map
|
96
70
|
* @param {boolean} [out=false]
|
@@ -117,6 +91,8 @@
|
|
117
91
|
TiltSlider,
|
118
92
|
VcsZoomButton,
|
119
93
|
VcsCompass,
|
94
|
+
VContainer,
|
95
|
+
VRow,
|
120
96
|
},
|
121
97
|
setup() {
|
122
98
|
/** @type {VcsUiApp} */
|
@@ -125,17 +101,19 @@
|
|
125
101
|
const headingRef = ref(0);
|
126
102
|
const tiltRef = ref(0);
|
127
103
|
|
128
|
-
|
129
|
-
|
130
|
-
const setActiveMap = (map) => {
|
104
|
+
const handleRenderEvent = ({ map }) => {
|
131
105
|
viewMode.value = getViewModeForMap(map);
|
132
|
-
|
133
|
-
|
106
|
+
const vp = map.getViewpointSync();
|
107
|
+
if (vp) {
|
108
|
+
headingRef.value = vp.heading;
|
109
|
+
tiltRef.value = vp.pitch;
|
110
|
+
}
|
134
111
|
};
|
135
112
|
|
136
|
-
app.maps.
|
137
|
-
|
138
|
-
|
113
|
+
const postRenderHandler = app.maps.postRender.addEventListener(handleRenderEvent);
|
114
|
+
if (app.maps.activeMap) {
|
115
|
+
handleRenderEvent({ map: app.maps.activeMap });
|
116
|
+
}
|
139
117
|
const heading = computed({
|
140
118
|
get() { return headingRef.value; },
|
141
119
|
async set(headingValue) {
|
@@ -169,6 +147,7 @@
|
|
169
147
|
if (destroy) {
|
170
148
|
destroy();
|
171
149
|
}
|
150
|
+
postRenderHandler();
|
172
151
|
});
|
173
152
|
|
174
153
|
return {
|
@@ -23,6 +23,7 @@
|
|
23
23
|
}
|
24
24
|
</style>
|
25
25
|
<script>
|
26
|
+
import { VCard, VIcon } from 'vuetify/lib';
|
26
27
|
import VcsTooltip from '../components/notification/VcsTooltip.vue';
|
27
28
|
|
28
29
|
/**
|
@@ -33,7 +34,11 @@
|
|
33
34
|
*/
|
34
35
|
export default {
|
35
36
|
name: 'OrientationToolsButton',
|
36
|
-
components: {
|
37
|
+
components: {
|
38
|
+
VcsTooltip,
|
39
|
+
VCard,
|
40
|
+
VIcon,
|
41
|
+
},
|
37
42
|
props: {
|
38
43
|
icon: {
|
39
44
|
type: String,
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import {
|
2
2
|
OpenlayersMap,
|
3
|
-
CesiumMap,
|
4
3
|
ObliqueMap,
|
5
4
|
VectorLayer,
|
6
5
|
VectorStyleItem,
|
@@ -308,12 +307,10 @@ class OverviewMap {
|
|
308
307
|
}
|
309
308
|
this._active = true;
|
310
309
|
const { activeMap } = this._app.maps;
|
311
|
-
if (activeMap instanceof
|
312
|
-
await this._initializeForCesium(activeMap);
|
313
|
-
} else if (activeMap instanceof OpenlayersMap) {
|
314
|
-
await this._initializeForOpenlayers(activeMap);
|
315
|
-
} else if (activeMap instanceof ObliqueMap) {
|
310
|
+
if (activeMap instanceof ObliqueMap) {
|
316
311
|
await this._initializeForOblique(activeMap);
|
312
|
+
} else {
|
313
|
+
await this._initializePostRenderHandler(activeMap);
|
317
314
|
}
|
318
315
|
}
|
319
316
|
|
@@ -343,45 +340,19 @@ class OverviewMap {
|
|
343
340
|
}
|
344
341
|
|
345
342
|
/**
|
346
|
-
* @param {import("@vcmap/core").
|
343
|
+
* @param {import("@vcmap/core").VcsMap} map
|
347
344
|
* @returns {Promise<void>}
|
348
345
|
* @private
|
349
346
|
*/
|
350
|
-
async
|
347
|
+
async _initializePostRenderHandler(map) {
|
351
348
|
if (!this._cameraIconLayer) {
|
352
349
|
this._setupCameraIconLayer();
|
353
350
|
}
|
354
|
-
|
355
|
-
|
356
|
-
const cesiumScene = cesiumViewer.scene;
|
357
|
-
const navRemover = this._addNavigationListener(cesiumMap);
|
358
|
-
const prRemover = cesiumScene.postRender.addEventListener(this._addCameraFeature, this);
|
359
|
-
const cleanupTasks = () => {
|
360
|
-
prRemover();
|
361
|
-
navRemover();
|
362
|
-
this._cameraIconLayer.deactivate();
|
363
|
-
};
|
364
|
-
this._listeners.push(cleanupTasks);
|
365
|
-
await this._cameraIconLayer.activate();
|
366
|
-
}
|
367
|
-
}
|
368
|
-
|
369
|
-
/**
|
370
|
-
* @param {import("@vcmap/core").OpenlayersMap} map
|
371
|
-
* @returns {Promise<void>}
|
372
|
-
* @private
|
373
|
-
*/
|
374
|
-
async _initializeForOpenlayers(map) {
|
375
|
-
if (!this._cameraIconLayer) {
|
376
|
-
this._setupCameraIconLayer();
|
377
|
-
}
|
378
|
-
const { olMap } = map;
|
379
|
-
const navListener = this._addNavigationListener(map);
|
380
|
-
const prUnKey = olMap.on('postrender', this._addCameraFeature.bind(this));
|
381
|
-
|
351
|
+
const navRemover = this._addNavigationListener(map);
|
352
|
+
const prRemover = map.postRender.addEventListener(this._addCameraFeature.bind(this));
|
382
353
|
const cleanupTasks = () => {
|
383
|
-
|
384
|
-
|
354
|
+
prRemover();
|
355
|
+
navRemover();
|
385
356
|
this._cameraIconLayer.deactivate();
|
386
357
|
};
|
387
358
|
this._listeners.push(cleanupTasks);
|
@@ -543,7 +514,7 @@ class OverviewMap {
|
|
543
514
|
* @private
|
544
515
|
*/
|
545
516
|
_addCameraFeature() {
|
546
|
-
const viewpoint = this._app.maps.activeMap
|
517
|
+
const viewpoint = this._app.maps.activeMap?.getViewpointSync();
|
547
518
|
if (!viewpoint || !viewpoint.isValid() || viewpoint.equals(this._cachedViewpoint)) {
|
548
519
|
return;
|
549
520
|
}
|
@@ -47,6 +47,7 @@
|
|
47
47
|
</style>
|
48
48
|
<script>
|
49
49
|
import { clamp } from 'ol/math.js';
|
50
|
+
import { VCard, VSlider } from 'vuetify/lib';
|
50
51
|
import VcsTooltip from '../components/notification/VcsTooltip.vue';
|
51
52
|
|
52
53
|
/**
|
@@ -58,6 +59,8 @@
|
|
58
59
|
name: 'TiltSlider',
|
59
60
|
components: {
|
60
61
|
VcsTooltip,
|
62
|
+
VCard,
|
63
|
+
VSlider,
|
61
64
|
},
|
62
65
|
props: {
|
63
66
|
value: {
|
@@ -33,6 +33,7 @@
|
|
33
33
|
import { fromEvent, merge, of, Subject } from 'rxjs';
|
34
34
|
import { takeUntil, tap } from 'rxjs/operators';
|
35
35
|
|
36
|
+
import { VSheet } from 'vuetify/lib';
|
36
37
|
import MapNavCompass from './mapNavCompass.vue';
|
37
38
|
|
38
39
|
/**
|
@@ -45,6 +46,7 @@
|
|
45
46
|
name: 'VcsCompass',
|
46
47
|
components: {
|
47
48
|
MapNavCompass,
|
49
|
+
VSheet,
|
48
50
|
},
|
49
51
|
props: {
|
50
52
|
viewMode: {
|
@@ -0,0 +1,89 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
class="ma-1 d-flex flex-row align-center"
|
4
|
+
v-if="item"
|
5
|
+
>
|
6
|
+
<v-list-item-icon v-if="item.icon" class="px-1">
|
7
|
+
<v-icon>
|
8
|
+
{{ item.icon }}
|
9
|
+
</v-icon>
|
10
|
+
</v-list-item-icon>
|
11
|
+
<div
|
12
|
+
class="px-2 d-flex align-center"
|
13
|
+
:title="$t('search.select')"
|
14
|
+
>
|
15
|
+
<span v-html="marked" />
|
16
|
+
</div>
|
17
|
+
<VcsActionButtonList
|
18
|
+
v-if="hasActions"
|
19
|
+
:actions="item.actions"
|
20
|
+
:block-overflow="true"
|
21
|
+
:overflow-count="2"
|
22
|
+
small
|
23
|
+
right
|
24
|
+
/>
|
25
|
+
</div>
|
26
|
+
</template>
|
27
|
+
|
28
|
+
<script>
|
29
|
+
import { computed } from 'vue';
|
30
|
+
import { VIcon, VListItemIcon } from 'vuetify/lib';
|
31
|
+
import VcsActionButtonList from '../components/buttons/VcsActionButtonList.vue';
|
32
|
+
|
33
|
+
/**
|
34
|
+
* @param {string} text
|
35
|
+
* @param {string} query
|
36
|
+
* @returns {string}
|
37
|
+
*/
|
38
|
+
function markText(text, query) {
|
39
|
+
let replacement = text;
|
40
|
+
if (query) {
|
41
|
+
const partials = query.split(/[.,\s]/)
|
42
|
+
.filter(partial => partial.trim());
|
43
|
+
partials.forEach((partial) => {
|
44
|
+
replacement = replacement
|
45
|
+
.replaceAll(new RegExp(`(^|[^>])(${partial})`, 'ig'), '<span class="primary--text">$2</span>');
|
46
|
+
});
|
47
|
+
}
|
48
|
+
return replacement;
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* ResultItem with optional icon or image, title and optional actions
|
53
|
+
* @vue-prop {string} query - The query string to mark results
|
54
|
+
* @vue-prop {ResultItem} resultItem
|
55
|
+
* @vue-computed {boolean} hasActions - Whether result item has actions or not
|
56
|
+
* @vue-computed {string} marked - The result item's title with highlighted query string
|
57
|
+
*/
|
58
|
+
export default {
|
59
|
+
name: 'ResultItem',
|
60
|
+
components: {
|
61
|
+
VcsActionButtonList,
|
62
|
+
VListItemIcon,
|
63
|
+
VIcon,
|
64
|
+
},
|
65
|
+
props: {
|
66
|
+
query: {
|
67
|
+
type: String,
|
68
|
+
default: '',
|
69
|
+
},
|
70
|
+
item: {
|
71
|
+
type: Object,
|
72
|
+
required: true,
|
73
|
+
},
|
74
|
+
},
|
75
|
+
setup(props) {
|
76
|
+
const hasActions = computed(() => props.item?.actions?.length > 0);
|
77
|
+
const marked = computed(() => markText(props.item.title, props.query));
|
78
|
+
|
79
|
+
return {
|
80
|
+
hasActions,
|
81
|
+
marked,
|
82
|
+
};
|
83
|
+
},
|
84
|
+
};
|
85
|
+
</script>
|
86
|
+
|
87
|
+
<style lang="scss" scoped>
|
88
|
+
|
89
|
+
</style>
|
@@ -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>
|