From 62d3399223757c6df902c30f096c2b9952d8068b Mon Sep 17 00:00:00 2001 From: sua yoo Date: Thu, 3 Aug 2023 16:58:56 -0700 Subject: [PATCH] Add info bar to Collection detail view (#1036) - Adds Collection info bar to detail view - Update "Web Captures" -> "Archived Items" - Updates Collection list columns to match - Refactors `btrix-desc-list` and usage in `workflow-details` to reuse horizontal info bar component --- frontend/src/components/desc-list.ts | 52 +++++++-- frontend/src/pages/org/collection-detail.ts | 111 +++++++++++++++----- frontend/src/pages/org/collections-list.ts | 46 ++++---- frontend/src/pages/org/workflow-detail.ts | 37 +++---- 4 files changed, 166 insertions(+), 80 deletions(-) diff --git a/frontend/src/components/desc-list.ts b/frontend/src/components/desc-list.ts index 9a088735..b9ddb683 100644 --- a/frontend/src/components/desc-list.ts +++ b/frontend/src/components/desc-list.ts @@ -16,9 +16,14 @@ */ import { LitElement, html, css } from "lit"; import { property } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; export class DescListItem extends LitElement { static styles = css` + :host { + display: contents; + } + dt { color: var(--sl-color-neutral-500); font-size: var(--sl-font-size-x-small); @@ -28,22 +33,30 @@ export class DescListItem extends LitElement { dd { margin: 0; - padding: 0; + padding: 0 0 var(--sl-spacing-2x-small); color: var(--sl-color-neutral-700); font-size: var(--sl-font-size-medium); font-family: var(--font-monostyle-family); font-variation-settings: var(--font-monostyle-variation); line-height: 1rem; } + + .item { + display: flex; + justify-content: var(--justify-item, initial); + border-right: var(--border-right, 0px); + } `; @property({ type: String }) label: string = ""; render() { - return html`
-
${this.label}
-
+ return html`
+
+
${this.label}
+
+
`; } } @@ -52,13 +65,38 @@ export class DescList extends LitElement { static styles = css` dl { display: grid; - grid-template-columns: 100%; - grid-gap: 1rem; margin: 0; } + + .vertical { + grid-template-columns: 100%; + gap: 1rem; + } + + .horizontal { + --justify-item: center; + --border-right: 1px solid var(--sl-panel-border-color); + grid-auto-flow: column; + } + + /* Although this only applies to .horizontal, apply to any last child + since we can't do complex selectors with ::slotted */ + ::slotted(*:last-of-type) { + --border-right: 0px; + } `; + @property({ type: Boolean }) + horizontal = false; + render() { - return html`
`; + return html`
+ +
`; } } diff --git a/frontend/src/pages/org/collection-detail.ts b/frontend/src/pages/org/collection-detail.ts index 3915eca2..dd7347f1 100644 --- a/frontend/src/pages/org/collection-detail.ts +++ b/frontend/src/pages/org/collection-detail.ts @@ -4,6 +4,7 @@ import { choose } from "lit/directives/choose.js"; import { when } from "lit/directives/when.js"; import { guard } from "lit/directives/guard.js"; import queryString from "query-string"; +import type { TemplateResult } from "lit"; import type { AuthState } from "../../utils/AuthService"; import LiteElement, { html } from "../../utils/LiteElement"; @@ -18,7 +19,7 @@ import type { PageChangeEvent } from "../../components/pagination"; const ABORT_REASON_THROTTLE = "throttled"; const DESCRIPTION_MAX_HEIGHT_PX = 200; -const TABS = ["replay", "web-captures"] as const; +const TABS = ["replay", "items"] as const; export type Tab = (typeof TABS)[number]; @localized() @@ -42,7 +43,7 @@ export class CollectionDetail extends LiteElement { private collection?: Collection; @state() - private webCaptures?: APIPaginatedList; + private archivedItems?: APIPaginatedList; @state() private openDialogName?: "delete"; @@ -51,16 +52,21 @@ export class CollectionDetail extends LiteElement { private isDescriptionExpanded = false; // Use to cancel requests - private getWebCapturesController: AbortController | null = null; + private getArchivedItemsController: AbortController | null = null; + + // TODO localize + private numberFormatter = new Intl.NumberFormat(undefined, { + notation: "compact", + }); private readonly tabLabels: Record = { replay: { icon: { name: "link-replay", library: "app" }, text: msg("Replay"), }, - "web-captures": { + items: { icon: { name: "list-ul", library: "default" }, - text: msg("Web Captures"), + text: msg("Archived Items"), }, }; @@ -70,7 +76,7 @@ export class CollectionDetail extends LiteElement { this.fetchCollection(); } if (changedProperties.has("collectionId")) { - this.fetchWebCaptures(); + this.fetchArchivedItems(); } } @@ -82,7 +88,7 @@ export class CollectionDetail extends LiteElement { render() { return html`${this.renderHeader()} -
+

@@ -91,13 +97,14 @@ export class CollectionDetail extends LiteElement {

${when(this.isCrawler, this.renderActions)}
+
${this.renderInfoBar()}
${this.renderTabs()}
${choose( this.resourceTab, [ ["replay", this.renderOverview], - ["web-captures", this.renderWebCaptures], + ["items", this.renderArchivedItems], ], () => html`` @@ -215,6 +222,56 @@ export class CollectionDetail extends LiteElement { `; }; + private renderInfoBar() { + return html` + + ${this.renderDetailItem(msg("Archived Items"), (col) => + col.crawlCount === 1 + ? msg("1 item") + : msg(str`${this.numberFormatter.format(col.crawlCount)} items`) + )} + ${this.renderDetailItem( + msg("Total Size"), + (col) => html`` + )} + ${this.renderDetailItem(msg("Total Pages"), (col) => + col.pageCount === 1 + ? msg("1 page") + : msg(str`${this.numberFormatter.format(col.pageCount)} pages`) + )} + ${this.renderDetailItem( + msg("Last Updated"), + (col) => html`` + )} + + `; + } + + private renderDetailItem( + label: string | TemplateResult, + renderContent: (collection: Collection) => any + ) { + return html` + + ${when( + this.collection, + () => renderContent(this.collection!), + () => html`` + )} + + `; + } + private renderDescription() { return html`
@@ -289,15 +346,17 @@ export class CollectionDetail extends LiteElement {
${this.renderDescription()}
`; - private renderWebCaptures = () => html`
+ private renderArchivedItems = () => html`
${when( - this.webCaptures, + this.archivedItems, () => { - const { items, page, total, pageSize } = this.webCaptures!; + const { items, page, total, pageSize } = this.archivedItems!; const hasItems = items.length; return html`
- ${hasItems ? this.renderWebCaptureList() : this.renderEmptyState()} + ${hasItems + ? this.renderArchivedItemsList() + : this.renderEmptyState()}
${when( hasItems || page > 1, @@ -308,7 +367,7 @@ export class CollectionDetail extends LiteElement { totalCount=${total} size=${pageSize} @page-change=${async (e: PageChangeEvent) => { - await this.fetchWebCaptures({ + await this.fetchArchivedItems({ page: e.detail.page, }); @@ -330,20 +389,20 @@ export class CollectionDetail extends LiteElement { )}
`; - private renderWebCaptureList() { - if (!this.webCaptures) return; + private renderArchivedItemsList() { + if (!this.archivedItems) return; return html` - ${this.webCaptures.items.map(this.renderWebCaptureItem)} + ${this.archivedItems.items.map(this.renderArchivedItem)} `; } private renderEmptyState() { - if (this.webCaptures?.page && this.webCaptures?.page > 1) { + if (this.archivedItems?.page && this.archivedItems?.page > 1) { return html`

@@ -362,7 +421,7 @@ export class CollectionDetail extends LiteElement { `; } - private renderWebCaptureItem = (wc: Crawl | Upload) => + private renderArchivedItem = (wc: Crawl | Upload) => html`

@@ -478,10 +537,10 @@ export class CollectionDetail extends LiteElement { /** * Fetch web captures and update internal state */ - private async fetchWebCaptures(params?: APIPaginationQuery): Promise { - this.cancelInProgressGetWebCaptures(); + private async fetchArchivedItems(params?: APIPaginationQuery): Promise { + this.cancelInProgressGetArchivedItems(); try { - this.webCaptures = await this.getWebCaptures(); + this.archivedItems = await this.getArchivedItems(); } catch (e: any) { if (e === ABORT_REASON_THROTTLE) { console.debug("Fetch web captures aborted to throttle"); @@ -495,14 +554,14 @@ export class CollectionDetail extends LiteElement { } } - private cancelInProgressGetWebCaptures() { - if (this.getWebCapturesController) { - this.getWebCapturesController.abort(ABORT_REASON_THROTTLE); - this.getWebCapturesController = null; + private cancelInProgressGetArchivedItems() { + if (this.getArchivedItemsController) { + this.getArchivedItemsController.abort(ABORT_REASON_THROTTLE); + this.getArchivedItemsController = null; } } - private async getWebCaptures( + private async getArchivedItems( params?: Partial<{ state: CrawlState[]; }> & diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts index ab04c59f..33b7882a 100644 --- a/frontend/src/pages/org/collections-list.ts +++ b/frontend/src/pages/org/collections-list.ts @@ -369,14 +369,14 @@ export class CollectionsList extends LiteElement { return html`
-
+
${this.renderDetails()}
@@ -683,7 +683,7 @@ export class WorkflowDetail extends LiteElement { private renderDetails() { return html` -
+ ${this.renderDetailItem( msg("Status"), () => html` @@ -711,26 +711,20 @@ export class WorkflowDetail extends LiteElement { ` : html`${msg("No Schedule")}` )} - ${this.renderDetailItem( - msg("Created By"), - () => - msg( - str`${ - this.workflow!.createdByName - } on ${this.dateFormatter.format( - new Date(`${this.workflow!.created}Z`) - )}` - ), - true + ${this.renderDetailItem(msg("Created By"), () => + msg( + str`${this.workflow!.createdByName} on ${this.dateFormatter.format( + new Date(`${this.workflow!.created}Z`) + )}` + ) )} -
+ `; } private renderDetailItem( label: string | TemplateResult, - renderContent: () => any, - isLast = false + renderContent: () => any ) { return html` @@ -740,7 +734,6 @@ export class WorkflowDetail extends LiteElement { () => html`` )} - ${when(!isLast, () => html`
`)} `; } @@ -878,7 +871,7 @@ export class WorkflowDetail extends LiteElement { const skeleton = html``; return html` -
+ ${this.renderDetailItem(msg("Pages Crawled"), () => this.lastCrawlStats ? msg( @@ -906,12 +899,10 @@ export class WorkflowDetail extends LiteElement { >` : skeleton )} - ${this.renderDetailItem( - msg("Crawler Instances"), - () => (this.workflow ? this.workflow.scale : skeleton), - true + ${this.renderDetailItem(msg("Crawler Instances"), () => + this.workflow ? this.workflow.scale : skeleton )} -
+ `; };