import { useEffect, useState } from 'react' import { ChevronDown, LayoutGrid, Map as MapIcon, Plus, SlidersHorizontal, X, } from 'lucide-react' import { useEditorStore } from '../store/editorStore' import { useMapStore } from '../store/mapStore' import { useAuthStore } from '../store/authStore' import { useUiStore, type EditorKind } from '../store/uiStore' import { deleteTileset, listTilesets, loadTilesetIntoEditor, newTileset, subscribeLibrary, type TilesetIndexEntry, } from '../store/persistence' import { deleteMapLocal, listMaps, loadMapIntoEditor, subscribeMapLibrary, type MapIndexEntry, } from '../store/mapPersistence' import { type DocKind } from '../lib/docTypes' import { TOOL } from '../lib/editorMeta' import AtprotoBrowser from './AtprotoBrowser' import LoginDialog from './LoginDialog' import CollapsibleSection from './CollapsibleSection' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { cn } from '@/lib/utils' const ROW = 'flex items-center gap-2 rounded-md border border-transparent px-1.5 py-1 hover:bg-accent' const LIST_SCROLL = 'flex max-h-56 list-none flex-col gap-1 overflow-y-auto p-0' /** The left sidebar: a tool indicator, two collapsible/scrollable document * lists (tilesets, maps), and the New / Load actions. Picking, renaming, * creating, or loading a document drives the active editor. Both tilesets and * maps persist to (and restore from) the IndexedDB library; the active doc shows * as "unsaved"/"working" until its first autosave. (The world editor is not * surfaced here yet — no library listing or New option.) */ export default function Library() { const editorKind = useUiStore((s) => s.editorKind) const setEditorKind = useUiStore((s) => s.setEditorKind) const setSidebarView = useUiStore((s) => s.setSidebarView) /** Open a document in its editor and surface that editor's tools. */ const openInEditor = (kind: EditorKind) => { setEditorKind(kind) setSidebarView('tools') } const activeId = useEditorStore((s) => s.id) const activeName = useEditorStore((s) => s.tilesetName) const mapName = useMapStore((s) => s.name) const setMapName = useMapStore((s) => s.setName) const activeMapId = useMapStore((s) => s.stableId) const status = useAuthStore((s) => s.status) const [entries, setEntries] = useState([]) const [mapEntries, setMapEntries] = useState([]) /** the kind queued by "New", pending login + creation (null when idle). */ const [pendingNew, setPendingNew] = useState(null) const [error, setError] = useState(null) useEffect(() => { let alive = true const refresh = () => listTilesets().then((list) => alive && setEntries(list)) refresh() const unsub = subscribeLibrary(refresh) return () => { alive = false unsub() } }, []) useEffect(() => { let alive = true const refresh = () => listMaps().then((list) => alive && setMapEntries(list)) refresh() const unsub = subscribeMapLibrary(refresh) return () => { alive = false unsub() } }, []) // "New" requires login. Queuing a kind opens the login dialog when signed out; // once status flips to 'in' (or already is) this creates the document. A new // tileset is minted into the user's repo with a verified-free TID so Save is // enabled immediately (see newTileset); maps stay local until map persistence. useEffect(() => { if (status !== 'in' || pendingNew == null) return const kind = pendingNew void (async () => { try { if (kind === 'tileset') { const { agent, did } = useAuthStore.getState() if (!agent || !did) return await newTileset(agent, did) } else if (kind === 'map') { useMapStore.getState().reset() } useUiStore.getState().setEditorKind(kind) useUiStore.getState().setSidebarView('tools') } catch (err) { setError(err instanceof Error ? err.message : 'Could not create the document.') } finally { setPendingNew(null) } })() }, [status, pendingNew]) const onNew = (kind: DocKind) => { setError(null) setPendingNew(kind) } const onDeleteTileset = async (entry: TilesetIndexEntry) => { if (!window.confirm(`Delete tileset "${entry.name}"? This can't be undone.`)) return await deleteTileset(entry.id) } const onDeleteMap = async (entry: MapIndexEntry) => { if (!window.confirm(`Delete map "${entry.name}"? This can't be undone.`)) return await deleteMapLocal(entry.id) } const tilesetActive = editorKind === 'tileset' const mapActive = editorKind === 'map' const unsavedActive = !entries.some((e) => e.id === activeId) // the active map isn't in the library until it's been edited (autosaved). const mapUnsaved = !mapEntries.some((e) => e.id === activeMapId) const Indicator = TOOL[editorKind].icon return (

{TOOL[editorKind].label}

{/* ---- Tilesets ---- */}
    {entries.map((entry) => { const active = entry.id === activeId return (
  • ) })} {/* The active tileset isn't in the library until it's been edited+saved. */} {unsavedActive && (
  • unsaved
  • )}
{/* ---- Maps ---- */}
    {mapEntries.map((entry) => { const active = entry.id === activeMapId return (
  • {active ? ( // the active map renames in place; its name autosaves to the index. setEditorKind('map')} onChange={(e) => setMapName(e.target.value)} /> ) : ( )}
  • ) })} {/* The active map isn't in the library until it's been edited+saved. */} {mapUnsaved && (
  • setEditorKind('map')} onChange={(e) => setMapName(e.target.value)} /> working
  • )}
{/* ---- New / Load ---- */}
onNew('tileset')}> Tileset onNew('map')}> Map
{error && (

{error}

)} { if (!next) setPendingNew(null) }} reason="Log in to create a new document in your repo." />
) }