@vcmap/module-selector 2.0.1 → 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.1",
3
+ "version": "2.0.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -65,6 +65,8 @@
65
65
  "dependencies": {
66
66
  "@vcsuite/logger": "^1.0.1",
67
67
  "@vcsuite/parsers": "^2.0.0",
68
- "fast-deep-equal": "^3.1.3"
68
+ "fast-deep-equal": "^3.1.3",
69
+ "@vcsuite/publisher-sdk": "^5.2.12",
70
+ "node-fetch": "^2.7.0"
69
71
  }
70
72
  }
@@ -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>