import React, { useEffect, useRef, useMemo, useState } from 'react'; import { TldrawUiButton } from '@tldraw/tldraw'; import { CCShapesPanel } from './CCShapesPanel'; import { CCSlidesPanel } from './CCSlidesPanel'; import { CCFilesPanel } from './CCFilesPanel'; import { CCCabinetsPanel } from './CCCabinetsPanel'; import { CCYoutubePanel } from './CCYoutubePanel'; import { CCGraphPanel } from './CCGraphPanel'; import { CCSearchPanel } from './CCSearchPanel'; import { CCGraphNavPanel } from './navigation/CCGraphNavPanel'; import { CCTranscriptionPanel } from './CCTranscriptionPanel'; import { PANEL_DIMENSIONS, Z_INDICES } from './panel-styles'; import './panel.css'; // import { CCNavigationPanel } from './navigation/CCNavigationPanel'; import { BaseContext, ViewContext } from '../../../../../types/navigation'; // import { CCNodeSnapshotPanel } from './navigation/CCNodeSnapshotPanel'; import { useTLDraw } from '../../../../../contexts/TLDrawContext'; export const PANEL_TYPES = { default: [ { id: 'cabinets', label: 'Cabinets', order: 5 }, { id: 'navigation', label: 'Navigation', order: 10 }, { id: 'node-snapshot', label: 'Node', order: 20 }, { id: 'files', label: 'Files', order: 25 }, { id: 'cc-shapes', label: 'Shapes', order: 30 }, { id: 'transcription', label: 'Transcription', order: 35 }, { id: 'slides', label: 'Slides', order: 40 }, { id: 'youtube', label: 'YouTube', order: 50 }, { id: 'graph', label: 'Graph', order: 60 }, { id: 'search', label: 'Search', order: 70 }, ], } as const; export type PanelType = typeof PANEL_TYPES.default[number]['id']; interface BasePanelProps { initialPanelType?: PanelType; isExpanded?: boolean; isPinned?: boolean; onExpandedChange?: (expanded: boolean) => void; onPinnedChange?: (pinned: boolean) => void; currentContext?: BaseContext; onContextChange?: (context: BaseContext) => void; currentExtendedContext?: ViewContext; onExtendedContextChange?: (context: ViewContext) => void; isMenuOpen?: boolean; onMenuOpenChange?: (open: boolean) => void; } function getIconSvg(panelId: PanelType, size = '1.25rem') { const common = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const }; switch (panelId) { case 'cabinets': return ( ); case 'transcription': return ( ); case 'cc-shapes': return ( ); case 'slides': return ( ); case 'youtube': return ( ); case 'graph': return ( ); case 'search': return ( ); case 'navigation': return ( ); case 'node-snapshot': return ( ); default: return ( ); } } function isDarkMode() { if (typeof window === 'undefined') return false; return document.documentElement.dataset.colorMode === 'dark' || window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } export const BasePanel: React.FC = ({ initialPanelType = 'files', isExpanded: controlledIsExpanded, isPinned: controlledIsPinned, onExpandedChange, onPinnedChange, isMenuOpen = false, onMenuOpenChange = () => {}, }) => { const { tldrawPreferences } = useTLDraw(); const [prefersDarkMode, setPrefersDarkMode] = useState(isDarkMode()); const [menuOpen, setMenuOpen] = useState(false); const menuRef = useRef(null); useEffect(() => { const mq = window.matchMedia('(prefers-color-scheme: dark)'); const handler = () => setPrefersDarkMode(isDarkMode()); mq.addEventListener?.('change', handler); return () => mq.removeEventListener?.('change', handler); }, []); const colorMode = (tldrawPreferences?.colorScheme === 'system' ? prefersDarkMode : tldrawPreferences?.colorScheme === 'dark') ? 'dark' : 'light'; const availablePanels = PANEL_TYPES.default; const [currentPanelType, setCurrentPanelType] = useState(initialPanelType); const [internalIsExpanded, setInternalIsExpanded] = useState(true); const [internalIsPinned, setInternalIsPinned] = useState(true); const isExpanded = controlledIsExpanded ?? internalIsExpanded; const isPinned = controlledIsPinned ?? internalIsPinned; const handleExpandedChange = (expanded: boolean) => { setInternalIsExpanded(expanded); onExpandedChange?.(expanded); }; const handlePinToggle = () => { const newPinned = !isPinned; setInternalIsPinned(newPinned); onPinnedChange?.(newPinned); }; const panelRef = useRef(null); const dimensions = PANEL_DIMENSIONS[currentPanelType as keyof typeof PANEL_DIMENSIONS]; useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (isPinned) return; const isClickOutside = panelRef.current && !panelRef.current.contains(event.target as Node); const target = event.target as HTMLElement; const isPanelElement = target.closest('.panel-root, .panel-handle, .tlui-button'); if (isClickOutside && !isPanelElement) { handleExpandedChange(false); } }; if (isExpanded) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isExpanded, isPinned]); const renderCurrentPanel = () => { switch (currentPanelType) { case 'cabinets': return ; case 'transcription': return ; case 'files': return ; case 'cc-shapes': return ; case 'slides': return ; case 'youtube': return ; case 'graph': return ; case 'search': return ; case 'navigation': return ; default: return null; } }; const toggleMenu = (open: boolean) => { setMenuOpen(open); onMenuOpenChange(open); }; return ( <> {!isExpanded && ( handleExpandedChange(true)} onTouchEnd={(e) => { e.stopPropagation(); handleExpandedChange(true); }} > › )} {isExpanded && ( toggleMenu(!menuOpen)} > {getIconSvg(currentPanelType)} {availablePanels.find(p => p.id === currentPanelType)?.label} {menuOpen && ( {[...availablePanels] .sort((a, b) => a.order - b.order) .map(type => ( { setCurrentPanelType(type.id as PanelType); toggleMenu(false); }} > {getIconSvg(type.id as PanelType)} {type.label} {type.id === 'cabinets' && 'Manage file cabinets'} {type.id === 'transcription' && 'Record and transcribe lessons'} {type.id === 'cc-shapes' && 'Add shapes and elements to your canvas'} {type.id === 'slides' && 'Manage presentation slides'} {type.id === 'youtube' && 'Embed YouTube videos'} {type.id === 'graph' && 'View and manage graph connections'} {type.id === 'search' && 'Search through your content'} {type.id === 'navigation' && 'Navigate through different contexts'} {type.id === 'node-snapshot' && 'Manage node snapshots'} ))} )} {isPinned ? ( <> > ) : ( <> > )} {renderCurrentPanel()} )} > ); };