Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b37fde1
feat(core): add plugin store watcher
jedi-of-the-sea Mar 6, 2026
4f33de0
refactor(pins): use pluginStoreWatcher for watching coordinateSource
jedi-of-the-sea Mar 6, 2026
11b075c
refactor(pins): use pluginStoreWatcher for watching coordinateSource
jedi-of-the-sea Mar 6, 2026
7e7aeef
Merge remote-tracking branch 'origin/next' into vue3/add-plugin-store…
jedi-of-the-sea Mar 6, 2026
1fc2774
Merge remote-tracking branch 'origin/next' into vue3/add-plugin-store…
jedi-of-the-sea Mar 6, 2026
18036b0
Merge branch 'next' into vue3/add-plugin-store-watcher
jedi-of-the-sea Mar 6, 2026
f528a15
test: accept use of usePluginStoreWatcher in plugins
jedi-of-the-sea Mar 6, 2026
be3377b
fix(core): watch coreStore in usePluginWatcher
jedi-of-the-sea Mar 6, 2026
c5ed058
test: switch order of plugins for testing plugin store watcher
jedi-of-the-sea Mar 9, 2026
51a48af
Merge branch 'next' into vue3/add-plugin-store-watcher
jedi-of-the-sea Mar 10, 2026
60ab99b
Merge branch 'next' into vue3/add-plugin-store-watcher
jedi-of-the-sea Mar 10, 2026
1bf97fe
Merge branch 'next' into vue3/add-plugin-store-watcher
jedi-of-the-sea Mar 10, 2026
43dc809
Update src/core/composables/usePluginStoreWatcher.ts
jedi-of-the-sea Mar 11, 2026
7c0f3cc
test: restore original test
jedi-of-the-sea Mar 11, 2026
a977903
refactor: extract pluginStoreWatcher from core
jedi-of-the-sea Mar 11, 2026
074ec91
fix: delete unnnecessary comments
jedi-of-the-sea Mar 11, 2026
bea374c
refactor: access plugin store through coreStore
jedi-of-the-sea Mar 11, 2026
9456b59
refactor: use getPluginStore instead of manually looking through plug…
jedi-of-the-sea Mar 11, 2026
e8a36a9
refactor(core): add getter usedPlugins
jedi-of-the-sea Mar 11, 2026
52e24fd
refactor: change pluginIsInstalled to require both plugin ID and load…
jedi-of-the-sea Mar 11, 2026
2aac0a4
refactor: delete unreachable code, leave guard check to prevent type …
jedi-of-the-sea Mar 11, 2026
4a7d352
feat: add warnings when plugin store cannot be found
jedi-of-the-sea Mar 11, 2026
ed83242
fix(pins): correct callback parameter types for usePluginStoreWatcher
jedi-of-the-sea Mar 12, 2026
f89d83e
fix(reverseGeocoder): correct callback parameter types for usePluginS…
jedi-of-the-sea Mar 12, 2026
9f6d623
test(reverseGeocoder): adjust tests to watcher changes
jedi-of-the-sea Mar 12, 2026
fecfba8
refactor(attributions): use pluginStoreWatcher for watching listenToC…
jedi-of-the-sea Mar 12, 2026
d5941bb
test: change order of plugins for more testing
jedi-of-the-sea Mar 12, 2026
42571a3
Merge branch 'next' into vue3/add-plugin-store-watcher
jedi-of-the-sea Mar 12, 2026
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
153 changes: 78 additions & 75 deletions examples/snowbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
colorScheme,
startCenter: [565874, 5934140],
layers: [
// TODO: Add internalization to snowbox

Check warning on line 95 in examples/snowbox/index.js

View workflow job for this annotation

GitHub Actions / Linting

Unexpected 'todo' comment: 'TODO: Add internalization to snowbox'
{
id: basemapId,
visibility: true,
Expand Down Expand Up @@ -232,6 +232,56 @@
document.getElementById('secondMapContainer').innerText = ''
})

