feat: Show additional collection details (#2455)
Resolves https://github.com/webrecorder/browsertrix/issues/2452 ## Changes - Displays page count and collection size in listing grid - Displays month if collection period is in the same year - Displays collection size in About > Details section - Minor refactor: move byte formatting into `localize.ts` utility file, move slash (`/`) separator into own utility file
This commit is contained in:
parent
e13c3bfb48
commit
65a40c4816
@ -33,35 +33,5 @@ export class LocalizeController extends SlLocalizeController {
|
||||
return localize.duration(duration);
|
||||
};
|
||||
|
||||
/**
|
||||
* From https://github.com/shoelace-style/shoelace/blob/v2.18.0/src/components/format-bytes/format-bytes.component.ts
|
||||
*/
|
||||
readonly bytes = (value: number, options?: Intl.NumberFormatOptions) => {
|
||||
if (isNaN(value)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const opts: Intl.NumberFormatOptions = {
|
||||
unit: "byte",
|
||||
unitDisplay: "short",
|
||||
...options,
|
||||
};
|
||||
const bitPrefixes = ["", "kilo", "mega", "giga", "tera"]; // petabit isn't a supported unit
|
||||
const bytePrefixes = ["", "kilo", "mega", "giga", "tera", "peta"];
|
||||
const prefix = opts.unit === "bit" ? bitPrefixes : bytePrefixes;
|
||||
const index = Math.max(
|
||||
0,
|
||||
Math.min(Math.floor(Math.log10(value) / 3), prefix.length - 1),
|
||||
);
|
||||
const unit = prefix[index] + opts.unit;
|
||||
const valueToFormat = parseFloat(
|
||||
(value / Math.pow(1000, index)).toPrecision(3),
|
||||
);
|
||||
|
||||
return localize.number(valueToFormat, {
|
||||
style: "unit",
|
||||
unit,
|
||||
unitDisplay: opts.unitDisplay,
|
||||
});
|
||||
};
|
||||
readonly bytes = localize.bytes;
|
||||
}
|
||||
|
@ -14,8 +14,10 @@ import { CollectionThumbnail } from "./collection-thumbnail";
|
||||
import { SelectCollectionAccess } from "./select-collection-access";
|
||||
|
||||
import { BtrixElement } from "@/classes/BtrixElement";
|
||||
import { textSeparator } from "@/layouts/separator";
|
||||
import { RouteNamespace } from "@/routes";
|
||||
import type { PublicCollection } from "@/types/collection";
|
||||
import { pluralOf } from "@/utils/pluralize";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
|
||||
/**
|
||||
@ -45,7 +47,7 @@ export class CollectionsGrid extends BtrixElement {
|
||||
pagination!: Node[];
|
||||
|
||||
render() {
|
||||
const gridClassNames = tw`grid flex-1 grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`;
|
||||
const gridClassNames = tw`grid flex-1 grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-3`;
|
||||
|
||||
if (!this.collections || !this.slug) {
|
||||
const thumb = html`
|
||||
@ -114,14 +116,22 @@ export class CollectionsGrid extends BtrixElement {
|
||||
></sl-icon>`
|
||||
: nothing}
|
||||
<strong
|
||||
class="text-base font-medium leading-tight text-stone-700 transition-colors group-hover:text-cyan-600"
|
||||
class="text-base font-medium leading-tight text-stone-800 transition-colors group-hover:text-cyan-600"
|
||||
>
|
||||
${collection.name}
|
||||
</strong>
|
||||
<div class="mt-1.5 flex gap-2 leading-tight text-stone-400">
|
||||
<div>
|
||||
${this.localize.number(collection.pageCount)}
|
||||
${pluralOf("pages", collection.pageCount)}
|
||||
</div>
|
||||
${textSeparator()}
|
||||
<div>${this.localize.bytes(collection.totalSize)}</div>
|
||||
</div>
|
||||
${collection.caption &&
|
||||
html`
|
||||
<p
|
||||
class="mt-1.5 text-pretty leading-relaxed text-stone-500 transition-colors group-hover:text-cyan-600"
|
||||
class="mt-1.5 text-pretty leading-relaxed text-stone-500"
|
||||
>
|
||||
${collection.caption}
|
||||
</p>
|
||||
@ -180,7 +190,7 @@ export class CollectionsGrid extends BtrixElement {
|
||||
</div>
|
||||
`;
|
||||
|
||||
private renderDateBadge(collection: PublicCollection) {
|
||||
renderDateBadge(collection: PublicCollection) {
|
||||
if (!collection.dateEarliest || !collection.dateLatest) return;
|
||||
|
||||
const earliestYear = this.localize.date(collection.dateEarliest, {
|
||||
@ -190,10 +200,32 @@ export class CollectionsGrid extends BtrixElement {
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
let date = "";
|
||||
|
||||
if (earliestYear === latestYear) {
|
||||
const earliestMonth = new Date(collection.dateEarliest).getMonth();
|
||||
const latestMonth = new Date(collection.dateLatest).getMonth();
|
||||
|
||||
if (earliestMonth === latestMonth) {
|
||||
date = this.localize.date(collection.dateEarliest, {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
} else {
|
||||
date = `${this.localize.date(collection.dateEarliest, {
|
||||
month: "short",
|
||||
})} – ${this.localize.date(collection.dateLatest, {
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})}`;
|
||||
}
|
||||
} else {
|
||||
date = `${earliestYear} – ${latestYear} `;
|
||||
}
|
||||
|
||||
return html`
|
||||
<btrix-badge variant="primary" class="absolute right-3 top-3">
|
||||
${earliestYear}
|
||||
${latestYear !== earliestYear ? html` – ${latestYear} ` : nothing}
|
||||
${date}
|
||||
</btrix-badge>
|
||||
`;
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ export function metadataColumn(collection?: Collection | PublicCollection) {
|
||||
render: (col) =>
|
||||
`${localize.number(col.pageCount)} ${pluralOf("pages", col.pageCount)}`,
|
||||
})}
|
||||
${metadataItem({
|
||||
label: metadata.totalSize,
|
||||
render: (col) => `${localize.bytes(col.totalSize)}`,
|
||||
})}
|
||||
</btrix-desc-list>
|
||||
`;
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import clsx from "clsx";
|
||||
import { html, nothing, type TemplateResult } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { textSeparator } from "./separator";
|
||||
|
||||
import { NavigateController } from "@/controllers/navigate";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
|
||||
@ -26,15 +28,7 @@ function navigateBreadcrumb(e: MouseEvent, href: string) {
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
export function breadcrumbSeparator() {
|
||||
return html`<span
|
||||
class="font-mono font-thin text-neutral-400"
|
||||
role="separator"
|
||||
>/</span
|
||||
> `;
|
||||
}
|
||||
|
||||
const separator = breadcrumbSeparator();
|
||||
const breadcrumbSeparator = textSeparator();
|
||||
const skeleton = html`<sl-skeleton class="w-48"></sl-skeleton>`;
|
||||
|
||||
function breadcrumbLink({ href, content }: Breadcrumb, classNames?: string) {
|
||||
@ -65,11 +59,11 @@ function pageBreadcrumbs(breadcrumbs: Breadcrumb[]) {
|
||||
${breadcrumbs.length
|
||||
? breadcrumbs.map(
|
||||
(breadcrumb, i) => html`
|
||||
${i !== 0 ? separator : nothing}
|
||||
${i !== 0 ? breadcrumbSeparator : nothing}
|
||||
${breadcrumbLink(breadcrumb, tw`max-w-[30ch]`)}
|
||||
`,
|
||||
)
|
||||
: html`${skeleton} ${separator} ${skeleton}`}
|
||||
: html`${skeleton} ${breadcrumbSeparator} ${skeleton}`}
|
||||
</nav>
|
||||
`;
|
||||
}
|
||||
|
12
frontend/src/layouts/separator.ts
Normal file
12
frontend/src/layouts/separator.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { html } from "lit";
|
||||
|
||||
/**
|
||||
* For separatoring text in the same line, e.g. for breadcrumbs or item details
|
||||
*/
|
||||
export function textSeparator() {
|
||||
return html`<span
|
||||
class="font-mono font-thin text-neutral-400"
|
||||
role="separator"
|
||||
>/</span
|
||||
> `;
|
||||
}
|
@ -4,4 +4,5 @@ export const metadata = {
|
||||
dateLatest: msg("Collection Period"),
|
||||
uniquePageCount: msg("Unique Pages in Collection"),
|
||||
pageCount: msg("Total Pages Crawled"),
|
||||
totalSize: msg("Collection Size"),
|
||||
};
|
||||
|
@ -264,6 +264,38 @@ export class Localize {
|
||||
const pluralRule = formatter.select(value);
|
||||
return phrases[pluralRule];
|
||||
};
|
||||
|
||||
/**
|
||||
* From https://github.com/shoelace-style/shoelace/blob/v2.18.0/src/components/format-bytes/format-bytes.component.ts
|
||||
*/
|
||||
readonly bytes = (value: number, options?: Intl.NumberFormatOptions) => {
|
||||
if (isNaN(value)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const opts: Intl.NumberFormatOptions = {
|
||||
unit: "byte",
|
||||
unitDisplay: "short",
|
||||
...options,
|
||||
};
|
||||
const bitPrefixes = ["", "kilo", "mega", "giga", "tera"]; // petabit isn't a supported unit
|
||||
const bytePrefixes = ["", "kilo", "mega", "giga", "tera", "peta"];
|
||||
const prefix = opts.unit === "bit" ? bitPrefixes : bytePrefixes;
|
||||
const index = Math.max(
|
||||
0,
|
||||
Math.min(Math.floor(Math.log10(value) / 3), prefix.length - 1),
|
||||
);
|
||||
const unit = prefix[index] + opts.unit;
|
||||
const valueToFormat = parseFloat(
|
||||
(value / Math.pow(1000, index)).toPrecision(3),
|
||||
);
|
||||
|
||||
return localize.number(valueToFormat, {
|
||||
style: "unit",
|
||||
unit,
|
||||
unitDisplay: opts.unitDisplay,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const localize = new Localize(sourceLocale);
|
||||
|
Loading…
Reference in New Issue
Block a user