Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Add label and assignee inputs to the create issue view
Open rudolfs opened 1 year ago
8 files changed +114 -42 ece9ee1e 6bbaaa31
modified src/components/AssigneeInput.svelte
@@ -122,19 +122,18 @@
    {#if allowedToEdit}
      <div class="global-flex" style:margin-left="auto">
        {#if showInput}
-
          <Icon onclick={addAssignee} name="checkmark" styleCursor="pointer" />
+
          <Icon
+
            onclick={addAssignee}
+
            name="checkmark"
+
            disabled={!valid || inputValue === ""} />
          <Icon
            onclick={() => {
              inputValue = "";
              showInput = false;
            }}
-
            name="cross"
-
            styleCursor="pointer" />
+
            name="cross" />
        {:else}
-
          <Icon
-
            name="plus"
-
            onclick={() => (showInput = true)}
-
            styleCursor="pointer"></Icon>
+
          <Icon name="plus" onclick={() => (showInput = true)}></Icon>
        {/if}
      </div>
    {/if}
@@ -152,9 +151,10 @@
            <Icon name="cross" onclick={() => removeAssignee(assignee)} />
          {/if}
        </button>
-
      {:else}
-
        <div class="txt-missing">Not assigned to anyone.</div>
      {/each}
+
      {#if updatedAssignees.length === 0 && !showInput}
+
        <div class="txt-missing">Not assigned to anyone.</div>
+
      {/if}
    {:else}
      {#each updatedAssignees as assignee}
        <NodeId {...authorForNodeId(assignee)} />
modified src/components/Icon.svelte
@@ -4,7 +4,7 @@
  interface Props {
    size?: "16" | "32";
    onclick?: () => void;
-
    styleCursor?: "default" | "pointer" | "inherit";
+
    disabled?: boolean;
    name:
      | "arrow-left"
      | "arrow-right"
@@ -46,8 +46,8 @@
  const {
    size = "16",
    onclick = undefined,
-
    styleCursor = "inherit",
    name,
+
    disabled = false,
  }: Props = $props();
</script>

@@ -59,14 +59,29 @@
    -webkit-user-select: none;
    user-select: none;
  }
+
  .hoverable {
+
    color: var(--color-foreground-dim);
+
  }
+
  .hoverable:not(.disabled):hover {
+
    color: var(--color-foreground-default);
+
  }
+
  .disabled {
+
    color: var(--color-foreground-disabled);
+
  }
</style>

<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<svg
-
  style:cursor={styleCursor}
+
  style:cursor={onclick && !disabled ? "pointer" : "inherit"}
+
  class:hoverable={onclick}
+
  class:disabled
  role="img"
-
  {onclick}
+
  onclick={() => {
+
    if (onclick && !disabled) {
+
      onclick();
+
    }
+
  }}
  aria-label={`icon-${name}`}
  width={size}
  height={size}
modified src/components/IssueSecondColumn.svelte
@@ -8,6 +8,8 @@
  import Icon from "./Icon.svelte";
  import OutlineButton from "./OutlineButton.svelte";

+
  const activeRouteStore = router.activeRouteStore;
+

  interface Props {
    repo: RepoInfo;
    selectedIssueId?: string;
@@ -36,6 +38,7 @@

  <OutlineButton
    variant="ghost"
+
    disabled={$activeRouteStore.resource === "repo.createIssue"}
    onclick={() => {
      void router.push({
        resource: "repo.createIssue",
modified src/components/LabelInput.svelte
@@ -109,19 +109,18 @@
    {#if allowedToEdit}
      <div class="global-flex" style:margin-left="auto">
        {#if showInput}
-
          <Icon onclick={addLabel} name="checkmark" styleCursor="pointer" />
+
          <Icon
+
            onclick={addLabel}
+
            disabled={!valid || inputValue === ""}
+
            name="checkmark" />
          <Icon
            onclick={() => {
              inputValue = "";
              showInput = false;
            }}
-
            name="cross"
-
            styleCursor="pointer" />
+
            name="cross" />
        {:else}
-
          <Icon
-
            name="plus"
-
            onclick={() => (showInput = true)}
-
            styleCursor="pointer"></Icon>
+
          <Icon name="plus" onclick={() => (showInput = true)}></Icon>
        {/if}
      </div>
    {/if}
@@ -139,9 +138,10 @@
            <Icon name="cross" onclick={() => removeLabel(label)} />
          {/if}
        </button>
-
      {:else}
-
        <div class="txt-missing">No labels.</div>
      {/each}
+
      {#if updatedLabels.length === 0 && !showInput}
+
        <div class="txt-missing">No labels.</div>
+
      {/if}
    {:else}
      {#each updatedLabels as label}
        <Label {label} />
modified src/components/ReactionSelector.svelte
@@ -52,7 +52,7 @@
  {popoverPositionLeft}
  popoverPadding="0">
  {#snippet toggle(onclick)}
-
    <Icon name="face" {onclick} styleCursor="pointer" />
+
    <Icon name="face" {onclick} />
  {/snippet}
  {#snippet popover()}
    <Border variant="ghost">
modified src/components/Thread.svelte
@@ -111,7 +111,7 @@
        partial(editComment, root.id)}
      reactOnComment={reactOnComment && partial(reactOnComment, root.id)}>
      {#snippet actions()}
-
        <Icon name="reply" onclick={toggleReply} styleCursor="pointer" />
+
        <Icon name="reply" onclick={toggleReply} />
      {/snippet}
    </CommentComponent>
  </div>
modified src/views/repo/CreateIssue.svelte
@@ -1,5 +1,4 @@
<script lang="ts">
-
  import type { Author } from "@bindings/cob/Author";
  import type { Config } from "@bindings/config/Config";
  import type { Issue } from "@bindings/cob/issue/Issue";
  import type { RepoInfo } from "@bindings/repo/RepoInfo";
@@ -7,11 +6,17 @@
  import { invoke } from "@app/lib/invoke";

  import * as router from "@app/lib/router";
+
  import { nodeRunning } from "@app/lib/events";

+
  import { announce } from "@app/components/AnnounceSwitch.svelte";
+

+
  import AssigneeInput from "@app/components/AssigneeInput.svelte";
+
  import Border from "@app/components/Border.svelte";
  import Button from "@app/components/Button.svelte";
  import Icon from "@app/components/Icon.svelte";
  import InlineTitle from "@app/components/InlineTitle.svelte";
  import IssueSecondColumn from "@app/components/IssueSecondColumn.svelte";
+
  import LabelInput from "@app/components/LabelInput.svelte";
  import Link from "@app/components/Link.svelte";
  import Markdown from "@app/components/Markdown.svelte";
  import NodeId from "@app/components/NodeId.svelte";
@@ -30,20 +35,26 @@

  const { repo, issues, config }: Props = $props();

-
  let title: string = $state("");
  let description: string = $state("");
  let preview: boolean = $state(false);
-
  const announce = false;
+
  let title: string = $state("");

-
  const labels: string[] = [];
-
  const assignees: Author[] = [];
  const embeds: { name: string; content: string }[] = [];

+
  let assignees: string[] = $state([]);
+
  let labels: string[] = $state([]);
+

  async function createIssue() {
    const response: Issue = await invoke("create_issue", {
      rid: repo.rid,
-
      new: { title, description, labels, assignees, embeds },
-
      opts: { announce },
+
      new: {
+
        title,
+
        description,
+
        labels: $state.snapshot(labels),
+
        assignees: $state.snapshot(assignees),
+
        embeds,
+
      },
+
      opts: { announce: $nodeRunning && $announce },
    });
    void router.push({
      resource: "repo.issue",
@@ -66,7 +77,7 @@
  }
  .content {
    padding: 0 1rem 1rem 1rem;
-
    height: calc(100% - 8rem);
+
    height: calc(100% - 14rem);
  }
  .body {
    background-color: var(--color-background-float);
@@ -74,6 +85,21 @@
    min-height: calc(100% + 2px);
    clip-path: var(--2px-corner-fill);
  }
+
  .metadata-divider {
+
    width: 2px;
+
    background-color: var(--color-fill-ghost);
+
    height: calc(100% + 4px);
+
    top: 0;
+
    position: relative;
+
  }
+
  .metadata-section {
+
    padding: 0.5rem;
+
    font-size: var(--font-size-small);
+
    display: flex;
+
    flex-direction: column;
+
    align-items: flex-start;
+
    height: 100%;
+
  }
</style>

<Layout>
@@ -117,6 +143,35 @@
        <TextInput placeholder="Title" autofocus bind:value={title} />
      </div>
    {/if}
+

+
    <div style:margin-bottom="0.35rem">
+
      <Border variant="ghost" styleGap="0">
+
        <div class="metadata-section" style:flex="1">
+
          <LabelInput
+
            allowedToEdit={true}
+
            {labels}
+
            submitInProgress={false}
+
            save={newLabels => {
+
              labels = newLabels;
+
            }} />
+
        </div>
+

+
        <div class="metadata-divider"></div>
+

+
        <div class="metadata-section" style:flex="1">
+
          <AssigneeInput
+
            allowedToEdit={true}
+
            assignees={assignees.map(assignee => {
+
              return { did: assignee };
+
            })}
+
            submitInProgress={false}
+
            save={newAssignees => {
+
              assignees = newAssignees;
+
            }} />
+
        </div>
+
      </Border>
+
    </div>
+

    {#if preview}
      <div class="txt-small body">
        {#if description.trim() === ""}
modified src/views/repo/Issue.svelte
@@ -64,6 +64,7 @@
  let updatedTitle = $state("");
  let labelSaveInProgress: boolean = $state(false);
  let assigneesSaveInProgress: boolean = $state(false);
+
  let focusReply: boolean = $state(false);

  $effect(() => {
    // The component doesn't get destroyed when we switch between different
@@ -77,6 +78,7 @@
    topLevelReplyOpen = false;
    editingTitle = false;
    updatedTitle = issue.title;
+
    focusReply = false;
  });

  const project = $derived(repo.payloads["xyz.radicle.project"]!);
@@ -132,6 +134,7 @@
      behavior: "smooth",
      block: "center",
    });
+
    focusReply = true;
  }

  async function reload() {
@@ -339,6 +342,7 @@
    gap: 1rem;
    margin-left: 1rem;
    align-items: center;
+
    height: 40px;
  }

  .metadata-divider {
@@ -402,9 +406,9 @@
  {/snippet}

  <div class="content">
-
    <div style:margin-bottom="1rem" style:margin-top="-4px">
+
    <div style:margin-bottom="1rem" style:margin-top="-6px">
      {#if editingTitle}
-
        <div class="global-flex">
+
        <div class="title">
          <TextInput
            valid={updatedTitle.trim().length > 0}
            bind:value={updatedTitle}
@@ -420,7 +424,6 @@
            }} />
          <div class="title-icons">
            <Icon
-
              styleCursor="pointer"
              name="checkmark"
              onclick={async () => {
                if (updatedTitle.trim().length > 0) {
@@ -428,7 +431,6 @@
                }
              }} />
            <Icon
-
              styleCursor="pointer"
              name="cross"
              onclick={() => {
                updatedTitle = issue.title;
@@ -442,10 +444,7 @@
          <InlineTitle content={issue.title} fontSize="medium" />
          {#if roles.isDelegateOrAuthor( config.publicKey, repo.delegates.map(delegate => delegate.did), issue.body.author.did, )}
            <div class="title-icons">
-
              <Icon
-
                styleCursor="pointer"
-
                name="pen"
-
                onclick={() => (editingTitle = !editingTitle)} />
+
              <Icon name="pen" onclick={() => (editingTitle = !editingTitle)} />
              <IssueStateButton issueState={issue.state} save={saveState} />
            </div>
          {/if}
@@ -511,7 +510,7 @@
          issue.body.id,
        )}>
        {#snippet actions()}
-
          <Icon styleCursor="pointer" name="reply" onclick={toggleReply} />
+
          <Icon name="reply" onclick={toggleReply} />
        {/snippet}
      </CommentComponent>
    </div>
@@ -546,7 +545,7 @@
      <CommentToggleInput
        disallowEmptyBody
        rid={repo.rid}
-
        focus
+
        focus={focusReply}
        onexpand={toggleReply}
        onclose={topLevelReplyOpen
          ? () => (topLevelReplyOpen = false)