Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178<script lang="ts"> import { untrack } from "svelte"; import type { Did } from "@atcute/lexicons/syntax"; import { now as tidNow } from "@atcute/tid"; import Button from "$lib/components/ui/Button.svelte"; import Card from "$lib/components/ui/Card.svelte"; import Input from "$lib/components/ui/Input.svelte"; import Spinner from "$lib/components/ui/Spinner.svelte"; import ErrorAlert from "$lib/components/ui/Error.svelte"; import Separator from "$lib/components/ui/Separator.svelte"; import ArrowUp from "$icon/arrow-up"; import Check from "$icon/check"; import X from "$icon/x"; import { getAuth } from "$lib/auth.svelte"; import AccountSelector from "$lib/components/auth/AccountSelector.svelte"; import { contentStats } from "$lib/api/blob"; import { createBobbinClient } from "$lib/api/client"; import { putString } from "$lib/api/strings"; import type { StringRecord } from "$lib/api/records"; import { createAction } from "$lib/action.svelte"; import Field from "$lib/components/ui/Field.svelte"; import PierreEditor from "$lib/components/editor/PierreEditor.svelte";
interface Props { mode?: "create" | "edit"; rkey?: string; swapRecord?: string; createdAt?: string; filename?: string; description?: string; content?: string; bobbinUrl: string; onsaved: (rkey: string, handle: string) => void; oncancel?: () => void; }
let { mode = "create", rkey, swapRecord, createdAt, filename: initialFilename = "", description: initialDescription = "", content: initialContent = "", bobbinUrl, onsaved, oncancel }: Props = $props();
const auth = getAuth(); const accounts = $derived(auth?.accounts ?? []);
let picked = $state<string | null>(null); const acting = $derived( accounts.find((account) => account.did === picked) ?? { did: auth?.currentDid ?? "", handle: auth?.currentUser?.handle ?? "" } );
let filename = $state(untrack(() => initialFilename)); let description = $state(untrack(() => initialDescription)); let content = $state(untrack(() => initialContent)); let lastCid = $state<string | undefined>(untrack(() => swapRecord)); let lastRkey = $state<string | undefined>(untrack(() => rkey));
const stats = $derived(contentStats(content));
const publish = createAction(async (event: SubmitEvent) => { event.preventDefault(); if (!auth?.currentDid) return; if (filename.trim() === "") throw new Error("Filename is required"); if (content.length === 0) throw new Error("Content is required"); const agent = await auth.agentFor(acting.did as Did); const targetRkey = lastRkey ?? tidNow(); const record: StringRecord = { $type: "sh.tangled.string", filename: filename.trim(), description: description.trim(), contents: content, createdAt: createdAt ?? new Date().toISOString() }; const written = await putString( agent, createBobbinClient({ serviceUrl: bobbinUrl }), targetRkey, record, lastCid ); lastRkey = targetRkey; lastCid = written.cid; onsaved(targetRkey, acting.handle); });</script>
<Card> <form onsubmit={publish.run} class="flex flex-col gap-md"> <div class="flex flex-col gap-sm"> <div class="flex flex-col gap-xs md:flex-row"> <Field label="Filename" required={true}> <Input id="filename" name="filename" placeholder="main.rs" required bind:value={filename} disabled={publish.loading} class="md:max-w-64" /> </Field> <Field label="Description" class="flex-1"> <Input id="description" name="description" placeholder="What this string is for" maxlength={280} bind:value={description} disabled={publish.loading} /> </Field> </div>
<Field label="Content" required={true}> <PierreEditor bind:contents={content} name={filename} disabled={publish.loading} /> </Field> </div>
<div class="flex items-center justify-between"> <div class="px-xs text-foreground-muted"> <span class="typography-monospace-small" >{stats.lines} line{stats.lines !== 1 ? "s" : ""}</span > <Separator variant="dot" class="px-2xs" /> <span class="typography-monospace-small" >{stats.bytes} byte{stats.bytes !== 1 ? "s" : ""}</span > </div>
<div class="flex items-center gap-xs"> {#if mode === "edit"} <Button type="button" variant="default" icon={X} disabled={publish.loading} onclick={oncancel} > Cancel </Button> {/if} {#if mode === "create"} <AccountSelector {accounts} align="right" bind:value={() => acting.did, (did) => (picked = did)} label="Publish as" disabled={publish.loading} group="string-form" /> {/if} <Button type="submit" variant="primary" icon={publish.loading ? Spinner : mode === "edit" ? Check : ArrowUp} disabled={publish.loading} insetShadow={true} > {mode === "edit" ? "Save changes" : "Publish"} </Button> </div> </div>
{#if publish.error} <ErrorAlert label={publish.error} /> {/if} </form></Card>