Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Tweak patch page
Open rudolfs opened 1 year ago
12 files changed +256 -62 5f26f71b 8171b114
modified public/index.css
@@ -133,6 +133,21 @@ body {
    0 100%
  );

+
  --2px-bottom-corner-fill: polygon(
+
    0 0,
+
    100% 0,
+
    100% calc(100% - 4px),
+
    calc(100% - 2px) calc(100% - 4px),
+
    calc(100% - 2px) calc(100% - 2px),
+
    calc(100% - 4px) calc(100% - 2px),
+
    calc(100% - 4px) 100%,
+
    4px 100%,
+
    4px calc(100% - 2px),
+
    2px calc(100% - 2px),
+
    2px calc(100% - 4px),
+
    0 calc(100% - 4px)
+
  );
+

  --3px-corner-fill: polygon(
    0 6px,
    2px 6px,
modified src/components/Changeset.svelte
@@ -20,10 +20,12 @@

<div class="diff-list">
  {#each diff.files as file}
-
    <FileDiff
-
      filePath={"path" in file ? file.path : file.newPath}
-
      oldFilePath={"oldPath" in file ? file.oldPath : undefined}
-
      fileDiff={file.diff}
-
      headerBadgeCaption={file.status} />
+
    <div style:margin-bottom="1rem">
+
      <FileDiff
+
        filePath={"path" in file ? file.path : file.newPath}
+
        oldFilePath={"oldPath" in file ? file.oldPath : undefined}
+
        fileDiff={file.diff}
+
        headerBadgeCaption={file.status} />
+
    </div>
  {/each}
</div>
modified src/components/Changeset/FileDiff.svelte
@@ -65,8 +65,6 @@
<style>
  .container {
    font-size: var(--font-size-small);
-
    background: var(--color-background-float);
-
    border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
    overflow-x: auto;
  }
  .actions {
modified src/components/DropdownList.svelte
@@ -14,7 +14,6 @@
<style>
  .dropdown {
    align-items: center;
-
    border-radius: var(--border-radius-small);
    max-height: 60vh;
    overflow-y: auto;
  }
modified src/components/File.svelte
@@ -16,11 +16,19 @@
    height: 3rem;
    align-items: center;
    padding: 0 0.5rem 0 1rem;
-
    border: 1px solid var(--color-border-hint);
-
    border-top-left-radius: var(--border-radius-small);
-
    border-top-right-radius: var(--border-radius-small);
-
    background-color: var(--color-background-default);
    z-index: 2;
+
    font-size: var(--font-size-small);
+
  }
+
  .header::after {
+
    position: absolute;
+
    z-index: -1;
+
    content: " ";
+
    background-color: var(--color-fill-float-hover);
+
    clip-path: var(--2px-top-corner-fill);
+
    width: 100%;
+
    height: 100%;
+
    top: 0;
+
    left: 0;
  }

  .sticky {
@@ -38,24 +46,17 @@
  .container {
    position: relative;
    overflow-x: auto;
-
    border: 1px solid var(--color-border-hint);
-
    border-top: 0;
-
    background: var(--color-background-float);
-
    border-bottom-left-radius: var(--border-radius-small);
-
    border-bottom-right-radius: var(--border-radius-small);
  }
-
  @media (max-width: 719.98px) {
-
    .header {
-
      border-radius: 0;
-
      border-left: 0;
-
      border-right: 0;
-
      padding: 0 1rem 0 1rem;
-
    }
-
    .container {
-
      border-radius: 0;
-
      border-left: 0;
-
      border-right: 0;
-
    }
+
  .container::after {
+
    position: absolute;
+
    z-index: -1;
+
    content: " ";
+
    background-color: var(--color-background-float);
+
    clip-path: var(--2px-bottom-corner-fill);
+
    width: 100%;
+
    height: 100%;
+
    top: 0;
+
    left: 0;
  }
</style>

modified src/components/Markdown.svelte
@@ -337,6 +337,7 @@
    padding: 1rem !important;
    overflow: scroll;
    scrollbar-width: none;
+
    clip-path: var(--2px-corner-fill);
  }

  .markdown :global(pre::-webkit-scrollbar) {
added src/components/PatchStateBadge.svelte
@@ -0,0 +1,20 @@
+
<script lang="ts">
+
  import type { Patch } from "@bindings/cob/patch/Patch";
+

+
  import capitalize from "lodash/capitalize";
+
  import { patchStatusColor } from "@app/lib/utils";
+

+
  interface Props {
+
    state: Patch["state"];
+
  }
+

+
  const { state }: Props = $props();
+
</script>
+

+
<div
+
  class="global-counter txt-small"
+
  style:width="fit-content"
+
  style:color="var(--color-foreground-match-background)"
+
  style:background-color={patchStatusColor[state.status]}>
+
  {capitalize(state.status)}
+
</div>
modified src/views/repo/Issue.svelte
@@ -331,7 +331,7 @@
    top: 0;
  }
  .content {
-
    padding: 0 1rem 1rem 0;
+
    padding: 1rem 1rem 1rem 0;
  }
  .connector {
    width: 2px;
@@ -394,7 +394,7 @@
  {/snippet}

  <div class="content">
-
    <div style:margin-bottom="0.5rem" style:margin-top="-6px">
+
    <div style:margin-bottom="1rem">
      {#if editingTitle}
        <div class="title">
          <TextInput
modified src/views/repo/Issues.svelte
@@ -40,7 +40,7 @@
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-medium);
    display: flex;
-
    padding: 0 1rem 0.5rem 1rem;
+
    padding: 1rem 1rem 0.5rem 1rem;
    align-items: center;
    justify-content: space-between;
  }
modified src/views/repo/Layout.svelte
@@ -127,7 +127,6 @@
  }

  .content {
-
    padding-top: 1rem;
    grid-column: 3 / 4;
    width: 100%;
    overflow: scroll;
modified src/views/repo/Patch.svelte
@@ -1,25 +1,35 @@
<script lang="ts">
-
  import type { Diff } from "@bindings/diff/Diff";
+
  import type { Author } from "@bindings/cob/Author";
  import type { Config } from "@bindings/config/Config";
+
  import type { Diff } from "@bindings/diff/Diff";
  import type { PaginatedQuery } from "@bindings/cob/PaginatedQuery";
  import type { Patch } from "@bindings/cob/patch/Patch";
+
  import type { PatchStatus } from "./router";
  import type { RepoInfo } from "@bindings/repo/RepoInfo";
  import type { Revision } from "@bindings/cob/patch/Revision";
-
  import type { PatchStatus } from "./router";

+
  import * as roles from "@app/lib/roles";
+
  import { formatOid } from "@app/lib/utils";
  import { invoke } from "@app/lib/invoke";
+
  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 Changeset from "@app/components/Changeset.svelte";
  import CommentComponent from "@app/components/Comment.svelte";
  import CopyableId from "@app/components/CopyableId.svelte";
  import Icon from "@app/components/Icon.svelte";
-
  import Id from "@app/components/Id.svelte";
  import InlineTitle from "@app/components/InlineTitle.svelte";
+
  import LabelInput from "@app/components/LabelInput.svelte";
  import Layout from "./Layout.svelte";
  import Link from "@app/components/Link.svelte";
  import NodeId from "@app/components/NodeId.svelte";
+
  import PatchStateBadge from "@app/components/PatchStateBadge.svelte";
  import PatchTeaser from "@app/components/PatchTeaser.svelte";
  import Sidebar from "@app/components/Sidebar.svelte";
-
  import Changeset from "@app/components/Changeset.svelte";
+
  import Button from "@app/components/Button.svelte";

  interface Props {
    repo: RepoInfo;
@@ -34,9 +44,12 @@
  let { repo, patch, patches, revisions, config, status }: Props = $props();
  /* eslint-enable prefer-const */

-
  let items = $state(patches.content);
  let cursor = patches.cursor;
  let more = patches.more;
+
  let items = $state(patches.content);
+
  let labelSaveInProgress: boolean = $state(false);
+
  let assigneesSaveInProgress: boolean = $state(false);
+
  let tab: "patch" | "revisions" = $state("patch");

  $effect(() => {
    items = patches.content;
@@ -44,6 +57,15 @@
    more = patches.more;
  });

+
  $effect(() => {
+
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+
    patch.id;
+

+
    tab = "patch";
+
  });
+

+
  const project = $derived(repo.payloads["xyz.radicle.project"]!);
+

  async function loadHighlightedDiff(rid: string, base: string, head: string) {
    return invoke<Diff>("get_diff", {
      rid,
@@ -81,7 +103,66 @@
    }
  }

-
  const project = $derived(repo.payloads["xyz.radicle.project"]!);
+
  async function saveLabels(labels: string[]) {
+
    try {
+
      labelSaveInProgress = true;
+
      await invoke("edit_patch", {
+
        rid: repo.rid,
+
        cobId: patch.id,
+
        action: {
+
          type: "label",
+
          labels,
+
        },
+
        opts: { announce: $nodeRunning && $announce },
+
      });
+
    } catch (error) {
+
      console.error("Editing labels failed", error);
+
    } finally {
+
      labelSaveInProgress = false;
+
      await reload();
+
    }
+
  }
+

+
  async function saveAssignees(assignees: Author[]) {
+
    try {
+
      assigneesSaveInProgress = true;
+
      await invoke("edit_patch", {
+
        rid: repo.rid,
+
        cobId: patch.id,
+
        action: {
+
          type: "assign",
+
          assignees: assignees.map(a => a.did),
+
        },
+
        opts: { announce: $nodeRunning && $announce },
+
      });
+
    } catch (error) {
+
      console.error("Editing assignees failed", error);
+
    } finally {
+
      assigneesSaveInProgress = false;
+
      await reload();
+
    }
+
  }
+

+
  async function reload() {
+
    [config, repo, patches, patch, revisions] = await Promise.all([
+
      invoke<Config>("config"),
+
      invoke<RepoInfo>("repo_by_id", {
+
        rid: repo.rid,
+
      }),
+
      invoke<PaginatedQuery<Patch[]>>("list_patches", {
+
        rid: repo.rid,
+
        status,
+
      }),
+
      invoke<Patch>("patch_by_id", {
+
        rid: repo.rid,
+
        id: patch.id,
+
      }),
+
      invoke<Revision[]>("revisions_by_patch", {
+
        rid: repo.rid,
+
        id: patch.id,
+
      }),
+
    ]);
+
  }
</script>

<style>
@@ -90,8 +171,7 @@
    font-weight: var(--font-weight-medium);
    -webkit-user-select: text;
    user-select: text;
-
    margin-bottom: 0.5rem;
-
    margin-top: 0.35rem;
+
    margin-bottom: 1rem;
  }
  .patch-list {
    margin-top: 0.5rem;
@@ -101,7 +181,7 @@
    padding-bottom: 1rem;
  }
  .content {
-
    padding: 0 1rem 1rem 0;
+
    padding: 1.5rem 1rem 1rem 0;
  }

  .patch-body {
@@ -120,6 +200,25 @@
    height: 100%;
    top: 0;
  }
+
  .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%;
+
  }
+
  .metadata-section-title {
+
    margin-bottom: 0.5rem;
+
    color: var(--color-foreground-dim);
+
  }
  .breadcrumbs {
    display: flex;
    gap: 0.5rem;
@@ -191,26 +290,86 @@
      {patch.title}
    </div>

-
    <div class="txt-small patch-body">
-
      <CommentComponent
-
        caption="opened"
-
        rid={repo.rid}
-
        id={patch.id}
-
        lastEdit={revisions[0].description.length > 1
-
          ? revisions[0].description.at(-1)
-
          : undefined}
-
        author={revisions[0].author}
-
        reactions={revisions[0].reactions}
-
        timestamp={revisions[0].description.slice(-1)[0].timestamp}
-
        body={revisions[0].description.slice(-1)[0].body}>
-
      </CommentComponent>
+
    <Border variant="ghost" styleGap="0">
+
      <div class="metadata-section" style:min-width="8rem">
+
        <div class="metadata-section-title">Status</div>
+
        <PatchStateBadge state={patch.state} />
+
      </div>
+

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

+
      <div class="metadata-section" style:flex="1">
+
        <LabelInput
+
          allowedToEdit={!!roles.isDelegateOrAuthor(
+
            config.publicKey,
+
            repo.delegates.map(delegate => delegate.did),
+
            patch.author.did,
+
          )}
+
          labels={patch.labels}
+
          submitInProgress={labelSaveInProgress}
+
          save={saveLabels} />
+
      </div>
+

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

+
      <div class="metadata-section" style:flex="1">
+
        <AssigneeInput
+
          allowedToEdit={!!roles.isDelegateOrAuthor(
+
            config.publicKey,
+
            repo.delegates.map(delegate => delegate.did),
+
            patch.author.did,
+
          )}
+
          assignees={patch.assignees}
+
          submitInProgress={assigneesSaveInProgress}
+
          save={saveAssignees} />
+
      </div>
+
    </Border>
+

+
    <div class="global-flex" style:gap="0" style:margin-top="1rem">
+
      <Button
+
        flatRight
+
        active={tab === "patch"}
+
        variant="ghost"
+
        onclick={() => {
+
          tab = "patch";
+
        }}>
+
        Patch
+
      </Button>
+

+
      <Button
+
        flatLeft
+
        variant="ghost"
+
        active={tab === "revisions"}
+
        onclick={() => {
+
          tab = "revisions";
+
        }}>
+
        Revision: {formatOid(revisions.slice(-1)[0].id)}
+
        <span class="global-counter" style:height="22px">latest</span>
+
      </Button>
    </div>
-
    <div class="txt-small" style:margin-top="1rem">Revisions</div>
-
    {#each revisions as revision}
-
      <div><Id id={revision.id} variant="oid" /></div>
+

+
    {#if tab === "patch"}
+
      <div class="txt-small patch-body">
+
        <CommentComponent
+
          caption="opened"
+
          rid={repo.rid}
+
          id={patch.id}
+
          lastEdit={revisions[0].description.length > 1
+
            ? revisions[0].description.at(-1)
+
            : undefined}
+
          author={revisions[0].author}
+
          reactions={revisions[0].reactions}
+
          timestamp={revisions[0].description.slice(-1)[0].timestamp}
+
          body={revisions[0].description.slice(-1)[0].body}>
+
        </CommentComponent>
+
      </div>
+
    {:else}
+
      {@const revision = revisions.slice(-1)[0]}
      {#await loadHighlightedDiff(repo.rid, revision.base, revision.head) then diff}
-
        <Changeset {diff} repoId={repo.rid} />
+
        <div style:margin-top="1rem">
+
          <Changeset {diff} repoId={repo.rid} />
+
        </div>
      {/await}
-
    {/each}
+
    {/if}
  </div>
</Layout>
modified src/views/repo/Patches.svelte
@@ -63,10 +63,10 @@
  .header {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-medium);
-
    padding: 0 1rem 0.5rem 1rem;
+
    padding: 1rem 1rem 0.5rem 1rem;
    display: flex;
    align-items: center;
-
    height: 42px;
+
    height: 58px;
  }
  .breadcrumbs {
    display: flex;