@vcmap/ui 5.0.0-rc.14 → 5.0.0-rc.16
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/README.md +33 -31
- package/build/build.js +9 -0
- package/build/buildHelpers.js +12 -10
- package/build/commonViteConfig.js +3 -10
- package/config/base.config.json +30 -24
- package/config/dev.config.json +13 -1
- package/config/www.config.json +104 -17
- package/dist/assets/cesium.430460.js +137226 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.5089ba.js +16024 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/index.854f8e2b.js +1 -0
- package/dist/assets/ol.9be53a.js +44279 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/{ui.15ef6a.css → ui.49010a.css} +1 -1
- package/dist/assets/ui.49010a.js +16776 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.247c1c.js +4675 -0
- package/dist/assets/vue.js +5 -2
- package/dist/assets/{vuetify.202322.css → vuetify.735e58.css} +1 -1
- package/dist/assets/vuetify.735e58.js +21019 -0
- package/dist/assets/vuetify.js +5 -2
- package/dist/index.html +1 -1
- package/index.html +77 -0
- package/index.js +8 -1
- package/package.json +12 -10
- package/plugins/@vcmap/create-link/fallbackCreateLink.vue +4 -1
- package/plugins/@vcmap/create-link/index.js +4 -1
- package/plugins/@vcmap/pluginExample/exampleActions.js +45 -0
- package/plugins/@vcmap/pluginExample/index.js +38 -1
- package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +152 -98
- package/plugins/@vcmap/project-selector/ContextsListComponent.vue +8 -1
- package/plugins/@vcmap/project-selector/ProjectSelectorComponent.vue +27 -1
- package/plugins/@vcmap/search-nominatim/LICENSE.md +14 -0
- package/plugins/@vcmap/search-nominatim/README.md +2 -0
- package/plugins/@vcmap/search-nominatim/config.json +4 -0
- package/plugins/@vcmap/search-nominatim/index.js +26 -0
- package/plugins/@vcmap/search-nominatim/nominatim.js +170 -0
- package/plugins/@vcmap/search-nominatim/package.json +43 -0
- package/plugins/@vcmap/theme-changer/ThemeChangerComponent.vue +26 -0
- package/plugins/buttonExamples/ButtonExamples.vue +28 -1
- package/plugins/categoryTest/Categories.vue +16 -0
- package/plugins/categoryTest/Category.vue +30 -4
- package/plugins/example/mySuperComponent.vue +12 -1
- package/plugins/notifier/index.js +31 -0
- package/plugins/notifier/notifierTester.vue +88 -0
- package/plugins/package.json +2 -1
- package/plugins/simple-graph/SimpleGraphComponent.vue +5 -11
- package/plugins/test/allIconsComponent.vue +16 -0
- package/plugins/test/editor.vue +3 -0
- package/plugins/test/emptyComponent.vue +3 -0
- package/plugins/test/index.js +22 -0
- package/plugins/test/myCustomHeader.vue +9 -1
- package/plugins/test/testList.vue +287 -0
- package/plugins/test/vcsContent.vue +3 -0
- package/plugins/test/windowManagerExample.vue +3 -0
- package/plugins/wizardExample/index.js +41 -0
- package/plugins/wizardExample/wizardExample.vue +77 -0
- package/src/actions/actionHelper.js +103 -2
- package/src/actions/styleSelector.vue +9 -0
- package/src/application/VcsApp.vue +95 -17
- package/src/application/VcsAttributions.vue +63 -0
- package/src/application/VcsAttributionsFooter.vue +87 -0
- package/src/application/{Navbar.vue → VcsNavbar.vue} +35 -2
- package/src/application/VcsSettings.vue +4 -0
- package/src/application/attributionsHelper.js +150 -0
- package/src/application/vcsAppWrapper.vue +5 -1
- package/src/components/buttons/VcsActionButtonList.vue +8 -1
- package/src/components/buttons/VcsButton.vue +7 -1
- package/src/components/form-inputs-controls/VcsCheckbox.vue +7 -2
- package/src/components/form-inputs-controls/VcsColorPicker.vue +4 -0
- package/src/components/form-inputs-controls/VcsFormSection.vue +55 -9
- package/src/components/form-inputs-controls/VcsRadio.vue +7 -1
- package/src/components/form-inputs-controls/VcsSelect.vue +38 -2
- package/src/components/form-inputs-controls/VcsTextArea.vue +2 -0
- package/src/components/form-inputs-controls/VcsTextField.vue +16 -4
- package/src/components/form-inputs-controls/VcsWizard.vue +133 -0
- package/src/components/imageElementInjector.vue +22 -0
- package/src/components/lists/VcsActionList.vue +12 -1
- package/src/components/lists/VcsList.vue +466 -0
- package/src/components/lists/VcsTreeview.vue +7 -3
- package/src/components/lists/VcsTreeviewLeaf.vue +23 -51
- package/src/components/lists/VcsTreeviewSearchbar.vue +6 -23
- package/src/components/notification/VcsTooltip.vue +14 -9
- package/src/components/tables/VcsTable.vue +129 -38
- package/src/contentTree/LayerTree.vue +1 -1
- package/src/contentTree/contentTreeItem.js +13 -13
- package/src/contentTree/subContentTreeItem.js +1 -1
- package/src/contentTree/vcsObjectContentTreeItem.js +1 -1
- package/src/featureInfo/AddressBalloonComponent.vue +17 -1
- package/src/featureInfo/BalloonComponent.vue +63 -27
- package/src/featureInfo/balloonFeatureInfoView.js +14 -14
- package/src/featureInfo/balloonHelper.js +4 -0
- package/src/featureInfo/featureInfo.js +23 -2
- package/src/featureInfo/featureInfoInteraction.js +1 -1
- package/src/i18n/de.js +22 -0
- package/src/i18n/en.js +22 -0
- package/src/icons/+all.js +4 -0
- package/src/icons/WandIcon.vue +63 -0
- package/src/legend/legendHelper.js +18 -12
- package/src/legend/styleLegendItem.vue +20 -1
- package/src/legend/vcsLegend.vue +29 -3
- package/src/manager/toolbox/GroupToolboxComponent.vue +13 -1
- package/src/manager/toolbox/SelectToolboxComponent.vue +13 -1
- package/src/manager/toolbox/ToolboxManager.vue +3 -0
- package/src/manager/window/WindowComponent.vue +15 -2
- package/src/manager/window/WindowComponentHeader.vue +38 -7
- package/src/manager/window/WindowManager.vue +1 -0
- package/src/manager/window/windowManager.js +11 -1
- package/src/navigation/mapNavigation.vue +15 -36
- package/src/navigation/orientationToolsButton.vue +6 -1
- package/src/navigation/overviewMap.js +19 -47
- package/src/navigation/tiltSlider.vue +3 -0
- package/src/navigation/vcsCompass.vue +2 -0
- package/src/notifier/notifier.js +121 -0
- package/src/notifier/notifierComponent.vue +84 -0
- package/src/search/resultItem.vue +89 -0
- package/src/search/resultsComponent.vue +98 -0
- package/src/search/search.js +326 -0
- package/src/search/searchComponent.vue +90 -0
- package/src/styles/_typography.scss +3 -0
- package/src/styles/utils/_cursor.scss +4 -0
- package/src/styles/variables.scss +23 -4
- package/src/vcsUiApp.js +35 -1
- package/src/vuePlugins/vuetify.js +2 -0
- package/dist/assets/cesium.9489f8.js +0 -8699
- package/dist/assets/core.aa346a.js +0 -4
- package/dist/assets/index.3cd4fffa.js +0 -1
- package/dist/assets/ol.39651b.js +0 -439
- package/dist/assets/ui.15ef6a.js +0 -71
- package/dist/assets/vue.cbe9d8.js +0 -9
- package/dist/assets/vuetify.202322.js +0 -148
@@ -1,18 +1,29 @@
|
|
1
1
|
<template>
|
2
2
|
<v-container class="fill-height pa-0" absolute fluid>
|
3
|
-
<
|
3
|
+
<VcsNavbar />
|
4
4
|
<v-container class="vcs-main pa-0" :class="{ 'vcs-main-xs': $vuetify.breakpoint.xs }" fluid absolute>
|
5
5
|
<template v-if="$vuetify.breakpoint.xs">
|
6
6
|
<img v-if="mobileLogo" :src="mobileLogo" alt="Logo" draggable="false" class="mobile-logo">
|
7
7
|
<div v-else class="company-logo-mobile mobile-logo" />
|
8
8
|
</template>
|
9
|
+
<VcsButton
|
10
|
+
v-if="!$vuetify.breakpoint.smAndUp && $vuetify.breakpoint.mobile"
|
11
|
+
:key="attributionAction.name"
|
12
|
+
:tooltip="attributionAction.title"
|
13
|
+
:icon="attributionAction.icon"
|
14
|
+
:active="attributionAction.active"
|
15
|
+
@click.stop="attributionAction.callback($event)"
|
16
|
+
small
|
17
|
+
class="z-index-1 mobile-attribution-btn"
|
18
|
+
/>
|
9
19
|
<VcsMap :map-id="mapId" />
|
10
20
|
<MapNavigation />
|
11
21
|
<ToolboxManagerComponent />
|
12
22
|
<WindowManagerComponent />
|
23
|
+
<NotifierComponent />
|
13
24
|
</v-container>
|
14
|
-
<v-footer absolute v-if="
|
15
|
-
|
25
|
+
<v-footer absolute v-if="$vuetify.breakpoint.smAndUp" min-height="22px">
|
26
|
+
<VcsAttributionsFooter :entries="attributionEntries" :attribution-action="attributionAction" />
|
16
27
|
</v-footer>
|
17
28
|
</v-container>
|
18
29
|
</template>
|
@@ -26,7 +37,7 @@
|
|
26
37
|
top: 48px;
|
27
38
|
left: 0;
|
28
39
|
right: 0;
|
29
|
-
bottom:
|
40
|
+
bottom: 22px;
|
30
41
|
}
|
31
42
|
|
32
43
|
.vcs-main-xs {
|
@@ -43,6 +54,12 @@
|
|
43
54
|
z-index: 1;
|
44
55
|
}
|
45
56
|
|
57
|
+
.mobile-attribution-btn{
|
58
|
+
position: fixed;
|
59
|
+
right: 2px;
|
60
|
+
bottom: 56px;
|
61
|
+
}
|
62
|
+
|
46
63
|
</style>
|
47
64
|
|
48
65
|
<script>
|
@@ -56,12 +73,14 @@
|
|
56
73
|
watch,
|
57
74
|
} from 'vue';
|
58
75
|
import { getVcsAppById } from '@vcmap/core';
|
76
|
+
import { VContainer, VFooter } from 'vuetify/lib';
|
77
|
+
import { getLogger } from '@vcsuite/logger';
|
59
78
|
import WindowManagerComponent from '../manager/window/WindowManager.vue';
|
60
79
|
import ToolboxManagerComponent from '../manager/toolbox/ToolboxManager.vue';
|
61
80
|
import { ButtonLocation } from '../manager/navbarManager.js';
|
62
81
|
import { vcsAppSymbol } from '../pluginHelper.js';
|
63
82
|
import VcsMap from './VcsMap.vue';
|
64
|
-
import
|
83
|
+
import VcsNavbar from './VcsNavbar.vue';
|
65
84
|
import { createMapButtonAction, createToggleAction } from '../actions/actionHelper.js';
|
66
85
|
import MapNavigation from '../navigation/mapNavigation.vue';
|
67
86
|
import VcsSettings from './VcsSettings.vue';
|
@@ -70,6 +89,11 @@
|
|
70
89
|
import { defaultPrimaryColor } from '../vuePlugins/vuetify.js';
|
71
90
|
import VcsLegend from '../legend/vcsLegend.vue';
|
72
91
|
import { getLegendEntries } from '../legend/legendHelper.js';
|
92
|
+
import VcsAttributionsFooter from './VcsAttributionsFooter.vue';
|
93
|
+
import VcsButton from '../components/buttons/VcsButton.vue';
|
94
|
+
import VcsAttributions from './VcsAttributions.vue';
|
95
|
+
import { getAttributions } from './attributionsHelper.js';
|
96
|
+
import NotifierComponent from '../notifier/notifierComponent.vue';
|
73
97
|
|
74
98
|
/**
|
75
99
|
* You should call this function in the component providing the vcsUiApp to your
|
@@ -80,17 +104,23 @@
|
|
80
104
|
* @returns {function():void}
|
81
105
|
*/
|
82
106
|
export function setupPluginMountedListeners(app) {
|
83
|
-
|
107
|
+
/**
|
108
|
+
* wrapped execution of onVcsAppMounted hook
|
109
|
+
* @param {VcsPlugin} plugin
|
110
|
+
*/
|
111
|
+
function onVcsAppMounted(plugin) {
|
84
112
|
if (plugin.onVcsAppMounted) {
|
85
|
-
|
113
|
+
try {
|
114
|
+
plugin.onVcsAppMounted(app);
|
115
|
+
} catch (e) {
|
116
|
+
getLogger('VcsUiApp').error(`Error in plugin ${plugin.name} onVcsAppMounted hook`, e);
|
117
|
+
}
|
86
118
|
}
|
87
|
-
}
|
119
|
+
}
|
88
120
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
}
|
93
|
-
});
|
121
|
+
[...app.plugins].forEach(onVcsAppMounted);
|
122
|
+
|
123
|
+
return app.plugins.added.addEventListener(onVcsAppMounted);
|
94
124
|
}
|
95
125
|
|
96
126
|
/**
|
@@ -178,10 +208,10 @@
|
|
178
208
|
|
179
209
|
/**
|
180
210
|
* adds or removes the legend button, depending on the number of entries
|
181
|
-
* @param {
|
211
|
+
* @param {Array<LegendEntry>} newEntries
|
182
212
|
*/
|
183
213
|
const handleLegendButton = (newEntries) => {
|
184
|
-
if (
|
214
|
+
if (newEntries.length > 0) {
|
185
215
|
if (!app.navbarManager.has('legend')) {
|
186
216
|
app.navbarManager.add(
|
187
217
|
{
|
@@ -197,7 +227,7 @@
|
|
197
227
|
app.windowManager.remove('legend');
|
198
228
|
}
|
199
229
|
};
|
200
|
-
handleLegendButton(entries);
|
230
|
+
handleLegendButton(entries.value);
|
201
231
|
|
202
232
|
const stopWatching = watch(
|
203
233
|
entries,
|
@@ -329,6 +359,45 @@
|
|
329
359
|
};
|
330
360
|
}
|
331
361
|
|
362
|
+
/**
|
363
|
+
* This helper gets attributions of all active maps, layers and oblique collections and returns an array of entries.
|
364
|
+
* It also returns a attributionAction to toggle the attributions window and a destroy function.
|
365
|
+
* @param {VcsUiApp} app
|
366
|
+
* @returns {{attributionEntries: import("vue").Ref<Array<AttributionEntry>>, attributionAction: VcsAction, destroyAttributions: function():void}}
|
367
|
+
*/
|
368
|
+
export function setupAttributions(app) {
|
369
|
+
const { entries, destroy } = getAttributions(app);
|
370
|
+
|
371
|
+
const { action: attributionAction, destroy: attributionDestroy } = createToggleAction(
|
372
|
+
{
|
373
|
+
name: 'attributionToggle',
|
374
|
+
icon: 'mdi-chevron-double-right',
|
375
|
+
title: 'footer.attributions.tooltip',
|
376
|
+
},
|
377
|
+
{
|
378
|
+
id: 'attribution',
|
379
|
+
component: VcsAttributions,
|
380
|
+
state: {
|
381
|
+
headerTitle: 'footer.attributions.title',
|
382
|
+
headerIcon: 'mdi-copyright',
|
383
|
+
},
|
384
|
+
slot: WindowSlot.DYNAMIC_RIGHT,
|
385
|
+
props: { entries },
|
386
|
+
},
|
387
|
+
app.windowManager,
|
388
|
+
vcsAppSymbol,
|
389
|
+
);
|
390
|
+
|
391
|
+
return {
|
392
|
+
attributionEntries: entries,
|
393
|
+
attributionAction,
|
394
|
+
destroyAttributions: () => {
|
395
|
+
destroy();
|
396
|
+
attributionDestroy();
|
397
|
+
},
|
398
|
+
};
|
399
|
+
}
|
400
|
+
|
332
401
|
/**
|
333
402
|
* The base component to setup the entire application. To embed the VcsApp, use this component.
|
334
403
|
* @vue-prop {string} appId - the id of the app to inject. this will setup listeners on the app to call vcsAppMounted on plugins
|
@@ -336,11 +405,16 @@
|
|
336
405
|
*/
|
337
406
|
export default {
|
338
407
|
components: {
|
408
|
+
VcsButton,
|
409
|
+
VcsAttributionsFooter,
|
339
410
|
MapNavigation,
|
340
|
-
|
411
|
+
VcsNavbar,
|
341
412
|
VcsMap,
|
342
413
|
WindowManagerComponent,
|
343
414
|
ToolboxManagerComponent,
|
415
|
+
VContainer,
|
416
|
+
VFooter,
|
417
|
+
NotifierComponent,
|
344
418
|
},
|
345
419
|
props: {
|
346
420
|
appId: {
|
@@ -360,6 +434,7 @@
|
|
360
434
|
const settingsDestroy = setupSettingsWindow(app);
|
361
435
|
const destroyComponentsWindow = setupComponentsWindow(app);
|
362
436
|
const destroyThemingListener = setupUiConfigTheming(app, getCurrentInstance().proxy.$vuetify);
|
437
|
+
const { attributionEntries, attributionAction, destroyAttributions } = setupAttributions(app);
|
363
438
|
|
364
439
|
let pluginMountedListener;
|
365
440
|
onMounted(() => {
|
@@ -376,11 +451,14 @@
|
|
376
451
|
settingsDestroy();
|
377
452
|
destroyComponentsWindow();
|
378
453
|
destroyThemingListener();
|
454
|
+
destroyAttributions();
|
379
455
|
});
|
380
456
|
|
381
457
|
return {
|
382
458
|
mapId,
|
383
459
|
mobileLogo: computed(() => app.uiConfig.config.value.mobileLogo ?? app.uiConfig.config.value.logo),
|
460
|
+
attributionEntries,
|
461
|
+
attributionAction,
|
384
462
|
};
|
385
463
|
},
|
386
464
|
};
|
@@ -0,0 +1,63 @@
|
|
1
|
+
<template>
|
2
|
+
<v-list>
|
3
|
+
<v-list-item v-for="({key, title, attributions}) in entries" :key="key">
|
4
|
+
<v-list-item-content>
|
5
|
+
<v-list-item-title>{{ $t(title) }}</v-list-item-title>
|
6
|
+
<v-list-item-subtitle
|
7
|
+
v-for="attribution in attributions"
|
8
|
+
:key="attribution.provider"
|
9
|
+
:title="`${$t(attribution.provider)} ${attribution.year}`"
|
10
|
+
>
|
11
|
+
<a
|
12
|
+
:href="attribution.url"
|
13
|
+
target="_blank"
|
14
|
+
class="text--secondary"
|
15
|
+
>
|
16
|
+
{{ $t(attribution.provider) }} {{ attribution.year }}
|
17
|
+
</a>
|
18
|
+
</v-list-item-subtitle>
|
19
|
+
</v-list-item-content>
|
20
|
+
</v-list-item>
|
21
|
+
</v-list>
|
22
|
+
</template>
|
23
|
+
|
24
|
+
<style lang="scss" scoped>
|
25
|
+
|
26
|
+
::v-deep {
|
27
|
+
a:before {
|
28
|
+
content: '\00a9\00a0'
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
</style>
|
33
|
+
|
34
|
+
<script >
|
35
|
+
import {
|
36
|
+
VList,
|
37
|
+
VListItem,
|
38
|
+
VListItemContent,
|
39
|
+
VListItemTitle,
|
40
|
+
VListItemSubtitle,
|
41
|
+
} from 'vuetify/lib';
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Lists attributions of maps, layers and oblique collections
|
45
|
+
* @vue-prop {import("vue").Ref<Array<AttributionEntry>} entries - array with one entry per active VcsObject
|
46
|
+
*/
|
47
|
+
export default {
|
48
|
+
name: 'VcsAttributions',
|
49
|
+
components: {
|
50
|
+
VList,
|
51
|
+
VListItem,
|
52
|
+
VListItemContent,
|
53
|
+
VListItemTitle,
|
54
|
+
VListItemSubtitle,
|
55
|
+
},
|
56
|
+
props: {
|
57
|
+
entries: {
|
58
|
+
type: Object,
|
59
|
+
default: () => {},
|
60
|
+
},
|
61
|
+
},
|
62
|
+
};
|
63
|
+
</script>
|
@@ -0,0 +1,87 @@
|
|
1
|
+
<template>
|
2
|
+
<span class="attribution-wrap">
|
3
|
+
<span v-for="attribution in mergedAttributions" class="attribution-span" :key="attribution.provider">
|
4
|
+
<a
|
5
|
+
:href="attribution.url"
|
6
|
+
target="_blank"
|
7
|
+
class="text--secondary"
|
8
|
+
>{{ $t(attribution.provider) }} <span>{{ attribution.years }}</span></a>
|
9
|
+
</span>
|
10
|
+
<VcsButton
|
11
|
+
class="d-flex text--secondary"
|
12
|
+
small
|
13
|
+
:key="attributionAction.name"
|
14
|
+
:tooltip="attributionAction.title"
|
15
|
+
:icon="attributionAction.icon"
|
16
|
+
:active="attributionAction.active"
|
17
|
+
@click.stop="attributionAction.callback($event)"
|
18
|
+
/>
|
19
|
+
</span>
|
20
|
+
</template>
|
21
|
+
|
22
|
+
<style lang="scss" scoped>
|
23
|
+
.attribution-wrap{
|
24
|
+
white-space: nowrap;
|
25
|
+
margin-right: 40px;
|
26
|
+
overflow: hidden;
|
27
|
+
text-overflow: ellipsis;
|
28
|
+
|
29
|
+
.vcs-button-wrap{
|
30
|
+
position: absolute;
|
31
|
+
right: 4px;
|
32
|
+
bottom: 3px;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
a:before {
|
36
|
+
content: '\00a9\00a0';
|
37
|
+
}
|
38
|
+
.attribution-span {
|
39
|
+
font-size: smaller;
|
40
|
+
&:before {
|
41
|
+
content: '\00a0\007c\00a0';
|
42
|
+
}
|
43
|
+
&:first-child::before{
|
44
|
+
content: '';
|
45
|
+
}
|
46
|
+
span{
|
47
|
+
font-size: inherit;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
</style>
|
52
|
+
|
53
|
+
<script >
|
54
|
+
|
55
|
+
import { computed } from 'vue';
|
56
|
+
import { mergeAttributions } from './attributionsHelper.js';
|
57
|
+
import VcsButton from '../components/buttons/VcsButton.vue';
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Lists attributions of maps, layers and oblique collections within the footer
|
61
|
+
* @vue-prop {import("vue").Ref<Array<AttributionEntry>} entries - array with one entry per active VcsObject
|
62
|
+
* @vue-prop {VcsAction} attributionAction - action to open attribution window
|
63
|
+
* @vue-computed {Array<{provider: string, years: string, url: URL}>} mergedAttributions - array with one entry per provider
|
64
|
+
*/
|
65
|
+
export default {
|
66
|
+
name: 'VcsAttributionsFooter',
|
67
|
+
components: {
|
68
|
+
VcsButton,
|
69
|
+
},
|
70
|
+
props: {
|
71
|
+
entries: {
|
72
|
+
type: Array,
|
73
|
+
required: true,
|
74
|
+
},
|
75
|
+
attributionAction: {
|
76
|
+
type: Object,
|
77
|
+
required: true,
|
78
|
+
},
|
79
|
+
},
|
80
|
+
setup(props) {
|
81
|
+
const mergedAttributions = computed(() => mergeAttributions(props.entries));
|
82
|
+
return {
|
83
|
+
mergedAttributions,
|
84
|
+
};
|
85
|
+
},
|
86
|
+
};
|
87
|
+
</script>
|
@@ -86,6 +86,17 @@
|
|
86
86
|
:show-icon="true"
|
87
87
|
/>
|
88
88
|
</v-menu>
|
89
|
+
<VcsButton
|
90
|
+
class="d-flex"
|
91
|
+
v-if="searchAction"
|
92
|
+
large
|
93
|
+
:key="searchAction.name"
|
94
|
+
:tooltip="searchAction.title"
|
95
|
+
:icon="searchAction.icon"
|
96
|
+
:active="searchAction.active"
|
97
|
+
@click.stop="searchAction.callback($event)"
|
98
|
+
v-bind="{...$attrs}"
|
99
|
+
/>
|
89
100
|
<v-menu
|
90
101
|
offset-y
|
91
102
|
v-if="menuActions.length > 0"
|
@@ -124,15 +135,30 @@
|
|
124
135
|
</style>
|
125
136
|
|
126
137
|
<script>
|
127
|
-
import { inject, ref, computed } from 'vue';
|
138
|
+
import { inject, ref, computed, onUnmounted } from 'vue';
|
139
|
+
import {
|
140
|
+
VCol, VContainer, VDivider, VMenu, VRow, VToolbar, VToolbarItems,
|
141
|
+
} from 'vuetify/lib';
|
128
142
|
import { ButtonLocation, getActionsByLocation } from '../manager/navbarManager.js';
|
129
143
|
import VcsActionButtonList from '../components/buttons/VcsActionButtonList.vue';
|
130
144
|
import VcsActionList from '../components/lists/VcsActionList.vue';
|
131
145
|
import VcsButton from '../components/buttons/VcsButton.vue';
|
146
|
+
import { createSearchButtonAction } from '../actions/actionHelper.js';
|
132
147
|
|
133
148
|
export default {
|
134
149
|
name: 'VcsNavbar',
|
135
|
-
components: {
|
150
|
+
components: {
|
151
|
+
VcsActionButtonList,
|
152
|
+
VcsActionList,
|
153
|
+
VcsButton,
|
154
|
+
VToolbar,
|
155
|
+
VContainer,
|
156
|
+
VRow,
|
157
|
+
VCol,
|
158
|
+
VToolbarItems,
|
159
|
+
VDivider,
|
160
|
+
VMenu,
|
161
|
+
},
|
136
162
|
setup() {
|
137
163
|
const app = inject('vcsApp');
|
138
164
|
|
@@ -142,12 +168,19 @@
|
|
142
168
|
() => getActionsByLocation(buttonComponents.value, location, [...app.plugins].map(p => p.name)),
|
143
169
|
);
|
144
170
|
|
171
|
+
const { searchAction, destroy: destroySearchAction } = createSearchButtonAction(app);
|
172
|
+
|
173
|
+
onUnmounted(() => {
|
174
|
+
destroySearchAction();
|
175
|
+
});
|
176
|
+
|
145
177
|
return {
|
146
178
|
mapActions: getActions(ButtonLocation.MAP),
|
147
179
|
contentActions: getActions(ButtonLocation.CONTENT),
|
148
180
|
toolActions: getActions(ButtonLocation.TOOL),
|
149
181
|
projectActions: getActions(ButtonLocation.PROJECT),
|
150
182
|
shareActions: getActions(ButtonLocation.SHARE),
|
183
|
+
searchAction,
|
151
184
|
menuActions: getActions(ButtonLocation.MENU),
|
152
185
|
config: app.uiConfig.config,
|
153
186
|
};
|
@@ -34,6 +34,7 @@
|
|
34
34
|
import {
|
35
35
|
ref, inject, onUnmounted, getCurrentInstance, computed,
|
36
36
|
} from 'vue';
|
37
|
+
import { VCol, VContainer, VRow } from 'vuetify/lib';
|
37
38
|
import VcsLabel from '../components/form-inputs-controls/VcsLabel.vue';
|
38
39
|
import VcsSelect from '../components/form-inputs-controls/VcsSelect.vue';
|
39
40
|
|
@@ -42,6 +43,9 @@
|
|
42
43
|
components: {
|
43
44
|
VcsSelect,
|
44
45
|
VcsLabel,
|
46
|
+
VContainer,
|
47
|
+
VRow,
|
48
|
+
VCol,
|
45
49
|
},
|
46
50
|
setup() {
|
47
51
|
const app = inject('vcsApp');
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import { ref } from 'vue';
|
2
|
+
import { ObliqueMap } from '@vcmap/core';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @typedef {Object} Attribution.Options
|
6
|
+
* @property {string} provider - name of the data provider
|
7
|
+
* @property {number} [year] - year of dataset
|
8
|
+
* @property {URL} url - link to data provider
|
9
|
+
*/
|
10
|
+
|
11
|
+
/**
|
12
|
+
* @typedef {Object} AttributionEntry
|
13
|
+
* @property {string} key - name of the VcsObject the attribution applies to
|
14
|
+
* @property {string} title - title of the VcsObject the attribution applies to
|
15
|
+
* @property {Attribution.Options|Array<Attribution.Options>} attributions - attributions of a map, layer or oblique collection
|
16
|
+
*/
|
17
|
+
|
18
|
+
/**
|
19
|
+
* merges attribution entries of same providers
|
20
|
+
* @param {Array<AttributionEntry>} entries
|
21
|
+
* @returns {Array<{provider: string, years: string, url: URL}>}
|
22
|
+
*/
|
23
|
+
export function mergeAttributions(entries) {
|
24
|
+
const providers = {};
|
25
|
+
entries.forEach(({ attributions }) => {
|
26
|
+
attributions.forEach(({ provider, year, url }) => {
|
27
|
+
const providerObject = providers[provider];
|
28
|
+
if (providerObject) {
|
29
|
+
if (year) {
|
30
|
+
const index = providerObject.years.indexOf(year);
|
31
|
+
if (url && index === -1) {
|
32
|
+
if (providerObject.years.every(y => Number(y) < Number(year))) {
|
33
|
+
providerObject.url = url;
|
34
|
+
}
|
35
|
+
if (year) {
|
36
|
+
const set = new Set([...providerObject.years, Number(year)]);
|
37
|
+
providerObject.years = [...set].sort((a, b) => a - b);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
} else {
|
42
|
+
providers[provider] = {
|
43
|
+
years: year ? [Number(year)] : [],
|
44
|
+
url,
|
45
|
+
};
|
46
|
+
}
|
47
|
+
});
|
48
|
+
});
|
49
|
+
return Object.keys(providers).map(provider => ({
|
50
|
+
provider,
|
51
|
+
years: providers[provider].years.join(', '),
|
52
|
+
url: providers[provider].url,
|
53
|
+
}));
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Gets attributions of all active maps, layers and oblique collections and returns an array of entries.
|
58
|
+
* Each entry is defined by a key derived from the object's className and name, and it's associated attributions.
|
59
|
+
* Listens to state changes of maps, layers and oblique collections and synchronizes the entries array correspondingly.
|
60
|
+
* Returns a destroy function to clear listeners.
|
61
|
+
* @param {import("ui").VcsUiApp} app
|
62
|
+
* @returns {{entries: import("vue").Ref<Array<AttributionEntry>>, destroy: function():void}}
|
63
|
+
*/
|
64
|
+
export function getAttributions(app) {
|
65
|
+
/**
|
66
|
+
* @type {import("vue").Ref<Array<AttributionEntry>>}
|
67
|
+
*/
|
68
|
+
const entries = ref([]);
|
69
|
+
/**
|
70
|
+
* @type {function():void}
|
71
|
+
*/
|
72
|
+
let obliqueListener = () => {};
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Adds an entry for an object using a combination of the object's className and name as key.
|
76
|
+
* @param {import("@vcmap/core").VcsMap|import("@vcmap/core").Layer|import("@vcmap/core").ObliqueCollection} object
|
77
|
+
*/
|
78
|
+
function addAttributions(object) {
|
79
|
+
const { attributions } = object.properties;
|
80
|
+
if (!attributions) { return; }
|
81
|
+
const key = `${object.className}_${object.name}`;
|
82
|
+
const idx = entries.value.findIndex(e => e.key === key);
|
83
|
+
if (idx < 0) {
|
84
|
+
entries.value.push({
|
85
|
+
key,
|
86
|
+
title: object.properties?.title ?? `${object.className}: ${object.name}`,
|
87
|
+
attributions: Array.isArray(attributions) ? attributions : [attributions],
|
88
|
+
});
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* @param {import("@vcmap/core").VcsMap|import("@vcmap/core").Layer|import("@vcmap/core").ObliqueCollection} object
|
94
|
+
*/
|
95
|
+
function removeAttributions(object) {
|
96
|
+
const idx = entries.value.findIndex(e => e.key === `${object.className}_${object.name}`);
|
97
|
+
if (idx >= 0) {
|
98
|
+
entries.value.splice(idx, 1);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* adds or removes a AttributionEntry on layer state changes
|
104
|
+
* @param {import("@vcmap/core").VcsMap|import("@vcmap/core").Layer|import("@vcmap/core").ObliqueCollection} object
|
105
|
+
*/
|
106
|
+
function syncAttributions(object) {
|
107
|
+
if (object?.properties?.attributions === undefined) { return; }
|
108
|
+
if (object.active || object.loaded) {
|
109
|
+
addAttributions(object);
|
110
|
+
} else {
|
111
|
+
removeAttributions(object);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
*
|
117
|
+
* @param {import("@vcmap/core").VcsMap} map
|
118
|
+
*/
|
119
|
+
function initAttributions(map) {
|
120
|
+
if (!map) { return; }
|
121
|
+
obliqueListener();
|
122
|
+
entries.value.splice(0);
|
123
|
+
syncAttributions(map);
|
124
|
+
[...map.layerCollection].forEach((layer) => {
|
125
|
+
if (layer.isSupported(map)) {
|
126
|
+
syncAttributions(layer);
|
127
|
+
}
|
128
|
+
});
|
129
|
+
if (map instanceof ObliqueMap) {
|
130
|
+
syncAttributions(map.collection);
|
131
|
+
obliqueListener = map.collectionChanged.addEventListener(syncAttributions);
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
const listeners = [
|
136
|
+
app.maps.mapActivated.addEventListener(initAttributions),
|
137
|
+
app.layers.stateChanged.addEventListener(syncAttributions),
|
138
|
+
app.layers.removed.addEventListener(removeAttributions),
|
139
|
+
app.maps.removed.addEventListener(removeAttributions),
|
140
|
+
];
|
141
|
+
|
142
|
+
initAttributions(app.maps.activeMap);
|
143
|
+
|
144
|
+
const destroy = () => {
|
145
|
+
listeners.forEach(cb => cb());
|
146
|
+
obliqueListener();
|
147
|
+
};
|
148
|
+
|
149
|
+
return { entries, destroy };
|
150
|
+
}
|
@@ -5,6 +5,7 @@
|
|
5
5
|
</template>
|
6
6
|
|
7
7
|
<script>
|
8
|
+
import { VApp } from 'vuetify/lib';
|
8
9
|
import VcsApp from './VcsApp.vue';
|
9
10
|
|
10
11
|
/**
|
@@ -21,7 +22,10 @@
|
|
21
22
|
required: true,
|
22
23
|
},
|
23
24
|
},
|
24
|
-
components: {
|
25
|
+
components: {
|
26
|
+
VcsApp,
|
27
|
+
VApp,
|
28
|
+
},
|
25
29
|
};
|
26
30
|
</script>
|
27
31
|
|
@@ -38,6 +38,7 @@
|
|
38
38
|
</style>
|
39
39
|
<script>
|
40
40
|
|
41
|
+
import { VIcon, VMenu, VSpacer } from 'vuetify/lib';
|
41
42
|
import VcsButton from './VcsButton.vue';
|
42
43
|
import VcsActionList, { validateActions } from '../lists/VcsActionList.vue';
|
43
44
|
|
@@ -54,7 +55,13 @@
|
|
54
55
|
*/
|
55
56
|
export default {
|
56
57
|
name: 'VcsActionButtonList',
|
57
|
-
components: {
|
58
|
+
components: {
|
59
|
+
VcsActionList,
|
60
|
+
VcsButton,
|
61
|
+
VMenu,
|
62
|
+
VIcon,
|
63
|
+
VSpacer,
|
64
|
+
},
|
58
65
|
props: {
|
59
66
|
actions: {
|
60
67
|
type: Array,
|
@@ -94,6 +94,7 @@
|
|
94
94
|
</style>
|
95
95
|
|
96
96
|
<script>
|
97
|
+
import { VBtn, VIcon } from 'vuetify/lib';
|
97
98
|
import VcsBadge from '../notification/VcsBadge.vue';
|
98
99
|
import VcsTooltip from '../notification/VcsTooltip.vue';
|
99
100
|
|
@@ -118,7 +119,12 @@
|
|
118
119
|
*/
|
119
120
|
export default {
|
120
121
|
name: 'VcsButton',
|
121
|
-
components: {
|
122
|
+
components: {
|
123
|
+
VcsTooltip,
|
124
|
+
VcsBadge,
|
125
|
+
VBtn,
|
126
|
+
VIcon,
|
127
|
+
},
|
122
128
|
inheritAttrs: false,
|
123
129
|
props: {
|
124
130
|
active: {
|