From 2f99431f2d1809864120efddb71a12595569a7f2 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Tue, 13 Jan 2026 17:20:29 +0100 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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 = {