Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Improve assignee and label input
Sebastian Martinez committed 2 years ago
commit 55f8f4ba8756e65d3386be4338da9dc5bc22f1ac
parent 2b03aec64515ae9fa6e7bd4d9f1bfa7131c64e9f
4 files changed +119 -144
modified src/components/Badge.svelte
@@ -3,12 +3,14 @@
    | "caution"
    | "foreground"
    | "background"
+
    | "outline"
    | "yellowOutline"
    | "neutral"
    | "negative"
    | "positive"
    | "primary"
    | "secondary";
+
  export let round: boolean = false;
  export let style: string | undefined = undefined;
  export let size: "tiny" | "small" | "medium" = "tiny";
  export let title: string | undefined = undefined;
@@ -25,7 +27,8 @@
    white-space: nowrap;
    align-items: center;
    gap: 0.25rem;
-
    max-width: 13.5rem;
+
    /* This is aprox. a DID assignee */
+
    max-width: 16rem;
  }
  .background {
    color: currentColor;
@@ -52,6 +55,13 @@
    color: var(--color-foreground-yellow);
    background-color: transparent;
  }
+
  .outline {
+
    border: 1px solid var(--color-border-hint);
+
    background-color: transparent;
+
  }
+
  .outline:hover {
+
    border-color: var(--color-fill-secondary);
+
  }
  .negative {
    color: var(--color-foreground-match-background);
    background-color: var(--color-foreground-red);
@@ -80,21 +90,30 @@
    font-size: var(--font-size-small);
    padding: 0.75rem 1rem;
  }
+
  .round {
+
    aspect-ratio: 1/1;
+
    justify-content: center;
+
    padding: unset;
+
  }
</style>

+
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
  role="button"
  tabindex="0"
+
  on:click
  on:mouseenter
  on:mouseleave
  class="badge"
  {style}
  {title}
+
  class:round
  class:tiny={size === "tiny"}
  class:small={size === "small"}
  class:medium={size === "medium"}
  class:caution={variant === "caution"}
  class:yellow-outline={variant === "yellowOutline"}
+
  class:outline={variant === "outline"}
  class:background={variant === "background"}
  class:foreground={variant === "foreground"}
  class:neutral={variant === "neutral"}
modified src/views/projects/Cob/AssigneeInput.svelte
@@ -11,17 +11,19 @@

  const dispatch = createEventDispatcher<{ save: string[] }>();

-
  export let mode: "readCreate" | "readEdit" | "readOnly" = "readOnly";
  export let locallyAuthenticated: boolean = false;
  export let assignees: string[] = [];
  export let submitInProgress: boolean = false;

+
  let showInput: boolean = false;
  let updatedAssignees: string[] = assignees;
  let inputValue = "";
  let validationMessage: string | undefined = undefined;
  let valid: boolean = false;
  let assignee: string | undefined = undefined;

+
  const removeToggles: Record<string, boolean> = {};
+

  $: {
    if (inputValue !== "") {
      const parsedNodeId = parseNodeId(inputValue);
@@ -48,35 +50,23 @@
    if (valid && assignee) {
      updatedAssignees = [...updatedAssignees, assignee];
      inputValue = "";
-
      if (mode === "readCreate") {
-
        dispatch("save", updatedAssignees);
-
      }
+
      dispatch("save", updatedAssignees);
+
      showInput = false;
    }
  }

  function removeAssignee(assignee: string) {
    updatedAssignees = updatedAssignees.filter(x => x !== assignee);
-
    if (mode === "readCreate") {
-
      dispatch("save", updatedAssignees);
-
    }
+
    dispatch("save", updatedAssignees);
+
    showInput = false;
  }
</script>

<style>
  .header {
-
    display: flex;
-
    gap: 0.5rem;
-
    align-items: center;
    font-size: var(--font-size-small);
    margin-bottom: 0.75rem;
  }
-
  .actions {
-
    margin-left: auto;
-
    display: flex;
-
    align-items: center;
-
    justify-content: center;
-
    gap: 0.5rem;
-
  }
  .body {
    display: flex;
    flex-wrap: wrap;
@@ -92,61 +82,61 @@
</style>

<div>
-
  <div class="header">
-
    <span>Assignees</span>
+
  <div class="header">Assignees</div>
+
  <div class="body">
    {#if locallyAuthenticated}
-
      <div class="actions">
-
        {#if mode === "readEdit"}
-
          <IconButton
-
            title="save assignees"
-
            loading={submitInProgress}
-
            on:click={() => {
-
              dispatch("save", updatedAssignees);
-
              mode = "readOnly";
-
            }}>
-
            <IconSmall name="checkmark" />
-
          </IconButton>
+
      {#each updatedAssignees as assignee}
+
        <Badge
+
          variant="neutral"
+
          size="small"
+
          style="cursor: pointer;"
+
          on:click={() => (removeToggles[assignee] = !removeToggles[assignee])}>
+
          <div class="assignee">
+
            <Avatar inline nodeId={assignee} />
+
            <span>{formatNodeId(assignee)}</span>
+
            {#if removeToggles[assignee]}
+
              <IconButton title="remove assignee">
+
                <IconSmall
+
                  name="cross"
+
                  on:click={() => removeAssignee(assignee)} />
+
              </IconButton>
+
            {/if}
+
          </div>
+
        </Badge>
+
      {/each}
+
      {#if showInput}
+
        <div style="width:100%; display: flex; align-items: center;">
+
          <TextInput
+
            {valid}
+
            {validationMessage}
+
            autofocus
+
            disabled={submitInProgress}
+
            bind:value={inputValue}
+
            placeholder="Add assignee"
+
            on:submit={addAssignee} />
          <IconButton
-
            title="dismiss changes"
-
            loading={submitInProgress}
            on:click={() => {
-
              updatedAssignees = assignees;
              inputValue = "";
-
              mode = "readOnly";
+
              showInput = false;
            }}>
            <IconSmall name="cross" />
          </IconButton>
-
        {:else if mode !== "readCreate"}
-
          <IconButton
-
            title="edit assignees"
-
            loading={submitInProgress}
-
            on:click={() => (mode = "readEdit")}>
-
            <IconSmall name="edit" />
+
          <IconButton on:click={addAssignee}>
+
            <IconSmall name="checkmark" />
          </IconButton>
-
        {/if}
-
      </div>
-
    {/if}
-
  </div>
-
  <div class="body">
-
    {#if locallyAuthenticated && (mode === "readCreate" || mode === "readEdit")}
-
      {#each updatedAssignees as assignee}
-
        <Badge variant="neutral">
-
          <div class="assignee">
-
            <Avatar inline nodeId={assignee} />
-
            <span>{formatNodeId(assignee)}</span>
-
            <IconButton title="remove assignee">
-
              <IconSmall
-
                name="cross"
-
                on:click={() => removeAssignee(assignee)} />
-
            </IconButton>
-
          </div>
-
        </Badge>
+
        </div>
      {:else}
-
        <div class="txt-missing">No assignees</div>
-
      {/each}
+
        <Badge
+
          variant="outline"
+
          size="small"
+
          round
+
          on:click={() => (showInput = true)}>
+
          <IconSmall name="plus" />
+
        </Badge>
+
      {/if}
    {:else}
      {#each updatedAssignees as assignee}
-
        <Badge variant="neutral">
+
        <Badge variant="neutral" size="small">
          <div class="assignee">
            <Avatar inline nodeId={assignee} />
            <span>{formatNodeId(assignee)}</span>
@@ -157,15 +147,4 @@
      {/each}
    {/if}
  </div>
-
  {#if locallyAuthenticated && (mode === "readCreate" || mode === "readEdit")}
-
    <div style:margin-bottom="1rem" style:margin-top="1rem">
-
      <TextInput
-
        {valid}
-
        {validationMessage}
-
        disabled={submitInProgress}
-
        bind:value={inputValue}
-
        placeholder="Add assignee"
-
        on:submit={addAssignee} />
-
    </div>
-
  {/if}
</div>
modified src/views/projects/Cob/LabelInput.svelte
@@ -8,17 +8,19 @@

  const dispatch = createEventDispatcher<{ save: string[] }>();

-
  export let mode: "readCreate" | "readEdit" | "readOnly" = "readOnly";
  export let locallyAuthenticated: boolean = false;
  export let labels: string[] = [];
  export let submitInProgress: boolean = false;

+
  let showInput: boolean = false;
  let updatedLabels: string[] = labels;
  let inputValue = "";
  let validationMessage: string | undefined = undefined;
  let valid: boolean = false;
  let sanitizedValue: string | undefined = undefined;

+
  const removeToggles: Record<string, boolean> = {};
+

  $: {
    sanitizedValue = inputValue.trim();

@@ -42,110 +44,87 @@
    if (valid && sanitizedValue) {
      updatedLabels = [...updatedLabels, sanitizedValue];
      inputValue = "";
-
      if (mode === "readCreate") {
-
        dispatch("save", updatedLabels);
-
      }
+
      dispatch("save", updatedLabels);
+
      showInput = false;
    }
  }

  function removeLabel(label: string) {
    updatedLabels = updatedLabels.filter(x => x !== label);
-
    if (mode === "readCreate") {
-
      dispatch("save", updatedLabels);
-
    }
+
    dispatch("save", updatedLabels);
+
    showInput = false;
  }
</script>

<style>
  .metadata-section-header {
-
    display: flex;
-
    gap: 1rem;
-
    align-items: center;
    font-size: var(--font-size-small);
    margin-bottom: 0.75rem;
  }
  .metadata-section-body {
    display: flex;
+
    align-items: center;
    flex-wrap: wrap;
    flex-direction: row;
    gap: 0.5rem;
  }
-
  .actions {
-
    margin-left: auto;
-
    display: flex;
-
    align-items: center;
-
    justify-content: center;
-
    gap: 0.5rem;
-
  }
</style>

<div>
-
  <div class="metadata-section-header">
-
    <span>Labels</span>
+
  <div class="metadata-section-header">Labels</div>
+
  <div class="metadata-section-body">
    {#if locallyAuthenticated}
-
      <div class="actions">
-
        {#if mode === "readEdit"}
-
          <IconButton
-
            loading={submitInProgress}
-
            title="save labels"
-
            on:click={() => {
-
              dispatch("save", updatedLabels);
-
              mode = "readOnly";
-
            }}>
-
            <IconSmall name="checkmark" />
-
          </IconButton>
+
      {#each updatedLabels as label}
+
        <Badge
+
          variant="neutral"
+
          size="small"
+
          style="cursor: pointer;"
+
          on:click={() => (removeToggles[label] = !removeToggles[label])}>
+
          <div aria-label="chip" class="label">{label}</div>
+
          {#if removeToggles[label]}
+
            <IconButton title="remove label">
+
              <IconSmall name="cross" on:click={() => removeLabel(label)} />
+
            </IconButton>
+
          {/if}
+
        </Badge>
+
      {/each}
+
      {#if showInput}
+
        <div style="width:100%; display: flex; align-items: center;">
+
          <TextInput
+
            autofocus
+
            {valid}
+
            {validationMessage}
+
            disabled={submitInProgress}
+
            size="small"
+
            placeholder="Add label"
+
            bind:value={inputValue}
+
            on:submit={addLabel} />
          <IconButton
-
            loading={submitInProgress}
-
            title="dismiss changes"
            on:click={() => {
-
              updatedLabels = labels;
              inputValue = "";
-
              mode = "readOnly";
+
              showInput = false;
            }}>
            <IconSmall name="cross" />
          </IconButton>
-
        {:else if mode !== "readCreate"}
-
          <IconButton
-
            loading={submitInProgress}
-
            title="edit labels"
-
            on:click={() => (mode = "readEdit")}>
-
            <IconSmall name="edit" />
-
          </IconButton>
-
        {/if}
-
      </div>
-
    {/if}
-
  </div>
-
  <div class="metadata-section-body">
-
    {#if locallyAuthenticated && (mode === "readCreate" || mode === "readEdit")}
-
      {#each updatedLabels as label}
-
        <Badge variant="neutral">
-
          <div aria-label="chip" class="label">{label}</div>
-
          <IconButton title="remove label">
-
            <IconSmall name="cross" on:click={() => removeLabel(label)} />
+
          <IconButton on:click={addLabel}>
+
            <IconSmall name="checkmark" />
          </IconButton>
-
        </Badge>
+
        </div>
      {:else}
-
        <div class="txt-missing">No labels</div>
-
      {/each}
+
        <Badge
+
          variant="outline"
+
          size="small"
+
          round
+
          on:click={() => (showInput = true)}>
+
          <IconSmall name="plus"></IconSmall>
+
        </Badge>
+
      {/if}
    {:else}
      {#each updatedLabels as label}
-
        <Badge variant="neutral">
+
        <Badge variant="neutral" size="small">
          {label}
        </Badge>
-
      {:else}
-
        <div class="txt-missing">No labels</div>
      {/each}
    {/if}
  </div>
-
  {#if locallyAuthenticated && (mode === "readCreate" || mode === "readEdit")}
-
    <div style:margin-bottom="2rem" style:margin-top="1rem">
-
      <TextInput
-
        {valid}
-
        {validationMessage}
-
        disabled={submitInProgress}
-
        bind:value={inputValue}
-
        placeholder="Add label"
-
        on:submit={addLabel} />
-
    </div>
-
  {/if}
</div>
modified src/views/projects/Issue/New.svelte
@@ -232,12 +232,10 @@
        </div>
        <div class="metadata">
          <AssigneeInput
-
            mode="readCreate"
            locallyAuthenticated={Boolean(session)}
            on:save={({ detail: updatedAssignees }) =>
              (assignees = updatedAssignees)} />
          <LabelInput
-
            mode="readCreate"
            locallyAuthenticated={Boolean(session)}
            on:save={({ detail: updatedLabels }) => (labels = updatedLabels)} />
        </div>