addPlugin(
map,
pluginFooter({
leftEntries: [{ id: 'mockPointer', component: MockPointerPosition }],
rightEntries: [
pluginScale({}),
pluginAttributions({
icons: {
close: 'kern-icon--keyboard-arrow-up',
},
listenToChanges: [
{
key: 'activeBackgroundId',
plugin: 'layerChooser',
},
{
key: 'activeMaskIds',
plugin: 'layerChooser',
},
{
key: 'zoom',
},
],
layerAttributions: [
{
id: basemapId,
title: 'snowbox.attributions.basemap',
},
{
id: basemapGreyId,
title: 'snowbox.attributions.basemapGrey',
},
{
id: reports,
title: 'snowbox.attributions.reports',
},
{
id: ausgleichsflaechen,
title: 'snowbox.attributions.ausgleichsflaechen',
},
{
id: denkmal,
title: `Karte Kulturdenkmale (Denkmalliste): © <a href="https://www.schleswig-holstein.de/DE/landesregierung/ministerien-behoerden/LD/ld_node.html" target="_blank">Landesamt für Denkmalpflege</a> <MONTH> <YEAR>`,
},
],
}),
],
})
)

addPlugin(
map,
pluginToast({
Expand Down Expand Up @@ -265,27 +315,22 @@
loaderStyle: 'BasicLoader',
})
)

