@vcmap/ui 5.0.0-rc.26 → 5.0.0-rc.27

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 (102) hide show
  1. package/build/buildCesium.js +7 -0
  2. package/config/dev.config.json +4 -0
  3. package/dist/assets/cesium/ThirdParty/Workers/basis_transcoder.js +21 -0
  4. package/dist/assets/cesium/ThirdParty/Workers/draco_decoder_nodejs.js +117 -0
  5. package/dist/assets/cesium/ThirdParty/Workers/package.json +1 -0
  6. package/dist/assets/cesium/ThirdParty/Workers/pako_deflate.min.js +2 -0
  7. package/dist/assets/cesium/ThirdParty/Workers/pako_inflate.min.js +2 -0
  8. package/dist/assets/cesium/ThirdParty/Workers/z-worker-pako.js +1 -0
  9. package/dist/assets/cesium/ThirdParty/basis_transcoder.wasm +0 -0
  10. package/dist/assets/cesium/ThirdParty/draco_decoder.wasm +0 -0
  11. package/dist/assets/cesium/ThirdParty/google-earth-dbroot-parser.js +8337 -0
  12. package/dist/assets/{cesium.305b7c.js → cesium.82fdbe.js} +4 -4
  13. package/dist/assets/cesium.js +1 -1
  14. package/dist/assets/{core.f3d6d4.js → core.df069a.js} +2 -2
  15. package/dist/assets/core.js +1 -1
  16. package/dist/assets/index-1cff371d.js +1 -0
  17. package/dist/assets/ol.js +1 -1
  18. package/dist/assets/style/icon-marker-blue.534e37.png +0 -0
  19. package/dist/assets/style/icon-marker-green.0b6a92.png +0 -0
  20. package/dist/assets/style/icon-marker-o-blue.7b6d62.png +0 -0
  21. package/dist/assets/style/icon-marker-o-green.c863c0.png +0 -0
  22. package/dist/assets/style/icon-marker-o-red.93ff58.png +0 -0
  23. package/dist/assets/style/icon-marker-o.036477.png +0 -0
  24. package/dist/assets/style/icon-marker-red.313d03.png +0 -0
  25. package/dist/assets/style/icon-marker.70960f.png +0 -0
  26. package/dist/assets/style/icon-pin-blue.7be369.png +0 -0
  27. package/dist/assets/style/icon-pin-green.cbb935.png +0 -0
  28. package/dist/assets/style/icon-pin-red.3f25b2.png +0 -0
  29. package/dist/assets/style/icon-pin.b7ce77.png +0 -0
  30. package/dist/assets/{ui.74022f.css → ui.3ed7ff.css} +2 -2
  31. package/dist/assets/ui.3ed7ff.js +14763 -0
  32. package/dist/assets/ui.js +1 -1
  33. package/dist/assets/vue.js +2 -2
  34. package/dist/assets/{vuetify.30486f.js → vuetify.614278.js} +1 -1
  35. package/dist/assets/vuetify.js +2 -2
  36. package/dist/index.html +1 -1
  37. package/index.js +17 -1
  38. package/package.json +1 -1
  39. package/plugins/@vcmap/create-link/index.js +8 -8
  40. package/plugins/@vcmap-show-case/collection-manager-example/index.js +6 -1
  41. package/plugins/@vcmap-show-case/form-inputs-example/FormInputsExample.vue +36 -8
  42. package/plugins/@vcmap-show-case/form-inputs-example/exampleActions.js +0 -19
  43. package/plugins/@vcmap-show-case/form-inputs-example/index.js +1 -2
  44. package/plugins/@vcmap-show-case/style-input-example/README.md +4 -0
  45. package/plugins/@vcmap-show-case/style-input-example/index.js +42 -0
  46. package/plugins/@vcmap-show-case/style-input-example/package.json +5 -0
  47. package/plugins/@vcmap-show-case/style-input-example/styleExample.vue +191 -0
  48. package/plugins/@vcmap-show-case/window-tester/WindowExample.vue +12 -13
  49. package/plugins/@vcmap-show-case/window-tester/windowExampleToggleChild.vue +44 -0
  50. package/public/assets/style/icon-marker-blue.png +0 -0
  51. package/public/assets/style/icon-marker-green.png +0 -0
  52. package/public/assets/style/icon-marker-o-blue.png +0 -0
  53. package/public/assets/style/icon-marker-o-green.png +0 -0
  54. package/public/assets/style/icon-marker-o-red.png +0 -0
  55. package/public/assets/style/icon-marker-o.png +0 -0
  56. package/public/assets/style/icon-marker-red.png +0 -0
  57. package/public/assets/style/icon-marker.png +0 -0
  58. package/public/assets/style/icon-pin-blue.png +0 -0
  59. package/public/assets/style/icon-pin-green.png +0 -0
  60. package/public/assets/style/icon-pin-red.png +0 -0
  61. package/public/assets/style/icon-pin.png +0 -0
  62. package/src/actions/actionHelper.js +1 -0
  63. package/src/application/VcsApp.vue +7 -1
  64. package/src/components/buttons/VcsActionButtonList.vue +1 -0
  65. package/src/components/form-inputs-controls/VcsCheckbox.vue +3 -2
  66. package/src/components/form-inputs-controls/VcsFormSection.vue +59 -9
  67. package/src/components/form-inputs-controls/VcsLabel.vue +10 -0
  68. package/src/components/form-inputs-controls/VcsRadioGrid.vue +175 -0
  69. package/src/components/form-inputs-controls/VcsSelect.vue +3 -0
  70. package/src/components/form-inputs-controls/VcsTextField.vue +12 -0
  71. package/src/components/icons/+all.js +4 -0
  72. package/src/components/icons/EditVerticesIcon.vue +39 -0
  73. package/src/components/lists/VcsActionList.vue +2 -0
  74. package/src/components/style/MenuWrapper.vue +138 -0
  75. package/src/components/style/VcsFillMenu.vue +61 -0
  76. package/src/components/style/VcsFillSelector.vue +45 -0
  77. package/src/components/style/VcsImageMenu.vue +84 -0
  78. package/src/components/style/VcsImageSelector.vue +609 -0
  79. package/src/components/style/VcsStrokeMenu.vue +73 -0
  80. package/src/components/style/VcsStrokeSelector.vue +87 -0
  81. package/src/components/style/VcsTextMenu.vue +81 -0
  82. package/src/components/style/VcsTextSelector.vue +271 -0
  83. package/src/components/style/VcsVectorStyleComponent.vue +119 -0
  84. package/src/components/style/composables.js +84 -0
  85. package/src/contentTree/contentTreeCollection.js +1 -0
  86. package/src/i18n/de.js +51 -17
  87. package/src/i18n/en.js +56 -22
  88. package/src/legend/vcsLegend.vue +7 -5
  89. package/src/manager/collectionManager/CollectionComponent.vue +9 -17
  90. package/src/manager/collectionManager/CollectionComponentList.vue +22 -9
  91. package/src/manager/collectionManager/CollectionManager.vue +20 -1
  92. package/src/manager/collectionManager/collectionComponent.js +11 -0
  93. package/src/manager/window/WindowComponent.vue +2 -1
  94. package/src/manager/window/WindowManager.vue +23 -9
  95. package/src/manager/window/windowHelper.js +76 -4
  96. package/src/manager/window/windowManager.js +38 -6
  97. package/src/vcsUiApp.js +1 -0
  98. package/dist/assets/index-f94d5be3.js +0 -1
  99. package/dist/assets/ui.74022f.js +0 -13466
  100. /package/dist/assets/{ol.39cc05.js → ol.90a5d0.js} +0 -0
  101. /package/dist/assets/{vue.9b8c6e.js → vue.537ff3.js} +0 -0
  102. /package/dist/assets/{vuetify.30486f.css → vuetify.614278.css} +0 -0
