@vcmap/ui 5.0.0-rc.16 → 5.0.0-rc.18

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 (67) hide show
  1. package/build/buildHelpers.js +7 -1
  2. package/config/base.config.json +3 -45
  3. package/config/www.config.json +756 -132
  4. package/dist/assets/{cesium.430460.js → cesium.2f992f.js} +0 -0
  5. package/dist/assets/cesium.js +1 -1
  6. package/dist/assets/{core.5089ba.js → core.cb0408.js} +1700 -1718
  7. package/dist/assets/core.js +1 -1
  8. package/dist/assets/{index.854f8e2b.js → index.bccdf969.js} +1 -1
  9. package/dist/assets/{ol.9be53a.js → ol.5e3fd0.js} +0 -0
  10. package/dist/assets/ol.js +1 -1
  11. package/dist/assets/ui.08c48f.css +1 -0
  12. package/dist/assets/{ui.49010a.js → ui.08c48f.js} +6254 -5906
  13. package/dist/assets/ui.js +1 -1
  14. package/dist/assets/{vue.247c1c.js → vue.228ead.js} +0 -0
  15. package/dist/assets/vue.js +2 -2
  16. package/dist/assets/{vuetify.735e58.css → vuetify.0b5039.css} +0 -0
  17. package/dist/assets/{vuetify.735e58.js → vuetify.0b5039.js} +5 -2
  18. package/dist/assets/vuetify.js +2 -2
  19. package/dist/index.html +1 -1
  20. package/index.js +14 -3
  21. package/package.json +2 -2
  22. package/plugins/@vcmap/pluginExample/index.js +2 -1
  23. package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +7 -0
  24. package/plugins/buttonExamples/ButtonExamples.vue +18 -0
  25. package/plugins/categoryTest/Categories.vue +27 -13
  26. package/plugins/categoryTest/Category.vue +7 -1
  27. package/plugins/categoryTest/index.js +1 -1
  28. package/plugins/package.json +1 -1
  29. package/plugins/test/allIconsComponent.vue +3 -3
  30. package/plugins/test/index.js +9 -5
  31. package/plugins/test/testList.vue +4 -1
  32. package/plugins/test/toolbox-data.js +168 -111
  33. package/plugins/test/vcsContent.vue +1 -1
  34. package/plugins/test/windowManagerExample.vue +9 -7
  35. package/src/actions/actionHelper.js +13 -10
  36. package/src/application/VcsApp.vue +25 -26
  37. package/src/application/VcsNavbar.vue +1 -1
  38. package/src/components/buttons/VcsButton.vue +14 -3
  39. package/src/components/form-inputs-controls/VcsCheckbox.vue +1 -0
  40. package/src/components/form-inputs-controls/VcsFormSection.vue +14 -6
  41. package/src/components/lists/VcsActionList.vue +2 -0
  42. package/src/components/lists/VcsList.vue +4 -2
  43. package/src/contentTree/contentTreeCollection.js +9 -0
  44. package/src/contentTree/layerContentTreeItem.js +3 -3
  45. package/src/featureInfo/BalloonComponent.vue +5 -2
  46. package/src/featureInfo/balloonFeatureInfoView.js +2 -8
  47. package/src/featureInfo/balloonHelper.js +22 -5
  48. package/src/featureInfo/featureInfo.js +1 -2
  49. package/src/i18n/de.js +12 -3
  50. package/src/i18n/en.js +10 -1
  51. package/src/legend/legendHelper.js +6 -7
  52. package/src/legend/vcsLegend.vue +12 -3
  53. package/src/manager/categoryManager/CategoryComponent.vue +115 -0
  54. package/src/manager/categoryManager/CategoryComponentList.vue +57 -0
  55. package/src/manager/categoryManager/CategoryManager.vue +35 -0
  56. package/src/manager/categoryManager/categoryManager.js +251 -165
  57. package/src/manager/contextMenu/contextMenuManager.js +8 -2
  58. package/src/manager/toolbox/ToolboxManager.vue +1 -0
  59. package/src/manager/window/WindowComponent.vue +49 -75
  60. package/src/manager/window/WindowComponentHeader.vue +49 -7
  61. package/src/manager/window/WindowManager.vue +53 -30
  62. package/src/manager/window/windowHelper.js +341 -0
  63. package/src/manager/window/windowManager.js +162 -150
  64. package/src/notifier/notifier.js +4 -5
  65. package/src/vcsUiApp.js +7 -1
  66. package/dist/assets/ui.49010a.css +0 -1
  67. 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,13 +79,33 @@ 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]
