import { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from "react"; import { applyNodedcTheme, type NodedcTheme, type RgbTuple } from "@nodedc/ui-core"; import { AdminNavigationPanel, AppHeader, ApplicationPanel, ApplicationShell, Button, Checker, ColorField, ConfirmationModal, ControlRow, Dropdown, FieldFrame, GlassSurface, HeaderAvatar, HeaderNavigation, HeaderProfile, HeaderProfileButton, HeaderWorkspace, Icon, IconButton, Inspector, MediaSourceField, RangeControl, Select, SettingsCard, StatusBadge, Switch, TextAreaField, TextField, useApplicationWorkspace, Window, WindowFooterActions, type IconName, } from "@nodedc/ui-react"; type CatalogSection = "foundation" | "controls" | "media" | "windows" | "modals" | "inspector" | "icons"; const engineAccentStyle = { "--nodedc-accent-rgb": "148 123 219", "--nodedc-on-accent-rgb": "247 248 244", } as CSSProperties; const accents: Array<{ label: string; value: RgbTuple; hex: string }> = [ { label: "Hub", value: [195, 255, 102], hex: "#c3ff66" }, { label: "Engine", value: [148, 123, 219], hex: "#947bdb" }, { label: "BIM", value: [78, 164, 255], hex: "#4ea4ff" }, { label: "SEO", value: [32, 32, 34], hex: "#202022" }, ]; const selectOptions = [ { value: "active", label: "Активен", description: "Сервис доступен пользователям" }, { value: "maintenance", label: "Техработы", description: "Временно недоступен" }, { value: "disabled", label: "Отключён", description: "Запуск запрещён" }, ] as const; const sectionDefinitions: Record = { foundation: { eyebrow: "01 / FOUNDATION", title: "Основа", description: "Тема, акцент, стекло и неизменяемая геометрия приложения.", icon: "panel", }, controls: { eyebrow: "02 / CONTROLS", title: "Контролы", description: "Кнопки, поля, selection, Engine-настройки и понятные статусы операций.", icon: "sliders", }, media: { eyebrow: "03 / MEDIA & SETTINGS", title: "Медиа и настройки", description: "Общий CMS/Launcher-контракт файла, URL, превью и нейтральных карточек настроек.", icon: "image", }, windows: { eyebrow: "04 / WINDOWS", title: "Окна", description: "Обычные окна и modeless-панели с единым portal- и focus-контрактом.", icon: "apps", }, modals: { eyebrow: "05 / MODALS", title: "Модалки", description: "Создание, подтверждение и разрушительные действия в адаптивном формате.", icon: "clipboard", }, inspector: { eyebrow: "06 / INSPECTOR", title: "Инспектор", description: "Точный новый Engine Environment Settings без legacy-инспекторов.", icon: "settings", }, icons: { eyebrow: "07 / ICONS", title: "Иконки", description: "Канонический общий набор по Launcher, SEO, BIM Viewer и новым участкам Engine.", icon: "grid", }, }; const iconGroups: Array<{ title: string; note: string; icons: IconName[] }> = [ { title: "Окно и слой", note: "Launcher / Hub", icons: ["close", "plus", "expand", "minimize", "refresh", "panel", "apps"], }, { title: "Навигация", note: "Launcher / Engine / BIM", icons: ["chevron-left", "chevron-right", "chevron-down", "grid", "list", "sliders", "search"], }, { title: "Редактирование", note: "Launcher / SEO", icons: ["save", "edit", "trash", "copy", "upload", "download", "external"], }, { title: "Состояние и доступ", note: "Вся платформа", icons: ["check", "alert", "activity", "lock", "key", "shield", "circle"], }, { title: "Сущности", note: "Общий словарь", icons: ["profile", "users", "building", "globe", "database", "network", "inbox", "mail"], }, { title: "Контент", note: "SEO / BIM / Engine", icons: ["image", "video", "file", "folder", "clipboard", "settings"], }, ]; const iconLabels: Record = { activity: "Активность", alert: "Предупреждение", apps: "Приложения", building: "Компания", check: "Готово", "chevron-down": "Раскрыть", "chevron-left": "Назад", "chevron-right": "Вперёд", circle: "Маркер", clipboard: "Список", close: "Закрыть", copy: "Копировать", database: "База данных", download: "Скачать", edit: "Редактировать", expand: "Развернуть", external: "Открыть снаружи", file: "Файл", folder: "Папка", globe: "Публичный контур", grid: "Обзор", image: "Изображение", inbox: "Уведомления", key: "Ключ", list: "Список задач", lock: "Закрыто", mail: "Приглашение", minimize: "Свернуть", network: "Связи", panel: "Панель", plus: "Добавить", profile: "Профиль", refresh: "Обновить", save: "Сохранить", search: "Поиск", settings: "Настройки", shield: "Доступ", sliders: "Параметры", trash: "Удалить", upload: "Загрузить", users: "Участники", video: "Видео", }; function rgbFromHex(hex: string): RgbTuple | null { const normalized = hex.trim().replace(/^#/, ""); if (!/^[0-9a-f]{6}$/i.test(normalized)) return null; return [ Number.parseInt(normalized.slice(0, 2), 16), Number.parseInt(normalized.slice(2, 4), 16), Number.parseInt(normalized.slice(4, 6), 16), ]; } function Preview({ title, note, children, className }: { title: string; note?: string; children: ReactNode; className?: string; }) { return (
{title} {note ? {note} : null}
{children}
); } export function CatalogApp() { const [theme, setTheme] = useState("dark"); const [accentHex, setAccentHex] = useState("#c3ff66"); const workspace = useApplicationWorkspace(); const { navigationOpen: guidelineOpen, activeView: activeSection, contentExpanded: panelExpanded } = workspace; const [selectedStatus, setSelectedStatus] = useState<(typeof selectOptions)[number]["value"]>("active"); const [checker, setChecker] = useState(true); const [brightness, setBrightness] = useState(49); const [glowDistance, setGlowDistance] = useState(105); const [usePortColors, setUsePortColors] = useState(false); const [connectionType, setConnectionType] = useState("spline"); const [lightColor, setLightColor] = useState("#7a3cff"); const [connectionColor, setConnectionColor] = useState("#404040"); const [fillColor, setFillColor] = useState("#8f83ff"); const [strokeColor, setStrokeColor] = useState("#2b2b36"); const [windowOpen, setWindowOpen] = useState(false); const [inspectorOpen, setInspectorOpen] = useState(false); const [createModalOpen, setCreateModalOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false); const [projectName, setProjectName] = useState("NODE.DC Platform"); const [notes, setNotes] = useState("Общий контракт компонентов без доменной логики приложения."); const [mediaSource, setMediaSource] = useState<"file" | "url">("file"); const [mediaUrl, setMediaUrl] = useState(""); const [mediaFileName, setMediaFileName] = useState("hub_vitrina-mqv7yd7k.mov"); const [mediaVisible, setMediaVisible] = useState(true); const accent = useMemo(() => rgbFromHex(accentHex) ?? accents[0].value, [accentHex]); useEffect(() => { applyNodedcTheme(document.documentElement, { theme, accent }); }, [accent, theme]); const openSection = (next: string) => { workspace.openView(next as CatalogSection); }; const closeGuideline = workspace.closeNavigation; const inspectorSections = [ { id: "illumination", label: "Подсветка", tone: "accent" as const, content: ( <> `${value}%`} onChange={setBrightness} /> `${value}%`} onChange={setGlowDistance} /> ), }, { id: "connections", label: "Связи", tone: "accent" as const, content: ( <>
setInspectorOpen(false)} > setCreateModalOpen(false)} footer={ <> } >
setProjectName(event.target.value)} /> setNotes(event.target.value)} />