@vcmap/module-selector 2.0.0 → 2.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcmap/module-selector",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,13 +45,13 @@
45
45
  ],
46
46
  "prettier": "@vcsuite/eslint-config/prettier.js",
47
47
  "peerDependencies": {
48
- "@vcmap/core": "^6.0.0-rc.10",
49
- "@vcmap/ui": "^6.0.0-rc.9",
48
+ "@vcmap/core": "^6.0.0",
49
+ "@vcmap/ui": "^6.0.1",
50
50
  "vue": "~3.4.38",
51
- "vuetify": "^3.7.2"
51
+ "vuetify": "^3.7.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@vcmap/plugin-cli": "^4.0.0-rc.5",
54
+ "@vcmap/plugin-cli": "^4.0.0",
55
55
  "@vcsuite/eslint-config": "^3.0.8",
56
56
  "@vitest/coverage-v8": "^2.1.1",
57
57
  "jest-canvas-mock": "^2.5.2",
@@ -61,5 +61,12 @@
61
61
  "vitest": "^2.1.1",
62
62
  "vue-tsc": "^2.1.6"
63
63
  },
64
- "mapVersion": "^6.0"
64
+ "mapVersion": "^6.0",
65
+ "dependencies": {
66
+ "@vcsuite/logger": "^1.0.1",
67
+ "@vcsuite/parsers": "^2.0.0",
68
+ "fast-deep-equal": "^3.1.3",
69
+ "@vcsuite/publisher-sdk": "^5.2.12",
70
+ "node-fetch": "^2.7.0"
71
+ }
65
72
  }
@@ -77,7 +77,15 @@
77
77
  <VcsFormButton
78
78
  variant="filled"
79
79
  class="ma-5"
80
- @click="startApplication"
80
+ @click="
81
+ startApplication(
82
+ plugin.selectedMainModuleIndex.value,
83
+ plugin.selectedNestedModuleIndex.value,
84
+ app,
85
+ modules,
86
+ basisModule,
87
+ )
88
+ "
81
89
  :disabled="isButtonDisabled"
82
90
  >
83
91
  {{ $st('moduleSelector.startButton') }}
@@ -99,14 +107,14 @@
99
107
  } from 'vuetify/components';
100
108
  import { VcsFormButton, VcsUiApp } from '@vcmap/ui';
101
109
  import { computed, defineComponent, inject, PropType } from 'vue';
102
- import { loadModule, unloadModule } from './moduleHelper.js';
103
110
  import { name } from '../package.json';
104
111
  import { BasisModule, ModuleSelectorPlugin, ModuleType } from './index.js';
105
112
  import type { Module } from './index.ts';
113
+ import { startApplication } from './moduleHelper';
106
114
 
