@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.
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <v-container class="py-0 px-1">
3
+ <v-row
4
+ no-gutters
5
+ v-for="{ key, checkbox, input, placeholder } in inputs"
6
+ :key="key"
7
+ >
8
+ <v-col>
9
+ <VcsCheckbox
10
+ :label="`appConfigurator.editors.featureInfo.window.position.${key}`"
11
+ v-model="checkbox.value"
12
+ />
13
+ </v-col>
14
+ <v-col>
15
+ <VcsTextField
16
+ clearable
17
+ :disabled="!checkbox.value"
18
+ :placeholder="placeholder"
19
+ v-model.trim="input.value"
20
+ />
21
+ </v-col>
22
+ </v-row>
23
+ </v-container>
24
+ </template>
25
+ <script lang="ts">
26
+ import {
27
+ computed,
28
+ defineComponent,
29
+ PropType,
30
+ Ref,
31
+ WritableComputedRef,
32
+ } from 'vue';
33
+ import { VContainer, VRow, VCol } from 'vuetify/components';
34
+ import {
35
+ useModelHasProperty,
36
+ useProxiedComplexModel,
37
+ VcsCheckbox,
38
+ VcsTextField,
39
+ WindowPositionOptions,
40
+ } from '@vcmap/ui';
41
+
42
+ export default defineComponent({
43
+ name: 'WindowPositionSettings',
44
+ components: {
45
+ VContainer,
46
+ VRow,
47
+ VCol,
48
+ VcsCheckbox,
49
+ VcsTextField,
50
+ },
51
+ props: {
52
+ modelValue: {
53
+ type: Object as PropType<WindowPositionOptions>,
54
+ required: true,
55
+ },
56
+ positionKeys: {
57
+ type: Array as PropType<(keyof WindowPositionOptions)[]>,
58
+ default: () => ['height', 'maxHeight', 'width', 'maxWidth'],
59
+ },
60
+ },
61
+ setup(props, { emit }) {
62
+ const localValue: Ref<WindowPositionOptions> = useProxiedComplexModel(
63
+ props,
64
+ 'modelValue',
65
+ emit,
66
+ );
67
+
68
+ const inputs = computed(() => {
69
+ return props.positionKeys.reduce(
70
+ (acc, key) => {
71
+ acc[key] = {
72
+ key,
73
+ checkbox: useModelHasProperty(localValue, key, null),
74
+ input: computed({
75
+ get() {
76
+ return localValue.value[key];
77
+ },
78
+ set(value) {
79
+ localValue.value[key] = value;
80
+ },
81
+ }),
82
+ placeholder: 'auto',
83
+ };
84
+ return acc;
85
+ },
86
+ {} as Record<
87
+ keyof WindowPositionOptions,
88
+ {
89
+ key: keyof WindowPositionOptions;
90
+ checkbox: WritableComputedRef<boolean>;
91
+ input: WritableComputedRef<string | number | undefined>;
92
+ placeholder: string | null | undefined;
93
+ }
94
+ >,
95
+ );
96
+ });
97
+
98
+ return {
99
+ inputs,
100
+ };
101
+ },
102
+ });
103
+ </script>
104
+
105
+ <style scoped></style>
@@ -0,0 +1,25 @@
1
+ import { authorize, setServerUrl } from '@vcsuite/publisher-sdk';
2
+ import { getLogger } from '@vcsuite/logger';
3
+ import { name } from '../../package.json';
4
+ import { ModuleSelectorConfig } from '../index';
5
+
6
+ // eslint-disable-next-line import/prefer-default-export
7
+ export function setupPublisherSDK(options: ModuleSelectorConfig): void {
8
+ try {
9
+ if (options.serverUrl) {
10
+ const serverUrl = new URL(
11
+ options.serverUrl,
12
+ window.location.href,
13
+ ).toString();
14
+ setServerUrl(serverUrl);
15
+
16
+ const token = options.token || localStorage.getItem('Meteor.loginToken');
17
+ if (token) {
18
+ authorize(token);
19
+ getLogger(name).info('Publisher connection successful');
20
+ }
21
+ }
22
+ } catch (err) {
23
+ getLogger(name).error('Failed to connect to publisher', err);
24
+ }
25
+ }
@@ -2,10 +2,17 @@ import { ModuleSelectorConfig } from './index.js';
2
2
 
