Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e14ded9
feat(iconMenu): `menus` should be mutable
oeninghe-dataport Jul 6, 2026
8f36876
test(snowbox): allow removal and live insertion of iconMenu plugins
oeninghe-dataport Jul 6, 2026
4888d29
fix(filter): reset filter state on plugin removal
oeninghe-dataport Jul 6, 2026
866b395
Merge branch 'next' into fix/668-iconMenu-plugin-removal
oeninghe-dataport Jul 29, 2026
d7e23ae
refactor(iconMenu): extract lib function watchArray
oeninghe-dataport Jul 29, 2026
b47d6cf
feat(iconMenu): allow adding and removal of plugins
oeninghe-dataport Jul 29, 2026
28a8ef6
refactor(iconMenu): replace watcher with explicit mutations
oeninghe-dataport Jul 29, 2026
bb128d2
test(snowbox): adapt new iconMenu modification
oeninghe-dataport Jul 29, 2026
1d136e7
refactor(iconMenu): replace open/focusOpen watcher with mutations
oeninghe-dataport Jul 29, 2026
fe13760
fix: remove unused watchArray lib function
oeninghe-dataport Aug 3, 2026
d2b0008
refactor(iconMenu): simplify store action calls
oeninghe-dataport Aug 3, 2026
ee8f935
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
962aa61
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
441b0be
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
42c1204
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
b6d0d30
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
7b57647
fix(iconMenu): use `toRaw`, better safe than sorry
oeninghe-dataport Aug 3, 2026
b44a099
Merge branch 'next' into fix/668-iconMenu-plugin-removal
oeninghe-dataport Aug 3, 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
1 change: 1 addition & 0 deletions examples/snowbox/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ <h1>POLAR map client</h1>
</select>
</label>
<button id="color-scheme-switcher">Switch to dark mode</button>
<button id="toggle-filter">Add/Remove filter plugin</button>
<div>
Coordinates of currently selected feature: <span id="selected-feature-coordinates"></span>
</div>
Expand Down
19 changes: 17 additions & 2 deletions examples/snowbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
colorScheme,
startCenter: [565874, 5934140],
layers: [
// TODO: Add internalization to snowbox

Check warning on line 96 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 @@ -394,6 +394,7 @@
layoutTag: 'BOTTOM_LEFT',
})
)
let filterItem
addPlugin(
map,
pluginIconMenu({
Expand All @@ -409,7 +410,7 @@
plugin: pluginLayerChooser({}),
},
],
[
(filterItem = [
{
plugin: pluginFilter({
layers: {
Expand Down Expand Up @@ -473,7 +474,7 @@
},
}),
},
],
]),
[
{
plugin: pluginGeoLocation({
Expand Down Expand Up @@ -583,3 +584,17 @@
colorScheme = colorScheme === 'light' ? 'dark' : 'light'
updateState(map, 'core', 'colorScheme', colorScheme)
})

let hasFilter = true
document
.getElementById('toggle-filter')
.addEventListener('click', ({ target }) => {
const store = getStore(map, 'iconMenu')
if (hasFilter) {
store.removePlugin('filter')
hasFilter = false
} else {
store.addPlugin(filterItem)
hasFilter = true
}
})
2 changes: 2 additions & 0 deletions src/plugins/filter/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ export const useFilterStore = defineStore('plugins/filter', () => {
{ deep: true, immediate: true }
)
)
teardownCallbacks.push(callback)
Comment thread
dopenguin marked this conversation as resolved.
})
}

