Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
Add `Download` radio button to `Clone` popover
Open did:key:z6MkkfM3...sVz5 opened 1 year ago

Only works for seeds that have a working /raw/:rid/archive endpoint

check check-visual check-unit-test check-http-client-unit-test check-radicle-httpd check-e2e check-build check-http

👉 Preview 👉 Workflow runs 👉 Branch on GitHub

7 files changed +112 -14 af334bb0 867a4de8
modified src/components/Icon.svelte
@@ -4,6 +4,7 @@
  import { unreachable } from "@app/lib/utils";
  export let name:
    | "activity"
+
    | "archive"
    | "arrow-box-up-right"
    | "arrow-reply"
    | "badge"
@@ -86,6 +87,16 @@
      fill-rule="evenodd"
      clip-rule="evenodd"
      d="M2.76032 2.0563C2.56506 1.86104 2.24848 1.86104 2.05322 2.0563C1.85795 2.25156 1.85795 2.56815 2.05322 2.76341L3.41389 4.12408C2.52941 5.16972 1.99551 6.52284 1.99551 8C1.99551 11.3162 4.68381 14.0045 8 14.0045C11.3162 14.0045 14.0045 11.3162 14.0045 8C14.0045 4.68381 11.3162 1.99551 8 1.99551C7.13343 1.99551 6.30847 2.1794 5.5631 2.51075C5.31076 2.62293 5.19714 2.91842 5.30932 3.17075C5.42149 3.42308 5.71698 3.5367 5.96931 3.42453C6.58928 3.14892 7.27606 2.99551 8 2.99551C10.7639 2.99551 13.0045 5.2361 13.0045 8C13.0045 10.7639 10.7639 13.0045 8 13.0045C5.2361 13.0045 2.99551 10.7639 2.99551 8C2.99551 6.79876 3.41827 5.69689 4.12393 4.83412L4.84861 5.5588C4.32565 6.23309 4.01382 7.08042 4.01382 8C4.01382 10.2015 5.7985 11.9862 8 11.9862C10.2015 11.9862 11.9862 10.2015 11.9862 8C11.9862 5.79849 10.2015 4.01382 8 4.01382C7.6541 4.01382 7.31777 4.058 6.99668 4.14128C6.72939 4.21061 6.5689 4.4835 6.63823 4.7508C6.70756 5.01809 6.98045 5.17858 7.24775 5.10925C7.48754 5.04705 7.73952 5.01382 8 5.01382C9.64923 5.01382 10.9862 6.35078 10.9862 8C10.9862 9.64922 9.64923 10.9862 8 10.9862C6.35078 10.9862 5.01382 9.64922 5.01382 8C5.01382 7.35653 5.21697 6.76101 5.56321 6.27341L6.29783 7.00802C6.12778 7.29932 6.03009 7.63844 6.03009 8C6.03009 9.08795 6.91205 9.96991 8 9.96991C9.08794 9.96991 9.9699 9.08795 9.9699 8C9.9699 7.27462 9.57756 6.64143 8.9959 6.30011C8.75773 6.16035 8.45137 6.24013 8.31161 6.47829C8.17185 6.71646 8.25163 7.02282 8.48979 7.16258C8.77813 7.33178 8.9699 7.64377 8.9699 8C8.9699 8.53567 8.53566 8.96991 8 8.96991C7.46433 8.96991 7.03009 8.53567 7.03009 8C7.03009 7.87888 7.05216 7.76309 7.09262 7.6563C7.24121 7.67561 7.39681 7.62819 7.51096 7.51404C7.70622 7.31878 7.70622 7.0022 7.51096 6.80694L2.76032 2.0563Z" />
+
  {:else if name === "archive"}
+
    <path d="M2 3H3L3 13H2L2 3Z" />
+
    <path d="M13 3L14 3L14 13H13L13 3Z" />
+
    <path d="M5 8L6 8V9H5V8Z" />
+
    <path d="M10 8H11V9H10V8Z" />
+
    <path d="M6 9L10 9L10 10H6V9Z" />
+
    <path d="M3 13L13 13V14L3 14L3 13Z" />
+
    <path d="M3 2L13 2V3L3 3L3 2Z" />
+
    <path d="M6 7H10V8L6 8V7Z" />
+
    <path d="M2 5L14 5V6L2 6V5Z" />
  {:else if name === "arrow-box-up-right"}
    <path
      fill-rule="evenodd"
