Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Implement issue state change button
Open rudolfs opened 1 year ago
5 files changed +196 -11 319b1502 daaef13f
added src/components/DropdownList.svelte
@@ -0,0 +1,29 @@
+
<script lang="ts" generics="T">
+
  export let items: T[];
+
  export let styleDropdownMinWidth: string | undefined = undefined;
+
</script>
+

+
<style>
+
  .dropdown {
+
    align-items: center;
+
    border-radius: var(--border-radius-small);
+
    max-height: 60vh;
+
    overflow-y: auto;
+
  }
+
  .dropdown-item {
+
    padding: 2px;
+
    font-size: var(--font-size-small);
+
  }
+
</style>
+

+
<div class="dropdown" style:min-width={styleDropdownMinWidth}>
+
  {#each items as item}
+
    <div class="dropdown-item">
+
      <slot name="item" {item} />
+
    </div>
+
  {:else}
+
    <div class="dropdown-item">
+
      <slot name="empty" />
+
    </div>
+
  {/each}
+
</div>
added src/components/DropdownListItem.svelte
@@ -0,0 +1,57 @@
+
<script lang="ts">
+
  export let selected: boolean;
+
  export let disabled: boolean = false;
+
  export let title: string | undefined = undefined;
+
  export let style: string | undefined = undefined;
+
</script>
+

+
<style>
+
  .item {
+
    cursor: pointer;
+
    display: flex;
+
    align-items: center;
+
    flex-direction: row;
+
    gap: 0.375rem;
+
    padding: 0.5rem 0.375rem;
+
    white-space: nowrap;
+
    font-size: var(--font-size-small);
+
    font-weight: var(--font-weight-regular);
+
    color: var(--color-foreground-contrast);
+
    clip-path: var(--1px-corner-fill);
+
  }
+
  .item.disabled {
+
    color: var(--color-foreground-disabled);
+
  }
+
  .item:hover,
+
  .selected {
+
    background-color: var(--color-fill-ghost);
+
  }
+
  .selected {
+
    font-weight: var(--font-weight-semibold);
+
    color: var(--color-foreground-contrast);
+
    background-color: var(--color-fill-ghost);
+
  }
+
  .item:hover.selected {
+
    background-color: var(--color-fill-ghost-hover);
+
  }
+
  .item:hover.selected.disabled {
+
    background-color: var(--color-fill-ghost);
+
  }
+
  .item:hover.disabled {
+
    cursor: not-allowed;
+
    background-color: var(--color-background-float);
+
  }
+
</style>
+

+
<!-- svelte-ignore a11y-click-events-have-key-events -->
+
<div
+
  role="button"
+
  tabindex="0"
+
  class="item"
+
  class:selected
+
  class:disabled
+
  {style}
+
  {title}
+
  on:click>
+
  <slot />
+
</div>
modified src/components/Icon.svelte
@@ -9,6 +9,7 @@
    | "arrow-left"
    | "arrow-right"
    | "checkmark"
+
    | "chevron-down"
    | "chevron-right"
    | "comment"
    | "copy"
@@ -92,6 +93,17 @@
    <path d="M4 8V9H3L3 8H4Z" />
    <path d="M5 9L5 10L4 10L4 9H5Z" />
    <path d="M6 10L6 11H5L5 10L6 10Z" />
+
  {:else if name === "chevron-down"}
+
    <path d="M9 10V11H8V10H9Z" />
+
    <path d="M10 9V10L9 10V9H10Z" />
+
    <path d="M11 8V9H10V8L11 8Z" />
+
    <path d="M12 7V8L11 8V7H12Z" />
+
    <path d="M4 6V7H3L3 6L4 6Z" />
+
    <path d="M13 6V7L12 7L12 6L13 6Z" />
+
    <path d="M5 7L5 8H4L4 7L5 7Z" />
+
    <path d="M6 8V9H5L5 8L6 8Z" />
+
    <path d="M8 10V11H7L7 10H8Z" />
+
    <path d="M7 9L7 10H6L6 9L7 9Z" />
  {:else if name === "chevron-right"}
    <path d="M9 7L10 7L10 8L9 8L9 7Z" />
    <path d="M8 6H9V7H8L8 6Z" />
added src/components/IssueStateButton.svelte
@@ -0,0 +1,71 @@
+
<script lang="ts">
+
  import type { State } from "@bindings/cob/issue/State";
+

+
  import isEqual from "lodash/isEqual";
+

+
  import { closeFocused } from "@app/components/Popover.svelte";
+

+
  import Border from "@app/components/Border.svelte";
+
  import Button from "@app/components/Button.svelte";
+
  import DropdownList from "@app/components/DropdownList.svelte";
+
  import DropdownListItem from "@app/components/DropdownListItem.svelte";
+
  import Icon from "@app/components/Icon.svelte";
+
  import Popover from "@app/components/Popover.svelte";
+

+
  export let state: State;
+
  export let save: (state: State) => Promise<void>;
+

+
  const actions: { caption: string; state: State }[] = [
+
    { caption: "Reopen", state: { status: "open" } },
+
    {
+
      caption: "Close as solved",
+
      state: { status: "closed", reason: "solved" },
+
    },
+
    { caption: "Close as other", state: { status: "closed", reason: "other" } },
+
  ];
+

+
  // Pick a default for the action button when the issue state changes.
+
  $: selectedAction = state.status === "open" ? actions[1] : actions[0];
+
</script>
+

+
<style>
+
  .main {
+
    display: flex;
+
    flex-direction: row;
+
    justify-content: center;
+
    gap: 1px;
+
  }
+
</style>
+

+
<div class="main">
+
  <Button
+
    variant="secondary"
+
    onclick={() => void save(selectedAction["state"])}>
+
    {selectedAction["caption"]}
+
  </Button>
+

+
  <Popover
+
    popoverPadding="0"
+
    popoverPositionTop="3rem"
+
    popoverPositionRight="0">
+
    <Button slot="toggle" let:toggle onclick={toggle} variant="secondary">
+
      <div style:height="22px" class="global-flex">
+
        <Icon name="chevron-down" />
+
      </div>
+
    </Button>
+
    <Border variant="ghost" slot="popover">
+
      <DropdownList items={actions.filter(a => !isEqual(a.state, state))}>
+
        <svelte:fragment slot="item" let:item={action}>
+
          <DropdownListItem
+
            selected={isEqual(selectedAction, action)}
+
            on:click={() => {
+
              selectedAction = action;
+
              closeFocused();
+
            }}>
+
            {action.caption}
+
          </DropdownListItem>
+
        </svelte:fragment>
+
      </DropdownList>
+
    </Border>
+
  </Popover>
+
</div>
modified src/views/repo/Issue.svelte
@@ -20,6 +20,7 @@
  import { announce } from "@app/components/AnnounceSwitch.svelte";

  import Border from "@app/components/Border.svelte";
+
  import IssueStateButton from "@app/components/IssueStateButton.svelte";
  import CommentComponent from "@app/components/Comment.svelte";
  import CommentToggleInput from "@app/components/CommentToggleInput.svelte";
  import CopyableId from "@app/components/CopyableId.svelte";
@@ -29,10 +30,10 @@
  import IssueTimelineLifecycleAction from "@app/components/IssueTimelineLifecycleAction.svelte";
  import Link from "@app/components/Link.svelte";
  import NodeId from "@app/components/NodeId.svelte";
+
  import TextInput from "@app/components/TextInput.svelte";
  import Thread from "@app/components/Thread.svelte";

  import Layout from "./Layout.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";

  export let repo: RepoInfo;
  export let issue: Issue;
@@ -150,9 +151,7 @@
        opts: { announce: $announce },
      });
    } catch (error) {
-
      if (error instanceof Error) {
-
        console.error("Issue comment editing failed: ", error);
-
      }
+
      console.error("Issue comment editing failed: ", error);
    } finally {
      await reload();
    }
