@vcmap/ui 5.0.0-rc.10 → 5.0.0-rc.13
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 +12 -5
- package/build/build.js +6 -3
- package/build/buildHelpers.js +12 -4
- package/build/buildPreview.js +7 -0
- package/build/getPluginProxies.js +4 -0
- package/config/aerowest.config.json +13 -3
- package/config/base.config.json +398 -219
- package/config/codes.config.json +397 -0
- package/config/dev.config.json +375 -1
- package/config/graphFeatureInfo.config.json +100 -0
- package/config/www.config.json +1232 -0
- package/dist/assets/{cesium.eb5667.js → cesium.21663e.js} +0 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.63242d.js +4 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/font/OFL.txt +93 -0
- package/dist/assets/font/TitilliumWeb-Regular.woff2 +0 -0
- package/dist/assets/{index.4ccd4433.js → index.44b91cfe.js} +1 -1
- package/dist/assets/{ol.ef03b1.js → ol.88ba9d.js} +0 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui.3c2933.css +1 -0
- package/dist/assets/ui.3c2933.js +71 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.c897fc.js +9 -0
- package/dist/assets/vue.js +2 -1
- package/dist/assets/{vuetify.401a29.css → vuetify.147c3a.css} +1 -1
- package/dist/assets/{vuetify.401a29.js → vuetify.147c3a.js} +72 -72
- package/dist/assets/vuetify.js +2 -2
- package/dist/index.html +1 -5
- package/index.js +39 -5
- package/lib/vue.js +1 -0
- package/map.config.json +15 -6
- package/package.json +17 -8
- package/plugins/@vcmap/create-link/fallbackCreateLink.vue +71 -0
- package/plugins/@vcmap/create-link/index.js +83 -0
- package/plugins/@vcmap/create-link/package.json +6 -0
- package/plugins/@vcmap/pluginExample/index.js +2 -2
- package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +20 -3
- package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +1 -1
- package/plugins/@vcmap/project-selector/index.js +1 -1
- package/plugins/@vcmap/project-selector/package.json +1 -2
- package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +1 -1
- package/plugins/@vcmap/theme-changer/index.js +1 -1
- package/plugins/@vcmap/theme-changer/package.json +1 -2
- package/plugins/categoryTest/Categories.vue +89 -1
- package/plugins/categoryTest/Category.vue +1 -1
- package/plugins/example/index.js +10 -23
- package/plugins/simple-graph/README.md +51 -0
- package/plugins/simple-graph/SimpleGraphComponent.vue +70 -0
- package/plugins/simple-graph/index.js +17 -0
- package/plugins/simple-graph/package.json +11 -0
- package/plugins/simple-graph/simpleGraphView.js +76 -0
- package/plugins/test/editor.vue +1 -1
- package/plugins/test/index.js +76 -9
- package/plugins/test/toolbox-data.js +82 -57
- package/plugins/test/windowManagerExample.vue +1 -1
- package/src/actions/stateRefAction.js +2 -2
- package/src/actions/styleSelector.vue +1 -1
- package/src/application/Navbar.vue +13 -2
- package/src/application/VcsApp.vue +301 -116
- package/src/application/VcsMap.vue +1 -1
- package/src/application/VcsSettings.vue +1 -1
- package/src/application/vcsAppWrapper.vue +1 -0
- package/src/assets/font/OFL.txt +93 -0
- package/src/assets/font/TitilliumWeb-Regular.woff2 +0 -0
- package/src/components/form-inputs-controls/VcsCheckbox.vue +13 -0
- package/src/components/form-inputs-controls/VcsColorPicker.vue +1 -1
- package/src/components/form-inputs-controls/VcsRadio.vue +123 -0
- package/src/components/form-output/VcsFormattedNumber.vue +1 -1
- package/src/components/lists/VcsActionList.vue +22 -7
- package/src/components/lists/VcsTreeview.vue +4 -4
- package/src/components/lists/VcsTreeviewLeaf.vue +10 -3
- package/src/components/lists/VcsTreeviewSearchbar.vue +1 -2
- package/src/components/tables/VcsTable.vue +245 -0
- package/src/contentTree/LayerTree.vue +1 -1
- package/src/contentTree/contentTreeCollection.js +4 -4
- package/src/contentTree/contentTreeItem.js +9 -9
- package/src/contentTree/groupContentTreeItem.js +1 -1
- package/src/contentTree/layerContentTreeItem.js +15 -1
- package/src/contentTree/layerGroupContentTreeItem.js +21 -1
- package/src/contentTree/nodeContentTreeItem.js +1 -1
- package/src/featureInfo/AddressBalloonComponent.vue +47 -0
- package/src/featureInfo/BalloonComponent.vue +140 -0
- package/src/featureInfo/abstractFeatureInfoView.js +313 -0
- package/src/featureInfo/addressBalloonFeatureInfoView.js +118 -0
- package/src/featureInfo/balloonFeatureInfoView.js +151 -0
- package/src/featureInfo/balloonHelper.js +132 -0
- package/src/featureInfo/featureInfo.js +457 -0
- package/src/featureInfo/featureInfoInteraction.js +42 -0
- package/src/featureInfo/iframeFeatureInfoView.js +95 -0
- package/src/featureInfo/tableFeatureInfoView.js +106 -0
- package/src/i18n/de.js +26 -0
- package/src/i18n/en.js +26 -0
- package/src/i18n/i18nCollection.js +17 -0
- package/src/icons/+all.js +80 -0
- package/src/icons/ClippingHorizontalIcon.vue +7 -0
- package/src/icons/ClippingIcon.vue +7 -0
- package/src/icons/ClippingVerticalIcon.vue +7 -0
- package/src/icons/ColorPickerIcon.vue +7 -0
- package/src/icons/ComponentsIcon.vue +2 -2
- package/src/icons/DimensionsHouseIcon.vue +11 -9
- package/src/icons/EditIcon.vue +7 -0
- package/src/icons/GlobalTerrainIcon.vue +9 -0
- package/src/icons/GroundIcon.vue +18 -0
- package/src/icons/HideIcon.vue +12 -0
- package/src/icons/LogoutIcon.vue +7 -0
- package/src/icons/ObjectAttributeIcon.vue +2 -13
- package/src/icons/PedestrianIcon.vue +2 -3
- package/src/icons/PenIcon.vue +2 -9
- package/src/icons/PoiIcon.vue +5 -2
- package/src/icons/PointSelectIcon.vue +4 -2
- package/src/icons/QueryIcon.vue +6 -7
- package/src/icons/ScreenshotIcon.vue +16 -0
- package/src/icons/ShareIcon.vue +4 -16
- package/src/icons/SkipNextIcon.vue +3 -1
- package/src/icons/TerrainBoxIcon.vue +9 -0
- package/src/icons/ToolsIcon.vue +4 -30
- package/src/icons/UploadIcon.vue +2 -9
- package/src/icons/UserProfileIcon.vue +7 -0
- package/src/icons/UserShareIcon.vue +7 -0
- package/src/icons/VideoRecorderIcon.vue +5 -9
- package/src/icons/ViewpointFlightIcon.vue +11 -0
- package/src/icons/ViewpointIcon.vue +11 -0
- package/src/icons/Viewshed360Icon.vue +7 -0
- package/src/icons/ViewshedConeIcon.vue +7 -0
- package/src/icons/ViewshedIcon.vue +7 -0
- package/src/icons/WallIcon.vue +4 -9
- package/src/legend/legendHelper.js +193 -0
- package/src/legend/styleLegendItem.vue +129 -0
- package/src/legend/vcsLegend.vue +92 -0
- package/src/manager/buttonManager.js +7 -12
- package/src/manager/categoryManager/ComponentsManager.vue +30 -0
- package/src/manager/categoryManager/categoryManager.js +500 -0
- package/src/manager/contextMenu/contextMenuComponent.vue +43 -0
- package/src/manager/contextMenu/contextMenuInteraction.js +42 -0
- package/src/manager/contextMenu/contextMenuManager.js +197 -0
- package/src/manager/navbarManager.js +9 -9
- package/src/manager/toolbox/GroupToolboxComponent.vue +118 -0
- package/src/manager/toolbox/SelectToolboxComponent.vue +128 -0
- package/src/manager/toolbox/ToolboxManager.vue +116 -98
- package/src/manager/toolbox/toolboxManager.js +235 -86
- package/src/manager/window/WindowComponent.vue +1 -1
- package/src/manager/window/WindowManager.vue +5 -3
- package/src/manager/window/windowManager.js +118 -14
- package/src/navigation/mapNavigation.vue +3 -5
- package/src/navigation/overviewMap.js +28 -5
- package/src/navigation/vcsCompass.vue +1 -1
- package/src/pluginHelper.js +42 -10
- package/src/setup.js +0 -2
- package/src/state.js +256 -0
- package/src/styles/_theming.scss +0 -5
- package/src/styles/variables.scss +7 -0
- package/src/styles/vcsFont.scss +17 -0
- package/src/uiConfig.js +79 -0
- package/src/vcsUiApp.js +213 -22
- package/src/vuePlugins/vuetify.js +14 -4
- package/config/berlin.config.json +0 -510
- package/dist/assets/core.216494.js +0 -4
- package/dist/assets/ui.99a1a7.css +0 -1
- package/dist/assets/ui.99a1a7.js +0 -70
- package/dist/assets/vue-composition-api.c5aca1.js +0 -14
- package/dist/assets/vue-composition-api.js +0 -2
- package/dist/assets/vue.762edd.js +0 -9
- package/lib/vue-composition-api.js +0 -2
- package/src/manager/toolbox/ToolboxGroupComponent.vue +0 -128
@@ -41,7 +41,7 @@
|
|
41
41
|
<script>
|
42
42
|
import {
|
43
43
|
onMounted, onUnmounted, computed, ref, nextTick,
|
44
|
-
} from '
|
44
|
+
} from 'vue';
|
45
45
|
import { fromEvent } from 'rxjs';
|
46
46
|
import { switchMap, take, map, tap } from 'rxjs/operators';
|
47
47
|
import { WindowSlot } from './windowManager.js';
|
@@ -1,5 +1,7 @@
|
|
1
1
|
<template>
|
2
|
-
<div
|
2
|
+
<div
|
3
|
+
:class="$vuetify.breakpoint.xs ? 'win-container-mobile' : 'unset'"
|
4
|
+
>
|
3
5
|
<WindowComponent
|
4
6
|
v-for="(id, zIndex) in componentIds"
|
5
7
|
:key="id"
|
@@ -46,7 +48,7 @@
|
|
46
48
|
</style>
|
47
49
|
|
48
50
|
<script>
|
49
|
-
import { inject } from '
|
51
|
+
import { inject, ref } from 'vue';
|
50
52
|
|
51
53
|
import WindowComponent from './WindowComponent.vue';
|
52
54
|
import WindowComponentHeader from './WindowComponentHeader.vue';
|
@@ -109,7 +111,7 @@
|
|
109
111
|
};
|
110
112
|
|
111
113
|
return {
|
112
|
-
componentIds,
|
114
|
+
componentIds: ref(componentIds),
|
113
115
|
getComponent: id => windowManager.get(id).component,
|
114
116
|
getHeaderComponent: id => windowManager.get(id).headerComponent || WindowComponentHeader,
|
115
117
|
getStyles,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { reactive, ref } from '
|
1
|
+
import { reactive, ref } from 'vue';
|
2
2
|
import { VcsEvent } from '@vcmap/core';
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
4
4
|
import { parseEnumValue } from '@vcsuite/parsers';
|
@@ -72,8 +72,8 @@ export const WindowPositions = {
|
|
72
72
|
/**
|
73
73
|
* @typedef WindowComponentOptions
|
74
74
|
* @property {string} [id] Optional ID, If not provided an uuid will be generated.
|
75
|
-
* @property {
|
76
|
-
* @property {
|
75
|
+
* @property {import("vue").Component} component Main Component which is shown below the header.
|
76
|
+
* @property {import("vue").Component} [headerComponent] Replaces the Header Component.
|
77
77
|
* @property {WindowPositionOptions} [position] Will be ignored if WindowSlot !== DETACHED, can be given otherwise or default will be used
|
78
78
|
* @property {WindowState} [state]
|
79
79
|
* @property {WindowSlot} [slot] If WindowSlot is not detached the position will be ignored
|
@@ -94,8 +94,8 @@ export const WindowPositions = {
|
|
94
94
|
/**
|
95
95
|
* @typedef WindowComponent
|
96
96
|
* @property {string} id
|
97
|
-
* @property {
|
98
|
-
* @property {
|
97
|
+
* @property {import("vue").Component} component
|
98
|
+
* @property {import("vue").Component} [headerComponent]
|
99
99
|
* @property {WindowPosition} position
|
100
100
|
* @property {WindowState} state
|
101
101
|
* @property {Ref<UnwrapRef<WindowSlot>>} slot
|
@@ -150,20 +150,124 @@ export function windowPositionFromOptions(windowPositionOptions, windowPosition
|
|
150
150
|
return Object.assign(windowPosition, result);
|
151
151
|
}
|
152
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
|
+
}
|
177
|
+
}
|
178
|
+
return null;
|
179
|
+
}
|
180
|
+
|
153
181
|
/**
|
154
182
|
* WindowPositionOptions from client position relative to a HTMLElement
|
155
|
-
* @param {number} x
|
156
|
-
* @param {number} y
|
157
|
-
* @param {
|
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]
|
158
187
|
* @returns {WindowPositionOptions}
|
159
188
|
*/
|
160
|
-
export function getWindowPositionOptions(x, y, element =
|
161
|
-
|
162
|
-
if (
|
163
|
-
|
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 };
|
164
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 };
|
202
|
+
}
|
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 };
|
216
|
+
}
|
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 };
|
237
|
+
}
|
238
|
+
|
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);
|
253
|
+
}
|
254
|
+
|
255
|
+
/**
|
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}
|
262
|
+
*/
|
263
|
+
export function getFittedWindowPositionOptionsFromMapEvent(windowPosition, width, height) {
|
264
|
+
const mapElement = getActiveMapElement();
|
265
|
+
if (!mapElement) {
|
266
|
+
return { left: windowPosition.x, top: windowPosition.y };
|
267
|
+
}
|
268
|
+
|
165
269
|
const { left, top } = mapElement.getBoundingClientRect();
|
166
|
-
return
|
270
|
+
return getFittedWindowPositionOptions(windowPosition.x + left, windowPosition.y + top, width, height, mapElement);
|
167
271
|
}
|
168
272
|
|
169
273
|
/**
|
@@ -185,7 +289,7 @@ export class WindowManager {
|
|
185
289
|
* reactive ordered array of ids,
|
186
290
|
* @type {Array<string>}
|
187
291
|
*/
|
188
|
-
this.componentIds =
|
292
|
+
this.componentIds = [];
|
189
293
|
|
190
294
|
/**
|
191
295
|
* @type {Map<string, WindowComponent>}
|
@@ -36,7 +36,7 @@
|
|
36
36
|
</template>
|
37
37
|
|
38
38
|
<script>
|
39
|
-
import { computed, inject, ref, onUnmounted } from '
|
39
|
+
import { computed, inject, ref, reactive, onUnmounted } from 'vue';
|
40
40
|
import { ObliqueMap, CesiumMap, OpenlayersMap } from '@vcmap/core';
|
41
41
|
import { unByKey } from 'ol/Observable.js';
|
42
42
|
import { createOverviewMapAction } from '../actions/actionHelper.js';
|
@@ -119,7 +119,7 @@
|
|
119
119
|
VcsCompass,
|
120
120
|
},
|
121
121
|
setup() {
|
122
|
-
/** @type {
|
122
|
+
/** @type {VcsUiApp} */
|
123
123
|
const app = inject('vcsApp');
|
124
124
|
const viewMode = ref(OrientationToolsViewMode.TWO_D);
|
125
125
|
const headingRef = ref(0);
|
@@ -179,9 +179,7 @@
|
|
179
179
|
isOblique: computed(() => viewMode.value === OrientationToolsViewMode.OBLIQUE),
|
180
180
|
zoomIn() { zoom(app.maps.activeMap); }, // debounce?
|
181
181
|
zoomOut() { zoom(app.maps.activeMap, true); },
|
182
|
-
|
183
|
-
right: () => {},
|
184
|
-
overviewAction: ref(action),
|
182
|
+
overviewAction: reactive(action),
|
185
183
|
};
|
186
184
|
},
|
187
185
|
};
|
@@ -20,7 +20,7 @@ import { unByKey } from 'ol/Observable.js';
|
|
20
20
|
import VectorSource from 'ol/source/Vector.js';
|
21
21
|
import { WindowSlot } from '../manager/window/windowManager.js';
|
22
22
|
import OverviewMapClickedInteraction from './overviewMapClickedInteraction.js';
|
23
|
-
import {
|
23
|
+
import { defaultPrimaryColor } from '../vuePlugins/vuetify.js';
|
24
24
|
import { vcsAppSymbol } from '../pluginHelper.js';
|
25
25
|
import VcsMap from '../application/VcsMap.vue';
|
26
26
|
|
@@ -99,8 +99,8 @@ class OverviewMap {
|
|
99
99
|
*/
|
100
100
|
this._obliqueSelectedImageLayer = null;
|
101
101
|
|
102
|
-
const
|
103
|
-
const fillColor = Color.fromCssColorString(
|
102
|
+
const primary = app.uiConfig.config.value.primaryColor ?? defaultPrimaryColor;
|
103
|
+
const fillColor = Color.fromCssColorString('#EDEDED');
|
104
104
|
|
105
105
|
/**
|
106
106
|
* @type {VectorStyleItem}
|
@@ -199,7 +199,7 @@ class OverviewMap {
|
|
199
199
|
* @type {Array<function():void>}
|
200
200
|
* @private
|
201
201
|
*/
|
202
|
-
this.
|
202
|
+
this._collectionListeners = [
|
203
203
|
this._app.maps.layerCollection.added.addEventListener((layer) => {
|
204
204
|
if (layer.properties.showInOverviewMap) {
|
205
205
|
const clone = deserializeLayer(this._app, layer.toJSON());
|
@@ -219,6 +219,16 @@ class OverviewMap {
|
|
219
219
|
this._map.layerCollection.remove(clone);
|
220
220
|
}
|
221
221
|
}),
|
222
|
+
this._app.uiConfig.added.addEventListener((item) => {
|
223
|
+
if (item?.name === 'primaryColor') {
|
224
|
+
this._setObliqueColor(item.value);
|
225
|
+
}
|
226
|
+
}),
|
227
|
+
this._app.uiConfig.removed.addEventListener((item) => {
|
228
|
+
if (item?.name === 'primaryColor') {
|
229
|
+
this._setObliqueColor(defaultPrimaryColor);
|
230
|
+
}
|
231
|
+
}),
|
222
232
|
];
|
223
233
|
}
|
224
234
|
|
@@ -254,6 +264,18 @@ class OverviewMap {
|
|
254
264
|
return this._mapClicked;
|
255
265
|
}
|
256
266
|
|
267
|
+
/**
|
268
|
+
* @param {string} color
|
269
|
+
* @private
|
270
|
+
*/
|
271
|
+
_setObliqueColor(color) {
|
272
|
+
this.obliqueUnselectedStyle?.stroke?.setColor(color);
|
273
|
+
this.obliqueSelectedStyle?.stroke?.setColor(color);
|
274
|
+
this._obliqueTileLayer?.forceRedraw?.();
|
275
|
+
this._obliqueImageLayer?.forceRedraw?.();
|
276
|
+
this._obliqueSelectedImageLayer?.forceRedraw?.();
|
277
|
+
}
|
278
|
+
|
257
279
|
/**
|
258
280
|
* @private
|
259
281
|
*/
|
@@ -574,7 +596,8 @@ class OverviewMap {
|
|
574
596
|
|
575
597
|
destroy() {
|
576
598
|
this._clearListeners();
|
577
|
-
this.
|
599
|
+
this._collectionListeners.forEach(cb => cb());
|
600
|
+
this._collectionListeners = [];
|
578
601
|
if (this._mapPointerListener) {
|
579
602
|
this._mapPointerListener();
|
580
603
|
this._mapPointerListener = null;
|
package/src/pluginHelper.js
CHANGED
@@ -19,6 +19,39 @@ export const vcsAppSymbol = Symbol('vcsApp');
|
|
19
19
|
*/
|
20
20
|
export const pluginFactorySymbol = Symbol('pluginFactory');
|
21
21
|
|
22
|
+
/**
|
23
|
+
* A symbol added to each plugin which describes the base URL from which the plugin was loaded (without the filename)
|
24
|
+
* @type {symbol}
|
25
|
+
*/
|
26
|
+
export const pluginBaseUrlSymbol = Symbol('pluginBaseUrl');
|
27
|
+
|
28
|
+
/**
|
29
|
+
* A helper function to create an absolute URL from a relative plugin asset URL. For example, when
|
30
|
+
* shipping your plugin with a "plugin-asset/icon.png", you can always retrieve said icon with getPluginAssetUrl(app, name, 'pluing-assets/icon.png')
|
31
|
+
* Returns null, if the plugin does not exist.
|
32
|
+
* @param {VcsUiApp} app
|
33
|
+
* @param {string} pluginName
|
34
|
+
* @param {string} asset
|
35
|
+
* @returns {string|null}
|
36
|
+
*/
|
37
|
+
export function getPluginAssetUrl(app, pluginName, asset) {
|
38
|
+
check(pluginName, String);
|
39
|
+
check(asset, String);
|
40
|
+
|
41
|
+
const plugin = app.plugins.getByKey(pluginName);
|
42
|
+
if (plugin && plugin[pluginBaseUrlSymbol]) {
|
43
|
+
const baseUrl = new URL(plugin[pluginBaseUrlSymbol]);
|
44
|
+
const assetUrl = new URL(asset.replace(/^\//, '/'), baseUrl);
|
45
|
+
baseUrl.searchParams.forEach((value, key) => {
|
46
|
+
if (!assetUrl.searchParams.has(key)) {
|
47
|
+
assetUrl.searchParams.set(key, value);
|
48
|
+
}
|
49
|
+
});
|
50
|
+
return assetUrl.toString();
|
51
|
+
}
|
52
|
+
return null;
|
53
|
+
}
|
54
|
+
|
22
55
|
/**
|
23
56
|
* validates the name according to package name pattern
|
24
57
|
* @param {string} name
|
@@ -32,18 +65,14 @@ export function isValidPackageName(name) {
|
|
32
65
|
}
|
33
66
|
|
34
67
|
/**
|
35
|
-
* @param {VcsUiApp} app
|
36
68
|
* @param {string} name
|
37
69
|
* @param {PluginConfig} config
|
38
|
-
* @param {string} [registry='https://plugins.virtualcitymap.de/']
|
39
70
|
* @returns {Promise<VcsPlugin|null>}
|
40
71
|
*/
|
41
|
-
export async function loadPlugin(
|
72
|
+
export async function loadPlugin(name, config) {
|
42
73
|
let module = config.entry;
|
43
74
|
|
44
|
-
if (
|
45
|
-
module = `${registry.replace(/\/?$/, '')}/${name}/${config.version || '*'}/index.js`;
|
46
|
-
} else if (!/^(https?:\/\/|\/)/.test(module)) {
|
75
|
+
if (!/^(https?:\/\/|\/)/.test(module)) {
|
47
76
|
module = `${window.location.origin}${window.location.pathname.replace(/\/?$/, '/')}${module}`;
|
48
77
|
} else if (module === '_dev') {
|
49
78
|
module = `/${name}.js`;
|
@@ -65,7 +94,9 @@ export async function loadPlugin(app, name, config, registry = 'https://plugins.
|
|
65
94
|
getLogger().error(`plugin ${name} does not provide a default exported function`);
|
66
95
|
return null;
|
67
96
|
}
|
68
|
-
const
|
97
|
+
const baseUrl = new URL(module);
|
98
|
+
baseUrl.pathname = baseUrl.pathname.replace(/\/[^/]+$/, '/');
|
99
|
+
const pluginInstance = await plugin.default(config, baseUrl.toString());
|
69
100
|
|
70
101
|
if (!pluginInstance.name) {
|
71
102
|
getLogger().error(`plugin ${name} does not expose a name`);
|
@@ -75,6 +106,7 @@ export async function loadPlugin(app, name, config, registry = 'https://plugins.
|
|
75
106
|
return null;
|
76
107
|
}
|
77
108
|
pluginInstance[pluginFactorySymbol] = plugin.default;
|
109
|
+
pluginInstance[pluginBaseUrlSymbol] = baseUrl.toString();
|
78
110
|
return pluginInstance;
|
79
111
|
} catch (err) {
|
80
112
|
getLogger().error(`failed to load plugin ${name}`);
|
@@ -89,9 +121,8 @@ export async function loadPlugin(app, name, config, registry = 'https://plugins.
|
|
89
121
|
*/
|
90
122
|
export function serializePlugin(plugin) {
|
91
123
|
const serializedPlugin = plugin.toJSON ? plugin.toJSON() : {};
|
92
|
-
|
93
|
-
|
94
|
-
}
|
124
|
+
serializedPlugin[pluginFactorySymbol] = plugin[pluginFactorySymbol];
|
125
|
+
serializedPlugin[pluginBaseUrlSymbol] = plugin[pluginBaseUrlSymbol];
|
95
126
|
return serializedPlugin;
|
96
127
|
}
|
97
128
|
|
@@ -102,5 +133,6 @@ export function serializePlugin(plugin) {
|
|
102
133
|
export async function deserializePlugin(serializedPlugin) {
|
103
134
|
const reincarnation = await serializedPlugin[pluginFactorySymbol](serializedPlugin);
|
104
135
|
reincarnation[pluginFactorySymbol] = serializedPlugin[pluginFactorySymbol];
|
136
|
+
reincarnation[pluginBaseUrlSymbol] = serializedPlugin[pluginBaseUrlSymbol];
|
105
137
|
return reincarnation;
|
106
138
|
}
|
package/src/setup.js
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
import Vue from 'vue';
|
2
|
-
import VueCompositionAPI from '@vue/composition-api';
|
3
2
|
|
4
3
|
// eslint-disable-next-line no-unused-vars
|
5
4
|
import * as core from '@vcmap/core';
|
6
5
|
// pull in entire core for vcsClassRegistry
|
7
6
|
|
8
7
|
Vue.config.productionTip = false;
|
9
|
-
Vue.use(VueCompositionAPI);
|
10
8
|
|
11
9
|
window.CESIUM_BASE_URL = '/node_modules/@vcmap/cesium/Source/';
|
package/src/state.js
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
import { check } from '@vcsuite/check';
|
2
|
+
import { getLogger } from '@vcsuite/logger';
|
3
|
+
import { ViewPoint } from '@vcmap/core';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @typedef {Object} LayerState
|
7
|
+
* @property {string} name
|
8
|
+
* @property {boolean} active
|
9
|
+
* @property {string} [styleName]
|
10
|
+
*/
|
11
|
+
|
12
|
+
/**
|
13
|
+
* The URL state of a layer is an array. The first entry is the layer name,
|
14
|
+
* the second its active state encoded in an integer (1 active, 0 inactive),
|
15
|
+
* the third and optional entry, is an optional styleName to set on the layer
|
16
|
+
* @typedef {[string,number,string|0]} UrlLayerState
|
17
|
+
*/
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @typedef {Object} PluginState
|
21
|
+
* @property {string} name
|
22
|
+
* @property {*} state
|
23
|
+
*/
|
24
|
+
|
25
|
+
/**
|
26
|
+
* The URL state of a plugin is an array. The first entry is the plugin name, the second entry is
|
27
|
+
* an encoded object, which is the plugins state.
|
28
|
+
* @typedef {[string, *]} UrlPluginState
|
29
|
+
*/
|
30
|
+
|
31
|
+
/**
|
32
|
+
* The URL state of a viewpoint is an array, the first entry is the camera position (or 0)
|
33
|
+
* the second is the ground position (or 0), the third is the distance, the last three are
|
34
|
+
* heading, pitch, roll in that order
|
35
|
+
* @typedef {[Array<number>|0,Array<number>|0,number,number,number,number]} UrlViewpointState
|
36
|
+
*/
|
37
|
+
|
38
|
+
/**
|
39
|
+
* @typedef {Object} AppState
|
40
|
+
* @property {import("@vcmap/core").ViewPointOptions} [activeViewpoint]
|
41
|
+
* @property {string} [activeMap]
|
42
|
+
* @property {Array<string>} contextIds
|
43
|
+
* @property {Array<LayerState>} layers
|
44
|
+
* @property {Array<PluginState>} plugins
|
45
|
+
* @property {string} [activeObliqueCollection]
|
46
|
+
*/
|
47
|
+
|
48
|
+
/**
|
49
|
+
* The URL state of the app is an array. To null parameters, pass in 0 instead.
|
50
|
+
* The first entry is the viewpoint state, the second the active map name
|
51
|
+
* The third is an array of contexts to apply the state to
|
52
|
+
* the fourth is an array of layer states
|
53
|
+
* the fifth is an array of plugin states
|
54
|
+
* the sixth is the currently active oblique collection or 0 if not applicable
|
55
|
+
* @typedef {[UrlViewpointState,string,Array<string>,Array<UrlLayerState>,Array<UrlPluginState>,(string|0)]} UrlAppState
|
56
|
+
*/
|
57
|
+
|
58
|
+
/**
|
59
|
+
* @type {number}
|
60
|
+
*/
|
61
|
+
const MAX_URL_LENGTH = 2048;
|
62
|
+
|
63
|
+
/**
|
64
|
+
* @returns {AppState}
|
65
|
+
*/
|
66
|
+
export function createEmptyState() {
|
67
|
+
return {
|
68
|
+
contextIds: [],
|
69
|
+
layers: [],
|
70
|
+
plugins: [],
|
71
|
+
};
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* @param {UrlViewpointState} state
|
76
|
+
* @returns {import("@vcmap/core").ViewPointOptions|null}
|
77
|
+
*/
|
78
|
+
function parseUrlViewpointState(state) {
|
79
|
+
const vp = new ViewPoint({
|
80
|
+
cameraPosition: state[0] ?? undefined,
|
81
|
+
groundPosition: state[1] ?? undefined,
|
82
|
+
distance: state[2] > 0 ? state[2] : undefined,
|
83
|
+
heading: state[3],
|
84
|
+
pitch: state[4],
|
85
|
+
roll: state[5],
|
86
|
+
});
|
87
|
+
|
88
|
+
return vp.isValid() ? vp.toJSON() : null;
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* @param {UrlLayerState} state
|
93
|
+
* @returns {LayerState}
|
94
|
+
*/
|
95
|
+
function parseUrlLayerState(state) {
|
96
|
+
const layerState = {
|
97
|
+
name: state[0],
|
98
|
+
active: !!state[1],
|
99
|
+
};
|
100
|
+
if (state[2] !== 0) {
|
101
|
+
layerState.styleName = state[2];
|
102
|
+
}
|
103
|
+
return layerState;
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* @param {LayerState} state
|
108
|
+
* @returns {UrlLayerState}
|
109
|
+
*/
|
110
|
+
function writeUrlLayerState(state) {
|
111
|
+
return [state.name, state.active ? 1 : 0, state.styleName ?? 0];
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* @param {UrlPluginState} state
|
116
|
+
* @returns {PluginState}
|
117
|
+
*/
|
118
|
+
function parseUrlPluginState(state) {
|
119
|
+
return {
|
120
|
+
name: state[0],
|
121
|
+
state: state[1],
|
122
|
+
};
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* @param {PluginState} state
|
127
|
+
* @returns {UrlPluginState}
|
128
|
+
*/
|
129
|
+
function writeUrlPluginState(state) {
|
130
|
+
return [state.name, state.state];
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* @param {UrlAppState} urlState
|
135
|
+
* @returns {AppState}
|
136
|
+
*/
|
137
|
+
function parseUrlAppState(urlState) {
|
138
|
+
const state = createEmptyState();
|
139
|
+
if (Array.isArray(urlState[0])) {
|
140
|
+
state.activeViewpoint = parseUrlViewpointState(urlState[0]);
|
141
|
+
}
|
142
|
+
if (typeof urlState[1] === 'string') {
|
143
|
+
state.activeMap = urlState[1];
|
144
|
+
}
|
145
|
+
if (Array.isArray(urlState[2])) {
|
146
|
+
state.contextIds = urlState[2].slice();
|
147
|
+
}
|
148
|
+
if (Array.isArray(urlState[3])) {
|
149
|
+
urlState[3].forEach((urlLayerState) => {
|
150
|
+
if (Array.isArray(urlLayerState)) {
|
151
|
+
state.layers.push(parseUrlLayerState(urlLayerState));
|
152
|
+
}
|
153
|
+
});
|
154
|
+
}
|
155
|
+
if (Array.isArray(urlState[4])) {
|
156
|
+
urlState[4].forEach((urlPluginState) => {
|
157
|
+
if (Array.isArray(urlPluginState)) {
|
158
|
+
state.plugins.push(parseUrlPluginState(urlPluginState));
|
159
|
+
}
|
160
|
+
});
|
161
|
+
}
|
162
|
+
if (typeof urlState[5] === 'string') {
|
163
|
+
state.activeObliqueCollection = urlState[5];
|
164
|
+
}
|
165
|
+
return state;
|
166
|
+
}
|
167
|
+
|
168
|
+
/**
|
169
|
+
* @param {AppState} state
|
170
|
+
* @param {number} maxLength
|
171
|
+
* @returns {UrlAppState}
|
172
|
+
*/
|
173
|
+
function writeUrlAppState(state, maxLength) {
|
174
|
+
/**
|
175
|
+
* @type {UrlAppState}
|
176
|
+
*/
|
177
|
+
const urlState = new Array(6).fill(0);
|
178
|
+
if (state.activeViewpoint) {
|
179
|
+
urlState[0] = [
|
180
|
+
state.activeViewpoint.cameraPosition?.slice() ?? 0,
|
181
|
+
state.activeViewpoint.groundPosition?.slice() ?? 0,
|
182
|
+
state.activeViewpoint.distance ?? 0,
|
183
|
+
state.activeViewpoint.heading,
|
184
|
+
state.activeViewpoint.pitch,
|
185
|
+
state.activeViewpoint.roll,
|
186
|
+
];
|
187
|
+
}
|
188
|
+
|
189
|
+
if (state.activeMap) {
|
190
|
+
urlState[1] = state.activeMap;
|
191
|
+
}
|
192
|
+
|
193
|
+
urlState[2] = state.contextIds.slice();
|
194
|
+
urlState[3] = [];
|
195
|
+
urlState[4] = [];
|
196
|
+
|
197
|
+
if (state.activeObliqueCollection) {
|
198
|
+
urlState[5] = state.activeObliqueCollection;
|
199
|
+
}
|
200
|
+
|
201
|
+
state.layers.forEach((layerState) => {
|
202
|
+
const layerUrlState = writeUrlLayerState(layerState);
|
203
|
+
if ((JSON.stringify(urlState).length + JSON.stringify(layerUrlState).length) < maxLength) {
|
204
|
+
urlState[3].push(layerUrlState);
|
205
|
+
}
|
206
|
+
});
|
207
|
+
|
208
|
+
state.plugins.forEach((pluginState) => {
|
209
|
+
const urlPluginState = writeUrlPluginState(pluginState);
|
210
|
+
if ((JSON.stringify(urlState).length + JSON.stringify(urlPluginState).length) < maxLength) {
|
211
|
+
urlState[4].push(urlPluginState);
|
212
|
+
}
|
213
|
+
});
|
214
|
+
|
215
|
+
if (urlState[3].length !== state.layers.length || urlState[4].length !== state.plugins.length) {
|
216
|
+
getLogger('StateManagement').warning('State too large for URL: Not all layers and plugins are represented');
|
217
|
+
}
|
218
|
+
|
219
|
+
return urlState;
|
220
|
+
}
|
221
|
+
|
222
|
+
/**
|
223
|
+
* @param {(URL)=} url
|
224
|
+
* @returns {AppState}
|
225
|
+
*/
|
226
|
+
export function getStateFromURL(url) {
|
227
|
+
check(url, URL);
|
228
|
+
|
229
|
+
if (url.searchParams.has('state')) {
|
230
|
+
try {
|
231
|
+
return parseUrlAppState(JSON.parse(url.searchParams.get('state')));
|
232
|
+
} catch (e) {
|
233
|
+
getLogger('StateManager').error('failed to parse the state URL parameter');
|
234
|
+
}
|
235
|
+
}
|
236
|
+
return createEmptyState();
|
237
|
+
}
|
238
|
+
|
239
|
+
/**
|
240
|
+
* @param {AppState} state
|
241
|
+
* @param {URL} url - sets the query parameter "state" on this URL
|
242
|
+
*/
|
243
|
+
export function setStateToUrl(state, url) {
|
244
|
+
check(state, {
|
245
|
+
activeMap: [String, undefined],
|
246
|
+
activeViewpoint: [Object, undefined],
|
247
|
+
activeObliqueCollection: [String, undefined],
|
248
|
+
layers: Array,
|
249
|
+
plugins: Array,
|
250
|
+
contextIds: [String],
|
251
|
+
});
|
252
|
+
check(url, URL);
|
253
|
+
|
254
|
+
const maxLength = MAX_URL_LENGTH - url.toString().length;
|
255
|
+
url.searchParams.set('state', JSON.stringify(writeUrlAppState(state, maxLength)));
|
256
|
+
}
|