@@ -0,0 +1,609 @@
1
+ <template>
2
+ <v-sheet>
3
+ <v-container class="px-1 py-0">
4
+ <v-row no-gutters>
5
+ <v-col class="d-flex justify-center py-1">
6
+ <canvas ref="canvas" width="50" height="50" />
7
+ </v-col>
8
+ </v-row>
9
+ </v-container>
10
+ <v-divider />
11
+ <div class="px-1">
12
+ <v-tabs v-model="selectedImageTypeTab" height="40" centered>
13
+ <v-tab
14
+ v-for="item in Object.values(ImageType).map((value) => ({
15
+ value,
16
+ label: `components.style.${value}`,
17
+ }))"
18
+ :key="item.value"
19
+ light
20
+ >
21
+ {{ $t(item.label) }}
22
+ </v-tab>
23
+ </v-tabs>
24
+ </div>
25
+ <v-divider />
26
+ <v-container class="px-1 pt-1 pb-0">
27
+ <VcsRadioGrid
28
+ v-model="selectedImage"
29
+ :items="currentItems"
30
+ :disabled="!value"
31
+ item-value="src"
32
+ >
33
+ <template #label="{ src }" v-if="selectedType === ImageType.SHAPE">
34
+ <v-icon size="24">{{ src }}</v-icon>
35
+ </template>
36
+ </VcsRadioGrid>
37
+ <div v-if="selectedType === ImageType.ICON">
38
+ <v-row no-gutters>
39
+ <v-col>
40
+ <VcsLabel html-for="style-icon-opacity">
41
+ {{ $t('components.style.opacity') }}
42
+ </VcsLabel>
43
+ </v-col>
44
+ <v-col>
45
+ <VcsSlider
46
+ id="style-icon-opacity"
47
+ v-model="selectedOpacity"
48
+ step="0.1"
49
+ type="number"
50
+ max="1"
51
+ min="0"
52
+ :disabled="currentType !== ImageType.ICON"
53
+ ></VcsSlider>
54
+ </v-col>
55
+ </v-row>
56
+ </div>
57
+ <div v-else-if="selectedType === ImageType.SHAPE">
58
+ <v-row
59
+ no-gutters
60
+ v-for="input in shapeSingleValueInputs"
61
+ :key="input.name"
62
+ >
63
+ <v-col>
64
+ <VcsLabel>{{ $t(`components.style.${input.text}`) }}</VcsLabel>
65
+ </v-col>
66
+ <v-col cols="3">
67
+ <VcsTextField
68
+ :id="`style-shape-${input.text}`"
69
+ type="number"
70
+ :unit="input.unit || ''"
71
+ v-model.number="input.value.value"
72
+ :disabled="currentType !== ImageType.SHAPE"
73
+ :step="input.step || 1"
74
+ :min="input.range?.[0] || 0"
75
+ :max="input.range?.[1] || undefined"
76
+ :rules="[
77
+ (v) => !input.isRequired || !!v || 'components.style.required',
78
+ (v) =>
79
+ !input.range ||
80
+ (!input.isRequired && !v) ||
81
+ between(v, input.range) ||
82
+ `${$t('components.style.allowedRange')}: ${input.range.join(
83
+ ' - ',
84
+ )}`,
85
+ ]"
86
+ :show-spin-buttons="true"
87
+ />
88
+ </v-col>
89
+ </v-row>
90
+ </div>
91
+ <v-row
92
+ no-gutters
93
+ v-if="extendedShapeSettings || selectedType === ImageType.ICON"
94
+ >
95
+ <v-col>
96
+ <VcsLabel>{{ $t('components.style.scale') }}</VcsLabel>
97
+ </v-col>
98
+ <v-col cols="3">
99
+ <VcsTextField
100
+ id="style-shape-scaleX"
101
+ type="number"
102
+ v-model.number="selectedScale.x.value"
103
+ prefix="X"
104
+ placeholder="1"
105
+ :disabled="currentType !== selectedType"
106
+ />
107
+ </v-col>
108
+ <v-col cols="3">
109
+ <VcsTextField
110
+ id="style-shape-scaleY"
111
+ type="number"
112
+ v-model.number="selectedScale.y.value"
113
+ prefix="Y"
114
+ placeholder="1"
115
+ :disabled="currentType !== selectedType"
116
+ />
117
+ </v-col>
118
+ </v-row>
119
+ <VcsStrokeMenu
120
+ v-if="selectedType === ImageType.SHAPE"
121
+ v-model="selectedStroke"
122
+ :value-default="valueDefault?.stroke"
123
+ :disabled="!value || currentType !== ImageType.SHAPE"
124
+ />
125
+ <VcsFillMenu
126
+ v-if="selectedType === ImageType.SHAPE"
127
+ v-model="selectedFill"
128
+ :value-default="valueDefault?.fill"
129
+ :disabled="!value || currentType !== ImageType.SHAPE"
130
+ />
131
+ </v-container>
132
+ </v-sheet>
133
+ </template>
134
+
135
+ <script>
136
+ import { computed, onMounted, ref, watch } from 'vue';
137
+ import {
138
+ VSheet,
139
+ VDivider,
140
+ VContainer,
141
+ VRow,
142
+ VCol,
143
+ VIcon,
144
+ VTabs,
145
+ VTab,
146
+ } from 'vuetify/lib';
147
+ import {
148
+ VcsLabel,
149
+ VcsTextField,
150
+ VcsFillMenu,
151
+ VcsStrokeMenu,
152
+ VcsRadioGrid,
153
+ VcsSlider,
154
+ } from '@vcmap/ui';
155
+ import { Circle, Fill, Icon, RegularShape, Stroke, Style } from 'ol/style.js';
156
+ import { toContext } from 'ol/render.js';
157
+ import { Point } from 'ol/geom.js';
158
+ import { useSelectedKey, between } from './composables.js';
159
+
160
+ /**
161
+ * @enum {string}
162
+ * @property {string} SHAPE - Regularshape or Circle
163
+ * @property {string} ICON - Raster images
164
+ */
165
+ export const ImageType = {
166
+ SHAPE: 'shape',
167
+ ICON: 'icon',
168
+ };
169
+
170
+ /**
171
+ * Draws an image style on a canvas.
172
+ * @param {HTMLCanvasElement} canvas The canvas to draw on
173
+ * @param {import("ol/style/Image").Options} imageOptions The JSON options of the image style.
174
+ * @param {boolean} fitToCanvas If the circle, shape or icon should be fitted into the canvas or if it should be draw with it's actual size.
175
+ */
176
+ export async function drawImageStyle(
177
+ canvas,
178
+ imageOptions,
179
+ fitToCanvas = false,
180
+ ) {
181
+ const context = canvas.getContext('2d');
182
+ context.clearRect(0, 0, canvas.width, canvas.height);
183
+ if (!imageOptions?.radius && !imageOptions?.src) {
184
+ return;
185
+ }
186
+ const vectorContext = toContext(context, { pixelRatio: 1 });
187
+ let imageStyle;
188
+ let size;
189
+ if (imageOptions.radius) {
190
+ // TODO: Replace with getImageStyleFromOptions from styleHelpers.ts in @vcmap/core
191
+ const { radius } = imageOptions;
192
+ size = [radius * 2, radius * 2];
193
+ const options = {
194
+ stroke: new Stroke(
195
+ imageOptions.stroke
196
+ ? {
197
+ color: imageOptions.stroke.color,
198
+ width: imageOptions.stroke.width,
199
+ }
200
+ : null,
201
+ ),
202
+ fill: new Fill(
203
+ imageOptions.fill
204
+ ? {
205
+ color: imageOptions.fill.color,
206
+ }
207
+ : null,
208
+ ),
209
+ radius,
210
+ };
211
+ if (imageOptions.points) {
212
+ options.radius2 = imageOptions.radius2;
213
+ options.angle = imageOptions.angle;
214
+ options.points = imageOptions.points;
215
+ options.rotation = imageOptions.rotation;
216
+ options.scale = imageOptions.scale;
217
+ imageStyle = new RegularShape(options);
218
+ } else {
219
+ imageStyle = new Circle(options);
220
+ }
221
+ } else if (imageOptions.src) {
222
+ // Somehow the icon does not load the img when providing the src. And icon.load() is not async.
223
+ // Therefore the img first has to be loaded and then passed to new Icon
224
+ const img = new Image();
225
+ img.src = imageOptions.src;
226
+ await img.decode();
227
+ imageStyle = new Icon({
228
+ img,
229
+ imgSize: [img.width, img.height],
230
+ opacity: imageOptions.opacity,
231
+ scale: imageOptions.scale,
232
+ });
233
+ size = [img.width, img.height];
234
+ }
235
+ if (fitToCanvas) {
236
+ const paddingShare = 1.5;
237
+ let oldScale;
238
+ if (imageOptions.scale) {
239
+ oldScale = Array.isArray(imageOptions.scale)
240
+ ? imageOptions.scale
241
+ : [imageOptions.scale, imageOptions.scale];
242
+ } else {
243
+ oldScale = [1, 1];
244
+ }
245
+ const smallerRatio = Math.min(
246
+ canvas.width / paddingShare / (size[0] * oldScale[0]),
247
+ canvas.height / paddingShare / (size[1] * oldScale[1]),
248
+ );
249
+ const newScale = oldScale.map((dimension) => dimension * smallerRatio);
250
+ imageStyle.setScale(newScale);
251
+ }
252
+ vectorContext.setStyle(new Style({ image: imageStyle }));
253
+ vectorContext.drawGeometry(
254
+ new Point([canvas.width / 2, canvas.height / 2]),
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Compares two regular shapes based on points, radius2, angle, rotation and scale.
260
+ * @param {import('ol/style/RegularShape').Options} shape1 A ol regular shape.
261
+ * @param {import('ol/style/RegularShape').Options} shape2 Another ol regular shape.
262
+ * @returns {boolean} If shapes are equal.
263
+ */
264
+ function isEqualShape(shape1, shape2) {
265
+ return (
266
+ ((!shape1.points && !shape2.points) || shape1.points === shape2.points) &&
267
+ shape1.radius2 === shape2.radius2 &&
268
+ ((!shape1.angle && !shape2.angle) || shape1.angle === shape2.angle) &&
269
+ ((!shape1.rotation && !shape2.rotation) ||
270
+ shape1.rotation === shape2.rotation) &&
271
+ ((!shape1.scale && !shape2.scale) ||
272
+ (!shape1.scale && shape2.scale === 1) ||
273
+ (shape1.scale === 1 && !shape2.scale) ||
274
+ shape1.scale === shape2.scale ||
275
+ (Array.isArray(shape1.scale) &&
276
+ Array.isArray(shape2.scale) &&
277
+ shape1.scale.every((value, index) => value === shape2.scale[index])))
278
+ );
279
+ }
280
+
281
+ /** Presets for different shapes with a matching mdi icon as src. */
282
+ export const defaultShapes = [
283
+ {
284
+ src: 'mdi-circle-outline',
285
+ value: { radius: 10 },
286
+ },
287
+ {
288
+ src: 'mdi-square-outline',
289
+ value: { points: 4, radius: 10, angle: Math.PI / 4 },
290
+ },
291
+ {
292
+ src: 'mdi-rectangle-outline',
293
+ value: {
294
+ radius: 10 / Math.SQRT2,
295
+ radius2: 10,
296
+ points: 4,
297
+ angle: 0,
298
+ scale: [1, 0.5],
299
+ },
300
+ },
301
+ {
302
+ src: 'mdi-triangle-outline',
303
+ value: {
304
+ points: 3,
305
+ radius: 10,
306
+ angle: 0,
307
+ },
308
+ },
309
+ {
310
+ src: 'mdi-star-outline',
311
+ value: {
312
+ points: 5,
313
+ radius: 10,
314
+ radius2: 4,
315
+ angle: 0,
316
+ },
317
+ },
318
+ {
319
+ src: 'mdi-plus',
320
+ value: {
321
+ points: 4,
322
+ radius: 10,
323
+ radius2: 0,
324
+ angle: 0,
325
+ },
326
+ },
327
+ {
328
+ src: 'mdi-close',
329
+ value: {
330
+ points: 4,
331
+ radius: 10,
332
+ radius2: 0,
333
+ angle: Math.PI / 4,
334
+ },
335
+ },
336
+ ];
337
+
338
+ export const defaultIcons = [
339
+ { src: '/assets/style/icon-marker.png', anchor: [0.5, 1] },
340
+ { src: '/assets/style/icon-marker-blue.png', anchor: [0.5, 1] },
341
+ { src: '/assets/style/icon-marker-green.png', anchor: [0.5, 1] },
342
+ { src: '/assets/style/icon-marker-red.png', anchor: [0.5, 1] },
343
+ { src: '/assets/style/icon-marker-o.png', anchor: [0.5, 1] },
344
+ { src: '/assets/style/icon-marker-o-blue.png', anchor: [0.5, 1] },
345
+ { src: '/assets/style/icon-marker-o-green.png', anchor: [0.5, 1] },
346
+ { src: '/assets/style/icon-marker-o-red.png', anchor: [0.5, 1] },
347
+ { src: '/assets/style/icon-pin.png', anchor: [0.5, 1] },
348
+ { src: '/assets/style/icon-pin-blue.png', anchor: [0.5, 1] },
349
+ { src: '/assets/style/icon-pin-green.png', anchor: [0.5, 1] },
350
+ { src: '/assets/style/icon-pin-red.png', anchor: [0.5, 1] },
351
+ ];
352
+
353
+ /**
354
+ * @description Allows to model a JSON representation of ol/style/Image style. It makes use of VcsStrokeMenu and VcsFillMenu.
355
+ * @vue-prop {import("ol/style/RegularShape").Options | import("ol/style/Circle").Options | import("ol/style/Icon").Options} value - The Image options
356
+ * @vue-prop {import("ol/style/RegularShape").Options | import("ol/style/Circle").Options | import("ol/style/Icon").Options} valueDefault - The default image options
357
+ * @vue-prop {Array<import("ol/style/Icon").Options>} [iconOptions] - The icon options too choose from. Scale and opacity are ignored. The defaults are 3 different shapes with 4 different colors.
358
+ * @vue-prop {boolean} [extendedShapeSettings=false] - If true, there are all the input fields needed to create arbitrary ol RegularShapes.
359
+ */
360
+ export default {
361
+ name: 'VcsImageSelector',
362
+ components: {
363
+ VSheet,
364
+ VDivider,
365
+ VContainer,
366
+ VRow,
367
+ VCol,
368
+ VIcon,
369
+ VTabs,
370
+ VTab,
371
+ VcsLabel,
372
+ VcsTextField,
373
+ VcsFillMenu,
374
+ VcsStrokeMenu,
375
+ VcsRadioGrid,
376
+ VcsSlider,
377
+ },
378
+ props: {
379
+ value: {
380
+ type: Object,
381
+ default: undefined,
382
+ },
383
+ valueDefault: {
384
+ type: Object,
385
+ default: undefined,
386
+ },
387
+ extendedShapeSettings: {
388
+ type: Boolean,
389
+ default: false,
390
+ },
391
+ iconOptions: {
392
+ type: Array,
393
+ default: () => defaultIcons,
394
+ },
395
+ },
396
+ setup(props, { emit }) {
397
+ const currentType = computed(() => {
398
+ if (props.value?.radius) {
399
+ return ImageType.SHAPE;
400
+ } else if (props.value?.src) {
401
+ return ImageType.ICON;
402
+ } else {
403
+ return undefined;
404
+ }
405
+ });
406
+
407
+ const initialTab = Object.values(ImageType).findIndex(
408
+ (type) => type === currentType.value,
409
+ );
410
+
411
+ const selectedImageTypeTab = ref(initialTab !== -1 ? initialTab : 0);
412
+
413
+ const selectedType = computed(
414
+ () => Object.values(ImageType)[selectedImageTypeTab.value],
415
+ );
416
+
417
+ const customIcon = 'mdi-dots-horizontal';
418
+
419
+ const canvas = ref();
420
+
421
+ const shapeSingleValueInputs = computed(() => {
422
+ const inputs = [
423
+ { name: 'radius', unit: 'px', range: [1, 100], isRequired: true },
424
+ ];
425
+ if (props.extendedShapeSettings) {
426
+ [
427
+ { name: 'points', range: [0, 10] },
428
+ { name: 'radius2', unit: 'px', range: [0, 100] },
429
+ { name: 'angle', step: 0.1, unit: 'rad' },
430
+ { name: 'rotation', step: 0.1, unit: 'rad' },
431
+ ].forEach((entry) => inputs.push(entry));
432
+ }
433
+ return inputs.map((input) => {
434
+ return {
435
+ text: input.name,
436
+ value: useSelectedKey(
437
+ () => props.value,
438
+ input.name,
439
+ props.valueDefault[input.name],
440
+ emit,
441
+ input.range,
442
+ input.isRequired,
443
+ ),
444
+ unit: input.unit,
445
+ range: input.range,
446
+ step: input.step,
447
+ };
448
+ });
449
+ });
450
+
451
+ // reduces the array to an object with x and y as keys and computed properties with a getter and setter as values.
452
+ const selectedScale = ['x', 'y'].reduce(
453
+ (acc, dimension, index, array) => {
454
+ return {
455
+ ...acc,
456
+ [dimension]: computed({
457
+ get() {
458
+ if (Array.isArray(props.value?.scale)) {
459
+ return props.value.scale[index];
460
+ } else {
461
+ return props.value.scale;
462
+ }
463
+ },
464
+ set(value) {
465
+ let newValue = value;
466
+ if (!value || value < 0) {
467
+ newValue = 1;
468
+ }
469
+ const otherDimension = array[1 - index];
470
+ let newScale;
471
+ if (newValue !== selectedScale[otherDimension]) {
472
+ newScale = [];
473
+ newScale[index] = newValue;
474
+ newScale[1 - index] =
475
+ selectedScale[otherDimension].value || 1;
476
+ } else {
477
+ newScale = newValue;
478
+ }
479
+ const newImage = JSON.parse(JSON.stringify(props.value));
480
+ emit('input', Object.assign(newImage, { scale: newScale }));
481
+ },
482
+ }),
483
+ };
484
+ },
485
+ {},
486
+ );
487
+
488
+ const selectedOpacity = useSelectedKey(
489
+ () => props.value,
490
+ 'opacity',
491
+ props.valueDefault.opacity,
492
+ emit,
493
+ );
494
+
495
+ const selectedFill = useSelectedKey(
496
+ () => props.value,
497
+ 'fill',
498
+ props.valueDefault.fill,
499
+ emit,
500
+ );
501
+ const selectedStroke = useSelectedKey(
502
+ () => props.value,
503
+ 'stroke',
504
+ props.valueDefault.stroke,
505
+ emit,
506
+ );
507
+
508
+ const selectedImage = computed({
509
+ get() {
510
+ if (currentType.value !== selectedType.value) {
511
+ return undefined;
512
+ } else if (currentType.value === ImageType.SHAPE) {
513
+ const equalShape = defaultShapes.find((preset) =>
514
+ isEqualShape(props.value, preset.value),
515
+ );
516
+ if (equalShape) {
517
+ return equalShape.src;
518
+ } else {
519
+ return customIcon;
520
+ }
521
+ } else if (currentType.value === ImageType.ICON) {
522
+ return props.value?.src;
523
+ } else {
524
+ return undefined;
525
+ }
526
+ },
527
+ set(value) {
528
+ let newImage = {};
529
+ if (selectedType.value === ImageType.SHAPE) {
530
+ const switchFromICON = currentType.value === ImageType.ICON;
531
+ let newPreset;
532
+ if (value === customIcon && !switchFromICON) {
533
+ return;
534
+ } else if (value === customIcon) {
535
+ newPreset = defaultShapes[0];
536
+ } else {
537
+ newPreset = defaultShapes.find((preset) => preset.src === value);
538
+ }
539
+
540
+ if (newPreset) {
541
+ let fill;
542
+ let stroke;
543
+
544
+ if (switchFromICON) {
545
+ fill = props.valueDefault?.fill;
546
+ stroke = props.valueDefault?.stroke;
547
+ } else {
548
+ fill = selectedFill.value;
549
+ stroke = selectedStroke.value;
550
+ }
551
+ newImage = JSON.parse(JSON.stringify(newPreset.value));
552
+ Object.assign(newImage, {
553
+ fill,
554
+ stroke,
555
+ });
556
+ }
557
+ } else if (selectedType.value === ImageType.ICON) {
558
+ newImage = props.iconOptions.find((option) => option.src === value);
559
+ Object.assign(newImage, {
560
+ scale: selectedScale.value || 1,
561
+ opacity: selectedOpacity.value || 1,
562
+ });
563
+ }
564
+ emit('input', JSON.parse(JSON.stringify(newImage)));
565
+ },
566
+ });
567
+
568
+ const currentItems = computed(() => {
569
+ let items = [];
570
+ if (selectedType.value === ImageType.SHAPE) {
571
+ items = [...defaultShapes];
572
+ if (props.extendedShapeSettings) {
573
+ items.push({ src: customIcon });
574
+ }
575
+ } else if (selectedType.value === ImageType.ICON) {
576
+ items = props.iconOptions;
577
+ }
578
+ return items;
579
+ });
580
+
581
+ onMounted(() => {
582
+ drawImageStyle(canvas.value, props.value);
583
+ watch(
584
+ () => props.value,
585
+ () => {
586
+ drawImageStyle(canvas.value, props.value);
587
+ },
588
+ { deep: true },
589
+ );
590
+ });
591
+
592
+ return {
593
+ ImageType,
594
+ selectedType,
595
+ currentType,
596
+ selectedImage,
597
+ canvas,
598
+ shapeSingleValueInputs,
599
+ selectedScale,
600
+ selectedOpacity,
601
+ selectedFill,
602
+ selectedStroke,
603
+ between,
604
+ currentItems,
605
+ selectedImageTypeTab,
606
+ };
607
+ },
608
+ };
609
+ </script>
@@ -0,0 +1,73 @@
1
+ <template>
2
+ <MenuWrapper
3
+ v-bind="{ value, valueDefault, disabled }"
4
+ :value-fallback="{ color: [0, 0, 0, 1], width: 1 }"
5
+ v-on="$listeners"
6
+ name="components.style.stroke"
7
+ >
8
+ <template #preview>
9
+ <v-sheet
10
+ class="stroke-box"
11
+ :style="{
12
+ borderColor: rgbaString,
13
+ }"
14
+ width="100%"
15
+ height="100%"
16
+ />
17
+ </template>
18
+ <template #content>
19
+ <VcsStrokeSelector :value="value" v-on="$listeners" />
20
+ </template>
21
+ </MenuWrapper>
22
+ </template>
23
+
24
+ <script>
25
+ import { computed } from 'vue';
26
+ import { VSheet } from 'vuetify/lib';
27
+ import { VcsStrokeSelector } from '@vcmap/ui';
28
+ import MenuWrapper from './MenuWrapper.vue';
29
+ import { useColorObject, rgbaObjectToString } from './composables.js';
30
+
31
+ /**
32
+ * @description A wrapper for the VcsStrokeSelector, that has a small color preview and a menu that pops up when clicking the preview, containing the stroke selector.
33
+ * When clicking the reset button, the valueDefault is emitted, when unchecking the checkbox in front of the preview, null is emitted. If it is checked again, valueDefault is emitted. If the valueDefault is undefined or null, { color: [0, 0, 0, 1], width: 1 } is emitted.
34
+ * @vue-prop {import("ol/style/Stroke").Options} [value] - The Stroke Options
35
+ * @vue-prop {import("ol/style/Stroke").Options} [valueDefault] - The default Stroke Options.
36
+ * @vue-prop {boolean} [disabled=false] - Disable the input
37
+ */
38
+ export default {
39
+ name: 'VcsStrokeMenu',
40
+ components: {
41
+ VSheet,
42
+ VcsStrokeSelector,
43
+ MenuWrapper,
44
+ },
45
+ props: {
46
+ value: {
47
+ type: Object,
48
+ default: undefined,
49
+ },
50
+ valueDefault: {
51
+ type: Object,
52
+ default: undefined,
53
+ },
54
+ disabled: {
55
+ type: Boolean,
56
+ default: false,
57
+ },
58
+ },
59
+ setup(props) {
60
+ const rgbaObject = useColorObject(() => props.value?.color);
61
+ return {
62
+ rgbaString: computed(() => rgbaObjectToString(rgbaObject.value)),
63
+ };
64
+ },
65
+ };
66
+ </script>
67
+
68
+ <style scoped>
69
+ .stroke-box {
70
+ border: 3px solid;
71
+ background-color: transparent;
72
+ }
73
+ </style>