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
This commit is contained in:
parent
af09d56ef6
commit
62d3399223
@ -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`<div>
|
||||
<dt>${this.label}</dt>
|
||||
<dd><slot></slot></dd>
|
||||
return html`<div class="item">
|
||||
<div class="content">
|
||||
<dt>${this.label}</dt>
|
||||
<dd><slot></slot></dd>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@ -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`<dl><slot></slot></dl>`;
|
||||
return html`<dl
|
||||
class=${classMap({
|
||||
vertical: !this.horizontal,
|
||||
horizontal: this.horizontal,
|
||||
})}
|
||||
>
|
||||
<slot></slot>
|
||||
</dl>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Tab, { icon: any; text: string }> = {
|
||||
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()}
|
||||
<header class="md:flex items-center gap-2 pb-3 mb-3 border-b">
|
||||
<header class="md:flex items-center gap-2 pb-3">
|
||||
<h1
|
||||
class="flex-1 min-w-0 text-xl font-semibold leading-7 truncate mb-2 md:mb-0"
|
||||
>
|
||||
@ -91,13 +97,14 @@ export class CollectionDetail extends LiteElement {
|
||||
</h1>
|
||||
${when(this.isCrawler, this.renderActions)}
|
||||
</header>
|
||||
<div class="border rounded-lg py-2 mb-3">${this.renderInfoBar()}</div>
|
||||
<div class="mb-3">${this.renderTabs()}</div>
|
||||
|
||||
${choose(
|
||||
this.resourceTab,
|
||||
[
|
||||
["replay", this.renderOverview],
|
||||
["web-captures", this.renderWebCaptures],
|
||||
["items", this.renderArchivedItems],
|
||||
],
|
||||
|
||||
() => html`<btrix-not-found></btrix-not-found>`
|
||||
@ -215,6 +222,56 @@ export class CollectionDetail extends LiteElement {
|
||||
`;
|
||||
};
|
||||
|
||||
private renderInfoBar() {
|
||||
return html`
|
||||
<btrix-desc-list horizontal>
|
||||
${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`<sl-format-bytes
|
||||
value=${col.totalSize || 0}
|
||||
display="narrow"
|
||||
></sl-format-bytes>`
|
||||
)}
|
||||
${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`<sl-format-date
|
||||
date=${`${col.modified}Z`}
|
||||
month="2-digit"
|
||||
day="2-digit"
|
||||
year="2-digit"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
></sl-format-date>`
|
||||
)}
|
||||
</btrix-desc-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDetailItem(
|
||||
label: string | TemplateResult,
|
||||
renderContent: (collection: Collection) => any
|
||||
) {
|
||||
return html`
|
||||
<btrix-desc-list-item label=${label}>
|
||||
${when(
|
||||
this.collection,
|
||||
() => renderContent(this.collection!),
|
||||
() => html`<sl-skeleton class="w-full"></sl-skeleton>`
|
||||
)}
|
||||
</btrix-desc-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDescription() {
|
||||
return html`
|
||||
<section>
|
||||
@ -289,15 +346,17 @@ export class CollectionDetail extends LiteElement {
|
||||
<div class="my-7">${this.renderDescription()}</div>
|
||||
`;
|
||||
|
||||
private renderWebCaptures = () => html`<section>
|
||||
private renderArchivedItems = () => html`<section>
|
||||
${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`
|
||||
<section>
|
||||
${hasItems ? this.renderWebCaptureList() : this.renderEmptyState()}
|
||||
${hasItems
|
||||
? this.renderArchivedItemsList()
|
||||
: this.renderEmptyState()}
|
||||
</section>
|
||||
${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 {
|
||||
)}
|
||||
</section>`;
|
||||
|
||||
private renderWebCaptureList() {
|
||||
if (!this.webCaptures) return;
|
||||
private renderArchivedItemsList() {
|
||||
if (!this.archivedItems) return;
|
||||
|
||||
return html`
|
||||
<btrix-crawl-list
|
||||
baseUrl=${`/orgs/${this.orgId}/collections/view/${this.collectionId}/artifact`}
|
||||
>
|
||||
${this.webCaptures.items.map(this.renderWebCaptureItem)}
|
||||
${this.archivedItems.items.map(this.renderArchivedItem)}
|
||||
</btrix-crawl-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderEmptyState() {
|
||||
if (this.webCaptures?.page && this.webCaptures?.page > 1) {
|
||||
if (this.archivedItems?.page && this.archivedItems?.page > 1) {
|
||||
return html`
|
||||
<div class="border-t border-b py-5">
|
||||
<p class="text-center text-neutral-500">
|
||||
@ -362,7 +421,7 @@ export class CollectionDetail extends LiteElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderWebCaptureItem = (wc: Crawl | Upload) =>
|
||||
private renderArchivedItem = (wc: Crawl | Upload) =>
|
||||
html`
|
||||
<btrix-crawl-list-item .crawl=${wc}>
|
||||
<div slot="menuTrigger" role="none"></div>
|
||||
@ -478,10 +537,10 @@ export class CollectionDetail extends LiteElement {
|
||||
/**
|
||||
* Fetch web captures and update internal state
|
||||
*/
|
||||
private async fetchWebCaptures(params?: APIPaginationQuery): Promise<void> {
|
||||
this.cancelInProgressGetWebCaptures();
|
||||
private async fetchArchivedItems(params?: APIPaginationQuery): Promise<void> {
|
||||
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[];
|
||||
}> &
|
||||
|
||||
@ -369,14 +369,14 @@ export class CollectionsList extends LiteElement {
|
||||
return html`
|
||||
<header class="py-2 text-neutral-600 leading-none">
|
||||
<div
|
||||
class="hidden md:grid md:grid-cols-[repeat(2,1fr)_16ch_repeat(3,10ch)_2.5rem] gap-3"
|
||||
class="hidden md:grid md:grid-cols-[repeat(2,1fr)_repeat(3,12ch)_16ch_2.5rem] gap-3"
|
||||
>
|
||||
<div class="col-span-1 text-xs pl-3">${msg("Collection Name")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Top 3 Tags")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Last Updated")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Size")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Web Captures")}</div>
|
||||
<div class="col-span-2 text-xs">${msg("Total Pages")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Archived Items")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Total Size")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Total Pages")}</div>
|
||||
<div class="col-span-2 text-xs">${msg("Last Updated")}</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul class="contents">
|
||||
@ -446,7 +446,7 @@ export class CollectionsList extends LiteElement {
|
||||
html`<li class="mb-2 last:mb-0">
|
||||
<div class="block border rounded leading-none">
|
||||
<div
|
||||
class="relative p-3 md:p-0 grid grid-cols-1 md:grid-cols-[repeat(2,1fr)_16ch_repeat(3,10ch)_2.5rem] gap-3 lg:h-10 items-center"
|
||||
class="relative p-3 md:p-0 grid grid-cols-1 md:grid-cols-[repeat(2,1fr)_repeat(3,12ch)_16ch_2.5rem] gap-3 lg:h-10 items-center"
|
||||
>
|
||||
<div class="col-span-1 md:pl-3 truncate font-semibold">
|
||||
<a
|
||||
@ -465,15 +465,12 @@ export class CollectionsList extends LiteElement {
|
||||
html`<btrix-tag class="mr-1" size="small">${tag}</btrix-tag>`
|
||||
)}
|
||||
</div>
|
||||
<div class="col-span-1 text-xs text-neutral-500 font-monostyle">
|
||||
<sl-format-date
|
||||
date=${`${col.modified}Z`}
|
||||
month="2-digit"
|
||||
day="2-digit"
|
||||
year="2-digit"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
></sl-format-date>
|
||||
<div
|
||||
class="col-span-1 truncate text-xs text-neutral-500 font-monostyle"
|
||||
>
|
||||
${col.crawlCount === 1
|
||||
? msg("1 item")
|
||||
: msg(str`${this.numberFormatter.format(col.crawlCount)} items`)}
|
||||
</div>
|
||||
<div
|
||||
class="col-span-1 truncate text-xs text-neutral-500 font-monostyle"
|
||||
@ -483,15 +480,6 @@ export class CollectionsList extends LiteElement {
|
||||
display="narrow"
|
||||
></sl-format-bytes>
|
||||
</div>
|
||||
<div
|
||||
class="col-span-1 truncate text-xs text-neutral-500 font-monostyle"
|
||||
>
|
||||
${col.crawlCount === 1
|
||||
? msg("1 capture")
|
||||
: msg(
|
||||
str`${this.numberFormatter.format(col.crawlCount)} captures`
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
class="col-span-1 truncate text-xs text-neutral-500 font-monostyle"
|
||||
>
|
||||
@ -499,6 +487,16 @@ export class CollectionsList extends LiteElement {
|
||||
? msg("1 page")
|
||||
: msg(str`${this.numberFormatter.format(col.pageCount)} pages`)}
|
||||
</div>
|
||||
<div class="col-span-1 text-xs text-neutral-500 font-monostyle">
|
||||
<sl-format-date
|
||||
date=${`${col.modified}Z`}
|
||||
month="2-digit"
|
||||
day="2-digit"
|
||||
year="2-digit"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
></sl-format-date>
|
||||
</div>
|
||||
<div
|
||||
class="actionsCol absolute top-0 right-0 md:relative col-span-1 flex items-center justify-center"
|
||||
>
|
||||
|
||||
@ -306,7 +306,7 @@ export class WorkflowDetail extends LiteElement {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="col-span-1 border rounded-lg py-2 h-14">
|
||||
<section class="col-span-1 border rounded-lg py-2">
|
||||
${this.renderDetails()}
|
||||
</section>
|
||||
|
||||
@ -683,7 +683,7 @@ export class WorkflowDetail extends LiteElement {
|
||||
|
||||
private renderDetails() {
|
||||
return html`
|
||||
<dl class="h-14 px-3 md:px-0 md:flex justify-evenly">
|
||||
<btrix-desc-list horizontal>
|
||||
${this.renderDetailItem(
|
||||
msg("Status"),
|
||||
() => html`
|
||||
@ -711,26 +711,20 @@ export class WorkflowDetail extends LiteElement {
|
||||
`
|
||||
: html`<span class="text-neutral-400">${msg("No Schedule")}</span>`
|
||||
)}
|
||||
${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`)
|
||||
)}`
|
||||
)
|
||||
)}
|
||||
</dl>
|
||||
</btrix-desc-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDetailItem(
|
||||
label: string | TemplateResult,
|
||||
renderContent: () => any,
|
||||
isLast = false
|
||||
renderContent: () => any
|
||||
) {
|
||||
return html`
|
||||
<btrix-desc-list-item label=${label}>
|
||||
@ -740,7 +734,6 @@ export class WorkflowDetail extends LiteElement {
|
||||
() => html`<sl-skeleton class="w-full"></sl-skeleton>`
|
||||
)}
|
||||
</btrix-desc-list-item>
|
||||
${when(!isLast, () => html`<hr class="flex-0 border-l w-0 h-10" />`)}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -878,7 +871,7 @@ export class WorkflowDetail extends LiteElement {
|
||||
const skeleton = html`<sl-skeleton class="w-full"></sl-skeleton>`;
|
||||
|
||||
return html`
|
||||
<dl class="px-3 md:px-0 md:flex justify-evenly">
|
||||
<btrix-desc-list horizontal>
|
||||
${this.renderDetailItem(msg("Pages Crawled"), () =>
|
||||
this.lastCrawlStats
|
||||
? msg(
|
||||
@ -906,12 +899,10 @@ export class WorkflowDetail extends LiteElement {
|
||||
></sl-format-bytes>`
|
||||
: skeleton
|
||||
)}
|
||||
${this.renderDetailItem(
|
||||
msg("Crawler Instances"),
|
||||
() => (this.workflow ? this.workflow.scale : skeleton),
|
||||
true
|
||||
${this.renderDetailItem(msg("Crawler Instances"), () =>
|
||||
this.workflow ? this.workflow.scale : skeleton
|
||||
)}
|
||||
</dl>
|
||||
</btrix-desc-list>
|
||||
`;
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user