From ef816cdcf46803ce63bbe96e61772fa84433a224 Mon Sep 17 00:00:00 2001 From: Alina Date: Tue, 17 Feb 2026 22:02:25 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20frontend=20MVP=20=E2=80=94=20=D0=B4?= =?UTF-8?q?=D0=B5=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D1=81=D1=85?= =?UTF-8?q?=D0=B5=D0=BC=D0=B0=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B5=D0=B9=20?= =?UTF-8?q?=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=20(AntV?= =?UTF-8?q?=20X6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - React 18 + TypeScript strict + AntV X6 2.x + AntD 5 + Zustand - Custom nodes: SiteNode, CrossDeviceNode, SpliceNode, DeviceNode, CardNode - 8-слойный автолейаут, порты (left/right), линии с цветами по статусу - Toolbar, дерево навигации, карточка объекта, таблица соединений - Контекстные меню, легенда, drag линий/нод, создание линий из портов - Моковые данные: 3 сайта, 10 устройств, 15 линий Co-Authored-By: Claude Opus 4.6 --- .gitignore | 9 + ARCHITECTURE.md | 531 ++ CLAUDE.md | 97 + frontend/.gitignore | 24 + frontend/README.md | 73 + frontend/eslint.config.js | 23 + frontend/index.html | 13 + frontend/package-lock.json | 4512 +++++++++++++++++ frontend/package.json | 45 + frontend/src/App.tsx | 26 + frontend/src/components/AppLayout.tsx | 119 + .../ConnectionsPanel/ConnectionsPanel.tsx | 170 + .../src/components/Legend/LegendModal.tsx | 86 + .../src/components/SidePanel/LeftPanel.tsx | 149 + .../src/components/SidePanel/RightPanel.tsx | 134 + frontend/src/components/Toolbar/Toolbar.tsx | 176 + frontend/src/constants/layerMapping.ts | 40 + frontend/src/constants/lineStyles.ts | 29 + frontend/src/constants/sizes.ts | 24 + frontend/src/constants/statusColors.ts | 55 + frontend/src/features/schema/SchemaCanvas.tsx | 228 + .../schema/context-menu/ContextMenu.tsx | 139 + .../src/features/schema/edges/edgeConfig.ts | 76 + .../src/features/schema/edges/edgeGrouping.ts | 76 + .../src/features/schema/graph/initGraph.ts | 207 + .../features/schema/graph/registerNodes.ts | 64 + .../src/features/schema/helpers/dataMapper.ts | 283 ++ .../schema/helpers/portSideResolver.ts | 64 + .../src/features/schema/layout/autoLayout.ts | 193 + .../src/features/schema/nodes/CardNode.tsx | 44 + .../features/schema/nodes/CrossDeviceNode.tsx | 83 + .../src/features/schema/nodes/DeviceNode.tsx | 80 + .../src/features/schema/nodes/SiteNode.tsx | 60 + .../src/features/schema/nodes/SpliceNode.tsx | 46 + .../src/features/schema/ports/portConfig.ts | 63 + frontend/src/hooks/useContextMenu.ts | 21 + frontend/src/hooks/useGraph.ts | 5 + frontend/src/index.css | 12 + frontend/src/main.tsx | 5 + frontend/src/mock/schemaData.ts | 315 ++ frontend/src/store/schemaStore.ts | 108 + frontend/src/types/graph.ts | 13 + frontend/src/types/index.ts | 136 + frontend/src/utils/index.ts | 14 + frontend/tsconfig.app.json | 28 + frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 26 + frontend/vite.config.ts | 7 + 48 files changed, 8738 insertions(+) create mode 100644 .gitignore create mode 100644 ARCHITECTURE.md create mode 100644 CLAUDE.md create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/components/AppLayout.tsx create mode 100644 frontend/src/components/ConnectionsPanel/ConnectionsPanel.tsx create mode 100644 frontend/src/components/Legend/LegendModal.tsx create mode 100644 frontend/src/components/SidePanel/LeftPanel.tsx create mode 100644 frontend/src/components/SidePanel/RightPanel.tsx create mode 100644 frontend/src/components/Toolbar/Toolbar.tsx create mode 100644 frontend/src/constants/layerMapping.ts create mode 100644 frontend/src/constants/lineStyles.ts create mode 100644 frontend/src/constants/sizes.ts create mode 100644 frontend/src/constants/statusColors.ts create mode 100644 frontend/src/features/schema/SchemaCanvas.tsx create mode 100644 frontend/src/features/schema/context-menu/ContextMenu.tsx create mode 100644 frontend/src/features/schema/edges/edgeConfig.ts create mode 100644 frontend/src/features/schema/edges/edgeGrouping.ts create mode 100644 frontend/src/features/schema/graph/initGraph.ts create mode 100644 frontend/src/features/schema/graph/registerNodes.ts create mode 100644 frontend/src/features/schema/helpers/dataMapper.ts create mode 100644 frontend/src/features/schema/helpers/portSideResolver.ts create mode 100644 frontend/src/features/schema/layout/autoLayout.ts create mode 100644 frontend/src/features/schema/nodes/CardNode.tsx create mode 100644 frontend/src/features/schema/nodes/CrossDeviceNode.tsx create mode 100644 frontend/src/features/schema/nodes/DeviceNode.tsx create mode 100644 frontend/src/features/schema/nodes/SiteNode.tsx create mode 100644 frontend/src/features/schema/nodes/SpliceNode.tsx create mode 100644 frontend/src/features/schema/ports/portConfig.ts create mode 100644 frontend/src/hooks/useContextMenu.ts create mode 100644 frontend/src/hooks/useGraph.ts create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/mock/schemaData.ts create mode 100644 frontend/src/store/schemaStore.ts create mode 100644 frontend/src/types/graph.ts create mode 100644 frontend/src/types/index.ts create mode 100644 frontend/src/utils/index.ts create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de40431 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules +dist +dist-ssr +*.local +*.log +.DS_Store +.idea +.vscode +*.sw? diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..d49805e --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,531 @@ +# Test-X6: Детальная схема связей устройства — Архитектура + +## Обзор проекта + +Веб-приложение для визуализации и редактирования детальных схем связей сетевых устройств. Основано на библиотеке **AntV X6** для рендеринга интерактивных графов. Позволяет отображать сайты, устройства, карты, порты и линии связи с полной поддержкой вложенности, автоматической раскладки и многопользовательского редактирования. + +--- + +## Технологический стек + +| Слой | Технологии | +|------|-----------| +| **Frontend** | React 18, TypeScript, AntD 5, AntV X6 2.x, Vite | +| **Backend** | NestJS, TypeORM, PostgreSQL | +| **Инфраструктура** | Docker, k3s, Drone CI, Harbor | +| **Линтинг** | ESLint, Prettier, tsc strict mode | + +--- + +## Структура монорепозитория + +``` +test-x6/ +├── frontend/ # React-приложение +│ ├── src/ +│ │ ├── api/ # API-клиент, типы запросов/ответов +│ │ ├── components/ # Общие UI-компоненты +│ │ │ ├── Layout/ # Общий layout приложения +│ │ │ ├── Toolbar/ # Панель инструментов +│ │ │ ├── SidePanel/ # Левая и правая боковые панели +│ │ │ └── ConnectionsPanel/# Нижняя панель соединений +│ │ ├── features/ +│ │ │ └── schema/ # Основной модуль схемы +│ │ │ ├── graph/ # Инициализация и конфигурация X6 Graph +│ │ │ ├── nodes/ # Custom Nodes (Site, Cross, Splice, Device, Card) +│ │ │ ├── ports/ # Логика портов (контейнеры, сортировка, drag) +│ │ │ ├── edges/ # Линии (стили, группировка, перемычки) +│ │ │ ├── layout/ # Автолейаут (8 слоёв) +│ │ │ ├── context-menu/# Контекстные меню +│ │ │ ├── selection/ # Выделение (rectangle, lasso) +│ │ │ ├── locking/ # Блокировка / concurrency +│ │ │ └── export/ # Экспорт PNG, Excel +│ │ ├── hooks/ # React-хуки +│ │ ├── store/ # Zustand store +│ │ ├── types/ # TypeScript типы и интерфейсы +│ │ ├── utils/ # Утилиты +│ │ ├── constants/ # Константы (цвета, размеры, слои) +│ │ ├── App.tsx +│ │ └── main.tsx +│ ├── index.html +│ ├── vite.config.ts +│ ├── tsconfig.json +│ ├── .eslintrc.cjs +│ └── package.json +├── backend/ # NestJS API +│ ├── src/ +│ │ ├── modules/ +│ │ │ ├── sites/ # CRUD сайтов +│ │ │ ├── devices/ # CRUD устройств (активных и пассивных) +│ │ │ ├── cards/ # CRUD карт +│ │ │ ├── ports/ # CRUD портов +│ │ │ ├── lines/ # CRUD линий связи +│ │ │ ├── schema/ # Сборка и отдача схемы, сохранение layout +│ │ │ ├── locks/ # Блокировки редактирования +│ │ │ └── export/ # Экспорт (PNG-метаданные, Excel) +│ │ ├── common/ # Guards, interceptors, filters, pipes +│ │ ├── database/ # TypeORM конфигурация, миграции +│ │ ├── app.module.ts +│ │ └── main.ts +│ ├── tsconfig.json +│ ├── .eslintrc.cjs +│ └── package.json +├── CLAUDE.md +├── ARCHITECTURE.md +└── docker-compose.yml +``` + +--- + +## Архитектура Frontend + +### Граф (X6 Graph) + +Центральный компонент — `SchemaCanvas`, обёртка над `@antv/x6` Graph. Инициализация: + +``` +SchemaCanvas +├── useGraphInit() — создание экземпляра Graph, плагины (Selection, Snapline, MiniMap, Keyboard) +├── useGraphData() — загрузка данных, маппинг в ноды/эджи X6 +├── useGraphInteraction() — обработчики событий (click, dblclick, contextmenu, drag) +└── useGraphLayout() — автоматическая раскладка (8 слоёв) +``` + +### Custom Nodes + +Каждый тип ноды — отдельный класс, наследующий `Shape.Rect` (или `Shape.HTML` для сложных случаев): + +| Нода | Класс | Особенности | +|------|-------|-------------| +| **SiteNode** | `Shape.Rect` + custom markup | Контейнер с шапкой (чёрный фон, 4 строки), вложенность (embedding), динамический размер | +| **CrossDeviceNode** | `Shape.Rect` + custom attrs | Асимметричные скругления (rx/ry по углам через ``), пропорция 1:3, L/S порты | +| **SpliceClosureNode** | `Shape.Rect` | Квадрат 98×98, border-radius 6px | +| **OtherDeviceNode** | `Shape.Rect` | Адаптивный размер под количество портов | +| **CardNode** | `Shape.Rect` (embedded) | Вложенный в устройство, асимметричный контур (верх/низ 1.5px, бока 5px), условное отображение | + +### Система портов + +```typescript +// Двухконтейнерная архитектура портов +interface PortContainerConfig { + left: PortGroup; // Левый контейнер (ЛК) + right: PortGroup; // Правый контейнер (ПК) +} +``` + +Логика распределения: +1. При загрузке — определение стороны на основе относительного положения связанных устройств +2. При перемещении устройства — `node:move` событие, пересчёт пересечения границ, автопереброс портов +3. Ручной drag — пользователь перетаскивает порт между ЛК и ПК + +### Линии (Edges) + +```typescript +// Конфигурация линии +interface EdgeConfig { + style: EdgeLineStyle; // solid, dashed, dotted (Appendix B) + color: string; // по статусу (Appendix A) + medium: 'optical' | 'copper' | 'wireless' | 'unknown'; + router: 'orth' | 'normal'; // ломаная / прямая + grouped: boolean; // входит ли в группу +} +``` + +Группировка: при >1 линии между парой устройств — рендеринг одной линии с badge-счётчиком. Тултип по hover — разбивка по статусам. + +### Автолейаут (8 слоёв) + +```typescript +// Маппинг типов устройств на слои +const LAYER_MAPPING: Record = { + 1: ['cross_optical', 'rrl', 'wireless', 'satellite'], + 2: ['tspu', 'dwdm'], + 3: ['men', 'sdh', 'multiservice_platform'], + 4: ['ip', 'optical_modem', 'optical_mux', 'lan_wlan'], + 5: ['ran_controller', 'mgn', 'mgx', 'server', 'sorm', 'mob', 'fix'], + 6: ['voip', 'xdsl', 'pdh'], + 7: ['ran_base_station', 'unknown', 'video_surveillance'], + 8: [], // overflow from layer 1 (>6 cross) + copper cross +}; +``` + +Алгоритм: +1. Определить тип каждого устройства → маппинг в слой +2. Проверить overflow (слой 1, >6 кроссов → перенос в слой 8) +3. Рассчитать Y-координаты слоёв (пустые — схлопнуть) +4. Внутри слоя — расположить устройства в ряд с отступами +5. Применить позиции + +### State Management (Zustand) + +``` +SchemaStore +├── graph: Graph | null — экземпляр X6 Graph +├── data: SchemaData — текущие данные схемы +├── mode: 'view' | 'edit' — режим работы +├── selectedElements: string[] — выделенные элементы +├── clipboard: ClipboardData | null — буфер обмена +├── displaySettings: DisplaySettings — настройки отображения +├── locks: LockInfo[] — информация о блокировках +├── portSettings: Record — пользовательские настройки портов +└── layoutPositions: Record — сохранённые позиции +``` + +--- + +## Архитектура Backend + +### API Endpoints + +``` +# Схема +GET /api/schema/:deviceId — получить детальную схему устройства +PUT /api/schema/:deviceId/layout — сохранить layout (позиции, порты, карты) + +# Сайты +GET /api/sites — список сайтов (с фильтрацией) +GET /api/sites/:id — карточка сайта +POST /api/sites — создать сайт +PUT /api/sites/:id — обновить сайт +DELETE /api/sites/:id — удалить сайт + +# Устройства +GET /api/devices — список устройств (фильтры: тип, сайт) +GET /api/devices/:id — карточка устройства +POST /api/devices — создать устройство +PUT /api/devices/:id — обновить устройство +DELETE /api/devices/:id — удалить устройство +POST /api/devices/:id/copy — копирование (body: { withConnections: boolean }) +PUT /api/devices/:id/move-site — перенос на другой сайт + +# Карты +GET /api/devices/:deviceId/cards — карты устройства +PUT /api/cards/:id/visibility — показать/скрыть карту + +# Порты +GET /api/devices/:deviceId/ports — порты устройства +PUT /api/ports/order — сохранить порядок портов +PUT /api/ports/:id/settings — настройки порта (цвет, сторона) + +# Линии +GET /api/lines — линии (фильтры: device, site) +GET /api/lines/:id — карточка линии +POST /api/lines — создать линию +PUT /api/lines/:id — обновить линию +DELETE /api/lines/:id — удалить линию +POST /api/lines/:id/break — разрыв линии +POST /api/lines/:id/copy — копирование линии + +# Блокировки +POST /api/locks — заблокировать элементы +DELETE /api/locks/:id — разблокировать +GET /api/locks/schema/:deviceId — текущие блокировки на схеме + +# Экспорт +GET /api/export/excel/:deviceId — экспорт в Excel +``` + +### Модель данных (TypeORM Entities) + +``` +┌──────────────────────┐ +│ Site │ +├──────────────────────┤ +│ id: UUID │ +│ name: string │ +│ address: string │ +│ erpCode: string │ +│ code1C: string │ +│ status: SiteStatus │ +│ parentSiteId: UUID? │ +│ ──────────────────── │ +│ devices: Device[] │ +│ childSites: Site[] │ +└──────────────────────┘ + │ 1:N + ▼ +┌──────────────────────┐ +│ Device │ +├──────────────────────┤ +│ id: UUID │ +│ name: string │ +│ networkName: string │ +│ ipAddress: string │ +│ marking: string │ +│ id1: string │ +│ id2: string │ +│ type: DeviceType │ +│ group: DeviceGroup │ // 'active' | 'passive' | 'cross' | 'splice' +│ category: string │ // для маппинга в слой +│ status: DeviceStatus │ +│ siteId: UUID │ +│ ──────────────────── │ +│ cards: Card[] │ +│ ports: Port[] │ +│ site: Site │ +└──────────────────────┘ + │ 1:N + ▼ +┌──────────────────────┐ +│ Card │ +├──────────────────────┤ +│ id: UUID │ +│ slotName: string │ +│ networkName: string │ +│ status: CardStatus │ +│ visible: boolean │ +│ deviceId: UUID │ +│ ──────────────────── │ +│ ports: Port[] │ +│ device: Device │ +└──────────────────────┘ + │ 1:N + ▼ +┌──────────────────────┐ +│ Port │ +├──────────────────────┤ +│ id: UUID │ +│ name: string │ +│ slotName: string │ +│ side: 'left' | 'right'│ +│ sortOrder: number │ +│ labelColor: string │ +│ deviceId: UUID │ +│ cardId: UUID? │ +│ ──────────────────── │ +│ device: Device │ +│ card: Card? │ +│ linesA: Line[] │ +│ linesZ: Line[] │ +└──────────────────────┘ + +┌──────────────────────┐ +│ Line │ +├──────────────────────┤ +│ id: UUID │ +│ name: string │ +│ templateName: string │ +│ status: LineStatus │ +│ type: LineType │ // simple | complex +│ medium: Medium │ // optical | copper | wireless | unknown +│ lineStyle: LineStyle │ // solid | dashed | dotted +│ portAId: UUID │ +│ portZId: UUID │ +│ ──────────────────── │ +│ portA: Port │ +│ portZ: Port │ +│ fibers: Fiber[] │ // для сложных линий +└──────────────────────┘ + +┌──────────────────────┐ +│ Fiber │ +├──────────────────────┤ +│ id: UUID │ +│ name: string │ +│ status: FiberStatus │ +│ lineId: UUID │ +│ portAId: UUID │ +│ portZId: UUID │ +└──────────────────────┘ + +┌──────────────────────┐ +│ SchemaLayout │ +├──────────────────────┤ +│ id: UUID │ +│ userId: UUID │ +│ deviceId: UUID │ // центральное устройство схемы +│ positions: jsonb │ // { nodeId: {x, y, w, h} } +│ portOrders: jsonb │ // { deviceId: { portId: sortOrder } } +│ cardVisibility: jsonb │ // { cardId: boolean } +│ settings: jsonb │ // настройки отображения +│ updatedAt: Date │ +└──────────────────────┘ + +┌──────────────────────┐ +│ EditLock │ +├──────────────────────┤ +│ id: UUID │ +│ schemaDeviceId: UUID │ // схема которую заблокировали +│ userId: UUID │ +│ userName: string │ +│ lockedAt: Date │ +│ expiresAt: Date │ // автоистечение через N минут +└──────────────────────┘ +``` + +### Enums и цветовая карта + +```typescript +// Статусы (для цветовой карты — Appendix A) +enum EntityStatus { + Active = 'active', + Planned = 'planned', + UnderConstruction = 'under_construction', + Reserved = 'reserved', + Faulty = 'faulty', + Decommissioned = 'decommissioned', + Unknown = 'unknown', +} + +// Типы устройств (для маппинга в слои) +enum DeviceCategory { + CrossOptical = 'cross_optical', + CrossCopper = 'cross_copper', + RRL = 'rrl', + Wireless = 'wireless', + Satellite = 'satellite', + TSPU = 'tspu', + DWDM = 'dwdm', + MEN = 'men', + SDH = 'sdh', + MultiservicePlatform = 'multiservice_platform', + IP = 'ip', + OpticalModem = 'optical_modem', + OpticalMux = 'optical_mux', + LanWlan = 'lan_wlan', + RanController = 'ran_controller', + MGN = 'mgn', + MGX = 'mgx', + Server = 'server', + SORM = 'sorm', + MOB = 'mob', + FIX = 'fix', + VOIP = 'voip', + xDSL = 'xdsl', + PDH = 'pdh', + RanBaseStation = 'ran_base_station', + Unknown = 'unknown', + VideoSurveillance = 'video_surveillance', +} + +// Группы устройств +enum DeviceGroup { + Active = 'active', + Passive = 'passive', +} + +// Среда передачи +enum Medium { + Optical = 'optical', + Copper = 'copper', + Wireless = 'wireless', + Unknown = 'unknown', +} +``` + +--- + +## Ключевые архитектурные решения + +### 1. Рендеринг нод: SVG vs HTML + +- **Сайт (SiteNode):** SVG `Shape.Rect` с кастомным markup. Шапка — `` для текстового переноса. +- **Кросс:** SVG `` для асимметричного скругления. +- **Карты и порты:** SVG элементы, вложенные через X6 embedding (parent-child). + +### 2. Вложенность (Embedding) + +X6 нативно поддерживает `embedding`: +```typescript +graph.options.embedding = { + enabled: true, + findParent: 'bbox', // или кастомная функция +}; +``` +- Сайт → устройства: `device.setParent(site)` +- Устройство → карты: `card.setParent(device)` +- Карта → порты: `port.setParent(card)` (через X6 ports API) + +### 3. Группировка линий + +Реализуется на уровне рендеринга: +1. При загрузке данных — группировка линий по парам `(deviceA, deviceZ)` +2. Если count > 1 → рендеринг одной «групповой» линии с label-счётчиком +3. По клику «Разгруппировать» → замена на N отдельных линий с разбивкой по статусам + +### 4. Автолейаут + +Кастомный алгоритм (не dagre/elk): +1. Парсинг устройств сайта → распределение по 8 слоям +2. Расчёт геометрии слоёв (Y offset, высота по максимальному элементу) +3. Размещение устройств внутри слоя (X offset, горизонтальный ряд) +4. Схлопывание пустых слоёв +5. Позиционирование дочерних сайтов по нижней границе родителя + +### 5. Блокировка / Concurrency + +- При переходе в режим редактирования → `POST /api/locks` (pessimistic lock) +- Блокировка с TTL (auto-expire через 30 минут неактивности) +- Heartbeat каждые 60 секунд для продления блокировки +- Другие пользователи видят значок блокировки (overlay на нодах) +- При сохранении → `DELETE /api/locks/:id` + +### 6. Сохранение состояния + +- Позиции, порядок портов, видимость карт — JSON в таблице `schema_layout` +- Привязка к паре `(userId, deviceId)` — у каждого пользователя свой layout +- При отсутствии сохранённого layout — применяется автолейаут + +--- + +## Зависимости Frontend + +| Пакет | Назначение | +|-------|-----------| +| `@antv/x6` | Основная библиотека графов | +| `@antv/x6-plugin-selection` | Выделение (rectangle, rubberband) | +| `@antv/x6-plugin-snapline` | Привязка к сетке | +| `@antv/x6-plugin-minimap` | Мини-карта | +| `@antv/x6-plugin-keyboard` | Горячие клавиши | +| `@antv/x6-plugin-clipboard` | Буфер обмена | +| `@antv/x6-plugin-history` | Undo/Redo | +| `@antv/x6-plugin-export` | Экспорт PNG | +| `antd` | UI-компоненты (меню, модалки, таблицы, формы) | +| `zustand` | State management | +| `axios` | HTTP-клиент | +| `xlsx` | Экспорт Excel | + +--- + +## Фазы реализации + +### Фаза 1: Каркас и базовые ноды +- Инициализация проекта (frontend + backend) +- Настройка X6 Graph с базовыми плагинами +- Custom Nodes: SiteNode, OtherDeviceNode (простые) +- Базовые Entity и CRUD (sites, devices) +- Загрузка и отображение статической схемы + +### Фаза 2: Порты и линии +- Система портов (двухконтейнерная, сортировка) +- CardNode с вложенными портами +- Линии с привязкой к портам +- Стили линий по статусу и среде передачи +- CrossDeviceNode, SpliceClosureNode + +### Фаза 3: Интерактивность +- Контекстные меню (6 типов) +- Drag-and-drop устройств +- Создание линий (4 способа) +- Выделение (rectangle, lasso) +- Автолейаут (8 слоёв) + +### Фаза 4: Панели и UI +- Toolbar с настройками +- Левая панель (дерево навигации) +- Правая панель (карточки объектов) +- Нижняя панель соединений + +### Фаза 5: Режимы работы и persistence +- Режим просмотра / редактирования +- Блокировки (concurrency) +- Сохранение layout (позиции, порты, карты) +- Копирование/вставка со связями +- Разрыв линии + +### Фаза 6: Группировка, экспорт, полировка +- Группировка линий (счётчик, тултип, разгруппировка) +- Экспорт PNG и Excel +- Перемычки (self-loops) +- Snap-to-grid, MiniMap +- Тестирование, оптимизация производительности diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d6b4039 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,97 @@ +# Test-X6 — Детальная схема связей устройства + +Визуализация и редактирование детальных схем связей сетевых устройств на базе AntV X6. + +## Стек + +- **Frontend:** React 18 + TypeScript + AntD 5 + AntV X6 2.x + Zustand + Vite +- **Backend:** NestJS + TypeORM + PostgreSQL +- **Линтинг:** ESLint + Prettier + tsc strict + +## Структура проекта + +``` +test-x6/ +├── frontend/ # React SPA +│ └── src/ +│ ├── api/ # HTTP-клиент, типы API +│ ├── components/# Toolbar, SidePanel, ConnectionsPanel, Layout +│ ├── features/ +│ │ └── schema/# Ядро: graph/, nodes/, ports/, edges/, layout/, context-menu/, locking/, export/ +│ ├── hooks/ # React-хуки +│ ├── store/ # Zustand +│ ├── types/ # Общие TypeScript-типы +│ ├── constants/ # Цвета статусов, размеры нод, маппинг слоёв +│ └── utils/ +├── backend/ # NestJS API +│ └── src/ +│ ├── modules/ # sites, devices, cards, ports, lines, schema, locks, export +│ ├── common/ # Guards, pipes, filters, interceptors +│ └── database/ # TypeORM config, миграции +└── ARCHITECTURE.md # Детальная архитектура +``` + +## Ключевые документы + +- `ARCHITECTURE.md` — архитектура, модель данных, API, фазы реализации +- `frontend/src/features/schema/` — ядро графа X6 + +## Команды + +```bash +# Frontend +cd frontend && npm run dev # dev-сервер +cd frontend && npm run build # сборка +cd frontend && npm run lint # линтинг +cd frontend && npm run typecheck # проверка типов + +# Backend +cd backend && npm run start:dev # dev-сервер +cd backend && npm run build # сборка +cd backend && npm run lint # линтинг +cd backend && npm run typecheck # проверка типов +cd backend && npm run migration:run # применить миграции +cd backend && npm run migration:generate # сгенерировать миграцию +``` + +## Правила разработки + +### Общие +- Язык кода и комментариев — **английский** +- Язык документации и UI — **русский** +- Строгий TypeScript: `strict: true`, никаких `any` +- Все сущности используют UUID как первичный ключ + +### Frontend +- Каждый Custom Node X6 — отдельный файл в `features/schema/nodes/` +- Логика портов (распределение по контейнерам, сортировка, drag) — в `features/schema/ports/` +- Стили линий и группировка — в `features/schema/edges/` +- Автолейаут (8 слоёв) — в `features/schema/layout/` +- State management — Zustand (не Redux, не Context) +- Компоненты AntD — для всех UI-элементов (модалки, меню, таблицы, формы) + +### Backend +- NestJS модули — по одному на доменную сущность +- Все эндпоинты — REST, префикс `/api` +- Валидация — `class-validator` + `class-transformer` +- Миграции TypeORM — явные, без `synchronize: true` в production + +### X6 специфика +- Вложенность: Site → Device → Card → Port (через X6 embedding) +- Порты: двухконтейнерная система (left/right), автопереброс при перемещении устройств +- Линии: два режима роутинга (orth/normal), группировка при >1 линии между устройствами +- Кроссы: пропорция 1:3, асимметричное скругление, L-порты слева, S-порты справа +- Перемычки (self-loops): линия выходит из устройства и входит обратно, не пересекая его + +## Цветовая карта статусов + +Статусы определяют цвет контура/заливки нод и цвет линий. Константы хранятся в `frontend/src/constants/statusColors.ts`. + +## Домен + +- **Сайт** — физическая площадка с устройствами, может содержать дочерние сайты +- **Устройство** — сетевое оборудование (активное: роутер, свитч; пассивное: кросс, муфта) +- **Карта** — вложенная плата/модуль внутри устройства +- **Порт** — точка подключения на устройстве или карте +- **Линия** — физическое соединение между портами (простая или сложная — с волокнами) +- **Волокно (Fiber)** — отдельное волокно внутри сложной линии diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..d2e7761 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..ec47849 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Схема связей устройств + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..a9a9783 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4512 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@ant-design/icons": "^6.1.0", + "@antv/x6": "^2.19.2", + "@antv/x6-plugin-clipboard": "^2.1.6", + "@antv/x6-plugin-export": "^2.1.6", + "@antv/x6-plugin-history": "^2.2.4", + "@antv/x6-plugin-keyboard": "^2.2.3", + "@antv/x6-plugin-minimap": "^2.0.7", + "@antv/x6-plugin-selection": "^2.2.2", + "@antv/x6-plugin-snapline": "^2.1.7", + "@antv/x6-plugin-transform": "^2.1.8", + "@antv/x6-react-shape": "^2.2.3", + "antd": "^6.3.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "xlsx": "^0.18.5", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.0.tgz", + "integrity": "sha512-eZFrPCnrYrF3XtL7qA4L75P0qA3TtZta8H3Yggy7UYFh8gZgu5bSMNF+v4UVCzGxzYmx8ZvPdgOce0BJ6PsW9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.1.tgz", + "integrity": "sha512-RKxkj5pGFB+FkPJ5NGhoX3DK3xsv0pMltha7Ei1AnY3tILeq38L7tuhaWDPQI/5nlPxOog44wvqpNyyGcUsNMg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.0", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.0.tgz", + "integrity": "sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@antv/x6": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@antv/x6/-/x6-2.19.2.tgz", + "integrity": "sha512-lr9sUAlrR7eSVANru8kUNZUqCRl7eOCgb36M61FMDonUYREwkHpRN+0zq+1egSSOdz3HHSDBjs7UMtGH1ZEg2w==", + "license": "MIT", + "dependencies": { + "@antv/x6-common": "^2.0.16", + "@antv/x6-geometry": "^2.0.5", + "utility-types": "^3.10.0" + } + }, + "node_modules/@antv/x6-common": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@antv/x6-common/-/x6-common-2.0.17.tgz", + "integrity": "sha512-37g7vmRkNdYzZPdwjaMSZEGv/MMH0S4r70/Jwoab1mioycmuIBN73iyziX8m56BvJSDucZ3J/6DU07otWqzS6A==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.15", + "utility-types": "^3.10.0" + } + }, + "node_modules/@antv/x6-geometry": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@antv/x6-geometry/-/x6-geometry-2.0.5.tgz", + "integrity": "sha512-MId6riEQkxphBpVeTcL4ZNXL4lScyvDEPLyIafvWMcWNTGK0jgkK7N20XSzqt8ltJb0mGUso5s56mrk8ysHu2A==", + "license": "MIT" + }, + "node_modules/@antv/x6-plugin-clipboard": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-clipboard/-/x6-plugin-clipboard-2.1.6.tgz", + "integrity": "sha512-roZPLnZx6PK8MBvee0QMo90fz/TXeF0WNe4EGin2NBq5M1I5XTWrYvA6N2XVIiWAAI67gjQeEE8TpkL7f8QdqA==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-export": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-export/-/x6-plugin-export-2.1.6.tgz", + "integrity": "sha512-m0ukMmZhrFE5n7uCR43DVQBdiUfpjGN+vm1mc+6RTZdHK8pa6Mxr0RZztaxPy34YA4tli+bGY3ePslsNPfh6PQ==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-history": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-history/-/x6-plugin-history-2.2.4.tgz", + "integrity": "sha512-9gHHvEW4Fla+1hxUV49zNgJyIMoV9CjVM52MrFgAJcvyRn1Kvxz4MfxiKlG+DEZUs+/zvfjl9pS6gJOd8laRkg==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-keyboard": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-keyboard/-/x6-plugin-keyboard-2.2.3.tgz", + "integrity": "sha512-pnCIC+mDyKKfkcDyLePfGxKVIqXBcldTgannITkHC1kc0IafRS1GMvzpvuDGrM5haRYd6Nwz8kjkJyHkJE4GPA==", + "license": "MIT", + "dependencies": { + "mousetrap": "^1.6.5" + }, + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-minimap": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-minimap/-/x6-plugin-minimap-2.0.7.tgz", + "integrity": "sha512-8zzESCx0jguFPCOKCA0gPFb6JmRgq81CXXtgJYe34XhySdZ6PB23I5Po062lNsEuTjTjnzli4lju8vvU+jzlGw==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-selection": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-selection/-/x6-plugin-selection-2.2.2.tgz", + "integrity": "sha512-s2gtR9Onlhr7HOHqyqg0d+4sG76JCcQEbvrZZ64XmSChlvieIPlC3YtH4dg1KMNhYIuBmBmpSum6S0eVTEiPQw==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-snapline": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-snapline/-/x6-plugin-snapline-2.1.7.tgz", + "integrity": "sha512-AsysoCb9vES0U2USNhEpYuO/W8I0aYfkhlbee5Kt4NYiMfQfZKQyqW/YjDVaS2pm38C1NKu1LdPVk/BBr4CasA==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-plugin-transform": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@antv/x6-plugin-transform/-/x6-plugin-transform-2.1.8.tgz", + "integrity": "sha512-GvJuiJ4BKp0H7+qx3R1I+Vzbw5gXp9+oByXo/WyVxE3urOC7LC5sqnaDfIjyYMN6ROLPYPZraLSeSyYBgMgcDw==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x" + } + }, + "node_modules/@antv/x6-react-shape": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@antv/x6-react-shape/-/x6-react-shape-2.2.3.tgz", + "integrity": "sha512-42PGxk3XLnx9bsHQiRPauai5UQCrsh0MbWI3MyHRpjQaC30FK1rqyjH/EJ8qH0p6/cAndszZzCaiPqBbs3FqLQ==", + "license": "MIT", + "peerDependencies": { + "@antv/x6": "^2.x", + "react": ">=18.0.0", + "react-dom": ">= 18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.14.0.tgz", + "integrity": "sha512-Ip9356xwZUR2nbW5PRVGif4B/bDve4pLa/N+PGbvBaTnjbvmN4PFMBGQSmlDlzKP1ovxaYMvwF/dI9lXNLT4iQ==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.2.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.0.tgz", + "integrity": "sha512-o7Vavj7yyfVxFmeynXf0fCHVlC0UTE9al74c6nYuLck+gjuVdQNWSVXR8Efq/mmWFy7891SCOsfaPq6Eqe1s/g==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz", + "integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.8.4.tgz", + "integrity": "sha512-Ay6PM7phkTkquplG8fWfUGFZ2GTLx9diTl4f0d8Eqxd7W1u1KjE9AQooFQHOHnhZf0Ya3z51+5EKCWHmt/dNEw==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.6.2.tgz", + "integrity": "sha512-OgIn2RAoaSBqaIgzJf/X6iflIa9LpTozci1lagLBdURDFhGA370v0+T0tXxOi8YShMjTha531sFhwtnrv+EJaQ==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^5.1.0", + "@rc-component/util": "^1.6.2", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.6.0.tgz", + "integrity": "sha512-tSfn2ZE/oP082g4QIOxeehkmgnXB7R+5AFj/lIFr4k7pEuxHBdyGIq9axoCY9qea8NN0DY6p4IB/F07tLqaT5A==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.1.2.tgz", + "integrity": "sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.6.0.tgz", + "integrity": "sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.1.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/textarea": "~1.1.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.2.0.tgz", + "integrity": "sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.1.6.tgz", + "integrity": "sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-1.2.0.tgz", + "integrity": "sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.0.tgz", + "integrity": "sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz", + "integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.9.0.tgz", + "integrity": "sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.0.tgz", + "integrity": "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.1.tgz", + "integrity": "sha512-NfXXMmiR+SmUuKE1NwJESzEUYUFWIDUn2uXpxCTOLwiRUUakd62DRNFjRJArgzyFW8S5rsL4aX5XlyIXyC/vRA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.6.10.tgz", + "integrity": "sha512-y4+2LnyGZrAorIBwflk78PmFVUWcSc9pcljiH72oHj7K1YY/BFUmj224pD7P4o7J+tbIFES45Z7LIpjVmvYlNA==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz", + "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.9.1.tgz", + "integrity": "sha512-FVI5ZS/GdB3BcgexfCYKi3iHhZS3Fr59EtsxORszYGrfpH1eWr33eDNSYkVfLI6tfJ7vftJDd9D5apfFWqkdJg==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.1.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.7.0.tgz", + "integrity": "sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/textarea": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/textarea/-/textarea-1.1.2.tgz", + "integrity": "sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.1.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.3.0.tgz", + "integrity": "sha512-K04K9r32kUC+auBSQfr+Fss4SpSIS9JGe56oq/ALAX0p+i2ylYOI1MgR83yBY7v96eO6ZFXcM/igCQmubps0Ow==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.2.3.tgz", + "integrity": "sha512-mG8hF2ogQcKaEpfyxzPvMWqqkptofd7Sf+YiXOpPzuXLTLwNKfLDJtysc1/oybopbnzxNqWh2Vgwi+GYwNIb7w==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.8.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.8.0.tgz", + "integrity": "sha512-iYsPq3nuLYvGqdvFAW+l+I9ASRIOVbMXyA8FGZg2lGym/GwkaWeJGzI4eJ7c9IOEhRj0oyfIN4S92Fl3J05mjQ==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.2.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.0.tgz", + "integrity": "sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.2.0", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz", + "integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.9.0.tgz", + "integrity": "sha512-5uW6AfhIigCWeEQDthTozlxiT4Prn6xYQWeO0xokjcaa186OtwPRHBZJ2o0T0FhbjGhZ3vXdbkv0sx3gAYW7Vg==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz", + "integrity": "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", + "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", + "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", + "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", + "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", + "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", + "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", + "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", + "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", + "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", + "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", + "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.3.0.tgz", + "integrity": "sha512-bbHJcASrRHp02wTpr940KtUHlTT6tvmaD4OAjqgOJXNmTQ/+qBDdBVWY/yeDV41p/WbWjTLlaqRGVbL3UEVpNw==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.0", + "@ant-design/cssinjs-utils": "^2.1.1", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.1.0", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.28.4", + "@rc-component/cascader": "~1.14.0", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.0", + "@rc-component/dialog": "~1.8.4", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.6.2", + "@rc-component/image": "~1.6.0", + "@rc-component/input": "~1.1.2", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.6.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/motion": "~1.1.6", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~1.2.0", + "@rc-component/pagination": "~1.2.0", + "@rc-component/picker": "~1.9.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~1.1.1", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.6.5", + "@rc-component/slider": "~1.0.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.9.1", + "@rc-component/tabs": "~1.7.0", + "@rc-component/textarea": "~1.1.2", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.3.0", + "@rc-component/tree": "~1.2.3", + "@rc-component/tree-select": "~1.8.0", + "@rc-component/trigger": "^3.9.0", + "@rc-component/upload": "~1.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==", + "license": "Apache-2.0 WITH LLVM-exception" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", + "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.0", + "@typescript-eslint/parser": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..1149fb1 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,45 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "typecheck": "tsc -b --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^6.1.0", + "@antv/x6": "^2.19.2", + "@antv/x6-plugin-clipboard": "^2.1.6", + "@antv/x6-plugin-export": "^2.1.6", + "@antv/x6-plugin-history": "^2.2.4", + "@antv/x6-plugin-keyboard": "^2.2.3", + "@antv/x6-plugin-minimap": "^2.0.7", + "@antv/x6-plugin-selection": "^2.2.2", + "@antv/x6-plugin-snapline": "^2.1.7", + "@antv/x6-plugin-transform": "^2.1.8", + "@antv/x6-react-shape": "^2.2.3", + "antd": "^6.3.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "xlsx": "^0.18.5", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..3385647 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,26 @@ +import { ConfigProvider } from 'antd'; +import ruRU from 'antd/locale/ru_RU'; +import { AppLayout } from './components/AppLayout.tsx'; +import { Toolbar } from './components/Toolbar/Toolbar.tsx'; +import { LeftPanel } from './components/SidePanel/LeftPanel.tsx'; +import { RightPanel } from './components/SidePanel/RightPanel.tsx'; +import { ConnectionsPanel } from './components/ConnectionsPanel/ConnectionsPanel.tsx'; +import { LegendModal } from './components/Legend/LegendModal.tsx'; +import { SchemaCanvas } from './features/schema/SchemaCanvas.tsx'; +import { ContextMenu } from './features/schema/context-menu/ContextMenu.tsx'; + +export default function App() { + return ( + + } + leftPanel={} + canvas={} + rightPanel={} + bottomPanel={} + /> + + + + ); +} diff --git a/frontend/src/components/AppLayout.tsx b/frontend/src/components/AppLayout.tsx new file mode 100644 index 0000000..f8913ac --- /dev/null +++ b/frontend/src/components/AppLayout.tsx @@ -0,0 +1,119 @@ +import { useState, type ReactNode } from 'react'; +import { Button, Tooltip } from 'antd'; +import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; + +interface AppLayoutProps { + toolbar: ReactNode; + leftPanel: ReactNode; + canvas: ReactNode; + rightPanel: ReactNode; + bottomPanel: ReactNode; +} + +export function AppLayout({ + toolbar, + leftPanel, + canvas, + rightPanel, + bottomPanel, +}: AppLayoutProps) { + const [leftCollapsed, setLeftCollapsed] = useState(false); + const [rightCollapsed, setRightCollapsed] = useState(false); + + const leftWidth = leftCollapsed ? 0 : 240; + const rightWidth = rightCollapsed ? 0 : 280; + + return ( +
+ {/* Toolbar */} + {toolbar} + + {/* Main content */} +
+ {/* Left panel */} +
+ {!leftCollapsed && leftPanel} +
+ + {/* Left toggle */} +
+ +
+ + {/* Canvas + bottom panel */} +
+
{canvas}
+ {bottomPanel} +
+ + {/* Right toggle */} +
+ +
+ + {/* Right panel */} +
+ {!rightCollapsed && rightPanel} +
+
+
+ ); +} diff --git a/frontend/src/components/ConnectionsPanel/ConnectionsPanel.tsx b/frontend/src/components/ConnectionsPanel/ConnectionsPanel.tsx new file mode 100644 index 0000000..65c1690 --- /dev/null +++ b/frontend/src/components/ConnectionsPanel/ConnectionsPanel.tsx @@ -0,0 +1,170 @@ +import { useState } from 'react'; +import { Table, Input, Button, Tag, Space } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { CloseOutlined, FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons'; +import { useSchemaStore } from '../../store/schemaStore.ts'; +import { STATUS_COLORS, STATUS_LABELS } from '../../constants/statusColors.ts'; +import { mockData } from '../../mock/schemaData.ts'; +import type { EntityStatus, Line } from '../../types/index.ts'; + +const { Search } = Input; + +interface ConnectionRow { + key: string; + lineName: string; + lineStatus: EntityStatus; + deviceAName: string; + portAName: string; + deviceZName: string; + portZName: string; +} + +export function ConnectionsPanel() { + const visible = useSchemaStore((s) => s.connectionsPanelVisible); + const setVisible = useSchemaStore((s) => s.setConnectionsPanelVisible); + const panelData = useSchemaStore((s) => s.connectionsPanelData); + const [searchValue, setSearchValue] = useState(''); + const [expanded, setExpanded] = useState(false); + + if (!visible || !panelData) return null; + + // Build rows from panel data + const rows: ConnectionRow[] = []; + + if (panelData.line) { + // Single line mode + const line = panelData.line as Line; + const portA = panelData.portA as { name: string } | null; + const portZ = panelData.portZ as { name: string } | null; + const devA = panelData.deviceA as { name: string } | null; + const devZ = panelData.deviceZ as { name: string } | null; + rows.push({ + key: line.id, + lineName: line.name, + lineStatus: line.status, + deviceAName: devA?.name ?? '—', + portAName: portA?.name ?? '—', + deviceZName: devZ?.name ?? '—', + portZName: portZ?.name ?? '—', + }); + } else if (panelData.lines) { + // Multiple lines mode + const lines = panelData.lines as Line[]; + for (const line of lines) { + const portA = mockData.ports.find((p) => p.id === line.portAId); + const portZ = mockData.ports.find((p) => p.id === line.portZId); + const devA = portA ? mockData.devices.find((d) => d.id === portA.deviceId) : null; + const devZ = portZ ? mockData.devices.find((d) => d.id === portZ.deviceId) : null; + rows.push({ + key: line.id, + lineName: line.name, + lineStatus: line.status, + deviceAName: devA?.name ?? '—', + portAName: portA?.name ?? '—', + deviceZName: devZ?.name ?? '—', + portZName: portZ?.name ?? '—', + }); + } + } + + const filtered = searchValue + ? rows.filter( + (r) => + r.lineName.toLowerCase().includes(searchValue.toLowerCase()) || + r.deviceAName.toLowerCase().includes(searchValue.toLowerCase()) || + r.deviceZName.toLowerCase().includes(searchValue.toLowerCase()), + ) + : rows; + + const columns: ColumnsType = [ + { + title: 'Линия', + dataIndex: 'lineName', + key: 'lineName', + render: (name: string, record: ConnectionRow) => { + const colors = STATUS_COLORS[record.lineStatus]; + return ( + + + {STATUS_LABELS[record.lineStatus]} + + {name} + + ); + }, + }, + { + title: 'Устройство A', + key: 'deviceA', + render: (_: unknown, record: ConnectionRow) => ( + + {record.deviceAName} ({record.portAName}) + + ), + }, + { + title: 'Устройство Z', + key: 'deviceZ', + render: (_: unknown, record: ConnectionRow) => ( + + {record.deviceZName} ({record.portZName}) + + ), + }, + ]; + + return ( +
+
+ Соединения + + setSearchValue(e.target.value)} + allowClear + /> +
+
+ + + + ); +} diff --git a/frontend/src/components/Legend/LegendModal.tsx b/frontend/src/components/Legend/LegendModal.tsx new file mode 100644 index 0000000..1d3e8ee --- /dev/null +++ b/frontend/src/components/Legend/LegendModal.tsx @@ -0,0 +1,86 @@ +import { Modal, Space, Tag } from 'antd'; +import { useSchemaStore } from '../../store/schemaStore.ts'; +import { STATUS_COLORS, STATUS_LABELS } from '../../constants/statusColors.ts'; +import { EntityStatus, LineStyle, Medium } from '../../types/index.ts'; + +export function LegendModal() { + const visible = useSchemaStore((s) => s.legendVisible); + const setVisible = useSchemaStore((s) => s.setLegendVisible); + + return ( + setVisible(false)} + footer={null} + width={480} + > +
+

Цвета статусов

+ + {Object.values(EntityStatus).map((status) => { + const colors = STATUS_COLORS[status]; + const label = STATUS_LABELS[status]; + return ( + + {label} + + ); + })} + +
+ +
+

Типы линий

+
+ {Object.values(LineStyle).map((style) => { + const dasharray = + style === LineStyle.Solid + ? '' + : style === LineStyle.Dashed + ? '8 4' + : '2 4'; + return ( +
+ + + + {style} +
+ ); + })} +
+
+ +
+

Среда передачи

+
+ {Object.values(Medium).map((medium) => ( +
+ {medium} + {medium === Medium.Optical && ' — оптическое волокно'} + {medium === Medium.Copper && ' — медный кабель'} + {medium === Medium.Wireless && ' — беспроводная связь'} + {medium === Medium.Unknown && ' — неизвестная среда'} +
+ ))} +
+
+
+ ); +} diff --git a/frontend/src/components/SidePanel/LeftPanel.tsx b/frontend/src/components/SidePanel/LeftPanel.tsx new file mode 100644 index 0000000..509b6f9 --- /dev/null +++ b/frontend/src/components/SidePanel/LeftPanel.tsx @@ -0,0 +1,149 @@ +import { useState, useMemo } from 'react'; +import { Tree, Input } from 'antd'; +import type { TreeDataNode } from 'antd'; +import { + ApartmentOutlined, + HddOutlined, + ClusterOutlined, +} from '@ant-design/icons'; +import { mockData } from '../../mock/schemaData.ts'; +import { useSchemaStore } from '../../store/schemaStore.ts'; + +const { Search } = Input; + +export function LeftPanel() { + const [searchValue, setSearchValue] = useState(''); + const [expandedKeys, setExpandedKeys] = useState(['sites', 'all-devices']); + const graph = useSchemaStore((s) => s.graph); + const setRightPanelData = useSchemaStore((s) => s.setRightPanelData); + + const treeData = useMemo((): TreeDataNode[] => { + const sitesTree: TreeDataNode[] = mockData.sites + .filter((s) => !s.parentSiteId) + .map((site) => { + const children: TreeDataNode[] = []; + + // Add devices belonging to this site + const siteDevices = mockData.devices.filter( + (d) => d.siteId === site.id, + ); + for (const device of siteDevices) { + children.push({ + key: device.id, + title: device.name, + icon: , + }); + } + + // Add child sites + const childSites = mockData.sites.filter( + (s) => s.parentSiteId === site.id, + ); + for (const childSite of childSites) { + const childDevices = mockData.devices.filter( + (d) => d.siteId === childSite.id, + ); + children.push({ + key: childSite.id, + title: childSite.name, + icon: , + children: childDevices.map((d) => ({ + key: d.id, + title: d.name, + icon: , + })), + }); + } + + return { + key: site.id, + title: site.name, + icon: , + children, + }; + }); + + return [ + { + key: 'sites', + title: 'Сайты', + icon: , + children: sitesTree, + }, + ]; + }, []); + + const filteredTreeData = useMemo(() => { + if (!searchValue) return treeData; + + const filterTree = (nodes: TreeDataNode[]): TreeDataNode[] => { + return nodes + .map((node) => { + const title = String(node.title ?? ''); + const match = title.toLowerCase().includes(searchValue.toLowerCase()); + const filteredChildren = node.children + ? filterTree(node.children) + : []; + if (match || filteredChildren.length > 0) { + return { ...node, children: filteredChildren }; + } + return null; + }) + .filter(Boolean) as TreeDataNode[]; + }; + + return filterTree(treeData); + }, [treeData, searchValue]); + + const handleSelect = (selectedKeys: React.Key[]) => { + const key = selectedKeys[0] as string; + if (!key || !graph) return; + + // Find the node on the graph and center on it + const cell = graph.getCellById(key); + if (cell) { + graph.centerCell(cell); + graph.select(cell); + + // Set right panel data + const data = cell.getData() as Record | undefined; + if (data) { + setRightPanelData(data); + } + } + }; + + return ( +
+
+ setSearchValue(e.target.value)} + allowClear + /> +
+
+ setExpandedKeys(keys as string[])} + onSelect={handleSelect} + blockNode + style={{ fontSize: 12 }} + /> +
+
+ ); +} diff --git a/frontend/src/components/SidePanel/RightPanel.tsx b/frontend/src/components/SidePanel/RightPanel.tsx new file mode 100644 index 0000000..03f0c8a --- /dev/null +++ b/frontend/src/components/SidePanel/RightPanel.tsx @@ -0,0 +1,134 @@ +import { Descriptions, Empty, Tag } from 'antd'; +import { useSchemaStore } from '../../store/schemaStore.ts'; +import { STATUS_COLORS, STATUS_LABELS } from '../../constants/statusColors.ts'; +import type { EntityStatus } from '../../types/index.ts'; + +function StatusTag({ status }: { status: EntityStatus }) { + const colors = STATUS_COLORS[status]; + const label = STATUS_LABELS[status]; + return ( + + {label} + + ); +} + +export function RightPanel() { + const data = useSchemaStore((s) => s.rightPanelData); + + if (!data) { + return ( +
+ +
+ ); + } + + const entityType = data.entityType as string; + const status = data.status as EntityStatus; + + if (entityType === 'site') { + return ( +
+ + {data.name as string} + {data.address as string} + {data.erpCode as string} + {data.code1C as string} + + +
+ ); + } + + if (entityType === 'device') { + return ( +
+ + {data.name as string} + {data.networkName as string} + {data.ipAddress as string} + {data.marking as string} + {data.group as string} + {data.category as string} + + +
+ ); + } + + if (entityType === 'line') { + return ( +
+ + {data.name as string} + {data.medium as string} + {data.lineStyle as string} + {data.type as string} + + +
+ ); + } + + if (entityType === 'card') { + return ( +
+ + {data.slotName as string} + {data.networkName as string} + + +
+ ); + } + + return ( +
+ + {Object.entries(data).map(([key, value]) => ( + + {String(value ?? '')} + + ))} + +
+ ); +} diff --git a/frontend/src/components/Toolbar/Toolbar.tsx b/frontend/src/components/Toolbar/Toolbar.tsx new file mode 100644 index 0000000..0c47491 --- /dev/null +++ b/frontend/src/components/Toolbar/Toolbar.tsx @@ -0,0 +1,176 @@ +import { Button, Slider, Space, Switch, Tooltip, message } from 'antd'; +import { + ZoomInOutlined, + ZoomOutOutlined, + ExpandOutlined, + PlusOutlined, + DeleteOutlined, + ReloadOutlined, + PictureOutlined, + AppstoreOutlined, + NodeIndexOutlined, + EyeOutlined, + EditOutlined, + InfoCircleOutlined, +} from '@ant-design/icons'; +import { useSchemaStore } from '../../store/schemaStore.ts'; + +export function Toolbar() { + const graph = useSchemaStore((s) => s.graph); + const mode = useSchemaStore((s) => s.mode); + const setMode = useSchemaStore((s) => s.setMode); + const displaySettings = useSchemaStore((s) => s.displaySettings); + const toggleGrid = useSchemaStore((s) => s.toggleGrid); + const toggleMinimap = useSchemaStore((s) => s.toggleMinimap); + const switchLineType = useSchemaStore((s) => s.switchLineType); + const toggleLabels = useSchemaStore((s) => s.toggleLabels); + const setLegendVisible = useSchemaStore((s) => s.setLegendVisible); + + const zoom = graph ? Math.round(graph.zoom() * 100) : 100; + + const handleZoomIn = () => graph?.zoom(0.1); + const handleZoomOut = () => graph?.zoom(-0.1); + const handleFit = () => graph?.zoomToFit({ padding: 40 }); + const handleZoomChange = (value: number) => { + if (graph) { + graph.zoomTo(value / 100); + } + }; + + const handleExportPng = () => { + message.info('В разработке'); + }; + + return ( +
+ {/* Left: display settings */} + + + } + unCheckedChildren={} + /> + + + + + + + + +
+ ); +} diff --git a/frontend/src/constants/layerMapping.ts b/frontend/src/constants/layerMapping.ts new file mode 100644 index 0000000..fe92cba --- /dev/null +++ b/frontend/src/constants/layerMapping.ts @@ -0,0 +1,40 @@ +import { DeviceCategory } from '../types/index.ts'; + +export const LAYER_MAPPING: Record = { + 1: [ + DeviceCategory.CrossOptical, + DeviceCategory.RRL, + DeviceCategory.Wireless, + DeviceCategory.Satellite, + ], + 2: [DeviceCategory.TSPU, DeviceCategory.DWDM], + 3: [ + DeviceCategory.MEN, + DeviceCategory.SDH, + DeviceCategory.MultiservicePlatform, + ], + 4: [ + DeviceCategory.IP, + DeviceCategory.OpticalModem, + DeviceCategory.OpticalMux, + DeviceCategory.LanWlan, + ], + 5: [ + DeviceCategory.RanController, + DeviceCategory.MGN, + DeviceCategory.MGX, + DeviceCategory.Server, + DeviceCategory.SORM, + DeviceCategory.MOB, + DeviceCategory.FIX, + ], + 6: [DeviceCategory.VOIP, DeviceCategory.xDSL, DeviceCategory.PDH], + 7: [ + DeviceCategory.RanBaseStation, + DeviceCategory.Unknown, + DeviceCategory.VideoSurveillance, + ], + 8: [DeviceCategory.CrossCopper], +}; + +export const MAX_CROSS_PER_LAYER = 6; diff --git a/frontend/src/constants/lineStyles.ts b/frontend/src/constants/lineStyles.ts new file mode 100644 index 0000000..13293bb --- /dev/null +++ b/frontend/src/constants/lineStyles.ts @@ -0,0 +1,29 @@ +import { LineStyle, Medium } from '../types/index.ts'; + +export interface LineVisualStyle { + strokeDasharray: string; + strokeWidth: number; +} + +const LINE_STYLE_MAP: Record = { + [LineStyle.Solid]: '', + [LineStyle.Dashed]: '8 4', + [LineStyle.Dotted]: '2 4', +}; + +const MEDIUM_WIDTH_MAP: Record = { + [Medium.Optical]: 2, + [Medium.Copper]: 1.5, + [Medium.Wireless]: 1.5, + [Medium.Unknown]: 1, +}; + +export function getLineVisualStyle( + lineStyle: LineStyle, + medium: Medium, +): LineVisualStyle { + return { + strokeDasharray: LINE_STYLE_MAP[lineStyle], + strokeWidth: MEDIUM_WIDTH_MAP[medium], + }; +} diff --git a/frontend/src/constants/sizes.ts b/frontend/src/constants/sizes.ts new file mode 100644 index 0000000..9767b62 --- /dev/null +++ b/frontend/src/constants/sizes.ts @@ -0,0 +1,24 @@ +export const SITE_HEADER_HEIGHT = 80; +export const SITE_PADDING = 30; +export const SITE_MIN_WIDTH = 400; +export const SITE_MIN_HEIGHT = 200; + +export const CROSS_WIDTH = 115; +export const CROSS_HEIGHT = 345; +export const CROSS_BORDER_RADIUS = 28; + +export const SPLICE_SIZE = 98; +export const SPLICE_BORDER_RADIUS = 6; + +export const DEVICE_MIN_WIDTH = 140; +export const DEVICE_MIN_HEIGHT = 80; +export const DEVICE_BORDER_RADIUS = 6; + +export const CARD_WIDTH = 100; +export const CARD_HEIGHT = 40; + +export const PORT_RADIUS = 6; + +export const LAYER_GAP = 40; +export const DEVICE_GAP = 30; +export const LAYER_PADDING_X = 20; diff --git a/frontend/src/constants/statusColors.ts b/frontend/src/constants/statusColors.ts new file mode 100644 index 0000000..a6468ef --- /dev/null +++ b/frontend/src/constants/statusColors.ts @@ -0,0 +1,55 @@ +import { EntityStatus } from '../types/index.ts'; + +export interface StatusColorSet { + border: string; + fill: string; + text: string; +} + +export const STATUS_COLORS: Record = { + [EntityStatus.Active]: { + border: '#52c41a', + fill: '#f6ffed', + text: '#389e0d', + }, + [EntityStatus.Planned]: { + border: '#1890ff', + fill: '#e6f7ff', + text: '#096dd9', + }, + [EntityStatus.UnderConstruction]: { + border: '#faad14', + fill: '#fffbe6', + text: '#d48806', + }, + [EntityStatus.Reserved]: { + border: '#722ed1', + fill: '#f9f0ff', + text: '#531dab', + }, + [EntityStatus.Faulty]: { + border: '#ff4d4f', + fill: '#fff2f0', + text: '#cf1322', + }, + [EntityStatus.Decommissioned]: { + border: '#8c8c8c', + fill: '#fafafa', + text: '#595959', + }, + [EntityStatus.Unknown]: { + border: '#bfbfbf', + fill: '#fafafa', + text: '#8c8c8c', + }, +}; + +export const STATUS_LABELS: Record = { + [EntityStatus.Active]: 'Активный', + [EntityStatus.Planned]: 'Планируемый', + [EntityStatus.UnderConstruction]: 'Строится', + [EntityStatus.Reserved]: 'Резерв', + [EntityStatus.Faulty]: 'Неисправный', + [EntityStatus.Decommissioned]: 'Выведен', + [EntityStatus.Unknown]: 'Неизвестно', +}; diff --git a/frontend/src/features/schema/SchemaCanvas.tsx b/frontend/src/features/schema/SchemaCanvas.tsx new file mode 100644 index 0000000..e90c45a --- /dev/null +++ b/frontend/src/features/schema/SchemaCanvas.tsx @@ -0,0 +1,228 @@ +import { useEffect, useRef } from 'react'; +import { useSchemaStore } from '../../store/schemaStore.ts'; +import { initGraph } from './graph/initGraph.ts'; +import { registerAllNodes } from './graph/registerNodes.ts'; +import { buildGraphData } from './helpers/dataMapper.ts'; +import { mockData } from '../../mock/schemaData.ts'; +import { DeviceGroup } from '../../types/index.ts'; + +let nodesRegistered = false; + +export function SchemaCanvas() { + const containerRef = useRef(null); + const minimapRef = useRef(null); + const setGraph = useSchemaStore((state) => state.setGraph); + const setContextMenu = useSchemaStore((state) => state.setContextMenu); + const setRightPanelData = useSchemaStore((state) => state.setRightPanelData); + const setConnectionsPanelData = useSchemaStore( + (state) => state.setConnectionsPanelData, + ); + const setConnectionsPanelVisible = useSchemaStore( + (state) => state.setConnectionsPanelVisible, + ); + const displaySettings = useSchemaStore((state) => state.displaySettings); + + useEffect(() => { + if (!containerRef.current) return; + + if (!nodesRegistered) { + registerAllNodes(); + nodesRegistered = true; + } + + const graph = initGraph(containerRef.current, minimapRef.current); + setGraph(graph); + + const { nodes, edges } = buildGraphData(mockData, displaySettings.lineType); + + // Add nodes first (sites, then devices, then cards) + const siteNodes = nodes.filter((n) => n.shape === 'site-node'); + const deviceNodes = nodes.filter( + (n) => n.shape !== 'site-node' && n.shape !== 'card-node', + ); + const cardNodes = nodes.filter((n) => n.shape === 'card-node'); + + for (const node of siteNodes) { + graph.addNode(node); + } + for (const node of deviceNodes) { + const graphNode = graph.addNode(node); + // Set parent (embed in site) + if (node.parent) { + const parentNode = graph.getCellById(node.parent); + if (parentNode) { + parentNode.addChild(graphNode); + } + } + } + for (const node of cardNodes) { + const graphNode = graph.addNode(node); + if (node.parent) { + const parentNode = graph.getCellById(node.parent); + if (parentNode) { + parentNode.addChild(graphNode); + } + } + } + + // Add edges + for (const edge of edges) { + graph.addEdge(edge); + } + + // Center content + graph.centerContent(); + + // Event handlers + graph.on('node:click', ({ node }) => { + const data = node.getData() as Record; + setRightPanelData(data); + }); + + graph.on('edge:click', ({ edge }) => { + const data = edge.getData() as Record; + const line = mockData.lines.find((l) => l.id === edge.id); + if (line) { + setRightPanelData({ + entityType: 'line', + entityId: line.id, + name: line.name, + status: line.status, + medium: line.medium, + lineStyle: line.lineStyle, + type: line.type, + }); + } else if (data) { + setRightPanelData(data); + } + }); + + graph.on('node:contextmenu', ({ e, node }) => { + e.preventDefault(); + const data = node.getData() as Record; + const entityType = data.entityType as string; + + let menuType: 'site' | 'active-device' | 'passive-device' = 'active-device'; + if (entityType === 'site') { + menuType = 'site'; + } else if (entityType === 'device') { + const group = data.group as string; + menuType = group === DeviceGroup.Passive ? 'passive-device' : 'active-device'; + } + + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + type: menuType, + data: data, + }); + }); + + graph.on('edge:contextmenu', ({ e, edge }) => { + e.preventDefault(); + const line = mockData.lines.find((l) => l.id === edge.id); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + type: 'line', + data: line + ? { + entityType: 'line', + entityId: line.id, + name: line.name, + status: line.status, + } + : {}, + }); + }); + + graph.on('blank:contextmenu', ({ e }) => { + e.preventDefault(); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + type: 'blank', + data: {}, + }); + }); + + graph.on('blank:click', () => { + setContextMenu(null); + setRightPanelData(null); + }); + + // Port side recalculation on node move + graph.on('node:moved', () => { + // In a full implementation, we'd recalculate port sides here + }); + + // Show connections panel on edge double click + graph.on('edge:dblclick', ({ edge }) => { + const line = mockData.lines.find((l) => l.id === edge.id); + if (line) { + const portA = mockData.ports.find((p) => p.id === line.portAId); + const portZ = mockData.ports.find((p) => p.id === line.portZId); + const devA = portA + ? mockData.devices.find((d) => d.id === portA.deviceId) + : null; + const devZ = portZ + ? mockData.devices.find((d) => d.id === portZ.deviceId) + : null; + + setConnectionsPanelData({ + line, + portA, + portZ, + deviceA: devA, + deviceZ: devZ, + }); + setConnectionsPanelVisible(true); + } + }); + + return () => { + graph.dispose(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Sync display settings + useEffect(() => { + const graph = useSchemaStore.getState().graph; + if (!graph) return; + + if (displaySettings.showGrid) { + graph.showGrid(); + } else { + graph.hideGrid(); + } + }, [displaySettings.showGrid]); + + return ( +
+
+
+
+ ); +} diff --git a/frontend/src/features/schema/context-menu/ContextMenu.tsx b/frontend/src/features/schema/context-menu/ContextMenu.tsx new file mode 100644 index 0000000..f4fe2eb --- /dev/null +++ b/frontend/src/features/schema/context-menu/ContextMenu.tsx @@ -0,0 +1,139 @@ +import { useEffect, useRef } from 'react'; +import { Dropdown, message } from 'antd'; +import type { MenuProps } from 'antd'; +import { useContextMenu } from '../../../hooks/useContextMenu.ts'; +import { useSchemaStore } from '../../../store/schemaStore.ts'; +import { mockData } from '../../../mock/schemaData.ts'; + +export function ContextMenu() { + const { contextMenu, hideMenu } = useContextMenu(); + const setRightPanelData = useSchemaStore((s) => s.setRightPanelData); + const setConnectionsPanelVisible = useSchemaStore( + (s) => s.setConnectionsPanelVisible, + ); + const setConnectionsPanelData = useSchemaStore( + (s) => s.setConnectionsPanelData, + ); + const triggerRef = useRef(null); + + useEffect(() => { + const handleClick = () => hideMenu(); + document.addEventListener('click', handleClick); + return () => document.removeEventListener('click', handleClick); + }, [hideMenu]); + + if (!contextMenu?.visible) return null; + + const wip = () => message.info('В разработке'); + + const siteMenu: MenuProps['items'] = [ + { key: 'view', label: 'Просмотр карточки', onClick: () => { setRightPanelData(contextMenu.data); hideMenu(); } }, + { key: 'add-device', label: 'Добавить устройство', onClick: wip }, + { key: 'edit', label: 'Редактировать', onClick: wip }, + { type: 'divider' }, + { key: 'delete', label: 'Удалить', danger: true, onClick: wip }, + ]; + + const activeDeviceMenu: MenuProps['items'] = [ + { key: 'view', label: 'Просмотр карточки', onClick: () => { setRightPanelData(contextMenu.data); hideMenu(); } }, + { key: 'connections', label: 'Показать соединения', onClick: () => { + const deviceId = contextMenu.data.entityId as string; + const deviceLines = mockData.lines.filter((l) => { + const portA = mockData.ports.find((p) => p.id === l.portAId); + const portZ = mockData.ports.find((p) => p.id === l.portZId); + return portA?.deviceId === deviceId || portZ?.deviceId === deviceId; + }); + setConnectionsPanelData({ lines: deviceLines, deviceId }); + setConnectionsPanelVisible(true); + hideMenu(); + }}, + { key: 'create-line', label: 'Создать линию', onClick: wip }, + { key: 'copy', label: 'Копировать', onClick: wip }, + { key: 'move', label: 'Переместить на другой сайт', onClick: wip }, + { type: 'divider' }, + { key: 'delete', label: 'Удалить', danger: true, onClick: wip }, + ]; + + const passiveDeviceMenu: MenuProps['items'] = [ + { key: 'view', label: 'Просмотр карточки', onClick: () => { setRightPanelData(contextMenu.data); hideMenu(); } }, + { key: 'connections', label: 'Показать соединения', onClick: () => { + const deviceId = contextMenu.data.entityId as string; + const deviceLines = mockData.lines.filter((l) => { + const portA = mockData.ports.find((p) => p.id === l.portAId); + const portZ = mockData.ports.find((p) => p.id === l.portZId); + return portA?.deviceId === deviceId || portZ?.deviceId === deviceId; + }); + setConnectionsPanelData({ lines: deviceLines, deviceId }); + setConnectionsPanelVisible(true); + hideMenu(); + }}, + { key: 'copy', label: 'Копировать', onClick: wip }, + { type: 'divider' }, + { key: 'delete', label: 'Удалить', danger: true, onClick: wip }, + ]; + + const lineMenu: MenuProps['items'] = [ + { key: 'view', label: 'Просмотр карточки', onClick: () => { setRightPanelData(contextMenu.data); hideMenu(); } }, + { key: 'connections', label: 'Показать соединения', onClick: () => { + const lineId = contextMenu.data.entityId as string; + const line = mockData.lines.find((l) => l.id === lineId); + if (line) { + const portA = mockData.ports.find((p) => p.id === line.portAId); + const portZ = mockData.ports.find((p) => p.id === line.portZId); + const devA = portA ? mockData.devices.find((d) => d.id === portA.deviceId) : null; + const devZ = portZ ? mockData.devices.find((d) => d.id === portZ.deviceId) : null; + setConnectionsPanelData({ line, portA, portZ, deviceA: devA, deviceZ: devZ }); + setConnectionsPanelVisible(true); + } + hideMenu(); + }}, + { key: 'break', label: 'Разорвать линию', onClick: wip }, + { key: 'copy', label: 'Копировать', onClick: wip }, + { type: 'divider' }, + { key: 'delete', label: 'Удалить', danger: true, onClick: wip }, + ]; + + const blankMenu: MenuProps['items'] = [ + { key: 'add-device', label: 'Добавить устройство', onClick: wip }, + { key: 'create-line', label: 'Создать линию', onClick: wip }, + { key: 'paste', label: 'Вставить', onClick: wip }, + { type: 'divider' }, + { key: 'fit', label: 'Уместить на экран', onClick: () => { + const graph = useSchemaStore.getState().graph; + graph?.zoomToFit({ padding: 40 }); + hideMenu(); + }}, + ]; + + const menuMap: Record = { + 'site': siteMenu, + 'active-device': activeDeviceMenu, + 'passive-device': passiveDeviceMenu, + 'line': lineMenu, + 'line-group': lineMenu, + 'blank': blankMenu, + }; + + const items = menuMap[contextMenu.type] ?? blankMenu; + + return ( +
+ { if (!open) hideMenu(); }} + trigger={['contextMenu']} + > +
+ +
+ ); +} diff --git a/frontend/src/features/schema/edges/edgeConfig.ts b/frontend/src/features/schema/edges/edgeConfig.ts new file mode 100644 index 0000000..48fc1dc --- /dev/null +++ b/frontend/src/features/schema/edges/edgeConfig.ts @@ -0,0 +1,76 @@ +import type { EntityStatus } from '../../../types/index.ts'; +import { Medium, LineStyle } from '../../../types/index.ts'; +import { STATUS_COLORS } from '../../../constants/statusColors.ts'; +import { getLineVisualStyle } from '../../../constants/lineStyles.ts'; +import type { GraphEdgeConfig } from '../../../types/graph.ts'; + +export function createEdgeConfig( + id: string, + sourceCell: string, + sourcePort: string, + targetCell: string, + targetPort: string, + status: EntityStatus, + medium: Medium, + lineStyle: LineStyle, + label: string, + routerType: 'manhattan' | 'normal' = 'manhattan', +): GraphEdgeConfig { + const colors = STATUS_COLORS[status]; + const visualStyle = getLineVisualStyle(lineStyle, medium); + + const router = + routerType === 'manhattan' + ? { name: 'manhattan' as const, args: { padding: 20 } } + : { name: 'normal' as const }; + + return { + id, + source: { cell: sourceCell, port: sourcePort }, + target: { cell: targetCell, port: targetPort }, + router, + connector: { name: 'rounded', args: { radius: 8 } }, + attrs: { + line: { + stroke: colors.border, + strokeWidth: visualStyle.strokeWidth, + strokeDasharray: visualStyle.strokeDasharray || undefined, + targetMarker: null, + sourceMarker: null, + }, + wrap: { + fill: 'none', + stroke: 'transparent', + strokeWidth: 20, + }, + }, + labels: label + ? [ + { + attrs: { + label: { + text: label, + fontSize: 9, + fill: '#595959', + textAnchor: 'middle', + textVerticalAnchor: 'middle', + }, + rect: { + ref: 'label', + fill: '#fff', + rx: 3, + ry: 3, + refWidth: '140%', + refHeight: '140%', + refX: '-20%', + refY: '-20%', + }, + }, + position: { distance: 0.5 }, + }, + ] + : [], + data: { status, medium, lineStyle, label }, + zIndex: 5, + }; +} diff --git a/frontend/src/features/schema/edges/edgeGrouping.ts b/frontend/src/features/schema/edges/edgeGrouping.ts new file mode 100644 index 0000000..ac2e7fa --- /dev/null +++ b/frontend/src/features/schema/edges/edgeGrouping.ts @@ -0,0 +1,76 @@ +import type { Line, Port, Device, SchemaData } from '../../../types/index.ts'; +import { STATUS_COLORS, STATUS_LABELS } from '../../../constants/statusColors.ts'; + +export interface LineGroup { + key: string; + deviceAId: string; + deviceZId: string; + lines: Line[]; + representativeLine: Line; + count: number; +} + +function getDeviceIdForPort( + portId: string, + data: SchemaData, +): string | null { + const port = data.ports.find((p: Port) => p.id === portId); + return port ? port.deviceId : null; +} + +export function groupLinesByDevicePair(data: SchemaData): LineGroup[] { + const groupMap = new Map(); + + for (const line of data.lines) { + const devA = getDeviceIdForPort(line.portAId, data); + const devZ = getDeviceIdForPort(line.portZId, data); + if (!devA || !devZ) continue; + + const key = [devA, devZ].sort().join('::'); + const existing = groupMap.get(key); + if (existing) { + existing.push(line); + } else { + groupMap.set(key, [line]); + } + } + + const groups: LineGroup[] = []; + for (const [key, lines] of groupMap.entries()) { + const [deviceAId, deviceZId] = key.split('::'); + groups.push({ + key, + deviceAId, + deviceZId, + lines, + representativeLine: lines[0], + count: lines.length, + }); + } + + return groups; +} + +export function getGroupTooltip( + group: LineGroup, + devices: Device[], +): string { + const devA = devices.find((d) => d.id === group.deviceAId); + const devZ = devices.find((d) => d.id === group.deviceZId); + + const statusCounts = new Map(); + for (const line of group.lines) { + const current = statusCounts.get(line.status) ?? 0; + statusCounts.set(line.status, current + 1); + } + + let tooltip = `${devA?.name ?? '?'} — ${devZ?.name ?? '?'}\n`; + tooltip += `Линий: ${group.count}\n`; + for (const [status, count] of statusCounts.entries()) { + const color = STATUS_COLORS[status as keyof typeof STATUS_COLORS]; + const label = STATUS_LABELS[status as keyof typeof STATUS_LABELS]; + tooltip += ` ${label}: ${count} (${color.border})\n`; + } + + return tooltip; +} diff --git a/frontend/src/features/schema/graph/initGraph.ts b/frontend/src/features/schema/graph/initGraph.ts new file mode 100644 index 0000000..1653f50 --- /dev/null +++ b/frontend/src/features/schema/graph/initGraph.ts @@ -0,0 +1,207 @@ +import { Graph } from '@antv/x6'; +import { Selection } from '@antv/x6-plugin-selection'; +import { Snapline } from '@antv/x6-plugin-snapline'; +import { MiniMap } from '@antv/x6-plugin-minimap'; +import { Keyboard } from '@antv/x6-plugin-keyboard'; +import { Clipboard } from '@antv/x6-plugin-clipboard'; +import { History } from '@antv/x6-plugin-history'; + + +export function initGraph( + container: HTMLDivElement, + minimapContainer: HTMLDivElement | null, +): Graph { + const graph = new Graph({ + container, + autoResize: true, + background: { color: '#f8f9fa' }, + grid: { + visible: true, + size: 10, + type: 'dot', + args: { color: '#d9d9d9', thickness: 1 }, + }, + panning: { enabled: true, eventTypes: ['rightMouseDown'] }, + mousewheel: { + enabled: true, + modifiers: ['ctrl', 'meta'], + minScale: 0.1, + maxScale: 3, + }, + connecting: { + snap: { radius: 30 }, + allowBlank: false, + allowLoop: true, + allowNode: false, + allowEdge: false, + allowMulti: true, + allowPort: true, + highlight: true, + router: { name: 'manhattan' }, + connector: { name: 'rounded', args: { radius: 8 } }, + connectionPoint: 'anchor', + anchor: 'center', + sourceAnchor: 'center', + targetAnchor: 'center', + createEdge() { + return this.createEdge({ + attrs: { + line: { + stroke: '#52c41a', + strokeWidth: 2, + targetMarker: null, + sourceMarker: null, + }, + }, + router: { name: 'manhattan' }, + connector: { name: 'rounded', args: { radius: 8 } }, + }); + }, + validateConnection({ sourcePort, targetPort, sourceMagnet, targetMagnet }) { + if (!sourceMagnet || !targetMagnet) return false; + if (!sourcePort || !targetPort) return false; + return sourcePort !== targetPort; + }, + }, + embedding: { + enabled: true, + findParent({ node }) { + // Only site nodes can be parents + const bbox = node.getBBox(); + return this.getNodes().filter((n) => { + if (n.id === node.id) return false; + const data = n.getData<{ entityType?: string }>(); + if (data?.entityType !== 'site') return false; + return n.getBBox().containsRect(bbox); + }); + }, + }, + interacting: { + nodeMovable: true, + edgeMovable: true, + edgeLabelMovable: true, + vertexMovable: true, + vertexAddable: true, + vertexDeletable: true, + }, + highlighting: { + magnetAvailable: { + name: 'stroke', + args: { attrs: { fill: '#5F95FF', stroke: '#5F95FF' } }, + }, + }, + }); + + // Show tools on edge hover: segments, vertices, and arrowheads for reconnecting + graph.on('edge:mouseenter', ({ edge }) => { + edge.addTools([ + { + name: 'segments', + args: { + snapRadius: 20, + attrs: { + fill: '#444', + width: 20, + height: 8, + rx: 4, + ry: 4, + }, + }, + }, + { + name: 'vertices', + args: { + attrs: { + r: 5, + fill: '#fff', + stroke: '#333', + strokeWidth: 1, + cursor: 'move', + }, + snapRadius: 20, + }, + }, + { + name: 'source-arrowhead', + args: { + attrs: { + d: 'M 0 -5 L 10 0 L 0 5 Z', + fill: '#333', + stroke: '#fff', + strokeWidth: 1, + cursor: 'move', + }, + }, + }, + { + name: 'target-arrowhead', + args: { + attrs: { + d: 'M 0 -5 L 10 0 L 0 5 Z', + fill: '#333', + stroke: '#fff', + strokeWidth: 1, + cursor: 'move', + }, + }, + }, + ]); + }); + + graph.on('edge:mouseleave', ({ edge }) => { + // Don't remove tools if edge is being dragged + if (edge.hasTool('source-arrowhead') || edge.hasTool('target-arrowhead')) { + // Delay removal to avoid removing during drag + setTimeout(() => { + if (!document.querySelector('.x6-widget-transform')) { + edge.removeTools(); + } + }, 200); + } else { + edge.removeTools(); + } + }); + + graph.use( + new Selection({ + enabled: true, + multiple: true, + rubberband: true, + movable: true, + showNodeSelectionBox: true, + }), + ); + + graph.use(new Snapline({ enabled: true })); + + if (minimapContainer) { + graph.use( + new MiniMap({ + container: minimapContainer, + width: 200, + height: 150, + padding: 10, + }), + ); + } + + graph.use(new Keyboard({ enabled: true })); + graph.use(new Clipboard({ enabled: true })); + graph.use(new History({ enabled: true })); + + // Keyboard shortcuts + graph.bindKey('ctrl+z', () => graph.undo()); + graph.bindKey('ctrl+shift+z', () => graph.redo()); + graph.bindKey('ctrl+c', () => graph.copy(graph.getSelectedCells())); + graph.bindKey('ctrl+v', () => graph.paste()); + graph.bindKey('delete', () => { + const cells = graph.getSelectedCells(); + if (cells.length) graph.removeCells(cells); + }); + graph.bindKey('ctrl+a', () => { + const cells = graph.getCells(); + graph.select(cells); + }); + + return graph; +} diff --git a/frontend/src/features/schema/graph/registerNodes.ts b/frontend/src/features/schema/graph/registerNodes.ts new file mode 100644 index 0000000..66a1744 --- /dev/null +++ b/frontend/src/features/schema/graph/registerNodes.ts @@ -0,0 +1,64 @@ +import { register } from '@antv/x6-react-shape'; +import { SiteNode } from '../nodes/SiteNode.tsx'; +import { CrossDeviceNode } from '../nodes/CrossDeviceNode.tsx'; +import { SpliceNode } from '../nodes/SpliceNode.tsx'; +import { DeviceNode } from '../nodes/DeviceNode.tsx'; +import { CardNode } from '../nodes/CardNode.tsx'; +import { + CROSS_WIDTH, + CROSS_HEIGHT, + SPLICE_SIZE, + DEVICE_MIN_WIDTH, + DEVICE_MIN_HEIGHT, + CARD_WIDTH, + CARD_HEIGHT, + SITE_MIN_WIDTH, + SITE_MIN_HEIGHT, +} from '../../../constants/sizes.ts'; +import { portGroups } from '../ports/portConfig.ts'; + +export function registerAllNodes(): void { + register({ + shape: 'site-node', + width: SITE_MIN_WIDTH, + height: SITE_MIN_HEIGHT, + component: SiteNode, + effect: ['data'], + }); + + register({ + shape: 'cross-device-node', + width: CROSS_WIDTH, + height: CROSS_HEIGHT, + component: CrossDeviceNode, + ports: { groups: portGroups }, + effect: ['data'], + }); + + register({ + shape: 'splice-node', + width: SPLICE_SIZE, + height: SPLICE_SIZE, + component: SpliceNode, + ports: { groups: portGroups }, + effect: ['data'], + }); + + register({ + shape: 'device-node', + width: DEVICE_MIN_WIDTH, + height: DEVICE_MIN_HEIGHT, + component: DeviceNode, + ports: { groups: portGroups }, + effect: ['data'], + }); + + register({ + shape: 'card-node', + width: CARD_WIDTH, + height: CARD_HEIGHT, + component: CardNode, + ports: { groups: portGroups }, + effect: ['data'], + }); +} diff --git a/frontend/src/features/schema/helpers/dataMapper.ts b/frontend/src/features/schema/helpers/dataMapper.ts new file mode 100644 index 0000000..d500ad6 --- /dev/null +++ b/frontend/src/features/schema/helpers/dataMapper.ts @@ -0,0 +1,283 @@ +import type { SchemaData } from '../../../types/index.ts'; +import { DeviceCategory } from '../../../types/index.ts'; +import type { GraphNodeConfig, GraphEdgeConfig, GraphBuildResult } from '../../../types/graph.ts'; +import { createPortItem } from '../ports/portConfig.ts'; +import { createEdgeConfig } from '../edges/edgeConfig.ts'; +import { autoLayout, type LayoutResult } from '../layout/autoLayout.ts'; +import { resolvePortSides } from './portSideResolver.ts'; +import { + CROSS_WIDTH, + CROSS_HEIGHT, + SPLICE_SIZE, + DEVICE_MIN_WIDTH, + DEVICE_MIN_HEIGHT, + CARD_WIDTH, + CARD_HEIGHT, +} from '../../../constants/sizes.ts'; + +function getDeviceShape(category: DeviceCategory, deviceName: string, marking: string): string { + if ( + category === DeviceCategory.CrossOptical || + category === DeviceCategory.CrossCopper + ) { + return 'cross-device-node'; + } + if ( + deviceName.toLowerCase().includes('муфта') || + marking.toLowerCase().includes('мток') + ) { + return 'splice-node'; + } + return 'device-node'; +} + +function getDeviceSize( + category: DeviceCategory, + deviceName: string, + marking: string, + portCount: number, +): { width: number; height: number } { + if ( + category === DeviceCategory.CrossOptical || + category === DeviceCategory.CrossCopper + ) { + return { width: CROSS_WIDTH, height: CROSS_HEIGHT }; + } + if ( + deviceName.toLowerCase().includes('муфта') || + marking.toLowerCase().includes('мток') + ) { + return { width: SPLICE_SIZE, height: SPLICE_SIZE }; + } + const portHeight = Math.max(portCount * 22, 60); + return { + width: DEVICE_MIN_WIDTH, + height: Math.max(DEVICE_MIN_HEIGHT, portHeight + 30), + }; +} + +export function buildGraphData( + data: SchemaData, + routerType: 'manhattan' | 'normal' = 'manhattan', +): GraphBuildResult { + const layout: LayoutResult = autoLayout(data); + const nodes: GraphNodeConfig[] = []; + const edges: GraphEdgeConfig[] = []; + + // Resolve port sides based on device positions + const devicePositions = new Map(); + for (const [id, pos] of layout.nodePositions.entries()) { + devicePositions.set(id, { x: pos.x, y: pos.y }); + } + const portSideMap = resolvePortSides(data, devicePositions); + + // 1. Create site nodes + for (const site of data.sites) { + const sitePos = layout.sitePositions.get(site.id); + if (!sitePos) continue; + + nodes.push({ + id: site.id, + shape: 'site-node', + x: sitePos.x, + y: sitePos.y, + width: sitePos.width, + height: sitePos.height, + data: { + name: site.name, + address: site.address, + erpCode: site.erpCode, + code1C: site.code1C, + status: site.status, + entityType: 'site', + entityId: site.id, + }, + zIndex: 1, + }); + } + + // 2. Create device nodes with ports + for (const device of data.devices) { + const pos = layout.nodePositions.get(device.id); + if (!pos) continue; + + const devicePorts = data.ports.filter( + (p) => p.deviceId === device.id && !p.cardId, + ); + const shape = getDeviceShape(device.category, device.name, device.marking); + const size = getDeviceSize(device.category, device.name, device.marking, devicePorts.length); + + const portItems = devicePorts.map((port) => { + const resolvedSide = portSideMap.get(port.id) ?? port.side; + const label = port.slotName ? `${port.slotName}:${port.name}` : port.name; + return createPortItem(port.id, resolvedSide, label, port.labelColor || undefined); + }); + + const node: GraphNodeConfig = { + id: device.id, + shape, + x: pos.x, + y: pos.y, + width: size.width, + height: size.height, + data: { + name: device.name, + networkName: device.networkName, + ipAddress: device.ipAddress, + marking: device.marking, + id1: device.id1, + id2: device.id2, + group: device.group, + category: device.category, + status: device.status, + entityType: 'device', + entityId: device.id, + }, + ports: { + groups: { + left: { + position: 'left', + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#8c8c8c', + strokeWidth: 1, + fill: '#fff', + }, + }, + label: { + position: { name: 'left', args: { x: -8 } }, + }, + }, + right: { + position: 'right', + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#8c8c8c', + strokeWidth: 1, + fill: '#fff', + }, + }, + label: { + position: { name: 'right', args: { x: 8 } }, + }, + }, + }, + items: portItems, + }, + parent: device.siteId, + zIndex: 2, + }; + + nodes.push(node); + } + + // 3. Create card nodes (embedded inside devices) + for (const card of data.cards) { + if (!card.visible) continue; + + const parentDevice = data.devices.find((d) => d.id === card.deviceId); + if (!parentDevice) continue; + + const parentPos = layout.nodePositions.get(card.deviceId); + if (!parentPos) continue; + + const cardPorts = data.ports.filter((p) => p.cardId === card.id); + const cardIndex = data.cards.filter( + (c) => c.deviceId === card.deviceId && c.visible, + ).indexOf(card); + + const cardX = parentPos.x + 10; + const cardY = parentPos.y + 40 + cardIndex * (CARD_HEIGHT + 6); + + const portItems = cardPorts.map((port) => { + const resolvedSide = portSideMap.get(port.id) ?? port.side; + const label = `${port.slotName}:${port.name}`; + return createPortItem(port.id, resolvedSide, label, port.labelColor || undefined); + }); + + nodes.push({ + id: card.id, + shape: 'card-node', + x: cardX, + y: cardY, + width: CARD_WIDTH, + height: CARD_HEIGHT, + data: { + slotName: card.slotName, + networkName: card.networkName, + status: card.status, + entityType: 'card', + entityId: card.id, + }, + ports: { + groups: { + left: { + position: 'left', + attrs: { + circle: { + r: 5, + magnet: true, + stroke: '#8c8c8c', + strokeWidth: 1, + fill: '#fff', + }, + }, + label: { + position: { name: 'left', args: { x: -6 } }, + }, + }, + right: { + position: 'right', + attrs: { + circle: { + r: 5, + magnet: true, + stroke: '#8c8c8c', + strokeWidth: 1, + fill: '#fff', + }, + }, + label: { + position: { name: 'right', args: { x: 6 } }, + }, + }, + }, + items: portItems, + }, + parent: card.deviceId, + zIndex: 3, + }); + } + + // 4. Create edges from lines + for (const line of data.lines) { + const portA = data.ports.find((p) => p.id === line.portAId); + const portZ = data.ports.find((p) => p.id === line.portZId); + if (!portA || !portZ) continue; + + // Determine source and target cells + const sourceCellId = portA.cardId ?? portA.deviceId; + const targetCellId = portZ.cardId ?? portZ.deviceId; + + edges.push( + createEdgeConfig( + line.id, + sourceCellId, + line.portAId, + targetCellId, + line.portZId, + line.status, + line.medium, + line.lineStyle, + line.name, + routerType, + ), + ); + } + + return { nodes, edges }; +} diff --git a/frontend/src/features/schema/helpers/portSideResolver.ts b/frontend/src/features/schema/helpers/portSideResolver.ts new file mode 100644 index 0000000..45d6764 --- /dev/null +++ b/frontend/src/features/schema/helpers/portSideResolver.ts @@ -0,0 +1,64 @@ +import type { SchemaData, Port } from '../../../types/index.ts'; +import { DeviceCategory } from '../../../types/index.ts'; + +interface DevicePosition { + x: number; + y: number; +} + +export function resolvePortSides( + data: SchemaData, + devicePositions: Map, +): Map { + const portSideMap = new Map(); + const device = (id: string) => data.devices.find((d) => d.id === id); + + for (const port of data.ports) { + const dev = device(port.deviceId); + if (!dev) { + portSideMap.set(port.id, port.side); + continue; + } + + // Cross devices: L ports → left, S ports → right (hardcoded) + if ( + dev.category === DeviceCategory.CrossOptical || + dev.category === DeviceCategory.CrossCopper + ) { + const side = port.slotName === 'L' || port.name.startsWith('L') ? 'left' : 'right'; + portSideMap.set(port.id, side); + continue; + } + + // For other devices, determine side based on connected device position + const connectedLine = data.lines.find( + (l) => l.portAId === port.id || l.portZId === port.id, + ); + + if (!connectedLine) { + portSideMap.set(port.id, port.side); + continue; + } + + const otherPortId = + connectedLine.portAId === port.id + ? connectedLine.portZId + : connectedLine.portAId; + const otherPort = data.ports.find((p: Port) => p.id === otherPortId); + if (!otherPort) { + portSideMap.set(port.id, port.side); + continue; + } + + const thisPos = devicePositions.get(port.deviceId); + const otherPos = devicePositions.get(otherPort.deviceId); + + if (thisPos && otherPos) { + portSideMap.set(port.id, otherPos.x < thisPos.x ? 'left' : 'right'); + } else { + portSideMap.set(port.id, port.side); + } + } + + return portSideMap; +} diff --git a/frontend/src/features/schema/layout/autoLayout.ts b/frontend/src/features/schema/layout/autoLayout.ts new file mode 100644 index 0000000..34d37d5 --- /dev/null +++ b/frontend/src/features/schema/layout/autoLayout.ts @@ -0,0 +1,193 @@ +import type { Device, Site, SchemaData } from '../../../types/index.ts'; +import { DeviceCategory } from '../../../types/index.ts'; +import { LAYER_MAPPING, MAX_CROSS_PER_LAYER } from '../../../constants/layerMapping.ts'; +import { + CROSS_WIDTH, + CROSS_HEIGHT, + SPLICE_SIZE, + DEVICE_MIN_WIDTH, + DEVICE_MIN_HEIGHT, + SITE_HEADER_HEIGHT, + SITE_PADDING, + SITE_MIN_WIDTH, + LAYER_GAP, + DEVICE_GAP, + LAYER_PADDING_X, +} from '../../../constants/sizes.ts'; + +export interface LayoutResult { + nodePositions: Map; + sitePositions: Map; +} + +function getDeviceSize(device: Device, portCount: number): { width: number; height: number } { + if ( + device.category === DeviceCategory.CrossOptical || + device.category === DeviceCategory.CrossCopper + ) { + return { width: CROSS_WIDTH, height: CROSS_HEIGHT }; + } + + // Splice device detection by name/marking + if (device.name.toLowerCase().includes('муфта') || device.marking.toLowerCase().includes('мток')) { + return { width: SPLICE_SIZE, height: SPLICE_SIZE }; + } + + // Dynamic height based on port count + const portHeight = Math.max(portCount * 22, 60); + return { + width: DEVICE_MIN_WIDTH, + height: Math.max(DEVICE_MIN_HEIGHT, portHeight + 30), + }; +} + +function getLayerForDevice(device: Device): number { + for (const [layer, categories] of Object.entries(LAYER_MAPPING)) { + if (categories.includes(device.category)) { + return parseInt(layer, 10); + } + } + return 7; // default to "unknown" layer +} + +function getSiteDevices(siteId: string, data: SchemaData): Device[] { + return data.devices.filter((d) => d.siteId === siteId); +} + +function layoutDevicesInSite( + devices: Device[], + data: SchemaData, + startX: number, + startY: number, +): { positions: Map; totalWidth: number; totalHeight: number } { + const positions = new Map(); + + // Assign devices to layers + const layers = new Map(); + for (const device of devices) { + const layer = getLayerForDevice(device); + const portCount = data.ports.filter((p) => p.deviceId === device.id && !p.cardId).length; + const size = getDeviceSize(device, portCount); + const existing = layers.get(layer); + if (existing) { + existing.push({ device, size }); + } else { + layers.set(layer, [{ device, size }]); + } + } + + // Handle overflow: layer 1 with >MAX_CROSS crosses → overflow to layer 8 + const layer1 = layers.get(1); + if (layer1 && layer1.length > MAX_CROSS_PER_LAYER) { + const overflow = layer1.splice(MAX_CROSS_PER_LAYER); + const layer8 = layers.get(8) ?? []; + layer8.push(...overflow); + layers.set(8, layer8); + } + + // Calculate layer Y positions (skip empty layers) + const sortedLayers = Array.from(layers.entries()).sort((a, b) => a[0] - b[0]); + + let currentY = startY; + let maxWidth = 0; + + for (const [, layerDevices] of sortedLayers) { + let currentX = startX + LAYER_PADDING_X; + let layerMaxHeight = 0; + + for (const { device, size } of layerDevices) { + positions.set(device.id, { + x: currentX, + y: currentY, + width: size.width, + height: size.height, + }); + currentX += size.width + DEVICE_GAP; + layerMaxHeight = Math.max(layerMaxHeight, size.height); + } + + maxWidth = Math.max(maxWidth, currentX - startX); + currentY += layerMaxHeight + LAYER_GAP; + } + + const totalHeight = currentY - startY; + const totalWidth = Math.max(maxWidth + LAYER_PADDING_X, SITE_MIN_WIDTH); + + return { positions, totalWidth, totalHeight }; +} + +export function autoLayout(data: SchemaData): LayoutResult { + const nodePositions = new Map(); + const sitePositions = new Map(); + + // Separate root sites and child sites + const rootSites = data.sites.filter((s: Site) => !s.parentSiteId); + const childSites = data.sites.filter((s: Site) => s.parentSiteId); + + let siteX = 50; + + for (const site of rootSites) { + const siteDevices = getSiteDevices(site.id, data); + + const contentStartY = SITE_HEADER_HEIGHT + SITE_PADDING; + const { positions, totalWidth, totalHeight } = layoutDevicesInSite( + siteDevices, + data, + siteX + SITE_PADDING, + contentStartY + 50, // offset for site positioning + ); + + // Add device positions + for (const [deviceId, pos] of positions.entries()) { + nodePositions.set(deviceId, pos); + } + + // Handle child sites at bottom of parent + const siteChildren = childSites.filter((cs) => cs.parentSiteId === site.id); + let childSiteExtraHeight = 0; + let childX = siteX + SITE_PADDING; + + for (const childSite of siteChildren) { + const childDevices = getSiteDevices(childSite.id, data); + const childContentStartY = contentStartY + totalHeight + 50; + + const childLayout = layoutDevicesInSite( + childDevices, + data, + childX + SITE_PADDING, + childContentStartY + SITE_HEADER_HEIGHT + SITE_PADDING, + ); + + for (const [deviceId, pos] of childLayout.positions.entries()) { + nodePositions.set(deviceId, pos); + } + + const childSiteWidth = Math.max(childLayout.totalWidth + SITE_PADDING * 2, 250); + const childSiteHeight = childLayout.totalHeight + SITE_HEADER_HEIGHT + SITE_PADDING * 2; + + sitePositions.set(childSite.id, { + x: childX, + y: childContentStartY, + width: childSiteWidth, + height: childSiteHeight, + }); + + childX += childSiteWidth + DEVICE_GAP; + childSiteExtraHeight = Math.max(childSiteExtraHeight, childSiteHeight + SITE_PADDING); + } + + const siteWidth = Math.max(totalWidth + SITE_PADDING * 2, childX - siteX); + const siteHeight = totalHeight + contentStartY + childSiteExtraHeight + SITE_PADDING; + + sitePositions.set(site.id, { + x: siteX, + y: 50, + width: siteWidth, + height: siteHeight, + }); + + siteX += siteWidth + 80; + } + + return { nodePositions, sitePositions }; +} diff --git a/frontend/src/features/schema/nodes/CardNode.tsx b/frontend/src/features/schema/nodes/CardNode.tsx new file mode 100644 index 0000000..2731ce7 --- /dev/null +++ b/frontend/src/features/schema/nodes/CardNode.tsx @@ -0,0 +1,44 @@ +import type { Node } from '@antv/x6'; +import { STATUS_COLORS } from '../../../constants/statusColors.ts'; +import type { EntityStatus } from '../../../types/index.ts'; + +interface CardNodeData { + slotName: string; + networkName: string; + status: EntityStatus; +} + +export function CardNode({ node }: { node: Node }) { + const data = node.getData() as CardNodeData; + const colors = STATUS_COLORS[data.status]; + const size = node.getSize(); + + return ( +
+ {data.slotName}:{data.networkName} +
+ ); +} diff --git a/frontend/src/features/schema/nodes/CrossDeviceNode.tsx b/frontend/src/features/schema/nodes/CrossDeviceNode.tsx new file mode 100644 index 0000000..5bdff44 --- /dev/null +++ b/frontend/src/features/schema/nodes/CrossDeviceNode.tsx @@ -0,0 +1,83 @@ +import type { Node } from '@antv/x6'; +import { STATUS_COLORS } from '../../../constants/statusColors.ts'; +import { CROSS_BORDER_RADIUS } from '../../../constants/sizes.ts'; +import type { EntityStatus } from '../../../types/index.ts'; + +interface CrossDeviceData { + name: string; + networkName: string; + marking: string; + id1: string; + id2: string; + status: EntityStatus; +} + +export function CrossDeviceNode({ node }: { node: Node }) { + const data = node.getData() as CrossDeviceData; + const colors = STATUS_COLORS[data.status]; + const size = node.getSize(); + const r = CROSS_BORDER_RADIUS; + const w = size.width; + const h = size.height; + + // Asymmetric rounding: top-left and bottom-right rounded + const path = ` + M ${r} 0 + L ${w} 0 + L ${w} ${h - r} + Q ${w} ${h} ${w - r} ${h} + L 0 ${h} + L 0 ${r} + Q 0 0 ${r} 0 + Z + `; + + return ( +
+ + + +
+
+ {data.name} +
+ {data.networkName && ( +
{data.networkName}
+ )} + {data.marking && ( +
{data.marking}
+ )} + {data.id1 && ( +
{data.id1}
+ )} +
+
+ ); +} diff --git a/frontend/src/features/schema/nodes/DeviceNode.tsx b/frontend/src/features/schema/nodes/DeviceNode.tsx new file mode 100644 index 0000000..77e33ba --- /dev/null +++ b/frontend/src/features/schema/nodes/DeviceNode.tsx @@ -0,0 +1,80 @@ +import type { Node } from '@antv/x6'; +import { STATUS_COLORS } from '../../../constants/statusColors.ts'; +import { DEVICE_BORDER_RADIUS } from '../../../constants/sizes.ts'; +import { DeviceGroup, type EntityStatus } from '../../../types/index.ts'; + +interface DeviceNodeData { + name: string; + networkName: string; + ipAddress: string; + marking: string; + id1: string; + id2: string; + group: DeviceGroup; + status: EntityStatus; +} + +export function DeviceNode({ node }: { node: Node }) { + const data = node.getData() as DeviceNodeData; + const colors = STATUS_COLORS[data.status]; + const size = node.getSize(); + const isActive = data.group === DeviceGroup.Active; + + return ( +
+
+ {data.name} +
+ {isActive ? ( + <> + {data.networkName && ( +
{data.networkName}
+ )} + {data.ipAddress && ( +
{data.ipAddress}
+ )} + + ) : ( + <> + {data.marking && ( +
{data.marking}
+ )} + {data.id1 && ( +
{data.id1}
+ )} + {data.id2 && ( +
{data.id2}
+ )} + + )} +
+ ); +} diff --git a/frontend/src/features/schema/nodes/SiteNode.tsx b/frontend/src/features/schema/nodes/SiteNode.tsx new file mode 100644 index 0000000..eb8bf27 --- /dev/null +++ b/frontend/src/features/schema/nodes/SiteNode.tsx @@ -0,0 +1,60 @@ +import type { Node } from '@antv/x6'; +import { STATUS_COLORS } from '../../../constants/statusColors.ts'; +import { SITE_HEADER_HEIGHT } from '../../../constants/sizes.ts'; +import type { EntityStatus } from '../../../types/index.ts'; + +interface SiteNodeData { + name: string; + address: string; + erpCode: string; + code1C: string; + status: EntityStatus; +} + +export function SiteNode({ node }: { node: Node }) { + const data = node.getData() as SiteNodeData; + const colors = STATUS_COLORS[data.status]; + const size = node.getSize(); + + return ( +
+
+
+ {data.name} +
+
+ {data.address} +
+
+ ERP: {data.erpCode} | 1С: {data.code1C} +
+
+
+ ); +} diff --git a/frontend/src/features/schema/nodes/SpliceNode.tsx b/frontend/src/features/schema/nodes/SpliceNode.tsx new file mode 100644 index 0000000..0b9b668 --- /dev/null +++ b/frontend/src/features/schema/nodes/SpliceNode.tsx @@ -0,0 +1,46 @@ +import type { Node } from '@antv/x6'; +import { STATUS_COLORS } from '../../../constants/statusColors.ts'; +import { SPLICE_BORDER_RADIUS } from '../../../constants/sizes.ts'; +import type { EntityStatus } from '../../../types/index.ts'; + +interface SpliceNodeData { + name: string; + marking: string; + id1: string; + id2: string; + status: EntityStatus; +} + +export function SpliceNode({ node }: { node: Node }) { + const data = node.getData() as SpliceNodeData; + const colors = STATUS_COLORS[data.status]; + const size = node.getSize(); + + return ( +
+
+ {data.name} +
+ {data.marking && ( +
{data.marking}
+ )} +
+ ); +} diff --git a/frontend/src/features/schema/ports/portConfig.ts b/frontend/src/features/schema/ports/portConfig.ts new file mode 100644 index 0000000..5ec6cc6 --- /dev/null +++ b/frontend/src/features/schema/ports/portConfig.ts @@ -0,0 +1,63 @@ +import { PORT_RADIUS } from '../../../constants/sizes.ts'; + +export const portGroups = { + left: { + position: 'left', + attrs: { + circle: { + r: PORT_RADIUS, + magnet: true, + stroke: '#8c8c8c', + strokeWidth: 1, + fill: '#fff', + }, + }, + label: { + position: { + name: 'left', + args: { x: -8, y: 0 }, + }, + }, + }, + right: { + position: 'right', + attrs: { + circle: { + r: PORT_RADIUS, + magnet: true, + stroke: '#8c8c8c', + strokeWidth: 1, + fill: '#fff', + }, + }, + label: { + position: { + name: 'right', + args: { x: 8, y: 0 }, + }, + }, + }, +}; + +export function createPortItem( + portId: string, + side: 'left' | 'right', + label: string, + labelColor?: string, +) { + return { + id: portId, + group: side, + attrs: { + text: { + text: label, + fontSize: 8, + fill: labelColor || '#595959', + }, + circle: { + fill: labelColor || '#fff', + stroke: labelColor || '#8c8c8c', + }, + }, + }; +} diff --git a/frontend/src/hooks/useContextMenu.ts b/frontend/src/hooks/useContextMenu.ts new file mode 100644 index 0000000..f0d12b5 --- /dev/null +++ b/frontend/src/hooks/useContextMenu.ts @@ -0,0 +1,21 @@ +import { useCallback } from 'react'; +import { useSchemaStore } from '../store/schemaStore.ts'; +import type { ContextMenuState } from '../store/schemaStore.ts'; + +export function useContextMenu() { + const contextMenu = useSchemaStore((state) => state.contextMenu); + const setContextMenu = useSchemaStore((state) => state.setContextMenu); + + const showMenu = useCallback( + (menu: Omit) => { + setContextMenu({ ...menu, visible: true }); + }, + [setContextMenu], + ); + + const hideMenu = useCallback(() => { + setContextMenu(null); + }, [setContextMenu]); + + return { contextMenu, showMenu, hideMenu }; +} diff --git a/frontend/src/hooks/useGraph.ts b/frontend/src/hooks/useGraph.ts new file mode 100644 index 0000000..a19a61c --- /dev/null +++ b/frontend/src/hooks/useGraph.ts @@ -0,0 +1,5 @@ +import { useSchemaStore } from '../store/schemaStore.ts'; + +export function useGraph() { + return useSchemaStore((state) => state.graph); +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..62b65cb --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,12 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #root { + width: 100%; + height: 100%; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..6a0a2d0 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,5 @@ +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App.tsx'; + +createRoot(document.getElementById('root')!).render(); diff --git a/frontend/src/mock/schemaData.ts b/frontend/src/mock/schemaData.ts new file mode 100644 index 0000000..4956a10 --- /dev/null +++ b/frontend/src/mock/schemaData.ts @@ -0,0 +1,315 @@ +import type { SchemaData } from '../types/index.ts'; +import { + EntityStatus, + DeviceGroup, + DeviceCategory, + Medium, + LineStyle, + LineType, +} from '../types/index.ts'; + +export const mockData: SchemaData = { + sites: [ + { + id: 'site-1', + name: 'Узел связи Центральный', + address: 'ул. Ленина, 1', + erpCode: 'ERP-001', + code1C: '1C-001', + status: EntityStatus.Active, + parentSiteId: null, + }, + { + id: 'site-2', + name: 'Узел связи Северный', + address: 'ул. Мира, 15', + erpCode: 'ERP-002', + code1C: '1C-002', + status: EntityStatus.Active, + parentSiteId: null, + }, + { + id: 'site-1-1', + name: 'Подузел Восточный', + address: 'ул. Ленина, 1, корп. 2', + erpCode: 'ERP-003', + code1C: '1C-003', + status: EntityStatus.Planned, + parentSiteId: 'site-1', + }, + ], + + devices: [ + // Site 1 devices + { + id: 'dev-cross-1', + name: 'Кросс оптический ОРШ-1', + networkName: 'ORS-1', + ipAddress: '', + marking: 'ОРШ-32', + id1: 'INV-001', + id2: 'SN-001', + group: DeviceGroup.Passive, + category: DeviceCategory.CrossOptical, + status: EntityStatus.Active, + siteId: 'site-1', + }, + { + id: 'dev-cross-2', + name: 'Кросс оптический ОРШ-2', + networkName: 'ORS-2', + ipAddress: '', + marking: 'ОРШ-16', + id1: 'INV-002', + id2: 'SN-002', + group: DeviceGroup.Passive, + category: DeviceCategory.CrossOptical, + status: EntityStatus.Reserved, + siteId: 'site-1', + }, + { + id: 'dev-dwdm-1', + name: 'DWDM Huawei OSN 8800', + networkName: 'DWDM-HW-01', + ipAddress: '10.0.1.10', + marking: '', + id1: 'INV-003', + id2: '', + group: DeviceGroup.Active, + category: DeviceCategory.DWDM, + status: EntityStatus.Active, + siteId: 'site-1', + }, + { + id: 'dev-router-1', + name: 'Маршрутизатор Cisco ASR 9000', + networkName: 'RTR-CISCO-01', + ipAddress: '10.0.1.1', + marking: '', + id1: 'INV-004', + id2: '', + group: DeviceGroup.Active, + category: DeviceCategory.IP, + status: EntityStatus.Active, + siteId: 'site-1', + }, + { + id: 'dev-switch-1', + name: 'Коммутатор Huawei S6730', + networkName: 'SW-HW-01', + ipAddress: '10.0.1.2', + marking: '', + id1: 'INV-005', + id2: '', + group: DeviceGroup.Active, + category: DeviceCategory.LanWlan, + status: EntityStatus.Active, + siteId: 'site-1', + }, + { + id: 'dev-server-1', + name: 'Сервер мониторинга', + networkName: 'SRV-MON-01', + ipAddress: '10.0.1.100', + marking: '', + id1: 'INV-006', + id2: '', + group: DeviceGroup.Active, + category: DeviceCategory.Server, + status: EntityStatus.Active, + siteId: 'site-1', + }, + // Site 1-1 (child site) device + { + id: 'dev-splice-1', + name: 'Муфта МТОК-96', + networkName: '', + ipAddress: '', + marking: 'МТОК-96', + id1: 'INV-007', + id2: 'SN-007', + group: DeviceGroup.Passive, + category: DeviceCategory.Unknown, + status: EntityStatus.Active, + siteId: 'site-1-1', + }, + // Site 2 devices + { + id: 'dev-cross-3', + name: 'Кросс оптический ОРШ-3', + networkName: 'ORS-3', + ipAddress: '', + marking: 'ОРШ-48', + id1: 'INV-008', + id2: 'SN-008', + group: DeviceGroup.Passive, + category: DeviceCategory.CrossOptical, + status: EntityStatus.Active, + siteId: 'site-2', + }, + { + id: 'dev-olt-1', + name: 'OLT Huawei MA5800', + networkName: 'OLT-HW-01', + ipAddress: '10.0.2.10', + marking: '', + id1: 'INV-009', + id2: '', + group: DeviceGroup.Active, + category: DeviceCategory.IP, + status: EntityStatus.UnderConstruction, + siteId: 'site-2', + }, + { + id: 'dev-xdsl-1', + name: 'DSLAM ZTE C300', + networkName: 'DSLAM-ZTE-01', + ipAddress: '10.0.2.20', + marking: '', + id1: 'INV-010', + id2: '', + group: DeviceGroup.Active, + category: DeviceCategory.xDSL, + status: EntityStatus.Faulty, + siteId: 'site-2', + }, + ], + + cards: [ + { + id: 'card-1', + slotName: '1', + networkName: 'TN12ST2', + status: EntityStatus.Active, + visible: true, + deviceId: 'dev-dwdm-1', + }, + { + id: 'card-2', + slotName: '2', + networkName: 'TN11LSX', + status: EntityStatus.Active, + visible: true, + deviceId: 'dev-dwdm-1', + }, + { + id: 'card-3', + slotName: '1', + networkName: 'RSP-480', + status: EntityStatus.Active, + visible: true, + deviceId: 'dev-router-1', + }, + ], + + ports: [ + // Cross 1 ports (L = left, S = right) + { id: 'p-c1-l1', name: 'L1', slotName: 'L', side: 'left', sortOrder: 1, labelColor: '#1890ff', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-l2', name: 'L2', slotName: 'L', side: 'left', sortOrder: 2, labelColor: '#1890ff', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-l3', name: 'L3', slotName: 'L', side: 'left', sortOrder: 3, labelColor: '#1890ff', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-l4', name: 'L4', slotName: 'L', side: 'left', sortOrder: 4, labelColor: '#1890ff', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-s1', name: 'S1', slotName: 'S', side: 'right', sortOrder: 1, labelColor: '#52c41a', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-s2', name: 'S2', slotName: 'S', side: 'right', sortOrder: 2, labelColor: '#52c41a', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-s3', name: 'S3', slotName: 'S', side: 'right', sortOrder: 3, labelColor: '#52c41a', deviceId: 'dev-cross-1', cardId: null }, + { id: 'p-c1-s4', name: 'S4', slotName: 'S', side: 'right', sortOrder: 4, labelColor: '#52c41a', deviceId: 'dev-cross-1', cardId: null }, + + // Cross 2 ports + { id: 'p-c2-l1', name: 'L1', slotName: 'L', side: 'left', sortOrder: 1, labelColor: '#1890ff', deviceId: 'dev-cross-2', cardId: null }, + { id: 'p-c2-l2', name: 'L2', slotName: 'L', side: 'left', sortOrder: 2, labelColor: '#1890ff', deviceId: 'dev-cross-2', cardId: null }, + { id: 'p-c2-s1', name: 'S1', slotName: 'S', side: 'right', sortOrder: 1, labelColor: '#52c41a', deviceId: 'dev-cross-2', cardId: null }, + { id: 'p-c2-s2', name: 'S2', slotName: 'S', side: 'right', sortOrder: 2, labelColor: '#52c41a', deviceId: 'dev-cross-2', cardId: null }, + + // DWDM card-1 ports + { id: 'p-dw-c1-1', name: 'IN', slotName: '1', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-dwdm-1', cardId: 'card-1' }, + { id: 'p-dw-c1-2', name: 'OUT', slotName: '1', side: 'right', sortOrder: 2, labelColor: '', deviceId: 'dev-dwdm-1', cardId: 'card-1' }, + // DWDM card-2 ports + { id: 'p-dw-c2-1', name: 'IN', slotName: '2', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-dwdm-1', cardId: 'card-2' }, + { id: 'p-dw-c2-2', name: 'OUT', slotName: '2', side: 'right', sortOrder: 2, labelColor: '', deviceId: 'dev-dwdm-1', cardId: 'card-2' }, + // DWDM device-level ports + { id: 'p-dw-1', name: 'Ge0/0/1', slotName: '0', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-dwdm-1', cardId: null }, + { id: 'p-dw-2', name: 'Ge0/0/2', slotName: '0', side: 'right', sortOrder: 2, labelColor: '', deviceId: 'dev-dwdm-1', cardId: null }, + + // Router card-3 ports + { id: 'p-rtr-c3-1', name: 'Te0/0/0/0', slotName: '1', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-router-1', cardId: 'card-3' }, + { id: 'p-rtr-c3-2', name: 'Te0/0/0/1', slotName: '1', side: 'right', sortOrder: 2, labelColor: '', deviceId: 'dev-router-1', cardId: 'card-3' }, + // Router device-level ports + { id: 'p-rtr-1', name: 'Ge0/0/0', slotName: '0', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-router-1', cardId: null }, + { id: 'p-rtr-2', name: 'Ge0/0/1', slotName: '0', side: 'left', sortOrder: 2, labelColor: '', deviceId: 'dev-router-1', cardId: null }, + { id: 'p-rtr-3', name: 'Ge0/0/2', slotName: '0', side: 'right', sortOrder: 3, labelColor: '', deviceId: 'dev-router-1', cardId: null }, + { id: 'p-rtr-4', name: 'Ge0/0/3', slotName: '0', side: 'right', sortOrder: 4, labelColor: '', deviceId: 'dev-router-1', cardId: null }, + + // Switch ports + { id: 'p-sw-1', name: 'Ge1/0/1', slotName: '1', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-switch-1', cardId: null }, + { id: 'p-sw-2', name: 'Ge1/0/2', slotName: '1', side: 'left', sortOrder: 2, labelColor: '', deviceId: 'dev-switch-1', cardId: null }, + { id: 'p-sw-3', name: 'Ge1/0/3', slotName: '1', side: 'right', sortOrder: 3, labelColor: '', deviceId: 'dev-switch-1', cardId: null }, + { id: 'p-sw-4', name: 'Ge1/0/4', slotName: '1', side: 'right', sortOrder: 4, labelColor: '', deviceId: 'dev-switch-1', cardId: null }, + + // Server ports + { id: 'p-srv-1', name: 'eth0', slotName: '0', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-server-1', cardId: null }, + { id: 'p-srv-2', name: 'eth1', slotName: '0', side: 'right', sortOrder: 2, labelColor: '', deviceId: 'dev-server-1', cardId: null }, + + // Splice ports + { id: 'p-sp-1', name: '1', slotName: '0', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-splice-1', cardId: null }, + { id: 'p-sp-2', name: '2', slotName: '0', side: 'left', sortOrder: 2, labelColor: '', deviceId: 'dev-splice-1', cardId: null }, + { id: 'p-sp-3', name: '3', slotName: '0', side: 'right', sortOrder: 3, labelColor: '', deviceId: 'dev-splice-1', cardId: null }, + { id: 'p-sp-4', name: '4', slotName: '0', side: 'right', sortOrder: 4, labelColor: '', deviceId: 'dev-splice-1', cardId: null }, + + // Cross 3 ports (site 2) + { id: 'p-c3-l1', name: 'L1', slotName: 'L', side: 'left', sortOrder: 1, labelColor: '#1890ff', deviceId: 'dev-cross-3', cardId: null }, + { id: 'p-c3-l2', name: 'L2', slotName: 'L', side: 'left', sortOrder: 2, labelColor: '#1890ff', deviceId: 'dev-cross-3', cardId: null }, + { id: 'p-c3-l3', name: 'L3', slotName: 'L', side: 'left', sortOrder: 3, labelColor: '#1890ff', deviceId: 'dev-cross-3', cardId: null }, + { id: 'p-c3-s1', name: 'S1', slotName: 'S', side: 'right', sortOrder: 1, labelColor: '#52c41a', deviceId: 'dev-cross-3', cardId: null }, + { id: 'p-c3-s2', name: 'S2', slotName: 'S', side: 'right', sortOrder: 2, labelColor: '#52c41a', deviceId: 'dev-cross-3', cardId: null }, + { id: 'p-c3-s3', name: 'S3', slotName: 'S', side: 'right', sortOrder: 3, labelColor: '#52c41a', deviceId: 'dev-cross-3', cardId: null }, + + // OLT ports + { id: 'p-olt-1', name: 'GPON0/0', slotName: '0', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-olt-1', cardId: null }, + { id: 'p-olt-2', name: 'GPON0/1', slotName: '0', side: 'left', sortOrder: 2, labelColor: '', deviceId: 'dev-olt-1', cardId: null }, + { id: 'p-olt-3', name: 'Uplink1', slotName: '0', side: 'right', sortOrder: 3, labelColor: '', deviceId: 'dev-olt-1', cardId: null }, + { id: 'p-olt-4', name: 'Uplink2', slotName: '0', side: 'right', sortOrder: 4, labelColor: '', deviceId: 'dev-olt-1', cardId: null }, + + // xDSL ports + { id: 'p-xdsl-1', name: 'ADSL0/0', slotName: '0', side: 'left', sortOrder: 1, labelColor: '', deviceId: 'dev-xdsl-1', cardId: null }, + { id: 'p-xdsl-2', name: 'ADSL0/1', slotName: '0', side: 'left', sortOrder: 2, labelColor: '', deviceId: 'dev-xdsl-1', cardId: null }, + { id: 'p-xdsl-3', name: 'Uplink1', slotName: '0', side: 'right', sortOrder: 3, labelColor: '', deviceId: 'dev-xdsl-1', cardId: null }, + { id: 'p-xdsl-4', name: 'Uplink2', slotName: '0', side: 'right', sortOrder: 4, labelColor: '', deviceId: 'dev-xdsl-1', cardId: null }, + ], + + lines: [ + // Cross1 L1 → DWDM card-1 IN (optical, active) + { id: 'line-1', name: 'ВОК Центр-DWDM-1', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-c1-s1', portZId: 'p-dw-c1-1' }, + // Cross1 L2 → DWDM card-2 IN (optical, active) + { id: 'line-2', name: 'ВОК Центр-DWDM-2', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-c1-s2', portZId: 'p-dw-c2-1' }, + // DWDM card-1 OUT → Router card-3 IN (optical, active) + { id: 'line-3', name: 'Транзит DWDM-RTR', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-dw-c1-2', portZId: 'p-rtr-c3-1' }, + // Router → Switch (copper, active) + { id: 'line-4', name: 'LAN RTR-SW', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Copper, lineStyle: LineStyle.Solid, portAId: 'p-rtr-3', portZId: 'p-sw-1' }, + // Switch → Server (copper, active) + { id: 'line-5', name: 'LAN SW-SRV', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Copper, lineStyle: LineStyle.Solid, portAId: 'p-sw-3', portZId: 'p-srv-1' }, + // Cross1 L3 → Splice (optical, planned) + { id: 'line-6', name: 'ВОК Центр-Муфта', templateName: '', status: EntityStatus.Planned, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Dashed, portAId: 'p-c1-s3', portZId: 'p-sp-1' }, + // Splice → Cross3 (optical, planned) + { id: 'line-7', name: 'ВОК Муфта-Северный', templateName: '', status: EntityStatus.Planned, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Dashed, portAId: 'p-sp-3', portZId: 'p-c3-l1' }, + // Cross3 → OLT (optical, under construction) + { id: 'line-8', name: 'ВОК Кросс3-OLT', templateName: '', status: EntityStatus.UnderConstruction, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-c3-s1', portZId: 'p-olt-1' }, + // OLT → xDSL (copper, faulty) + { id: 'line-9', name: 'Транзит OLT-DSLAM', templateName: '', status: EntityStatus.Faulty, type: LineType.Simple, medium: Medium.Copper, lineStyle: LineStyle.Solid, portAId: 'p-olt-3', portZId: 'p-xdsl-3' }, + // Cross2 → Router (optical, reserved) + { id: 'line-10', name: 'Резерв Кросс2-RTR', templateName: '', status: EntityStatus.Reserved, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Dotted, portAId: 'p-c2-s1', portZId: 'p-rtr-1' }, + // Router → Router (loopback copper) + { id: 'line-11', name: 'Перемычка RTR', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Copper, lineStyle: LineStyle.Solid, portAId: 'p-rtr-2', portZId: 'p-rtr-4' }, + // Cross1 → Cross2 (optical, active) + { id: 'line-12', name: 'Кросс-кросс', templateName: '', status: EntityStatus.Active, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-c1-s4', portZId: 'p-c2-l1' }, + // Cross1 L4 → Cross3 L2 (optical, decommissioned) + { id: 'line-13', name: 'ВОК Центр-Север (выведен)', templateName: '', status: EntityStatus.Decommissioned, type: LineType.Simple, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-c1-l4', portZId: 'p-c3-l2' }, + // Complex line: Cross3 → xDSL (optical, with fibers) + { id: 'line-14', name: 'Комплекс Кросс3-DSLAM', templateName: 'TEMPLATE-01', status: EntityStatus.Active, type: LineType.Complex, medium: Medium.Optical, lineStyle: LineStyle.Solid, portAId: 'p-c3-s2', portZId: 'p-xdsl-1' }, + // Switch → OLT uplink (copper, unknown) + { id: 'line-15', name: 'Транзит SW-OLT', templateName: '', status: EntityStatus.Unknown, type: LineType.Simple, medium: Medium.Copper, lineStyle: LineStyle.Dotted, portAId: 'p-sw-4', portZId: 'p-olt-4' }, + ], + + fibers: [ + { id: 'fiber-1', name: 'Волокно 1', status: EntityStatus.Active, lineId: 'line-14', portAId: 'p-c3-s2', portZId: 'p-xdsl-1' }, + { id: 'fiber-2', name: 'Волокно 2', status: EntityStatus.Planned, lineId: 'line-14', portAId: 'p-c3-s3', portZId: 'p-xdsl-2' }, + ], +}; diff --git a/frontend/src/store/schemaStore.ts b/frontend/src/store/schemaStore.ts new file mode 100644 index 0000000..76be05f --- /dev/null +++ b/frontend/src/store/schemaStore.ts @@ -0,0 +1,108 @@ +import { create } from 'zustand'; +import type { Graph } from '@antv/x6'; + +export interface DisplaySettings { + showGrid: boolean; + showMinimap: boolean; + lineType: 'manhattan' | 'normal'; + snapToGrid: boolean; + showLabels: boolean; +} + +export interface ContextMenuState { + visible: boolean; + x: number; + y: number; + type: 'site' | 'active-device' | 'passive-device' | 'line' | 'line-group' | 'blank'; + data: Record; +} + +interface SchemaStore { + graph: Graph | null; + mode: 'view' | 'edit'; + selectedElements: string[]; + displaySettings: DisplaySettings; + contextMenu: ContextMenuState | null; + rightPanelData: Record | null; + connectionsPanelData: Record | null; + connectionsPanelVisible: boolean; + legendVisible: boolean; + + setGraph: (graph: Graph) => void; + setMode: (mode: 'view' | 'edit') => void; + setSelectedElements: (elements: string[]) => void; + toggleGrid: () => void; + toggleMinimap: () => void; + switchLineType: () => void; + toggleSnapToGrid: () => void; + toggleLabels: () => void; + setContextMenu: (menu: ContextMenuState | null) => void; + setRightPanelData: (data: Record | null) => void; + setConnectionsPanelData: (data: Record | null) => void; + setConnectionsPanelVisible: (visible: boolean) => void; + setLegendVisible: (visible: boolean) => void; +} + +export const useSchemaStore = create((set) => ({ + graph: null, + mode: 'view', + selectedElements: [], + displaySettings: { + showGrid: true, + showMinimap: true, + lineType: 'manhattan', + snapToGrid: true, + showLabels: true, + }, + contextMenu: null, + rightPanelData: null, + connectionsPanelData: null, + connectionsPanelVisible: false, + legendVisible: false, + + setGraph: (graph) => set({ graph }), + setMode: (mode) => set({ mode }), + setSelectedElements: (elements) => set({ selectedElements: elements }), + toggleGrid: () => + set((state) => ({ + displaySettings: { + ...state.displaySettings, + showGrid: !state.displaySettings.showGrid, + }, + })), + toggleMinimap: () => + set((state) => ({ + displaySettings: { + ...state.displaySettings, + showMinimap: !state.displaySettings.showMinimap, + }, + })), + switchLineType: () => + set((state) => ({ + displaySettings: { + ...state.displaySettings, + lineType: + state.displaySettings.lineType === 'manhattan' ? 'normal' : 'manhattan', + }, + })), + toggleSnapToGrid: () => + set((state) => ({ + displaySettings: { + ...state.displaySettings, + snapToGrid: !state.displaySettings.snapToGrid, + }, + })), + toggleLabels: () => + set((state) => ({ + displaySettings: { + ...state.displaySettings, + showLabels: !state.displaySettings.showLabels, + }, + })), + setContextMenu: (menu) => set({ contextMenu: menu }), + setRightPanelData: (data) => set({ rightPanelData: data }), + setConnectionsPanelData: (data) => set({ connectionsPanelData: data }), + setConnectionsPanelVisible: (visible) => + set({ connectionsPanelVisible: visible }), + setLegendVisible: (visible) => set({ legendVisible: visible }), +})); diff --git a/frontend/src/types/graph.ts b/frontend/src/types/graph.ts new file mode 100644 index 0000000..6e4bfae --- /dev/null +++ b/frontend/src/types/graph.ts @@ -0,0 +1,13 @@ +import type { Node, Edge } from '@antv/x6'; + +export type GraphNodeConfig = Node.Metadata & { parent?: string }; + +export type GraphEdgeConfig = Edge.Metadata; + +export interface GraphBuildResult { + nodes: GraphNodeConfig[]; + edges: GraphEdgeConfig[]; +} + +export type X6Node = Node; +export type X6Edge = Edge; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..4e55a0c --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,136 @@ +export enum EntityStatus { + Active = 'active', + Planned = 'planned', + UnderConstruction = 'under_construction', + Reserved = 'reserved', + Faulty = 'faulty', + Decommissioned = 'decommissioned', + Unknown = 'unknown', +} + +export enum DeviceCategory { + CrossOptical = 'cross_optical', + CrossCopper = 'cross_copper', + RRL = 'rrl', + Wireless = 'wireless', + Satellite = 'satellite', + TSPU = 'tspu', + DWDM = 'dwdm', + MEN = 'men', + SDH = 'sdh', + MultiservicePlatform = 'multiservice_platform', + IP = 'ip', + OpticalModem = 'optical_modem', + OpticalMux = 'optical_mux', + LanWlan = 'lan_wlan', + RanController = 'ran_controller', + MGN = 'mgn', + MGX = 'mgx', + Server = 'server', + SORM = 'sorm', + MOB = 'mob', + FIX = 'fix', + VOIP = 'voip', + xDSL = 'xdsl', + PDH = 'pdh', + RanBaseStation = 'ran_base_station', + Unknown = 'unknown', + VideoSurveillance = 'video_surveillance', +} + +export enum DeviceGroup { + Active = 'active', + Passive = 'passive', +} + +export enum Medium { + Optical = 'optical', + Copper = 'copper', + Wireless = 'wireless', + Unknown = 'unknown', +} + +export enum LineStyle { + Solid = 'solid', + Dashed = 'dashed', + Dotted = 'dotted', +} + +export enum LineType { + Simple = 'simple', + Complex = 'complex', +} + +export interface Site { + id: string; + name: string; + address: string; + erpCode: string; + code1C: string; + status: EntityStatus; + parentSiteId: string | null; +} + +export interface Device { + id: string; + name: string; + networkName: string; + ipAddress: string; + marking: string; + id1: string; + id2: string; + group: DeviceGroup; + category: DeviceCategory; + status: EntityStatus; + siteId: string; +} + +export interface Card { + id: string; + slotName: string; + networkName: string; + status: EntityStatus; + visible: boolean; + deviceId: string; +} + +export interface Port { + id: string; + name: string; + slotName: string; + side: 'left' | 'right'; + sortOrder: number; + labelColor: string; + deviceId: string; + cardId: string | null; +} + +export interface Line { + id: string; + name: string; + templateName: string; + status: EntityStatus; + type: LineType; + medium: Medium; + lineStyle: LineStyle; + portAId: string; + portZId: string; +} + +export interface Fiber { + id: string; + name: string; + status: EntityStatus; + lineId: string; + portAId: string; + portZId: string; +} + +export interface SchemaData { + sites: Site[]; + devices: Device[]; + cards: Card[]; + ports: Port[]; + lines: Line[]; + fibers: Fiber[]; +} diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts new file mode 100644 index 0000000..8d93a8e --- /dev/null +++ b/frontend/src/utils/index.ts @@ -0,0 +1,14 @@ +let counter = 0; + +export function generateId(prefix = 'id'): string { + counter += 1; + return `${prefix}-${Date.now()}-${counter}`; +} + +export function isCrossDevice(category: string): boolean { + return category === 'cross_optical' || category === 'cross_copper'; +} + +export function isSpliceDevice(category: string): boolean { + return category === 'splice'; +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..2f416e5 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +})