johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type { ActorView } from '#lib/lexicons/dev/knotless/defs.ts';
import type * as addRunner from '#lib/lexicons/dev/knotless/addRunner.ts';
import type * as removeRunner from '#lib/lexicons/dev/knotless/removeRunner.ts';
import type { AtIdentifierString, AtUriString } from '@atproto/lex';
import { call } from '#lib/data.ts';
import Avatar from '#lib/atoms/Avatar.svelte';
import Person from '#lib/molecules/Person.svelte';
import Empty from '#lib/molecules/Empty.svelte';
/**
* The runners the repository `repo` names: the accounts whose checks of
* it show, and who land its merge queue. Its owner, who `owns` it, adds
* and removes them, as `knotless runner list` does.
*/
let { repo, runners, owns }: { repo: AtUriString; runners: ActorView[]; owns: boolean } =
$props();
let named = $state<ActorView[] | null>(null);
const shown = $derived(named ?? runners);
let handle = $state('');
let problem = $state('');
let warning = $state('');
// What is under way: 'add', or the runner being removed.
let busy = $state('');
async function change(runner: AtIdentifierString, add: boolean) {
busy = add ? 'add' : runner;
problem = '';
warning = '';
try {
const nsid = add ? 'dev.knotless.addRunner' : 'dev.knotless.removeRunner';
const after = await call<typeof addRunner.main | typeof removeRunner.main>(nsid, {
repo,
runner
});
named = after.runners;
warning = after.warning ?? '';
if (add) handle = '';
} catch (e) {
problem = (e as Error).message;
}
busy = '';
}
function submit(e: SubmitEvent) {
e.preventDefault();
if (handle.trim()) change(handle.trim() as AtIdentifierString, true);
}
</script>
<div class="box runner-list">
<div class="boxhead">
<b>Runners</b><span class="mute"
>The accounts a knotless runner reports as: their checks of this repository show, and they
land its merge queue.</span
>
</div>
{#if shown.length}
<ul class="list">
{#each shown as r (r.did)}
<li>
<Avatar name={r.handle} did={r.did} src={r.avatar} /><Person handle={r.handle} /><code
class="mute clip">{r.did}</code
>{#if owns}<button
class="btn end"
type="button"
disabled={!!busy}
aria-busy={busy === r.did}
onclick={() => change(r.did as AtIdentifierString, false)}
>{busy === r.did ? 'Removing…' : 'Remove'}</button
>{/if}
</li>
{/each}
</ul>
{:else}
<Empty title="No runners yet"
>Nothing checks this repository's pushes and pulls, and merges wait for no checks.</Empty
>
{/if}
{#if owns}
<form class="boxhead boxfoot add-runner" onsubmit={submit}>
<input
name="runner"
placeholder="runner.example.com"
aria-label="The runner's handle or DID"
autocapitalize="none"
spellcheck="false"
bind:value={handle}
/>
<button
class="btn primary"
type="submit"
disabled={!!busy || !handle.trim()}
aria-busy={busy === 'add'}>{busy === 'add' ? 'Adding…' : 'Add runner'}</button
>
</form>
{/if}
{#if problem}<p class="problem" role="alert">{problem}</p>{/if}
{#if warning}<p class="warning" role="status">{warning}</p>{/if}
</div>