@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.
- package/build/buildCesium.js +7 -0
- package/config/dev.config.json +4 -0
- package/dist/assets/cesium/ThirdParty/Workers/basis_transcoder.js +21 -0
- package/dist/assets/cesium/ThirdParty/Workers/draco_decoder_nodejs.js +117 -0
- package/dist/assets/cesium/ThirdParty/Workers/package.json +1 -0
- package/dist/assets/cesium/ThirdParty/Workers/pako_deflate.min.js +2 -0
- package/dist/assets/cesium/ThirdParty/Workers/pako_inflate.min.js +2 -0
- package/dist/assets/cesium/ThirdParty/Workers/z-worker-pako.js +1 -0
- package/dist/assets/cesium/ThirdParty/basis_transcoder.wasm +0 -0
- package/dist/assets/cesium/ThirdParty/draco_decoder.wasm +0 -0
- package/dist/assets/cesium/ThirdParty/google-earth-dbroot-parser.js +8337 -0
- package/dist/assets/{cesium.305b7c.js → cesium.82fdbe.js} +4 -4
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/{core.f3d6d4.js → core.df069a.js} +2 -2
- package/dist/assets/core.js +1 -1
- package/dist/assets/index-1cff371d.js +1 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/style/icon-marker-blue.534e37.png +0 -0
- package/dist/assets/style/icon-marker-green.0b6a92.png +0 -0
- package/dist/assets/style/icon-marker-o-blue.7b6d62.png +0 -0
- package/dist/assets/style/icon-marker-o-green.c863c0.png +0 -0
- package/dist/assets/style/icon-marker-o-red.93ff58.png +0 -0
- package/dist/assets/style/icon-marker-o.036477.png +0 -0
- package/dist/assets/style/icon-marker-red.313d03.png +0 -0
- package/dist/assets/style/icon-marker.70960f.png +0 -0
- package/dist/assets/style/icon-pin-blue.7be369.png +0 -0
- package/dist/assets/style/icon-pin-green.cbb935.png +0 -0
- package/dist/assets/style/icon-pin-red.3f25b2.png +0 -0
- package/dist/assets/style/icon-pin.b7ce77.png +0 -0
- package/dist/assets/{ui.74022f.css → ui.3ed7ff.css} +2 -2
- package/dist/assets/ui.3ed7ff.js +14763 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.js +2 -2
- package/dist/assets/{vuetify.30486f.js → vuetify.614278.js} +1 -1
- package/dist/assets/vuetify.js +2 -2
- package/dist/index.html +1 -1
- package/index.js +17 -1
- package/package.json +1 -1
- package/plugins/@vcmap/create-link/index.js +8 -8
- package/plugins/@vcmap-show-case/collection-manager-example/index.js +6 -1
- package/plugins/@vcmap-show-case/form-inputs-example/FormInputsExample.vue +36 -8
- package/plugins/@vcmap-show-case/form-inputs-example/exampleActions.js +0 -19
- package/plugins/@vcmap-show-case/form-inputs-example/index.js +1 -2
- package/plugins/@vcmap-show-case/style-input-example/README.md +4 -0
- package/plugins/@vcmap-show-case/style-input-example/index.js +42 -0
- package/plugins/@vcmap-show-case/style-input-example/package.json +5 -0
- package/plugins/@vcmap-show-case/style-input-example/styleExample.vue +191 -0
- package/plugins/@vcmap-show-case/window-tester/WindowExample.vue +12 -13
- package/plugins/@vcmap-show-case/window-tester/windowExampleToggleChild.vue +44 -0
- package/public/assets/style/icon-marker-blue.png +0 -0
- package/public/assets/style/icon-marker-green.png +0 -0
- package/public/assets/style/icon-marker-o-blue.png +0 -0
- package/public/assets/style/icon-marker-o-green.png +0 -0
- package/public/assets/style/icon-marker-o-red.png +0 -0
- package/public/assets/style/icon-marker-o.png +0 -0
- package/public/assets/style/icon-marker-red.png +0 -0
- package/public/assets/style/icon-marker.png +0 -0
- package/public/assets/style/icon-pin-blue.png +0 -0
- package/public/assets/style/icon-pin-green.png +0 -0
- package/public/assets/style/icon-pin-red.png +0 -0
- package/public/assets/style/icon-pin.png +0 -0
- package/src/actions/actionHelper.js +1 -0
- package/src/application/VcsApp.vue +7 -1
- package/src/components/buttons/VcsActionButtonList.vue +1 -0
- package/src/components/form-inputs-controls/VcsCheckbox.vue +3 -2
- package/src/components/form-inputs-controls/VcsFormSection.vue +59 -9
- package/src/components/form-inputs-controls/VcsLabel.vue +10 -0
- package/src/components/form-inputs-controls/VcsRadioGrid.vue +175 -0
- package/src/components/form-inputs-controls/VcsSelect.vue +3 -0
- package/src/components/form-inputs-controls/VcsTextField.vue +12 -0
- package/src/components/icons/+all.js +4 -0
- package/src/components/icons/EditVerticesIcon.vue +39 -0
- package/src/components/lists/VcsActionList.vue +2 -0
- package/src/components/style/MenuWrapper.vue +138 -0
- package/src/components/style/VcsFillMenu.vue +61 -0
- package/src/components/style/VcsFillSelector.vue +45 -0
- package/src/components/style/VcsImageMenu.vue +84 -0
- package/src/components/style/VcsImageSelector.vue +609 -0
- package/src/components/style/VcsStrokeMenu.vue +73 -0
- package/src/components/style/VcsStrokeSelector.vue +87 -0
- package/src/components/style/VcsTextMenu.vue +81 -0
- package/src/components/style/VcsTextSelector.vue +271 -0
- package/src/components/style/VcsVectorStyleComponent.vue +119 -0
- package/src/components/style/composables.js +84 -0
- package/src/contentTree/contentTreeCollection.js +1 -0
- package/src/i18n/de.js +51 -17
- package/src/i18n/en.js +56 -22
- package/src/legend/vcsLegend.vue +7 -5
- package/src/manager/collectionManager/CollectionComponent.vue +9 -17
- package/src/manager/collectionManager/CollectionComponentList.vue +22 -9
- package/src/manager/collectionManager/CollectionManager.vue +20 -1
- package/src/manager/collectionManager/collectionComponent.js +11 -0
- package/src/manager/window/WindowComponent.vue +2 -1
- package/src/manager/window/WindowManager.vue +23 -9
- package/src/manager/window/windowHelper.js +76 -4
- package/src/manager/window/windowManager.js +38 -6
- package/src/vcsUiApp.js +1 -0
- package/dist/assets/index-f94d5be3.js +0 -1
- package/dist/assets/ui.74022f.js +0 -13466
- /package/dist/assets/{ol.39cc05.js → ol.90a5d0.js} +0 -0
- /package/dist/assets/{vue.9b8c6e.js → vue.537ff3.js} +0 -0
- /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>
|