Something went wrong. Try again.
An atproto Tilemap editor
Something went wrong. Try again.
4.4 kB · 116 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117// ---------------------------------------------------------------------------// Authenticated atproto writes: publish a tileset to a repo.//// The read side and the local<->record serialization already exist// (src/lib/atproto.ts, src/store/persistence.ts). This is the write side: it// takes the authenticated agent from the auth store, uploads the embedded sheet// as a blob, and creates or overwrites a `world.tilemap.tileset` record.//// `tilesetToRecord` already builds the wire record from a local Tileset given an// `uploadBlob` callback; we just supply one backed by `com.atproto.repo.uploadBlob`.// ---------------------------------------------------------------------------
import type { Agent } from '@atproto/api'import { tilesetToRecord, type Tileset } from '@/store/persistence'import { TILESET_NSID, type BlobRef } from '@/lexicon/tileset'import { mapToRecord } from '@/lib/mapRecord'import { MAP_NSID } from '@/lexicon/map'
/** Decode a `data:<mime>;base64,<data>` URL into raw bytes + its MIME type. */export function dataUrlToBytes(dataUrl: string): { bytes: Uint8Array; mimeType: string } { const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(dataUrl) if (!match) throw new Error('Sheet image is not a valid data URL.') const mimeType = match[1] || 'application/octet-stream' const isBase64 = Boolean(match[2]) const data = match[3] if (isBase64) { const binary = atob(data) const bytes = new Uint8Array(binary.length) for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i) return { bytes, mimeType } } return { bytes: new TextEncoder().encode(decodeURIComponent(data)), mimeType }}
export interface PublishResult { did: string rkey: string uri: string cid: string}
/** Publish a tileset to the agent's repo. With `rkey`, overwrites that record * (Save / "Overwrite"); without it, creates a new record under a fresh TID * ("Save as a new tileset"). Embedded sheets are uploaded as a blob first; * external sheets carry no bytes (handled inside `tilesetToRecord`). */export async function publishTileset(opts: { agent: Agent did: string ts: Tileset rkey?: string}): Promise<PublishResult> { const { agent, did, ts, rkey } = opts
const built = await tilesetToRecord(ts, async (dataUrl) => { const { bytes, mimeType } = dataUrlToBytes(dataUrl) const res = await agent.com.atproto.repo.uploadBlob(bytes, { encoding: mimeType }) // The returned BlobRef instance serializes to the lexicon blob shape. return res.data.blob as unknown as BlobRef }) // putRecord/createRecord type `record` as an open object; our record carries a // $type and is structurally valid for the collection. const record = built as unknown as Record<string, unknown>
if (rkey) { const res = await agent.com.atproto.repo.putRecord({ repo: did, collection: TILESET_NSID, rkey, record, }) return { did, rkey, uri: res.data.uri, cid: res.data.cid } }
const res = await agent.com.atproto.repo.createRecord({ repo: did, collection: TILESET_NSID, record, }) return { did, rkey: res.data.uri.split('/').pop() ?? '', uri: res.data.uri, cid: res.data.cid }}
/** Publish the active map to the agent's repo. With `rkey`, overwrites that * record (Save); without it, creates a new record (Save As). The map is baked * to a self-contained render first (see {@link mapToRecord}); its atlas PNG and * layout JSON are uploaded as blobs, then referenced from the record. */export async function publishMap(opts: { agent: Agent did: string rkey?: string}): Promise<PublishResult> { const { agent, did, rkey } = opts
const built = await mapToRecord(async (dataUrl) => { const { bytes, mimeType } = dataUrlToBytes(dataUrl) const res = await agent.com.atproto.repo.uploadBlob(bytes, { encoding: mimeType }) return res.data.blob as unknown as BlobRef }) const record = built as unknown as Record<string, unknown>
if (rkey) { const res = await agent.com.atproto.repo.putRecord({ repo: did, collection: MAP_NSID, rkey, record, }) return { did, rkey, uri: res.data.uri, cid: res.data.cid } }
const res = await agent.com.atproto.repo.createRecord({ repo: did, collection: MAP_NSID, record, }) return { did, rkey: res.data.uri.split('/').pop() ?? '', uri: res.data.uri, cid: res.data.cid }}