~james/core

core/web/src/lib/components/repo/SubjectLabelPanel.svelte -rw-r--r-- 3.2 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 16 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<script lang="ts">
	import type { ResolvedPathname } from "$app/types";
	import {
		BASIC_LABEL_VALUE,
		createRepoLabel,
		type AppliedLabels,
		type LabelView,
		type SubjectLabels
	} from "#lib/api/labels.ts";
	import { didOf, labelKeyOf, type KnotOrigin } from "#lib/api/syntax.ts";
	import { cobRefOf } from "#lib/api/cob.ts";
	import { setTicketLabels } from "#lib/api/ticket.ts";
	import StaleReload from "#lib/components/ui/StaleReload.svelte";
	import { didFromUri, rkeyFromUri } from "#lib/api/uri.ts";
	import { getAuth } from "#lib/auth.svelte.ts";
	import LabelPanel, { type Label } from "#lib/components/repo/LabelPanel.svelte";
	import { labelHref } from "#lib/ticketQuery.ts";

	interface Props {
		subject: string;
		subjectCid?: string | null;
		// on a knot repo the uri authority is the repo did, not the owner
		isOwner: boolean;
		knot?: KnotOrigin;
		labels: SubjectLabels;
		listHref: string;
		settingsHref: string;
		canEdit?: boolean;
		summarize?: boolean;
		onwritten?: (cid: string) => void;
	}

	let {
		subject,
		subjectCid,
		isOwner,
		knot,
		labels,
		listHref,
		settingsHref,
		canEdit = false,
		summarize = false,
		onwritten
	}: Props = $props();

	const auth = getAuth();

	let applied = $derived<AppliedLabels>(labels.applied);

	let created = $state<LabelView[]>([]);
	let failure = $state<unknown>();

	const allLabels = $derived<Label[]>(
		[...labels.definitions, ...created].map((definition) => ({
			key: definition.uri,
			name: definition.value.name,
			color: definition.value.color ?? "",
			href: labelHref(listHref, definition.value.name) as ResolvedPathname
		}))
	);

	const picked = $derived(allLabels.filter((label) => applied[label.key]?.length));

	const change = async (keys: string[]) => {
		const agent = auth.agent;
		if (!agent || !knot) return;
		const before = applied;
		const current = picked.map((label) => label.key);
		const add = keys.filter((key) => !current.includes(key));
		const remove = current.filter((key) => !keys.includes(key));
		if (add.length === 0 && remove.length === 0) return;
		const next = { ...before };
		for (const key of add) next[key] = [BASIC_LABEL_VALUE];
		for (const key of remove) delete next[key];
		applied = next;
		failure = undefined;
		try {
			const written = await setTicketLabels(
				agent,
				knot,
				cobRefOf({ uri: subject, cid: subjectCid }),
				keys.map((key) => labelKeyOf(rkeyFromUri(key)))
			);
			onwritten?.(written.cid);
		} catch (cause) {
			applied = before;
			failure = cause;
		}
	};

	const createLabel = async (name: string, color: string) => {
		const agent = auth.agent;
		if (!agent || !knot) return;
		const repoDid = didOf(didFromUri(subject));
		const label = await createRepoLabel(agent, knot, repoDid, auth.bobbinUrl, {
			name,
			color
		}).catch(() => null);
		if (!label) return;
		created = [...created, label];
		await change([...picked.map((current) => current.key), label.uri]);
	};
</script>

<LabelPanel
	labels={picked}
	{allLabels}
	canEdit={canEdit && !!auth.agent}
	onLabelsChange={change}
	onCreateLabel={canEdit && isOwner ? createLabel : undefined}
	settingsHref={canEdit && isOwner ? settingsHref : undefined}
	{summarize}
/>
<StaleReload cause={failure} message />