/** * @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 { AISidebar } from './components/AISidebar'; import { SlashMenu } from './components/SlashMenu'; // Data & Types import { CARNETS, ALL_NOTES } from './constants'; import { NavigationView, SettingsTab, AITab, AITone, Carnet, Note } 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 [activeCarnetId, setActiveCarnetId] = useState('4'); const [activeNoteId, setActiveNoteId] = useState(null); 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); }; 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} />
{(activeView === 'notebooks' || activeView === 'shared' || activeView === 'reminders') && ( setShowNewCarnetModal({ isOpen: show, parentId, isRenaming, carnetId })} onDeleteNote={handleDeleteNote} /> )} {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' && ( )}
{/* 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" />