@vcmap/ui 5.0.0-rc.22 → 5.0.0-rc.23

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.
Files changed (91) hide show
  1. package/README.md +2 -2
  2. package/app.config.json +5 -0
  3. package/build/buildPreview.js +2 -2
  4. package/config/aerowest.config.json +2 -0
  5. package/config/base.config.json +1 -0
  6. package/config/codes.config.json +2 -0
  7. package/config/dev.config.json +6 -0
  8. package/config/graphFeatureInfo.config.json +3 -1
  9. package/config/projects.config.json +27 -0
  10. package/config/www.config.json +2 -0
  11. package/dist/assets/cesium.js +1 -1
  12. package/dist/assets/{core.a66593.js → core.9342a1.js} +7804 -5352
  13. package/dist/assets/core.js +1 -1
  14. package/dist/assets/index.fd041928.js +1 -0
  15. package/dist/assets/ol.js +1 -1
  16. package/dist/assets/ui.c27597.css +5 -0
  17. package/dist/assets/{ui.d760e4.js → ui.c27597.js} +5055 -4694
  18. package/dist/assets/ui.js +1 -1
  19. package/dist/assets/vue.js +2 -2
  20. package/dist/assets/{vuetify.427322.js → vuetify.2f1432.js} +1 -1
  21. package/dist/assets/vuetify.js +2 -2
  22. package/dist/index.html +1 -1
  23. package/index.js +5 -2
  24. package/package.json +3 -3
  25. package/plugins/@vcmap/project-selector/{ContextsListComponent.vue → ModulesListComponent.vue} +10 -10
  26. package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +15 -15
  27. package/plugins/@vcmap/project-selector/README.md +15 -21
  28. package/plugins/@vcmap/project-selector/config.json +3 -3
  29. package/plugins/@vcmap/project-selector/de.json +3 -0
  30. package/plugins/@vcmap/project-selector/en.json +3 -0
  31. package/plugins/@vcmap/project-selector/index.js +76 -101
  32. package/plugins/@vcmap/simple-graph/index.js +1 -1
  33. package/plugins/@vcmap-show-case/category-tester/Categories.vue +2 -2
  34. package/plugins/@vcmap-show-case/category-tester/Category.vue +1 -4
  35. package/plugins/@vcmap-show-case/config-editor/editor.vue +14 -14
  36. package/plugins/@vcmap-show-case/form-inputs-example/FormInputsExample.vue +18 -1
  37. package/plugins/@vcmap-show-case/form-inputs-example/index.js +1 -0
  38. package/plugins/@vcmap-show-case/table-example/DataTableExample.vue +202 -0
  39. package/plugins/@vcmap-show-case/table-example/README.md +3 -0
  40. package/plugins/@vcmap-show-case/table-example/index.js +47 -0
  41. package/plugins/@vcmap-show-case/table-example/package.json +5 -0
  42. package/src/actions/actionHelper.js +16 -27
  43. package/src/actions/styleSelector.vue +26 -19
  44. package/src/components/form-inputs-controls/VcsDatePicker.vue +111 -0
  45. package/src/components/form-inputs-controls/VcsTextField.vue +18 -7
  46. package/src/components/form-inputs-controls/VcsWizard.vue +3 -1
  47. package/src/components/icons/CheckboxCheckedIcon.vue +1 -1
  48. package/src/components/icons/LegendIcon.vue +10 -60
  49. package/src/components/lists/VcsList.vue +25 -6
  50. package/src/components/tables/VcsDataTable.vue +386 -0
  51. package/src/components/tables/VcsTable.vue +33 -278
  52. package/src/contentTree/contentTreeCollection.js +1 -1
  53. package/src/contentTree/layerContentTreeItem.js +3 -0
  54. package/src/downloadHelper.js +49 -0
  55. package/src/featureInfo/BalloonComponent.vue +9 -8
  56. package/src/featureInfo/abstractFeatureInfoView.js +1 -1
  57. package/src/featureInfo/featureInfo.js +3 -3
  58. package/src/i18n/de.js +8 -0
  59. package/src/i18n/en.js +8 -0
  60. package/src/i18n/i18nCollection.js +22 -22
  61. package/src/init.js +90 -7
  62. package/src/manager/categoryManager/CategoryComponent.vue +56 -47
  63. package/src/manager/categoryManager/CategoryManager.vue +23 -10
  64. package/src/manager/categoryManager/categoryManager.js +11 -11
  65. package/src/manager/navbarManager.js +18 -0
  66. package/src/manager/window/WindowComponent.vue +10 -15
  67. package/src/manager/window/WindowComponentHeader.vue +4 -2
  68. package/src/manager/window/WindowManager.vue +14 -15
  69. package/src/manager/window/windowHelper.js +1 -1
  70. package/src/manager/window/windowManager.js +18 -7
  71. package/src/navigation/mapNavCompass.vue +1 -1
  72. package/src/navigation/mapNavigation.vue +6 -6
  73. package/src/navigation/obliqueRotation.vue +36 -13
  74. package/src/navigation/orientationToolsButton.vue +0 -1
  75. package/src/navigation/overviewMap.js +5 -5
  76. package/src/navigation/vcsZoomButton.vue +37 -11
  77. package/src/pluginHelper.js +20 -0
  78. package/src/search/search.js +12 -3
  79. package/src/search/searchComponent.vue +15 -0
  80. package/src/state.js +6 -6
  81. package/src/uiConfig.js +3 -3
  82. package/src/vcsUiApp.js +44 -40
  83. package/src/vuePlugins/i18n.js +1 -0
  84. package/start.js +8 -2
  85. package/dist/assets/index.8b833ead.js +0 -1
  86. package/dist/assets/ui.d760e4.css +0 -5
  87. package/map.config.json +0 -44
  88. /package/dist/assets/{cesium.88cffd.js → cesium.166f91.js} +0 -0
  89. /package/dist/assets/{ol.d4539f.js → ol.d2cba3.js} +0 -0
  90. /package/dist/assets/{vue.db5102.js → vue.5d00e9.js} +0 -0
  91. /package/dist/assets/{vuetify.427322.css → vuetify.2f1432.css} +0 -0
