ui: Public Collections UI Nitpicks (#2287)

- Removes share link from the dialogue footer
- Removes stickied collection navigation, replaces with improved
viewport-based scaling!
- Adds a max-width for the collection description in the logged in view.
- Moves the markdown editor buttons to below the editor
- Controls are now In-line with how we handle dialogue options
elsewhere, fixes a minor responsive design issue.
- Minor copy changes

---------

Co-authored-by: emma <hi@emma.cafe>
Co-authored-by: sua yoo <sua@webrecorder.org>
This commit is contained in:
Henry Wilkinson 2025-01-08 19:00:21 -05:00 committed by sua yoo
parent d8655d3bc6
commit 56a634e593
No known key found for this signature in database
GPG Key ID: 5AD1B4C02D4F0567
10 changed files with 190 additions and 116 deletions

View File

@ -27,6 +27,14 @@ export class MarkdownEditor extends BtrixElement {
--ink-block-padding: var(--sl-input-spacing-small);
}
.ink-mde-textarea {
flex-grow: 1;
}
.ink-mde {
height: 100%;
}
.ink-mde {
border: solid var(--sl-input-border-width) var(--sl-input-border-color);
}
@ -56,7 +64,8 @@ export class MarkdownEditor extends BtrixElement {
}
.cm-line:only-child {
min-height: 8em;
height: 100%;
min-height: 20em;
}
`;
@ -111,7 +120,11 @@ export class MarkdownEditor extends BtrixElement {
render() {
const isInvalid = this.maxlength && this.value.length > this.maxlength;
return html`
<fieldset ?data-invalid=${isInvalid} ?data-user-invalid=${isInvalid}>
<fieldset
?data-invalid=${isInvalid}
?data-user-invalid=${isInvalid}
class="flex h-full flex-col"
>
${this.label && html`<label class="form-label">${this.label}</label>`}
<textarea id="editor-textarea"></textarea>
<div class="helpText flex items-baseline justify-between">

View File

@ -25,14 +25,14 @@ export class CollectionStartPageDialog extends BtrixElement {
{ label: string; icon: NonNullable<SlIcon["name"]>; detail: string }
> = {
[HomeView.Pages]: {
label: msg("Default"),
label: msg("List of Pages"),
icon: "list-ul",
detail: `${msg("ReplayWeb.Page default view")}`,
},
[HomeView.URL]: {
label: msg("Page"),
icon: "file-earmark",
detail: msg("Load a single page URL"),
label: msg("Start Page"),
icon: "file-earmark-richtext",
detail: msg("Show a single URL snapshot"),
},
};
@ -188,7 +188,7 @@ export class CollectionStartPageDialog extends BtrixElement {
<form @submit=${this.onSubmit}>
<sl-select
name="homeView"
label=${msg("Select View")}
label=${msg("Select Initial View")}
value=${this.homeView}
hoist
?disabled=${!this.replayLoaded}

View File

@ -63,11 +63,11 @@ export class CollectionsGrid extends BtrixElement {
href=${this.navigate.isPublicPage
? `/${RouteNamespace.PublicOrgs}/${this.slug}/collections/${collection.id}`
: `/${RouteNamespace.PrivateOrgs}/${this.slug}/collections/view/${collection.id}`}
class="group block h-full rounded-lg transition-all hover:scale-[102%]"
class="group block h-full rounded-lg"
@click=${this.navigate.link}
>
<div
class="relative mb-4 rounded-lg shadow-md shadow-stone-600/10 ring-1 ring-stone-600/10 transition-shadow group-hover:shadow-sm"
class="relative mb-4 rounded-lg shadow-md shadow-stone-600/10 ring-1 ring-stone-600/10 transition group-hover:shadow-stone-800/20 group-hover:ring-stone-800/20"
>
<btrix-collection-thumbnail
src=${ifDefined(

View File

@ -252,11 +252,7 @@ export class ShareCollection extends BtrixElement {
<sl-tab-panel name=${Tab.Link}>
<div class="px-4 pb-4">
${when(
showSettings && this.collection,
this.renderSettings,
this.renderShareLink,
)}
${when(showSettings && this.collection, this.renderSettings)}
</div>
</sl-tab-panel>
@ -266,7 +262,6 @@ export class ShareCollection extends BtrixElement {
</sl-tab-group>
<div slot="footer">
${when(showSettings, this.renderShareLink)}
<sl-button size="small" @click=${() => (this.showDialog = false)}>
${msg("Done")}
</sl-button>
@ -287,6 +282,10 @@ export class ShareCollection extends BtrixElement {
);
}}
></btrix-select-collection-access>
${when(
this.collection?.access != CollectionAccess.Private,
this.renderShareLink,
)}
${when(
this.org &&
!this.org.enablePublicProfile &&
@ -389,7 +388,7 @@ export class ShareCollection extends BtrixElement {
>
${isSelected
? html`<sl-icon
class="size-10 text-white drop-shadow-md"
class="size-10 stroke-black/50 text-white drop-shadow-md [paint-order:stroke]"
name="check-lg"
></sl-icon>`
: nothing}
@ -410,7 +409,7 @@ export class ShareCollection extends BtrixElement {
private readonly renderShareLink = () => {
return html`
<div class="text-left">
<div class="mt-4 text-left">
<div class="form-label">${msg("Link to Share")}</div>
<btrix-copy-field
class="mb-3"

View File

@ -15,12 +15,12 @@ export const iconFor = cached(
case "severe":
return html`<sl-icon
name="exclamation-triangle-fill"
class=${clsx("text-red-600", baseClasses, classList)}
class=${clsx("text-red-500", baseClasses, classList)}
></sl-icon>`;
case "moderate":
return html`<sl-icon
name="dash-square-fill"
class=${clsx("text-yellow-600", baseClasses, classList)}
class=${clsx("text-yellow-500", baseClasses, classList)}
></sl-icon>`;
case "good":
return html`<sl-icon
@ -37,7 +37,7 @@ export const iconFor = cached(
case "rejected":
return html`<sl-icon
name="hand-thumbs-down-fill"
class=${clsx("text-red-600", baseClasses, classList)}
class=${clsx("text-red-500", baseClasses, classList)}
></sl-icon>`;
case "commentOnly":
// Comment icons are rendered separately

View File

@ -29,9 +29,9 @@ export const textColorFromSeverity = cached((severity: Severity) => {
case "good":
return tw`text-green-600`;
case "moderate":
return tw`text-yellow-600`;
return tw`text-yellow-500`;
case "severe":
return tw`text-red-600`;
return tw`text-red-500`;
default:
return "";
}

View File

@ -39,7 +39,7 @@ export function page(
></btrix-document-title>
<div
class="mx-auto box-border flex min-h-full w-full max-w-screen-desktop flex-1 flex-col gap-3 p-3 lg:px-10"
class="mx-auto box-border flex min-h-full w-full max-w-screen-desktop flex-1 flex-col gap-3 p-3 lg:px-10 lg:pb-10"
>
${header.breadcrumbs ? html` ${pageNav(header.breadcrumbs)} ` : nothing}
${pageHeader(header)}

View File

@ -206,6 +206,7 @@ export class Collection extends BtrixElement {
`;
}
// TODO Consolidate with collection-detail.ts
private renderAbout(collection: PublicCollection) {
const dateRange = () => {
if (!collection.dateEarliest || !collection.dateLatest) {
@ -243,13 +244,13 @@ export class Collection extends BtrixElement {
return html`
<div class="flex flex-1 flex-col gap-10 lg:flex-row">
<section
class="flex-1 py-3 leading-relaxed lg:rounded-lg lg:border lg:p-6"
class="w-full max-w-4xl py-3 leading-relaxed lg:rounded-lg lg:border lg:p-6"
>
<btrix-markdown-viewer
value=${collection.description}
></btrix-markdown-viewer>
</section>
<section class="min-w-60 lg:-mt-8">
<section class="flex-1 lg:-mt-8">
<btrix-section-heading>
<h3>${msg("Metadata")}</h3>
</btrix-section-heading>

View File

@ -1,4 +1,5 @@
import { localized, msg, str } from "@lit/localize";
import clsx from "clsx";
import { html, nothing, type PropertyValues, type TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { choose } from "lit/directives/choose.js";
@ -24,6 +25,7 @@ import type { ArchivedItem, Crawl, Upload } from "@/types/crawler";
import type { CrawlState } from "@/types/crawlState";
import { pluralOf } from "@/utils/pluralize";
import { formatRwpTimestamp } from "@/utils/replay";
import { tw } from "@/utils/tailwind";
const ABORT_REASON_THROTTLE = "throttled";
const INITIAL_ITEMS_PAGE_SIZE = 20;
@ -115,7 +117,7 @@ export class CollectionDetail extends BtrixElement {
if (this.descriptionEditor) {
// FIXME Focus on editor ready instead of timeout
window.setTimeout(() => {
void this.descriptionEditor?.focus();
this.descriptionEditor && void this.descriptionEditor.focus();
}, 200);
}
}
@ -148,9 +150,7 @@ export class CollectionDetail extends BtrixElement {
<div class="mt-3 rounded-lg border px-4 py-2">
${this.renderInfoBar()}
</div>
<div
class="sticky top-0 z-50 -mx-3 mb-3 flex items-center justify-between bg-white px-3 pt-3 shadow-lg shadow-white"
>
<div class="flex items-center justify-between py-3">
${this.renderTabs()}
${when(this.isCrawler, () =>
choose(this.collectionTab, [
@ -174,40 +174,6 @@ export class CollectionDetail extends BtrixElement {
</sl-tooltip>
`,
],
[
Tab.About,
() =>
this.isEditingDescription
? html`
<div>
<sl-button
variant="primary"
size="small"
@click=${() => void this.saveDescription()}
?disabled=${!this.collection}
>
<sl-icon name="check-lg" slot="prefix"></sl-icon>
${msg("Save")}
</sl-button>
<sl-button
size="small"
@click=${() => (this.isEditingDescription = false)}
>
${msg("Cancel")}
</sl-button>
</div>
`
: html`
<sl-button
size="small"
@click=${() => (this.isEditingDescription = true)}
?disabled=${!this.collection}
>
<sl-icon name="pencil" slot="prefix"></sl-icon>
${msg("Edit")}
</sl-button>
`,
],
[
Tab.Items,
() => html`
@ -230,7 +196,7 @@ export class CollectionDetail extends BtrixElement {
Tab.Items,
() => guard([this.archivedItems], this.renderArchivedItems),
],
[Tab.About, () => this.renderDescription()],
[Tab.About, () => this.renderAbout()],
])}
<btrix-dialog
@ -544,38 +510,133 @@ export class CollectionDetail extends BtrixElement {
`;
}
private renderDescription() {
// TODO Consolidate with collection.ts
private renderAbout() {
const dateRange = (collection: Collection) => {
if (!collection.dateEarliest || !collection.dateLatest) {
return msg("n/a");
}
const format: Intl.DateTimeFormatOptions = {
month: "long",
year: "numeric",
};
const dateEarliest = this.localize.date(collection.dateEarliest, format);
const dateLatest = this.localize.date(collection.dateLatest, format);
if (dateEarliest === dateLatest) return dateLatest;
return msg(str`${dateEarliest} to ${dateLatest}`, {
desc: "Date range formatted to show full month name and year",
});
};
const skeleton = html`<sl-skeleton class="w-24"></sl-skeleton>`;
const metadata = html`
<btrix-desc-list>
<btrix-desc-list-item label=${msg("Collection Period")}>
<span class="font-sans"
>${this.collection ? dateRange(this.collection) : skeleton}</span
>
</btrix-desc-list-item>
</btrix-desc-list>
`;
return html`
<section>
${when(
this.collection,
(collection) =>
this.isEditingDescription
? html`
<btrix-markdown-editor
initialValue=${collection.description || ""}
placeholder=${msg("Tell viewers about this collection")}
maxlength=${4000}
></btrix-markdown-editor>
`
: html`
<div class="rounded-lg border p-4 leading-relaxed">
${collection.description
? html`
<btrix-markdown-viewer
value=${collection.description}
></btrix-markdown-viewer>
`
: html`
<p class="py-10 text-center text-neutral-500">
${msg("No description provided.")}
</p>
`}
</div>
`,
this.renderSpinner,
)}
</section>
<div class="flex flex-1 flex-col gap-10 lg:flex-row">
<section class="flex w-full max-w-4xl flex-col leading-relaxed">
<header class="mb-3 flex min-h-8 items-end justify-between">
<h2 class="text-base font-semibold leading-none">
${msg("Description")}
</h2>
${when(
this.collection?.description && !this.isEditingDescription,
() => html`
<sl-button
size="small"
@click=${() => (this.isEditingDescription = true)}
>
<sl-icon name="pencil" slot="prefix"></sl-icon>
${msg("Edit Description")}
</sl-button>
`,
)}
</header>
${when(
this.collection,
(collection) =>
this.isEditingDescription
? this.renderDescriptionForm()
: html`
<div
class=${clsx(
tw`flex-1 rounded-lg border p-3 lg:p-6`,
!collection.description &&
tw`flex flex-col items-center justify-center`,
)}
>
${collection.description
? html`
<btrix-markdown-viewer
value=${collection.description}
></btrix-markdown-viewer>
`
: html`
<div class="text-center text-neutral-500">
<p class="mb-3">
${msg("No description provided.")}
</p>
<sl-button
size="small"
@click=${() =>
(this.isEditingDescription = true)}
?disabled=${!this.collection}
>
<sl-icon name="pencil" slot="prefix"></sl-icon>
${msg("Add Description")}
</sl-button>
</div>
`}
</div>
`,
this.renderSpinner,
)}
</section>
<section class="flex-1">
<btrix-section-heading>
<h2>${msg("Metadata")}</h2>
</btrix-section-heading>
<div class="mt-5">${metadata}</div>
</section>
</div>
`;
}
private renderDescriptionForm() {
if (!this.collection) return;
return html`
<btrix-markdown-editor
class="flex-1"
initialValue=${this.collection.description || ""}
placeholder=${msg("Tell viewers about this collection")}
maxlength=${4000}
></btrix-markdown-editor>
<div class="flex-column mt-4 flex justify-between border-t pt-4">
<sl-button
size="small"
@click=${() => (this.isEditingDescription = false)}
>
${msg("Cancel")}
</sl-button>
<sl-button
variant="primary"
size="small"
@click=${() => void this.saveDescription()}
?disabled=${!this.collection}
>
${msg("Update Description")}
</sl-button>
</div>
`;
}
@ -708,32 +769,31 @@ export class CollectionDetail extends BtrixElement {
const headers = this.authState?.headers;
const config = JSON.stringify({ headers });
return html`<section>
<div class="aspect-4/3 overflow-hidden rounded-lg border">
<replay-web-page
source=${replaySource}
config="${config}"
coll=${this.collectionId}
url=${this.collection.homeUrl ||
/* must be empty string to reset the attribute: */ ""}
ts=${formatRwpTimestamp(this.collection.homeUrlTs) ||
/* must be empty string to reset the attribute: */ ""}
replayBase="/replay/"
noSandbox="true"
noCache="true"
@rwp-url-change=${() => {
if (!this.isRwpLoaded) {
this.isRwpLoaded = true;
}
}}
></replay-web-page>
</div>
return html` <section class="overflow-hidden rounded-lg border">
<replay-web-page
class="h-[calc(100vh-6.5rem)]"
source=${replaySource}
config="${config}"
coll=${this.collectionId}
url=${this.collection.homeUrl ||
/* must be empty string to reset the attribute: */ ""}
ts=${formatRwpTimestamp(this.collection.homeUrlTs) ||
/* must be empty string to reset the attribute: */ ""}
replayBase="/replay/"
noSandbox="true"
noCache="true"
@rwp-url-change=${() => {
if (!this.isRwpLoaded) {
this.isRwpLoaded = true;
}
}}
></replay-web-page>
</section>`;
};
private readonly renderSpinner = () => html`
<div
class="flex items-center justify-center rounded-lg border py-24 text-3xl"
class="flex min-h-full items-center justify-center rounded-lg border py-24 text-3xl"
>
<sl-spinner></sl-spinner>
</div>

View File

@ -602,6 +602,7 @@ export class Org extends BtrixElement {
if (params.collectionId) {
return html`<btrix-collection-detail
class="flex min-h-screen flex-1 flex-col pb-7"
collectionId=${params.collectionId}
collectionTab=${ifDefined(
params.collectionTab as CollectionTab | undefined,