addPlugin(
map,
pluginAddressSearch({
searchMethods: [
pluginReverseGeocoder({
url: 'https://geodienste.hamburg.de/HH_WPS',
coordinateSources: [
{
queryParameters: {
searchStreets: true,
searchHouseNumbers: true,
},
type: 'mpapi',
url: 'https://geodienste.hamburg.de/HH_WFS_GAGES?service=WFS&request=GetFeature&version=2.0.0',
plugin: 'pins',
key: 'coordinate',
},
],
minLength: 3,
waitMs: 300,
focusAfterSearch: true,
groupProperties: {
defaultGroup: {
limitResults: 5,
},
addressTarget: {
plugin: 'addressSearch',
key: 'selectResult',
},
zoomTo: 7,
})
)
addPlugin(
Expand All @@ -302,23 +347,6 @@
toZoomLevel: 7,
})
)
addPlugin(
map,
pluginReverseGeocoder({
url: 'https://geodienste.hamburg.de/HH_WPS',
coordinateSources: [
{
plugin: 'pins',
key: 'coordinate',
},
],
addressTarget: {
plugin: 'addressSearch',
key: 'selectResult',
},
zoomTo: 7,
})
)
addPlugin(
map,
pluginIconMenu({
Expand All @@ -336,7 +364,7 @@
},
],
menus: [
// TODO: Delete the mock plugins including the components once the correct plugins have been implemented

Check warning on line 367 in examples/snowbox/index.js

View workflow job for this annotation

GitHub Actions / Linting

Unexpected 'todo' comment: 'TODO: Delete the mock plugins including...'
[
{
plugin: pluginFullscreen({}),
Expand Down Expand Up @@ -373,53 +401,28 @@
],
})
)

addPlugin(
map,
pluginFooter({
leftEntries: [{ id: 'mockPointer', component: MockPointerPosition }],
rightEntries: [
pluginScale({}),
pluginAttributions({
icons: {
close: 'kern-icon--keyboard-arrow-up',
pluginAddressSearch({
searchMethods: [
{
queryParameters: {
searchStreets: true,
searchHouseNumbers: true,
},
listenToChanges: [
{
key: 'activeBackgroundId',
plugin: 'layerChooser',
},
{
key: 'activeMaskIds',
plugin: 'layerChooser',
},
{
key: 'zoom',
},
],
layerAttributions: [
{
id: basemapId,
title: 'snowbox.attributions.basemap',
},
{
id: basemapGreyId,
title: 'snowbox.attributions.basemapGrey',
},
{
id: reports,
title: 'snowbox.attributions.reports',
},
{
id: ausgleichsflaechen,
title: 'snowbox.attributions.ausgleichsflaechen',
},
{
id: denkmal,
title: `Karte Kulturdenkmale (Denkmalliste): © <a href="https://www.schleswig-holstein.de/DE/landesregierung/ministerien-behoerden/LD/ld_node.html" target="_blank">Landesamt für Denkmalpflege</a> <MONTH> <YEAR>`,
},
],
}),
type: 'mpapi',
url: 'https://geodienste.hamburg.de/HH_WFS_GAGES?service=WFS&request=GetFeature&version=2.0.0',
},
],
minLength: 3,
waitMs: 300,
focusAfterSearch: true,
groupProperties: {
defaultGroup: {
limitResults: 5,
},
},
})
)

Expand Down
164 changes: 164 additions & 0 deletions src/composables/usePluginStoreWatcher.ts
Copy link
Member

Choose a reason for hiding this comment

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

There should be some console warnings if something is not working when setting up the watchers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/* eslint-disable tsdoc/syntax */
/**
* @module \@polar/polar/core/composables/usePluginStoreWatcher
*/
/* eslint-enable tsdoc/syntax */

import { computed, watch, type ComputedRef, type WatchHandle } from 'vue'

import type { StoreReference } from '@/core/types'

import { useCoreStore } from '@/core/stores'

/**
* Configuration for a single store reference watcher.
* @internal
*/
interface WatcherConfig {
callback: (value: unknown) => void | Promise<void>
handle: WatchHandle | null
source: StoreReference
}

/**
* Generic composable for watching multiple plugin store references.
*
* It watches the list of installed plugins to detect when target plugins
* are added or removed, and manages the corresponding watchers accordingly.
*
* @param sources - Array of plugin store references to watch, or a computed reference to them, or a function returning them
* @param callback - Function called when any watched plugin store value changes
*
* @example
* ```typescript
* const { setupPlugin, teardownPlugin } = usePluginStoreWatcher(
* () => configuration.value.coordinateSources || [],
* (coordinate) => {
* if (coordinate) {
* await reverseGeocode(coordinate)
* }
* }
* )
* ```
*
* @internal
*/
export function usePluginStoreWatcher(
sources:
| StoreReference[]
| ComputedRef<StoreReference[]>
| (() => StoreReference[]),
callback: (value: unknown) => void | Promise<void>
) {
const coreStore = useCoreStore()
const sourcesArray = computed(() => {
if (typeof sources === 'function') {
return sources()
}
if ('value' in sources) {
return sources.value
}
return sources
})

const watchers: WatcherConfig[] = []
let pluginListWatcher: WatchHandle | null = null
let sourcesWatcher: WatchHandle | null = null

function setupWatcherForSource(watcherConfig: WatcherConfig) {
if (watcherConfig.handle !== null) {
return
}

if (!watcherConfig.source.plugin) {
return
}

const store = coreStore.getPluginStore(watcherConfig.source.plugin)

if (!store) {
console.warn(
`usePluginStoreWatcher: "${watcherConfig.source.plugin}" not found. Cannot watch "${watcherConfig.source.key}".`
)
return
}

watcherConfig.handle = watch(
() => store[watcherConfig.source.key],
watcherConfig.callback
)
}

function removeWatcherForSource(watcherConfig: WatcherConfig) {
if (watcherConfig.handle) {
watcherConfig.handle()
watcherConfig.handle = null
}
}

function updateWatchersBasedOnInstalledPlugins() {
const currentSources = sourcesArray.value

watchers.forEach((watcherConfig, index) => {
if (!currentSources.some((s) => s === watcherConfig.source)) {
removeWatcherForSource(watcherConfig)
watchers.splice(index, 1)
}
})

currentSources.forEach((source) => {
let watcherConfig = watchers.find((w) => w.source === source)

if (!watcherConfig) {
watcherConfig = { source, callback, handle: null }
watchers.push(watcherConfig)
}

const pluginIsInstalled =
source.plugin && coreStore.getPluginStore(source.plugin)

if (pluginIsInstalled && !watcherConfig.handle) {
setupWatcherForSource(watcherConfig)
} else if (!pluginIsInstalled && watcherConfig.handle) {
removeWatcherForSource(watcherConfig)
}
})
}

function setupPlugin() {
updateWatchersBasedOnInstalledPlugins()

sourcesWatcher = watch(sourcesArray, () => {
updateWatchersBasedOnInstalledPlugins()
})

pluginListWatcher = watch(
() => coreStore.usedPlugins,
() => {
updateWatchersBasedOnInstalledPlugins()
}
)
}

function teardownPlugin() {
watchers.forEach((watcher) => {
removeWatcherForSource(watcher)
})
watchers.length = 0

if (sourcesWatcher) {
sourcesWatcher()
sourcesWatcher = null
}

if (pluginListWatcher) {
pluginListWatcher()
pluginListWatcher = null
}
}

return {
setupPlugin,
teardownPlugin,
}
}
7 changes: 7 additions & 0 deletions src/core/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ export const useCoreStore = defineStore('core', () => {
*/
getPluginStore: pluginStore.getPluginStore,

/**
* Returns a list of IDs of all currently installed plugins.
*
* @readonly
*/
usedPlugins: computed(() => pluginStore.plugins.map((p) => p.id)),

/**
* Allows reading or setting the OIDC token used for service accesses.
*/
Expand Down
Loading
Loading