Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198<script lang="ts"> import { refreshAll } from "$app/navigation"; import { page } from "$app/state"; import ArrowRight from "$icon/arrow-right"; import LogIn from "$icon/log-in"; import LogOut from "$icon/log-out"; import RotateCcw from "$icon/rotate-ccw"; import X from "$icon/x"; import { getAuth } from "#lib/auth.svelte.ts"; import { createAction } from "#lib/action.svelte.ts"; import { createBobbinClient } from "#lib/api/client.ts"; import Meta from "#lib/components/seo/Meta.svelte"; import Button from "#lib/components/ui/Button.svelte"; import ConfirmModal from "#lib/components/ui/ConfirmModal.svelte"; import ErrorAlert from "#lib/components/ui/Error.svelte"; import { importFromLiar, MigrationScopeError, unmarkLiarAccount, type ImportProgress } from "#lib/migration/import.ts"; import { waitForMigration, type MigrationConfig } from "#lib/migration/status.ts";
interface Props { config: MigrationConfig & { bobbinUrl: string }; onSignOut: () => void; }
let { config, onSignOut }: Props = $props();
const auth = getAuth();
let progress = $state<ImportProgress | null>(null); let waiting = $state(false); let needsGrant = $state(false); let confirmSkip = $state(false); // a retry skips whatever already went through let imported = false; let unmarked = false;
const migrate = createAction(async (skip: boolean = false) => { const agent = auth.agent; if (!agent) throw new Error("Still signing you in. Try again in a moment."); needsGrant = false; confirmSkip = false;
try { const fetch = config.liarMock ? (await import("#lib/migration/mock.ts")).mockLiarFetch : undefined; if (!imported && !skip) { await importFromLiar(agent, { liarPdsUrl: config.liarPdsUrl, bobbinUrl: config.bobbinUrl, fetch, onProgress: (next) => (progress = next) }); imported = true; } if (!unmarked) { progress = { phase: "finalizing", done: 0, total: 0, overall: { done: 1, total: 1 } }; await unmarkLiarAccount(agent, config.bobbinUrl, { fetch }); unmarked = true; }
progress = null; waiting = true; const settled = await waitForMigration( createBobbinClient({ serviceUrl: config.bobbinUrl }), agent.sub, config ); if (!settled) { throw new Error( "Your data is migrated, but Tangled hasn't switched you over yet. Try again in a minute." ); } await refreshAll(); } catch (cause) { needsGrant = cause instanceof MigrationScopeError; throw cause; } finally { progress = null; waiting = false; } });
const reauthorize = createAction(async () => { const did = auth.currentDid; if (!did) return; await auth.signIn(did, page.url.pathname + page.url.search); });
const status = $derived.by(() => { if (waiting) return "Waiting for Tangled to switch you over…"; if (progress?.phase === "reading") return "Reading records…"; if (progress?.phase === "blobs") { return `Copying files (${progress.done} of ${progress.total})`; } if (progress?.phase === "importing") { return `Migrating records (${progress.done} of ${progress.total})`; } if (progress?.phase === "finalizing") return "Finishing up…"; return null; });
const percent = $derived.by(() => { if (waiting || progress?.phase === "finalizing") return 100; if (!progress?.overall.total) return 0; return Math.round((progress.overall.done / progress.overall.total) * 100); });
const indeterminate = $derived(waiting || progress?.phase === "reading");</script>
<Meta noindex title="Migrate your data" />
<section class="mx-auto flex w-full max-w-medium flex-col gap-md px-7 py-5xl"> <h1 class="typography-heading-2 text-foreground-default">Migrate your data</h1> <p class="typography-paragraph-regular text-foreground-muted"> Tangled now stores your data in a new record format. Migrate it to keep using Tangled {#if auth.currentUser}as <span class="font-medium text-foreground-default" >{auth.currentUser.handle}</span >{/if}. If anything fails, you can safely try again. </p>
<ErrorAlert label={reauthorize.error ?? migrate.error} />
<div class="flex flex-wrap items-center gap-sm"> {#if needsGrant} <Button variant="primary" icon={LogIn} loading={reauthorize.loading} onclick={reauthorize.run} > Sign in again to grant permission </Button> {:else} <Button variant={migrate.error ? "danger" : "primary"} icon={migrate.error ? RotateCcw : ArrowRight} loading={migrate.loading} disabled={!auth.agent} onclick={() => migrate.run()} > {migrate.error ? "Try again" : "Start migration"} </Button> {/if} <Button variant="ghost" icon={X} disabled={!auth.agent || migrate.loading} onclick={() => (confirmSkip = true)} > Skip migration </Button> <Button variant="ghost" icon={LogOut} disabled={migrate.loading} onclick={onSignOut}> Sign out </Button> </div>
{#if status} <div class="flex flex-col gap-xs" role="status"> <p class="typography-paragraph-small text-foreground-muted tabular-nums">{status}</p> <div class={[ "h-2 w-full overflow-hidden rounded-full border border-border-default bg-background-inset", indeterminate && "animate-pulse motion-reduce:animate-none" ]} role="progressbar" aria-valuenow={indeterminate ? undefined : percent} aria-valuemin={0} aria-valuemax={100} > <div class="h-full rounded-full bg-background-info-emphasis transition-transform duration-250 ease-out-strong motion-reduce:transition-none" style:transform="translateX(-{100 - percent}%)" ></div> </div> </div> {/if}</section>
<ConfirmModal bind:open={confirmSkip} title="Skip migration?" description="Your existing Tangled data (sh.tangled.* records) won't be copied to the new format. It stays in your PDS, but Tangled no longer reads it, so none of it will appear on Tangled." confirmLabel="Skip migration" loading={migrate.loading} onconfirm={() => migrate.run(true)}/>