mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Probe archive download support on demand
The source and commit views hit the /raw archive endpoint with a HEAD request on every page load to decide whether to show the download UI. That endpoint runs `git archive` server-side. Move detection to user interaction: the clone popover probes when the Download tab is opened, and the commit view checks on click, navigating on success or showing a message on failure. Cache only successful results per URL so transient or moving-ref 404s aren't latched for the session.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
export const archiveUnsupportedMessage =
|
||||
"The radicle-httpd on this node doesn't support archive downloads.";
|
||||
|
||||
const supportedUrls = new Set<string>();
|
||||
|
||||
export async function archiveDownloadSupported(url: string): Promise<boolean> {
|
||||
if (supportedUrls.has(url)) {
|
||||
return true;
|
||||
}
|
||||
const response = await fetch(url, { method: "HEAD" });
|
||||
if (response.ok) {
|
||||
supportedUrls.add(url);
|
||||
}
|
||||
return response.ok;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Commit, Repo } from "@http-client";
|
||||
|
||||
import {
|
||||
archiveDownloadSupported,
|
||||
archiveUnsupportedMessage,
|
||||
} from "@app/lib/archive";
|
||||
import { baseUrlToString, formatObjectId } from "@app/lib/utils";
|
||||
import { renderCommitDescription } from "@app/lib/commit";
|
||||
|
||||
@@ -13,6 +17,7 @@
|
||||
import JobCob from "@app/components/JobCob.svelte";
|
||||
import Layout from "./Layout.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
import Separator from "./Separator.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
@@ -21,16 +26,20 @@
|
||||
export let nodeId: string;
|
||||
export let nodeAvatarUrl: string | undefined;
|
||||
|
||||
let enabledArchiveDownload = false;
|
||||
let downloading = false;
|
||||
|
||||
void fetch(
|
||||
`${baseUrlToString(baseUrl)}/raw/${repo.rid}/${commit.commit.id}.tar.gz`,
|
||||
{
|
||||
method: "HEAD",
|
||||
},
|
||||
).then(response => {
|
||||
enabledArchiveDownload = response.ok;
|
||||
});
|
||||
async function downloadArchive(): Promise<boolean> {
|
||||
const url = `${baseUrlToString(baseUrl)}/raw/${repo.rid}/${commit.commit.id}.tar.gz`;
|
||||
try {
|
||||
if (await archiveDownloadSupported(url)) {
|
||||
window.location.href = url;
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$: header = commit.commit;
|
||||
</script>
|
||||
@@ -59,6 +68,10 @@
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.download-error {
|
||||
font: var(--txt-body-m-regular);
|
||||
width: 18rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {nodeId} {nodeAvatarUrl} {baseUrl} {repo}>
|
||||
@@ -108,14 +121,30 @@
|
||||
<Icon name="chevron-left-right" />
|
||||
</Button>
|
||||
</Link>
|
||||
{#if enabledArchiveDownload}
|
||||
<a
|
||||
href={`${baseUrlToString(baseUrl)}/raw/${repo.rid}/${commit.commit.id}.tar.gz`}>
|
||||
<Button variant="outline">
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
|
||||
<Button
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
let:expanded
|
||||
on:click={async () => {
|
||||
if (expanded) {
|
||||
toggle();
|
||||
} else if (!downloading) {
|
||||
downloading = true;
|
||||
const ok = await downloadArchive();
|
||||
downloading = false;
|
||||
if (!ok) {
|
||||
toggle();
|
||||
}
|
||||
}
|
||||
}}
|
||||
variant="outline">
|
||||
<Icon name="archive" />Download
|
||||
</Button>
|
||||
</a>
|
||||
{/if}
|
||||
<div slot="popover" class="download-error">
|
||||
{archiveUnsupportedMessage}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</span>
|
||||
<CommitAuthorship {header}>
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
import type { BaseUrl } from "@http-client";
|
||||
|
||||
import config from "@app/lib/config";
|
||||
import {
|
||||
archiveDownloadSupported,
|
||||
archiveUnsupportedMessage,
|
||||
} from "@app/lib/archive";
|
||||
import { parseRepositoryId } from "@app/lib/utils";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import Command from "@app/components/Command.svelte";
|
||||
import ExternalLink from "@app/components/ExternalLink.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
import Radio from "@app/components/Radio.svelte";
|
||||
|
||||
@@ -18,18 +23,15 @@
|
||||
export let name: string;
|
||||
export let currentRefname: string;
|
||||
|
||||
let enabledArchiveDownload = false;
|
||||
|
||||
void fetch(
|
||||
`${baseUrlToString(baseUrl)}/raw/${id}/archive/${currentRefname}`,
|
||||
{
|
||||
method: "HEAD",
|
||||
},
|
||||
).then(response => {
|
||||
enabledArchiveDownload = response.ok;
|
||||
});
|
||||
|
||||
let activeTab: "radicle" | "git" | "archive" = "radicle";
|
||||
let archiveProbe: Promise<boolean> | undefined;
|
||||
|
||||
function showArchiveTab() {
|
||||
activeTab = "archive";
|
||||
archiveProbe = archiveDownloadSupported(
|
||||
`${baseUrlToString(baseUrl)}/raw/${id}/archive/${currentRefname}`,
|
||||
);
|
||||
}
|
||||
|
||||
$: radCloneUrl = `rad clone ${id}`;
|
||||
$: portFragment =
|
||||
@@ -83,19 +85,15 @@
|
||||
<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";
|
||||
}}>
|
||||
on:click={showArchiveTab}>
|
||||
<Icon name="archive" />
|
||||
Download
|
||||
</Button>
|
||||
{/if}
|
||||
</Radio>
|
||||
</div>
|
||||
|
||||
@@ -119,17 +117,28 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if activeTab === "archive"}
|
||||
{#await archiveProbe}
|
||||
<Loading small center />
|
||||
{:then supported}
|
||||
{#if supported}
|
||||
<div>
|
||||
<label for="git-clone-url">
|
||||
If you don't have Radicle installed, you can still download an archive
|
||||
of the repository.
|
||||
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.
|
||||
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>
|
||||
{:else}
|
||||
{archiveUnsupportedMessage}
|
||||
{/if}
|
||||
{:catch}
|
||||
{archiveUnsupportedMessage}
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
Reference in New Issue
Block a user