@@ -66,14 +66,12 @@
66
66
  VCard,
67
67
  VForm,
68
68
  VTextarea,
69
- VBtn,
70
69
  } from 'vuetify/lib';
71
70
 
72
71
  export default {
73
72
  name: 'CategoryComponent',
74
73
  components: {
75
74
  VcsButton,
76
- VSheet,
77
75
  VVirtualScroll,
78
76
  VListItem,
79
77
  VListItemContent,
@@ -84,7 +82,6 @@
84
82
  VForm,
85
83
  VTextarea,
86
84
  VSheet,
87
- VBtn,
88
85
  },
89
86
  props: {
90
87
  categoryName: {
@@ -129,7 +126,7 @@
129
126
  dialog.value = false;
130
127
  },
131
128
  download() {
132
- const stringObject = JSON.stringify(categoryObject.serializeForContext(app.dynamicContextId), null, 2);
129
+ const stringObject = JSON.stringify(categoryObject.serializeModule(app.dynamicModuleId), null, 2);
133
130
  downloadLink.value = `data:text/json;charset=utf-8,${encodeURIComponent(stringObject)}`;
134
131
  if (downloadLink.value) {
135
132
  nextTick(() => {
@@ -8,10 +8,10 @@
8
8
  v-else
9
9
  />
10
10
 
11
- <VcsButton @click="replaceContext">
11
+ <VcsButton @click="replaceModule">
12
12
  Apply
13
13
  </VcsButton>
14
- <VcsButton @click="removeContext">
14
+ <VcsButton @click="removeModule">
15
15
  Remove
16
16
  </VcsButton>
17
17
  </div>
@@ -20,10 +20,10 @@
20
20
  <script>
21
21
  import { ref, inject } from 'vue';
22
22
  import { VcsButton } from '@vcmap/ui';
23
- import { Context } from '@vcmap/core';
23
+ import { VcsModule } from '@vcmap/core';
24
24
  import { VProgressCircular, VTextarea } from 'vuetify/lib';
25
25
 
26
- const contextId = 'foo';
26
+ const moduleId = 'foo';
27
27
 
28
28
  export default {
29
29
  name: 'Editor',
@@ -35,25 +35,25 @@
35
35
  setup() {
36
36
  /** @type {VcsUiApp} */
37
37
  const app = inject('vcsApp');
38
- const context = app.getContextById(contextId);
39
- const configString = ref(JSON.stringify(context ? context.config : {}, null, 2));
38
+ const module = app.getModuleById(moduleId);
39
+ const configString = ref(JSON.stringify(module ? module.config : {}, null, 2));
40
40
  const loading = ref(false);
41
41
 
42
42
  return {
43
43
  configString,
44
44
  loading,
45
- async replaceContext() {
45
+ async replaceModule() {
46
46
  loading.value = true;
47
47
  const config = JSON.parse(configString.value);
48
- config.id = contextId;
49
- const newContext = new Context(config);
50
- await this.removeContext();
51
- await app.addContext(newContext);
48
+ config.id = moduleId;
49
+ const newModule = new VcsModule(config);
50
+ await this.removeModule();
51
+ await app.addModule(newModule);
52
52
  loading.value = false;
53
53
  },
54
- async removeContext() {
55
- if (app.getContextById(contextId)) {
56
- await app.removeContext(contextId);
54
+ async removeModule() {
55
+ if (app.getModuleById(moduleId)) {
56
+ await app.removeModule(moduleId);
57
57
  }
58
58
  },
59
59
  };
@@ -143,6 +143,19 @@
143
143
  />
144
144
  </v-col>
145
145
  </v-row>
146
+ <v-row
147
+ no-gutters
148
+ align="center"
149
+ >
150
+ <v-col>
151
+ <VcsLabel html-for="dateInput" :dense="dense">
152
+ Date
153
+ </VcsLabel>
154
+ </v-col>
155
+ <v-col>
156
+ <VcsDatePicker id="dateInput" v-model="state.dateInput" />
157
+ </v-col>
158
+ </v-row>
146
159
  </v-container>
147
160
  </template>
148
161
  </VcsFormSection>
@@ -254,7 +267,9 @@
254
267
  </v-row>
255
268
  <v-row no-gutters>
256
269
  <v-col>
257
- <VcsLabel :dense="dense">Text</VcsLabel>
270
+ <VcsLabel :dense="dense">
271
+ Text
272
+ </VcsLabel>
258
273
  </v-col>
259
274
  </v-row>
260
275
  <v-row
@@ -370,6 +385,7 @@
370
385
  VcsFormSection,
371
386
  VcsLabel,
372
387
  VcsTextArea,
388
+ VcsDatePicker,
373
389
  } from '@vcmap/ui';
374
390
  import { VCol, VContainer, VForm, VRow } from 'vuetify/lib';
375
391
  import packageJSON from './package.json';
@@ -391,6 +407,7 @@
391
407
  VRow,
392
408
  VCol,
393
409
  VContainer,
410
+ VcsDatePicker,
394
411
  },
395
412
  props: {
396
413
  actions: {
@@ -84,6 +84,7 @@ export default function (config) {
84
84
  email: '',
85
85
  prependedInput: '',
86
86
  files: [],
87
+ dateInput: '',
87
88
  };
88
89
 
89
90
  /** @type {FormInputsExampleState} */
@@ -0,0 +1,202 @@
1
+ <template>
2
+ <v-sheet>
3
+ <v-sheet class="px-2 d-grid">
4
+ <v-switch
5
+ v-model="selectable"
6
+ label="Selectable"
7
+ />
8
+ <v-switch
9
+ :disabled="!selectable"
10
+ v-model="selectSingle"
11
+ label=" Single Select"
12
+ />
13
+ <v-switch
14
+ v-model="searchable"
15
+ label="Searchable"
16
+ />
17
+ <v-dialog
18
+ v-model="dialog"
19
+ width="400"
20
+ >
21
+ <template #activator="{ on }">
22
+ <vcs-button v-on="on">
23
+ Add An item
24
+ </vcs-button>
25
+ </template>
26
+ <v-card class="pa-2">
27
+ <v-form
28
+ @submit.prevent="add"
29
+ >
30
+ <vcs-text-field
31
+ v-model="newItem.name"
32
+ label="Name"
33
+ :rules="required"
34
+ />
35
+ <vcs-text-field
36
+ v-model="newItem.type"
37
+ label="type"
38
+ :rules="required"
39
+ />
40
+ <vcs-text-field
41
+ v-model="newItem.date"
42
+ type="date"
43
+ label="date"
44
+ />
45
+ <vcs-button
46
+ type="submit"
47
+ >
48
+ Add
49
+ </vcs-button>
50
+ </v-form>
51
+ </v-card>
52
+ </v-dialog>
53
+ </v-sheet>
54
+
55
+ <vcs-data-table
56
+ :items="items"
57
+ item-key="id"
58
+ :headers="headers"
59
+ :show-select="selectable"
60
+ :single-select="selectSingle"
61
+ :show-searchbar="searchable"
62
+ v-model="selected"
63
+ >
64
+ <!-- eslint-disable-next-line -->
65
+ <template v-slot:item.actions="{ item }">
66
+ <VcsActionButtonList
67
+ v-if="item.actions"
68
+ :actions="item.actions"
69
+ :block-overflow="true"
70
+ :overflow-count="2"
71
+ small
72
+ />
73
+ </template>
74
+ </vcs-data-table>
75
+ </v-sheet>
76
+ </template>
77
+
78
+ <script>
79
+ import { VcsDataTable, VcsButton, VcsTextField, VcsActionButtonList } from '@vcmap/ui';
80
+ import {
81
+ VSwitch,
82
+ VSheet,
83
+ VDialog,
84
+ VCard,
85
+ VForm,
86
+ } from 'vuetify/lib';
87
+ import { ref } from 'vue';
88
+
89
+ const defaultHeaders = [
90
+ {
91
+ text: 'Name',
92
+ value: 'name',
93
+ },
94
+ {
95
+ text: 'Type',
96
+ value: 'type',
97
+ },
98
+ {
99
+ text: 'Datum',
100
+ value: 'date',
101
+ sort: (a, b) => new Date(b) - new Date(a),
102
+ },
103
+ { text: 'Actions', value: 'actions', sortable: false },
104
+ ];
105
+
106
+ const defaultItems = [
107
+ {
108
+ id: 0,
109
+ name: 'foo',
110
+ type: 'Foo',
111
+ date: '8/3/2023 9:24',
112
+ actions: [
113
+ {
114
+ name: 'console.log',
115
+ icon: 'mdi-printer',
116
+ callback() {
117
+ console.log('foo action');
118
+ },
119
+ },
120
+ ],
121
+ },
122
+ {
123
+ id: 1,
124
+ name: 'bar',
125
+ type: 'Bar',
126
+ date: '1/8/2022 19:54',
127
+ },
128
+ {
129
+ id: 2,
130
+ name: 'baz',
131
+ type: 'Baz',
132
+ date: '11/2/2012 14:03',
133
+ },
134
+ ];
135
+
136
+ export default {
137
+ name: 'DataTableExample',
138
+ components: {
139
+ VcsDataTable,
140
+ VcsButton,
141
+ VcsTextField,
142
+ VcsActionButtonList,
143
+ VSwitch,
144
+ VSheet,
145
+ VDialog,
146
+ VCard,
147
+ VForm,
148
+ },
149
+ setup() {
150
+ const selectable = ref(true);
151
+ const searchable = ref(true);
152
+ const selectSingle = ref(false);
153
+ const selected = ref([]);
154
+ const items = ref(defaultItems);
155
+ const headers = ref(defaultHeaders);
156
+ const newItem = ref({
157
+ id: items.value.length,
158
+ name: 'foo',
159
+ type: 'foo',
160
+ date: new Date(),
161
+ });
162
+ const dialog = ref(false);
163
+
164
+ return {
165
+ selectable,
166
+ searchable,
167
+ selectSingle,
168
+ selected,
169
+ items,
170
+ headers,
171
+ newItem,
172
+ dialog,
173
+ required: [
174
+ v => !!v || 'Input may not be null',
175
+ v => v.length > 0 || 'Input must have a length',
176
+ ],
177
+ add() {
178
+ const item = {
179
+ name: newItem.value.name,
180
+ type: newItem.value.type,
181
+ date: newItem.value.date,
182
+ };
183
+
184
+ items.value.push(item);
185
+ newItem.value = {
186
+ name: 'foo',
187
+ type: 'foo',
188
+ date: new Date(),
189
+ };
190
+ dialog.value = false;
191
+ },
192
+ };
193
+ },
194
+ };
195
+ </script>
196
+
197
+ <style lang="scss" scoped>
198
+ .d-grid{
199
+ display: grid;
200
+ grid-template-columns: 1fr 1fr;
201
+ }
202
+ </style>
@@ -0,0 +1,3 @@
1
+ # List Example
2
+
3
+ This is a show-case plugin demonstrating the usage of [VcsDataTable](../../../src/components/tables/VcsDataTable.vue).
@@ -0,0 +1,47 @@
1
+ import { ButtonLocation, createToggleAction, WindowSlot } from '@vcmap/ui';
2
+ import packageJSON from './package.json';
3
+ import DataTableExample from './DataTableExample.vue';
4
+
5
+ /**
6
+ * @returns {VcsPlugin}
7
+ */
8
+ export default async function iconsExample() {
9
+ return {
10
+ get name() { return packageJSON.name; },
11
+ get version() { return packageJSON.version; },
12
+ get vcMapVersion() { return packageJSON.vcMapVersion; },
13
+ onVcsAppMounted(app) {
14
+ const { action, destroy } = createToggleAction(
15
+ {
16
+ name: 'Table Example',
17
+ title: 'Table Example Plugin',
18
+ },
19
+ {
20
+ id: 'table-example',
21
+ component: DataTableExample,
22
+ slot: WindowSlot.DYNAMIC_LEFT,
23
+ state: {
24
+ headerTitle: 'Table Example',
25
+ },
26
+ position: {
27
+ width: 500,
28
+ },
29
+ },
30
+ app.windowManager,
31
+ packageJSON.name,
32
+ );
33
+ app.navbarManager.add(
34
+ { action },
35
+ packageJSON.name,
36
+ ButtonLocation.TOOL,
37
+ );
38
+ this._destroyAction = destroy;
39
+ },
40
+ destroy() {
41
+ if (this._destroyAction) {
42
+ this._destroyAction();
43
+ this._destroyAction = null;
44
+ }
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@vcmap-show-case/table-example",
3
+ "version": "1.0.0",
4
+ "vcMapVersion": "~5.0.0"
5
+ }
@@ -203,29 +203,18 @@ export function createModalAction(actionOptions, modalComponent, app, owner) {
203
203
  check(owner, [String, vcsAppSymbol]);
204
204
 
205
205
  const id = uuid();
206
+ const { position: windowPositionOptions, ...component } = modalComponent;
207
+ let modalActivator = null;
206
208
 
207
- const addModal = (zIndex) => {
208
- const child = document.getElementById(id);
209
- if (!child) {
210
- const elem = document.createElement('div');
211
- elem.id = id;
212
- Object.assign(elem.style, {
213
- position: 'absolute',
214
- zIndex,
215
- top: 0,
216
- bottom: 0,
217
- left: 0,
218
- right: 0,
219
- });
220
- elem.onclick = () => { app.windowManager.remove(id); };
221
- document.body.appendChild(elem);
222
- }
223
- };
224
-
225
- const removeModal = () => {
226
- const child = document.getElementById(id);
227
- if (child) {
228
- child.parentElement.removeChild(child);
209
+ /**
210
+ * Closes the modal at mousedown on an app element
211
+ * Requires mousedown event bubbling on app elements (same behaviour as v-menu).
212
+ * @param {MouseEvent} e
213
+ */
214
+ const handleMouseDown = (e) => {
215
+ const div = document.getElementById(`window-component--${id}`);
216
+ if (!div?.contains(e.target) && !modalActivator?.contains(e.target)) {
217
+ app.windowManager.remove(id);
229
218
  }
230
219
  };
231
220
 
@@ -236,10 +225,11 @@ export function createModalAction(actionOptions, modalComponent, app, owner) {
236
225
  if (!this.active) {
237
226
  this.active = true;
238
227
  const { left, top, width } = event.currentTarget.getBoundingClientRect();
239
- const position = getWindowPositionOptions(left + width, top, app.maps.target);
228
+ modalActivator = event.currentTarget;
229
+ const position = { ...getWindowPositionOptions(left + width, top, app.maps.target), ...windowPositionOptions };
240
230
  const state = { ...modalComponent?.state, hideHeader: true };
241
- app.windowManager.add({ position, ...modalComponent, id, state }, owner);
242
- addModal(app.windowManager.componentIds.length - 2);
231
+ app.windowManager.add({ position, ...component, id, state }, owner);
232
+ document.addEventListener('mousedown', handleMouseDown);
243
233
  } else {
244
234
  this.active = false;
245
235
  app.windowManager.remove(id);
@@ -252,10 +242,9 @@ export function createModalAction(actionOptions, modalComponent, app, owner) {
252
242
  app.windowManager.removed.addEventListener(({ id: windowId }) => {
253
243
  if (windowId === id) {
254
244
  action.active = false;
255
- removeModal();
245
+ document.removeEventListener('mousedown', handleMouseDown);
256
246
  }
257
247
  }),
258
- removeModal,
259
248
  ];
260
249
 
261
250
  const destroy = () => { listeners.forEach((cb) => { cb(); }); };
@@ -1,6 +1,16 @@
1
1
  <template>
2
- <v-sheet v-if="items" class="pa-2">
3
- <VcsSelect :items="items" v-model="currentStyle" />
2
+ <v-sheet v-if="items" class="pt-1 pb-0 px-0">
3
+ <v-list>
4
+ <v-list-item
5
+ v-for="(item, index) in items"
6
+ :key="index"
7
+ @click.stop="select(item.value)"
8
+ >
9
+ <v-list-item-title :class="{ 'primary--text': item.value === currentStyleName }">
10
+ {{ item.text }}
11
+ </v-list-item-title>
12
+ </v-list-item>
13
+ </v-list>
4
14
  <v-list v-if="currentStyleLegend.length > 0">
5
15
  <v-list-item
6
16
  v-for="(entry, index) in currentStyleLegend"
@@ -21,18 +31,17 @@
21
31
  <script>
22
32
  import { computed, inject, onUnmounted, ref } from 'vue';
23
33
  import {
24
- VChip, VList, VListItem, VListItemContent, VListItemIcon, VSheet,
34
+ VChip, VList, VListItem, VListItemContent, VListItemIcon, VListItemTitle, VSheet,
25
35
  } from 'vuetify/lib';
26
- import VcsSelect from '../components/form-inputs-controls/VcsSelect.vue';
27
36
 
28
37
  export default {
29
38
  name: 'StyleSelector',
30
39
  components: {
31
- VcsSelect,
32
40
  VSheet,
33
41
  VList,
34
42
  VListItem,
35
43
  VListItemIcon,
44
+ VListItemTitle,
36
45
  VChip,
37
46
  VListItemContent,
38
47
  },
@@ -46,8 +55,8 @@
46
55
  required: true,
47
56
  },
48
57
  },
49
- setup({ layerName, availableStyles }) {
50
- /** @type {VcsApp} */
58
+ setup({ layerName, availableStyles }, { attrs }) {
59
+ /** @type {VcsUiApp} */
51
60
  const app = inject('vcsApp');
52
61
  /** @type {import("@vcmap/core").FeatureLayer} */
53
62
  const layer = app.layers.getByKey(layerName);
@@ -68,16 +77,6 @@
68
77
  styleChangedListener();
69
78
  });
70
79
 
71
- const currentStyle = computed({
72
- get() { return currentStyleName.value; },
73
- set(styleName) {
74
- const style = styleName === defaultStyle ?
75
- layer.defaultStyle :
76
- app.styles.getByKey(styleName);
77
- layer.setStyle(style);
78
- },
79
- });
80
-
81
80
  const items = computed(() => {
82
81
  return [
83
82
  { text: 'Default', value: defaultStyle },
@@ -85,15 +84,23 @@
85
84
  ];
86
85
  });
87
86
 
87
+ function select(styleName) {
88
+ const style = styleName === defaultStyle ?
89
+ layer.defaultStyle :
90
+ app.styles.getByKey(styleName);
91
+ layer.setStyle(style);
92
+ app.windowManager.remove(attrs['window-state'].id);
93
+ }
94
+
88
95
  return {
89
- currentStyle,
96
+ currentStyleName,
90
97
  currentStyleLegend,
91
98
  items,
99
+ select,
92
100
  };
93
101
  },
94
102
  };
95
103
  </script>
96
104
 
97
105
  <style scoped>
98
-
99
106
  </style>
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <v-menu
3
+ v-model="menuOpen"
4
+ :close-on-content-click="false"
5
+ transition="scale-transition"
6
+ offset-y
7
+ max-width="290px"
8
+ min-width="290px"
9
+ >
10
+ <template #activator="{ on, attrs }">
11
+ <v-text-field
12
+ v-model="formattedDate"
13
+ :placeholder="formatDate(new Date().toISOString())"
14
+ v-bind="{...$attrs, ...attrs}"
15
+ v-on="{...$listeners, ...on}"
16
+ :prepend-icon="icon"
17
+ readonly
18
+ hide-details
19
+ class="ma-0 py-1"
20
+ />
21
+ </template>
22
+ <v-date-picker v-model="date" no-title @input="menuOpen = false" :locale="locale" color="primary">
23
+ <v-btn color="primary" @click="goToToday">
24
+ {{ $t('datePicker.today') }}
25
+ </v-btn>
26
+ </v-date-picker>
27
+ </v-menu>
28
+ </template>
29
+ <style lang="scss" scoped>
30
+ ::v-deep {
31
+ .v-input__control{
32
+ padding: 0 8px;
33
+ }
34
+ }
35
+ </style>
36
+ <script>
37
+ import {
38
+ computed, ref, watch, inject, onUnmounted, onBeforeMount,
39
+ } from 'vue';
40
+ import {
41
+ VMenu, VTextField, VDatePicker, VBtn,
42
+ } from 'vuetify/lib';
43
+ /**
44
+ * @description stylized wrapper around {@link https://v15.vuetifyjs.com/en/components/date-pickers/#month-pickers-in-dialog-and-menu}.
45
+ * @vue-prop {string} value - value of the date picker in {@link https://tc39.es/ecma262/#sec-date-time-string-format | Date Time String Format}
46
+ * @vue-prop {string} icon - specify optional prepend icon, defaults to mdi-calendar
47
+ * @vue-event {string} input - raised when calendar date is selected
48
+ */
49
+ export default {
50
+ name: 'VcsDatePicker',
51
+ props: {
52
+ value: {
53
+ type: String,
54
+ required: true,
55
+ },
56
+ icon: {
57
+ type: String,
58
+ default: 'mdi-calendar',
59
+ },
60
+ },
61
+ components: {
62
+ VMenu,
63
+ VTextField,
64
+ VDatePicker,
65
+ VBtn,
66
+ },
67
+ setup(props, context) {
68
+ /**
69
+ * @type {import("@vcmap/ui").VcsUiApp}
70
+ */
71
+ const app = /** @type {import("@vcmap/ui").VcsUiApp} */ (inject('vcsApp'));
72
+ const localValue = ref(props.value);
73
+ const menuOpen = ref(false);
74
+ const locale = ref(app.locale);
75
+
76
+ const isValid = date => !Number.isNaN(Date.parse(date));
77
+ const setFromValue = () => {
78
+ if (isValid(localValue.value)) {
79
+ localValue.value = props.value;
80
+ } else {
81
+ // eslint-disable-next-line no-console
82
+ console.error('Invalid date provided: ', props.value);
83
+ }
84
+ };
85
+ onBeforeMount(() => setFromValue());
86
+ const destroyLocaleChanged = app.localeChanged.addEventListener(() => { locale.value = app.locale; });
87
+ onUnmounted(() => destroyLocaleChanged());
88
+
89
+ const formatDate = (date) => {
90
+ if (date) {
91
+ return new Intl.DateTimeFormat(locale.value, { dateStyle: 'short' }).format(Date.parse(date));
92
+ }
93
+ return '';
94
+ };
95
+ const goToToday = () => {
96
+ localValue.value = new Date(Date.now()).toISOString().substring(0, 10);
97
+ menuOpen.value = false;
98
+ };
99
+ watch(() => props.value, () => setFromValue());
100
+ const formattedDate = computed({
101
+ get: () => { return formatDate(localValue.value); },
102
+ set: () => {},
103
+ });
104
+ const date = computed({ get: () => localValue.value, set: (nv) => { localValue.value = nv; context.emit('input', localValue.value); } });
105
+
106
+ return {
107
+ formattedDate, date, menuOpen, formatDate, locale, goToToday,
108
+ };
109
+ },
110
+ };
111
+ </script>