~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
203 lines · 6.0 KB Raw History
<script lang="ts">
	import type { RepoViewBasic, RepoViewDetailed } from '#lib/lexicons/dev/knotless/defs.ts';
	import type * as archiveRepo from '#lib/lexicons/dev/knotless/archiveRepo.ts';
	import type * as deleteRepo from '#lib/lexicons/dev/knotless/deleteRepo.ts';
	import type * as transferRepo from '#lib/lexicons/dev/knotless/transferRepo.ts';
	import { goto, invalidateAll } from '$app/navigation';
	import { call } from '#lib/data.ts';
	import { urlPath } from '#lib/format.ts';
	import type { AtIdentifierString } from '@atproto/lex';
	import HandleInput from '#lib/molecules/HandleInput.svelte';
	import Dialog from '#lib/molecules/Dialog.svelte';
	/**
	 * What cannot be taken back, or not easily: offering `repo`, owned as
	 * `authority` names it, to another account, or withdrawing the offer
	 * it has, `offer`, as `knotless repo transfer` does; archiving it, making
	 * it read only, or unarchiving it, as `knotless repo archive` does; and
	 * deleting it, once
	 * its full name is typed, as `knotless repo delete` does. Transferring and
	 * deleting it are its owner's alone: unless it `owns` it, an admin of
	 * the organization that owns it archives it but is told who does those.
	 */
	let {
		repo,
		authority,
		offer,
		owns = true
	}: {
		repo: RepoViewDetailed;
		authority: string;
		offer?: RepoViewBasic;
		owns?: boolean;
	} = $props();
	const full = $derived(`${authority}/${repo.name}`);
	let to = $state('');
	let as = $state('');
	let typed = $state('');
	let problem = $state('');
	let archiveProblem = $state('');
	let deleteProblem = $state('');
	// Which is under way: 'offer', 'withdraw', 'archive' or 'delete'.
	let busy = $state('');
	async function transfer(e?: SubmitEvent) {
		e?.preventDefault();
		busy = e ? 'offer' : 'withdraw';
		problem = '';
		try {
			const input = e
				? { repo: repo.uri, to: to.trim() as AtIdentifierString, name: as.trim() || undefined }
				: { repo: repo.uri };
			await call<typeof transferRepo.main>('dev.knotless.transferRepo', input);
			await invalidateAll();
			to = as = '';
		} catch (err) {
			problem = (err as Error).message;
		}
		busy = '';
	}
	async function archive() {
		busy = 'archive';
		archiveProblem = '';
		try {
			await call<typeof archiveRepo.main>('dev.knotless.archiveRepo', {
				repo: repo.uri,
				unarchive: !!repo.archivedAt
			});
			await invalidateAll();
		} catch (err) {
			archiveProblem = (err as Error).message;
		}
		busy = '';
	}
	async function remove(e: SubmitEvent) {
		e.preventDefault();
		busy = 'delete';
		deleteProblem = '';
		try {
			await call<typeof deleteRepo.main>('dev.knotless.deleteRepo', { repo: repo.uri });
			await goto(`/${urlPath(authority)}`);
		} catch (err) {
			deleteProblem = (err as Error).message;
		}
		busy = '';
	}
</script>

<section class="setting danger">
	<h2>Danger zone</h2>
	{#if owns}
		<div class="danger-row">
			<div>
				<h3>Transfer ownership</h3>
				{#if offer}
					<p class="hint">
						{`Offered to ${offer.handle}, as ${offer.handle}/${offer.name}: it moves once they accept it, from this repository's page.`}
					</p>
				{:else}
					<p class="hint">
						Offer it to another account. It moves, with its pulls, issues and forks, once they
						accept; until then it stays here.
					</p>
				{/if}
			</div>
			{#if offer}<button
					class="btn"
					type="button"
					disabled={!!busy}
					aria-busy={busy === 'withdraw'}
					onclick={() => transfer()}
					>{busy === 'withdraw' ? 'Withdrawing…' : 'Withdraw offer'}</button
				>{/if}
		</div>
		{#if !offer}
			<form class="form transfer" onsubmit={transfer}>
				<label for="transfer-to">Account</label>
				<HandleInput id="transfer-to" name="to" autocomplete="off" bind:value={to} />
				<label for="transfer-name">Name there <span class="mute">(optional)</span></label>
				<input
					id="transfer-name"
					name="name"
					placeholder={repo.name}
					maxlength="100"
					autocapitalize="none"
					spellcheck="false"
					bind:value={as}
				/>
				<div>
					<button class="btn danger" type="submit" disabled={!!busy} aria-busy={busy === 'offer'}
						>{busy === 'offer' ? 'Offering…' : 'Offer it'}</button
					>
				</div>
			</form>
		{/if}
		{#if problem}<p class="problem" role="alert">{problem}</p>{/if}
	{/if}
	<div class="danger-row">
		<div>
			{#if repo.archivedAt}
				<h3>Unarchive this repository</h3>
				<p class="hint">
					It is read only now. Unarchiving lets pushes, pulls, issues and comments in again, and its
					workflows run again.
				</p>
			{:else}
				<h3>Archive this repository</h3>
				<p class="hint">
					Make it read only. Pushes, pulls, issues and comments are turned away, and its workflows
					stop. You can unarchive it later.
				</p>
			{/if}
		</div>
		<button
			class="btn danger"
			type="button"
			disabled={!!busy}
			aria-busy={busy === 'archive'}
			onclick={archive}
			>{busy === 'archive'
				? repo.archivedAt
					? 'Unarchiving…'
					: 'Archiving…'
				: repo.archivedAt
					? 'Unarchive'
					: 'Archive'}</button
		>
	</div>
	{#if archiveProblem}<p class="problem" role="alert">{archiveProblem}</p>{/if}
	{#if owns}
		<div class="danger-row">
			<div>
				<h3>Delete this repository</h3>
				<p class="hint">
					Its history, branches and tags go for good. Pulls, issues and comments others wrote stay
					in their accounts.
				</p>
			</div>
			<button class="btn danger" type="button" popovertarget="delete-repo">Delete</button>
		</div>
	{:else}
		<p class="hint">{`Only ${authority} itself can transfer or delete this repository.`}</p>
	{/if}
</section>

<Dialog id="delete-repo" title={`Delete ${full}`}>
	<form onsubmit={remove}>
		<label for="delete-typed">{`To confirm, type ${full}`}</label>
		<input
			id="delete-typed"
			name="confirm"
			autocapitalize="none"
			autocomplete="off"
			spellcheck="false"
			bind:value={typed}
		/>
		{#if deleteProblem}<p class="problem" role="alert">{deleteProblem}</p>{/if}
		<button
			class="btn danger solid"
			type="submit"
			disabled={!!busy || typed !== full}
			aria-busy={busy === 'delete'}
			>{busy === 'delete' ? 'Deleting…' : 'Delete this repository'}</button
		>
	</form>
</Dialog>