Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Add patch checkout popover
Merged did:key:z6MkkfM3...sVz5 opened 1 year ago
5 files changed +145 -31 10b055cf 3cbebb66
added src/components/Clipboard.svelte
@@ -0,0 +1,41 @@
+
<script lang="ts">
+
  import debounce from "lodash/debounce";
+

+
  import Icon from "@app/components/Icon.svelte";
+
  import { writeToClipboard } from "@app/lib/invoke";
+

+
  interface Props {
+
    text: string;
+
  }
+

+
  const { text }: Props = $props();
+

+
  let icon: "copy" | "checkmark" = $state("copy");
+

+
  const restoreIcon = debounce(() => {
+
    icon = "copy";
+
  }, 800);
+

+
  export async function copy() {
+
    await writeToClipboard(text);
+
    icon = "checkmark";
+
    restoreIcon();
+
  }
+
</script>
+

+
<style>
+
  .clipboard {
+
    width: 1.5rem;
+
    height: 1.5rem;
+
    cursor: pointer;
+
    display: inline-flex;
+
    justify-content: center;
+
    align-items: center;
+
    user-select: none;
+
  }
+
</style>
+

+
<!-- svelte-ignore a11y_click_events_have_key_events -->
+
<span role="button" tabindex="0" class="clipboard" onclick={copy}>
+
  <Icon name={icon} />
+
</span>
added src/components/Command.svelte
@@ -0,0 +1,37 @@
+
<script lang="ts">
+
  import Clipboard from "@app/components/Clipboard.svelte";
+
  import Border from "./Border.svelte";
+

+
  interface Props {
+
    command: string;
+
    styleWidth: string;
+
  }
+

+
  const { command, styleWidth }: Props = $props();
+

+
  let clipboard: Clipboard;
+
</script>
+

+
<style>
+
  .cmd {
+
    color: var(--color-foreground-dim);
+
  }
+
  .cmd:hover {
+
    color: var(--color-foreground-contrast);
+
  }
+
</style>
+

+
<div class="cmd txt-monospace" style:width={styleWidth}>
+
  <Border
+
    hoverable
+
    onclick={() => clipboard.copy()}
+
    styleBackgroundColor="var(--color-background-float)"
+
    styleCursor="pointer"
+
    styleJustifyContent="space-between"
+
    stylePadding="0.25rem 0.5rem"
+
    {styleWidth}
+
    variant="ghost">
+
    $ {command}
+
    <Clipboard bind:this={clipboard} text={command} />
+
  </Border>
+
</div>
modified src/components/CopyableId.svelte
@@ -1,28 +1,9 @@
<script lang="ts">
-
  import type { ComponentProps } from "svelte";
+
  import Clipboard from "./Clipboard.svelte";

-
  import debounce from "lodash/debounce";
-
  import { writeText } from "@tauri-apps/plugin-clipboard-manager";
+
  const { id }: { id: string } = $props();

-
  import Icon from "./Icon.svelte";
-

-
  const {
-
    id,
-
  }: {
-
    id: string;
-
  } = $props();
-

-
  let icon: ComponentProps<typeof Icon>["name"] = $state("copy");
-

-
  const restoreIcon = debounce(() => {
-
    icon = "copy";
-
  }, 1000);
-

-
  async function copy() {
-
    await writeText(id);
-
    icon = "checkmark";
-
    restoreIcon();
-
  }
+
  let clipboard: Clipboard;
</script>

<style>
@@ -40,8 +21,8 @@
<div
  role="button"
  tabindex="0"
-
  onclick={copy}
+
  onclick={() => clipboard.copy()}
  class="copyable-id global-flex txt-small txt-monospace">
  {id}
-
  <Icon name={icon} />
+
  <Clipboard bind:this={clipboard} text={id} />
</div>
modified src/components/Icon.svelte
@@ -17,6 +17,7 @@
      | "broom"
      | "broom-double"
      | "checkmark"
+
      | "checkout"
      | "chevron-down"
      | "chevron-right"
      | "clock"
@@ -237,6 +238,20 @@
    <path d="M4 8V9H3L3 8H4Z" />
    <path d="M5 9L5 10L4 10L4 9H5Z" />
    <path d="M6 10L6 11H5L5 10L6 10Z" />
+
  {:else if name === "checkout"}
