import { useMemo, useState } from 'react' import { deviceTemplateToRow } from '../../data/catalogRowMapping' import { allDeviceCategories, allDeviceTemplates, hiddenDeviceTemplates } from '../../domain/project' import type { DeviceTemplate } from '../../domain/types' import { useCatalogStore } from '../../state/catalogStore' import { useProjectStore } from '../../state/projectStore' import { useSubmissionStore } from '../../state/submissionStore' import { TEMPLATE_DRAG_MIME } from '../canvas/FlowCanvas' import Chevron from '../common/Chevron' import ConnectorLibraryModal from './ConnectorLibraryModal' import DeviceTemplateEditor from './DeviceTemplateEditor' type EditorTarget = { mode: 'new' } | { mode: 'edit'; template: DeviceTemplate } | { mode: 'suggestEdit'; template: DeviceTemplate } export default function DevicePalette() { const project = useProjectStore((s) => s.project) const addDeviceFromTemplate = useProjectStore((s) => s.addDeviceFromTemplate) const catalog = useCatalogStore((s) => s.catalog) const hiddenPublicIds = useCatalogStore((s) => s.hiddenPublicDeviceTemplateIds) const removeCustomDeviceTemplate = useCatalogStore((s) => s.removeCustomDeviceTemplate) const hidePublicDeviceTemplate = useCatalogStore((s) => s.hidePublicDeviceTemplate) const restorePublicDeviceTemplate = useCatalogStore((s) => s.restorePublicDeviceTemplate) const mySubmissions = useSubmissionStore((s) => s.mySubmissions) const submitForReview = useSubmissionStore((s) => s.submitForReview) const [editorTarget, setEditorTarget] = useState(null) const [portTypesOpen, setPortTypesOpen] = useState(false) const [hiddenListOpen, setHiddenListOpen] = useState(false) // Collapsed by default — expanding on demand is the whole point (keeps a // library with many devices from turning into one long scroll). const [expandedCategories, setExpandedCategories] = useState>(new Set()) const groups = useMemo(() => { const categories = allDeviceCategories(catalog) const categoryName = (id: string) => categories.find((c) => c.id === id)?.name ?? id const byCategory = new Map() for (const template of allDeviceTemplates(catalog, hiddenPublicIds)) { const list = byCategory.get(template.category) ?? [] list.push(template) byCategory.set(template.category, list) } return [...byCategory.entries()] .map(([categoryId, templates]) => ({ categoryId, categoryName: categoryName(categoryId), templates: templates.sort((a, b) => a.name.localeCompare(b.name)), })) .sort((a, b) => a.categoryName.localeCompare(b.categoryName)) }, [catalog, hiddenPublicIds]) const toggleCategory = (categoryId: string) => { setExpandedCategories((prev) => { const next = new Set(prev) if (next.has(categoryId)) next.delete(categoryId) else next.add(categoryId) return next }) } const expandCategory = (categoryId: string) => { setExpandedCategories((prev) => new Set(prev).add(categoryId)) } const handleDragStart = (event: React.DragEvent, template: DeviceTemplate) => { event.dataTransfer.setData(TEMPLATE_DRAG_MIME, template.id) event.dataTransfer.effectAllowed = 'move' } const handleQuickAdd = (template: DeviceTemplate) => { // Cascade placement so repeated quick-adds don't stack exactly on top of each other. const offset = (project.devices.length % 8) * 24 addDeviceFromTemplate(catalog, template.id, { x: 80 + offset, y: 80 + offset }) } const handleDelete = (template: DeviceTemplate) => { if (!template.custom) { const confirmed = window.confirm( `Hide "${template.name}" from your device library? It's a public default, so you can bring it back later from "Show hidden defaults" at the bottom of this panel. Devices already placed on the canvas from it won't be affected.`, ) if (confirmed) hidePublicDeviceTemplate(template.id) return } const confirmed = window.confirm( `Delete "${template.name}" from your device library? Devices already placed on the canvas from it won't be affected.`, ) if (confirmed) removeCustomDeviceTemplate(template.id) } const hiddenDefaults = useMemo(() => hiddenDeviceTemplates(catalog, hiddenPublicIds), [catalog, hiddenPublicIds]) const pendingEntityIds = useMemo( () => new Set( mySubmissions.filter((s) => s.entityType === 'device_template' && s.status === 'pending').map((s) => s.entityId), ), [mySubmissions], ) const handleSubmitForPublic = (template: DeviceTemplate) => { submitForReview('device_template', template.id, deviceTemplateToRow(template)) } return ( ) }