From 2f99431f2d1809864120efddb71a12595569a7f2 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Tue, 13 Jan 2026 17:20:29 +0100 Subject: [PATCH 01/47] refactor(reverseGeocoder): introduce shared lib getRefStore --- src/lib/getRefStore.ts | 13 +++++++++++++ src/plugins/reverseGeocoder/store.ts | 9 +++------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/lib/getRefStore.ts diff --git a/src/lib/getRefStore.ts b/src/lib/getRefStore.ts new file mode 100644 index 0000000000..bd466c7396 --- /dev/null +++ b/src/lib/getRefStore.ts @@ -0,0 +1,13 @@ +import type { StoreReference } from '@/core' +import { useCoreStore } from '@/core/stores/export' + +/** + * Get the store for a `StoreReference`. + * + * @param ref - Store reference + * @returns Referenced store + */ +export function getRefStore(ref: StoreReference) { + const coreStore = useCoreStore() + return ref.plugin ? coreStore.getPluginStore(ref.plugin) : coreStore +} diff --git a/src/plugins/reverseGeocoder/store.ts b/src/plugins/reverseGeocoder/store.ts index d39f2f70d9..07e2070a2c 100644 --- a/src/plugins/reverseGeocoder/store.ts +++ b/src/plugins/reverseGeocoder/store.ts @@ -12,6 +12,7 @@ import { acceptHMRUpdate, defineStore } from 'pinia' import { computed, ref, watch, type Reactive, type WatchHandle } from 'vue' import { useCoreStore } from '@/core/stores' +import { getRefStore } from '@/lib/getRefStore' import { indicateLoading } from '@/lib/indicateLoading' import { @@ -41,9 +42,7 @@ export const useReverseGeocoderStore = defineStore( function setupPlugin() { for (const source of configuration.value.coordinateSources || []) { - const store = source.plugin - ? coreStore.getPluginStore(source.plugin) - : coreStore + const store = getRefStore(source) if (!store) { continue } @@ -71,9 +70,7 @@ export const useReverseGeocoderStore = defineStore( target: NonNullable, feature: ReverseGeocoderFeature ) { - const targetStore = target.plugin - ? coreStore.getPluginStore(target.plugin) - : coreStore + const targetStore = getRefStore(target) if (!targetStore) { return } From 74e98dd1cde43759cd05805b3849ddca85e5f1b3 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Tue, 13 Jan 2026 17:22:14 +0100 Subject: [PATCH 02/47] feat(core): export `center` and `extent` as instable API --- src/core/components/PolarMap.ce.vue | 4 +++- src/core/stores/index.ts | 15 +++++++++++++++ src/core/stores/main.ts | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/components/PolarMap.ce.vue b/src/core/components/PolarMap.ce.vue index 7f788a0422..dad3ac922b 100644 --- a/src/core/components/PolarMap.ce.vue +++ b/src/core/components/PolarMap.ce.vue @@ -31,13 +31,15 @@ import { updateDragAndZoomInteractions } from '../utils/map/updateDragAndZoomInt import PolarMapOverlay from './PolarMapOverlay.ce.vue' const mainStore = useMainStore() -const { hasWindowSize, hasSmallDisplay, center, zoom } = storeToRefs(mainStore) +const { hasWindowSize, hasSmallDisplay, center, extent, zoom } = + storeToRefs(mainStore) const polarMapContainer = useTemplateRef('polar-map-container') const overlay = useTemplateRef('polar-map-overlay') function onMove() { center.value = mainStore.map.getView().getCenter() || center.value + extent.value = mainStore.map.getView().calculateExtent() zoom.value = mainStore.map.getView().getZoom() || zoom.value } diff --git a/src/core/stores/index.ts b/src/core/stores/index.ts index 88a82d49c4..24b92ed3ab 100644 --- a/src/core/stores/index.ts +++ b/src/core/stores/index.ts @@ -29,6 +29,13 @@ export const useCoreStore = defineStore('core', () => { const markerStore = useMarkerStore() return { + /** + * Read or modify center coordinate of the map. + * + * @internal + */ + center: mainStoreRefs.center, + /** * Color scheme the client should be using. * @@ -60,6 +67,14 @@ export const useCoreStore = defineStore('core', () => { */ deviceIsHorizontal: computed(() => mainStore.deviceIsHorizontal), + /** + * Extent of the map. + * + * @alpha + * @readonly + */ + extent: computed(() => mainStore.extent), + /** * Whether the map has a maximum height of {@link SMALL_DISPLAY_HEIGHT} and * a maximum width of {@link SMALL_DISPLAY_WIDTH}. diff --git a/src/core/stores/main.ts b/src/core/stores/main.ts index e997721b57..de7e876255 100644 --- a/src/core/stores/main.ts +++ b/src/core/stores/main.ts @@ -1,5 +1,6 @@ import type { Feature, Map } from 'ol' import type { Coordinate } from 'ol/coordinate' +import type { Extent } from 'ol/extent' import type { Point } from 'ol/geom' import { toMerged } from 'es-toolkit' @@ -27,6 +28,7 @@ export const useMainStore = defineStore('main', () => { defaults ) ) + const extent = ref([0, 0, 0, 0]) const language = ref('') const lightElement = ref(null) const map = shallowRef({} as Map) @@ -100,6 +102,7 @@ export const useMainStore = defineStore('main', () => { serviceRegister, shadowRoot, center, + extent, zoom, // Getters layout, From 0da4c0aace7a1fc2e4cbbf30975169fa28fcecb7 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Fri, 16 Jan 2026 13:54:17 +0100 Subject: [PATCH 03/47] feat(core): add shared pagination component --- src/components/kern/KernPagination.ce.vue | 154 ++++++++++++++++++++++ src/core/types/locales.ts | 12 +- src/core/vuePlugins/i18next.ts | 7 + src/locales.ts | 59 +++++++++ 4 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 src/components/kern/KernPagination.ce.vue create mode 100644 src/locales.ts diff --git a/src/components/kern/KernPagination.ce.vue b/src/components/kern/KernPagination.ce.vue new file mode 100644 index 0000000000..8d717fa4c3 --- /dev/null +++ b/src/components/kern/KernPagination.ce.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/src/core/types/locales.ts b/src/core/types/locales.ts index 43ac237249..496c9d0c06 100644 --- a/src/core/types/locales.ts +++ b/src/core/types/locales.ts @@ -2,7 +2,8 @@ import type { ResourceKey } from 'i18next' import type { BundledPluginId, BundledPluginLocaleResources } from '@/core' import type { resourcesEn as core } from '@/core/locales' -import type { CoreId } from '@/core/vuePlugins/i18next' +import type { resourcesEn as shared } from '@/locales' +import type { CoreId, SharedId } from '@/core/vuePlugins/i18next' /** @internal */ export interface Locale { @@ -12,9 +13,14 @@ export interface Locale { /** @internal */ export type LocaleResources = { - [T in typeof CoreId | BundledPluginId]: T extends BundledPluginId + [T in + | typeof CoreId + | typeof SharedId + | BundledPluginId]: T extends BundledPluginId ? BundledPluginLocaleResources - : typeof core + : T extends typeof SharedId + ? typeof shared + : typeof core } type ToLocaleOverride = T extends string diff --git a/src/core/vuePlugins/i18next.ts b/src/core/vuePlugins/i18next.ts index 17fee380a5..5c3291f0c3 100644 --- a/src/core/vuePlugins/i18next.ts +++ b/src/core/vuePlugins/i18next.ts @@ -5,8 +5,10 @@ import LanguageDetector from 'i18next-browser-languagedetector' import I18NextVue from 'i18next-vue' import locales from '../locales' +import sharedLocales from '@/locales' export const CoreId = 'core' +export const SharedId = 'shared' export const I18Next: Plugin = { async install(app) { @@ -28,6 +30,11 @@ export const I18Next: Plugin = { supportedLngs: locales.map(({ type }) => type), }) + // This is no plugin itself, but bundled for usage of all plugins + sharedLocales.forEach((lng) => { + i18next.addResourceBundle(lng.type, SharedId, lng.resources, true) + }) + // eslint-disable-next-line no-console console.info(`Successfully initialized i18next.`) } catch (error: unknown) { diff --git a/src/locales.ts b/src/locales.ts new file mode 100644 index 0000000000..28806ca6e1 --- /dev/null +++ b/src/locales.ts @@ -0,0 +1,59 @@ +/* eslint-disable tsdoc/syntax */ +/** + * This is the documentation for the locales keys for POLAR shared components. + * These locales are *NOT* exported, but documented only. + * + * @module locales/shared + */ +/* eslint-enable tsdoc/syntax */ + +import type { Locale } from '@/core/types' + +/** + * German locales for POLAR core. + * For overwriting these values, pass a partial object of this in `locales`. + */ +export const resourcesDe = { + pagination: { + currentPage: 'Aktuelle Seite, Seite {{page}}', + page: 'Seite {{page}}', + next: 'Nächste Seite', + previous: 'Vorherige Seite', + wrapper: 'Seitenauswahl', + }, +} as const + +/** + * English locales for POLAR core. + * For overwriting these values, pass a partial object of this in `locales`. + */ +export const resourcesEn = { + pagination: { + currentPage: 'Aktuelle Seite, Seite {{page}}', + page: 'Seite {{page}}', + next: 'Nächste Seite', + previous: 'Vorherige Seite', + wrapper: 'Seitenauswahl', + }, +} as const + +/** + * Core locales. + * + * @privateRemarks + * The first entry will be used as fallback. + * + * @internal + */ +const locales: Locale[] = [ + { + type: 'de', + resources: resourcesDe, + }, + { + type: 'en', + resources: resourcesEn, + }, +] + +export default locales From 15972bfff219673bb5a3ba109cbaae995271fcf1 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Tue, 13 Jan 2026 17:24:02 +0100 Subject: [PATCH 04/47] test(snowbox): allow jumping to Hamburg via button --- examples/snowbox/YetAnotherEmptyComponent.vue | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/snowbox/YetAnotherEmptyComponent.vue b/examples/snowbox/YetAnotherEmptyComponent.vue index affcb15908..ca48bebf73 100644 --- a/examples/snowbox/YetAnotherEmptyComponent.vue +++ b/examples/snowbox/YetAnotherEmptyComponent.vue @@ -1,7 +1,17 @@ From d449c8d5d3a0eb16984bb7c1c3b2490bdba8683c Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 16 Feb 2026 14:09:32 +0100 Subject: [PATCH 05/47] test(snowbox): debug assistant for browser devtools The usually helpful variables (map, stores, etc.) are auto-exported as global variables for use with the browser's devtools in snowbox. --- examples/snowbox/debug-assistant.js | 25 +++++++++++++++++++++++++ examples/snowbox/index.html | 1 + src/core/stores/index.ts | 16 ++++++++++++++++ src/core/stores/plugin.ts | 5 ++++- 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 examples/snowbox/debug-assistant.js diff --git a/examples/snowbox/debug-assistant.js b/examples/snowbox/debug-assistant.js new file mode 100644 index 0000000000..49a8b521ff --- /dev/null +++ b/examples/snowbox/debug-assistant.js @@ -0,0 +1,25 @@ +import { getStore } from '@polar/polar' + +function initializeDebugAssistant() { + const map = document.getElementById('snowbox') + const coreStore = getStore(map, 'core') + const activePluginIds = coreStore.activePluginIds + + window.map = map + window.olMap = coreStore.map + window.coreStore = coreStore + window.activePluginIds = activePluginIds + for (const pluginId of activePluginIds) { + window[`${pluginId}Store`] = coreStore.getPluginStore(pluginId) + } +} + +// We want to load as late as possible. +// Especially, the timeout stuff from snowbox code should be done when doing this. +setTimeout(() => { + initializeDebugAssistant() + + // 7 seconds may be long sometimes, inform the developer about it. + // eslint-disable-next-line no-console + console.info('POLAR debug assistant was successfully initialized') +}, 7000) diff --git a/examples/snowbox/index.html b/examples/snowbox/index.html index 79a0317df1..69c45bfbe9 100644 --- a/examples/snowbox/index.html +++ b/examples/snowbox/index.html @@ -8,6 +8,7 @@ + diff --git a/src/plugins/gfi/components/GfiFeatureList.ce.vue b/src/plugins/gfi/components/GfiFeatureList.ce.vue new file mode 100644 index 0000000000..ba1291e255 --- /dev/null +++ b/src/plugins/gfi/components/GfiFeatureList.ce.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/plugins/gfi/components/GfiUI.ce.vue b/src/plugins/gfi/components/GfiUI.ce.vue new file mode 100644 index 0000000000..0a940eba09 --- /dev/null +++ b/src/plugins/gfi/components/GfiUI.ce.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/plugins/gfi/composables/useFeatureDisplayLayer.ts b/src/plugins/gfi/composables/useFeatureDisplayLayer.ts new file mode 100644 index 0000000000..fb7e91a272 --- /dev/null +++ b/src/plugins/gfi/composables/useFeatureDisplayLayer.ts @@ -0,0 +1,86 @@ +import type { Feature as GeoJsonFeature } from 'geojson' +import type { Style } from 'ol/style' + +import { Feature, Map } from 'ol' +import { GeoJSON } from 'ol/format' +import VectorLayer from 'ol/layer/Vector' +import { Vector } from 'ol/source' +import { onScopeDispose, watch, type Ref } from 'vue' + +function getFeatureDisplayLayer() { + const featureDisplayLayer = new VectorLayer({ + source: new Vector({ + features: [], + }), + }) + + featureDisplayLayer.set('polarInternalId', 'pluginGfiFeatureDisplay') + featureDisplayLayer.setZIndex(90) + // NOTE: This may be changed in the future to not use the default styling of @masterportal/masterportalapi + featureDisplayLayer.set('styleId', 'defaultHighlightFeaturesPoint') + + return featureDisplayLayer +} + +function isVectorSource(source): source is Vector { + return source instanceof Vector +} + +/** + * reset feature layer's features. + */ +function clear(featureDisplayLayer: VectorLayer): void { + const source = featureDisplayLayer.getSource() + if (isVectorSource(source)) { + source.clear() + } +} + +/** + * add feature from jsonable GeoJson object. + */ +function addFeature( + feature: GeoJsonFeature, + featureDisplayLayer: VectorLayer +): void { + const source = featureDisplayLayer.getSource() + if (isVectorSource(source)) { + // Since ol@10, readFeature may also return a Feature[]? + source.addFeature(new GeoJSON().readFeature(feature) as Feature) + } +} + +export function useFeatureDisplayLayer(options: { + map: Map + style: Ref diff --git a/src/plugins/gfi/stores/feature.ts b/src/plugins/gfi/stores/feature.ts index ac0d63d13c..8369151744 100644 --- a/src/plugins/gfi/stores/feature.ts +++ b/src/plugins/gfi/stores/feature.ts @@ -4,13 +4,16 @@ import { rawLayerList } from '@masterportal/masterportalapi' import { isEqual } from 'es-toolkit' import { MapBrowserEvent, Overlay } from 'ol' import { acceptHMRUpdate, defineStore } from 'pinia' -import { computed, onScopeDispose, ref, watch } from 'vue' +import { computed, nextTick, onScopeDispose, ref, watch } from 'vue' import { useCoreStore } from '@/core/stores' -import type { GfiLayerConfiguration, RequestGfiParameters } from '../types' - import { useMultiSelection } from '../composables/useMultiSelection' +import { + PluginId, + type GfiLayerConfiguration, + type RequestGfiParameters, +} from '../types' import { requestGfi } from '../utils/requestGfi' import { updateTooltip } from '../utils/updateTooltip' import { useGfiMainStore } from './main' @@ -236,6 +239,21 @@ export const useGfiFeatureStore = defineStore('plugins/gfi/feature', () => { coreStore.map.removeOverlay(overlay) }) + if (gfiMainStore.configuration.renderType === 'iconMenu') { + // TODO: Find a better solution to wait for this plugin + // As, in this case, we render as part of the iconMenu, the iconMenu store will be available soon. + void nextTick(() => { + const iconMenuStore = coreStore.getPluginStore('iconMenu') + if (iconMenuStore) { + watch(selectedFeature, (newFeature) => { + if (newFeature) { + iconMenuStore.openMenuById(PluginId) + } + }) + } + }) + } + return { visibleFeatures, selectedFeatureIndex, From 978d7183564ba9dd6a5f888092cb6c0b9838ac28 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:24:55 +0100 Subject: [PATCH 09/47] fix(gfi): remove legacy manual plugin prefix for console MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- src/plugins/gfi/utils/requestGfiWms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/gfi/utils/requestGfiWms.ts b/src/plugins/gfi/utils/requestGfiWms.ts index 24aaf48704..cba4bd2fdf 100644 --- a/src/plugins/gfi/utils/requestGfiWms.ts +++ b/src/plugins/gfi/utils/requestGfiWms.ts @@ -60,7 +60,7 @@ function readTextFeatures(text: string): Feature[] { feature.set(key || '', value) } else { console.error( - '@polar/plugin-gfi: Found property before feature declaration in readTextFeatures.', + 'Found property before feature declaration in readTextFeatures.', line, 'Skipping ...' ) From 6d6008610b652bce6a8af3ea91851e9567733d77 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:25:56 +0100 Subject: [PATCH 10/47] docs: fix shared locales hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- src/locales.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales.ts b/src/locales.ts index 28806ca6e1..62c02c9aa6 100644 --- a/src/locales.ts +++ b/src/locales.ts @@ -10,7 +10,7 @@ import type { Locale } from '@/core/types' /** - * German locales for POLAR core. + * German locales for POLAR shared components. * For overwriting these values, pass a partial object of this in `locales`. */ export const resourcesDe = { From 6182677d1048092aafd7730eb1589250826de8a3 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:26:08 +0100 Subject: [PATCH 11/47] docs: fix shared locales hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- src/locales.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales.ts b/src/locales.ts index 62c02c9aa6..1e89b2e026 100644 --- a/src/locales.ts +++ b/src/locales.ts @@ -38,7 +38,7 @@ export const resourcesEn = { } as const /** - * Core locales. + * POLAR shared components locales. * * @privateRemarks * The first entry will be used as fallback. From 439ae45a5d2e8c26b5173dba24e72a9a0852ff47 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:26:22 +0100 Subject: [PATCH 12/47] docs: fix shared locales hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- src/locales.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales.ts b/src/locales.ts index 1e89b2e026..071900ed54 100644 --- a/src/locales.ts +++ b/src/locales.ts @@ -24,7 +24,7 @@ export const resourcesDe = { } as const /** - * English locales for POLAR core. + * English locales for POLAR shared components. * For overwriting these values, pass a partial object of this in `locales`. */ export const resourcesEn = { From 63a5d72d1600116f523581777689924d8b7f72b6 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Wed, 15 Apr 2026 20:24:39 +0200 Subject: [PATCH 13/47] docs(gfi): remove duplicate ToDo hint --- src/plugins/gfi/utils/requestGfiWfs.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/gfi/utils/requestGfiWfs.ts b/src/plugins/gfi/utils/requestGfiWfs.ts index 020d35545a..018c37ec89 100644 --- a/src/plugins/gfi/utils/requestGfiWfs.ts +++ b/src/plugins/gfi/utils/requestGfiWfs.ts @@ -57,7 +57,6 @@ export default ({ const { geometryName } = layerConfiguration const code = map.getView().getProjection().getCode() const typeName = version === '2.0.0' ? 'typeNames' : 'typename' - // TODO: Layer list needs better typing const featureUrl = new URL(url as string) featureUrl.searchParams.set('service', 'WFS') From 55ff02a5c82ac6277921ac862cd9e2b84ff78685 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Wed, 15 Apr 2026 20:31:56 +0200 Subject: [PATCH 14/47] docs(gfi): use typedoc-linking to layout nineRegions --- src/core/types/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/types/plugin.ts b/src/core/types/plugin.ts index 92144c5f34..df4613230f 100644 --- a/src/core/types/plugin.ts +++ b/src/core/types/plugin.ts @@ -55,7 +55,8 @@ export interface PluginOptions { /** * The region where the plugin should be rendered. - * Required for nine-regions layout, ignored otherwise. + * + * Only usable if {@link MapConfiguration.layout | `layout`} is set to `'nineRegions'`. */ layoutTag?: keyof typeof NineLayoutTag From c2edb5291cd4747f72447857ba24c886c3b03955 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Wed, 15 Apr 2026 21:14:47 +0200 Subject: [PATCH 15/47] feat(core): introduce InterfacePluginOptions --- src/core/types/plugin.ts | 2 ++ src/plugins/attributions/types.ts | 4 ++-- src/plugins/fullscreen/types.ts | 4 ++-- src/plugins/geoLocation/types.ts | 5 +++-- src/plugins/gfi/types.ts | 4 ++-- src/plugins/layerChooser/index.ts | 8 ++++++-- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/core/types/plugin.ts b/src/core/types/plugin.ts index df4613230f..66f67fab48 100644 --- a/src/core/types/plugin.ts +++ b/src/core/types/plugin.ts @@ -59,7 +59,9 @@ export interface PluginOptions { * Only usable if {@link MapConfiguration.layout | `layout`} is set to `'nineRegions'`. */ layoutTag?: keyof typeof NineLayoutTag +} +export interface InterfacePluginOptions extends PluginOptions { /** * Defines if the plugin is rendered independent or as part of the icon menu. * This is automatically set by the icon menu; you should not need to touch this. diff --git a/src/plugins/attributions/types.ts b/src/plugins/attributions/types.ts index 714f13296d..00cdaeccb9 100644 --- a/src/plugins/attributions/types.ts +++ b/src/plugins/attributions/types.ts @@ -1,4 +1,4 @@ -import type { PluginOptions, StoreReference } from '@/core' +import type { InterfacePluginOptions, StoreReference } from '@/core' export const PluginId = 'attributions' @@ -63,7 +63,7 @@ export interface Attribution { * All parameters are optional. However, setting neither {@link layerAttributions} * nor {@link staticAttributions} results in an empty window. */ -export interface AttributionsPluginOptions extends PluginOptions { +export interface AttributionsPluginOptions extends InterfacePluginOptions { /** * Optional icon override. */ diff --git a/src/plugins/fullscreen/types.ts b/src/plugins/fullscreen/types.ts index 1e12f43c35..9d9a7515b3 100644 --- a/src/plugins/fullscreen/types.ts +++ b/src/plugins/fullscreen/types.ts @@ -1,4 +1,4 @@ -import type { PluginOptions } from '@/core' +import type { InterfacePluginOptions } from '@/core' /** * Plugin identifier. @@ -8,7 +8,7 @@ export const PluginId = 'fullscreen' /** * Plugin options for fullscreen plugin. */ -export interface FullscreenPluginOptions extends PluginOptions { +export interface FullscreenPluginOptions extends InterfacePluginOptions { /** * Defines the target container to show in fullscreen mode. * This defaults to the web component (i.e., the map with its plugin controls). diff --git a/src/plugins/geoLocation/types.ts b/src/plugins/geoLocation/types.ts index 6e96297d32..b656dc8f4d 100644 --- a/src/plugins/geoLocation/types.ts +++ b/src/plugins/geoLocation/types.ts @@ -1,4 +1,4 @@ -import type { LayerBoundPluginOptions } from '@/core' +import type { LayerBoundPluginOptions, InterfacePluginOptions } from '@/core' /** * Plugin identifier. @@ -13,7 +13,8 @@ export type PluginState = 'LOCATABLE' | 'LOCATED' | 'DISABLED' /** * Plugin options for geoLocation plugin. */ -export interface GeoLocationPluginOptions extends LayerBoundPluginOptions { +export interface GeoLocationPluginOptions + extends LayerBoundPluginOptions, InterfacePluginOptions { /** * If `true`, the location check will be run on map start-up. If `false`, the * feature has to be triggered with a button press by the user. diff --git a/src/plugins/gfi/types.ts b/src/plugins/gfi/types.ts index 22bd13c344..3e8b36bf93 100644 --- a/src/plugins/gfi/types.ts +++ b/src/plugins/gfi/types.ts @@ -7,7 +7,7 @@ import type { ImageWMS, TileWMS } from 'ol/source' import type { Options as Fill } from 'ol/style/Fill' import type { Options as Stroke } from 'ol/style/Stroke' -import type { PluginOptions, StoreReference } from '@/core' +import type { InterfacePluginOptions, StoreReference } from '@/core' /** * Plugin identifier. @@ -238,7 +238,7 @@ export interface FeatureList { * } * ``` */ -export interface GfiPluginOptions extends PluginOptions { +export interface GfiPluginOptions extends InterfacePluginOptions { /** * Maps a string (must be a layer ID) to a behaviour configuration for that layer. */ diff --git a/src/plugins/layerChooser/index.ts b/src/plugins/layerChooser/index.ts index 3e406f2f19..c0a6d5516f 100644 --- a/src/plugins/layerChooser/index.ts +++ b/src/plugins/layerChooser/index.ts @@ -4,7 +4,11 @@ */ /* eslint-enable tsdoc/syntax */ -import type { PluginContainer, PluginOptions, PolarPluginStore } from '@/core' +import type { + PluginContainer, + InterfacePluginOptions, + PolarPluginStore, +} from '@/core' import component from './components/LayerChooser.ce.vue' import locales from './locales' @@ -25,7 +29,7 @@ import { PluginId } from './types' * @returns Plugin for use with {@link addPlugin}. */ export default function pluginLayerChooser( - options: PluginOptions + options: InterfacePluginOptions ): PluginContainer { return { id: PluginId, From 79852f313b59b0e25e2344c51bd520cc5dc8c579 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Wed, 15 Apr 2026 21:47:27 +0200 Subject: [PATCH 16/47] refactor(gfi): introduce new composable usePluginStoreWatcher --- src/plugins/gfi/stores/feature.ts | 33 +++++++++++++------------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/plugins/gfi/stores/feature.ts b/src/plugins/gfi/stores/feature.ts index 8369151744..91cf640fc8 100644 --- a/src/plugins/gfi/stores/feature.ts +++ b/src/plugins/gfi/stores/feature.ts @@ -6,6 +6,7 @@ import { MapBrowserEvent, Overlay } from 'ol' import { acceptHMRUpdate, defineStore } from 'pinia' import { computed, nextTick, onScopeDispose, ref, watch } from 'vue' +import { usePluginStoreWatcher } from '@/composables/usePluginStoreWatcher' import { useCoreStore } from '@/core/stores' import { useMultiSelection } from '../composables/useMultiSelection' @@ -108,25 +109,19 @@ export const useGfiFeatureStore = defineStore('plugins/gfi/feature', () => { gfiMainStore.featureInformation = result } - for (const source of gfiMainStore.configuration.coordinateSources || []) { - const store = source.plugin - ? coreStore.getPluginStore(source.plugin) - : coreStore - if (!store) { - continue - } - watch( - () => store[source.key], - async (coordinate) => { - if (coordinate) { - await getFeatureInfo(coordinate) - } else { - gfiMainStore.featureInformation = {} - } - }, - { immediate: true } - ) - } + usePluginStoreWatcher( + gfiMainStore.configuration.coordinateSources || [], + async (coordinate) => { + if (coordinate) { + await getFeatureInfo( + coordinate as RequestGfiParameters['coordinateOrExtent'] + ) + } else { + gfiMainStore.featureInformation = {} + } + }, + { immediate: true } + ) if (gfiMainStore.configuration.multiSelect) { const multiSelection = useMultiSelection({ From 9f2541387f0486a0b408601530631914017a0c95 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Thu, 16 Apr 2026 16:05:12 +0200 Subject: [PATCH 17/47] fix(core): consider hoveredFeature set from plugin again --- src/core/utils/map/setupMarkers.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/core/utils/map/setupMarkers.ts b/src/core/utils/map/setupMarkers.ts index 89281abb7e..51c3e7e380 100644 --- a/src/core/utils/map/setupMarkers.ts +++ b/src/core/utils/map/setupMarkers.ts @@ -222,13 +222,19 @@ export function setupMarkers(map: Map) { } if (feature !== null && feature !== toRaw(store.selected)) { store.hovered = markRaw(feature) - const isMultiFeature = store.hovered.get('features')?.length > 1 - const style = getMarkerStyle( - getLayerConfiguration(feature.get('_polarLayerId') as string) - .hoverStyle, - isMultiFeature + const layerId = feature.get('_polarLayerId') as string + const selectedCluster = + // @ts-expect-error | Found layers always have a source and getDistance is defined on cluster sources. + typeof findLayer(map, layerId)?.getSource().getDistance === 'function' + ? getCluster(map, feature, '_polarLayerId') + : feature + selectedCluster.setStyle( + getMarkerStyle( + getLayerConfiguration(feature.get('_polarLayerId') as string) + .hoverStyle, + selectedCluster.get('features')?.length > 1 + ) ) - feature.setStyle(style) } } ) From d15b2c2353723ec832a3e7d74f3eb042686e8bb2 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Thu, 16 Apr 2026 16:09:07 +0200 Subject: [PATCH 18/47] chore(core): markers: fix double star in comment --- src/core/utils/markers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/utils/markers.ts b/src/core/utils/markers.ts index 33e82e45e6..a14e2d3b4c 100644 --- a/src/core/utils/markers.ts +++ b/src/core/utils/markers.ts @@ -71,7 +71,7 @@ const anchor = [0.5, 1] /** * The map became a little laggy due to constant re-generation of styles. * This memoization function optimises this issue by reusing styles. - * */ + */ const memoizeStyle = (getMarker: GetMarkerFunction): GetMarkerFunction => { const singleCache = new Map() const multiCache = new Map() From 4dfe6afafec4d3d4e80347114b8619c61a0e02d7 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Thu, 16 Apr 2026 16:09:41 +0200 Subject: [PATCH 19/47] test(snowbox): enable bindWithCoreHoverSelect for gfi plugin --- examples/snowbox/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/snowbox/index.js b/examples/snowbox/index.js index e5172c1fac..6eebb74ca4 100644 --- a/examples/snowbox/index.js +++ b/examples/snowbox/index.js @@ -447,7 +447,7 @@ addPlugin( key: 'activeMaskIds', }, mode: 'visible', - bindWithCoreHoverSelect: false, + bindWithCoreHoverSelect: true, pageLength: 5, text: { title: (feature) => From 875a35cb25a0e88c47fec7b97b66c80482d20052 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Thu, 16 Apr 2026 16:45:21 +0200 Subject: [PATCH 20/47] fix(gfi): wrap text for empty view in feature list --- src/plugins/gfi/components/GfiFeatureList.ce.vue | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/gfi/components/GfiFeatureList.ce.vue b/src/plugins/gfi/components/GfiFeatureList.ce.vue index ba1291e255..f34b453b8b 100644 --- a/src/plugins/gfi/components/GfiFeatureList.ce.vue +++ b/src/plugins/gfi/components/GfiFeatureList.ce.vue @@ -2,9 +2,12 @@

{{ $t(($) => $.list.header, { ns: 'gfi' }) }}

- +