@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
@@ -78,6 +78,7 @@ export const WindowPositions = {
|
|
78
78
|
* @property {WindowState} [state]
|
79
79
|
* @property {WindowSlot} [slot] If WindowSlot is not detached the position will be ignored
|
80
80
|
* @property {Object} [props]
|
81
|
+
* @property {Object} [provides]
|
81
82
|
*/
|
82
83
|
|
83
84
|
/**
|
@@ -87,6 +88,8 @@ export const WindowPositions = {
|
|
87
88
|
* @property {boolean} [hideHeader] be used to not show the header.
|
88
89
|
* @property {string} [headerTitle]
|
89
90
|
* @property {string} [headerIcon]
|
91
|
+
* @property {Array<VcsAction>} [headerActions]
|
92
|
+
* @property {number} [headerActionsOverflowCount]
|
90
93
|
* @property {Object<string, string>} styles[styles] Can be used to add additional styles to the root WindowComponent. Use Vue Style Bindings Object Syntax https://vuejs.org/v2/guide/class-and-style.html
|
91
94
|
* @property {Array<string>|Object<string,string>} [classes] Can be used to add additional classes to the root WindowComponent. Use Vue Class Bindings Syntax https://vuejs.org/v2/guide/class-and-style.html
|
92
95
|
*/
|
@@ -100,6 +103,7 @@ export const WindowPositions = {
|
|
100
103
|
* @property {WindowState} state
|
101
104
|
* @property {Ref<UnwrapRef<WindowSlot>>} slot
|
102
105
|
* @property {Object} props
|
106
|
+
* @property {Object} provides
|
103
107
|
*/
|
104
108
|
|
105
109
|
/**
|
@@ -417,7 +421,7 @@ class WindowManager {
|
|
417
421
|
/**
|
418
422
|
* adds a windowComponent to the WindowManager and renders the Window at the provided position/slot.
|
419
423
|
* The reactive WindowState Object can be used to watch Changes on position/WindowSlot.
|
420
|
-
* The WindowState Object can also be used to change hideHeader, headerTitle, headerIcon, styles and classes
|
424
|
+
* The WindowState Object can also be used to change hideHeader, headerTitle, headerIcon, headerActions, styles and classes
|
421
425
|
* @param {WindowComponentOptions|WindowComponent} windowComponentOptions
|
422
426
|
* @param {string|symbol} owner pluginName or vcsAppSymbol
|
423
427
|
* @throws {Error} if a windowComponent with the same ID has already been added
|
@@ -450,11 +454,14 @@ class WindowManager {
|
|
450
454
|
hideHeader: !!windowComponentOptions?.state?.hideHeader,
|
451
455
|
headerTitle: windowComponentOptions?.state?.headerTitle,
|
452
456
|
headerIcon: windowComponentOptions?.state?.headerIcon,
|
457
|
+
headerActions: windowComponentOptions?.state?.headerActions,
|
458
|
+
headerActionsOverflow: windowComponentOptions?.state?.headerActionsOverflow,
|
453
459
|
classes,
|
454
460
|
styles,
|
455
461
|
});
|
456
462
|
|
457
463
|
const props = windowComponentOptions.props || {};
|
464
|
+
const provides = windowComponentOptions.provides || {};
|
458
465
|
|
459
466
|
const position = reactive(windowPosition);
|
460
467
|
/**
|
@@ -482,6 +489,9 @@ class WindowManager {
|
|
482
489
|
get props() {
|
483
490
|
return props;
|
484
491
|
},
|
492
|
+
get provides() {
|
493
|
+
return provides;
|
494
|
+
},
|
485
495
|
};
|
486
496
|
this._removeWindowAtSlot(slot);
|
487
497
|
this._windowComponents.set(id, windowComponent);
|
@@ -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,
|
@@ -206,7 +205,7 @@ class OverviewMap {
|
|
206
205
|
clone.activate();
|
207
206
|
const idx = this._map.layerCollection.indexOf(clone);
|
208
207
|
if (idx < 0) {
|
209
|
-
this._map.layerCollection.add(clone);
|
208
|
+
this._map.layerCollection.add(clone, 0);
|
210
209
|
} else {
|
211
210
|
this._map.layerCollection.remove(clone);
|
212
211
|
this._map.layerCollection.add(clone, idx);
|
@@ -299,6 +298,7 @@ class OverviewMap {
|
|
299
298
|
this._setupMapInteraction();
|
300
299
|
}
|
301
300
|
await this._map.activate();
|
301
|
+
this.map.setTarget('overview-map-container');
|
302
302
|
if (!this._active) {
|
303
303
|
this._mapActivatedListener = this._app.maps.mapActivated.addEventListener(() => {
|
304
304
|
this._clearListeners();
|
@@ -308,12 +308,10 @@ class OverviewMap {
|
|
308
308
|
}
|
309
309
|
this._active = true;
|
310
310
|
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) {
|
311
|
+
if (activeMap instanceof ObliqueMap) {
|
316
312
|
await this._initializeForOblique(activeMap);
|
313
|
+
} else {
|
314
|
+
await this._initializePostRenderHandler(activeMap);
|
317
315
|
}
|
318
316
|
}
|
319
317
|
|
@@ -326,7 +324,6 @@ class OverviewMap {
|
|
326
324
|
this._app.windowManager.add(getWindowComponentOptions(), vcsAppSymbol);
|
327
325
|
}
|
328
326
|
await this._activate();
|
329
|
-
this.map.setTarget('overview-map-container');
|
330
327
|
}
|
331
328
|
|
332
329
|
/**
|
@@ -343,45 +340,20 @@ 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();
|
350
|
+
this._syncCameraViewAndFeature();
|
353
351
|
}
|
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
|
-
|
352
|
+
const navRemover = this._addNavigationListener(map);
|
353
|
+
const prRemover = map.postRender.addEventListener(this._syncCameraViewAndFeature.bind(this));
|
382
354
|
const cleanupTasks = () => {
|
383
|
-
|
384
|
-
|
355
|
+
prRemover();
|
356
|
+
navRemover();
|
385
357
|
this._cameraIconLayer.deactivate();
|
386
358
|
};
|
387
359
|
this._listeners.push(cleanupTasks);
|
@@ -542,8 +514,8 @@ class OverviewMap {
|
|
542
514
|
* Adds and maintains the view and camera feature
|
543
515
|
* @private
|
544
516
|
*/
|
545
|
-
|
546
|
-
const viewpoint = this._app.maps.activeMap
|
517
|
+
_syncCameraViewAndFeature() {
|
518
|
+
const viewpoint = this._app.maps.activeMap?.getViewpointSync();
|
547
519
|
if (!viewpoint || !viewpoint.isValid() || viewpoint.equals(this._cachedViewpoint)) {
|
548
520
|
return;
|
549
521
|
}
|
@@ -554,8 +526,6 @@ class OverviewMap {
|
|
554
526
|
let { distance } = viewpoint;
|
555
527
|
if (position[2] && !(distance && distance < position[2] * 4)) {
|
556
528
|
distance = position[2] * 4;
|
557
|
-
} else if (position[2] == null) {
|
558
|
-
position[2] = distance;
|
559
529
|
}
|
560
530
|
|
561
531
|
distance = distance > this.minimumHeight ? distance : this.minimumHeight;
|
@@ -580,9 +550,11 @@ class OverviewMap {
|
|
580
550
|
this.cameraIconStyle.image.setRotation(rotationRadians);
|
581
551
|
|
582
552
|
viewpoint.heading = 0;
|
583
|
-
viewpoint.cameraPosition
|
584
|
-
|
585
|
-
|
553
|
+
if (viewpoint.cameraPosition) {
|
554
|
+
viewpoint.cameraPosition = position;
|
555
|
+
viewpoint.groundPosition = null;
|
556
|
+
viewpoint.distance = distance * 4;
|
557
|
+
}
|
586
558
|
this._map.gotoViewpoint(viewpoint);
|
587
559
|
}
|
588
560
|
|
@@ -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,121 @@
|
|
1
|
+
import { ref, shallowRef } from 'vue';
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @typedef {Object} NotificationOptions
|
6
|
+
* @property {string} message
|
7
|
+
* @property {NotificationType} type
|
8
|
+
* @property {string} [title]
|
9
|
+
* @property {number} [timeout=5000]
|
10
|
+
*/
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @typedef {Object} Notification
|
14
|
+
* @property {string} id
|
15
|
+
* @property {string} message
|
16
|
+
* @property {NotificationType} type
|
17
|
+
* @property {string} [title]
|
18
|
+
* @property {number} timeout
|
19
|
+
* @property {import("vue").Ref<boolean>} open
|
20
|
+
* @property {function():void} close
|
21
|
+
*/
|
22
|
+
|
23
|
+
/**
|
24
|
+
* @enum {string}
|
25
|
+
*/
|
26
|
+
export const NotificationType = {
|
27
|
+
ERROR: 'error',
|
28
|
+
WARNING: 'warning',
|
29
|
+
INFO: 'info',
|
30
|
+
SUCCESS: 'success',
|
31
|
+
};
|
32
|
+
|
33
|
+
/**
|
34
|
+
* @param {NotificationOptions} options
|
35
|
+
* @param {Notifier} notifier
|
36
|
+
* @returns {Notification}
|
37
|
+
*/
|
38
|
+
function createNotification(options, notifier) {
|
39
|
+
const {
|
40
|
+
type,
|
41
|
+
title,
|
42
|
+
message,
|
43
|
+
timeout,
|
44
|
+
} = options;
|
45
|
+
const id = uuidv4();
|
46
|
+
const open = ref(true);
|
47
|
+
|
48
|
+
return {
|
49
|
+
get id() { return id; },
|
50
|
+
get type() { return type; },
|
51
|
+
get title() { return title; },
|
52
|
+
get message() { return message; },
|
53
|
+
get timeout() { return timeout ?? 5000; },
|
54
|
+
get open() { return open; },
|
55
|
+
set open(value) {
|
56
|
+
open.value = value?.value ?? value; // when used as a v-model, this is set as a boolean
|
57
|
+
if (!open.value) {
|
58
|
+
this.close();
|
59
|
+
}
|
60
|
+
},
|
61
|
+
close() {
|
62
|
+
open.value = false;
|
63
|
+
setTimeout(() => {
|
64
|
+
notifier.remove(this);
|
65
|
+
}, 100);
|
66
|
+
},
|
67
|
+
};
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* API for adding snackbar notification to the VcsUiApp. This is simply a container and on its own will not render anything.
|
72
|
+
* Typically, you do not need to instantiate this yourself, but use the notifier on the {@see VcsUiApp}.
|
73
|
+
* @class
|
74
|
+
*/
|
75
|
+
class Notifier {
|
76
|
+
constructor() {
|
77
|
+
/**
|
78
|
+
* @type {import("vue").Ref<Array<Notification>>}
|
79
|
+
* @private
|
80
|
+
*/
|
81
|
+
this._notifications = shallowRef([]);
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* @type {import("vue").Ref<Array<Notification>>}
|
86
|
+
* @readonly
|
87
|
+
*/
|
88
|
+
get notifications() {
|
89
|
+
return this._notifications;
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* @param {NotificationOptions} notification
|
94
|
+
* @returns {Notification}
|
95
|
+
*/
|
96
|
+
add(notification) {
|
97
|
+
const note = createNotification(notification, this);
|
98
|
+
this._notifications.value.push(note);
|
99
|
+
return note;
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* @param {Notification} notification
|
104
|
+
*/
|
105
|
+
remove(notification) {
|
106
|
+
const index = this._notifications.value.indexOf(notification);
|
107
|
+
if (index > -1) {
|
108
|
+
this._notifications.value.splice(index, 1);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* @param {Notification} notification
|
114
|
+
* @returns {boolean}
|
115
|
+
*/
|
116
|
+
has(notification) {
|
117
|
+
return this._notifications.value.includes(notification);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
export default Notifier;
|
@@ -0,0 +1,84 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<v-snackbar
|
4
|
+
dark
|
5
|
+
v-for="(notification) in notifications"
|
6
|
+
:key="notification.id"
|
7
|
+
v-model="notification.open"
|
8
|
+
:timeout="notification.timeout"
|
9
|
+
>
|
10
|
+
<v-icon
|
11
|
+
:color="notification.type"
|
12
|
+
>
|
13
|
+
{{ icon[notification.type] }}
|
14
|
+
</v-icon>
|
15
|
+
<span class="snack-title">{{ $t(notification.title || defaultTitle[notification.type]) }}</span>
|
16
|
+
<template #action="{ attrs }">
|
17
|
+
<VcsButton
|
18
|
+
icon="mdi-close"
|
19
|
+
small
|
20
|
+
v-bind="attrs"
|
21
|
+
@click="notification.open = false"
|
22
|
+
/>
|
23
|
+
</template>
|
24
|
+
<span>{{ $t(notification.message) }}</span>
|
25
|
+
</v-snackbar>
|
26
|
+
</div>
|
27
|
+
</template>
|
28
|
+
|
29
|
+
<script>
|
30
|
+
import { VSnackbar, VIcon } from 'vuetify/lib';
|
31
|
+
import { inject } from 'vue';
|
32
|
+
import VcsButton from '../components/buttons/VcsButton.vue';
|
33
|
+
import { NotificationType } from './notifier.js';
|
34
|
+
|
35
|
+
export default {
|
36
|
+
components: {
|
37
|
+
VSnackbar,
|
38
|
+
VcsButton,
|
39
|
+
VIcon,
|
40
|
+
},
|
41
|
+
name: 'NotifierComponent',
|
42
|
+
setup() {
|
43
|
+
const app = inject('vcsApp');
|
44
|
+
|
45
|
+
return {
|
46
|
+
notifications: app.notifier.notifications,
|
47
|
+
icon: {
|
48
|
+
[NotificationType.ERROR]: 'mdi-alert-box',
|
49
|
+
[NotificationType.WARNING]: 'mdi-alert',
|
50
|
+
[NotificationType.INFO]: 'mdi-information',
|
51
|
+
[NotificationType.SUCCESS]: 'mdi-check-circle',
|
52
|
+
},
|
53
|
+
defaultTitle: {
|
54
|
+
[NotificationType.ERROR]: 'notification.error',
|
55
|
+
[NotificationType.WARNING]: 'notification.warning',
|
56
|
+
[NotificationType.INFO]: 'notification.information',
|
57
|
+
[NotificationType.SUCCESS]: 'notification.success',
|
58
|
+
},
|
59
|
+
};
|
60
|
+
},
|
61
|
+
};
|
62
|
+
</script>
|
63
|
+
|
64
|
+
<style lang="scss" scoped>
|
65
|
+
.v-snack{
|
66
|
+
::v-deep{
|
67
|
+
.v-snack__content{
|
68
|
+
display: grid;
|
69
|
+
gap: 8px 4px;
|
70
|
+
grid-template-columns: 20px auto;
|
71
|
+
.v-icon{
|
72
|
+
grid-row-start: 1;
|
73
|
+
grid-row-end: 3;
|
74
|
+
align-self: start;
|
75
|
+
font-size: 17px;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
.v-snack__action{
|
79
|
+
align-self: flex-start;
|
80
|
+
margin-top: 4px;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
</style>
|
@@ -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>
|