@vcmap/module-selector 2.0.0 → 2.0.1

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.
@@ -0,0 +1,660 @@
1
+ <template>
2
+ <AbstractConfigEditor
3
+ @submit="apply"
4
+ v-if="localConfig"
5
+ v-bind="{ ...$attrs, ...$props }"
6
+ >
7
+ <v-container v-if="currentGroup === undefined" class="py-0 px-0">
8
+ <VcsFormSection heading="moduleSelector.configEditor.general">
9
+ <v-container class="py-0 px-1">
10
+ <v-row no-gutters>
11
+ <v-col cols="6">
12
+ <VcsLabel html-for="moduleselector-window-title">
13
+ {{ $st('moduleSelector.configEditor.windowTitle') }}
14
+ </VcsLabel>
15
+ </v-col>
16
+ <v-col>
17
+ <VcsTextField
18
+ id="moduleselector-window-title"
19
+ :placeholder="$t('moduleSelector.title')"
20
+ v-model="localConfig.windowTitle"
21
+ />
22
+ </v-col>
23
+ </v-row>
24
+ <v-row no-gutters>
25
+ <v-col>
26
+ <VcsCheckbox
27
+ v-model="localConfig.isActiveOnStart"
28
+ label="moduleSelector.configEditor.isActiveOnStart"
29
+ />
30
+ </v-col>
31
+ </v-row>
32
+ <v-row no-gutters>
33
+ <v-col>
34
+ <VcsCheckbox
35
+ v-model="localConfig.requireModuleSelection"
36
+ label="moduleSelector.configEditor.requireModuleSelection"
37
+ />
38
+ </v-col>
39
+ </v-row>
40
+ </v-container>
41
+ </VcsFormSection>
42
+ <VcsFormSection
43
+ heading="moduleSelector.configEditor.windowPosition"
44
+ :expandable="true"
45
+ >
46
+ <WindowPositionSettings
47
+ v-model="localConfig.position!"
48
+ :position-keys="[
49
+ 'top',
50
+ 'right',
51
+ 'bottom',
52
+ 'left',
53
+ 'width',
54
+ 'maxWidth',
55
+ 'height',
56
+ 'maxHeight',
57
+ ]"
58
+ />
59
+ </VcsFormSection>
60
+ <VcsFormSection heading="moduleSelector.configEditor.baseModule">
61
+ <v-container class="py-0 px-1">
62
+ <v-row no-gutters>
63
+ <v-col>
64
+ <VcsCheckbox
65
+ id="BaseModuleCheckbox"
66
+ v-model="basisModuleExists"
67
+ label="moduleSelector.configEditor.baseModuleCheckbox"
68
+ >
69
+ </VcsCheckbox>
70
+ </v-col>
71
+ </v-row>
72
+ <v-row v-if="basisModuleExists" no-gutters>
73
+ <v-col cols="6">
74
+ <VcsLabel required html-for="basisModuleName">
75
+ {{ $st('moduleSelector.configEditor.title') }}
76
+ </VcsLabel>
77
+ </v-col>
78
+ <v-col>
79
+ <VcsTextField
80
+ id="basisModuleName"
81
+ placeholder="Base Module"
82
+ :rules="[
83
+ basisModuleExists &&
84
+ (!basisModule.title || basisModule.title === '')
85
+ ? () => 'moduleSelector.configEditor.error'
86
+ : () => true,
87
+ ]"
88
+ v-model="basisModule.title"
89
+ />
90
+ </v-col>
91
+ </v-row>
92
+ <v-row v-if="basisModuleExists" no-gutters>
93
+ <v-col cols="6">
94
+ <VcsLabel required html-for="basisModuleIcon">
95
+ {{ $st('components.style.icon') }}
96
+ </VcsLabel>
97
+ </v-col>
98
+ <v-col>
99
+ <VcsTextField
100
+ id="basisModuleIcon"
101
+ placeholder="mdi-home-city"
102
+ :rules="[
103
+ basisModuleExists &&
104
+ (!basisModule.icon || basisModule.icon === '')
105
+ ? () => 'moduleSelector.configEditor.error'
106
+ : () => true,
107
+ ]"
108
+ v-model="basisModule.icon"
109
+ />
110
+ </v-col>
111
+ </v-row>
112
+ </v-container>
113
+ </VcsFormSection>
114
+ </v-container>
115
+ <v-container v-else class="py-0 px-1">
116
+ <v-breadcrumbs :items="breadcrumbItems" divider="/">
117
+ <template #item="{ item }">
118
+ <v-breadcrumbs-item
119
+ :key="item.title"
120
+ :disabled="item.disabled"
121
+ :href="item.href"
122
+ @click="item.disabled ? null : returnLevel()"
123
+ >
124
+ {{ item.title }}
125
+ </v-breadcrumbs-item>
126
+ </template>
127
+ </v-breadcrumbs>
128
+
129
+ <v-row no-gutters>
130
+ <v-col cols="6">
131
+ <VcsLabel required html-for="groupModuleName">
132
+ {{ $st('moduleSelector.configEditor.groupName') }}
133
+ </VcsLabel>
134
+ </v-col>
135
+ <v-col>
136
+ <VcsTextField
137
+ id="groupModuleName"
138
+ placeholder="Module Name"
139
+ v-model="currentGroup!.title"
140
+ :rules="[(v: string) => !!v || 'moduleSelector.configEditor.error']"
141
+ />
142
+ </v-col>
143
+ </v-row>
144
+ <v-row no-gutters>
145
+ <v-col cols="6">
146
+ <VcsLabel required html-for="groupModuleIcon">
147
+ {{ $st('moduleSelector.configEditor.moduleIcon') }}
148
+ </VcsLabel>
149
+ </v-col>
150
+ <v-col>
151
+ <VcsTextField
152
+ id="groupModuleIcon"
153
+ placeholder="Module Icon"
154
+ v-model="currentGroup!.icon"
155
+ :rules="[
156
+ (v: string) => !!v || 'moduleSelector.configEditor.editorError',
157
+ ]"
158
+ />
159
+ </v-col>
160
+ </v-row>
161
+ </v-container>
162
+ <VcsFormSection
163
+ heading="moduleSelector.configEditor.heading"
164
+ :start-open="true"
165
+ :header-actions="headerActions"
166
+ ><!--V-if currentItem group > cards || wenn url oder empty listItems-->
167
+ <VcsList
168
+ v-if="currentGroup === undefined"
169
+ :items="listItemArray"
170
+ :draggable="true"
171
+ @item-moved="move"
172
+ />
173
+ <VcsList
174
+ v-else
175
+ :items="currentGroup.cards"
176
+ :draggable="true"
177
+ @item-moved="move"
178
+ />
179
+ <v-dialog
180
+ v-if="editingItem"
181
+ :model-value="true"
182
+ width="400"
183
+ :persistent="true"
184
+ >
185
+ <ModuleEditor
186
+ :model-value="editingItem"
187
+ @close="editingItem = undefined"
188
+ @submit="updateItem()"
189
+ />
190
+ </v-dialog>
191
+ </VcsFormSection>
192
+ </AbstractConfigEditor>
193
+ </template>
194
+
195
+ <script lang="ts">
196
+ import {
197
+ AbstractConfigEditor,
198
+ VcsAction,
199
+ VcsCheckbox,
200
+ VcsFormSection,
201
+ VcsLabel,
202
+ VcsList,
203
+ VcsListItem,
204
+ VcsTextField,
205
+ VcsUiApp,
206
+ } from '@vcmap/ui';
207
+ import {
208
+ computed,
209
+ defineComponent,
210
+ inject,
211
+ PropType,
212
+ reactive,
213
+ Ref,
214
+ ref,
215
+ watch,
216
+ } from 'vue';
217
+ import {
218
+ VBreadcrumbs,
219
+ VBreadcrumbsItem,
220
+ VCol,
221
+ VContainer,
222
+ VDialog,
223
+ VRow,
224
+ } from 'vuetify/components';
225
+ import ModuleEditor from './ModuleEditor.vue';
226
+ import { Module, ModuleSelectorConfig, ModuleType } from '../index';
227
+ import getDefaultOptions from '../defaultOptions.js';
228
+ import WindowPositionSettings from './WindowPositionSettings.vue';
229
+
230
+ interface VcsListItemWithLabel extends VcsListItem {
231
+ id: string;
232
+ title: string;
233
+ icon: string;
234
+ type: string;
235
+ moduleUrl?: string;
236
+ cards?: VcsListItemWithLabel[];
237
+ }
238
+
239
+ export default defineComponent({
240
+ name: 'ModuleSelectorConfigEditor',
241
+ components: {
242
+ VcsCheckbox,
243
+ VDialog,
244
+ VcsList,
245
+ VcsFormSection,
246
+ VRow,
247
+ VcsLabel,
248
+ AbstractConfigEditor,
249
+ VcsTextField,
250
+ VCol,
251
+ VContainer,
252
+ ModuleEditor,
253
+ VBreadcrumbs,
254
+ VBreadcrumbsItem,
255
+ WindowPositionSettings,
256
+ },
257
+ props: {
258
+ getConfig: {
259
+ type: Function as PropType<() => ModuleSelectorConfig>,
260
+ required: true,
261
+ },
262
+ setConfig: {
263
+ type: Function as PropType<(c?: ModuleSelectorConfig) => void>,
264
+ required: true,
265
+ },
266
+ },
267
+ setup(props) {
268
+ const app = inject('vcsApp') as VcsUiApp;
269
+ const level = ref<number>(0);
270
+ const localConfig = ref<ModuleSelectorConfig | undefined>(undefined);
271
+ const listItemArray: Ref<VcsListItemWithLabel[]> = ref([]);
272
+ const currentGroup: Ref<VcsListItemWithLabel | undefined> =
273
+ ref(undefined);
274
+ const editingItem: Ref<
275
+ | { id: string; title: string; icon: string; moduleUrl: string }
276
+ | undefined
277
+ > = ref(undefined);
278
+ const listItems: Ref<VcsListItemWithLabel[] | undefined> = ref(undefined);
279
+
280
+ localConfig.value = { ...getDefaultOptions(), ...props.getConfig() };
281
+
282
+ const basisModule = ref();
283
+
284
+ if (localConfig.value?.basisModule) {
285
+ basisModule.value = localConfig.value?.basisModule;
286
+ }
287
+
288
+ const basisModuleExists = computed({
289
+ get() {
290
+ return !!basisModule.value;
291
+ },
292
+ set(value: boolean) {
293
+ if (value) {
294
+ basisModule.value = localConfig.value?.basisModule || { title: '' };
295
+ } else {
296
+ basisModule.value = null;
297
+ }
298
+ },
299
+ });
300
+ const headerActions = reactive<VcsAction[]>([]);
301
+
302
+ function removeIdAndActions(
303
+ item: VcsListItemWithLabel,
304
+ ): Module<ModuleType> {
305
+ if (item.type === 'url') {
306
+ return {
307
+ title: item.title,
308
+ type: item.type,
309
+ icon: item.icon,
310
+ moduleUrl: item.moduleUrl || '',
311
+ } as Module<'url'>;
312
+ } else {
313
+ const updatedCards =
314
+ item.cards?.map((card) => {
315
+ return {
316
+ title: card.title,
317
+ icon: card.icon,
318
+ moduleUrl: card.moduleUrl,
319
+ type: card.type,
320
+ };
321
+ }) || [];
322
+ return {
323
+ title: item.title,
324
+ type: item.type,
325
+ icon: item.icon,
326
+ ...(updatedCards.length ? { cards: updatedCards } : {}),
327
+ } as Module<'group'>;
328
+ }
329
+ }
330
+
331
+ const headerGroupAction = {
332
+ name: 'moduleSelector.configEditor.addGroup',
333
+ callback(): void {
334
+ const newGroup = {
335
+ type: 'group',
336
+ name: '',
337
+ id: '',
338
+ title: 'title',
339
+ icon: 'icon',
340
+ cards: [],
341
+ };
342
+
343
+ listItemArray.value?.push(newGroup);
344
+ currentGroup.value = newGroup;
345
+
346
+ const newIndex = listItemArray.value.indexOf(newGroup);
347
+
348
+ newGroup.id = `${newGroup.title}-${newIndex}`;
349
+
350
+ headerActions.splice(1, 1);
351
+ if (headerActions.length > 0) {
352
+ headerActions[0].icon = '$vcsPlus';
353
+ headerActions[0].title =
354
+ 'moduleSelector.configEditor.TooltipAddModule';
355
+ }
356
+ },
357
+ };
358
+
359
+ function removeIdAndActionsFromArray(
360
+ items: VcsListItemWithLabel[],
361
+ ): Module<ModuleType>[] {
362
+ return items.map(removeIdAndActions);
363
+ }
364
+
365
+ function createListItem(
366
+ moduleInfo: Module<ModuleType>,
367
+ index: number,
368
+ ): VcsListItemWithLabel {
369
+ const listItemObject: VcsListItemWithLabel = {
370
+ id: `${moduleInfo.title}-${index}`,
371
+ name: moduleInfo.title,
372
+ title: moduleInfo.title,
373
+ icon: moduleInfo.icon,
374
+ type: moduleInfo.type,
375
+ actions: [],
376
+ };
377
+ if (moduleInfo.type === 'url') {
378
+ listItemObject.moduleUrl = moduleInfo.moduleUrl;
379
+ listItemObject.actions?.push({
380
+ name: 'edit',
381
+ title: 'moduleSelector.configEditor.TooltipEditModule',
382
+ icon: '$vcsEdit',
383
+ callback(): void {
384
+ if (
385
+ currentGroup.value === undefined &&
386
+ listItemArray.value.length > 0
387
+ ) {
388
+ const item = listItemArray.value.find(
389
+ (i) => i.id === listItemObject.id,
390
+ );
391
+
392
+ if (item) {
393
+ editingItem.value = {
394
+ id: item.id,
395
+ title: item.title,
396
+ icon: item.icon,
397
+ moduleUrl: item.moduleUrl!,
398
+ };
399
+ }
400
+ } else {
401
+ const item = currentGroup.value!.cards!.find(
402
+ (i) => i.id === listItemObject.id,
403
+ );
404
+
405
+ if (item) {
406
+ editingItem.value = {
407
+ id: item.id,
408
+ title: item.title,
409
+ icon: item.icon,
410
+ moduleUrl: item.moduleUrl!,
411
+ };
412
+ }
413
+ }
414
+ },
415
+ });
416
+ }
417
+ if (moduleInfo.type === 'group') {
418
+ listItemObject.actions?.push({
419
+ name: 'editGroup',
420
+ title: 'moduleSelector.configEditor.TooltipEditGroup',
421
+ icon: 'mdi-playlist-edit',
422
+ callback(): void {
423
+ if (listItemArray.value.length > 0) {
424
+ const item = listItemArray.value.find(
425
+ (i) => i.id === listItemObject.id,
426
+ );
427
+ if (item) {
428
+ currentGroup.value = item;
429
+ headerActions.splice(1, 1);
430
+ if (headerActions.length > 0) {
431
+ headerActions[0].icon = '$vcsPlus';
432
+ headerActions[0].title =
433
+ 'moduleSelector.configEditor.TooltipAddModule';
434
+ }
435
+ }
436
+ }
437
+ },
438
+ });
439
+ if (moduleInfo.cards) {
440
+ listItemObject.cards = moduleInfo.cards.map(createListItem);
441
+ }
442
+ }
443
+ listItemObject.actions?.push({
444
+ name: 'linkButton.editor.remove',
445
+ title: 'moduleSelector.configEditor.TooltipDeleteMG',
446
+ icon: '$vcsTrashCan',
447
+ callback(): void {
448
+ if (currentGroup.value) {
449
+ currentGroup.value.cards = currentGroup.value?.cards?.filter(
450
+ (item) => item.id !== listItemObject.id,
451
+ );
452
+ } else {
453
+ listItemArray.value = listItemArray.value?.filter(
454
+ (item) => item.id !== listItemObject.id,
455
+ );
456
+ }
457
+ },
458
+ });
459
+ return listItemObject;
460
+ }
461
+
462
+ if (localConfig.value?.modules) {
463
+ listItemArray.value = localConfig.value.modules.map(createListItem);
464
+ }
465
+
466
+ headerActions.push(
467
+ {
468
+ name: 'moduleSelector.configEditor.add',
469
+ callback(): void {
470
+ editingItem.value = {
471
+ id: '',
472
+ title: 'title',
473
+ icon: 'icon',
474
+ moduleUrl: 'url',
475
+ };
476
+ },
477
+ },
478
+ headerGroupAction,
479
+ );
480
+ watch(localConfig, (config) => {
481
+ if (config?.modules) {
482
+ listItems.value = config.modules.map((moduleInfo, index) =>
483
+ createListItem(moduleInfo, index),
484
+ );
485
+ } else {
486
+ listItems.value = [];
487
+ }
488
+ });
489
+
490
+ const apply = (): void => {
491
+ props.setConfig({
492
+ ...localConfig.value,
493
+ modules: removeIdAndActionsFromArray(listItemArray.value),
494
+ basisModule: basisModule.value,
495
+ });
496
+ };
497
+
498
+ const updateItem = (): void => {
499
+ if (currentGroup.value === undefined) {
500
+ if (editingItem.value) {
501
+ if (editingItem.value?.id !== '') {
502
+ const item = listItemArray.value.find(
503
+ (i) => i.id === editingItem.value?.id,
504
+ );
505
+ if (item) {
506
+ item.title = editingItem.value.title;
507
+ item.icon = editingItem.value.icon;
508
+ item.moduleUrl = editingItem.value.moduleUrl;
509
+ }
510
+ } else {
511
+ listItemArray.value.push({
512
+ id: `${editingItem.value.title}-${listItemArray.value.length}`,
513
+ title: editingItem.value.title,
514
+ name: editingItem.value.title,
515
+ icon: editingItem.value.icon,
516
+ moduleUrl: editingItem.value.moduleUrl,
517
+ type: 'url',
518
+ });
519
+
520
+ const filteredItems = removeIdAndActionsFromArray(
521
+ listItemArray.value,
522
+ );
523
+
524
+ listItemArray.value = filteredItems.map(createListItem);
525
+ }
526
+ }
527
+ } else if (editingItem.value && editingItem.value?.id !== '') {
528
+ const item = currentGroup.value.cards!.find(
529
+ (i) => i.id === editingItem.value!.id,
530
+ );
531
+ if (item) {
532
+ item.title = editingItem.value.title;
533
+ item.icon = editingItem.value.icon;
534
+ item.moduleUrl = editingItem.value.moduleUrl;
535
+ }
536
+
537
+ let groupId;
538
+ if (currentGroup.value?.id || currentGroup.value?.id !== '') {
539
+ groupId = currentGroup.value.id;
540
+ } else {
541
+ groupId = `${currentGroup.value.title}-${listItemArray.value.length - 1}`;
542
+ }
543
+ currentGroup.value = listItemArray.value.find(
544
+ (i) => i.id === groupId,
545
+ );
546
+ } else {
547
+ currentGroup.value.cards!.push({
548
+ id: `${editingItem.value!.title}-${currentGroup.value?.cards?.length}`,
549
+ title: editingItem.value!.title,
550
+ name: editingItem.value!.title,
551
+ icon: editingItem.value!.icon,
552
+ moduleUrl: editingItem.value!.moduleUrl,
553
+ type: 'url',
554
+ });
555
+ const filteredItems = removeIdAndActionsFromArray(
556
+ listItemArray.value,
557
+ );
558
+
559
+ listItemArray.value = filteredItems.map(createListItem);
560
+
561
+ let groupId;
562
+ if (currentGroup.value?.id || currentGroup.value?.id !== '') {
563
+ groupId = currentGroup.value.id;
564
+ } else {
565
+ groupId = `${currentGroup.value?.title}-${listItemArray.value.length - 1}`;
566
+ }
567
+ currentGroup.value = listItemArray.value?.find(
568
+ (item) => item.id === groupId,
569
+ );
570
+ }
571
+
572
+ editingItem.value = undefined;
573
+ };
574
+
575
+ const returnLevel = (): void => {
576
+ currentGroup.value = undefined;
577
+ if (headerActions.length > 0) {
578
+ delete headerActions[0].icon;
579
+ delete headerActions[0].title;
580
+ }
581
+ headerActions.push(headerGroupAction);
582
+ };
583
+
584
+ const breadcrumbItems = ref([
585
+ {
586
+ title: app.vueI18n.t(
587
+ 'moduleSelector.configEditor.breadcrumbs.overview',
588
+ ),
589
+ disabled: false,
590
+ href: '#',
591
+ },
592
+ {
593
+ title: app.vueI18n.t('moduleSelector.configEditor.breadcrumbs.group'),
594
+ disabled: true,
595
+ href: '#',
596
+ },
597
+ ]);
598
+ watch(currentGroup, (currentG) => {
599
+ if (currentG && currentG.title) {
600
+ breadcrumbItems.value[1].title = `${app.vueI18n.t('moduleSelector.configEditor.breadcrumbs.group')}: ${currentG.title}`;
601
+ currentG.name = currentG.title;
602
+ }
603
+ });
604
+ return {
605
+ editingItem,
606
+ localConfig,
607
+ listItemArray,
608
+ breadcrumbItems,
609
+ currentGroup,
610
+ apply,
611
+ returnLevel,
612
+ updateItem,
613
+ level,
614
+ listItems: listItems as unknown as unknown[],
615
+ basisModule,
616
+ basisModuleExists,
617
+ headerActions,
618
+ move({
619
+ item,
620
+ targetIndex,
621
+ }: {
622
+ item: VcsListItemWithLabel;
623
+ targetIndex: number;
624
+ }): void {
625
+ if (listItemArray.value && currentGroup.value === undefined) {
626
+ let target = targetIndex;
627
+ target = target >= 0 ? target : 0;
628
+ target =
629
+ target < listItemArray.value?.length
630
+ ? target
631
+ : listItemArray.value.length - 1;
632
+ const from = listItemArray.value?.indexOf(item);
633
+ if (from !== target) {
634
+ listItemArray.value?.splice(from, 1);
635
+ listItemArray.value?.splice(target, 0, item);
636
+ }
637
+ } else if ((currentGroup.value?.cards?.length ?? 0) > 1) {
638
+ let target = targetIndex;
639
+ target = target >= 0 ? target : 0;
640
+ target =
641
+ target < currentGroup.value!.cards!.length
642
+ ? target
643
+ : currentGroup.value!.cards!.length - 1;
644
+ const from = currentGroup.value!.cards!.indexOf(item);
645
+ if (from && from !== target) {
646
+ currentGroup.value!.cards!.splice(from, 1);
647
+ currentGroup.value!.cards!.splice(target, 0, item);
648
+ }
649
+ }
650
+ },
651
+ };
652
+ },
653
+ });
654
+ </script>
655
+
656
+ <style scoped>
657
+ .v-breadcrumbs {
658
+ padding: 16px 0 4px 0;
659
+ }
660
+ </style>