function teardownPlugin() {
filterMainStore.state = {}
teardownCallbacks.forEach((callback) => {
callback()
})
Expand Down
9 changes: 1 addition & 8 deletions src/plugins/iconMenu/components/NineRegionsButton.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { storeToRefs } from 'pinia'
import { computed, inject } from 'vue'

import PolarIconButton from '@/components/PolarIconButton.ce.vue'
import { useCoreStore } from '@/core/stores'

import { useIconMenuStore } from '../store'

Expand All @@ -30,13 +29,7 @@ const active = computed(() => open.value === props.id)
const updateMaxWidth = inject('updateMaxWidth') as () => void

function toggle() {
if (open.value === props.id) {
open.value = null
useCoreStore().setMoveHandle(null)
} else {
open.value = props.id
iconMenuStore.openInMoveHandle(props.id)
}
iconMenuStore.openMenuById(open.value === props.id ? null : props.id)
updateMaxWidth()
}
</script>
6 changes: 2 additions & 4 deletions src/plugins/iconMenu/components/StandardFocusMenu.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,14 @@ const maxHeight = computed(() =>

function toggle(id: string) {
if (iconMenuStore.focusOpen === id) {
iconMenuStore.focusOpen = null
iconMenuStore.openFocusMenuById(null)
pluginComponent.value = null
coreStore.setMoveHandle(null)
} else {
iconMenuStore.focusOpen = id
iconMenuStore.openFocusMenuById(id)
pluginComponent.value = markRaw(
(props.menus.find(({ plugin }) => plugin.id === id) as Menu).plugin
.component as Component
)
iconMenuStore.openInMoveHandle(id, true)
}
}
</script>
Expand Down
8 changes: 1 addition & 7 deletions src/plugins/iconMenu/components/StandardMenuList.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,7 @@ function updateMaxWidth() {
}

function toggle(id: string) {
if (open.value === id) {
open.value = null
coreStore.setMoveHandle(null)
} else {
open.value = id
iconMenuStore.openInMoveHandle(id)
}
iconMenuStore.openMenuById(open.value === id ? null : id)
updateMaxWidth()
}
</script>
Expand Down
157 changes: 108 additions & 49 deletions src/plugins/iconMenu/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
*/
/* eslint-enable tsdoc/syntax */

import type { Component } from 'vue'
import type { Icon } from '@/core'
import type { Menu } from './types'
import type { FocusMenu, Menu } from './types'

import { toMerged } from 'es-toolkit'
import { t } from 'i18next'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { computed, markRaw, ref, toRaw } from 'vue'
import { computed, markRaw, readonly, ref, toRaw } from 'vue'

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

Expand All @@ -28,7 +26,7 @@ export const useIconMenuStore = defineStore('plugins/iconMenu', () => {
const coreStore = useCoreStore()

const menus = ref<Array<Menu[]>>([])
const focusMenus = ref<(Menu & { icon: Icon })[]>([])
const focusMenus = ref<FocusMenu[]>([])
const open = ref<string | null>(null)
const focusOpen = ref<string | null>(null)

Expand All @@ -42,6 +40,50 @@ export const useIconMenuStore = defineStore('plugins/iconMenu', () => {
() => coreStore.configuration.iconMenu?.layoutTag ?? ''
)

function isPluginInIconMenu(pluginId: string) {
const display = coreStore.configuration[pluginId]?.displayComponent
return typeof display === 'boolean' ? display : true
}
Comment on lines +43 to +46

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.

This is not related to the intention of this PR thus 🎩, but I realized that this never did anything if the configuration was directly added to the plugin. If you see a quick solution, add it here, otherwise, I'll make a note for myself.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I do not get why this check is necessary at all. I kept it here to prohibit regression, but in what scenario I'd add a plugin to the iconMenu (not standalone) but then do not display it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So, if you agree, I'll remove this check

function addPlugin(menuGroup: Menu[]) {
const filteredMenuGroup = menuGroup.filter(({ plugin: { id } }) =>
isPluginInIconMenu(id)
)
filteredMenuGroup.forEach(({ plugin }) => {
if (plugin.component) {
markRaw(toRaw(plugin.component))
}
coreStore.addPlugin(toMerged(plugin, { independent: false }))
})
menus.value.push(filteredMenuGroup)
}
function addFocusPlugin(menu: FocusMenu) {
if (!isPluginInIconMenu(menu.plugin.id)) {
return
}
if (menu.plugin.component) {
markRaw(toRaw(menu.plugin.component))
}
coreStore.addPlugin(toMerged(menu.plugin, { independent: false }))
focusMenus.value.push(menu)
}
function removePlugin(pluginId: string) {
if (open.value === pluginId) {
open.value = null
}
menus.value = menus.value
.map((menuGroup) =>
menuGroup.filter(({ plugin: { id } }) => id !== pluginId)
)
.filter((menuGroup) => menuGroup.length > 0)
if (focusOpen.value === pluginId) {
focusOpen.value = null
}
focusMenus.value = focusMenus.value.filter(
({ plugin: { id } }) => id !== pluginId
)
coreStore.removePlugin(pluginId)
}

const visibleMenus = computed(() =>
menus.value.map((menuGroup) =>
menuGroup.filter(
Expand All @@ -56,41 +98,12 @@ export const useIconMenuStore = defineStore('plugins/iconMenu', () => {
)

function setupPlugin() {
// Components are marked raw so they themselves are not made reactive
menus.value = (coreStore.configuration.iconMenu?.menus || []).map(
(menuGroup) =>
menuGroup
.filter(({ plugin: { id } }) => {
const display = coreStore.configuration[id]?.displayComponent
return typeof display === 'boolean' ? display : true
})
.map((menuItem) => ({
...menuItem,
plugin: {
...menuItem.plugin,
component: markRaw(toRaw(menuItem.plugin.component as Component)),
},
}))
)
focusMenus.value = (coreStore.configuration.iconMenu?.focusMenus || [])
.filter(({ plugin: { id } }) => {
const display = coreStore.configuration[id]?.displayComponent
return typeof display === 'boolean' ? display : true
})
.map((menuItem) => ({
...menuItem,
plugin: {
...menuItem.plugin,
component: markRaw(toRaw(menuItem.plugin.component as Component)),
},
}))

menus.value
.concat(focusMenus.value)
.flat()
.forEach(({ plugin }) => {
coreStore.addPlugin(toMerged(plugin, { independent: false }))
})
;(coreStore.configuration.iconMenu?.menus || []).forEach((menuGroup) => {
addPlugin(menuGroup)
})
;(coreStore.configuration.iconMenu?.focusMenus || []).forEach((menu) => {
addFocusPlugin(menu)
})

const initiallyOpen = coreStore.configuration.iconMenu?.initiallyOpen
if (
Expand All @@ -112,21 +125,25 @@ export const useIconMenuStore = defineStore('plugins/iconMenu', () => {
}
function teardownPlugin() {}

function openMenuById(openId: string) {
function openMenuById(openId: string | null) {
const entry = menus.value.flat().find(({ plugin: { id } }) => id === openId)

if (entry) {
if (openId && entry) {
open.value = openId
openInMoveHandle(openId)
} else {
open.value = null
coreStore.setMoveHandle(null)
}
}

function openFocusMenuById(openId: string) {
function openFocusMenuById(openId: string | null) {
const entry = focusMenus.value.find(({ plugin: { id } }) => id === openId)

if (entry) {
if (openId && entry) {
focusOpen.value = openId
openInMoveHandle(openId, true)
} else {
focusOpen.value = null
coreStore.setMoveHandle(null)
}
}

Expand Down Expand Up @@ -172,13 +189,55 @@ export const useIconMenuStore = defineStore('plugins/iconMenu', () => {
return {
visibleMenus,
visibleFocusMenus,
open,
focusOpen,

/**
* Determines which menu is currently open.
*
* To change the open menu, use {@link openMenuById}.
*
* @readonly
Comment thread
oeninghe-dataport marked this conversation as resolved.
* @alpha
*/
open: readonly(open),

/**
* Determines which focus menu is currently open.
*
* To change the open focus menu, use {@link openFocusMenuById}.
*
* @readonly
Comment thread
oeninghe-dataport marked this conversation as resolved.
* @alpha
*/
focusOpen: readonly(focusOpen),

buttonComponent,
openInMoveHandle,
openMenuById,
openFocusMenuById,

/**
* Appends a group of plugins to the icon menu.
*
* @param menu - The menu item group to add.
Comment thread
oeninghe-dataport marked this conversation as resolved.
* @alpha
*/
addPlugin,

/**
* Appends a plugin to the icon menu as a focus menu.
*
* @param menu - The focus menu item to add.
Comment thread
oeninghe-dataport marked this conversation as resolved.
* @alpha
*/
addFocusPlugin,

/**
* Removes a plugin from the icon menu.
*
* @param pluginId - The ID of the plugin to remove.
Comment thread
oeninghe-dataport marked this conversation as resolved.
* @alpha
*/
removePlugin,

/** @alpha */
layoutTag,

Expand Down
4 changes: 4 additions & 0 deletions src/plugins/iconMenu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export interface Menu {
icon?: Icon
}

export interface FocusMenu extends Menu {
icon: Icon
}

/**
* Plugin options for iconMenu plugin.
*/
Expand Down
Loading