81
111
  * @property {Object} [provides]
@@ -86,11 +116,14 @@ export const WindowPositions = {
86
116
  * @property {string} id
87
117
  * @property {string|vcsAppSymbol} owner Owner of the window, set by windowManager on add
88
118
  * @property {boolean} [hideHeader] be used to not show the header.
119
+ * @property {boolean} [hidePin] be used to not show the pin button.
89
120
  * @property {string} [headerTitle]
90
121
  * @property {string} [headerIcon]
91
122
  * @property {Array<VcsAction>} [headerActions]
92
123
  * @property {number} [headerActionsOverflowCount]
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
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
94
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
95
128
  */
96
129
 
@@ -99,9 +132,11 @@ export const WindowPositions = {
99
132
  * @property {string} id
100
133
  * @property {import("vue").Component} component
101
134
  * @property {import("vue").Component} [headerComponent]
102
- * @property {WindowPosition} position
103
135
  * @property {WindowState} state
104
- * @property {Ref<UnwrapRef<WindowSlot>>} slot
136
+ * @property {WindowPosition} position
137
+ * @property {WindowPositionOptions} initialPositionOptions
138
+ * @property {import("vue").Ref<WindowSlot>} slot
139
+ * @property {WindowSlot} initialSlot
105
140
  * @property {Object} props
106
141
  * @property {Object} provides
107
142
  */
@@ -110,7 +145,7 @@ export const WindowPositions = {
110
145
  * @param {string|number|undefined} pos
111
146
  * @returns {string|undefined}
112
147
  */
113
- function parsePosition(pos) {
148
+ export function posToPixel(pos) {
114
149
  if (typeof pos === 'number') {
115
150
  return `${pos}px`;
116
151
  }
@@ -118,17 +153,18 @@ function parsePosition(pos) {
118
153
  }
119
154
 
120
155
  /**
156
+ * Returns CSS position string properties
121
157
  * @param {WindowPositionOptions} windowPositionOptions
122
158
  * @param {WindowPosition=} windowPosition
123
159
  * @returns {WindowPosition}
124
160
  */
125
161
  export function windowPositionFromOptions(windowPositionOptions, windowPosition = {}) {
126
- let left = parsePosition(windowPositionOptions.left) || 'unset';
127
- const right = parsePosition(windowPositionOptions.right) || 'unset';
128
- let top = parsePosition(windowPositionOptions.top) || 'unset';
129
- const bottom = parsePosition(windowPositionOptions.bottom) || 'unset';
130
- let width = parsePosition(windowPositionOptions.width) || 'auto';
131
- 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';
132
168
  if (left !== 'unset' && right !== 'unset') {
133
169
  width = 'auto'; // left + right takes precedence over configured width
134
170
  } else if (width === 'auto') {
@@ -151,127 +187,37 @@ export function windowPositionFromOptions(windowPositionOptions, windowPosition
151
187
  width,
152
188
  height,
153
189
  };
154
- return Object.assign(windowPosition, result);
155
- }
156
-
157
- /**
158
- * @enum {number}
159
- * @property {number} TOP_LEFT
160
- * @property {number} TOP_RIGHT
161
- * @property {number} BOTTOM_LEFT
162
- * @property {number} BOTTOM_RIGHT
163
- */
164
- export const WindowAlignment = {
165
- TOP_LEFT: 1,
166
- TOP_RIGHT: 2,
167
- BOTTOM_LEFT: 3,
168
- BOTTOM_RIGHT: 4,
169
- };
170
-
171
- /**
172
- * @returns {HTMLElement|null}
173
- */
174
- function getActiveMapElement() {
175
- const mapElements = document.getElementsByClassName('mapElement');
176
- for (let i = 0; i < mapElements.length; i++) {
177
- const element = mapElements.item(i);
178
- if (element.style.display !== 'none') {
179
- return element;
180
- }
190
+ if (windowPositionOptions.maxWidth) {
191
+ result.maxWidth = posToPixel(windowPositionOptions.maxWidth);
181
192
  }
182
- return null;
183
- }
184
-
185
- /**
186
- * WindowPositionOptions from client position relative to a HTMLElement
187
- * @param {number} x - client pixel position
188
- * @param {number} y - client pixel position
189
- * @param {HTMLElement} [element='mapElement'] - the element. if none is provided, the currently active mapElement will be taken
190
- * @param {WindowAlignment} [alignment=WindowAlignment.TOP_LEFT]
191
- * @returns {WindowPositionOptions}
192
- */
193
- export function getWindowPositionOptions(x, y, element, alignment = WindowAlignment.TOP_LEFT) {
194
- const mapElement = element ?? getActiveMapElement();
195
- if (!mapElement) {
196
- return { left: x, top: y };
193
+ if (windowPositionOptions.maxHeight) {
194
+ result.maxHeight = posToPixel(windowPositionOptions.maxHeight);
197
195
  }
198
-
199
- const { left, top, width, height } = mapElement.getBoundingClientRect();
200
- if (alignment === WindowAlignment.TOP_LEFT) {
201
- return { left: x - left, top: y - top };
202
- } else if (alignment === WindowAlignment.TOP_RIGHT) {
203
- return { right: (left + width) - x, top: y - top };
204
- } else if (alignment === WindowAlignment.BOTTOM_LEFT) {
205
- return { left: x - left, bottom: (height + top) - y };
196
+ if (windowPositionOptions.minHeight) {
197
+ result.minHeight = posToPixel(windowPositionOptions.minHeight);
206
198
  }
207
- return { right: (left + width) - x, bottom: (height + top) - y };
208
- }
209
-
210
- /**
211
- * Get window position options based on a pixel in the map
212
- * @param {import("@vcmap/cesium").Cartesian2} windowPosition - the window position, as retrieved from an InteractionEvent
213
- * @param {WindowAlignment} [alignment]
214
- * @returns {WindowPositionOptions}
215
- */
216
- export function getWindowPositionOptionsFromMapEvent(windowPosition, alignment) {
217
- const mapElement = getActiveMapElement();
218
- if (!mapElement) {
219
- return { left: windowPosition.x, top: windowPosition.y };
199
+ if (windowPositionOptions.minWidth) {
200
+ result.minWidth = posToPixel(windowPositionOptions.minWidth);
220
201
  }
221
202
 
222
- const { left, top } = mapElement.getBoundingClientRect();
223
- return getWindowPositionOptions(windowPosition.x + left, windowPosition.y + top, mapElement, alignment);
224
- }
225
-
226
-
227
- /**
228
- * Fits a window aligned top left so it fits into the parent. this will change the alignment to be bottom or right depending
229
- * on if the window would not fit into the parent.
230
- * @param {number} x - client pixel position
231
- * @param {number} y - client pixel position
232
- * @param {number} width - window width to fit
233
- * @param {number} height - window height to fit
234
- * @param {HTMLElement} [element='mapElement'] - the element. if none is provided, the currently active mapElement will be taken
235
- * @returns {WindowPositionOptions}
236
- */
237
- export function getFittedWindowPositionOptions(x, y, width, height, element) {
238
- const mapElement = element ?? getActiveMapElement();
239
- if (!mapElement) {
240
- return { left: x, top: y };
241
- }
242
-
243
- const { width: parentWidth, height: parentHeight } = mapElement.getBoundingClientRect();
244
- const bottom = y + height > parentHeight;
245
- const right = x + width > parentWidth;
246
- let alignment = WindowAlignment.TOP_LEFT;
247
- if (bottom) {
248
- if (right) {
249
- alignment = WindowAlignment.BOTTOM_RIGHT;
250
- } else {
251
- alignment = WindowAlignment.BOTTOM_LEFT;
252
- }
253
- } else if (right) {
254
- alignment = WindowAlignment.TOP_RIGHT;
255
- }
256
- return getWindowPositionOptions(x, y, mapElement, alignment);
203
+ return Object.assign(windowPosition, result);
257
204
  }
258
205
 
259
206
  /**
260
- * Fits a window aligned top left so it fits into currently active map. this will change the alignment to be bottom or right depending
261
- * on if the window would not fit into active map element.
262
- * @param {import("@vcmap/cesium").Cartesian2} windowPosition - the window position, as retrieved from an InteractionEvent
263
- * @param {number} width
264
- * @param {number} height
265
- * @returns {WindowPositionOptions}
207
+ * Sets a position on a component. Updates dockable state.
208
+ * @param {WindowComponent} windowComponent
209
+ * @param {WindowComponentOptions} windowPositionOptions
266
210
  */
267
- export function getFittedWindowPositionOptionsFromMapEvent(windowPosition, width, height) {
268
- const mapElement = getActiveMapElement();
269
- if (!mapElement) {
270
- 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;
271
216
  }
272
-
273
- const { left, top } = mapElement.getBoundingClientRect();
274
- 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);
275
221
  }
276
222
 
277
223
  /**
@@ -300,6 +246,11 @@ class WindowManager {
300
246
  * @private
301
247
  */
302
248
  this._windowComponents = new Map();
249
+ /**
250
+ * @type {Map<string, WindowPosition>}
251
+ * @private
252
+ */
253
+ this._windowPositionsCache = new Map();
303
254
  }
304
255
 
305
256
  /**
@@ -327,6 +278,7 @@ class WindowManager {
327
278
  check(id, String);
328
279
  const windowComponent = this._windowComponents.get(id);
329
280
  if (windowComponent) {
281
+ this._cachePosition(windowComponent);
330
282
  const index = this.componentIds.indexOf(id);
331
283
  this.componentIds.splice(index, 1);
332
284
  this._windowComponents.delete(id);
@@ -342,14 +294,7 @@ class WindowManager {
342
294
  setWindowPositionOptions(id, windowPositionOptions) {
343
295
  const windowComponent = this._windowComponents.get(id);
344
296
  if (windowComponent) {
345
- const isSlotPosition = windowPositionOptions === WindowPositions.TOP_LEFT ||
346
- windowPositionOptions === WindowPositions.TOP_LEFT2 ||
347
- windowPositionOptions === WindowPositions.TOP_RIGHT;
348
- // not one of the default Positions, so we also have to DETACH the windowState.
349
- if (!isSlotPosition) {
350
- windowComponent.slot.value = WindowSlot.DETACHED;
351
- }
352
- windowPositionFromOptions(windowPositionOptions, windowComponent.position);
297
+ setWindowPosition(windowComponent, windowPositionOptions);
353
298
  }
354
299
  }
355
300
 
@@ -418,6 +363,42 @@ class WindowManager {
418
363
  }
419
364
  }
420
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
+
421
402
  /**
422
403
  * adds a windowComponent to the WindowManager and renders the Window at the provided position/slot.
423
404
  * The reactive WindowState Object can be used to watch Changes on position/WindowSlot.
@@ -452,10 +433,13 @@ class WindowManager {
452
433
  id,
453
434
  owner,
454
435
  hideHeader: !!windowComponentOptions?.state?.hideHeader,
436
+ hidePin: !!windowComponentOptions?.state?.hidePin,
455
437
  headerTitle: windowComponentOptions?.state?.headerTitle,
456
438
  headerIcon: windowComponentOptions?.state?.headerIcon,
457
439
  headerActions: windowComponentOptions?.state?.headerActions,
458
440
  headerActionsOverflow: windowComponentOptions?.state?.headerActionsOverflow,
441
+ dockable: false,
442
+ infoUrl: windowComponentOptions?.state?.infoUrl,
459
443
  classes,
460
444
  styles,
461
445
  });
@@ -464,6 +448,7 @@ class WindowManager {
464
448
  const provides = windowComponentOptions.provides || {};
465
449
 
466
450
  const position = reactive(windowPosition);
451
+ const initialPosition = { ...windowPositionOptions };
467
452
  /**
468
453
  * @type {WindowComponent}
469
454
  */
@@ -483,9 +468,15 @@ class WindowManager {
483
468
  get slot() {
484
469
  return slotRef;
485
470
  },
471
+ get initialSlot() {
472
+ return slot;
473
+ },
486
474
  get position() {
487
475
  return position;
488
476
  },
477
+ get initialPositionOptions() {
478
+ return initialPosition;
479
+ },
489
480
  get props() {
490
481
  return props;
491
482
  },
@@ -493,7 +484,10 @@ class WindowManager {
493
484
  return provides;
494
485
  },
495
486
  };
496
- this._removeWindowAtSlot(slot);
487
+ const cached = this._assignCachedPosition(windowComponent);
488
+ if (!cached) {
489
+ this._removeWindowAtSlot(slot);
490
+ }
497
491
  this._windowComponents.set(id, windowComponent);
498
492
  this.componentIds.push(id);
499
493
  this._handleSlotsChanged(slot);
@@ -515,6 +509,24 @@ class WindowManager {
515
509
  }
516
510
  }
517
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
+
518
530
  /**
519
531
  * removes all windowComponents of a specific owner (plugin) and fires removed Events
520
532
  * @param {string|vcsAppSymbol} owner
@@ -95,7 +95,8 @@ class Notifier {
95
95
  */
96
96
  add(notification) {
97
97
  const note = createNotification(notification, this);
98
- this._notifications.value.push(note);
98
+ // use spread since push won't trigger updates
99
+ this._notifications.value = [...this._notifications.value, note];
99
100
  return note;
100
101
  }
101
102
 
@@ -103,10 +104,8 @@ class Notifier {
103
104
  * @param {Notification} notification
104
105
  */
105
106
  remove(notification) {
106
- const index = this._notifications.value.indexOf(notification);
107
- if (index > -1) {
108
- this._notifications.value.splice(index, 1);
109
- }
107
+ // reassign to trigger update
108
+ this._notifications.value = this._notifications.value.filter(n => n !== notification);
110
109
  }
111
110
 
112
111
  /**
package/src/vcsUiApp.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  defaultDynamicContextId,
9
9
  ObliqueMap,
10
10
  Viewpoint,
11
- volatileContextId,
11
+ volatileContextId, VcsEvent,
12
12
  } from '@vcmap/core';
13
13
  import { getLogger as getLoggerByName } from '@vcsuite/logger';
14
14
  import {
@@ -235,6 +235,12 @@ class VcsUiApp extends VcsApp {
235
235
  * @private
236
236
  */
237
237
  this._cachedAppState = getStateFromURL(new URL(window.location.href));
238
+ /**
239
+ * An event triggered when the VcsApp was mounted and a target was set for the maps.
240
+ * Provides the id of the target html element.
241
+ * @type {import("@vcmap/core").VcsEvent<string>}
242
+ */
243
+ this.mounted = new VcsEvent();
238
244
  }
239
245
 
240
246
  /**