+
    <path d="M5 5H11V6H5V5Z" />
+
    <path d="M4 6L5 6L5 11H4L4 6Z" />
+
    <path d="M11 6L12 6V11H11L11 6Z" />
+
    <path d="M3 11H4L4 12H3V11Z" />
+
    <path d="M12 11L13 11V12H12V11Z" />
+
    <path d="M3 13H13V14H3V13Z" />
+
    <path d="M4 10H12V11L4 11L4 10Z" />
+
    <path d="M13 12L14 12V13L13 13V12Z" />
+
    <path d="M2 12H3L3 13H2V12Z" />
+
    <path d="M7 2L9 2V6H7V2Z" />
+
    <path d="M7 7H9V8H7V7Z" />
+
    <path d="M6 6H10V7H6V6Z" />
+
    <path d="M5 5H11V6H5V5Z" />
  {:else if name === "chevron-down"}
    <path d="M9 10V11H8V10H9Z" />
    <path d="M10 9V10L9 10V9H10Z" />
modified src/views/repo/Patch.svelte
@@ -28,6 +28,8 @@

  import AssigneeInput from "@app/components/AssigneeInput.svelte";
  import Border from "@app/components/Border.svelte";
+
  import Button from "@app/components/Button.svelte";
+
  import Command from "@app/components/Command.svelte";
  import CopyableId from "@app/components/CopyableId.svelte";
  import DropdownList from "@app/components/DropdownList.svelte";
  import DropdownListItem from "@app/components/DropdownListItem.svelte";
@@ -77,6 +79,7 @@
  let cursor: number = $state(0);
  let more: boolean = $state(false);
  let patchTeasers: Patch[] = $state([]);
+
  let checkoutPopoverExpanded = $state(false);

  let patches = $state(initialPatches);
  let status = $state(initialStatus);
@@ -105,6 +108,13 @@
    more = patches.more;
  });

+
  const checkoutCommand = $derived.by(() => {
+
    if (tab === "revisions" && selectedRevision.id !== patch.id) {
+
      return `rad patch checkout ${formatOid(patch.id)} --revision ${formatOid(selectedRevision.id)}`;
+
    } else {
+
      return `rad patch checkout ${formatOid(patch.id)}`;
+
    }
+
  });
  const project = $derived(repo.payloads["xyz.radicle.project"]!);

  async function editTitle(rid: string, patchId: string, title: string) {
@@ -609,13 +619,43 @@
              </div>
              <InlineTitle content={patch.title} fontSize="medium" />
            </div>
-
            {#if roles.isDelegateOrAuthor( config.publicKey, repo.delegates.map(delegate => delegate.did), patch.author.did, )}
-
              <div class="title-icons">
-
                <Icon
-
                  name="pen"
-
                  onclick={() => (editingTitle = !editingTitle)} />
-
              </div>
-
            {/if}
+
            <div
+
              class="global-flex txt-small"
+
              style:margin-left="auto"
+
              style:z-index="40"
+
              style:gap="0.75rem">
+
              {#if roles.isDelegateOrAuthor( config.publicKey, repo.delegates.map(delegate => delegate.did), patch.author.did, )}
+
                <div class="title-icons">
+
                  <Icon
+
                    name="pen"
+
                    onclick={() => (editingTitle = !editingTitle)} />
+
                </div>
+
              {/if}
+

+
              <Popover
+
                bind:expanded={checkoutPopoverExpanded}
+
                popoverPositionRight="0"
+
                popoverPositionTop="3rem">
+
                {#snippet toggle(onclick)}
+
                  <Button styleHeight="2rem" variant="secondary" {onclick}>
+
                    <Icon name="checkout" />Checkout<Icon name="chevron-down" />
+
                  </Button>
+
                {/snippet}
+
                {#snippet popover()}
+
                  <Border
+
                    styleAlignItems="flex-start"
+
                    styleBackgroundColor="var(--color-background-float)"
+
                    styleFlexDirection="column"
+
                    styleGap="0.5rem"
+
                    stylePadding="1rem"
+
                    styleWidth="max-content"
+
                    variant="ghost">
+
                    To checkout this patch in your working copy, run:
+
                    <Command command={checkoutCommand} styleWidth="100%" />
+
                  </Border>
+
                {/snippet}
+
              </Popover>
+
            </div>
          </div>
        {/if}
      </div>