diff --git a/webclient-vue/src/stores/__tests__/areas.spec.js b/webclient-vue/src/stores/__tests__/areas.spec.js index 1a0d644..984ed2a 100644 --- a/webclient-vue/src/stores/__tests__/areas.spec.js +++ b/webclient-vue/src/stores/__tests__/areas.spec.js @@ -163,6 +163,38 @@ }); }); + describe("expandedNodeIds", () => { + it("restores expanded nodes from localStorage on init", () => { + localStorage.setItem("sh:areas:expandedNodes", JSON.stringify([1, 3])); + setActivePinia(createPinia()); + + const store = useAreasStore(); + expect(store.isNodeExpanded(1)).toBe(true); + expect(store.isNodeExpanded(3)).toBe(true); + expect(store.isNodeExpanded(2)).toBe(false); + }); + + it("persists toggled nodes to localStorage", async () => { + const store = useAreasStore(); + store.toggleNode(5); + await new Promise((r) => setTimeout(r, 0)); + + const raw = localStorage.getItem("sh:areas:expandedNodes"); + expect(JSON.parse(raw)).toContain(5); + }); + + it("removes collapsed nodes from localStorage", async () => { + const store = useAreasStore(); + store.toggleNode(5); + await new Promise((r) => setTimeout(r, 0)); + store.toggleNode(5); + await new Promise((r) => setTimeout(r, 0)); + + const raw = localStorage.getItem("sh:areas:expandedNodes"); + expect(JSON.parse(raw)).not.toContain(5); + }); + }); + describe("assignToArea", () => { it("sets parent_id on success", async () => { areasApi.placeInArea.mockResolvedValue({ ok: true }); diff --git a/webclient-vue/src/stores/areas.js b/webclient-vue/src/stores/areas.js index c32f59b..3f99c79 100644 --- a/webclient-vue/src/stores/areas.js +++ b/webclient-vue/src/stores/areas.js @@ -1,8 +1,33 @@ -import { ref, computed } from "vue"; +import { ref, computed, watch } from "vue"; import { defineStore } from "pinia"; import { areasApi } from "../api/modules/areas"; import { useAsyncRequest } from "../composables/useAsyncRequest"; +const EXPANDED_NODES_KEY = "sh:areas:expandedNodes"; + +function loadExpandedNodes() { + try { + const raw = localStorage.getItem(EXPANDED_NODES_KEY); + if (raw) { + const ids = JSON.parse(raw); + if (Array.isArray(ids)) { + return new Set(ids); + } + } + } catch { + // ignore corrupt storage + } + return new Set(); +} + +function saveExpandedNodes(set) { + try { + localStorage.setItem(EXPANDED_NODES_KEY, JSON.stringify([...set])); + } catch { + // ignore storage errors + } +} + function buildAreaTree(areas) { const map = {}; const roots = []; @@ -45,7 +70,15 @@ const areaTree = computed(() => buildAreaTree(areas.value)); - const expandedNodeIds = ref(new Set()); + const expandedNodeIds = ref(loadExpandedNodes()); + + watch( + expandedNodeIds, + (next) => { + saveExpandedNodes(next); + }, + { deep: true } + ); function toggleNode(id) { const next = new Set(expandedNodeIds.value);