Four data-only hooks on BasePlugin (get_settings_cards, get_asset_panels, get_map_overlays, get_asset_presentation) with a GET-only /api/pluginui consumer surface copying the dashboard-widgets semantics. Pilots: warranty declares its asset panel; measuringtools supplies its settings card, presentation, and calibration overlay - the last hardcoded settings-nav entry is now hook-sourced. Generic renderers for panels/overlays/presentation deferred per the ADR's incremental adoption plan (documented in CONTRACT-STABILITY.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
2.7 KiB
JavaScript
87 lines
2.7 KiB
JavaScript
// Settings catalog: the static core groups (settingsNav.js) merged with the
|
|
// plugin-contributed cards from the ADR-010 get_settings_cards hook, served by
|
|
// GET /api/pluginui/settings-cards. Both SettingsLayout (rail) and SettingsIndex
|
|
// (landing) read the merged catalog so plugins add settings cards with no core
|
|
// edit. Fetched once into a module-level ref and shared across callers.
|
|
import { ref } from 'vue'
|
|
import { Ruler, Wrench, Cog, Package, Settings, Puzzle, SlidersHorizontal, Palette, Bell, Network, Printer, Droplets } from 'lucide-vue-next'
|
|
import api from '../api'
|
|
import { settingsGroups } from '../views/settings/settingsNav'
|
|
|
|
// Icon string keys (as returned by the hook) mapped to Lucide components,
|
|
// same idea as the sidebar iconMap. Unknown keys fall back to a puzzle piece.
|
|
const iconMap = {
|
|
ruler: Ruler,
|
|
wrench: Wrench,
|
|
cog: Cog,
|
|
package: Package,
|
|
settings: Settings,
|
|
puzzle: Puzzle,
|
|
sliders: SlidersHorizontal,
|
|
palette: Palette,
|
|
bell: Bell,
|
|
network: Network,
|
|
printer: Printer,
|
|
droplets: Droplets,
|
|
}
|
|
|
|
function resolveIcon(key) {
|
|
return iconMap[key] || Puzzle
|
|
}
|
|
|
|
// Merge plugin cards into a fresh copy of the core groups. A card joins the
|
|
// group whose title matches its `group`; a new group is appended at the end.
|
|
function mergeCards(baseGroups, cards) {
|
|
const merged = baseGroups.map(group => ({
|
|
title: group.title,
|
|
cards: [...group.cards],
|
|
}))
|
|
const byTitle = new Map(merged.map(group => [group.title, group]))
|
|
|
|
for (const card of cards) {
|
|
const entry = {
|
|
to: card.to,
|
|
icon: resolveIcon(card.icon),
|
|
title: card.title,
|
|
description: card.description || '',
|
|
position: card.position ?? 99,
|
|
}
|
|
let group = byTitle.get(card.group)
|
|
if (!group) {
|
|
group = { title: card.group, cards: [] }
|
|
byTitle.set(card.group, group)
|
|
merged.push(group)
|
|
}
|
|
group.cards.push(entry)
|
|
}
|
|
|
|
// Order plugin cards within a group by position; core cards keep their order.
|
|
for (const group of merged) {
|
|
group.cards.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
|
}
|
|
return merged
|
|
}
|
|
|
|
// Shared across component instances: seeded with the core groups, replaced with
|
|
// the merged catalog once the hook endpoint answers.
|
|
const groups = ref(settingsGroups)
|
|
let loaded = false
|
|
|
|
async function loadCatalog() {
|
|
try {
|
|
const response = await api.get('/pluginui/settings-cards')
|
|
groups.value = mergeCards(settingsGroups, response.data.data || [])
|
|
} catch (error) {
|
|
// Degrade to the core-only catalog; the rail still works without plugins.
|
|
groups.value = settingsGroups
|
|
}
|
|
}
|
|
|
|
export function useSettingsCatalog() {
|
|
if (!loaded) {
|
|
loaded = true
|
|
loadCatalog()
|
|
}
|
|
return { groups }
|
|
}
|