import { useEffect, useRef, useState } from 'react' import { Copy, FilePlus2, Loader2, LogOut } from 'lucide-react' import { listRecords, type RepoRecord } from '../lib/atproto' import { EXTERNAL_IMAGE_TYPE, TILESET_NSID, type TilesetRecord } from '../lexicon/tileset' import { publishTileset } from '../lib/atprotoWrite' import { serializeTileset, saveActiveTileset } from '../store/persistence' import { useEditorStore } from '../store/editorStore' import { useAuthStore } from '../store/authStore' import HandleAutocomplete from './HandleAutocomplete' import { Avatar, AvatarFallback } from '@/components/ui/avatar' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog' type TilesetRepoRecord = RepoRecord /** "Save As": publish the active tileset to the user's atproto repo. Logged out, * it prompts for a handle and runs an OAuth popup login. Logged in, it lists the * user's existing tilesets so they can overwrite one, or create a new record. * Self-contained (owns its trigger button), mirroring {@link AtprotoBrowser}. */ export default function SaveAsDialog() { const status = useAuthStore((s) => s.status) const did = useAuthStore((s) => s.did) const handle = useAuthStore((s) => s.handle) const agent = useAuthStore((s) => s.agent) const login = useAuthStore((s) => s.login) const logout = useAuthStore((s) => s.logout) const authError = useAuthStore((s) => s.error) const tilesetName = useEditorStore((s) => s.tilesetName) const setSource = useEditorStore((s) => s.setSource) const [open, setOpen] = useState(false) const [loginHandle, setLoginHandle] = useState('') const [loggingIn, setLoggingIn] = useState(false) const [records, setRecords] = useState(null) const [listing, setListing] = useState(false) const [listError, setListError] = useState(null) /** rkey being written, '__new__' for a new record, or null when idle. */ const [publishing, setPublishing] = useState(null) const [publishError, setPublishError] = useState(null) const ctrlRef = useRef(null) // Load the user's existing tilesets when the dialog is open and signed in. useEffect(() => { if (!open || status !== 'in' || !did) return const ctrl = new AbortController() ctrlRef.current = ctrl const run = async () => { setListing(true) setListError(null) setRecords(null) try { const res = await listRecords(did, TILESET_NSID, ctrl.signal) if (!ctrl.signal.aborted) setRecords(res.records) } catch (err) { if (!ctrl.signal.aborted) setListError(err instanceof Error ? err.message : 'Could not list your tilesets.') } finally { if (!ctrl.signal.aborted) setListing(false) } } void run() return () => ctrl.abort() }, [open, status, did]) const onOpenChange = (next: boolean) => { setOpen(next) if (!next) { ctrlRef.current?.abort() setLoginHandle('') setRecords(null) setListError(null) setPublishError(null) setPublishing(null) } } const doLogin = async (h: string) => { if (!h.trim()) return setLoggingIn(true) try { await login(h) } catch { // error surfaced via authError } finally { setLoggingIn(false) } } /** Publish the active tileset: `rkey` overwrites, undefined creates a new record. */ const saveAs = async (rkey?: string) => { if (!agent || !did) return setPublishing(rkey ?? '__new__') setPublishError(null) try { const ts = serializeTileset() const res = await publishTileset({ agent, did, ts, rkey }) setSource({ did, rkey: res.rkey }) await saveActiveTileset() onOpenChange(false) } catch (err) { setPublishError(err instanceof Error ? err.message : 'Could not save the tileset.') } finally { setPublishing(null) } } const busy = publishing !== null return ( Save to atproto Publish “{tilesetName || 'Untitled'}” to your repo as a new record, or overwrite one of your existing tilesets. {status === 'loading' ? (

Checking your session…

) : status !== 'in' ? ( // ---- logged out: handle + login ----

Log in with your atproto account to save. A window will open to your PDS.

{authError &&

{authError}

}
) : ( // ---- logged in: identity + targets ----
{(handle || did || '?').slice(0, 1)} @{handle || did}
Or overwrite {listing ? (

Loading your tilesets…

) : listError ? (

{listError}

) : records && records.length === 0 ? (

No tilesets in your repo yet.

) : ( records && (
    {records.map((rec) => { const name = (typeof rec.value?.name === 'string' && rec.value.name.trim()) || '(untitled)' const external = (rec.value?.image as { $type?: string } | undefined)?.$type === EXTERNAL_IMAGE_TYPE return (
  • {name} {rec.rkey} {external && ' · external image'}
  • ) })}
) )}
{publishError &&

{publishError}

}
)}
) }