mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-11-15 08:00:36 +08:00
184 lines
4.8 KiB
Svelte
184 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import type { Author } from "@bindings/cob/Author";
|
|
|
|
import { authorForNodeId, parseNodeId } from "@app/lib/utils";
|
|
|
|
import Icon from "@app/components/Icon.svelte";
|
|
import NodeId from "@app/components/NodeId.svelte";
|
|
import TextInput from "@app/components/TextInput.svelte";
|
|
|
|
interface Props {
|
|
allowedToEdit: boolean;
|
|
assignees: Author[];
|
|
submitInProgress: boolean;
|
|
save: (updatedAssignees: string[]) => void;
|
|
}
|
|
|
|
const {
|
|
allowedToEdit = false,
|
|
assignees,
|
|
submitInProgress = false,
|
|
save,
|
|
}: Props = $props();
|
|
|
|
let updatedAssignees: Author[] = $state([]);
|
|
let showInput: boolean = $state(false);
|
|
let inputValue = $state("");
|
|
let validationMessage: string | undefined = $state(undefined);
|
|
let valid: boolean = $state(false);
|
|
let assignee: string | undefined = undefined;
|
|
|
|
let removeToggles: Record<string, boolean> = $state({});
|
|
|
|
$effect(() => {
|
|
// Reset component state whenever the assignees change in the parent. This
|
|
// happens when the issue ID changes for example when the user navigates
|
|
// to a different issue via the sidebar.
|
|
updatedAssignees = assignees;
|
|
|
|
showInput = false;
|
|
validationMessage = undefined;
|
|
valid = true;
|
|
removeToggles = {};
|
|
});
|
|
|
|
$effect(() => {
|
|
if (inputValue === "") {
|
|
validationMessage = "";
|
|
valid = true;
|
|
} else {
|
|
const parsedNodeId = parseNodeId(inputValue);
|
|
if (parsedNodeId) {
|
|
assignee = `${parsedNodeId.prefix}${parsedNodeId.pubkey}`;
|
|
if (updatedAssignees.find(({ did }) => did === assignee)) {
|
|
validationMessage = "This assignee is already added";
|
|
valid = false;
|
|
} else {
|
|
validationMessage = undefined;
|
|
valid = true;
|
|
}
|
|
} else {
|
|
validationMessage = "This is not a valid DID";
|
|
valid = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
function addAssignee() {
|
|
if (valid && assignee) {
|
|
updatedAssignees = [...updatedAssignees, { did: assignee }];
|
|
inputValue = "";
|
|
save($state.snapshot(updatedAssignees.map(x => x.did)));
|
|
showInput = false;
|
|
}
|
|
}
|
|
|
|
function removeAssignee(assignee: Author) {
|
|
updatedAssignees = updatedAssignees.filter(
|
|
({ did }) => did !== assignee.did,
|
|
);
|
|
save($state.snapshot(updatedAssignees.map(x => x.did)));
|
|
showInput = false;
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.header {
|
|
font-size: var(--font-size-small);
|
|
margin-bottom: 0.5rem;
|
|
color: var(--color-foreground-dim);
|
|
}
|
|
.body {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
flex-direction: row;
|
|
gap: 0.5rem;
|
|
font-size: var(--font-size-small);
|
|
}
|
|
.validation-message {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
color: var(--color-foreground-red);
|
|
position: relative;
|
|
margin-top: 0.5rem;
|
|
}
|
|
button {
|
|
border: 0;
|
|
cursor: pointer;
|
|
gap: 0.5rem;
|
|
background-color: transparent;
|
|
border: none;
|
|
display: flex;
|
|
color: var(--color-foreground-default);
|
|
}
|
|
</style>
|
|
|
|
<div style:width="100%">
|
|
<div class="global-flex" style:align-items="flex-start">
|
|
<div class="header">Assignees</div>
|
|
|
|
{#if allowedToEdit}
|
|
<div class="global-flex" style:margin-left="auto">
|
|
{#if showInput}
|
|
<Icon onclick={addAssignee} name="checkmark" styleCursor="pointer" />
|
|
<Icon
|
|
onclick={() => {
|
|
inputValue = "";
|
|
showInput = false;
|
|
}}
|
|
name="cross"
|
|
styleCursor="pointer" />
|
|
{:else}
|
|
<Icon
|
|
name="plus"
|
|
onclick={() => (showInput = true)}
|
|
styleCursor="pointer"></Icon>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="body">
|
|
{#if allowedToEdit}
|
|
{#each updatedAssignees as assignee}
|
|
<button
|
|
class="txt-small"
|
|
onclick={() =>
|
|
(removeToggles[assignee.did] = !removeToggles[assignee.did])}>
|
|
<NodeId {...authorForNodeId(assignee)} />
|
|
{#if removeToggles[assignee.did]}
|
|
<Icon name="cross" onclick={() => removeAssignee(assignee)} />
|
|
{/if}
|
|
</button>
|
|
{:else}
|
|
<div class="txt-missing">Not assigned to anyone.</div>
|
|
{/each}
|
|
{:else}
|
|
{#each updatedAssignees as assignee}
|
|
<NodeId {...authorForNodeId(assignee)} />
|
|
{:else}
|
|
<div class="txt-missing">Not assigned to anyone.</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
|
|
{#if showInput}
|
|
<div style:margin-top="0.5rem">
|
|
<TextInput
|
|
autofocus
|
|
{valid}
|
|
disabled={submitInProgress}
|
|
placeholder="Add assignee"
|
|
bind:value={inputValue}
|
|
onSubmit={addAssignee} />
|
|
{#if !valid && validationMessage}
|
|
<div class="validation-message">
|
|
<Icon name="warning" />{validationMessage}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|