modified src/lib/utils.ts
@@ -252,3 +252,10 @@ export function twemoji(
export function formatObjectId(id: string): string {
  return id.substring(0, 7);
}
+

+
export function formatQualifiedRefname(
+
  refname: string,
+
  peer: string | undefined,
+
): string {
+
  return peer ? `refs/namespaces/${peer}/refs/heads/${refname}` : refname;
+
}
modified src/views/repos/Commit.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
  import type { BaseUrl, Commit, Repo, SeedingPolicy } from "@http-client";

-
  import { formatObjectId } from "@app/lib/utils";
+
  import { baseUrlToString, formatObjectId } from "@app/lib/utils";

  import Button from "@app/components/Button.svelte";
  import Changeset from "@app/views/repos/Changeset.svelte";
@@ -13,6 +13,7 @@
  import Link from "@app/components/Link.svelte";
  import Separator from "./Separator.svelte";
  import Share from "./Share.svelte";
+
  import { enableHide } from "@app/lib/modal";

  export let baseUrl: BaseUrl;
  export let seedingPolicy: SeedingPolicy;
@@ -20,6 +21,17 @@
  export let repo: Repo;
  export let nodeAvatarUrl: string | undefined;

+
  let enabledArchiveDownload = false;
+

+
  void fetch(
+
    `${baseUrlToString(baseUrl)}/raw/${repo.rid}/${commit.commit.id}.tar.gz`,
+
    {
+
      method: "HEAD",
+
    },
+
  ).then(response => {
+
    enabledArchiveDownload = response.ok;
+
  });
+

  $: header = commit.commit;
</script>

@@ -88,6 +100,14 @@
                <Icon name="chevron-left-right" />
              </Button>
            </Link>
+
            {#if enabledArchiveDownload}
+
              <a
+
                href={`${baseUrlToString(baseUrl)}/raw/${repo.rid}/${commit.commit.id}.tar.gz`}>
+
                <Button variant="outline">
+
                  <Icon name="archive" />Download
+
                </Button>
+
              </a>
+
            {/if}
            <Share />
          </div>
        </span>
modified src/views/repos/Header/CloneButton.svelte
@@ -14,8 +14,10 @@
  export let baseUrl: BaseUrl;
  export let id: string;
  export let name: string;
+
  export let currentRefname: string;
+
  export let enabledArchiveDownload: boolean;

-
  let radicle: boolean = true;
+
  let activeTab: "radicle" | "git" | "archive" = "radicle";

  $: radCloneUrl = `rad clone ${id}`;
  $: portFragment =
@@ -23,6 +25,7 @@
    baseUrl.port === config.nodes.defaultHttpdPort
      ? ""
      : `:${baseUrl.port}`;
+
  $: archiveUrl = `curl -OJ ${baseUrl.scheme}://${baseUrl.hostname}${portFragment}/raw/${id}/archive/${currentRefname}`;
  $: gitCloneUrl = `git clone ${baseUrl.scheme}://${
    baseUrl.hostname
  }${portFragment}/${parseRepositoryId(id)?.pubkey ?? id}.git ${name}`;
@@ -47,13 +50,13 @@

  <div slot="popover" style:width="24rem" class="popover">
    <div style:margin-bottom="1.5rem">
-
      <Radio ariaLabel="Toggle render method">
+
      <Radio ariaLabel="Toggle render method" styleGap="2px">
        <Button
          styleWidth="100%"
          styleBorderRadius="0"
-
          variant={radicle ? "selected" : "not-selected"}
+
          variant={activeTab === "radicle" ? "selected" : "not-selected"}
          on:click={() => {
-
            radicle = true;
+
            activeTab = "radicle";
          }}>
          <Icon name="logo" />
          Radicle
@@ -62,24 +65,37 @@
        <Button
          styleWidth="100%"
          styleBorderRadius="0"
-
          variant={!radicle ? "selected" : "not-selected"}
+
          variant={activeTab === "git" ? "selected" : "not-selected"}
          on:click={() => {
-
            radicle = false;
+
            activeTab = "git";
          }}>
          <Icon name="git" />
          Git
        </Button>
+
        {#if enabledArchiveDownload}
+
          <div class="global-spacer"></div>
+
          <Button
+
            styleWidth="100%"
+
            styleBorderRadius="0"
+
            variant={activeTab === "archive" ? "selected" : "not-selected"}
+
            on:click={() => {
+
              activeTab = "archive";
+
            }}>
+
            <Icon name="archive" />
+
            Download
+
          </Button>
+
        {/if}
      </Radio>
    </div>

-
    {#if radicle}
+
    {#if activeTab === "radicle"}
      <label for="rad-clone-url">
        Use the <ExternalLink href="https://radicle.xyz">
          Radicle CLI
        </ExternalLink> to clone this repository.
      </label>
      <Command command={radCloneUrl} />
-
    {:else}
+
    {:else if activeTab === "git"}
      <div>
        <label for="git-clone-url">
          If you don't have Radicle installed, you can still clone the
@@ -91,6 +107,18 @@
          such as issues or patches.
        </div>
      </div>
+
    {:else if activeTab === "archive"}
+
      <div>
+
        <label for="git-clone-url">
+
          If you don't have Radicle installed, you can still download an archive
+
          of the repository.
+
        </label>
+
        <Command command={archiveUrl} />
+
        <div style:margin-top="1.5rem">
+
          Note that a compressed archive of the source code does not include any
+
          of the social artifacts such as issues or patches nor the git history.
+
        </div>
+
      </div>
    {/if}
  </div>
</Popover>
modified src/views/repos/History.svelte
@@ -11,7 +11,7 @@

  import config from "virtual:config";
  import { HttpdClient } from "@http-client";
-
  import { baseUrlToString } from "@app/lib/utils";
+
  import { baseUrlToString, formatQualifiedRefname } from "@app/lib/utils";
  import { groupCommits } from "@app/lib/commit";

  import Button from "@app/components/Button.svelte";
@@ -104,7 +104,14 @@
      Commits
    </Link>
  </svelte:fragment>
-
  <RepoNameHeader {repo} {baseUrl} slot="header" />
+
  <RepoNameHeader
+
    {repo}
+
    currentRefname={formatQualifiedRefname(
+
      revision || repo.payloads["xyz.radicle.project"].data.defaultBranch,
+
      peer,
+
    )}
+
    {baseUrl}
+
    slot="header" />

  <div style:margin="1rem" slot="subheader">
    <Header
modified src/views/repos/Source.svelte
@@ -20,6 +20,7 @@
  import RepoNameHeader from "./Source/RepoNameHeader.svelte";
  import Separator from "./Separator.svelte";
  import TreeComponent from "./Source/Tree.svelte";
+
  import { formatQualifiedRefname } from "@app/lib/utils";

  export let baseUrl: BaseUrl;
  export let blobResult: BlobResult;
@@ -130,7 +131,14 @@
      <FilePath filenameWithPath={path} />
    {/if}
  </svelte:fragment>
-
  <RepoNameHeader {repo} {baseUrl} slot="header" />
+
  <RepoNameHeader
+
    {repo}
+
    currentRefname={formatQualifiedRefname(
+
      revision || repo.payloads["xyz.radicle.project"].data.defaultBranch,
+
      peer,
+
    )}
+
    {baseUrl}
+
    slot="header" />

  <div style:margin="1rem" slot="subheader">
    <Header
modified src/views/repos/Source/RepoNameHeader.svelte
@@ -3,7 +3,7 @@

  import dompurify from "dompurify";
  import { markdown } from "@app/lib/markdown";
-
  import { twemoji } from "@app/lib/utils";
+
  import { baseUrlToString, twemoji } from "@app/lib/utils";

  import Badge from "@app/components/Badge.svelte";
  import CloneButton from "@app/views/repos/Header/CloneButton.svelte";
@@ -15,6 +15,18 @@

  export let repo: Repo;
  export let baseUrl: BaseUrl;
+
  export let currentRefname: string;
+

+
  let enabledArchiveDownload = false;
+

+
  void fetch(
+
    `${baseUrlToString(baseUrl)}/raw/${repo.rid}/archive/${currentRefname}`,
+
    {
+
      method: "HEAD",
+
    },
+
  ).then(response => {
+
    enabledArchiveDownload = response.ok;
+
  });

  function render(content: string): string {
    return dompurify.sanitize(
@@ -89,7 +101,12 @@
        style:display="flex"
        style:gap="0.5rem"
        class="global-hide-on-mobile-down">
-
        <CloneButton {baseUrl} id={repo.rid} name={project.data.name} />
+
        <CloneButton
+
          {enabledArchiveDownload}
+
          {baseUrl}
+
          {currentRefname}
+
          id={repo.rid}
+
          name={project.data.name} />
        <SeedButton seedCount={repo.seeding} repoId={repo.rid} />
      </div>
      <div