Skip to content

Commit

Permalink
feat: Collection thumbnails, start page, and public view updates (#2209)
Browse files Browse the repository at this point in the history
- Allows user to choose collection replay home page and collection
thumbnail (resolves
#2182)
- Displays collection thumbnails on org dashboard and public page
- Enables downloading public collection (resolves
#2233)
- Adds caption as "Summary" to metadata dialog
- Moves description editor to "About" tab

---------

Co-authored-by: Emma Segal-Grossman <[email protected]>
  • Loading branch information
SuaYoo and emma-sg committed Dec 23, 2024
1 parent 30c50ef commit 6f2d7bb
Show file tree
Hide file tree
Showing 39 changed files with 2,587 additions and 720 deletions.
1 change: 1 addition & 0 deletions frontend/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
declare module "*.avif";
declare module "*.svg";
declare module "*.webp";
declare module "*.css";
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
38 changes: 25 additions & 13 deletions frontend/src/components/not-found.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { localized, msg } from "@lit/localize";
import { html } from "lit";
import { html, nothing } from "lit";
import { customElement } from "lit/decorators.js";

import { BtrixElement } from "@/classes/BtrixElement";
Expand All @@ -9,27 +9,39 @@ import { BtrixElement } from "@/classes/BtrixElement";
export class NotFound extends BtrixElement {
render() {
return html`
<div class="text-center text-neutral-500">
<p class="my-4 border-b py-4 text-xl leading-none text-neutral-400">
${msg("Page not found")}
<div class="text-center">
<p class="my-4 border-b py-4 text-xl leading-none text-neutral-500">
${msg("Sorry, we couldn’t find that page")}
</p>
<p>
<p class="text-neutral-600">
${msg("Check the URL to make sure you’ve entered it correctly.")}
</p>
<div class="my-4">
<sl-button href="/" @click=${this.navigate.link} size="small"
>${msg("Go to Home")}</sl-button
>
</div>
<p class="text-neutral-500">
${msg("Did you click a link to get here?")}
<button
class="text-blue-500 transition-colors hover:text-blue-600"
class="text-cyan-500 transition-colors hover:text-cyan-600"
@click=${() => {
window.history.back();
}}
>
${msg("Go Back")}
</button>
<br />
${msg("Or")}
<btrix-link
href="https://github.com/webrecorder/browsertrix/issues/new/choose"
>
${msg("Report a Broken Link")}
</btrix-link>
${this.navigate.isPublicPage
? nothing
: html`
<br />
${msg("Or")}
<btrix-link
href="https://github.com/webrecorder/browsertrix/issues/new/choose"
>
${msg("Report a Broken Link")}
</btrix-link>
`}
</p>
</div>
`;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ui/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class Button extends TailwindElement {
small: tw`min-h-6 min-w-6 rounded-md text-base`,
medium: tw`min-h-8 min-w-8 rounded-sm text-lg`,
}[this.size],
this.raised && tw`border shadow-sm`,
this.raised && tw`shadow ring-1 ring-neutral-200`,
this.filled
? [
tw`text-white`,
Expand Down
18 changes: 16 additions & 2 deletions frontend/src/components/ui/markdown-editor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { msg, str } from "@lit/localize";
import { localized, msg, str } from "@lit/localize";
import { wrap, type AwaitableInstance } from "ink-mde";
import { css, html, type PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators.js";
Expand All @@ -16,6 +16,7 @@ export type MarkdownChangeEvent = CustomEvent<MarkdownChangeDetail>;
*
* @fires btrix-change MarkdownChangeEvent
*/
@localized()
@customElement("btrix-markdown-editor")
export class MarkdownEditor extends BtrixElement {
static styles = css`
Expand Down Expand Up @@ -53,11 +54,18 @@ export class MarkdownEditor extends BtrixElement {
white-space: nowrap;
border-width: 0;
}
.cm-line:only-child {
min-height: 8em;
}
`;

@property({ type: String })
label = "";

@property({ type: String })
placeholder = "";

@property({ type: String })
initialValue = "";

Expand All @@ -76,6 +84,11 @@ export class MarkdownEditor extends BtrixElement {
return this.textarea?.checkValidity();
}

public async focus() {
await this.updateComplete;
(await this.editor)?.focus();
}

protected willUpdate(changedProperties: PropertyValues<this>): void {
if (
changedProperties.has("initialValue") &&
Expand All @@ -99,7 +112,7 @@ export class MarkdownEditor extends BtrixElement {
const isInvalid = this.maxlength && this.value.length > this.maxlength;
return html`
<fieldset ?data-invalid=${isInvalid} ?data-user-invalid=${isInvalid}>
<label class="form-label">${this.label}</label>
${this.label && html`<label class="form-label">${this.label}</label>`}
<textarea id="editor-textarea"></textarea>
<div class="helpText flex items-baseline justify-between">
<p class="text-xs">
Expand Down Expand Up @@ -181,6 +194,7 @@ export class MarkdownEditor extends BtrixElement {
taskList: false,
upload: false,
},
placeholder: this.placeholder,
});
}
}
12 changes: 12 additions & 0 deletions frontend/src/components/ui/markdown-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ export class MarkdownViewer extends LitElement {
img {
max-width: 100%;
}
p {
line-height: inherit;
}
p:first-child {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
`,
];

Expand Down
27 changes: 18 additions & 9 deletions frontend/src/components/ui/overflow-dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type { SlDropdown, SlMenu } from "@shoelace-style/shoelace";
import { html } from "lit";
import {
customElement,
property,
query,
queryAssignedElements,
state,
} from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";

import { TailwindElement } from "@/classes/TailwindElement";

Expand All @@ -26,6 +28,9 @@ import { TailwindElement } from "@/classes/TailwindElement";
@localized()
@customElement("btrix-overflow-dropdown")
export class OverflowDropdown extends TailwindElement {
@property({ type: Boolean })
raised = false;

@state()
private hasMenuItems?: boolean;

Expand All @@ -37,15 +42,19 @@ export class OverflowDropdown extends TailwindElement {

render() {
return html`
<sl-dropdown ?disabled=${!this.hasMenuItems} hoist>
<sl-icon-button
slot="trigger"
class="font-base attr-[disabled]:invisible part-[base]:p-3"
label=${msg("Actions")}
name="three-dots-vertical"
?disabled=${!this.hasMenuItems}
>
</sl-icon-button>
<sl-dropdown
?disabled=${!this.hasMenuItems}
hoist
distance=${ifDefined(this.raised ? "4" : undefined)}
>
<btrix-button slot="trigger" ?raised=${this.raised}>
<sl-icon
label=${msg("Actions")}
name="three-dots-vertical"
class="size-4 text-base leading-none"
></sl-icon>
</btrix-button>
<slot
@slotchange=${() => (this.hasMenuItems = this.menu.length > 0)}
></slot>
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/components/ui/section-heading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export class SectionHeading extends LitElement {
gap: 0.5rem;
font-size: var(--sl-font-size-medium);
color: var(--sl-color-neutral-500);
padding-top: var(--sl-spacing-x-small);
padding-bottom: var(--sl-spacing-x-small);
min-height: 2rem;
line-height: 1;
border-bottom: 1px solid var(--sl-panel-border-color);
margin-bottom: var(--margin);
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/ui/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ tableCSS.split("}").forEach((rule: string) => {
* @slot head
* @slot
* @csspart head
* @cssproperty --btrix-column-gap
* @cssproperty --btrix-cell-gap
* @cssproperty --btrix-cell-padding-top
* @cssproperty --btrix-cell-padding-left
Expand All @@ -58,13 +59,15 @@ tableCSS.split("}").forEach((rule: string) => {
export class Table extends LitElement {
static styles = css`
:host {
--btrix-column-gap: 0;
--btrix-cell-gap: 0;
--btrix-cell-padding-top: 0;
--btrix-cell-padding-bottom: 0;
--btrix-cell-padding-left: 0;
--btrix-cell-padding-right: 0;
display: grid;
column-gap: var(--btrix-column-gap, 0);
}
`;

Expand Down
85 changes: 84 additions & 1 deletion frontend/src/controllers/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { msg } from "@lit/localize";
import type { ReactiveController, ReactiveControllerHost } from "lit";
import throttle from "lodash/fp/throttle";

import { APIError, type Detail } from "@/utils/api";
import AuthService from "@/utils/AuthService";
Expand All @@ -12,6 +13,11 @@ export interface APIEventMap {
"btrix-storage-quota-update": CustomEvent<QuotaUpdateDetail>;
}

export enum AbortReason {
UserCancel = "user-canceled",
QuotaReached = "storage_quota_reached",
}

/**
* Utilities for interacting with the Browsertrix backend API
*
Expand All @@ -29,13 +35,20 @@ export interface APIEventMap {
export class APIController implements ReactiveController {
host: ReactiveControllerHost & EventTarget;

uploadProgress = 0;

private uploadRequest: XMLHttpRequest | null = null;

constructor(host: APIController["host"]) {
this.host = host;
host.addController(this);
}

hostConnected() {}
hostDisconnected() {}

hostDisconnected() {
this.cancelUpload();
}

async fetch<T = unknown>(path: string, options?: RequestInit): Promise<T> {
const auth = appState.auth;
Expand Down Expand Up @@ -156,4 +169,74 @@ export class APIController implements ReactiveController {
details: errorDetail as Detail[],
});
}

async upload(
path: string,
file: File,
): Promise<{ id: string; added: boolean; storageQuotaReached: boolean }> {
const auth = appState.auth;

if (!auth) throw new Error("auth not in state");

// TODO handle multiple uploads
if (this.uploadRequest) {
console.debug("upload request exists");
this.cancelUpload();
}

return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();

xhr.open("PUT", `/api/${path}`);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
Object.entries(auth.headers).forEach(([k, v]) => {
xhr.setRequestHeader(k, v);
});
xhr.addEventListener("load", () => {
if (xhr.status === 200) {
resolve(
JSON.parse(xhr.response as string) as {
id: string;
added: boolean;
storageQuotaReached: boolean;
},
);
}
if (xhr.status === 403) {
reject(AbortReason.QuotaReached);
}
});
xhr.addEventListener("error", () => {
reject(
new APIError({
message: xhr.statusText,
status: xhr.status,
}),
);
});
xhr.addEventListener("abort", () => {
reject(AbortReason.UserCancel);
});
xhr.upload.addEventListener("progress", this.onUploadProgress);

xhr.send(file);

this.uploadRequest = xhr;
});
}

readonly onUploadProgress = throttle(100)((e: ProgressEvent) => {
this.uploadProgress = (e.loaded / e.total) * 100;

this.host.requestUpdate();
});

private cancelUpload() {
if (this.uploadRequest) {
this.uploadRequest.abort();
this.uploadRequest = null;
}

this.onUploadProgress.cancel();
}
}
6 changes: 6 additions & 0 deletions frontend/src/controllers/navigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export class NavigateController implements ReactiveController {
return "/";
}

get isPublicPage() {
return window.location.pathname.startsWith(
`/${RouteNamespace.PublicOrgs}/`,
);
}

constructor(host: NavigateController["host"]) {
this.host = host;
host.addController(this);
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/features/archived-items/file-uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ enum AbortReason {
* ></btrix-file-uploader>
* ```
*
* @TODO Refactor to use this.api.upload
*
* @event request-close
* @event upload-start
* @event uploaded
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/features/collections/collection-items-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ export class CollectionItemsDialog extends BtrixElement {
this.close();
this.dispatchEvent(new CustomEvent("btrix-collection-saved"));
this.notify.toast({
message: msg(str`Successfully saved archived item selection.`),
message: msg(str`Archived item selection updated.`),
variant: "success",
icon: "check2-circle",
id: "archived-item-selection-status",
Expand All @@ -683,7 +683,7 @@ export class CollectionItemsDialog extends BtrixElement {
this.notify.toast({
message: isApiError(e)
? e.message
: msg("Something unexpected went wrong"),
: msg("Sorry, couldn't save archived item selection at this time."),
variant: "danger",
icon: "exclamation-octagon",
id: "archived-item-selection-status",
Expand Down
Loading

0 comments on commit 6f2d7bb

Please sign in to comment.