@vcmap/ui 5.0.0-rc.15 → 5.0.0-rc.17

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 (91) hide show
  1. package/build/buildHelpers.js +7 -1
  2. package/config/base.config.json +7 -45
  3. package/config/dev.config.json +5 -1
  4. package/config/www.config.json +14 -13
  5. package/dist/assets/{cesium.2e288a.js → cesium.41de56.js} +0 -0
  6. package/dist/assets/cesium.js +1 -1
  7. package/dist/assets/{core.8014d3.js → core.af84e3.js} +6077 -4544
  8. package/dist/assets/core.js +1 -1
  9. package/dist/assets/{index.3f74fa92.js → index.5b773cad.js} +1 -1
  10. package/dist/assets/{ol.31c3a5.js → ol.5c7490.js} +0 -0
  11. package/dist/assets/ol.js +1 -1
  12. package/dist/assets/ui.dffe32.css +1 -0
  13. package/dist/assets/{ui.36f84f.js → ui.dffe32.js} +7243 -6234
  14. package/dist/assets/ui.js +1 -1
  15. package/dist/assets/{vue.a39c10.js → vue.25da17.js} +0 -0
  16. package/dist/assets/vue.js +2 -2
  17. package/dist/assets/{vuetify.378637.css → vuetify.e4ece7.css} +1 -1
  18. package/dist/assets/{vuetify.378637.js → vuetify.e4ece7.js} +5 -2
  19. package/dist/assets/vuetify.js +2 -2
  20. package/dist/index.html +1 -1
  21. package/index.html +77 -0
  22. package/index.js +18 -3
  23. package/package.json +4 -2
  24. package/plugins/@vcmap/create-link/fallbackCreateLink.vue +4 -1
  25. package/plugins/@vcmap/create-link/index.js +4 -1
  26. package/plugins/@vcmap/pluginExample/exampleActions.js +45 -0
  27. package/plugins/@vcmap/pluginExample/index.js +26 -2
  28. package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +77 -42
  29. package/plugins/@vcmap/search-nominatim/nominatim.js +1 -1
  30. package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +4 -2
  31. package/plugins/categoryTest/Categories.vue +27 -13
  32. package/plugins/categoryTest/Category.vue +7 -1
  33. package/plugins/categoryTest/index.js +1 -1
  34. package/plugins/notifier/index.js +31 -0
  35. package/plugins/notifier/notifierTester.vue +88 -0
  36. package/plugins/package.json +1 -1
  37. package/plugins/test/allIconsComponent.vue +5 -5
  38. package/plugins/test/emptyComponent.vue +1 -1
  39. package/plugins/test/index.js +27 -3
  40. package/plugins/test/myCustomHeader.vue +9 -1
  41. package/plugins/test/testList.vue +290 -0
  42. package/plugins/test/vcsContent.vue +1 -1
  43. package/plugins/test/windowManagerExample.vue +12 -7
  44. package/plugins/wizardExample/index.js +41 -0
  45. package/plugins/wizardExample/wizardExample.vue +77 -0
  46. package/src/actions/actionHelper.js +10 -9
  47. package/src/application/VcsApp.vue +43 -34
  48. package/src/components/form-inputs-controls/VcsCheckbox.vue +1 -0
  49. package/src/components/form-inputs-controls/VcsFormSection.vue +23 -15
  50. package/src/components/form-inputs-controls/VcsSelect.vue +33 -1
  51. package/src/components/form-inputs-controls/VcsTextField.vue +11 -3
  52. package/src/components/form-inputs-controls/VcsWizard.vue +133 -0
  53. package/src/components/imageElementInjector.vue +22 -0
  54. package/src/components/lists/VcsList.vue +468 -0
  55. package/src/components/lists/VcsTreeview.vue +1 -2
  56. package/src/components/lists/VcsTreeviewLeaf.vue +18 -50
  57. package/src/components/lists/VcsTreeviewSearchbar.vue +1 -23
  58. package/src/components/tables/VcsTable.vue +13 -1
  59. package/src/contentTree/LayerTree.vue +1 -1
  60. package/src/contentTree/contentTreeCollection.js +9 -0
  61. package/src/contentTree/contentTreeItem.js +13 -13
  62. package/src/contentTree/layerContentTreeItem.js +1 -1
  63. package/src/contentTree/subContentTreeItem.js +1 -1
  64. package/src/contentTree/vcsObjectContentTreeItem.js +1 -1
  65. package/src/featureInfo/BalloonComponent.vue +13 -8
  66. package/src/featureInfo/balloonFeatureInfoView.js +16 -22
  67. package/src/featureInfo/balloonHelper.js +26 -5
  68. package/src/featureInfo/featureInfo.js +14 -2
  69. package/src/featureInfo/featureInfoInteraction.js +1 -1
  70. package/src/i18n/de.js +13 -1
  71. package/src/i18n/en.js +13 -1
  72. package/src/icons/+all.js +4 -0
  73. package/src/icons/WandIcon.vue +63 -0
  74. package/src/manager/categoryManager/CategoryComponent.vue +115 -0
  75. package/src/manager/categoryManager/CategoryComponentList.vue +57 -0
  76. package/src/manager/categoryManager/CategoryManager.vue +35 -0
  77. package/src/manager/categoryManager/categoryManager.js +251 -165
  78. package/src/manager/contextMenu/contextMenuManager.js +8 -2
  79. package/src/manager/window/WindowComponent.vue +51 -70
  80. package/src/manager/window/WindowComponentHeader.vue +81 -13
  81. package/src/manager/window/WindowManager.vue +54 -30
  82. package/src/manager/window/windowHelper.js +341 -0
  83. package/src/manager/window/windowManager.js +173 -151
  84. package/src/navigation/overviewMap.js +10 -9
  85. package/src/notifier/notifier.js +120 -0
  86. package/src/notifier/notifierComponent.vue +84 -0
  87. package/src/styles/variables.scss +19 -3
  88. package/src/vcsUiApp.js +26 -2
  89. package/src/vuePlugins/vuetify.js +2 -0
  90. package/dist/assets/ui.36f84f.css +0 -1
  91. package/src/manager/categoryManager/ComponentsManager.vue +0 -30