@@ -183,9 +182,7 @@
      }
      editingTitle = false;
    } catch (error) {
-
      if (error instanceof Error) {
-
        console.error("Issue editing failed: ", error);
-
      }
+
      console.error("Issue editing failed: ", error);
    }
  }

@@ -210,9 +207,25 @@
        opts: { announce: $announce },
      });
    } catch (error) {
-
      if (error instanceof Error) {
-
        console.error("Editing reactions failed", error);
-
      }
+
      console.error("Editing reactions failed", error);
+
    } finally {
+
      await reload();
+
    }
+
  }
+

+
  async function saveState(state: Issue["state"]) {
+
    try {
+
      await invoke("edit_issue", {
+
        rid: repo.rid,
+
        cobId: issue.id,
+
        action: {
+
          type: "lifecycle",
+
          state,
+
        },
+
        opts: { announce: $announce },
+
      });
+
    } catch (error) {
+
      console.error("Editing reactions failed", error);
    } finally {
      await reload();
    }
@@ -271,8 +284,9 @@

  .title-icons {
    display: flex;
-
    gap: 0.5rem;
+
    gap: 1rem;
    margin-left: 1rem;
+
    align-items: center;
  }
</style>

@@ -374,6 +388,7 @@
              updatedTitle = issue.title;
              editingTitle = !editingTitle;
            }} />
+
          <IssueStateButton state={issue.state} save={saveState} />
        </div>
      </div>
    {:else}
@@ -381,6 +396,7 @@
        <InlineTitle content={issue.title} fontSize="medium" />
        <div class="title-icons">
          <Icon name="pen" onclick={() => (editingTitle = !editingTitle)} />
+
          <IssueStateButton state={issue.state} save={saveState} />
        </div>
      </div>
    {/if}