3
3
  export default (): ModuleSelectorConfig => ({
4
4
  windowTitle: 'moduleSelector.title',
5
+ isActiveOnStart: true,
6
+ requireModuleSelection: false,
5
7
  position: {
6
8
  left: '33%',
7
9
  right: '33%',
8
10
  top: '10%',
9
11
  },
12
+ basisModule: undefined,
10
13
  modules: [],
14
+ serverUrl: '',
15
+ projectId: '',
16
+ appId: '',
17
+ token: '',
11
18
  });
package/src/index.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import {
2
+ ButtonLocation,
3
+ createToggleAction,
4
+ PluginConfigEditor,
2
5
  VcsPlugin,
3
6
  VcsUiApp,
4
- WindowSlot,
5
- ButtonLocation,
6
7
  WindowPositionOptions,
7
- createToggleAction,
8
+ WindowSlot,
8
9
  } from '@vcmap/ui';
9
10
  import { Ref, ref } from 'vue';
10
- import { name, version, mapVersion } from '../package.json';
11
- import moduleSelector, { windowIdModuleSelector } from './ModuleSelector.vue';
11
+ import { parseBoolean, parseInteger } from '@vcsuite/parsers';
12
+ import equal from 'fast-deep-equal';
13
+ import { mapVersion, name, version } from '../package.json';
14
+ import moduleSelector from './ModuleSelector.vue';
12
15
  import getDefaultOptions from './defaultOptions.js';
16
+ import ModuleSelectorConfigEditor from './config/ModuleSelectorConfigEditor.vue';
17
+ import { startApplication, windowIdModuleSelector } from './moduleHelper';
13
18
 
