Add an exported block registry (keywords, search, insertBlock) consumed by BlocksPanel, a NoticeProvider/NoticeHost toast stack, a guarded localStorage prefs helper, a ProjectExtras store persisted as project.json 'extras', and a CanvasOverlays mount point. Refs #38 Refs #41 Refs #49 Refs #48 Refs #57 Refs #58 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
import React, { createContext, useCallback, useContext, useMemo, useRef, ReactNode } from 'react';
|
|
import { useSaveGuard } from './SaveGuardContext';
|
|
|
|
/**
|
|
* Feature data that is not Craft state (style presets, custom sections, ...),
|
|
* persisted as the top-level `extras` key of project.json. Held in a ref, not
|
|
* React state: nothing renders from it directly, and a save queued behind
|
|
* another must read the latest value at send time.
|
|
*/
|
|
interface ProjectExtrasValue {
|
|
get<T>(key: string, fallback: T): T;
|
|
set(key: string, value: unknown): void;
|
|
update<T>(key: string, fn: (prev: T) => T, fallback: T): void;
|
|
all(): Record<string, unknown>;
|
|
/** Load hydration. Does NOT mark the editor dirty. */
|
|
replaceAll(extras: Record<string, unknown>): void;
|
|
}
|
|
|
|
const noop: ProjectExtrasValue = {
|
|
get: (_k, fallback) => fallback,
|
|
set: () => {},
|
|
update: () => {},
|
|
all: () => ({}),
|
|
replaceAll: () => {},
|
|
};
|
|
|
|
const ProjectExtrasContext = createContext<ProjectExtrasValue>(noop);
|
|
|
|
export const useProjectExtras = () => useContext(ProjectExtrasContext);
|
|
|
|
export const ProjectExtrasProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
const { markDirty } = useSaveGuard();
|
|
const store = useRef<Record<string, unknown>>({});
|
|
const markRef = useRef(markDirty);
|
|
markRef.current = markDirty;
|
|
|
|
const value = useMemo<ProjectExtrasValue>(() => ({
|
|
get: <T,>(key: string, fallback: T): T => (key in store.current ? (store.current[key] as T) : fallback),
|
|
set: (key, v) => {
|
|
store.current = { ...store.current, [key]: v };
|
|
markRef.current();
|
|
},
|
|
update: <T,>(key: string, fn: (prev: T) => T, fallback: T) => {
|
|
const prev = (key in store.current ? store.current[key] : fallback) as T;
|
|
store.current = { ...store.current, [key]: fn(prev) };
|
|
markRef.current();
|
|
},
|
|
all: () => store.current,
|
|
replaceAll: (extras) => { store.current = { ...extras }; },
|
|
}), []);
|
|
|
|
return <ProjectExtrasContext.Provider value={value}>{children}</ProjectExtrasContext.Provider>;
|
|
};
|