107
- export const windowIdModuleSelector = 'moduleSelector_window_id';
108
115
  export default defineComponent({
109
116
  name: 'ModuleSelector',
117
+ methods: { startApplication },
110
118
  components: {
111
119
  VSheet,
112
120
  VContainer,
@@ -168,29 +176,6 @@
168
176
  plugin.selectedMainModuleIndex.value = null;
169
177
  };
170
178
 
171
- const startApplication = async (): Promise<void> => {
172
- await unloadModule(app);
173
-
174
- const mainModuleIndex = plugin.selectedMainModuleIndex.value;
175
- const nestedModuleIndex = plugin.selectedNestedModuleIndex.value;
176
-
177
- if (mainModuleIndex === null && !props.basisModule) return;
178
-
179
- const mainModule = modules[mainModuleIndex!];
180
- const nestedModule =
181
- mainModule?.type === 'group'
182
- ? mainModule.cards?.[nestedModuleIndex!]
183
- : null;
184
-
185
- if (nestedModule?.moduleUrl) {
186
- await loadModule(nestedModule.moduleUrl, app);
187
- } else if (mainModule?.type === 'url') {
188
- await loadModule(mainModule.moduleUrl, app);
189
- }
190
-
191
- app.windowManager.remove(windowIdModuleSelector);
192
- };
193
-
194
179
  const isButtonDisabled = computed(() => {
195
180
  if (!props.requireModuleSelection) return false;
196
181
 
@@ -212,8 +197,8 @@
212
197
  selectModule,
213
198
  selectNestedModule,
214
199
  showMainCards,
215
- startApplication,
216
200
  isButtonDisabled,
201
+ app,
217
202
  };
218
203
  },
219
204
  });
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <v-card>
3
+ <VcsFormSection>
4
+ <v-form ref="form">
5
+ <v-container class="px-2 pt-0 pb-2">
6
+ <v-row no-gutters>
7
+ <v-col cols="6">
8
+ <VcsLabel html-for="layerName">
9
+ {{ $st('layerSlider.configEditor.layerName') }}
10
+ </VcsLabel>
11
+ </v-col>
12
+ <v-col cols="6">
13
+ <VcsSelect
14
+ id="layerName"
15
+ :items="moduleLocal"
16
+ placeholder="layer"
17
+ v-model="localModuleOptions.moduleName"
18
+ />
19
+ </v-col>
20
+ </v-row>
21
+ </v-container>
22
+ </v-form>
23
+ <v-divider />
24
+ <div class="d-flex pa-2">
25
+ <VcsFormButton @click="$emit('close')">
26
+ {{ $st('components.close') }}
27
+ </VcsFormButton>
28
+ <VcsFormButton
29
+ class="justify-end relativePosition"
30
+ :disabled="!isFormValid"
31
+ @click="
32
+ () => {
33
+ $emit('update:modelValue', localModuleOptions);
34
+ $emit('close');
35
+ }
36
+ "
37
+ variant="filled"
38
+ >
39
+ {{ $st('components.apply') }}
40
+ </VcsFormButton>
41
+ </div>
42
+ </VcsFormSection>
43
+ </v-card>
44
+ </template>
45
+
46
+ <script lang="ts">
47
+ import {
48
+ VcsFormButton,
49
+ VcsFormSection,
50
+ VcsLabel,
51
+ VcsSelect,
52
+ } from '@vcmap/ui';
53
+ import {
54
+ VCard,
55
+ VDivider,
56
+ VContainer,
57
+ VForm,
58
+ VCol,
59
+ VRow,
60
+ } from 'vuetify/components';
61
+ import { defineComponent, PropType, ref } from 'vue';
62
+
63
+ interface ModuleOptions {
64
+ moduleName: string | undefined;
65
+ }
66
+
67
+ export default defineComponent({
68
+ name: 'ModuleSelectorEdit',
69
+ title: 'Module Editor',
70
+ components: {
71
+ VcsSelect,
72
+ VcsLabel,
73
+ VCol,
74
+ VRow,
75
+ VForm,
76
+ VContainer,
77
+ VcsFormButton,
78
+ VDivider,
79
+ VCard,
80
+ VcsFormSection,
81
+ },
82
+ props: {
83
+ modelValue: {
84
+ type: Object as PropType<ModuleOptions>,
85
+ required: true,
86
+ },
87
+ },
88
+ setup(props) {
89
+ const localModuleOptions = ref(structuredClone(props.modelValue));
90
+ const isFormValid = ref(false);
91
+
92
+ const moduleLocal = ref();
93
+
94
+ return {
95
+ localModuleOptions,
96
+ moduleLocal,
97
+ isFormValid,
98
+ };
99
+ },
100
+ });
101
+ </script>
102
+
103
+ <style>
104
+ .relativePosition {
105
+ margin-left: 7em;
106
+ }
107
+ </style>
@@ -0,0 +1,390 @@
1
+ <template>
2
+ <v-card>
3
+ <div>
4
+ <VcsDataTable
5
+ v-if="isItemIdEmpty"
6
+ base-component="VDataTableServer"
7
+ :loading="loading"
8
+ :items="items"
9
+ :server-items-length="totalItems"
10
+ :server-pages-length="totalPages"
11
+ :searchbar-placeholder="
12
+ $t('moduleSelector.configEditor.moduleCloudPicker.searchName')
13
+ "
14
+ :headers="headers"
15
+ select-strategy="single"
16
+ return-object
17
+ show-select
18
+ item-value="_id"
19
+ v-model="selected"
20
+ @update:items="getItems"
21
+ >
22
+ <!-- eslint-disable-next-line -->
23
+ <template v-slot:item.type="{ item }">
24
+ <v-icon v-bind="{ ...$attrs }">
25
+ {{ typeIcon(item.type) }}
26
+ </v-icon>
27
+ <v-tooltip activator="parent" location="right">
28
+ {{ $t(`appConfigurator.dialogs.vcsModuleTable.${item.type}`) }}
29
+ </v-tooltip>
30
+ </template>
31
+ <!-- eslint-disable-next-line -->
32
+ <template v-slot:item.createdAt="{ item }">
33
+ {{ formatDate(item.createdAt, $i18n.locale) }}
34
+ </template>
35
+ <!-- eslint-disable-next-line -->
36
+ <template v-slot:item.updatedAt="{ item }">
37
+ {{ formatDate(item.updatedAt, $i18n.locale) }}
38
+ </template>
39
+ </VcsDataTable>
40
+ <VcsFormSection v-else>
41
+ <v-form v-model="isFValid" ref="form">
42
+ <v-container class="px-2 pt-0 pb-2">
43
+ <v-row no-gutters class="px-2">
44
+ <v-col cols="2">
45
+ <VcsLabel required html-for="moduleName">
46
+ {{ $st('moduleSelector.configEditor.moduleName') }}
47
+ </VcsLabel>
48
+ </v-col>
49
+ <v-col cols="4">
50
+ <VcsTextField
51
+ id="moduleName"
52
+ placeholder="Module Name"
53
+ v-model="selectedItem.title"
54
+ :rules="[requiredRule]"
55
+ />
56
+ </v-col>
57
+ </v-row>
58
+ <v-row no-gutters class="px-2">
59
+ <v-col cols="2">
60
+ <VcsLabel required html-for="moduleIcon">
61
+ {{ $st('moduleSelector.configEditor.moduleIcon') }}
62
+ </VcsLabel>
63
+ </v-col>
64
+ <v-col cols="4">
65
+ <VcsTextField
66
+ id="moduleIcon"
67
+ placeholder="Icon Name"
68
+ v-model="selectedItem.icon"
69
+ :rules="[requiredRule]"
70
+ />
71
+ </v-col>
72
+ </v-row>
73
+ </v-container>
74
+ </v-form>
75
+ </VcsFormSection>
76
+ <div class="d-flex px-2">
77
+ <VcsFormButton
78
+ v-if="isItemIdEmpty"
79
+ variant="filled"
80
+ :disabled="selected[0] === undefined"
81
+ class="ma-2"
82
+ @click="addSelected"
83
+ tooltip="appConfigurator.dialogs.addTooltip"
84
+ tooltip-position="bottom"
85
+ >
86
+ {{ $t('moduleSelector.configEditor.moduleCloudPicker.continue') }}
87
+ </VcsFormButton>
88
+ <VcsFormButton
89
+ v-if="!isItemIdEmpty"
90
+ :disabled="!isFValid"
91
+ variant="filled"
92
+ class="ma-2"
93
+ @click="
94
+ () => {
95
+ $emit('submit', selectedItem);
96
+ }
97
+ "
98
+ >
99
+ {{ $t('components.apply') }}
100
+ </VcsFormButton>
101
+ <VcsFormButton
102
+ v-if="!isItemIdEmpty"
103
+ @click="removeSelected"
104
+ class="ma-2"
105
+ >
106
+ {{ $t('moduleSelector.configEditor.moduleCloudPicker.back') }}
107
+ </VcsFormButton>
108
+ <VcsFormButton class="ma-2" @click="close">
109
+ {{ $t('components.cancel') }}
110
+ </VcsFormButton>
111
+ </div>
112
+ </div>
113
+ </v-card>
114
+ </template>
115
+
116
+ <script lang="ts">
117
+ import {
118
+ UpdateItemsEvent,
119
+ VcsDataTable,
120
+ VcsFormButton,
121
+ VcsUiApp,
122
+ VcsLabel,
123
+ VcsTextField,
124
+ VcsFormSection,
125
+ VcsPlugin,
126
+ } from '@vcmap/ui';
127
+ import { computed, defineComponent, inject, PropType, ref } from 'vue';
128
+ import {
129
+ VIcon,
130
+ VTooltip,
131
+ VCard,
132
+ VRow,
133
+ VCol,
134
+ VContainer,
135
+ VForm,
136
+ } from 'vuetify/components';
137
+ import {
138
+ getProjectModules,
139
+ type GetProjectModulesResponse,
140
+ } from '@vcsuite/publisher-sdk';
141
+ import { VcsModule, VcsModuleConfig } from '@vcmap/core';
142
+ import { getLogger } from '@vcsuite/logger';
143
+ import { name as pluginName } from '../../package.json';
144
+ import { type ModuleSelectorPlugin } from '../index.js';
145
+ import { setupPublisherSDK } from './setupPublisherSDK';
146
+
147
+ type ModuleTableItem = GetProjectModulesResponse['items'][number] & {
148
+ type: string;
149
+ disabled: boolean;
150
+ };
151
+
152
+ /**
153
+ * Adds a config as module to the app
154
+ * @param app
155
+ * @param config
156
+ */
157
+ export async function loadConfig(
158
+ app: VcsUiApp,
159
+ config: VcsModuleConfig,
160
+ ): Promise<void> {
161
+ if (config?._id && !app.getModuleById(config._id)) {
162
+ const module = new VcsModule(config);
163
+ config._id = config._id ?? module._id;
164
+ await app.addModule(module);
165
+ }
166
+ }
167
+
168
+ function formatDate(date: string, locale: string): string {
169
+ return new Date(date).toLocaleString(locale);
170
+ }
171
+
172
+ function getDefaultTableHeaders(): {
173
+ title: string;
174
+ value: string;
175
+ sortable?: boolean;
176
+ sort?: (a: string, b: string) => number | null;
177
+ }[] {
178
+ return [
179
+ {
180
+ title: 'moduleSelector.configEditor.moduleCloudPicker.headers.name',
181
+ value: 'name',
182
+ },
183
+ {
184
+ title: 'moduleSelector.configEditor.moduleCloudPicker.headers.type',
185
+ value: 'type',
186
+ sortable: false,
187
+ },
188
+ {
189
+ title:
190
+ 'moduleSelector.configEditor.moduleCloudPicker.headers.description',
191
+ value: 'description',
192
+ },
193
+ {
194
+ title:
195
+ 'moduleSelector.configEditor.moduleCloudPicker.headers.createdAt',
196
+ value: 'createdAt',
197
+ sort: (a: string, b: string): number =>
198
+ new Date(b).getTime() - new Date(a).getTime(),
199
+ },
200
+ {
201
+ title:
202
+ 'moduleSelector.configEditor.moduleCloudPicker.headers.updatedAt',
203
+ value: 'updatedAt',
204
+ sort: (a: string, b: string): number =>
205
+ new Date(b).getTime() - new Date(a).getTime(),
206
+ },
207
+ {
208
+ title:
209
+ 'moduleSelector.configEditor.moduleCloudPicker.headers.createdBy',
210
+ value: 'createdBy',
211
+ },
212
+ {
213
+ title:
214
+ 'moduleSelector.configEditor.moduleCloudPicker.headers.updatedBy',
215
+ value: 'updatedBy',
216
+ },
217
+ ];
218
+ }
219
+
220
+ export const windowIdVcsModuleTable = 'vcsModuleTable_window_id';
221
+ export default defineComponent({
222
+ name: 'CloudModuleSelector',
223
+ components: {
224
+ VcsDataTable,
225
+ VcsFormButton,
226
+ VTooltip,
227
+ VIcon,
228
+ VCard,
229
+ VcsLabel,
230
+ VcsTextField,
231
+ VcsFormSection,
232
+ VRow,
233
+ VCol,
234
+ VForm,
235
+ VContainer,
236
+ },
237
+ props: {
238
+ modelValue: {
239
+ type: Object as PropType<{
240
+ id: string;
241
+ title: string;
242
+ icon: string;
243
+ moduleUrl: string;
244
+ }>,
245
+ required: true,
246
+ },
247
+ },
248
+ setup(props, { emit }) {
249
+ const app = inject<VcsUiApp>('vcsApp')!;
250
+ const plugin = app.plugins.getByKey(pluginName)! as ModuleSelectorPlugin;
251
+ const appConfigurator = app.plugins.getByKey(
252
+ '@vcmap/app-configurator',
253
+ ) as VcsPlugin<object, object> & {
254
+ getUserName(userId: string): string;
255
+ config: object & { projectId: string };
256
+ };
257
+
258
+ setupPublisherSDK(appConfigurator.config || plugin.config);
259
+
260
+ const loading = ref(true);
261
+ const items = ref<ModuleTableItem[]>([]);
262
+ const totalItems = ref(0);
263
+ const totalPages = ref(0);
264
+ const selected = ref<
265
+ [
266
+ | { _id: string; name: string; icon: string; moduleUrl: string }
267
+ | undefined,
268
+ ]
269
+ >([undefined]);
270
+ const headers = getDefaultTableHeaders();
271
+ const configMap = new Map<string, VcsModuleConfig>();
272
+ const selectedItem = ref<{
273
+ id: string;
274
+ title: string;
275
+ icon: string;
276
+ moduleUrl: string;
277
+ }>(props.modelValue);
278
+ const isItemIdEmpty = computed(() => selectedItem.value.id === '');
279
+
280
+ const isFValid = ref(false);
281
+
282
+ const validateForm = (): void => {
283
+ isFValid.value =
284
+ !!selectedItem.value?.title && !!selectedItem.value?.icon;
285
+ };
286
+
287
+ const TypeIcons = {
288
+ standalone: '$vcsGlobeNature',
289
+ extension: '$vcsShapes',
290
+ };
291
+
292
+ function getItems(options: UpdateItemsEvent): void {
293
+ const { itemsPerPage, page, search, sortBy, sortDesc } = options;
294
+ loading.value = true;
295
+ if (appConfigurator?.config?.projectId) {
296
+ getProjectModules({
297
+ projectId: appConfigurator.config.projectId,
298
+ limit: itemsPerPage,
299
+ page: page - 1,
300
+ name: search,
301
+ orderBy: sortBy,
302
+ sort: sortDesc?.[0] ? 'desc' : 'asc',
303
+ })
304
+ .then((data) => {
305
+ items.value = data.items.map((i) => {
306
+ const dataTableEntry = {
307
+ name: i.name,
308
+ description: i.description,
309
+ createdAt: i.createdAt,
310
+ updatedAt: i.updatedAt,
311
+ createdBy:
312
+ appConfigurator?.getUserName(i.createdBy) || i.createdBy,
313
+ updatedBy:
314
+ appConfigurator?.getUserName(i.updatedBy) || i.updatedBy,
315
+
316
+ _id: i._id,
317
+ } as ModuleTableItem;
318
+
319
+ if ((i.maps?.length ?? 0) > 0 && (i.layers?.length ?? 0) > 0) {
320
+ dataTableEntry.type = 'standalone';
321
+ } else {
322
+ dataTableEntry.type = 'extension';
323
+ }
324
+ dataTableEntry.disabled = !!app.getModuleById(i._id);
325
+ configMap.set(i._id, i as VcsModuleConfig);
326
+ return dataTableEntry;
327
+ });
328
+ totalItems.value = data.totalCount;
329
+ totalPages.value = data.totalPages;
330
+ })
331
+ .catch((err) => {
332
+ getLogger('CloudModuleSelector.vue').error(
333
+ 'Failed to load project modules.',
334
+ err,
335
+ );
336
+ })
337
+ .finally(() => {
338
+ loading.value = false;
339
+ });
340
+ }
341
+ }
342
+
343
+ const close = (): void => {
344
+ emit('close');
345
+ };
346
+
347
+ const addSelected = (): void => {
348
+ if (selected.value.length > 0 && selected.value[0]?._id !== '') {
349
+ selectedItem.value.id = selected.value[0]!._id;
350
+ selectedItem.value.title = selected.value[0]!.name;
351
+ selectedItem.value.icon = '';
352
+ selectedItem.value.moduleUrl = `configs/${selected.value[0]!._id}.json`;
353
+ }
354
+ };
355
+ const removeSelected = (): void => {
356
+ selectedItem.value.id = '';
357
+ selectedItem.value.title = '';
358
+ selectedItem.value.icon = '';
359
+ selectedItem.value.moduleUrl = '';
360
+ };
361
+
362
+ const requiredRule = (value: string): string | boolean =>
363
+ !!value || 'moduleSelector.configEditor.editorError';
364
+
365
+ return {
366
+ selectedItem,
367
+ isItemIdEmpty,
368
+ loading,
369
+ items,
370
+ totalItems,
371
+ totalPages,
372
+ headers,
373
+ selected,
374
+ typeIcon(type: keyof typeof TypeIcons): string {
375
+ return TypeIcons[type];
376
+ },
377
+ formatDate,
378
+ getItems,
379
+ close,
380
+ addSelected,
381
+ isFValid,
382
+ validateForm,
383
+ removeSelected,
384
+ requiredRule,
385
+ };
386
+ },
387
+ });
388
+ </script>
389
+
390
+ <style scoped></style>