mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-30 08:40:52 +08:00
Redesign the entire app with a new UI kit. Typography, colors, icons, avatars, and border radii have all been updated for a more polished, consistent appearance that matches our other products like radicle-explorer. - Replace blockies-based avatars with a new DID-based avatar system - Move Settings and Create Issue into fullscreen modals - Change the default theme to follow the system theme - Remove window decorations on macOS for a cleaner, more native feel - Update the app icon - Fix popovers and tooltips Navigation: - Rewrite app navigation and layouts - Add a sidebar that gives you access to all your repos - Change the startup screen to open directly to the notification inbox - Add a getting started guide shown to new users on first launch Settings: - Add a setting to disable the notification badge - Add a code font setting Bug fixes: - Fix reactions and edits incorrectly targeting the root comment instead of the intended reply - Restrict issue/patch state, labels and assignees to delegates only
198 lines
4.6 KiB
Svelte
198 lines
4.6 KiB
Svelte
<script lang="ts">
|
|
import Button from "@app/components/Button.svelte";
|
|
import Icon from "@app/components/Icon.svelte";
|
|
import Label from "@app/components/Label.svelte";
|
|
import TextInput from "@app/components/TextInput.svelte";
|
|
|
|
interface Props {
|
|
allowedToEdit: boolean;
|
|
labels: string[];
|
|
submitInProgress: boolean;
|
|
save: (updatedLabels: string[]) => void;
|
|
preview?: boolean;
|
|
}
|
|
|
|
const {
|
|
allowedToEdit = false,
|
|
labels,
|
|
submitInProgress = false,
|
|
save,
|
|
preview = false,
|
|
}: Props = $props();
|
|
|
|
let updatedLabels: string[] = $state([]);
|
|
let showInput: boolean = $state(false);
|
|
let inputValue = $state("");
|
|
let validationMessage: string | undefined = $state();
|
|
let valid: boolean = $state(false);
|
|
|
|
const sanitizedValue = $derived(inputValue.trim());
|
|
|
|
let removeToggles: Record<string, boolean> = $state({});
|
|
|
|
$effect(() => {
|
|
// Reset component state whenever the labels change in the parent. This
|
|
// happens when the issue ID changes for example when the user navigates
|
|
// to a different issue via the sidebar.
|
|
updatedLabels = labels;
|
|
|
|
showInput = false;
|
|
validationMessage = undefined;
|
|
valid = true;
|
|
removeToggles = {};
|
|
});
|
|
|
|
$effect(() => {
|
|
if (inputValue !== "") {
|
|
if (sanitizedValue.length > 0) {
|
|
if (updatedLabels.includes(sanitizedValue)) {
|
|
valid = false;
|
|
validationMessage = "This label is already assigned";
|
|
} else {
|
|
valid = true;
|
|
validationMessage = undefined;
|
|
}
|
|
}
|
|
} else {
|
|
valid = true;
|
|
validationMessage = "";
|
|
}
|
|
});
|
|
|
|
function addLabel() {
|
|
if (valid && sanitizedValue) {
|
|
updatedLabels = [...updatedLabels, sanitizedValue].sort();
|
|
inputValue = "";
|
|
save($state.snapshot(updatedLabels));
|
|
showInput = false;
|
|
}
|
|
}
|
|
|
|
function removeLabel(label: string) {
|
|
updatedLabels = updatedLabels.filter(x => x !== label);
|
|
save($state.snapshot(updatedLabels));
|
|
showInput = false;
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
.validation-message {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
color: var(--color-feedback-error-text);
|
|
position: relative;
|
|
margin-top: 0.5rem;
|
|
}
|
|
.removable-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
cursor: pointer;
|
|
}
|
|
.input-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
button {
|
|
border: 0;
|
|
cursor: pointer;
|
|
gap: 0.5rem;
|
|
background-color: transparent;
|
|
border: none;
|
|
display: flex;
|
|
color: var(--color-text-secondary);
|
|
padding: 0;
|
|
align-items: center;
|
|
}
|
|
</style>
|
|
|
|
{#if preview}
|
|
<div class="row">
|
|
<Button variant="outline" disabled>
|
|
<Icon name="label" />
|
|
{#if updatedLabels.length === 0}
|
|
Add labels
|
|
{:else}
|
|
Labels
|
|
{/if}
|
|
</Button>
|
|
{#each updatedLabels as label}
|
|
<Label {label} />
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<div class="row">
|
|
{#if showInput}
|
|
<div class="input-row">
|
|
<div style:flex="1" style:min-width="0">
|
|
<TextInput
|
|
autofocus
|
|
{valid}
|
|
disabled={submitInProgress}
|
|
placeholder="Add label"
|
|
bind:value={inputValue}
|
|
onSubmit={addLabel} />
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
onclick={() => {
|
|
showInput = false;
|
|
inputValue = "";
|
|
}}>
|
|
<Icon name="close" />
|
|
</Button>
|
|
</div>
|
|
{:else}
|
|
<Button
|
|
variant="outline"
|
|
disabled={!allowedToEdit}
|
|
title={allowedToEdit
|
|
? undefined
|
|
: "Only delegates are allowed to add labels"}
|
|
onclick={() => {
|
|
inputValue = "";
|
|
showInput = true;
|
|
}}>
|
|
<Icon name="label" />
|
|
{#if updatedLabels.length === 0}
|
|
Add labels
|
|
{:else}
|
|
Labels
|
|
{/if}
|
|
</Button>
|
|
{/if}
|
|
|
|
{#each updatedLabels as label}
|
|
{#if allowedToEdit}
|
|
<button
|
|
class="removable-label"
|
|
onclick={() => (removeToggles[label] = !removeToggles[label])}>
|
|
<Label {label} />
|
|
{#if removeToggles[label]}
|
|
<Icon name="close" onclick={() => removeLabel(label)} />
|
|
{/if}
|
|
</button>
|
|
{:else}
|
|
<Label {label} />
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
|
|
{#if !valid && validationMessage}
|
|
<div class="validation-message">
|
|
<Icon name="warning" />{validationMessage}
|
|
</div>
|
|
{/if}
|
|
{/if}
|