feat: support multi-canvas (designs) in frontend-only standalone mode
Standalone mode (VITE_STANDALONE=true) skipped the designs system entirely: the design list stayed empty (switcher hidden) and all canvases collapsed onto a single localStorage key. Add a localStorage-backed design layer mirroring the backend designs API. - standaloneStorage util: list/create/update/delete designs + per-design canvas storage (homelable_designs + homelable_canvas:<id>). ensureSeed migrates a legacy single-canvas install into a default design so existing data survives. - App.tsx: load/seed designs, save + switch canvases per design id in standalone. - Sidebar.tsx: create/update/delete dispatch to standaloneStorage when standalone. - Tests for the new storage util. ha-relevant: no
This commit is contained in:
@@ -6,6 +6,7 @@ import { useCanvasStore } from '@/stores/canvasStore'
|
||||
import { useDesignStore } from '@/stores/designStore'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
import { designsApi } from '@/api/client'
|
||||
import * as standaloneStorage from '@/utils/standaloneStorage'
|
||||
import { resolveDesignIcon, DEFAULT_DESIGN_ICON } from '@/utils/designIcons'
|
||||
import { DesignModal, type DesignFormData } from '@/components/modals/DesignModal'
|
||||
import type { Design } from '@/types'
|
||||
@@ -43,11 +44,15 @@ export function Sidebar({ onAddNode, onAddGroupRect, onAddText, onScan, onZigbee
|
||||
if (!designModal) return
|
||||
try {
|
||||
if (designModal.mode === 'create') {
|
||||
const res = await designsApi.create({ name: data.name, icon: data.icon })
|
||||
addDesign(res.data)
|
||||
const created = STANDALONE
|
||||
? standaloneStorage.createDesign(data.name, data.icon)
|
||||
: (await designsApi.create({ name: data.name, icon: data.icon })).data
|
||||
addDesign(created)
|
||||
} else if (designModal.design) {
|
||||
const res = await designsApi.update(designModal.design.id, { name: data.name, icon: data.icon })
|
||||
updateDesign(res.data.id, { name: res.data.name, icon: res.data.icon })
|
||||
const updated = STANDALONE
|
||||
? standaloneStorage.updateDesign(designModal.design.id, { name: data.name, icon: data.icon })
|
||||
: (await designsApi.update(designModal.design.id, { name: data.name, icon: data.icon })).data
|
||||
if (updated) updateDesign(updated.id, { name: updated.name, icon: updated.icon })
|
||||
}
|
||||
setDesignModal(null)
|
||||
} catch {
|
||||
@@ -59,7 +64,11 @@ export function Sidebar({ onAddNode, onAddGroupRect, onAddText, onScan, onZigbee
|
||||
if (designs.length <= 1) { toast.error('Cannot delete the only canvas'); return }
|
||||
if (!window.confirm(`Delete canvas "${d.name}"? Its nodes and links will be removed.`)) return
|
||||
try {
|
||||
await designsApi.delete(d.id)
|
||||
if (STANDALONE) {
|
||||
standaloneStorage.deleteDesign(d.id)
|
||||
} else {
|
||||
await designsApi.delete(d.id)
|
||||
}
|
||||
removeDesign(d.id)
|
||||
toast.success('Canvas deleted')
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user