/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useMemo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; // Components import { Sidebar } from './components/Sidebar'; import { NotebooksView } from './components/NotebooksView'; import { AgentsView } from './components/AgentsView'; import { SettingsView } from './components/SettingsView'; import { TrashView } from './components/TrashView'; import { BrainstormView } from './components/BrainstormView/BrainstormView'; import { InsightsView } from './components/InsightsView'; import { TemporalView } from './components/TemporalView'; import { AISidebar } from './components/AISidebar'; import { SlashMenu } from './components/SlashMenu'; // Data & Types import { CARNETS, ALL_NOTES } from './constants'; import { NavigationView, SettingsTab, AITab, AITone, Carnet, Note, BrainstormIdea, NoteAccessLog } from './types'; export default function App() { const [activeView, setActiveView] = useState('notebooks'); const [activeSettingsTab, setActiveSettingsTab] = useState('general'); const [selectedAgentId, setSelectedAgentId] = useState(null); const [isDarkMode, setIsDarkMode] = useState(false); const [carnets, setCarnets] = useState(CARNETS); const [notes, setNotes] = useState(ALL_NOTES); const [accessLogs, setAccessLogs] = useState([ // Note n1: 14-day cycle { noteId: 'n1', accessedAt: new Date(Date.now() - 70 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n1', accessedAt: new Date(Date.now() - 56 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n1', accessedAt: new Date(Date.now() - 42 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n1', accessedAt: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n1', accessedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n1', accessedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, // Note n2: 7-day cycle { noteId: 'n2', accessedAt: new Date(Date.now() - 35 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n2', accessedAt: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n2', accessedAt: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n2', accessedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n2', accessedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n2', accessedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, // Note n3: 3-day cycle (frequent check) { noteId: 'n3', accessedAt: new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n3', accessedAt: new Date(Date.now() - 9 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n3', accessedAt: new Date(Date.now() - 6 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n3', accessedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, { noteId: 'n3', accessedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), action: 'view' }, ]); const logNoteAccess = (noteId: string, action: 'view' | 'edit' | 'search_hit' = 'view') => { const newLog: NoteAccessLog = { noteId, accessedAt: new Date().toISOString(), action }; setAccessLogs(prev => [...prev, newLog]); }; const [activeCarnetId, setActiveCarnetId] = useState('4'); const [activeNoteId, setActiveNoteId] = useState(null); const [brainstormSeed, setBrainstormSeed] = useState(null); const handleBrainstormNote = (note: Note) => { setActiveView('brainstorm'); // We'll use a small delay or a ref to pass this to BrainstormView if needed, // but better to just share state or use a CustomEvent window.dispatchEvent(new CustomEvent('start-brainstorm', { detail: { seed: note.title, sourceNoteId: note.id } })); }; React.useEffect(() => { if (activeNoteId) { logNoteAccess(activeNoteId); } }, [activeNoteId]); React.useEffect(() => { const handleSwitchView = (e: any) => { if (e.detail) { setActiveView(e.detail as NavigationView); } }; window.addEventListener('switch-view', handleSwitchView); return () => window.removeEventListener('switch-view', handleSwitchView); }, []); const [selectedTagIds, setSelectedTagIds] = useState([]); const [isAISidebarOpen, setIsAISidebarOpen] = useState(false); const [aiTab, setAiTab] = useState('discussion'); const [selectedTone, setSelectedTone] = useState('Professional'); // Modal States const [showNewCarnetModal, setShowNewCarnetModal] = useState<{ isOpen: boolean; parentId?: string; isRenaming?: boolean; carnetId?: string }>({ isOpen: false }); const [showNewNoteModal, setShowNewNoteModal] = useState(false); const [slashMenu, setSlashMenu] = useState<{ isOpen: boolean; top: number; left: number } | null>(null); // Form States const [newCarnetName, setNewCarnetName] = useState(''); const [newNoteTitle, setNewNoteTitle] = useState(''); const [newNoteContent, setNewNoteContent] = useState(''); const handleEditorKeyDown = (e: React.KeyboardEvent) => { if (e.key === '/') { const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); setSlashMenu({ isOpen: true, top: rect.bottom + window.scrollY, left: rect.left + window.scrollX }); } } }; const togglePin = (noteId: string) => { setNotes(notes.map(n => n.id === noteId ? { ...n, isPinned: !n.isPinned } : n)); }; const filteredNotes = useMemo(() => { let result = notes.filter(n => n.carnetId === activeCarnetId && !n.isDeleted); if (selectedTagIds.length > 0) { result = result.filter(note => selectedTagIds.every(tagId => note.tags?.some(tag => tag.id === tagId)) ); } return [...result].sort((a, b) => { if (a.isPinned && !b.isPinned) return -1; if (!a.isPinned && b.isPinned) return 1; return 0; }); }, [activeCarnetId, notes]); const activeNote = useMemo(() => notes.find(n => n.id === activeNoteId), [activeNoteId, notes]); const activeCarnet = useMemo(() => carnets.find(c => c.id === activeCarnetId), [activeCarnetId, carnets]); const handleAddCarnet = (e: React.FormEvent) => { e.preventDefault(); if (!newCarnetName.trim()) return; if (showNewCarnetModal.isRenaming && showNewCarnetModal.carnetId) { setCarnets(carnets.map(c => c.id === showNewCarnetModal.carnetId ? { ...c, name: newCarnetName, initial: newCarnetName.charAt(0).toUpperCase() } : c)); setShowNewCarnetModal({ isOpen: false }); setNewCarnetName(''); return; } const newCarnet: Carnet = { id: Date.now().toString(), name: newCarnetName, initial: newCarnetName.charAt(0).toUpperCase(), type: 'Project', parentId: showNewCarnetModal.parentId }; setCarnets([...carnets, newCarnet]); setNewCarnetName(''); setShowNewCarnetModal({ isOpen: false }); setActiveCarnetId(newCarnet.id); }; const handleDeleteCarnet = (id: string) => { if (window.confirm('Déplacer ce carnet et ses sous-carnets vers la corbeille ?')) { const idsToDelete = new Set([id]); const addChildren = (parentId: string) => { carnets.forEach(c => { if (c.parentId === parentId) { idsToDelete.add(c.id); addChildren(c.id); } }); }; addChildren(id); const deletedAt = new Date().toISOString(); setCarnets(carnets.map(c => idsToDelete.has(c.id) ? { ...c, isDeleted: true, deletedAt } : c)); setNotes(notes.map(n => idsToDelete.has(n.carnetId) ? { ...n, isDeleted: true, deletedAt } : n)); if (idsToDelete.has(activeCarnetId)) { setActiveCarnetId('1'); } } }; const handleDeleteNote = (id: string) => { const deletedAt = new Date().toISOString(); setNotes(notes.map(n => n.id === id ? { ...n, isDeleted: true, deletedAt } : n)); if (activeNoteId === id) setActiveNoteId(null); }; const handleRestoreCarnet = (id: string) => { setCarnets(carnets.map(c => c.id === id ? { ...c, isDeleted: false, deletedAt: undefined } : c)); // Optionally restore linked notes too? User might expect that. setNotes(notes.map(n => n.carnetId === id ? { ...n, isDeleted: false, deletedAt: undefined } : n)); }; const handleRestoreNote = (id: string) => { setNotes(notes.map(n => n.id === id ? { ...n, isDeleted: false, deletedAt: undefined } : n)); }; const handlePermanentDeleteNote = (id: string) => { setNotes(notes.filter(n => n.id !== id)); }; const handlePermanentDeleteCarnet = (id: string) => { const idsToDelete = new Set([id]); const addChildren = (parentId: string) => { carnets.forEach(c => { if (c.parentId === parentId) { idsToDelete.add(c.id); addChildren(c.id); } }); }; addChildren(id); setCarnets(carnets.filter(c => !idsToDelete.has(c.id))); setNotes(notes.filter(n => !idsToDelete.has(n.carnetId))); }; const handleAddNote = (e: React.FormEvent) => { e.preventDefault(); if (!newNoteTitle.trim() || !newNoteContent.trim()) return; const newNote: Note = { id: `n-${Date.now()}`, carnetId: activeCarnetId, title: newNoteTitle, date: new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(new Date()), content: newNoteContent, imageUrl: 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&q=80&w=800&h=600', tags: [] }; setNotes([newNote, ...notes]); setNewNoteTitle(''); setNewNoteContent(''); setShowNewNoteModal(false); setActiveNoteId(newNote.id); }; const handleConvertIdeaToNote = (idea: BrainstormIdea) => { const newNote: Note = { id: `n-gen-${Date.now()}`, carnetId: activeCarnetId, title: idea.title, date: new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(new Date()), content: `${idea.description}\n\n---\n**Connection to seed:** ${idea.connectionToSeed}\n**Novelty Score:** ${idea.noveltyScore}/10`, imageUrl: 'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&q=80&w=800&h=600', tags: [{ id: 't-ai', label: 'AI Generated', type: 'ai' }] }; setNotes([newNote, ...notes]); setActiveView('notebooks'); setActiveNoteId(newNote.id); }; return (
{ setShowNewCarnetModal({ isOpen: show, parentId, isRenaming, carnetId }); if (isRenaming && carnetId) { const carnet = carnets.find(c => c.id === carnetId); if (carnet) setNewCarnetName(carnet.name); } else { setNewCarnetName(''); } }} onDeleteCarnet={handleDeleteCarnet} onMoveCarnet={(draggedId, targetId) => { if (draggedId === targetId) return; // Basic circular check const isDescendant = (parentId: string, potentialChildId: string): boolean => { const childIds = carnets.filter(c => c.parentId === parentId).map(c => c.id); if (childIds.includes(potentialChildId)) return true; return childIds.some(id => isDescendant(id, potentialChildId)); }; if (targetId && isDescendant(draggedId, targetId)) { console.warn("Cannot move a notebook inside its own descendant"); return; } setCarnets(prev => prev.map(c => c.id === draggedId ? { ...c, parentId: targetId } : c)); }} />
{(activeView === 'notebooks' || activeView === 'shared' || activeView === 'reminders') && ( setShowNewCarnetModal({ isOpen: show, parentId, isRenaming, carnetId })} onDeleteNote={handleDeleteNote} onBrainstormNote={handleBrainstormNote} /> )} {activeView === 'trash' && ( n.isDeleted)} deletedCarnets={carnets.filter(c => c.isDeleted)} onRestoreNote={handleRestoreNote} onRestoreCarnet={handleRestoreCarnet} onPermanentDeleteNote={handlePermanentDeleteNote} onPermanentDeleteCarnet={handlePermanentDeleteCarnet} onEmptyTrash={() => { setNotes(notes.filter(n => !n.isDeleted)); setCarnets(carnets.filter(c => !c.isDeleted)); }} /> )} {activeView === 'agents' && ( )} {activeView === 'settings' && ( )} {activeView === 'brainstorm' && ( )} {activeView === 'insights' && ( { setActiveView('notebooks'); setActiveNoteId(noteId); }} /> )} {activeView === 'temporal' && ( { setActiveView('notebooks'); setActiveNoteId(noteId); }} /> )}
{/* Modals */} {showNewCarnetModal.isOpen && (
setShowNewCarnetModal({ isOpen: false })} className="absolute inset-0 bg-ink/40 backdrop-blur-sm" />

{showNewCarnetModal.isRenaming ? 'Rename Carnet' : (showNewCarnetModal.parentId ? 'Create Sub-Carnet' : 'Create New Carnet')}

{showNewCarnetModal.parentId && !showNewCarnetModal.isRenaming && (

Inside: {carnets.find(c => c.id === showNewCarnetModal.parentId)?.name}

)}
setNewCarnetName(e.target.value)} placeholder="E.g., Sustainable Patterns" className="w-full bg-white dark:bg-[#2A2A2A] border border-border rounded-lg px-4 py-3 outline-none focus:border-ink transition-colors font-serif italic text-lg text-ink dark:text-dark-ink" />
)} {showNewNoteModal && (
setShowNewNoteModal(false)} className="absolute inset-0 bg-ink/40 backdrop-blur-sm" /> {slashMenu?.isOpen && ( { console.log(type); setSlashMenu(null); }} onClose={() => setSlashMenu(null)} /> )}

Add Architectural Note

setNewNoteTitle(e.target.value)} placeholder="Enter the title of your study..." className="w-full bg-white dark:bg-[#2A2A2A] border border-border rounded-lg px-5 py-4 outline-none focus:border-ink transition-colors font-serif text-2xl text-ink dark:text-dark-ink" />