import { atom } from 'nanostores' import { db } from './db' type Note = { id: string x: number y: number content: string } type Space = { id: string notes: Note[] } export const currentSpaceId = atom('home') export const currentSpace = atom(null) export const navigationHistory = atom(['home']) export const editingNoteId = atom(null) export const selectedNoteIds = atom([]) export const existingSpaceIds = atom([]) export const refreshExistingSpaces = async () => { const spaces = await db.getAllSpaceIds() existingSpaceIds.set(spaces) } export const loadSpace = async (spaceId: string) => { // Check if current space is empty and delete it (except 'home') const currentSpace_value = currentSpace.get() const currentSpaceId_value = currentSpaceId.get() if (currentSpace_value && currentSpaceId_value !== 'home' && currentSpace_value.notes.length === 0) { await db.deleteSpace(currentSpaceId_value) } currentSpace.set(null) let space = await db.getSpace(spaceId) if (!space) { space = await db.createSpace(spaceId) } currentSpace.set(space) currentSpaceId.set(spaceId) window.history.pushState({}, '', `/#${spaceId}`) // Refresh list of existing spaces await refreshExistingSpaces() return space } export const navigateToSpace = async (spaceId: string) => { const history = navigationHistory.get() if (history[history.length - 1] !== spaceId) { navigationHistory.set([...history, spaceId]) } await loadSpace(spaceId) } export const goBack = async () => { const history = navigationHistory.get() if (history.length > 1) { const newHistory = history.slice(0, -1) navigationHistory.set(newHistory) const previousSpaceId = newHistory[newHistory.length - 1] await loadSpace(previousSpaceId) } } export const createNote = async (x: number, y: number) => { const space = currentSpace.get() if (!space) return null const note: Note = { id: `note-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, x, y, content: '' } await db.addNoteToSpace(space.id, note) const updatedSpace = await db.getSpace(space.id) currentSpace.set(updatedSpace!) return note.id } export const updateNote = async (noteId: string, updates: Partial) => { const space = currentSpace.get() if (!space) return await db.updateNoteInSpace(space.id, noteId, updates) const updatedSpace = await db.getSpace(space.id) currentSpace.set(updatedSpace!) } export const deleteNote = async (noteId: string) => { const space = currentSpace.get() if (!space) return await db.deleteNoteFromSpace(space.id, noteId) const updatedSpace = await db.getSpace(space.id) currentSpace.set(updatedSpace!) } export const getSpaceFromUrl = () => { const hash = window.location.hash.slice(1) return hash || 'home' } // Selection management export const selectNote = (noteId: string, multiSelect: boolean = false) => { const currentSelection = selectedNoteIds.get() if (multiSelect) { if (!currentSelection.includes(noteId)) { // Add to selection only const newSelection = [...currentSelection, noteId] selectedNoteIds.set(newSelection) } // If already selected and multiSelect, do nothing (don't remove) } else { // Single selection selectedNoteIds.set([noteId]) } } export const selectNotesInRect = (minX: number, minY: number, maxX: number, maxY: number) => { const space = currentSpace.get() if (!space) return const selectedIds = space.notes .filter(note => { // Check if note overlaps with rectangle (considering note has some width/height) const noteRight = note.x + 50 // Approximate note width const noteBottom = note.y + 20 // Approximate note height const inRect = !(note.x > maxX || noteRight < minX || note.y > maxY || noteBottom < minY) return inRect }) .map(note => note.id) selectedNoteIds.set(selectedIds) } export const clearSelection = () => { selectedNoteIds.set([]) } export const deleteSelectedNotes = async () => { const selectedIds = selectedNoteIds.get() const space = currentSpace.get() if (!space || selectedIds.length === 0) return for (const noteId of selectedIds) { await db.deleteNoteFromSpace(space.id, noteId) } const updatedSpace = await db.getSpace(space.id) currentSpace.set(updatedSpace!) clearSelection() } export const moveSelectedNotesLocal = (deltaX: number, deltaY: number) => { const selectedIds = selectedNoteIds.get() const space = currentSpace.get() if (!space || selectedIds.length === 0) return // Update positions locally without DB calls const updatedNotes = space.notes.map(note => { if (selectedIds.includes(note.id)) { return { ...note, x: note.x + deltaX, y: note.y + deltaY } } return note }) currentSpace.set({ ...space, notes: updatedNotes }) } export const saveSelectedNotesToDB = async () => { const selectedIds = selectedNoteIds.get() const space = currentSpace.get() if (!space || selectedIds.length === 0) return // Save to DB for (const noteId of selectedIds) { const note = space.notes.find(n => n.id === noteId) if (note) { await db.updateNoteInSpace(space.id, noteId, { x: note.x, y: note.y }) } } }