Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Style issue and patch list views
Open rudolfs opened 1 year ago
13 files changed +275 -103 e103fa21 797a64b8
modified package-lock.json
@@ -19,10 +19,12 @@
        "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
        "@tauri-apps/cli": "^2.0.0-rc.1",
        "@tsconfig/svelte": "^5.0.4",
+
        "@types/dompurify": "^3.0.5",
        "@types/lodash": "^4.17.7",
        "@types/node": "^20.9.0",
        "baconjs": "^3.0.19",
        "bs58": "^6.0.0",
+
        "dompurify": "^3.1.6",
        "eslint": "^9.10.0",
        "eslint-config-prettier": "^9.1.0",
        "eslint-plugin-svelte": "^2.44.0",
@@ -1107,6 +1109,15 @@
      "integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==",
      "dev": true
    },
+
    "node_modules/@types/dompurify": {
+
      "version": "3.0.5",
+
      "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
+
      "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/trusted-types": "*"
+
      }
+
    },
    "node_modules/@types/estree": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -1128,6 +1139,12 @@
        "undici-types": "~6.19.2"
      }
    },
+
    "node_modules/@types/trusted-types": {
+
      "version": "2.0.7",
+
      "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+
      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+
      "dev": true
+
    },
    "node_modules/@typescript-eslint/eslint-plugin": {
      "version": "8.6.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
@@ -1669,6 +1686,12 @@
        "node": ">=0.10.0"
      }
    },
+
    "node_modules/dompurify": {
+
      "version": "3.1.6",
+
      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
+
      "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==",
+
      "dev": true
+
    },
    "node_modules/esbuild": {
      "version": "0.21.5",
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
modified package.json
@@ -29,10 +29,12 @@
    "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
    "@tauri-apps/cli": "^2.0.0-rc.1",
    "@tsconfig/svelte": "^5.0.4",
+
    "@types/dompurify": "^3.0.5",
    "@types/lodash": "^4.17.7",
    "@types/node": "^20.9.0",
    "baconjs": "^3.0.19",
    "bs58": "^6.0.0",
+
    "dompurify": "^3.1.6",
    "eslint": "^9.10.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-svelte": "^2.44.0",
modified public/index.css
@@ -126,4 +126,42 @@ body {
    2px calc(100% - 6px),
    0 calc(100% - 6px)
  );
+

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

+
  --3px-bottom-corner-fill: polygon(
+
    0 0,
+
    100% 0,
+
    100% calc(100% - 6px),
+
    calc(100% - 2px) calc(100% - 6px),
+
    calc(100% - 2px) calc(100% - 4px),
+
    calc(100% - 4px) calc(100% - 4px),
+
    calc(100% - 4px) calc(100% - 2px),
+
    calc(100% - 6px) calc(100% - 2px),
+
    calc(100% - 6px) 100%,
+
    6px 100%,
+
    6px calc(100% - 2px),
+
    4px calc(100% - 2px),
+
    4px calc(100% - 4px),
+
    2px calc(100% - 4px),
+
    2px calc(100% - 6px),
+
    0 calc(100% - 6px)
+
  );
}
modified public/typography.css
@@ -91,7 +91,8 @@ html {
   * otherwise Safari breaks. */
  font-size: 16px;
  font-weight: var(--font-weight-regular);
-
  line-height: 1.5;
+
  /* On Safari this aligns text with different font-faces properly vertically. */
+
  line-height: 22px;
}

