Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/mapviewer/src/modules/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"appcache_update_available": "Neue Version der Offline Funktion verfügbar. Wollen Sie sie laden?",
"approx_abbr": "ca.",
"are": "ARE",
"attribution_overflow_hide_sources": "Weitere Quellenangaben ausblenden",
"attribution_overflow_show_source": "Eine weitere Quellenangabe anzeigen",
"attribution_overflow_show_sources": "{count} weitere Quellenangaben anzeigen",
Comment thread
rotwurstesser marked this conversation as resolved.
"are_service_link_href": "http://www.are.admin.ch/index.html?lang=de",
"are_service_link_label": "www.are.admin.ch",
"area": "Fläche",
Expand Down
3 changes: 3 additions & 0 deletions packages/mapviewer/src/modules/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"appcache_update_available": "A new version of offline function is available. Load it?",
"approx_abbr": "approx.",
"are": "ARE",
"attribution_overflow_hide_sources": "Hide additional attribution sources",
"attribution_overflow_show_source": "Show 1 more attribution source",
"attribution_overflow_show_sources": "Show {count} more attribution sources",
"are_service_link_href": "http://www.are.admin.ch/index.html?lang=en",
"are_service_link_label": "www.are.admin.ch",
"area": "Area",
Expand Down
3 changes: 3 additions & 0 deletions packages/mapviewer/src/modules/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"appcache_update_available": "Une mise à jour du site est disponible. Voulez-vous l'utiliser?",
"approx_abbr": "env.",
"are": "ARE",
"attribution_overflow_hide_sources": "Masquer les autres sources d'attribution",
"attribution_overflow_show_source": "Afficher une autre source d'attribution",
"attribution_overflow_show_sources": "Afficher {count} autres sources d'attribution",
"are_service_link_href": "http://www.are.admin.ch/index.html?lang=fr",
"are_service_link_label": "www.are.admin.ch",
"area": "Aire",
Expand Down
3 changes: 3 additions & 0 deletions packages/mapviewer/src/modules/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"appcache_update_available": "È a disposizione un aggiornamento del sito. Desiderate utilizzarlo?",
"approx_abbr": "ca.",
"are": "ARE",
"attribution_overflow_hide_sources": "Nascondi altre fonti di attribuzione",
"attribution_overflow_show_source": "Mostra un'altra fonte di attribuzione",
"attribution_overflow_show_sources": "Mostra altre {count} fonti di attribuzione",
"are_service_link_href": "http://www.are.admin.ch/index.html?lang=it",
"are_service_link_label": "www.are.admin.ch",
"area": "Zona",
Expand Down
3 changes: 3 additions & 0 deletions packages/mapviewer/src/modules/i18n/locales/rm.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"appcache_update_available": "Ina nova versiun stat a disposiziun offline. La vulais Vus chargiar giu?",
"approx_abbr": "ca.",
"are": "ARE",
"attribution_overflow_hide_sources": "Zuppentar ulteriuras funtaunas d'attribuziun",
"attribution_overflow_show_source": "Mussar ina ulteriura funtauna d'attribuziun",
"attribution_overflow_show_sources": "Mussar {count} ulteriuras funtaunas d'attribuziun",
"are_service_link_href": "http://www.are.admin.ch/index.html?lang=de",
"are_service_link_label": "www.are.admin.ch",
"area": "Surfatscha",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup>
const { sourceId, sourceName, sourceUrl, hasDataDisclaimer, isLast } = defineProps({
const { sourceId, sourceName, sourceUrl, hasDataDisclaimer, isLast, isTruncated } = defineProps({
sourceId: {
type: String,
required: true,
Expand All @@ -20,6 +20,10 @@ const { sourceId, sourceName, sourceUrl, hasDataDisclaimer, isLast } = definePro
type: Boolean,
default: false,
},
isTruncated: {
type: Boolean,
default: false,
},
})
</script>

Expand All @@ -29,9 +33,15 @@ const { sourceId, sourceName, sourceUrl, hasDataDisclaimer, isLast } = definePro
:id="`source-${sourceId}`"
:href="sourceUrl"
:target="sourceUrl ? '_blank' : null"
:rel="sourceUrl ? 'noopener noreferrer' : null"
class="map-footer-attribution-source clear-no-ios-long-press"
:class="{ 'text-primary': hasDataDisclaimer, 'is-link': sourceUrl || hasDataDisclaimer }"
:class="{
'text-primary': hasDataDisclaimer,
'is-link': sourceUrl || hasDataDisclaimer,
'is-truncated': isTruncated,
}"
:data-cy="`layer-copyright-${sourceName}`"
:title="sourceName"
>
{{ `${sourceName}${isLast ? '' : ','}` }}
</component>
Expand All @@ -44,6 +54,15 @@ const { sourceId, sourceName, sourceUrl, hasDataDisclaimer, isLast } = definePro
margin-left: 2px;
color: $black;

&.is-truncated {
display: inline-block;
max-width: min(16rem, 28vw);
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
white-space: nowrap;
}

&.is-link {
text-decoration: none;
&:hover {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'

Expand All @@ -8,6 +8,7 @@ import ThirdPartyDisclaimer from '@/utils/components/ThirdPartyDisclaimer.vue'

const store = useStore()
const { t } = useI18n()
const isExpanded = ref(false)

const visibleLayers = computed(() => store.getters.visibleLayers)
const currentBackgroundLayer = computed(() => store.getters.currentBackgroundLayer)
Expand All @@ -19,15 +20,16 @@ const layers = computed(() => {
layersWithAttributions.push(currentBackgroundLayer.value)
}
layersWithAttributions.push(
...visibleLayers.value.filter((layer) => layer.attributions.length > 0)
...visibleLayers.value.filter((layer) => (layer.attributions ?? []).length > 0)
)
return layersWithAttributions
})

const sources = computed(() => {
return layers.value
.map((layer) => {
return layer.attributions.map((attribution) => {
.flatMap((layer) => {
const isLocalFile = store.getters.isLocalFile(layer)
return (layer.attributions ?? []).map((attribution) => {
return {
id: attribution.name.replace(/[._]/g, '-'),
name: attribution.name,
Expand All @@ -37,26 +39,62 @@ const sources = computed(() => {
layer.isExternal,
layer.baseUrl
),
isLocalFile: store.getters.isLocalFile(layer),
isLocalFile,
isTruncated: isLocalFile,
}
})
})
.flat()
.filter((attribution, index, array) => {
const firstIndex = array.findIndex((item) => item.name === attribution.name)
return index === firstIndex
})
})

const VISIBLE_SOURCE_COUNT = 4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine in this mapviewer context!

Idea for SWISSGEO: Rather than a hardcoded number of attributions, we might consider the relative width of the attributions list. So e.g. 6 short attributions are ok, while 4 longer ones would already be compacted. Also, we can make this responsive and treat differently depending on the kind of the device / available screen width

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have places within the mapviewer where we automatically truncate and add a small tooltip which contains the full string (for example: title features). We could use a similar approach to have a character limit, be a bit smart about it to avoid cutting an attribution in the middle, and have it be our trigger to see if we need to truncate the attribution list or not.

@hansmannj hansmannj Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say we keep it as in this PR for the mapviewer.
Any more advanced logic, including responsiveness and the like, is for SWISSGEO's we-portal. And in SWISSGEO we also discuss this with the UX team

const primarySources = computed(() => sources.value.filter((source) => !source.isLocalFile))
const localSources = computed(() => sources.value.filter((source) => source.isLocalFile))
const visibleLocalSourceCount = computed(() =>
Math.max(VISIBLE_SOURCE_COUNT - primarySources.value.length, 0)
)
const inlineSources = computed(() => [
...primarySources.value,
...localSources.value.slice(0, visibleLocalSourceCount.value),
])
const expandedSources = computed(() => localSources.value.slice(visibleLocalSourceCount.value))
const hiddenSourceCount = computed(() => expandedSources.value.length)
const isCompact = computed(() => hiddenSourceCount.value > 0)
const toggleButtonText = computed(() => (isExpanded.value ? '-' : `+${hiddenSourceCount.value}`))

@hansmannj hansmannj Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, when expanded, we should display an x rather than -?
Picky, I know, sorry. And also probably a matter of personal taste, not really super important 😉


const expandButtonLabel = computed(() => {
if (isExpanded.value) {
return t('attribution_overflow_hide_sources')
}
return t(
hiddenSourceCount.value === 1
? 'attribution_overflow_show_source'
: 'attribution_overflow_show_sources',
{ count: hiddenSourceCount.value }
)
})

function closeExpanded() {
isExpanded.value = false
}

function toggleExpanded() {
isExpanded.value = !isExpanded.value
}
</script>

<template>
<div
class="map-footer-attribution"
v-click-outside="closeExpanded"

@hansmannj hansmannj Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess it might make sense to also close on pressing ESC or the like, not only on clicking outside, if that is possible without too large an effort

class="map-footer-attribution d-inline-flex align-items-center flex-wrap"
data-cy="layers-copyrights"
>
<span v-if="sources.length > 0">{{ t('copyright_data') }}</span>
<div
v-for="(source, index) in sources"
v-for="(source, index) in inlineSources"
:key="source.name"
class="d-inline-flex"
>
Expand All @@ -67,22 +105,70 @@ const sources = computed(() => {
:is-local-file="source.isLocalFile"
>
<MapFooterAttributionItem
:source-id="source.id"
:source-id="`${source.id}-${index}`"
:source-name="source.name"
:source-url="source.url"
:has-data-disclaimer="true"
:is-last="index === sources.length - 1"
:is-last="!isCompact && index === inlineSources.length - 1"

@hansmannj hansmannj Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need the !isCompact check here?
How about simply :is-last="index === inlineSources.length - 1"
This would probably prevent a trailing , such as currently observed in Source A, Source B, +3
But this is also a picky comment, sorry.
If we cannot get rid of the traling comma with low effort, then we let it stay there.

:is-truncated="source.isTruncated"
/>
</ThirdPartyDisclaimer>
<MapFooterAttributionItem
v-else
:source-id="source.id"
:source-id="`${source.id}-${index}`"
:source-name="source.name"
:source-url="source.url"
:has-data-disclaimer="false"
:is-last="index === sources.length - 1"
:is-last="!isCompact && index === inlineSources.length - 1"
:is-truncated="source.isTruncated"
/>
</div>
<button
v-if="isCompact"
type="button"
class="map-footer-attribution-toggle btn btn-light btn-sm ms-1 border px-1 py-0"
data-cy="attribution-expand-toggle"
:title="expandButtonLabel"
@click="toggleExpanded"
>
{{ toggleButtonText }}
</button>
<div
v-if="isCompact && isExpanded"
class="map-footer-attribution-expanded-list position-absolute d-flex flex-column end-0 gap-1 overflow-y-auto rounded border bg-white p-2 text-start text-wrap shadow-sm"
data-cy="attribution-expanded-list"
>
<div
v-for="(source, index) in expandedSources"
:key="source.name"
class="d-flex lh-base"
>
<ThirdPartyDisclaimer
v-if="source.hasDataDisclaimer"
:source-name="source.name"
:complete-disclaimer-on-click="!source.url"
:is-local-file="source.isLocalFile"
>
<MapFooterAttributionItem
:source-id="`${source.id}-expanded-${index}`"
:source-name="source.name"
:source-url="source.url"
:has-data-disclaimer="true"
:is-last="true"
:is-truncated="source.isTruncated"
/>
</ThirdPartyDisclaimer>
<MapFooterAttributionItem
v-else
:source-id="`${source.id}-expanded-${index}`"
:source-name="source.name"
:source-url="source.url"
:has-data-disclaimer="false"
:is-last="true"
:is-truncated="source.isTruncated"
/>
</div>
</div>
</div>
</template>

Expand All @@ -96,5 +182,18 @@ const sources = computed(() => {
text-align: center;
position: relative;
pointer-events: all;
max-width: calc(100vw - 0.5rem);
}

.map-footer-attribution-toggle {
font-size: inherit;
line-height: 1.2;
}

.map-footer-attribution-expanded-list {
bottom: calc(100% + 0.25rem);
z-index: $zindex-desktop-footer-infobox;
max-width: min(24rem, calc(100vw - 1rem));
max-height: 30vh;
}
</style>
Loading