@@ -8,10 +8,10 @@ import { vcsAppSymbol } from '../../pluginHelper.js';
8
8
  /**
9
9
  * @readonly
10
10
  * @enum {string}
11
- * @property {string} STATIC
12
- * @property {string} DYNAMIC_LEFT
13
- * @property {string} DYNAMIC_RIGHT
14
- * @property {string} DETACHED
11
+ * @property {string} STATIC - Static windows cannot be moved and will be positioned top-left.
12
+ * @property {string} DYNAMIC_LEFT - Positioned top-left, if no static window is present. Can be moved by user interaction.
13
+ * @property {string} DYNAMIC_RIGHT - Positioned top-right. Can be moved by user interaction.
14
+ * @property {string} DETACHED - Positioned at initial provided position. Can be moved by user interaction.
15
15
  */
16
16
  export const WindowSlot = {
17
17
  STATIC: 'static',
@@ -22,38 +22,47 @@ export const WindowSlot = {
22
22
 
23
23
 
24
24
  /**
25
- * @typedef WindowPositionOptions
25
+ * @typedef {Object} WindowPositionOptions
26
26
  * @property {string|number|undefined} left Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
27
27
  * @property {string|number|undefined} top Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
28
28
  * @property {string|number|undefined} right Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
29
29
  * @property {string|number|undefined} bottom Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
30
- * @property {string|number|undefined} width Can be a css position string (e.g. '320px') number values are treated as `px` values
31
- * @property {string|number|undefined} height Can be pixel-value string (e.g. '320px') number values are treated as `px` values
30
+ * @property {string|number|undefined} width Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
31
+ * @property {string|number|undefined} height Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
32
+ * @property {string|number|undefined} maxHeight Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
33
+ * @property {string|number|undefined} maxWidth Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
34
+ * @property {string|number|undefined} minHeight Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
35
+ * @property {string|number|undefined} minWidth Can be a css position string (e.g. '320px' or '50%') number values are treated as `px` values
32
36
  */
33
37
 
34
38
  /**
35
- * @typedef WindowPosition
36
- * @property {string} left - absolute to map container
37
- * @property {string} top - absolute to map container
38
- * @property {string} right - absolute to map container
39
- * @property {string} bottom - absolute to map container
40
- * @property {string} width
41
- * @property {string} height
39
+ * @typedef {Object} WindowPosition
40
+ * @property {string} left - The left CSS property participates in specifying the horizontal position of a window.
41
+ * @property {string} top - The top CSS property participates in specifying the vertical position of a window.
42
+ * @property {string} right - The right CSS property participates in specifying the horizontal position of a window.
43
+ * @property {string} bottom - The bottom CSS property participates in specifying the vertical position of a window.
44
+ * @property {string} width - The width CSS property sets an element's width.
45
+ * @property {string} height - The height CSS property sets an element's height.
46
+ * @property {string} [maxHeight] - It prevents the used value of the height property from becoming larger than the value specified for max-height. (max is target height, will be automatically updated)
47
+ * @property {string} [maxWidth] - It prevents the used value of the width property from becoming larger than the value specified by max-width. (max is target width, will be automatically updated)
48
+ * @property {string} [minHeight] - It prevents the used value of the height property from becoming smaller than the value specified for min-height.
49
+ * @property {string} [minWidth] - It prevents the used value of the width property from becoming smaller than the value specified for min-width.
42
50
  */
43
51
 
44
52
  /**
45
53
  * @readonly
46
- * @enum {WindowPositionOptions}
47
- * @property {Position} TOP_LEFT position of the DYNAMIC_LEFT or STATIC Slot
48
- * @property {Position} TOP_LEFT2 position of the DYNAMIC_LEFT Slot if a STATIC is present
49
- * @property {Position} TOP_RIGHT position of the DYNAMIC_RIGHT Slot
50
- * @property {Position} DETACHED default position of DETACHED Windows if no position is given
54
+ * @enum {WindowPosition}
55
+ * @property {WindowPosition} TOP_LEFT position of the DYNAMIC_LEFT or STATIC Slot
56
+ * @property {WindowPosition} TOP_LEFT2 position of the DYNAMIC_LEFT Slot if a STATIC is present
57
+ * @property {WindowPosition} TOP_RIGHT position of the DYNAMIC_RIGHT Slot
58
+ * @property {WindowPosition} DETACHED default position of DETACHED Windows if no position is given
51
59
  * @private
52
60
  */
53
61
  export const WindowPositions = {
54
62
  TOP_LEFT: {
55
63
  left: '0px',
56
64
  top: '0px',
65
+ maxWidth: '320px',
57
66
  },
58
67
  TOP_LEFT2: {
59
68
  left: '320px',
@@ -62,6 +71,7 @@ export const WindowPositions = {
62
71
  TOP_RIGHT: {
63
72
  right: '0px',
64
73
  top: '0px',
74
+ maxHeight: '70%',
65
75
  },
66
76
  DETACHED: {
67
77
  left: '200px',
@@ -69,15 +79,36 @@ export const WindowPositions = {
69
79
  },
70
80
  };
71
81
 
82
+ /**
83
+ * Return true, if all values of pos1 match with the corresponding values of pos2
84
+ * @param {WindowPosition} pos1
85
+ * @param {WindowPosition} pos2
86
+ * @returns {boolean}
87
+ */
88
+ export function compareWindowPositions(pos1, pos2) {
89
+ return !(Object.keys(pos1).some(key => pos1[key] !== pos2[key]));
90
+ }
91
+
92
+ /**
93
+ * Returns true, if the provided position is a slot position
94
+ * @param {WindowPosition} windowPosition
95
+ * @returns {boolean}
96
+ */
97
+ export function isSlotPosition(windowPosition) {
98
+ return [WindowPositions.TOP_LEFT, WindowPositions.TOP_LEFT2, WindowPositions.TOP_RIGHT]
99
+ .some(s => compareWindowPositions(s, windowPosition));
100
+ }
101
+
72
102
  /**
73
103
  * @typedef WindowComponentOptions
74
104
  * @property {string} [id] Optional ID, If not provided an uuid will be generated.
75
105
  * @property {import("vue").Component} component Main Component which is shown below the header.
76
106
  * @property {import("vue").Component} [headerComponent] Replaces the Header Component.
77
- * @property {WindowPositionOptions} [position] Will be ignored if WindowSlot !== DETACHED, can be given otherwise or default will be used
78
107
  * @property {WindowState} [state]
108
+ * @property {WindowPositionOptions} [position] Will be ignored if WindowSlot !== DETACHED, can be given otherwise or default will be used
79
109
  * @property {WindowSlot} [slot] If WindowSlot is not detached the position will be ignored
80
110
  * @property {Object} [props]
111
+ * @property {Object} [provides]
81
112
  */
82
113
 
83
114
  /**
@@ -85,9 +116,14 @@ export const WindowPositions = {
85
116
  * @property {string} id
86
117
  * @property {string|vcsAppSymbol} owner Owner of the window, set by windowManager on add
87
118
  * @property {boolean} [hideHeader] be used to not show the header.
119
+ * @property {boolean} [hidePin] be used to not show the pin button.
88
120
  * @property {string} [headerTitle]
89
121
  * @property {string} [headerIcon]
90
- * @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
122
+ * @property {Array<VcsAction>} [headerActions]
123
+ * @property {number} [headerActionsOverflowCount]
124
+ * @property {string} [infoUrl] An optional url referencing help or further information on the window's content.
125
+ * @property {boolean} [dockable] Auto derived from hidePin, current slot, current position and initial position.
126
+ * @property {Object<string, string>} [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
127
  * @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
128
  */
93
129
 
@@ -96,17 +132,20 @@ export const WindowPositions = {
96
132
  * @property {string} id
97
133
  * @property {import("vue").Component} component
98
134
  * @property {import("vue").Component} [headerComponent]
99
- * @property {WindowPosition} position
100
135
  * @property {WindowState} state
101
- * @property {Ref<UnwrapRef<WindowSlot>>} slot
136
+ * @property {WindowPosition} position
137
+ * @property {WindowPositionOptions} initialPositionOptions
138
+ * @property {import("vue").Ref<WindowSlot>} slot
139
+ * @property {WindowSlot} initialSlot
102
140
  * @property {Object} props
141
+ * @property {Object} provides
103
142
  */
104
143
 
105
144
  /**
106
145
  * @param {string|number|undefined} pos
107
146
  * @returns {string|undefined}
108
147
  */
109
- function parsePosition(pos) {
148
+ export function posToPixel(pos) {
110
149
  if (typeof pos === 'number') {
111
150
  return `${pos}px`;
112
151
  }
@@ -114,17 +153,18 @@ function parsePosition(pos) {
114
153
  }
115
154
 
116
155
  /**
156
+ * Returns CSS position string properties
117
157
  * @param {WindowPositionOptions} windowPositionOptions
118
158
  * @param {WindowPosition=} windowPosition
119
159
  * @returns {WindowPosition}
120
160
  */
121
161
  export function windowPositionFromOptions(windowPositionOptions, windowPosition = {}) {
122
- let left = parsePosition(windowPositionOptions.left) || 'unset';
123
- const right = parsePosition(windowPositionOptions.right) || 'unset';
124
- let top = parsePosition(windowPositionOptions.top) || 'unset';
125
- const bottom = parsePosition(windowPositionOptions.bottom) || 'unset';
126
- let width = parsePosition(windowPositionOptions.width) || 'auto';
127
- let height = parsePosition(windowPositionOptions.height) || 'auto';
162
+ let left = posToPixel(windowPositionOptions.left) || 'unset';
163
+ const right = posToPixel(windowPositionOptions.right) || 'unset';
164
+ let top = posToPixel(windowPositionOptions.top) || 'unset';
165
+ const bottom = posToPixel(windowPositionOptions.bottom) || 'unset';
166
+ let width = posToPixel(windowPositionOptions.width) || 'auto';
167
+ let height = posToPixel(windowPositionOptions.height) || 'auto';
128
168
  if (left !== 'unset' && right !== 'unset') {
129
169
  width = 'auto'; // left + right takes precedence over configured width
130
170
  } else if (width === 'auto') {
@@ -147,127 +187,37 @@ export function windowPositionFromOptions(windowPositionOptions, windowPosition
147
187
  width,
148
188
  height,
149
189
  };
150
- return Object.assign(windowPosition, result);
151
- }
152
-
153
- /**
154
- * @enum {number}
155
- * @property {number} TOP_LEFT
156
- * @property {number} TOP_RIGHT
157
- * @property {number} BOTTOM_LEFT
158
- * @property {number} BOTTOM_RIGHT
159
- */
160
- export const WindowAlignment = {
161
- TOP_LEFT: 1,
162
- TOP_RIGHT: 2,
163
- BOTTOM_LEFT: 3,
164
- BOTTOM_RIGHT: 4,
165
- };
166
-
167
- /**
168
- * @returns {HTMLElement|null}
169
- */
170
- function getActiveMapElement() {
171
- const mapElements = document.getElementsByClassName('mapElement');
172
- for (let i = 0; i < mapElements.length; i++) {
173
- const element = mapElements.item(i);
174
- if (element.style.display !== 'none') {
175
- return element;
176
- }
190
+ if (windowPositionOptions.maxWidth) {
191
+ result.maxWidth = posToPixel(windowPositionOptions.maxWidth);
177
192
  }
178
- return null;
179
- }
180
-
181
- /**
182
- * WindowPositionOptions from client position relative to a HTMLElement
183
- * @param {number} x - client pixel position
184
- * @param {number} y - client pixel position
185
- * @param {HTMLElement} [element='mapElement'] - the element. if none is provided, the currently active mapElement will be taken
186
- * @param {WindowAlignment} [alignment=WindowAlignment.TOP_LEFT]
187
- * @returns {WindowPositionOptions}
188
- */
189
- export function getWindowPositionOptions(x, y, element, alignment = WindowAlignment.TOP_LEFT) {
190
- const mapElement = element ?? getActiveMapElement();
191
- if (!mapElement) {
192
- return { left: x, top: y };
193
- }
194
-
195
- const { left, top, width, height } = mapElement.getBoundingClientRect();
196
- if (alignment === WindowAlignment.TOP_LEFT) {
197
- return { left: x - left, top: y - top };
198
- } else if (alignment === WindowAlignment.TOP_RIGHT) {
199
- return { right: (left + width) - x, top: y - top };
200
- } else if (alignment === WindowAlignment.BOTTOM_LEFT) {
201
- return { left: x - left, bottom: (height + top) - y };
193
+ if (windowPositionOptions.maxHeight) {
194
+ result.maxHeight = posToPixel(windowPositionOptions.maxHeight);
202
195
  }
203
- return { right: (left + width) - x, bottom: (height + top) - y };
204
- }
205
-
206
- /**
207
- * Get window position options based on a pixel in the map
208
- * @param {import("@vcmap/cesium").Cartesian2} windowPosition - the window position, as retrieved from an InteractionEvent
209
- * @param {WindowAlignment} [alignment]
210
- * @returns {WindowPositionOptions}
211
- */
212
- export function getWindowPositionOptionsFromMapEvent(windowPosition, alignment) {
213
- const mapElement = getActiveMapElement();
214
- if (!mapElement) {
215
- return { left: windowPosition.x, top: windowPosition.y };
196
+ if (windowPositionOptions.minHeight) {
197
+ result.minHeight = posToPixel(windowPositionOptions.minHeight);
216
198
  }
217
-
218
- const { left, top } = mapElement.getBoundingClientRect();
219
- return getWindowPositionOptions(windowPosition.x + left, windowPosition.y + top, mapElement, alignment);
220
- }
221
-
222
-
223
- /**
224
- * Fits a window aligned top left so it fits into the parent. this will change the alignment to be bottom or right depending
225
- * on if the window would not fit into the parent.
226
- * @param {number} x - client pixel position
227
- * @param {number} y - client pixel position
228
- * @param {number} width - window width to fit
229
- * @param {number} height - window height to fit
230
- * @param {HTMLElement} [element='mapElement'] - the element. if none is provided, the currently active mapElement will be taken
231
- * @returns {WindowPositionOptions}
232
- */
233
- export function getFittedWindowPositionOptions(x, y, width, height, element) {
234
- const mapElement = element ?? getActiveMapElement();
235
- if (!mapElement) {
236
- return { left: x, top: y };
199
+ if (windowPositionOptions.minWidth) {
200
+ result.minWidth = posToPixel(windowPositionOptions.minWidth);
237
201
  }
238
202
 
239
- const { width: parentWidth, height: parentHeight } = mapElement.getBoundingClientRect();
240
- const bottom = y + height > parentHeight;
241
- const right = x + width > parentWidth;
242
- let alignment = WindowAlignment.TOP_LEFT;
243
- if (bottom) {
244
- if (right) {
245
- alignment = WindowAlignment.BOTTOM_RIGHT;
246
- } else {
247
- alignment = WindowAlignment.BOTTOM_LEFT;
248
- }
249
- } else if (right) {
250
- alignment = WindowAlignment.TOP_RIGHT;
251
- }
252
- return getWindowPositionOptions(x, y, mapElement, alignment);
203
+ return Object.assign(windowPosition, result);
253
204
  }
254
205
 
255
206
  /**
256
- * Fits a window aligned top left so it fits into currently active map. this will change the alignment to be bottom or right depending
257
- * on if the window would not fit into active map element.
258
- * @param {import("@vcmap/cesium").Cartesian2} windowPosition - the window position, as retrieved from an InteractionEvent
259
- * @param {number} width
260
- * @param {number} height
261
- * @returns {WindowPositionOptions}
207
+ * Sets a position on a component. Updates dockable state.
208
+ * @param {WindowComponent} windowComponent
209
+ * @param {WindowComponentOptions} windowPositionOptions
262
210
  */
263
- export function getFittedWindowPositionOptionsFromMapEvent(windowPosition, width, height) {
264
- const mapElement = getActiveMapElement();
265
- if (!mapElement) {
266
- return { left: windowPosition.x, top: windowPosition.y };
211
+ function setWindowPosition(windowComponent, windowPositionOptions) {
212
+ const windowPosition = windowPositionFromOptions(windowPositionOptions, windowComponent.position);
213
+ // not one of the default Positions, so we also have to DETACH the windowState.
214
+ if (!isSlotPosition(windowPosition)) {
215
+ windowComponent.slot.value = WindowSlot.DETACHED;
267
216
  }
268
-
269
- const { left, top } = mapElement.getBoundingClientRect();
270
- return getFittedWindowPositionOptions(windowPosition.x + left, windowPosition.y + top, width, height, mapElement);
217
+ // check dockable state
218
+ const initialWindowPosition = windowPositionFromOptions(windowComponent.initialPositionOptions);
219
+ windowComponent.state.dockable = windowComponent.slot.value === WindowSlot.DETACHED &&
220
+ !compareWindowPositions(windowPosition, initialWindowPosition);
271
221
  }
272
222
 
273
223
  /**
@@ -296,6 +246,11 @@ class WindowManager {
296
246
  * @private
297
247
  */
298
248
  this._windowComponents = new Map();
249
+ /**
250
+ * @type {Map<string, WindowPosition>}
251
+ * @private
252
+ */
253
+ this._windowPositionsCache = new Map();
299
254
  }
300
255
 
301
256
  /**
@@ -323,6 +278,7 @@ class WindowManager {
323
278
  check(id, String);
324
279
  const windowComponent = this._windowComponents.get(id);
325
280
  if (windowComponent) {
281
+ this._cachePosition(windowComponent);
326
282
  const index = this.componentIds.indexOf(id);
327
283
  this.componentIds.splice(index, 1);
328
284
  this._windowComponents.delete(id);
@@ -338,14 +294,7 @@ class WindowManager {
338
294
  setWindowPositionOptions(id, windowPositionOptions) {
339
295
  const windowComponent = this._windowComponents.get(id);
340
296
  if (windowComponent) {
341
- const isSlotPosition = windowPositionOptions === WindowPositions.TOP_LEFT ||
342
- windowPositionOptions === WindowPositions.TOP_LEFT2 ||
343
- windowPositionOptions === WindowPositions.TOP_RIGHT;
344
- // not one of the default Positions, so we also have to DETACH the windowState.
345
- if (!isSlotPosition) {
346
- windowComponent.slot.value = WindowSlot.DETACHED;
347
- }
348
- windowPositionFromOptions(windowPositionOptions, windowComponent.position);
297
+ setWindowPosition(windowComponent, windowPositionOptions);
349
298
  }
350
299
  }
351
300
 
@@ -414,10 +363,46 @@ class WindowManager {
414
363
  }
415
364
  }
416
365
 
366
+ /**
367
+ * @param {string} id
368
+ * @returns {WindowPosition|undefined}
369
+ */
370
+ getCachedPosition(id) {
371
+ return this._windowPositionsCache.get(id);
372
+ }
373
+
374
+ /**
375
+ * Caches the position, if it differs from the initial position
376
+ * @param {WindowComponent} windowComponent
377
+ * @private
378
+ */
379
+ _cachePosition(windowComponent) {
380
+ const initialWindowPosition = windowPositionFromOptions(windowComponent.initialPositionOptions);
381
+ if (!compareWindowPositions(initialWindowPosition, windowComponent.position)) {
382
+ this._windowPositionsCache.set(windowComponent.id, { ...windowComponent.position });
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Returns true, if cached position was assigned.
388
+ * @param {WindowComponent} windowComponent
389
+ * @returns {boolean}
390
+ * @private
391
+ */
392
+ _assignCachedPosition(windowComponent) {
393
+ if (this._windowPositionsCache.has(windowComponent.id)) {
394
+ const windowPosition = this.getCachedPosition(windowComponent.id);
395
+ setWindowPosition(windowComponent, windowPosition);
396
+ this._windowPositionsCache.delete(windowComponent.id);
397
+ return true;
398
+ }
399
+ return false;
400
+ }
401
+
417
402
  /**
418
403
  * adds a windowComponent to the WindowManager and renders the Window at the provided position/slot.
419
404
  * 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
405
+ * The WindowState Object can also be used to change hideHeader, headerTitle, headerIcon, headerActions, styles and classes
421
406
  * @param {WindowComponentOptions|WindowComponent} windowComponentOptions
422
407
  * @param {string|symbol} owner pluginName or vcsAppSymbol
423
408
  * @throws {Error} if a windowComponent with the same ID has already been added
@@ -448,15 +433,22 @@ class WindowManager {
448
433
  id,
449
434
  owner,
450
435
  hideHeader: !!windowComponentOptions?.state?.hideHeader,
436
+ hidePin: !!windowComponentOptions?.state?.hidePin,
451
437
  headerTitle: windowComponentOptions?.state?.headerTitle,
452
438
  headerIcon: windowComponentOptions?.state?.headerIcon,
439
+ headerActions: windowComponentOptions?.state?.headerActions,
440
+ headerActionsOverflow: windowComponentOptions?.state?.headerActionsOverflow,
441
+ dockable: false,
442
+ infoUrl: windowComponentOptions?.state?.infoUrl,
453
443
  classes,
454
444
  styles,
455
445
  });
456
446
 
457
447
  const props = windowComponentOptions.props || {};
448
+ const provides = windowComponentOptions.provides || {};
458
449
 
459
450
  const position = reactive(windowPosition);
451
+ const initialPosition = { ...windowPositionOptions };
460
452
  /**
461
453
  * @type {WindowComponent}
462
454
  */
@@ -476,14 +468,26 @@ class WindowManager {
476
468
  get slot() {
477
469
  return slotRef;
478
470
  },
471
+ get initialSlot() {
472
+ return slot;
473
+ },
479
474
  get position() {
480
475
  return position;
481
476
  },
477
+ get initialPositionOptions() {
478
+ return initialPosition;
479
+ },
482
480
  get props() {
483
481
  return props;
484
482
  },
483
+ get provides() {
484
+ return provides;
485
+ },
485
486
  };
486
- this._removeWindowAtSlot(slot);
487
+ const cached = this._assignCachedPosition(windowComponent);
488
+ if (!cached) {
489
+ this._removeWindowAtSlot(slot);
490
+ }
487
491
  this._windowComponents.set(id, windowComponent);
488
492
  this.componentIds.push(id);
489
493
  this._handleSlotsChanged(slot);
@@ -505,6 +509,24 @@ class WindowManager {
505
509
  }
506
510
  }
507
511
 
512
+ /**
513
+ * Docks a window by resetting detached to its initial slot.
514
+ * Updates position according to its initial slot or initial position.
515
+ * Clears any cached position for this window.
516
+ * @param {string} id
517
+ */
518
+ pinWindow(id) {
519
+ const component = this.get(id);
520
+ if (!component?.state?.dockable) {
521
+ return;
522
+ }
523
+ this._removeWindowAtSlot(component.initialSlot);
524
+ component.slot.value = component.initialSlot;
525
+ const dockedPosition = this._getPositionOptionsForSlot(component.initialSlot, component.initialPositionOptions);
526
+ this.setWindowPositionOptions(id, dockedPosition);
527
+ this._windowPositionsCache.delete(id);
528
+ }
529
+
508
530
  /**
509
531
  * removes all windowComponents of a specific owner (plugin) and fires removed Events
510
532
  * @param {string|vcsAppSymbol} owner
@@ -205,7 +205,7 @@ class OverviewMap {
205
205
  clone.activate();
206
206
  const idx = this._map.layerCollection.indexOf(clone);
207
207
  if (idx < 0) {
208
- this._map.layerCollection.add(clone);
208
+ this._map.layerCollection.add(clone, 0);
209
209
  } else {
210
210
  this._map.layerCollection.remove(clone);
211
211
  this._map.layerCollection.add(clone, idx);
@@ -298,6 +298,7 @@ class OverviewMap {
298
298
  this._setupMapInteraction();
299
299
  }
300
300
  await this._map.activate();
301
+ this.map.setTarget('overview-map-container');
301
302
  if (!this._active) {
302
303
  this._mapActivatedListener = this._app.maps.mapActivated.addEventListener(() => {
303
304
  this._clearListeners();
@@ -323,7 +324,6 @@ class OverviewMap {
323
324
  this._app.windowManager.add(getWindowComponentOptions(), vcsAppSymbol);
324
325
  }
325
326
  await this._activate();
326
- this.map.setTarget('overview-map-container');
327
327
  }
328
328
 
329
329
  /**
@@ -347,9 +347,10 @@ class OverviewMap {
347
347
  async _initializePostRenderHandler(map) {
348
348
  if (!this._cameraIconLayer) {
349
349
  this._setupCameraIconLayer();
350
+ this._syncCameraViewAndFeature();
350
351
  }
351
352
  const navRemover = this._addNavigationListener(map);
352
- const prRemover = map.postRender.addEventListener(this._addCameraFeature.bind(this));
353
+ const prRemover = map.postRender.addEventListener(this._syncCameraViewAndFeature.bind(this));
353
354
  const cleanupTasks = () => {
354
355
  prRemover();
355
356
  navRemover();
@@ -513,7 +514,7 @@ class OverviewMap {
513
514
  * Adds and maintains the view and camera feature
514
515
  * @private
515
516
  */
516
- _addCameraFeature() {
517
+ _syncCameraViewAndFeature() {
517
518
  const viewpoint = this._app.maps.activeMap?.getViewpointSync();
518
519
  if (!viewpoint || !viewpoint.isValid() || viewpoint.equals(this._cachedViewpoint)) {
519
520
  return;
@@ -525,8 +526,6 @@ class OverviewMap {
525
526
  let { distance } = viewpoint;
526
527
  if (position[2] && !(distance && distance < position[2] * 4)) {
527
528
  distance = position[2] * 4;
528
- } else if (position[2] == null) {
529
- position[2] = distance;
530
529
  }
531
530
 
532
531
  distance = distance > this.minimumHeight ? distance : this.minimumHeight;
@@ -551,9 +550,11 @@ class OverviewMap {
551
550
  this.cameraIconStyle.image.setRotation(rotationRadians);
552
551
 
553
552
  viewpoint.heading = 0;
554
- viewpoint.cameraPosition = position;
555
- viewpoint.groundPosition = null;
556
- viewpoint.distance = distance * 4;
553
+ if (viewpoint.cameraPosition) {
554
+ viewpoint.cameraPosition = position;
555
+ viewpoint.groundPosition = null;
556
+ viewpoint.distance = distance * 4;
557
+ }
557
558
  this._map.gotoViewpoint(viewpoint);
558
559
  }
559
560
 
@@ -0,0 +1,120 @@
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
+ // use spread since push won't trigger updates
99
+ this._notifications.value = [...this._notifications.value, note];
100
+ return note;
101
+ }
102
+
103
+ /**
104
+ * @param {Notification} notification
105
+ */
106
+ remove(notification) {
107
+ // reassign to trigger update
108
+ this._notifications.value = this._notifications.value.filter(n => n !== notification);
109
+ }
110
+
111
+ /**
112
+ * @param {Notification} notification
113
+ * @returns {boolean}
114
+ */
115
+ has(notification) {
116
+ return this._notifications.value.includes(notification);
117
+ }
118
+ }
119
+
120
+ export default Notifier;