p {
modified src/components/Header.svelte
@@ -31,24 +31,7 @@
    z-index: -1;

    background-color: var(--color-background-float);
-
    clip-path: polygon(
-
      0 0,
-
      100% 0,
-
      100% calc(100% - 6px),
-
      calc(100% - 2px) calc(100% - 6px),
-
      calc(100% - 2px) calc(100% - 4px),
-
      calc(100% - 4px) calc(100% - 4px),
-
      calc(100% - 4px) calc(100% - 2px),
-
      calc(100% - 6px) calc(100% - 2px),
-
      calc(100% - 6px) 100%,
-
      6px 100%,
-
      6px calc(100% - 2px),
-
      4px calc(100% - 2px),
-
      4px calc(100% - 4px),
-
      2px calc(100% - 4px),
-
      2px calc(100% - 6px),
-
      0 calc(100% - 6px)
-
    );
+
    clip-path: var(--3px-bottom-corner-fill);
  }
  .breadcrumbs {
    gap: 0.5rem;
modified src/components/Icon.svelte
@@ -263,38 +263,13 @@
    <path d="M3 12H4L4 13H3L3 12Z" />
    <path d="M2 13L3 13L3 14H2V13Z" />
  {:else if name === "patch"}
-
    <path d="M9 2H10V3H9V2Z" />
-
    <path d="M10 2L11 2V3L10 3V2Z" />
-
    <path d="M13 6H14V7H13V6Z" />
-
    <path d="M13 5H14L14 6H13L13 5Z" />
-
    <path d="M11 2L12 2V3L11 3V2Z" />
-
    <path d="M7 6H8V7H7V6Z" />
-
    <path d="M6 7H7V8H6V7Z" />
-
    <path d="M2 11H3V12H2V11Z" />
-
    <path d="M2 10H3L3 11H2L2 10Z" />
-
    <path d="M12 3H13V4H12V3Z" />
-
    <path d="M8 7L9 7V8H8V7Z" />
-
    <path d="M7 8L8 8V9H7V8Z" />
-
    <path d="M3 12L4 12V13H3L3 12Z" />
-
    <path d="M13 4H14V5H13L13 4Z" />
-
    <path d="M9 8H10V9H9L9 8Z" />
-
    <path d="M8 9L9 9L9 10H8V9Z" />
-
    <path d="M4 13H5V14H4L4 13Z" />
-
    <path d="M5 13L6 13V14L5 14V13Z" />
-
    <path d="M8 3H9V4H8V3Z" />
-
    <path d="M12 7L13 7L13 8H12V7Z" />
-
    <path d="M7 4H8V5H7V4Z" />
-
    <path d="M11 8H12V9H11V8Z" />
-
    <path d="M6 5H7V6H6V5Z" />
-
    <path d="M10 9L11 9V10H10V9Z" />
-
    <path d="M5 6H6V7H5V6Z" />
-
    <path d="M9 10H10V11H9V10Z" />
-
    <path d="M4 7H5L5 8H4V7Z" />
-
    <path d="M8 11H9V12H8V11Z" />
-
    <path d="M3 8H4V9H3V8Z" />
-
    <path d="M7 12L8 12L8 13H7V12Z" />
-
    <path d="M2 9L3 9L3 10H2L2 9Z" />
-
    <path d="M6 13H7V14H6V13Z" />
+
    <path d="M2 3H3V13H2V3Z" />
+
    <path d="M3 13H12V14H3L3 13Z" />
+
    <path d="M3 2H12V3L3 3L3 2Z" />
+
    <path d="M12 3L13 3V13H12V3Z" />
+
    <path d="M7 4H8V9H7V4Z" />
+
    <path d="M5 6H10V7H5V6Z" />
+
    <path d="M5 10H10V11H5V10Z" />
  {:else if name === "plus"}
    <path d="M7.00002 2H9.00002V14H7.00002V2Z" />
    <path d="M14 7V9L2.00002 9L2.00002 7L14 7Z" />
added src/components/InlineTitle.svelte
@@ -0,0 +1,30 @@
+
<script lang="ts">
+
  import dompurify from "dompurify";
+
  import escape from "lodash/escape";
+

+
  export let content: string;
+
  export let fontSize: "tiny" | "small" | "regular" | "medium" | "large" =
+
    "small";
+

+
  function formatInlineTitle(input: string): string {
+
    return input.replaceAll(/`([^`]+)`/g, "<code>$1</code>");
+
  }
+
</script>
+

+
<style>
+
  .content :global(code) {
+
    font-family: var(--font-family-monospace);
+
    padding: 0.125rem 0.25rem;
+
    background-color: var(--color-fill-ghost);
+
  }
+
</style>
+

+
<span
+
  class="content"
+
  class:txt-large={fontSize === "large"}
+
  class:txt-medium={fontSize === "medium"}
+
  class:txt-regular={fontSize === "regular"}
+
  class:txt-small={fontSize === "small"}
+
  class:txt-tiny={fontSize === "tiny"}>
+
  {@html dompurify.sanitize(formatInlineTitle(escape(content)))}
+
</span>
added src/components/IssueTeaser.svelte
@@ -0,0 +1,79 @@
+
<script lang="ts">
+
  import type { Issue } from "@bindings/Issue";
+

+
  import { formatOid } from "@app/lib/utils";
+

+
  import Icon from "./Icon.svelte";
+
  import InlineTitle from "./InlineTitle.svelte";
+
  import NodeId from "./NodeId.svelte";
+

+
  export let issue: Issue;
+

+
  const statusColor: Record<Issue["state"]["status"], string> = {
+
    open: "var(--color-fill-success)",
+
    closed: "var(--color-foreground-red)",
+
  };
+
  const statusBackgroundColor: Record<Issue["state"]["status"], string> = {
+
    open: "var(--color-fill-diff-green)",
+
    closed: "var(--color-fill-diff-red)",
+
  };
+
</script>
+

+
<style>
+
  .issue-teaser {
+
    display: flex;
+
    align-items: center;
+
    justify-content: space-between;
+
    gap: 0.25rem;
+
    height: 5rem;
+
    background-color: var(--color-background-float);
+
    padding: 1rem;
+
    cursor: pointer;
+
    font-size: var(--font-size-regular);
+
  }
+
  .issue-teaser:hover {
+
    background-color: var(--color-fill-float-hover);
+
  }
+
  .status {
+
    padding: 0;
+
    margin-right: 1rem;
+
  }
+
  .issue-teaser:first-of-type {
+
    clip-path: var(--3px-top-corner-fill);
+
  }
+
  .issue-teaser:last-of-type {
+
    clip-path: var(--3px-bottom-corner-fill);
+
  }
+
  .issue-teaser:only-of-type {
+
    clip-path: var(--3px-corner-fill);
+
  }
+
</style>
+

+
<div class="issue-teaser">
+
  <div class="global-flex">
+
    <div
+
      class="global-counter status"
+
      style:color={statusColor[issue.state.status]}
+
      style:background-color={statusBackgroundColor[issue.state.status]}>
+
      <Icon name="issue" />
+
    </div>
+
    <div
+
      class="global-flex"
+
      style:flex-direction="column"
+
      style:align-items="flex-start">
+
      <InlineTitle content={issue.title} />
+
      <div class="global-flex txt-small">
+
        <NodeId
+
          nodeId={issue.author.did.replace("did:key:", "")}
+
          alias={issue.author.alias} />
+
        opened
+
        <div class="global-oid">{formatOid(issue.id)}</div>
+
      </div>
+
    </div>
+
  </div>
+
  <div class="global-flex">
+
    {#each issue.labels as label}
+
      <div class="global-counter txt-small">{label}</div>
+
    {/each}
+
  </div>
+
</div>
modified src/components/NodeId.svelte
@@ -15,7 +15,6 @@
    display: flex;
    align-items: center;
    gap: 0.375rem;
-
    height: 1rem;
    font-weight: var(--font-weight-semibold);
  }
  .no-alias {
added src/components/PatchTeaser.svelte
@@ -0,0 +1,84 @@
+
<script lang="ts">
+
  import type { Patch } from "@bindings/Patch";
+

+
  import { formatOid } from "@app/lib/utils";
+

+
  import Icon from "./Icon.svelte";
+
  import InlineTitle from "./InlineTitle.svelte";
+
  import NodeId from "./NodeId.svelte";
+

+
  export let patch: Patch;
+

+
  const statusColor: Record<Patch["state"]["status"], string> = {
+
    draft: "var(--color-fill-gray)",
+
    open: "var(--color-fill-success)",
+
    archived: "var(--color-foreground-yellow)",
+
    merged: "var(--color-fill-primary)",
+
  };
+

+
  const statusBackgroundColor: Record<Patch["state"]["status"], string> = {
+
    draft: "var(--color-fill-ghost)",
+
    open: "var(--color-fill-diff-green)",
+
    archived: "var(--color-fill-private)",
+
    merged: "var(--color-fill-delegate)",
+
  };
+
</script>
+

+
<style>
+
  .patch-teaser {
+
    display: flex;
+
    align-items: center;
+
    justify-content: space-between;
+
    gap: 0.25rem;
+
    height: 5rem;
+
    background-color: var(--color-background-float);
+
    padding: 1rem;
+
    cursor: pointer;
+
    font-size: var(--font-size-regular);
+
  }
+
  .patch-teaser:hover {
+
    background-color: var(--color-fill-float-hover);
+
  }
+
  .status {
+
    padding: 0;
+
    margin-right: 1rem;
+
  }
+
  .patch-teaser:first-of-type {
+
    clip-path: var(--3px-top-corner-fill);
+
  }
+
  .patch-teaser:last-of-type {
+
    clip-path: var(--3px-bottom-corner-fill);
+
  }
+
  .patch-teaser:only-of-type {
+
    clip-path: var(--3px-corner-fill);
+
  }
+
</style>
+

+
<div class="patch-teaser">
+
  <div class="global-flex">
+
    <div
+
      class="global-counter status"
+
      style:color={statusColor[patch.state.status]}
+
      style:background-color={statusBackgroundColor[patch.state.status]}>
+
      <Icon name="patch" />
+
    </div>
+
    <div
+
      class="global-flex"
+
      style:flex-direction="column"
+
      style:align-items="flex-start">
+
      <InlineTitle content={patch.title} />
+
      <div class="global-flex txt-small">
+
        <NodeId
+
          nodeId={patch.author.did.replace("did:key:", "")}
+
          alias={patch.author.alias} />
+
        opened
+
        <div class="global-oid">{formatOid(patch.id)}</div>
+
      </div>
+
    </div>
+
  </div>
+
  <div class="global-flex">
+
    {#each patch.labels as label}
+
      <div class="global-counter txt-small">{label}</div>
+
    {/each}
+
  </div>
+
</div>
modified src/views/repo/Issues.svelte
@@ -4,11 +4,10 @@
  import type { IssueStatus } from "./router";
  import type { RepoInfo } from "@bindings/RepoInfo";

-
  import { formatOid } from "@app/lib/utils";
-

  import Layout from "./Layout.svelte";

  import Icon from "@app/components/Icon.svelte";
+
  import IssueTeaser from "@app/components/IssueTeaser.svelte";
  import Link from "@app/components/Link.svelte";
  import NodeId from "@app/components/NodeId.svelte";

@@ -17,14 +16,6 @@
  export let config: Config;
  export let status: IssueStatus;

-
  const statusColor: Record<Issue["state"]["status"], string> = {
-
    open: "var(--color-fill-success)",
-
    closed: "var(--color-foreground-red)",
-
  };
-
  const statusBackgroundColor: Record<Issue["state"]["status"], string> = {
-
    open: "var(--color-fill-diff-green)",
-
    closed: "var(--color-fill-diff-red)",
-
  };
  $: project = repo.payloads["xyz.radicle.project"]!;
</script>

@@ -32,7 +23,8 @@
  .list {
    display: flex;
    flex-direction: column;
-
    gap: 0.5rem;
+
    gap: 2px;
+
    padding: 0 1rem 1rem 1rem;
  }
</style>

@@ -106,17 +98,7 @@

  <div class="list">
    {#each issues as issue}
-
      <div class="global-flex">
-
        <div
-
          class="global-counter"
-
          style:padding="0"
-
          style:color={statusColor[issue.state.status]}
-
          style:background-color={statusBackgroundColor[issue.state.status]}>
-
          <Icon name="issue" />
-
        </div>
-
        <div class="global-oid">{formatOid(issue.id)}</div>
-
        {issue.title}
-
      </div>
+
      <IssueTeaser {issue} />
    {/each}

    {#if issues.length === 0}
modified src/views/repo/Layout.svelte
@@ -29,7 +29,7 @@
  .sidebar {
    grid-column: 1 / 2;
    margin-left: 1rem;
-
    margin-right: 1.5rem;
+
    margin-right: 0.5rem;
    min-width: 14rem;
  }

modified src/views/repo/Patches.svelte
@@ -4,33 +4,18 @@
  import type { PatchStatus } from "./router";
  import type { RepoInfo } from "@bindings/RepoInfo";

-
  import { formatOid } from "@app/lib/utils";
-

  import Layout from "./Layout.svelte";

  import Icon from "@app/components/Icon.svelte";
  import Link from "@app/components/Link.svelte";
  import NodeId from "@app/components/NodeId.svelte";
+
  import PatchTeaser from "@app/components/PatchTeaser.svelte";

  export let repo: RepoInfo;
  export let patches: Patch[];
  export let config: Config;
  export let status: PatchStatus;

-
  const statusColor: Record<Patch["state"]["status"], string> = {
-
    draft: "var(--color-fill-gray)",
-
    open: "var(--color-fill-success)",
-
    archived: "var(--color-foreground-yellow)",
-
    merged: "var(--color-fill-primary)",
-
  };
-

-
  const statusBackgroundColor: Record<Patch["state"]["status"], string> = {
-
    draft: "var(--color-fill-ghost)",
-
    open: "var(--color-fill-diff-green)",
-
    archived: "var(--color-fill-private)",
-
    merged: "var(--color-fill-delegate)",
-
  };
-

  $: project = repo.payloads["xyz.radicle.project"]!;
</script>

@@ -38,7 +23,8 @@
  .list {
    display: flex;
    flex-direction: column;
-
    gap: 0.5rem;
+
    gap: 2px;
+
    padding: 0 1rem 1rem 1rem;
  }
</style>

@@ -131,17 +117,7 @@

  <div class="list">
    {#each patches as patch}
-
      <div class="global-flex">
-
        <div
-
          class="global-counter"
-
          style:padding="0"
-
          style:color={statusColor[patch.state.status]}
-
          style:background-color={statusBackgroundColor[patch.state.status]}>
-
          <Icon name="patch" />
-
        </div>
-
        <div class="global-oid">{formatOid(patch.id)}</div>
-
        {patch.title}
-
      </div>
+
      <PatchTeaser {patch} />
    {/each}

    {#if patches.length === 0}