floor-planner/src/lib/use-project.ts
Andras Schmelczer 8bf3e3b705
Some checks failed
Deploy to Pages / build (push) Failing after 10m45s
Simplify
2026-09-11 23:27:29 +01:00

64 lines
2.2 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { decodeState, encodeState } from './url-state';
import { reduceProject } from './project-state';
import type { ProjectAction } from './project-state';
export const PROJECT_STORAGE_KEY = 'floor-planner.project';
function readLocation() {
const link = window.location.search;
if (link && link !== '?' && link !== '?planner') return decodeState(link);
try {
const saved = decodeState(window.localStorage.getItem(PROJECT_STORAGE_KEY) ?? '');
if (!saved.error) return saved;
} catch {
/* Storage may be unavailable. */
}
return decodeState('');
}
/** React owns live state. Persistence is an output between navigations. */
export function useProject() {
const [loaded, setLoaded] = useState(() => ({ ...readLocation(), milestone: false }));
useEffect(() => {
const restore = () => setLoaded({ ...readLocation(), milestone: false });
window.addEventListener('popstate', restore);
return () => window.removeEventListener('popstate', restore);
}, []);
useEffect(() => {
if (loaded.error) return;
try {
const query = encodeState(loaded.state);
if (query !== window.location.search || window.location.hash)
window.history[loaded.milestone ? 'pushState' : 'replaceState'](
null,
'',
`${window.location.pathname}${query}`,
);
try {
window.localStorage.setItem(PROJECT_STORAGE_KEY, query);
} catch {
/* The URL remains available if storage is blocked or full. */
}
} catch (error) {
// Keep edits usable even if the browser cannot save this link.
setLoaded((current) =>
current === loaded
? {
...current,
error: error instanceof Error ? error.message : 'This plan could not be saved.',
}
: current,
);
}
}, [loaded]);
const dispatch = useCallback((action: ProjectAction) => {
setLoaded((current) => ({
state: reduceProject(current.state, action),
error: null,
milestone:
action.type === 'addRoom' || action.type === 'removeRoom' || action.type === 'visitRoom',
}));
}, []);
return { ...loaded, dispatch };
}