diff --git a/apps/catalog/src/CatalogApp.tsx b/apps/catalog/src/CatalogApp.tsx index fe8650f..c3ce509 100644 --- a/apps/catalog/src/CatalogApp.tsx +++ b/apps/catalog/src/CatalogApp.tsx @@ -47,6 +47,28 @@ const accents: Array<{ label: string; value: RgbTuple; hex: string }> = [ { label: "Magenta", value: [215, 70, 255], hex: "#d746ff" }, ]; +type MaterialDraft = { + panelHex: string; + panelOpacity: number; + fieldHex: string; + fieldOpacity: number; +}; + +const materialDefaults: Record = { + dark: { + panelHex: "#16161a", + panelOpacity: 68, + fieldHex: "#101016", + fieldOpacity: 78, + }, + light: { + panelHex: "#ffffff", + panelOpacity: 62, + fieldHex: "#ffffff", + fieldOpacity: 72, + }, +}; + type ShareRole = "viewer" | "editor" | "admin"; const shareRoleOptions = [ @@ -208,6 +230,11 @@ function Preview({ title, note, children, className }: { export function CatalogApp() { const [theme, setTheme] = useState("dark"); const [accentHex, setAccentHex] = useState("#ff2f92"); + const [materialByTheme, setMaterialByTheme] = useState>(() => ({ + dark: { ...materialDefaults.dark }, + light: { ...materialDefaults.light }, + })); + const [savedEnvironmentControl, setSavedEnvironmentControl] = useState(null); const workspace = useApplicationWorkspace(); const { navigationOpen: guidelineOpen, activeView: activeSection, contentExpanded: panelExpanded } = workspace; const [selectedStatus, setSelectedStatus] = useState<(typeof selectOptions)[number]["value"]>("active"); @@ -219,7 +246,9 @@ export function CatalogApp() { const [lightColor, setLightColor] = useState("#ff2f92"); const [connectionColor, setConnectionColor] = useState("#404040"); const [fillColor, setFillColor] = useState("#ff6caf"); + const [fillOpacity, setFillOpacity] = useState(100); const [strokeColor, setStrokeColor] = useState("#2b2b36"); + const [strokeOpacity, setStrokeOpacity] = useState(100); const [windowOpen, setWindowOpen] = useState(false); const [sidePanelOpen, setSidePanelOpen] = useState(false); const [createModalOpen, setCreateModalOpen] = useState(false); @@ -253,10 +282,22 @@ export function CatalogApp() { const stageMediaSrc = mediaSource === "url" && externalMediaSrc ? externalMediaSrc : fileMediaSrc; const accent = useMemo(() => rgbFromHex(accentHex) ?? accents[0].value, [accentHex]); + const material = materialByTheme[theme]; + const panelMaterial = useMemo(() => rgbFromHex(material.panelHex) ?? rgbFromHex(materialDefaults[theme].panelHex)!, [material.panelHex, theme]); + const fieldMaterial = useMemo(() => rgbFromHex(material.fieldHex) ?? rgbFromHex(materialDefaults[theme].fieldHex)!, [material.fieldHex, theme]); useEffect(() => { - applyNodedcTheme(document.documentElement, { theme, accent }); - }, [accent, theme]); + applyNodedcTheme(document.documentElement, { + theme, + accent, + material: { + panel: panelMaterial, + panelOpacity: material.panelOpacity / 100, + field: fieldMaterial, + fieldOpacity: material.fieldOpacity / 100, + }, + }); + }, [accent, fieldMaterial, material.fieldOpacity, material.panelOpacity, panelMaterial, theme]); useEffect(() => () => { if (mediaObjectUrlRef.current) URL.revokeObjectURL(mediaObjectUrlRef.current); @@ -296,16 +337,89 @@ export function CatalogApp() { const closeGuideline = workspace.closeNavigation; + const updateMaterial = (changes: Partial) => { + setMaterialByTheme((current) => ({ + ...current, + [theme]: { ...current[theme], ...changes }, + })); + }; + + const saveEnvironmentLayout = (control: string) => { + const draft = { + theme, + accentHex, + material, + environment: { + lightColor, + brightness, + glowDistance, + connectionType, + connectionColor, + usePortColors, + fillColor, + fillOpacity, + strokeColor, + strokeOpacity, + }, + }; + try { + window.localStorage.setItem("nodedc-design-guideline-draft", JSON.stringify(draft)); + } catch { + // The preview remains useful in restricted browser contexts even without local persistence. + } + setSavedEnvironmentControl(control); + }; + + const savedAction = (control: string) => ( + saveEnvironmentLayout(control)}> + + + ); + const environmentSections = [ + { + id: "application-material", + label: "Материал приложения", + description: "live draft", + tone: "accent" as const, + content: ( + <> + + + + + updateMaterial({ panelHex })} /> + + + `${value}%`} onChange={(panelOpacity) => updateMaterial({ panelOpacity })} /> + + + updateMaterial({ fieldHex })} /> + + + `${value}%`} onChange={(fieldOpacity) => updateMaterial({ fieldOpacity })} /> + +

+ {savedEnvironmentControl ? `«${savedEnvironmentControl}» сохранён в локальный layout. Сервер и MCP не используются.` : "Дискета сохраняет текущий параметр в локальный layout браузера."} +

+ + ), + }, { id: "illumination", label: "Подсветка", tone: "accent" as const, content: ( <> - - `${value}%`} onChange={setBrightness} /> - `${value}%`} onChange={setGlowDistance} /> + +
+ `${value}%`} onChange={setBrightness} /> + {savedAction("Яркость")} +
+
+ `${value}%`} onChange={setGlowDistance} /> + {savedAction("Дистанция свечения")} +
), }, @@ -315,7 +429,7 @@ export function CatalogApp() { tone: "accent" as const, content: ( <> - + onChange(Number(event.target.value))} {...props} /> - {label} + {showLabel ? {label} : null} {formatValue(value)} diff --git a/packages/ui-react/src/SharingModals.tsx b/packages/ui-react/src/SharingModals.tsx index 1f333fb..e237085 100644 --- a/packages/ui-react/src/SharingModals.tsx +++ b/packages/ui-react/src/SharingModals.tsx @@ -97,7 +97,7 @@ export function ShareAccessModal({ {canManage ? ( - ) : null} @@ -233,7 +233,7 @@ export function ShareLinkModal({ <> -