Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195<script module lang="ts"> export interface MigrationItem { id: string | number; name: string; migration: { status: "idle" | "submitting" | "error" | "success"; error?: string; task?: { taskId: string; name: string }; jobId?: string; jobStatus?: string; jobError?: string; repoDid?: string; }; }</script>
<script lang="ts"> import { resolve } from "$app/paths"; import ArrowRight from "$icon/arrow-right"; import CircleUserRound from "$icon/circle-user-round"; import RotateCcw from "$icon/rotate-ccw"; import UserKey from "$icon/user-key"; import { grantRefused, migrationRunning } from "#lib/api/migrator.ts"; import { plural } from "#lib/format.ts"; import Button from "#lib/components/ui/Button.svelte"; import FormStep from "#lib/components/ui/FormStep.svelte"; import MigrationRow, { stageLabel } from "#lib/components/import/MigrationRow.svelte"; import StartedBanner from "#lib/components/import/StartedBanner.svelte";
interface Props { number: number; title?: string; disabled?: boolean; items: MigrationItem[]; running?: boolean; submitting?: boolean; userHandle?: string; onRetry?: (item: MigrationItem) => void; onAuthorize?: () => void; }
let { number, title = "Migration", disabled = false, items, running, submitting = false, userHandle, onRetry, onAuthorize }: Props = $props();
let bannerVisible = $state(true);
const itemRunning = (item: MigrationItem) => { const m = item.migration; return ( m.status === "submitting" || (m.status === "success" && m.jobStatus !== undefined && migrationRunning(m.jobStatus)) ); };
const itemFailed = (item: MigrationItem) => { const m = item.migration; return ( m.status === "error" || (m.status === "success" && (m.jobStatus === "failed" || m.jobStatus === "authorization_required")) ); };
const needsGrant = (item: MigrationItem) => item.migration.status === "success" && grantRefused(item.migration.jobStatus, item.migration.jobError);
const isRunning = $derived(running ?? items.some(itemRunning)); const rowAuthorize = $derived(onAuthorize !== undefined && items.some(needsGrant));
const summary = $derived.by(() => { if (!items.length) return ""; const failed = items.filter(itemFailed).length; const active = items.filter(itemRunning).length; const done = items.length - failed - active; return `${items.length} ${plural(items.length, "migration", "migrations")}: ${done} done, ${failed} failed, ${active} in progress.`; });</script>
<FormStep step={number} {title} description="Follow the progress of your migrations." last spacing="none" class={disabled && items.length === 0 ? "pointer-events-none opacity-50" : undefined}> <p role="status" class="sr-only">{summary}</p> {#if !items.length} <p class="typography-paragraph-small text-foreground-muted">No migrations started yet.</p> {:else} {#if bannerVisible && isRunning} <div class="mb-sm"> <StartedBanner onDismiss={() => (bannerVisible = false)} /> </div> {/if} <div class="flex flex-col gap-xs" data-testid="migrationRows"> {#each items as item (item.id)} {@const m = item.migration} {@const failed = itemFailed(item)} {@const errorId = `migration-error-${item.id}`} <MigrationRow label={item.name} state={failed ? "failed" : m.status === "success" && m.jobStatus === "completed" ? "done" : "running"} percent={m.status === "success" && m.jobStatus === "completed" ? 100 : undefined} stage={!failed && m.status === "success" && m.jobStatus ? stageLabel(m.jobStatus) : undefined} href={userHandle && m.status === "success" && m.jobStatus === "completed" && m.task ? resolve(`/${userHandle}/${m.task.name}` as "/") : undefined} > {#snippet error()} <div id={errorId} class="flex flex-col gap-3xs"> {#if m.status === "error"} <p class="wrap-anywhere text-foreground-danger">{m.error}</p> {:else if needsGrant(item)} <p class="text-foreground-danger"> The migrator lost access to your Tangled or GitHub account. Authorize it again, then retry this migration. </p> {:else} <p class="text-foreground-danger"> Unable to migrate this repository. Retry the migration. </p> {#if m.jobError} <p class="line-clamp-2 wrap-anywhere text-foreground-muted" title={m.jobError} > {m.jobError} </p> {/if} {/if} </div> {/snippet} {#snippet action()} {#if needsGrant(item) && onAuthorize} <Button size="small" icon={UserKey} aria-describedby={errorId} onclick={onAuthorize} > Authorize<span class="sr-only"> the migrator</span> </Button> {:else if failed && onRetry} <Button size="small" icon={RotateCcw} disabled={submitting} aria-describedby={errorId} onclick={() => onRetry(item)} > Retry<span class="sr-only"> {item.name}</span> </Button> {/if} {/snippet} </MigrationRow> {/each} </div> <div class="mt-md flex flex-wrap justify-end gap-xs"> {#if onAuthorize && !rowAuthorize} <Button icon={UserKey} onclick={onAuthorize}>Authorize the migrator</Button> {/if} <Button href={userHandle ? (resolve(`/${userHandle}?tab=repos` as "/") as `/${string}`) : undefined} icon={CircleUserRound} > All my repositories<ArrowRight class="size-4" aria-hidden="true" /> </Button> </div> {/if}</FormStep>