Skip to content

feat: Download single WACZ file when possible #2805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BtrixElement } from "@/classes/BtrixElement";
import { ClipboardController } from "@/controllers/clipboard";
import { WorkflowTab } from "@/routes";
import type { Crawl, ListWorkflow, Workflow } from "@/types/crawler";
import { downloadLink } from "@/utils/crawl-workflows/downloadLink";
import { isNotFailed, isSuccessfullyFinished } from "@/utils/crawler";
import { isArchivingDisabled } from "@/utils/orgs";

Expand Down Expand Up @@ -209,13 +210,14 @@ export class WorkflowActionMenu extends BtrixElement {
private renderLatestCrawlMenu(latestCrawl: Crawl) {
const authToken = this.authState?.headers.Authorization.split(" ")[1];
const logTotals = this.logTotals;
const download = downloadLink(latestCrawl, this.authState);

return html`
<sl-menu slot="submenu">
<btrix-menu-item-link
href=${`/api/orgs/${this.orgId}/all-crawls/${latestCrawl.id}/download?auth_bearer=${authToken}`}
href=${download.path}
?disabled=${!latestCrawl.fileSize}
download
download=${download.name}
>
<sl-icon name="cloud-download" slot="prefix"></sl-icon>
${msg("Download Item")}
Expand Down
55 changes: 31 additions & 24 deletions frontend/src/pages/org/archived-item-detail/archived-item-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import type {
} from "@/types/crawler";
import type { QARun } from "@/types/qa";
import { isApiError } from "@/utils/api";
import { downloadLink } from "@/utils/crawl-workflows/downloadLink";
import { hasFiles } from "@/utils/crawl-workflows/hasFiles";
import {
isActive,
isNotFailed,
Expand Down Expand Up @@ -145,13 +147,6 @@ export class ArchivedItemDetail extends BtrixElement {

private timerId?: number;

private get hasFiles(): boolean | null {
if (!this.item) return null;
if (!this.item.resources) return false;

return this.item.resources.length > 0;
}

private get formattedFinishedDate() {
if (!this.item) return;

Expand Down Expand Up @@ -326,17 +321,7 @@ export class ArchivedItemDetail extends BtrixElement {
case "files":
sectionContent = this.renderPanel(
html` ${this.renderTitle(this.tabLabels.files)}
<sl-tooltip content=${msg("Download Files as Multi-WACZ")}>
<sl-button
href=${`/api/orgs/${this.orgId}/all-crawls/${this.itemId}/download?auth_bearer=${authToken}`}
download=${`browsertrix-${this.itemId}.wacz`}
size="small"
variant="primary"
>
<sl-icon slot="prefix" name="cloud-download"></sl-icon>
${msg("Download Files")}
</sl-button>
</sl-tooltip>`,
${this.renderDownloadFilesButton()}`,
this.renderFiles(),
);
break;
Expand Down Expand Up @@ -622,7 +607,7 @@ export class ArchivedItemDetail extends BtrixElement {
private renderMenu() {
if (!this.item) return;

const authToken = this.authState?.headers.Authorization.split(" ")[1];
const download = downloadLink(this.item, this.authState);

return html`
<sl-dropdown placement="bottom-end" distance="4" hoist>
Expand Down Expand Up @@ -657,8 +642,8 @@ export class ArchivedItemDetail extends BtrixElement {
`,
)}
<btrix-menu-item-link
href=${`/api/orgs/${this.orgId}/all-crawls/${this.itemId}/download?auth_bearer=${authToken}`}
download
href=${download.path}
download=${download.name}
>
<sl-icon name="cloud-download" slot="prefix"></sl-icon>
${msg("Download Item")}
Expand Down Expand Up @@ -763,7 +748,7 @@ export class ArchivedItemDetail extends BtrixElement {

const config = JSON.stringify({ headers });

const canReplay = this.hasFiles;
const canReplay = hasFiles(this.item);

return html`
<!-- https://github.com/webrecorder/browsertrix-crawler/blob/9f541ab011e8e4bccf8de5bd7dc59b632c694bab/screencast/index.html -->
Expand Down Expand Up @@ -977,10 +962,10 @@ export class ArchivedItemDetail extends BtrixElement {

private renderFiles() {
return html`
${this.hasFiles
${hasFiles(this.item)
? html`
<ul class="rounded-lg border text-sm">
${this.item!.resources!.map(
${this.item.resources.map(
(file) => html`
<li
class="flex justify-between border-t p-3 first:border-t-0"
Expand Down Expand Up @@ -1026,6 +1011,28 @@ export class ArchivedItemDetail extends BtrixElement {
`;
}

private renderDownloadFilesButton() {
if (!hasFiles(this.item)) return;

const singleFile = this.item.resources.length === 1;
const download = downloadLink(this.item, this.authState);

return html`<sl-tooltip
content=${msg("Download Files as Multi-WACZ")}
?disabled=${singleFile}
>
<sl-button
href=${download.path}
download=${download.name}
size="small"
variant="primary"
>
<sl-icon slot="prefix" name="cloud-download"></sl-icon>
${singleFile ? msg("Download File") : msg("Download Files")}
</sl-button>
</sl-tooltip>`;
}

private renderLogs() {
if (!this.itemId) return;

Expand Down
31 changes: 12 additions & 19 deletions frontend/src/pages/org/workflow-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type { APIPaginatedList, APIPaginationQuery } from "@/types/api";
import { type CrawlState } from "@/types/crawlState";
import { type StorageSeedFile } from "@/types/workflow";
import { isApiError } from "@/utils/api";
import { downloadLink } from "@/utils/crawl-workflows/downloadLink";
import { settingsForDuplicate } from "@/utils/crawl-workflows/settingsForDuplicate";
import {
DEFAULT_MAX_SCALE,
Expand Down Expand Up @@ -718,8 +719,7 @@ export class WorkflowDetail extends BtrixElement {
const authToken = this.authState?.headers.Authorization.split(" ")[1];
const disableDownload = this.isRunning;
const disableReplay = !latestCrawl.fileSize;
const replayHref = `/api/orgs/${this.orgId}/all-crawls/${latestCrawlId}/download?auth_bearer=${authToken}`;
const replayFilename = `browsertrix-${latestCrawlId}.wacz`;
const download = downloadLink(latestCrawl, this.authState);

return html`
<btrix-copy-button
Expand All @@ -736,22 +736,15 @@ export class WorkflowDetail extends BtrixElement {
?disabled=${!disableDownload}
>
<sl-button-group>
<sl-tooltip
content="${msg("Download Item as WACZ")} (${this.localize.bytes(
latestCrawl.fileSize || 0,
)})"
?disabled=${disableReplay}
<sl-button
size="small"
href=${download.path}
download=${download.name}
?disabled=${disableDownload || disableReplay}
>
<sl-button
size="small"
href=${replayHref}
download=${replayFilename}
?disabled=${disableDownload || disableReplay}
>
<sl-icon name="cloud-download" slot="prefix"></sl-icon>
${msg("Download")}
</sl-button>
</sl-tooltip>
<sl-icon name="cloud-download" slot="prefix"></sl-icon>
${msg("Download")}
</sl-button>
<sl-dropdown distance="4" placement="bottom-end" hoist>
<sl-button
slot="trigger"
Expand All @@ -765,9 +758,9 @@ export class WorkflowDetail extends BtrixElement {
</sl-button>
<sl-menu>
<btrix-menu-item-link
href=${replayHref}
href=${download.path}
?disabled=${disableDownload || disableReplay}
download=${replayFilename}
download=${download.name}
>
<sl-icon name="cloud-download" slot="prefix"></sl-icon>
${msg("Item")}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export type Range<F extends number, T extends number> = Exclude<
Enumerate<F>
>;

/** Array with at least one item */
export type NonEmptyArray<T> = [T, ...T[]];

export enum SortDirection {
Descending = -1,
Ascending = 1,
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/utils/crawl-workflows/downloadLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Auth } from "@/types/auth";
import type { ArchivedItem } from "@/types/crawler";
import { hasFiles } from "@/utils/crawl-workflows/hasFiles";

/**
* Get link to download archived item
*/
export function downloadLink(
item?: ArchivedItem,
authState?: Auth | null,
): { path: string; name: string } {
if (!hasFiles(item)) return { path: "", name: "" };

if (item.resources.length > 1) {
return {
path: `/api/orgs/${item.oid}/all-crawls/${item.id}/download?auth_bearer=${authState?.headers.Authorization.split(" ")[1]}`,
name: `${item.id}.wacz`,
};
}

const { path, name } = item.resources[0];

return { path, name };
}
14 changes: 14 additions & 0 deletions frontend/src/utils/crawl-workflows/hasFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ArchivedItem } from "@/types/crawler";
import type { NonEmptyArray } from "@/types/utils";

/**
* Check whether archived item has at least one WACZ file
*/
export function hasFiles(item?: ArchivedItem): item is ArchivedItem & {
resources: NonEmptyArray<NonNullable<ArchivedItem["resources"]>[number]>;
} {
if (!item) return false;
if (!item.resources) return false;

return Boolean(item.resources[0]);
}
Loading