diff --git a/examples/iceberg/App.vue b/examples/iceberg/App.vue index 4befdf0069..01ec40645c 100644 --- a/examples/iceberg/App.vue +++ b/examples/iceberg/App.vue @@ -45,6 +45,7 @@ import kernExtraIcons from 'virtual:kern-extra-icons' import { computed, ref } from 'vue' +import GfiConfig from './components/GfiConfig.vue' import IcebergMap from './components/IcebergMap.vue' import LayoutChooser from './components/LayoutChooser.vue' import TaskMenu from './components/TaskMenu.vue' @@ -55,6 +56,11 @@ const configTasks = [ label: 'Layout wählen', component: LayoutChooser, }, + { + id: 'configure-gfi', + label: 'GFI konfigurieren', + component: GfiConfig, + }, ] const tasks = computed(() => [ 'menu', diff --git a/examples/iceberg/components/GfiConfig.vue b/examples/iceberg/components/GfiConfig.vue new file mode 100644 index 0000000000..fea10d4350 --- /dev/null +++ b/examples/iceberg/components/GfiConfig.vue @@ -0,0 +1,84 @@ + + + diff --git a/examples/iceberg/components/IcebergMap.vue b/examples/iceberg/components/IcebergMap.vue index e9deb91b8c..bdb403da53 100644 --- a/examples/iceberg/components/IcebergMap.vue +++ b/examples/iceberg/components/IcebergMap.vue @@ -39,6 +39,7 @@ import pluginAddressSearch from '@polar/polar/plugins/addressSearch' import pluginAttributions from '@polar/polar/plugins/attributions' import pluginFilter from '@polar/polar/plugins/filter' import pluginFullscreen from '@polar/polar/plugins/fullscreen' +import pluginGfi from '@polar/polar/plugins/gfi' import pluginIconMenu from '@polar/polar/plugins/iconMenu' import pluginLayerChooser from '@polar/polar/plugins/layerChooser' import pluginPins from '@polar/polar/plugins/pins' @@ -119,6 +120,16 @@ watch(map, (map) => { layoutTag: 'TOP_LEFT', searchMethods: [], }), + pluginGfi({ + displayComponent: true, + layoutTag: 'TOP_LEFT', + layers: { + '1454': { + window: true, + geometry: true, + }, + }, + }), pluginPins({ coordinateSources: [{ plugin: 'addressSearch', key: 'chosenAddress' }], boundary: { diff --git a/examples/iceberg/stores/iceberg.ts b/examples/iceberg/stores/iceberg.ts index 7b22f3fb9c..09c40d768e 100644 --- a/examples/iceberg/stores/iceberg.ts +++ b/examples/iceberg/stores/iceberg.ts @@ -31,7 +31,6 @@ export const useIcebergStore = defineStore('iceberg', () => { name: 'Ausgleichsflächen', styleId: 'panda', visibility: true, - minZoom: 5, }, { id: '1693', @@ -69,6 +68,7 @@ export const useIcebergStore = defineStore('iceberg', () => { visibility: true, type: 'mask', name: 'snowbox.layers.mml', + minZoom: 5, }, ], startCenter: [565874, 5934140], @@ -132,6 +132,9 @@ export const useIcebergStore = defineStore('iceberg', () => { displayComponent: true, layoutTag: 'TOP_RIGHT', }, + gfi: { + directSelect: true, + } as MapConfiguration['gfi'], markers: { layers: [ { 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 @@ 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/core/components/PolarMap.ce.vue b/src/core/components/PolarMap.ce.vue index f5085e4b9b..bcafcb4a43 100644 --- a/src/core/components/PolarMap.ce.vue +++ b/src/core/components/PolarMap.ce.vue @@ -38,13 +38,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 d07d94e718..8aa4ab71bd 100644 --- a/src/core/stores/index.ts +++ b/src/core/stores/index.ts @@ -4,9 +4,12 @@ */ /* eslint-enable tsdoc/syntax */ +import type { Feature } from 'ol' + import { acceptHMRUpdate, defineStore, storeToRefs } from 'pinia' import { computed } from 'vue' +import { updateSelection } from '../utils/map/setupMarkers' import { useMainStore } from './main' import { useMarkerStore } from './marker' import { useMoveHandleStore } from './moveHandle' @@ -22,23 +25,26 @@ import { usePluginStore } from './plugin' export const useCoreStore = defineStore('core', () => { const mainStore = useMainStore() const mainStoreRefs = storeToRefs(mainStore) + const moveHandleStore = useMoveHandleStore() const pluginStore = usePluginStore() const markerStore = useMarkerStore() + const markerStoreRefs = storeToRefs(markerStore) return { /** - * The current center coordinates of the map. + * Read or modify center coordinate of the map. * * @alpha - * @readonly */ - center: computed(() => mainStore.center), + center: mainStoreRefs.center, /** * Color scheme the client should be using. + * + * @alpha */ colorScheme: mainStoreRefs.colorScheme, @@ -74,6 +80,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}. @@ -119,6 +133,22 @@ export const useCoreStore = defineStore('core', () => { */ zoom: mainStoreRefs.zoom, + /** + * Returns the layer with the given ID. + * + * @param layerId - ID of the layer + * @alpha + */ + getLayer: mainStore.getLayer, + + /** + * List of all active plugin's IDs. + * + * @readonly + * @alpha + */ + activePluginIds: computed(() => pluginStore.activePluginIds), + /** * Before instantiating the map, all required plugins have to be added. Depending on how you use POLAR, this may * already have been done. Ready-made clients (that is, packages prefixed `@polar/client-`) come with plugins prepared. @@ -215,18 +245,33 @@ export const useCoreStore = defineStore('core', () => { moveHandleTop: computed(() => moveHandleStore.top), /** - * Currently hovered marker feature or null. + * Feature that is hovered by the user with a marker. * - * @readonly + * @remarks + * The attribute _polarLayerId needs to be set. + * + * @alpha */ - hovered: computed(() => markerStore.hovered), + hoveredFeature: markerStoreRefs.hovered, /** - * Currently selected marker feature or null. + * Feature that was selected by the user with a marker. * - * @readonly + * If this value is modified, the newly selected feature is centered on the map. + * + * @remarks + * The attribute _polarLayerId needs to be set. + * + * Wait at least one `nextTick` after modifying {@link hoveredFeature} before touching this value. + * + * @alpha */ - selected: computed(() => markerStore.selected), + selectedFeature: computed({ + get: () => markerStore.selected, + set: (feature) => { + updateSelection(mainStore.map, feature as Feature, true) + }, + }), /** * Coordinates that were selected by the user with a marker. diff --git a/src/core/stores/main.ts b/src/core/stores/main.ts index 0537315402..8be26985d6 100644 --- a/src/core/stores/main.ts +++ b/src/core/stores/main.ts @@ -30,6 +30,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) @@ -81,6 +82,10 @@ export const useMainStore = defineStore('main', () => { center.value = (feature.getGeometry() as Point).getCoordinates() } + function getLayer(layerId: string) { + return map.value.getAllLayers().find((layer) => layer.get('id') === layerId) + } + function getLayerMapConfiguration(layerId: string) { const polar = configuration.value.layers.find( (layer) => layer.id === layerId @@ -119,6 +124,7 @@ export const useMainStore = defineStore('main', () => { serviceRegister, shadowRoot, center, + extent, zoom, // Getters layout, @@ -128,6 +134,7 @@ export const useMainStore = defineStore('main', () => { deviceIsHorizontal, // Actions centerOnFeature, + getLayer, updateHasSmallDisplay, getLayerMapConfiguration, setup, diff --git a/src/core/stores/plugin.ts b/src/core/stores/plugin.ts index b6b0d14398..9373fe9f46 100644 --- a/src/core/stores/plugin.ts +++ b/src/core/stores/plugin.ts @@ -1,7 +1,7 @@ import { toMerged } from 'es-toolkit' import i18next from 'i18next' import { acceptHMRUpdate, defineStore } from 'pinia' -import { markRaw, reactive } from 'vue' +import { computed, markRaw, reactive } from 'vue' import type { PluginContainer, @@ -18,6 +18,8 @@ export const usePluginStore = defineStore('plugin', () => { const plugins = reactive([]) const mainStore = useMainStore() + const activePluginIds = computed(() => plugins.map((plugin) => plugin.id)) + function addPlugin(plugin: PluginContainer) { const { id, locales, options, storeModule } = plugin @@ -81,6 +83,7 @@ export const usePluginStore = defineStore('plugin', () => { return { plugins, + activePluginIds, addPlugin, removePlugin, getPluginStore, diff --git a/src/core/types/locales.ts b/src/core/types/locales.ts index 43ac237249..07ab53668e 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 { CoreId, SharedId } from '@/core/vuePlugins/i18next' +import type { resourcesEn as shared } from '@/locales' /** @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/types/main.ts b/src/core/types/main.ts index 612990b5c6..3e2e69b841 100644 --- a/src/core/types/main.ts +++ b/src/core/types/main.ts @@ -6,6 +6,7 @@ import type { FilterPluginOptions } from '@/plugins/filter' import type { FooterPluginOptions } from '@/plugins/footer' import type { FullscreenPluginOptions } from '@/plugins/fullscreen' import type { GeoLocationPluginOptions } from '@/plugins/geoLocation' +import type { GfiPluginOptions } from '@/plugins/gfi' import type { IconMenuPluginOptions } from '@/plugins/iconMenu' import type { LoadingIndicatorOptions } from '@/plugins/loadingIndicator' import type { PinsPluginOptions } from '@/plugins/pins' @@ -325,6 +326,9 @@ export interface MapConfiguration extends MasterportalApiConfiguration { /** Configuration for geoLocation plugin. */ geoLocation?: GeoLocationPluginOptions + /** Configuration for gfi plugin. */ + gfi?: GfiPluginOptions + /** Configuration for iconMenu plugin. */ iconMenu?: IconMenuPluginOptions diff --git a/src/core/types/plugin.ts b/src/core/types/plugin.ts index a3506659c7..82c8ee4281 100644 --- a/src/core/types/plugin.ts +++ b/src/core/types/plugin.ts @@ -19,6 +19,9 @@ import type { useFullscreenStore as FullscreenStore } from '@/plugins/fullscreen import type { PluginId as GeoLocationPluginId } from '@/plugins/geoLocation' import type { resourcesEn as GeoLocationResources } from '@/plugins/geoLocation/locales' import type { useGeoLocationStore as GeoLocationStore } from '@/plugins/geoLocation/store' +import type { PluginId as GfiPluginId } from '@/plugins/gfi' +import type { resourcesEn as GfiResources } from '@/plugins/gfi/locales' +import type { useGfiStore as GfiStore } from '@/plugins/gfi/store' import type { PluginId as IconMenuPluginId } from '@/plugins/iconMenu' import type { resourcesEn as IconMenuResources } from '@/plugins/iconMenu/locales' import type { useIconMenuStore as IconMenuStore } from '@/plugins/iconMenu/store' @@ -43,11 +46,35 @@ import type { NineLayoutTag } from '../utils/NineLayoutTag' import type { Locale } from './locales' import type { Icon } from './theme' +/** + * Generic options for all plugins + */ export interface PluginOptions { + /** + * Should the component be visible at all. + * + * @defaultValue `false` + */ displayComponent?: boolean + + /** + * The region where the plugin should be rendered. + * + * 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. + * + * @defaultValue `'independent'` + */ + renderType?: 'independent' | 'iconMenu' | 'footer' +} + export interface BoundaryOptions { /** * ID of the vector layer to restrict requests to. @@ -100,6 +127,7 @@ export type BundledPluginId = | typeof FooterPluginId | typeof FullscreenPluginId | typeof GeoLocationPluginId + | typeof GfiPluginId | typeof IconMenuPluginId | typeof LayerChooserPluginId | typeof LoadingIndicatorId @@ -125,6 +153,7 @@ export type BundledPluginStores = | GetPluginStore | GetPluginStore | GetPluginStore + | GetPluginStore | GetPluginStore | GetPluginStore | GetPluginStore @@ -163,6 +192,7 @@ export type BundledPluginLocaleResources = typeof GeoLocationPluginId, typeof GeoLocationResources > + | GetPluginResources | GetPluginResources | GetPluginResources< T, diff --git a/src/core/utils/map/setupMarkers.ts b/src/core/utils/map/setupMarkers.ts index ab237ed8dd..51c3e7e380 100644 --- a/src/core/utils/map/setupMarkers.ts +++ b/src/core/utils/map/setupMarkers.ts @@ -91,7 +91,14 @@ function resolveClusterClick(map: Map, feature: Feature) { }) } -function updateSelection( +/** + * Update the selected marker in the map. + * + * @param map - Map + * @param feature - Feature to select + * @param centerOnFeature - Should the map center on the feature? + */ +export function updateSelection( map: Map, feature: Feature | null, centerOnFeature = false @@ -209,20 +216,25 @@ export function setupMarkers(map: Map) { stopWatcher = watch( () => store.hovered, - (feature) => { - if (feature !== null && feature !== toRaw(store.selected)) { - store.hovered?.setStyle(undefined) - store.hovered = null + (feature, oldFeature) => { + if (oldFeature !== null && oldFeature !== toRaw(store.selected)) { + oldFeature.setStyle(undefined) } 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 + ) ) - store.hovered.setStyle(style) } } ) 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() diff --git a/src/core/vuePlugins/i18next.ts b/src/core/vuePlugins/i18next.ts index 17fee380a5..0f66fdfd97 100644 --- a/src/core/vuePlugins/i18next.ts +++ b/src/core/vuePlugins/i18next.ts @@ -4,9 +4,12 @@ import i18next from 'i18next' import LanguageDetector from 'i18next-browser-languagedetector' import I18NextVue from 'i18next-vue' +import sharedLocales from '@/locales' + import locales from '../locales' export const CoreId = 'core' +export const SharedId = 'shared' export const I18Next: Plugin = { async install(app) { @@ -28,6 +31,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/lib/getRefStore.ts b/src/lib/getRefStore.ts new file mode 100644 index 0000000000..db4b396b1d --- /dev/null +++ b/src/lib/getRefStore.ts @@ -0,0 +1,14 @@ +import type { StoreReference } from '@/core' + +import { useCoreStore } from '@/core/stores' + +/** + * 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/locales.ts b/src/locales.ts new file mode 100644 index 0000000000..071900ed54 --- /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 shared components. + * 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 shared components. + * 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 + +/** + * POLAR shared components 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 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 aa1e5c3864..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,16 +8,7 @@ export const PluginId = 'fullscreen' /** * Plugin options for fullscreen plugin. */ -export interface FullscreenPluginOptions extends PluginOptions { - /** - * Defines if the fullscreen button is rendered independent or as part of the icon menu. - * - * This is only applicable if the layout is `'nineRegions'`. - * - * @defaultValue `'independent'` - */ - renderType?: 'independent' | 'iconMenu' - +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/store.ts b/src/plugins/geoLocation/store.ts index b8bfe69352..46dfe512e6 100644 --- a/src/plugins/geoLocation/store.ts +++ b/src/plugins/geoLocation/store.ts @@ -58,7 +58,7 @@ export const useGeoLocationStore = defineStore('plugins/geoLocation', () => { ) ) const boundary = computed(() => configuration.value.boundary) - const renderType = computed<'independent' | 'iconMenu'>( + const renderType = computed( () => configuration.value.renderType || 'independent' ) const state = computed(() => { diff --git a/src/plugins/geoLocation/types.ts b/src/plugins/geoLocation/types.ts index 3e99ddd847..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. @@ -32,16 +33,6 @@ export interface GeoLocationPluginOptions extends LayerBoundPluginOptions { */ keepCentered?: boolean - /** - * Defines if the geoLocation button is rendered independent or as part of the - * icon menu. - * - * This is only applicable if the layout is `'nineRegions'`. - * - * @defaultValue `'independent'` - */ - renderType?: 'independent' | 'iconMenu' - /** * If set to `true`, a tooltip will be shown when hovering the geoposition * marker on the map, indicating that it shows the user's position. diff --git a/src/plugins/gfi/components/GfiFeature.ce.vue b/src/plugins/gfi/components/GfiFeature.ce.vue new file mode 100644 index 0000000000..ff41df2d88 --- /dev/null +++ b/src/plugins/gfi/components/GfiFeature.ce.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/plugins/gfi/components/GfiFeatureList.ce.vue b/src/plugins/gfi/components/GfiFeatureList.ce.vue new file mode 100644 index 0000000000..60de18fecd --- /dev/null +++ b/src/plugins/gfi/components/GfiFeatureList.ce.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/plugins/gfi/components/GfiUI.ce.vue b/src/plugins/gfi/components/GfiUI.ce.vue new file mode 100644 index 0000000000..5f0589b8c6 --- /dev/null +++ b/src/plugins/gfi/components/GfiUI.ce.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/plugins/gfi/composables/useFeatureDisplayLayer.ts b/src/plugins/gfi/composables/useFeatureDisplayLayer.ts new file mode 100644 index 0000000000..90f5c07d23 --- /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