@vcmap/ui 5.0.0-rc.28 → 5.0.0-rc.30
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/config/base.config.json +22 -0
- package/config/dev.config.json +4 -0
- package/dist/assets/cesium/Workers/cesiumWorkerBootstrapper.js +1 -1
- package/dist/assets/cesium/Workers/package.js +1 -1
- package/dist/assets/cesium/Workers/transferTypedArrayTest.js +1 -1
- package/dist/assets/{cesium.973919.js → cesium.eaf7cc.js} +149552 -149560
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/{core.7a2173.js → core.b16511.js} +4077 -3965
- package/dist/assets/core.js +1 -1
- package/dist/assets/index-c115e3a1.js +1 -0
- package/dist/assets/{ol.f6e2e4.js → ol.4bbf0f.js} +11405 -11126
- package/dist/assets/ol.js +1 -1
- package/dist/assets/{ui.bd7a9a.css → ui.ab815e.css} +2 -2
- package/dist/assets/{ui.bd7a9a.js → ui.ab815e.js} +4232 -3512
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.js +2 -2
- package/dist/assets/{vuetify.95f6c3.js → vuetify.ea3fa8.js} +1 -1
- package/dist/assets/vuetify.js +2 -2
- package/dist/index.html +1 -1
- package/index.js +7 -0
- package/lib/olLib.js +6 -0
- package/package.json +3 -3
- package/plugins/@vcmap/search-nominatim/SearchNominatimEditor.vue +90 -0
- package/plugins/@vcmap/search-nominatim/index.js +37 -0
- package/plugins/@vcmap-show-case/form-inputs-example/FormInputsExample.vue +37 -1
- package/plugins/@vcmap-show-case/form-inputs-example/index.js +3 -0
- package/plugins/@vcmap-show-case/form-inputs-example/validation.js +11 -0
- package/plugins/@vcmap-show-case/style-input-example/styleExample.vue +0 -1
- package/plugins/@vcmap-show-case/vector-properties-example/index.js +40 -0
- package/plugins/@vcmap-show-case/vector-properties-example/package.json +5 -0
- package/plugins/@vcmap-show-case/vector-properties-example/vectorPropertiesExample.vue +109 -0
- package/plugins/@vcmap-show-case/window-tester/WindowExample.vue +27 -9
- package/plugins/@vcmap-show-case/window-tester/index.js +13 -1
- package/plugins/@vcmap-show-case/window-tester/windowExampleToggleChild.vue +11 -10
- package/src/actions/actionHelper.js +7 -3
- package/src/application/VcsApp.vue +31 -0
- package/src/components/form-inputs-controls/VcsChipArrayInput.vue +282 -0
- package/src/components/form-inputs-controls/VcsTextField.vue +12 -5
- package/src/components/icons/+all.js +3 -3
- package/src/components/lists/VcsTreeviewLeaf.vue +1 -1
- package/src/components/lists/VcsTreeviewSearchbar.vue +9 -4
- package/src/components/plugins/AbstractConfigEditor.vue +84 -0
- package/src/components/style/VcsImageSelector.vue +6 -5
- package/src/components/style/VcsTextSelector.vue +1 -1
- package/src/components/tables/VcsDataTable.vue +100 -13
- package/src/components/vector-properties/VcsVectorPropertiesComponent.vue +737 -0
- package/src/components/vector-properties/composables.js +93 -0
- package/src/contentTree/contentTreeCollection.js +3 -0
- package/src/featureInfo/abstractFeatureInfoView.js +3 -1
- package/src/featureInfo/balloonFeatureInfoView.js +3 -2
- package/src/featureInfo/featureInfo.js +1 -0
- package/src/i18n/de.js +44 -9
- package/src/i18n/en.js +42 -7
- package/src/manager/collectionManager/collectionComponent.js +1 -1
- package/src/manager/window/WindowComponent.vue +4 -1
- package/src/manager/window/WindowComponentHeader.vue +25 -13
- package/src/manager/window/windowManager.js +6 -2
- package/src/navigation/overviewMap.js +1 -1
- package/src/pluginHelper.js +57 -17
- package/src/uiConfig.js +1 -0
- package/src/vcsUiApp.js +45 -34
- package/dist/assets/index-1b09f88d.js +0 -1
- /package/dist/assets/{vue.d4be99.js → vue.67e80f.js} +0 -0
- /package/dist/assets/{vuetify.95f6c3.css → vuetify.ea3fa8.css} +0 -0
- /package/src/components/icons/{PolygonIcon.vue → PointIcon.vue} +0 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
import { computed } from 'vue';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Creates a computed property with a getter and a setter for a specific primitive object property/key of the modelled object. This composable allows to pass the computed to an v-model of a sub component. The getter returns the value of the key. The setter merges the passed value with the other object properties of the modelled object and emits the whole object as a input event, and only the changed props as an change event.
|
5
|
+
* @param {function():Object} modelObject Getter for the property that is modelled by the component and should be updated with input event, usually this is the 'value' prop.
|
6
|
+
* @param {string} key The key of the modelObject that should be returned on get and updated on set.
|
7
|
+
* @param {function(event: string, ...args: any[]):void} emit The emit function of the component context that is using this composable.
|
8
|
+
* @returns {import('vue').WriteableComputedRef} A computed with a getter and a setter.
|
9
|
+
*/
|
10
|
+
export function usePrimitiveProperty(modelObject, key, emit) {
|
11
|
+
return computed({
|
12
|
+
get() {
|
13
|
+
return modelObject()[key];
|
14
|
+
},
|
15
|
+
set(value) {
|
16
|
+
if (modelObject()[key] !== value) {
|
17
|
+
const newParams = structuredClone(modelObject());
|
18
|
+
const changedParams = { [key]: value || undefined }; // XXX boolean values need to be handled differently
|
19
|
+
emit('input', Object.assign(newParams, changedParams));
|
20
|
+
emit('propertyChange', changedParams);
|
21
|
+
}
|
22
|
+
},
|
23
|
+
});
|
24
|
+
}
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Creates an object with the entries of the input array as keys and computed properties as values. These have a getter and a setter for a specific entry of the array, that is an object property/key of the modelled object. This composable allows to pass the computeds to an v-model of a sub component. The getter returns the value of the array entry. The setter first merges the passed value with the other array entries, and then with the other object properties of the modelled object and emits the whole object as a input event, and only the changed props as an change event.
|
28
|
+
* @param {function():Object} modelObject Getter for the property that is modelled by the component and should be updated with input event, usually this is the 'value' prop.
|
29
|
+
* @param {string} key The key of the modelObject that should be returned on get and updated on set.
|
30
|
+
* @param {function(event: string, ...args: any[]):void} emit The emit function of the component context that is using this composable.
|
31
|
+
* @param {number} arrayLength The length of the array property.
|
32
|
+
* @returns {Object<string, import('vue').WriteableComputedRef>} An object with the provided names as keys and the corresponding computed properties with a getter and a setter.
|
33
|
+
*/
|
34
|
+
export function useArrayProperty(modelObject, key, emit, arrayLength) {
|
35
|
+
return Array(arrayLength)
|
36
|
+
.fill()
|
37
|
+
.map((entry, index) => {
|
38
|
+
return computed({
|
39
|
+
get() {
|
40
|
+
if (modelObject()?.[key]) {
|
41
|
+
return modelObject()[key][index];
|
42
|
+
} else {
|
43
|
+
return undefined;
|
44
|
+
}
|
45
|
+
},
|
46
|
+
set(value) {
|
47
|
+
const newParams = structuredClone(modelObject());
|
48
|
+
let changedParams;
|
49
|
+
if (!modelObject()?.[key]) {
|
50
|
+
const newArray = Array(arrayLength).fill();
|
51
|
+
newArray[index] = value;
|
52
|
+
changedParams = { [key]: newArray };
|
53
|
+
} else if (modelObject()[key][index] !== value) {
|
54
|
+
const newArray = [...modelObject()[key]];
|
55
|
+
newArray[index] = value;
|
56
|
+
changedParams = { [key]: newArray };
|
57
|
+
}
|
58
|
+
|
59
|
+
if (changedParams) {
|
60
|
+
emit('input', Object.assign(newParams, changedParams));
|
61
|
+
emit('propertyChange', changedParams);
|
62
|
+
}
|
63
|
+
},
|
64
|
+
});
|
65
|
+
});
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Creates a computed property that returns whether a specific key of the modelled object (value prop) is undefined or not. If set to true, the provided default value is applied to the key of the modelled object.
|
70
|
+
* @param {function():Object} modelObject Getter for the property that is modelled by the component and should be updated with input event, usually this is the 'value' prop.
|
71
|
+
* @param {string} key The key of the modelObject that should be return on get and updated on set.
|
72
|
+
* @param {function(event: string, ...args: any[]):void} emit The emit function of the component context that is using this composable.
|
73
|
+
* @param {*} valueDefault The defualt value that is set when `true` is provided to the setter of the computed property.
|
74
|
+
* @returns {import('vue').WriteableComputedRef} A computed ref with a setter and a getter.
|
75
|
+
*/
|
76
|
+
export function useHasProperty(modelObject, key, emit, valueDefault) {
|
77
|
+
return computed({
|
78
|
+
get() {
|
79
|
+
return modelObject()?.[key] !== undefined;
|
80
|
+
},
|
81
|
+
set(value) {
|
82
|
+
const newParams = structuredClone(modelObject());
|
83
|
+
let changedParams;
|
84
|
+
if (value) {
|
85
|
+
changedParams = { [key]: valueDefault };
|
86
|
+
} else {
|
87
|
+
changedParams = { [key]: undefined };
|
88
|
+
}
|
89
|
+
emit('input', Object.assign(newParams, changedParams));
|
90
|
+
emit('propertyChange', changedParams);
|
91
|
+
},
|
92
|
+
});
|
93
|
+
}
|
@@ -139,6 +139,9 @@ class ContentTreeCollection extends IndexedCollection {
|
|
139
139
|
state: {
|
140
140
|
headerIcon: subTreeViewItem.icon,
|
141
141
|
headerTitle: subTreeViewItem.title,
|
142
|
+
infoUrlCallback: app.getHelpUrlCallback(
|
143
|
+
'/components/contentspace.html#id_content',
|
144
|
+
),
|
142
145
|
},
|
143
146
|
},
|
144
147
|
app.windowManager,
|
@@ -303,15 +303,17 @@ class AbstractFeatureInfoView extends VcsObject {
|
|
303
303
|
/**
|
304
304
|
* This method is being called by featureInfo, whenever a new window is created (added to the windowManager).
|
305
305
|
* May be overwritten by classes extending AbstractFeatureInfoView.
|
306
|
+
* @param {VcsUiApp} app
|
306
307
|
* @param {FeatureInfoEvent} featureInfo
|
307
308
|
* @param {import("@vcmap/core").Layer} layer
|
308
309
|
* @returns {WindowComponentOptions}
|
309
310
|
*/
|
310
|
-
getWindowComponentOptions(featureInfo, layer) {
|
311
|
+
getWindowComponentOptions(app, featureInfo, layer) {
|
311
312
|
return {
|
312
313
|
state: this.window.state ?? {
|
313
314
|
headerTitle: layer.properties?.title || layer.name,
|
314
315
|
headerIcon: '$vcsInfo',
|
316
|
+
infoUrl: app.getHelpUrlCallback('/tools/infoTool.html'),
|
315
317
|
},
|
316
318
|
slot: this.window.slot ?? WindowSlot.DYNAMIC_LEFT,
|
317
319
|
component: this.component,
|
@@ -127,12 +127,13 @@ class BalloonFeatureInfoView extends AbstractFeatureInfoView {
|
|
127
127
|
}
|
128
128
|
|
129
129
|
/**
|
130
|
+
* @param {VcsUiApp} app
|
130
131
|
* @param {FeatureInfoEvent} featureInfo
|
131
132
|
* @param {import("@vcmap/core").Layer} layer
|
132
133
|
* @returns {WindowComponentOptions}
|
133
134
|
*/
|
134
|
-
getWindowComponentOptions(featureInfo, layer) {
|
135
|
-
const options = super.getWindowComponentOptions(featureInfo, layer);
|
135
|
+
getWindowComponentOptions(app, featureInfo, layer) {
|
136
|
+
const options = super.getWindowComponentOptions(app, featureInfo, layer);
|
136
137
|
options.state.hideHeader = true;
|
137
138
|
options.state.classes = ['balloon'];
|
138
139
|
options.slot = WindowSlot.DETACHED;
|
package/src/i18n/de.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
const messages = {
|
2
2
|
navbar: {
|
3
3
|
maps: {
|
4
|
-
CesiumMap: '3D
|
5
|
-
OpenlayersMap: '2D
|
6
|
-
ObliqueMap: '
|
4
|
+
CesiumMap: '3D-Karte aktivieren',
|
5
|
+
OpenlayersMap: '2D-Karte aktivieren',
|
6
|
+
ObliqueMap: 'Schrägluftbildkarte aktivieren',
|
7
7
|
},
|
8
8
|
menu: {
|
9
9
|
tooltip: 'Menü',
|
@@ -69,8 +69,8 @@ const messages = {
|
|
69
69
|
noResultsPlaceholder: 'Keine übereinstimmenden Einträge gefunden',
|
70
70
|
},
|
71
71
|
style: {
|
72
|
-
fill: '
|
73
|
-
stroke: '
|
72
|
+
fill: 'Füllung',
|
73
|
+
stroke: 'Linie',
|
74
74
|
reset: 'Zurücksetzen',
|
75
75
|
lineWidth: 'Linienbreite',
|
76
76
|
type: 'Typ',
|
@@ -81,7 +81,7 @@ const messages = {
|
|
81
81
|
rotation: 'Rotation',
|
82
82
|
scale: 'Skalierung',
|
83
83
|
opacity: 'Deckkraft',
|
84
|
-
image: '
|
84
|
+
image: 'Symbol',
|
85
85
|
icon: 'Icon',
|
86
86
|
presets: 'Vorlagen',
|
87
87
|
shape: 'Form',
|
@@ -97,11 +97,37 @@ const messages = {
|
|
97
97
|
italic: 'Kursiv',
|
98
98
|
text: 'Text',
|
99
99
|
enterText: 'Text hier eingeben',
|
100
|
-
allowedRange: 'Erlaubter Wertebereich',
|
101
|
-
notValid: 'Eingabe nicht gültig',
|
102
|
-
required: 'Eingabe ist erforderlich',
|
103
100
|
offset: 'Versatz',
|
104
101
|
},
|
102
|
+
vectorProperties: {
|
103
|
+
header: 'Vektor Eigenschaften',
|
104
|
+
altitudeMode: 'Höhenmodus',
|
105
|
+
clampToGround: 'Auf Gelände legen',
|
106
|
+
absolute: 'Absolut',
|
107
|
+
relativeToGround: 'Relativ zum Gelände',
|
108
|
+
groundLevel: 'Geländehöhe',
|
109
|
+
classificationType: 'Klassifizierung',
|
110
|
+
none: 'Keine',
|
111
|
+
both: 'Beide',
|
112
|
+
cesium3DTile: '3D Tiles',
|
113
|
+
terrain: 'Gelände',
|
114
|
+
heightAboveGround: 'Höhe über Gelände',
|
115
|
+
allowPicking: 'Auswahl erlauben',
|
116
|
+
scaleByDistance: 'Distanzskalierung',
|
117
|
+
eyeOffset: 'Blick Versatz',
|
118
|
+
storeys: 'Stockwerke',
|
119
|
+
storeyHeights: 'Stockwerkshöhe(n)',
|
120
|
+
aboveGround: 'Über Grund',
|
121
|
+
belowGround: 'Unter Grund',
|
122
|
+
modelUrl: 'Modell URL',
|
123
|
+
modelHeading: 'Modell Ausrichtung',
|
124
|
+
modelPitch: 'Modell Neigung',
|
125
|
+
modelRoll: 'Modell Rotation',
|
126
|
+
baseUrl: 'Basis URL',
|
127
|
+
extrudedHeight: 'Extrusion',
|
128
|
+
skirt: 'Skirts',
|
129
|
+
modelScale: 'Modell Skalierung',
|
130
|
+
},
|
105
131
|
},
|
106
132
|
settings: {
|
107
133
|
title: 'Einstellungen',
|
@@ -113,6 +139,15 @@ const messages = {
|
|
113
139
|
light: 'Hell',
|
114
140
|
},
|
115
141
|
},
|
142
|
+
help: {
|
143
|
+
title: 'Hilfe',
|
144
|
+
tooltip: 'Externe Hilfeseite in neuem Browser Tab öffnen',
|
145
|
+
},
|
146
|
+
validation: {
|
147
|
+
allowedRange: 'Erlaubter Wertebereich',
|
148
|
+
notValid: 'Eingabe nicht gültig',
|
149
|
+
required: 'Eingabe ist erforderlich',
|
150
|
+
},
|
116
151
|
featureInfo: {
|
117
152
|
activateToolTitle: 'Informationswerkzeug aktivieren',
|
118
153
|
deactivateToolTitle: 'Informationswerkzeug deaktivieren',
|
package/src/i18n/en.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
const messages = {
|
2
2
|
navbar: {
|
3
3
|
maps: {
|
4
|
-
CesiumMap: 'Enable 3D
|
5
|
-
OpenlayersMap: 'Enable 2D
|
6
|
-
ObliqueMap: 'Enable oblique
|
4
|
+
CesiumMap: 'Enable 3D map',
|
5
|
+
OpenlayersMap: 'Enable 2D map',
|
6
|
+
ObliqueMap: 'Enable oblique imagery map',
|
7
7
|
},
|
8
8
|
menu: {
|
9
9
|
tooltip: 'Menu',
|
@@ -81,7 +81,7 @@ const messages = {
|
|
81
81
|
rotation: 'Rotation',
|
82
82
|
scale: 'Scale',
|
83
83
|
opacity: 'Opacity',
|
84
|
-
image: '
|
84
|
+
image: 'Symbol',
|
85
85
|
icon: 'Icon',
|
86
86
|
shape: 'Shape',
|
87
87
|
presets: 'Presets',
|
@@ -97,11 +97,37 @@ const messages = {
|
|
97
97
|
italic: 'Italic',
|
98
98
|
text: 'Text',
|
99
99
|
enterText: 'Enter text here',
|
100
|
-
allowedRange: 'Allowed value range',
|
101
|
-
notValid: 'Input not valid',
|
102
|
-
required: 'Input is required',
|
103
100
|
offset: 'Offset',
|
104
101
|
},
|
102
|
+
vectorProperties: {
|
103
|
+
header: 'Vector properties',
|
104
|
+
altitudeMode: 'Altitude mode',
|
105
|
+
clampToGround: 'Clamp to ground',
|
106
|
+
absolute: 'Absolute',
|
107
|
+
relativeToGround: 'Relative to ground',
|
108
|
+
groundLevel: 'Ground level',
|
109
|
+
classificationType: 'Classification',
|
110
|
+
none: 'None',
|
111
|
+
both: 'Both',
|
112
|
+
cesium3DTile: '3D Tiles',
|
113
|
+
terrain: 'Terrain',
|
114
|
+
heightAboveGround: 'Height above ground',
|
115
|
+
allowPicking: 'Allow picking',
|
116
|
+
scaleByDistance: 'Scale by distance',
|
117
|
+
eyeOffset: 'Eye offset',
|
118
|
+
storeys: 'Storeys',
|
119
|
+
storeyHeights: 'Storey height(s)',
|
120
|
+
aboveGround: 'Above ground',
|
121
|
+
belowGround: 'Below ground',
|
122
|
+
modelUrl: 'Model URL',
|
123
|
+
modelHeading: 'Model heading',
|
124
|
+
modelPitch: 'Model pitch',
|
125
|
+
modelRoll: 'Model roll',
|
126
|
+
baseUrl: 'Base URL',
|
127
|
+
extrudedHeight: 'Extrusion',
|
128
|
+
skirt: 'Skirts',
|
129
|
+
modelScale: 'Model scale',
|
130
|
+
},
|
105
131
|
},
|
106
132
|
settings: {
|
107
133
|
title: 'Settings',
|
@@ -113,6 +139,15 @@ const messages = {
|
|
113
139
|
light: 'Light',
|
114
140
|
},
|
115
141
|
},
|
142
|
+
help: {
|
143
|
+
title: 'Help',
|
144
|
+
tooltip: 'Open external help page in new browser tab',
|
145
|
+
},
|
146
|
+
validation: {
|
147
|
+
allowedRange: 'Allowed value range',
|
148
|
+
notValid: 'Input not valid',
|
149
|
+
required: 'Input is required',
|
150
|
+
},
|
116
151
|
featureInfo: {
|
117
152
|
activateToolTitle: 'Enable Info Tool',
|
118
153
|
deactivateToolTitle: 'Disable Info Tool',
|
@@ -243,7 +243,7 @@ class CollectionComponent {
|
|
243
243
|
* @private
|
244
244
|
*/
|
245
245
|
_insertListItem(listItem) {
|
246
|
-
if (!this._listItems.value.
|
246
|
+
if (!this._listItems.value.some((i) => i.name === listItem.name)) {
|
247
247
|
if (this._collection instanceof IndexedCollection) {
|
248
248
|
const newItemIndex = this._collection.indexOfKey(listItem.name);
|
249
249
|
if (newItemIndex === this._collection.size - 1) {
|
@@ -8,7 +8,7 @@
|
|
8
8
|
:draggable="isDraggable"
|
9
9
|
:class="{
|
10
10
|
rounded: !isDocked,
|
11
|
-
marginToTop: isDocked,
|
11
|
+
marginToTop: isDocked || !isChild,
|
12
12
|
'rounded-br': isDynamicLeft,
|
13
13
|
'rounded-bl': isDynamicRight,
|
14
14
|
}"
|
@@ -20,6 +20,7 @@
|
|
20
20
|
class="pa-2"
|
21
21
|
:class="{
|
22
22
|
'cursor-grab': isDynamic,
|
23
|
+
child: isChild,
|
23
24
|
}"
|
24
25
|
>
|
25
26
|
<slot name="headerComponent" :props="$attrs" />
|
@@ -86,6 +87,7 @@
|
|
86
87
|
provide(key, value);
|
87
88
|
});
|
88
89
|
|
90
|
+
const isChild = computed(() => !!props.windowState.parentId);
|
89
91
|
const isDynamic = computed(() => props.slotWindow !== WindowSlot.STATIC);
|
90
92
|
const isDocked = computed(() => props.slotWindow !== WindowSlot.DETACHED);
|
91
93
|
const isDockedLeft = computed(() => {
|
@@ -144,6 +146,7 @@
|
|
144
146
|
|
145
147
|
return {
|
146
148
|
isDynamic,
|
149
|
+
isChild,
|
147
150
|
isDocked,
|
148
151
|
isDynamicLeft: isDockedLeft,
|
149
152
|
isDynamicRight: isDockedRight,
|
@@ -15,7 +15,7 @@
|
|
15
15
|
class="d-inline-block user-select-none font-weight-bold"
|
16
16
|
:class="{ 'text--primary': isOnTop }"
|
17
17
|
>
|
18
|
-
{{
|
18
|
+
{{ translatedHeaderTitle }}
|
19
19
|
</span>
|
20
20
|
</h3>
|
21
21
|
<div class="d-flex justify-space-between align-center">
|
@@ -27,7 +27,7 @@
|
|
27
27
|
<v-divider vertical inset class="mx-1" />
|
28
28
|
</template>
|
29
29
|
<VcsButton
|
30
|
-
v-if="
|
30
|
+
v-if="infoAction"
|
31
31
|
@click.stop="infoAction.callback()"
|
32
32
|
:icon="infoAction.icon"
|
33
33
|
:tooltip="infoAction.title"
|
@@ -65,7 +65,7 @@
|
|
65
65
|
|
66
66
|
<script>
|
67
67
|
import { VIcon, VDivider } from 'vuetify/lib';
|
68
|
-
import { computed } from 'vue';
|
68
|
+
import { computed, getCurrentInstance } from 'vue';
|
69
69
|
import VcsButton from '../../components/buttons/VcsButton.vue';
|
70
70
|
import VcsActionButtonList from '../../components/buttons/VcsActionButtonList.vue';
|
71
71
|
import { createLinkAction } from '../../actions/actionHelper.js';
|
@@ -75,6 +75,8 @@
|
|
75
75
|
* @vue-prop {WindowState} windowState - state of the window component.
|
76
76
|
* @vue-event {void} pin - raised when pin button is clicked
|
77
77
|
* @vue-event {void} close - raised when close button is clicked
|
78
|
+
* @vue-computed {boolean} isDockable
|
79
|
+
* @vue-computed {string} translatedHeaderTitle - translates header title and joins array
|
78
80
|
*/
|
79
81
|
export default {
|
80
82
|
name: 'WindowComponentHeader',
|
@@ -110,21 +112,31 @@
|
|
110
112
|
() => !props.windowState.hidePin && props.windowState.dockable,
|
111
113
|
);
|
112
114
|
|
113
|
-
const
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
115
|
+
const vm = getCurrentInstance().proxy;
|
116
|
+
|
117
|
+
const translatedHeaderTitle = computed(() =>
|
118
|
+
Array.isArray(props.windowState.headerTitle)
|
119
|
+
? props.windowState.headerTitle.map((t) => vm.$t(t)).join(' ')
|
120
|
+
: vm.$t(props.windowState.headerTitle),
|
121
|
+
);
|
122
|
+
|
123
|
+
const infoAction =
|
124
|
+
props.windowState.infoUrl || props.windowState.infoUrlCallback
|
125
|
+
? createLinkAction(
|
126
|
+
{
|
127
|
+
name: 'info',
|
128
|
+
title: 'content.infoAction.title',
|
129
|
+
icon: '$vcsHelp',
|
130
|
+
},
|
131
|
+
props.windowState.infoUrl || props.windowState.infoUrlCallback,
|
132
|
+
)
|
133
|
+
: undefined;
|
123
134
|
|
124
135
|
return {
|
125
136
|
pin,
|
126
137
|
close,
|
127
138
|
isDockable,
|
139
|
+
translatedHeaderTitle,
|
128
140
|
infoAction,
|
129
141
|
};
|
130
142
|
},
|
@@ -120,11 +120,12 @@ export function isSlotPosition(windowPosition) {
|
|
120
120
|
* @property {string|vcsAppSymbol} owner Owner of the window, set by windowManager on add
|
121
121
|
* @property {boolean} [hideHeader] be used to not show the header.
|
122
122
|
* @property {boolean} [hidePin] be used to not show the pin button.
|
123
|
-
* @property {string} [headerTitle]
|
123
|
+
* @property {string|string[]} [headerTitle] An optional translatable header. If an array is provided all elements are translated and joined afterward.
|
124
124
|
* @property {string} [headerIcon]
|
125
125
|
* @property {Array<VcsAction>} [headerActions]
|
126
126
|
* @property {number} [headerActionsOverflow]
|
127
127
|
* @property {string} [infoUrl] An optional url referencing help or further information on the window's content.
|
128
|
+
* @property {function():string} [infoUrlCallback] An optional function returning an url referencing help or further information. Can be used for urls depending on the app's locale, e.g. app.getHelpUrl()
|
128
129
|
* @property {boolean} [dockable] Auto derived from hidePin, current slot, current position and initial position.
|
129
130
|
* @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
|
130
131
|
* @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
|
@@ -466,6 +467,7 @@ class WindowManager {
|
|
466
467
|
);
|
467
468
|
}
|
468
469
|
const id = windowComponentOptions.id || uuidv4();
|
470
|
+
const parentId = windowComponentOptions?.parentId;
|
469
471
|
const slotOption =
|
470
472
|
windowComponentOptions.slot?.value || windowComponentOptions.slot;
|
471
473
|
const slot = parseEnumValue(slotOption, WindowSlot, WindowSlot.DETACHED);
|
@@ -492,6 +494,7 @@ class WindowManager {
|
|
492
494
|
|
493
495
|
const state = reactive({
|
494
496
|
id,
|
497
|
+
parentId,
|
495
498
|
owner,
|
496
499
|
hideHeader: !!windowComponentOptions?.state?.hideHeader,
|
497
500
|
hidePin: !!windowComponentOptions?.state?.hidePin,
|
@@ -502,6 +505,7 @@ class WindowManager {
|
|
502
505
|
windowComponentOptions?.state?.headerActionsOverflow,
|
503
506
|
dockable: false,
|
504
507
|
infoUrl: windowComponentOptions?.state?.infoUrl,
|
508
|
+
infoUrlCallback: windowComponentOptions?.state?.infoUrlCallback,
|
505
509
|
classes,
|
506
510
|
styles,
|
507
511
|
});
|
@@ -520,7 +524,7 @@ class WindowManager {
|
|
520
524
|
return id;
|
521
525
|
},
|
522
526
|
get parentId() {
|
523
|
-
return
|
527
|
+
return parentId;
|
524
528
|
},
|
525
529
|
get state() {
|
526
530
|
return state;
|
@@ -228,7 +228,7 @@ class OverviewMap {
|
|
228
228
|
clone.activate();
|
229
229
|
const idx = this._map.layerCollection.indexOf(clone);
|
230
230
|
if (idx < 0) {
|
231
|
-
this._map.layerCollection.add(clone
|
231
|
+
this._map.layerCollection.add(clone);
|
232
232
|
} else {
|
233
233
|
this._map.layerCollection.remove(clone);
|
234
234
|
this._map.layerCollection.add(clone, idx);
|
package/src/pluginHelper.js
CHANGED
@@ -25,9 +25,21 @@ export const pluginFactorySymbol = Symbol('pluginFactory');
|
|
25
25
|
*/
|
26
26
|
export const pluginBaseUrlSymbol = Symbol('pluginBaseUrl');
|
27
27
|
|
28
|
+
/**
|
29
|
+
* A symbol added to each plugin which describes the module URL from which the plugin was loaded (with the filename and including searchParams)
|
30
|
+
* @type {symbol}
|
31
|
+
*/
|
32
|
+
export const pluginModuleUrlSymbol = Symbol('pluginModuleUrl');
|
33
|
+
/**
|
34
|
+
* A symbol added to each plugin which describes the configured version range
|
35
|
+
* @type {symbol}
|
36
|
+
*/
|
37
|
+
export const pluginVersionRangeSymbol = Symbol('pluginVersionRange');
|
38
|
+
|
28
39
|
/**
|
29
40
|
* A helper function to create an absolute URL from a relative plugin asset URL. For example, when
|
30
41
|
* shipping your plugin with a "plugin-asset/icon.png", you can always retrieve said icon with getPluginAssetUrl(app, name, 'pluing-assets/icon.png')
|
42
|
+
* Sets the plugin version as searchParam.
|
31
43
|
* Returns null, if the plugin does not exist.
|
32
44
|
* @param {VcsUiApp} app
|
33
45
|
* @param {string} pluginName
|
@@ -47,6 +59,7 @@ export function getPluginAssetUrl(app, pluginName, asset) {
|
|
47
59
|
assetUrl.searchParams.set(key, value);
|
48
60
|
}
|
49
61
|
});
|
62
|
+
assetUrl.searchParams.set('version', plugin.version);
|
50
63
|
return assetUrl.toString();
|
51
64
|
}
|
52
65
|
return null;
|
@@ -114,15 +127,24 @@ export async function loadPlugin(name, config) {
|
|
114
127
|
baseUrl.pathname = baseUrl.pathname.replace(/\/[^/]+$/, '/');
|
115
128
|
const pluginInstance = await plugin.default(config, baseUrl.toString());
|
116
129
|
|
117
|
-
if (!pluginInstance.name) {
|
118
|
-
getLogger().error(
|
130
|
+
if (!(pluginInstance.name || pluginInstance.version)) {
|
131
|
+
getLogger().error(
|
132
|
+
`plugin ${name} does not conform to the VcsPlugin interface, which requires a name and version`,
|
133
|
+
);
|
119
134
|
if (pluginInstance.destroy) {
|
120
135
|
pluginInstance.destroy();
|
121
136
|
}
|
122
137
|
return null;
|
123
138
|
}
|
139
|
+
if (!isValidPackageName(pluginInstance.name)) {
|
140
|
+
getLogger().warning(
|
141
|
+
`plugin ${pluginInstance.name} has no valid package name!`,
|
142
|
+
);
|
143
|
+
}
|
124
144
|
pluginInstance[pluginFactorySymbol] = plugin.default;
|
125
145
|
pluginInstance[pluginBaseUrlSymbol] = baseUrl.toString();
|
146
|
+
pluginInstance[pluginModuleUrlSymbol] = module;
|
147
|
+
pluginInstance[pluginVersionRangeSymbol] = config.version;
|
126
148
|
return pluginInstance;
|
127
149
|
} catch (err) {
|
128
150
|
getLogger().error(`failed to load plugin ${name}`);
|
@@ -132,19 +154,25 @@ export async function loadPlugin(name, config) {
|
|
132
154
|
}
|
133
155
|
|
134
156
|
/**
|
157
|
+
* Returns relative url, if base is same, otherwise absolute url
|
158
|
+
* Removes version from searchParams, since version is serialized itself
|
135
159
|
* @param {string} base
|
136
|
-
* @param {string}
|
160
|
+
* @param {string} pluginUrl
|
137
161
|
* @returns {string}
|
138
162
|
*/
|
139
|
-
export function getPluginEntry(base,
|
163
|
+
export function getPluginEntry(base, pluginUrl) {
|
140
164
|
const baseUrl = new URL(base);
|
141
|
-
const
|
142
|
-
|
143
|
-
|
165
|
+
const pluginModuleUrl = new URL(pluginUrl);
|
166
|
+
pluginModuleUrl.searchParams.delete('version'); // semver is part of config
|
167
|
+
if (baseUrl.origin !== pluginModuleUrl.origin) {
|
168
|
+
return pluginModuleUrl.toString();
|
144
169
|
}
|
145
170
|
const baseSubs = baseUrl.pathname.split('/');
|
146
|
-
const pluginSubs =
|
147
|
-
|
171
|
+
const pluginSubs = pluginModuleUrl.pathname.split('/');
|
172
|
+
pluginModuleUrl.pathname = pluginSubs
|
173
|
+
.filter((sub, idx) => sub !== baseSubs[idx])
|
174
|
+
.join('/');
|
175
|
+
return `${pluginModuleUrl.pathname}${pluginModuleUrl.search}`.substring(1);
|
148
176
|
}
|
149
177
|
|
150
178
|
/**
|
@@ -154,24 +182,36 @@ export function getPluginEntry(base, pluginBase) {
|
|
154
182
|
export function serializePlugin(plugin) {
|
155
183
|
const serializedPlugin = plugin.toJSON ? plugin.toJSON() : {};
|
156
184
|
serializedPlugin.name = plugin.name;
|
185
|
+
if (serializedPlugin[pluginVersionRangeSymbol]) {
|
186
|
+
serializedPlugin.version = serializedPlugin[pluginVersionRangeSymbol];
|
187
|
+
}
|
157
188
|
serializedPlugin.entry = getPluginEntry(
|
158
189
|
window.location.href,
|
159
|
-
plugin[
|
190
|
+
plugin[pluginModuleUrlSymbol],
|
160
191
|
);
|
161
192
|
serializedPlugin[pluginFactorySymbol] = plugin[pluginFactorySymbol];
|
162
193
|
serializedPlugin[pluginBaseUrlSymbol] = plugin[pluginBaseUrlSymbol];
|
194
|
+
serializedPlugin[pluginModuleUrlSymbol] = plugin[pluginModuleUrlSymbol];
|
195
|
+
serializedPlugin[pluginVersionRangeSymbol] = plugin[pluginVersionRangeSymbol];
|
163
196
|
return serializedPlugin;
|
164
197
|
}
|
165
198
|
|
166
199
|
/**
|
167
200
|
* @param {Object} serializedPlugin
|
168
|
-
* @returns {Promise<VcsPlugin>}
|
201
|
+
* @returns {Promise<VcsPlugin|null>}
|
169
202
|
*/
|
170
203
|
export async function deserializePlugin(serializedPlugin) {
|
171
|
-
|
172
|
-
serializedPlugin
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
204
|
+
if (serializedPlugin[pluginFactorySymbol]) {
|
205
|
+
const reincarnation = await serializedPlugin[pluginFactorySymbol](
|
206
|
+
serializedPlugin,
|
207
|
+
);
|
208
|
+
reincarnation[pluginFactorySymbol] = serializedPlugin[pluginFactorySymbol];
|
209
|
+
reincarnation[pluginBaseUrlSymbol] = serializedPlugin[pluginBaseUrlSymbol];
|
210
|
+
reincarnation[pluginModuleUrlSymbol] =
|
211
|
+
serializedPlugin[pluginModuleUrlSymbol];
|
212
|
+
reincarnation[pluginVersionRangeSymbol] =
|
213
|
+
serializedPlugin[pluginVersionRangeSymbol];
|
214
|
+
return reincarnation;
|
215
|
+
}
|
216
|
+
return loadPlugin(serializedPlugin.name, serializedPlugin);
|
177
217
|
}
|
package/src/uiConfig.js
CHANGED
@@ -14,6 +14,7 @@ import { ref } from 'vue';
|
|
14
14
|
* @property {string} [appTitle] - an optional title to display next to the company logo
|
15
15
|
* @property {string} [primaryColor] - an optional primary color to use in all themes
|
16
16
|
* @property {boolean} [startingFeatureInfo] - an optional flag whether to activate feature info on startup (default active)
|
17
|
+
* @property {string} [helpBaseUrl='https://help.vc.systems/'] - an optional URL to a help landing page
|
17
18
|
*/
|
18
19
|
|
19
20
|
/**
|