@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
@@ -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, OpenlayersMap } from '@vcmap/core';
41
- import { unByKey } from 'ol/Observable.js';
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
- let postRenderHandler = () => {};
129
-
130
- const setActiveMap = (map) => {
104
+ const handleRenderEvent = ({ map }) => {
131
105
  viewMode.value = getViewModeForMap(map);
132
- postRenderHandler();
133
- postRenderHandler = mapPostRender(map, headingRef, tiltRef);
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.mapActivated.addEventListener(setActiveMap);
137
- setActiveMap(app.maps.activeMap);
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: { VcsTooltip },
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 CesiumMap) {
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").CesiumMap} cesiumMap
343
+ * @param {import("@vcmap/core").VcsMap} map
347
344
  * @returns {Promise<void>}
348
345
  * @private
349
346
  */
350
- async _initializeForCesium(cesiumMap) {
347
+ async _initializePostRenderHandler(map) {
351
348
  if (!this._cameraIconLayer) {
352
349
  this._setupCameraIconLayer();
350
+ this._syncCameraViewAndFeature();
353
351
  }
354
- if (cesiumMap.initialized) {
355
- const cesiumViewer = cesiumMap.getCesiumWidget();
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
- unByKey(prUnKey);
384
- navListener();
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
- _addCameraFeature() {
546
- const viewpoint = this._app.maps.activeMap.getViewpointSync();
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 = position;
584
- viewpoint.groundPosition = null;
585
- viewpoint.distance = distance * 4;
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>