import React, { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'; import { useEditor } from '@craftjs/core'; import { Modal } from '../../ui/Modal'; import { useSiteDesign } from '../../state/SiteDesignContext'; import { useProjectExtras } from '../../state/ProjectExtrasContext'; import { useNotices } from '../../state/NoticeContext'; import { useRegisterCommands } from '../../editor/commands'; import { CUSTOM_SECTIONS_KEY, CustomSection, SECTION_FAMILIES, SectionDef, SectionFamily, insertSection, isTopLevelSection, nodeTreeToSerializedTree, searchSections, useSections, } from '../../sections/catalog'; import { buildSectionPreviewDoc } from '../../sections/thumbnails'; import { exportToHtml } from '../../utils/html-export'; import { masterZoneState, type GlobalMaster } from '../../utils/global-sections'; import { useGlobalSections } from '../../state/GlobalSectionsContext'; import { usePages } from '../../state/PageContext'; // ---- open state: module-level so the command, the empty-state button and the // Blocks panel button can open the modal without a provider ---- interface LibraryState { open: boolean; saving: boolean } let state: LibraryState = { open: false, saving: false }; const listeners = new Set<() => void>(); const setState = (s: LibraryState) => { state = s; listeners.forEach((l) => l()); }; const subscribe = (cb: () => void) => { listeners.add(cb); return () => { listeners.delete(cb); }; }; export function openSectionLibrary(opts: { saving?: boolean } = {}): void { setState({ open: true, saving: !!opts.saving }); } export function closeSectionLibrary(): void { setState({ open: false, saving: false }); } export function useSectionLibraryState(): LibraryState { return useSyncExternalStore(subscribe, () => state, () => state); } /** Renders its iframe only once scrolled into view (60 live iframes at once is heavy). */ const LazyPreview: React.FC<{ def?: SectionDef; master?: GlobalMaster; title: string }> = ({ def, master, title }) => { const { design } = useSiteDesign(); const ref = useRef(null); const [seen, setSeen] = useState(typeof IntersectionObserver === 'undefined'); useEffect(() => { if (seen || !ref.current) return; const io = new IntersectionObserver((entries) => { if (entries.some((e) => e.isIntersecting)) { setSeen(true); io.disconnect(); } }); io.observe(ref.current); return () => io.disconnect(); }, [seen]); const doc = useMemo(() => { if (!seen) return ''; try { if (master) return exportToHtml(masterZoneState(master), { design, includeFonts: true }).html; return def ? buildSectionPreviewDoc(def, design) : ''; } catch { return ''; } }, [seen, def, master, design]); return (
{doc && (