14
19
  export type BasisModule = {
15
20
  title: string;
@@ -24,19 +29,33 @@ export type Module<T extends ModuleType> = T extends 'group'
24
29
  ? BasisModule & { moduleUrl: string; type: T }
25
30
  : never;
26
31
 
27
- type PluginConfig = Record<never, never>;
28
- type PluginState = Record<never, never>;
29
-
30
- export type ModuleSelectorConfig = PluginConfig & {
32
+ export type ModuleSelectorConfig = {
31
33
  windowTitle?: string;
32
34
  isActiveOnStart?: boolean;
33
35
  requireModuleSelection?: boolean;
36
+ serverUrl?: string;
37
+ projectId?: string;
38
+ appId?: string;
39
+ token?: string;
34
40
  position?: WindowPositionOptions;
35
41
  basisModule?: BasisModule;
36
- modules: Array<Module<ModuleType>>;
42
+ modules?: Array<Module<ModuleType>>;
43
+ };
44
+
45
+ export type PluginState = {
46
+ // selectedMainModuleIndex
47
+ smi?: number | undefined | null;
48
+ // selectedNestedModuleIndex
49
+ snmi?: number | undefined | null;
50
+ // module elector window open
51
+ w?: boolean;
52
+ modules?: Array<Module<ModuleType>>;
37
53
  };
38
54
 
39
- export type ModuleSelectorPlugin = VcsPlugin<PluginConfig, PluginState> & {
55
+ export type ModuleSelectorPlugin = VcsPlugin<
56
+ Partial<ModuleSelectorConfig>,
57
+ PluginState
58
+ > & {
40
59
  readonly config: ModuleSelectorConfig;
41
60
  selectedMainModuleIndex: Ref<number | undefined | null>;
42
61
  selectedNestedModuleIndex: Ref<number | undefined | null>;
@@ -45,6 +64,7 @@ export type ModuleSelectorPlugin = VcsPlugin<PluginConfig, PluginState> & {
45
64
  export default function plugin(
46
65
  configInput: ModuleSelectorConfig,
47
66
  ): ModuleSelectorPlugin {
67
+ let app: VcsUiApp;
48
68
  return {
49
69
  get name(): string {
50
70
  return name;
@@ -60,7 +80,11 @@ export default function plugin(
60
80
  },
61
81
  selectedMainModuleIndex: ref(undefined),
62
82
  selectedNestedModuleIndex: ref(undefined),
63
- initialize(vcsUiApp: VcsUiApp): Promise<void> {
83
+ async initialize(
84
+ vcsUiApp: VcsUiApp,
85
+ state: PluginState | undefined,
86
+ ): Promise<void> {
87
+ app = vcsUiApp;
64
88
  const moduleSelectorComponent = {
65
89
  id: windowIdModuleSelector,
66
90
  component: moduleSelector,
@@ -68,6 +92,7 @@ export default function plugin(
68
92
  position: this.config.position,
69
93
  state: {
70
94
  headerTitle: this.config.windowTitle,
95
+ infoUrlCallback: app.getHelpUrlCallback('/tools/moduleSelector.html'),
71
96
  },
72
97
  props: {
73
98
  modules: this.config.modules,
@@ -75,6 +100,35 @@ export default function plugin(
75
100
  requireModuleSelection: this.config.requireModuleSelection,
76
101
  },
77
102
  };
103
+ if (state) {
104
+ const smi = parseInteger(state.smi);
105
+ const snmi = parseInteger(state.snmi);
106
+ const w = parseBoolean(state.w);
107
+ const { modules } = this.config;
108
+
109
+ if (smi !== undefined && smi < modules?.length) {
110
+ this.selectedMainModuleIndex.value = smi;
111
+ const selectedModule = modules[smi];
112
+
113
+ if (
114
+ snmi !== undefined &&
115
+ selectedModule?.type === 'group' &&
116
+ snmi < selectedModule?.cards?.length
117
+ ) {
118
+ this.selectedNestedModuleIndex.value = snmi;
119
+ }
120
+ }
121
+
122
+ if (!w) {
123
+ await startApplication(
124
+ this.selectedMainModuleIndex.value,
125
+ this.selectedNestedModuleIndex.value,
126
+ app,
127
+ configInput.modules!,
128
+ configInput.basisModule,
129
+ );
130
+ }
131
+ }
78
132
 
79
133
  const { action } = createToggleAction(
80
134
  {
@@ -83,11 +137,11 @@ export default function plugin(
83
137
  icon: 'mdi-view-grid',
84
138
  },
85
139
  moduleSelectorComponent,
86
- vcsUiApp.windowManager,
140
+ app.windowManager,
87
141
  name,
88
142
  );
89
143
 
90
- vcsUiApp.navbarManager.add(
144
+ app.navbarManager.add(
91
145
  {
92
146
  id: action.name,
93
147
  action,
@@ -96,41 +150,176 @@ export default function plugin(
96
150
  ButtonLocation.PROJECT,
97
151
  );
98
152
 
99
- if (configInput.isActiveOnStart) {
100
- vcsUiApp.windowManager.add(moduleSelectorComponent, name);
153
+ if ((state && state.w) || (!state && configInput.isActiveOnStart)) {
154
+ app.windowManager.add(moduleSelectorComponent, name);
101
155
  }
102
156
 
103
157
  return Promise.resolve();
104
158
  },
105
159
  getDefaultOptions,
160
+ getState(): PluginState {
161
+ const smi = this.selectedMainModuleIndex?.value;
162
+ const snmi = this.selectedNestedModuleIndex?.value;
163
+ const windowState = app.windowManager.has(windowIdModuleSelector);
164
+ const isActive = this.config.isActiveOnStart;
165
+
166
+ return smi !== undefined || snmi !== undefined || windowState !== isActive
167
+ ? { smi, snmi, w: windowState }
168
+ : {};
169
+ },
170
+ getConfigEditors(): PluginConfigEditor<object>[] {
171
+ return [
172
+ {
173
+ component: ModuleSelectorConfigEditor,
174
+ title: 'moduleSelector.name',
175
+ infoUrlCallback: app.getHelpUrlCallback(
176
+ '/components/plugins/moduleSelectorConfig.html',
177
+ 'app-configurator',
178
+ ),
179
+ },
180
+ ];
181
+ },
106
182
  toJSON(): Partial<ModuleSelectorConfig> {
107
183
  const serial: Partial<ModuleSelectorConfig> = {};
108
- if (configInput.windowTitle != null) {
109
- serial.windowTitle = configInput.windowTitle;
184
+ const defaultOptions = getDefaultOptions();
185
+ if (this.config.windowTitle !== defaultOptions.windowTitle) {
186
+ serial.windowTitle = this.config.windowTitle;
187
+ }
188
+
189
+ if (this.config.isActiveOnStart !== defaultOptions.isActiveOnStart) {
190
+ serial.isActiveOnStart = this.config.isActiveOnStart;
191
+ }
192
+
193
+ if (
194
+ this.config.requireModuleSelection !==
195
+ defaultOptions.requireModuleSelection
196
+ ) {
197
+ serial.requireModuleSelection = this.config.requireModuleSelection;
198
+ }
199
+
200
+ if (!equal(this.config.position, defaultOptions.position)) {
201
+ serial.position = this.config.position;
202
+ }
203
+
204
+ if (!equal(this.config.modules, defaultOptions.modules)) {
205
+ serial.modules = this.config.modules;
206
+ }
207
+
208
+ if (!equal(this.config.basisModule, defaultOptions.basisModule)) {
209
+ serial.basisModule = this.config.basisModule;
210
+ }
211
+
212
+ if (configInput.serverUrl != null) {
213
+ serial.serverUrl = configInput.serverUrl;
214
+ }
215
+
216
+ if (configInput.projectId != null) {
217
+ serial.projectId = configInput.projectId;
110
218
  }
111
219
 
112
- if (configInput.position != null) {
113
- serial.position = configInput.position;
220
+ if (configInput.appId != null) {
221
+ serial.appId = configInput.appId;
114
222
  }
115
223
 
116
- if (configInput.modules != null) {
117
- serial.modules = configInput.modules;
224
+ if (configInput.token != null) {
225
+ serial.token = configInput.token;
118
226
  }
227
+
119
228
  return serial;
120
229
  },
121
230
  i18n: {
122
231
  de: {
123
232
  moduleSelector: {
233
+ name: 'Modul Auswahl Editor',
124
234
  title: 'Themenkartenauswahl',
125
235
  startButton: 'Anwendung starten',
126
236
  cardBack: 'Zurück',
237
+ configEditor: {
238
+ moduleCloudPicker: {
239
+ searchName: 'Suche nach Name...',
240
+ continue: 'Weiter',
241
+ back: 'Zurück',
242
+ headers: {
243
+ name: 'Modul Name',
244
+ type: 'Typ',
245
+ description: 'Beschreibung',
246
+ createdAt: 'Erzeugt am',
247
+ updatedAt: 'Aktualisiert am',
248
+ createdBy: 'Erzeugt von',
249
+ updatedBy: 'Aktualisiert von',
250
+ },
251
+ },
252
+ TooltipAddModule: 'Modul hinzufügen',
253
+ TooltipEditModule: 'Modul bearbeiten',
254
+ TooltipEditGroup: 'Modul-Gruppe bearbeiten',
255
+ TooltipDeleteMG: 'Modul/Gruppe löschen',
256
+ error: 'Bitte tragen Sie einen Namen ein',
257
+ editorError: 'Bitte füllen Sie das Feld aus',
258
+ breadcrumbs: { overview: 'Übersicht', group: 'Gruppe' },
259
+ heading: 'Themen- & Modulzuordnung',
260
+ baseModule: 'Basismodul',
261
+ baseModuleCheckbox: 'Basismodul anzeigen',
262
+ title: 'Titel',
263
+ add: 'Module hinzufügen',
264
+ addGroup: 'Module Gruppe hinzufügen',
265
+ moduleName: 'Module Name',
266
+ moduleIcon: 'Icon Name',
267
+ moduleUrl: 'Module URL',
268
+ groupName: 'Gruppen Name',
269
+ back: 'Zurück',
270
+ general: 'Allgemeine Einstellungen',
271
+ isActiveOnStart: 'Themenkartenauswahl beim Start der Karte zeigen',
272
+ requireModuleSelection: 'Modulauswahl erforderlich machen',
273
+ windowTitle: 'Fenster Titel',
274
+ windowPosition: 'Fenster Position',
275
+ },
127
276
  },
128
277
  },
129
278
  en: {
130
279
  moduleSelector: {
280
+ name: 'Module Selector Editor',
131
281
  title: 'Theme map selection',
132
282
  startButton: 'Start application',
133
283
  cardBack: 'Back',
284
+ configEditor: {
285
+ moduleCloudPicker: {
286
+ searchName: 'Search for name...',
287
+ continue: 'Continue',
288
+ back: 'Back',
289
+ headers: {
290
+ name: 'Module name',
291
+ type: 'Type',
292
+ description: 'Description',
293
+ createdAt: 'Created at',
294
+ updatedAt: 'Updated at',
295
+ createdBy: 'Created by',
296
+ updatedBy: 'Created by',
297
+ },
298
+ },
299
+ TooltipAddModule: 'Add module',
300
+ TooltipEditModule: 'Edit module',
301
+ TooltipEditGroup: 'Edit module group',
302
+ TooltipDeleteMG: 'Delete module/group',
303
+ error: 'Please add a name',
304
+ editorError: 'Please fill in the field',
305
+ breadcrumbs: { overview: 'Overview', group: 'Group' },
306
+ heading: 'Topic & module assignment',
307
+ baseModule: 'Base module',
308
+ baseModuleCheckbox: 'Show base module',
309
+ title: 'Title',
310
+ add: 'Add module',
311
+ addGroup: 'Add module Group',
312
+ moduleName: 'Module name',
313
+ moduleIcon: 'Icon name',
314
+ moduleUrl: 'Module URL',
315
+ groupName: 'Group name',
316
+ back: 'Back',
317
+ general: 'General settings',
318
+ isActiveOnStart: 'Show theme map selection on map start',
319
+ requireModuleSelection: 'Require module selection',
320
+ windowTitle: 'Window title',
321
+ windowPosition: 'Window position',
322
+ },
134
323
  },
135
324
  },
136
325
  },
@@ -1,6 +1,7 @@
1
1
  // eslint-disable-next-line import/prefer-default-export
2
2
  import { VcsModule } from '@vcmap/core';
3
3
  import { VcsUiApp } from '@vcmap/ui';
4
+ import type { BasisModule, Module, ModuleType } from './index';
4
5
 
5
6
  export async function loadModule(
6
7
  moduleUrl: string,
@@ -33,3 +34,33 @@ export async function unloadModule(app: VcsUiApp): Promise<void> {
33
34
  await app.maps.setActiveMap(activeMapName || [...app.maps][0]?.name);
34
35
  }
35
36
  }
37
+
38
+ export const windowIdModuleSelector = 'moduleSelector_window_id';
39
+
40
+ export async function startApplication(
41
+ mainModuleIndex: number | undefined | null,
42
+ nestedModuleIndex: number | undefined | null,
43
+ app: VcsUiApp,
44
+ modules: Module<ModuleType>[],
45
+ basisModule?: BasisModule,
46
+ ): Promise<void> {
47
+ await unloadModule(app);
48
+
49
+ if (mainModuleIndex === null && !basisModule) {
50
+ app.windowManager.remove(windowIdModuleSelector);
51
+ return;
52
+ }
53
+
54
+ const mainModule = modules[mainModuleIndex!];
55
+ const nestedModule =
56
+ mainModule?.type === 'group'
57
+ ? mainModule.cards?.[nestedModuleIndex!]
58
+ : null;
59
+
60
+ if (nestedModule?.moduleUrl) {
61
+ await loadModule(nestedModule.moduleUrl, app);
62
+ } else if (mainModule?.type === 'url') {
63
+ await loadModule(mainModule.moduleUrl, app);
64
+ }
65
+ app.windowManager.remove(windowIdModuleSelector);
66
+ }