Skip to content

Commit 65a40c4

Browse files
authored
feat: Show additional collection details (#2455)
Resolves #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
1 parent e13c3bf commit 65a40c4

File tree

7 files changed

+93
-48
lines changed

7 files changed

+93
-48
lines changed

frontend/src/controllers/localize.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,5 @@ export class LocalizeController extends SlLocalizeController {
3333
return localize.duration(duration);
3434
};
3535

36-
/**
37-
* From https://github.com/shoelace-style/shoelace/blob/v2.18.0/src/components/format-bytes/format-bytes.component.ts
38-
*/
39-
readonly bytes = (value: number, options?: Intl.NumberFormatOptions) => {
40-
if (isNaN(value)) {
41-
return "";
42-
}
43-
44-
const opts: Intl.NumberFormatOptions = {
45-
unit: "byte",
46-
unitDisplay: "short",
47-
...options,
48-
};
49-
const bitPrefixes = ["", "kilo", "mega", "giga", "tera"]; // petabit isn't a supported unit
50-
const bytePrefixes = ["", "kilo", "mega", "giga", "tera", "peta"];
51-
const prefix = opts.unit === "bit" ? bitPrefixes : bytePrefixes;
52-
const index = Math.max(
53-
0,
54-
Math.min(Math.floor(Math.log10(value) / 3), prefix.length - 1),
55-
);
56-
const unit = prefix[index] + opts.unit;
57-
const valueToFormat = parseFloat(
58-
(value / Math.pow(1000, index)).toPrecision(3),
59-
);
60-
61-
return localize.number(valueToFormat, {
62-
style: "unit",
63-
unit,
64-
unitDisplay: opts.unitDisplay,
65-
});
66-
};
36+
readonly bytes = localize.bytes;
6737
}

frontend/src/features/collections/collections-grid.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import { CollectionThumbnail } from "./collection-thumbnail";
1414
import { SelectCollectionAccess } from "./select-collection-access";
1515

1616
import { BtrixElement } from "@/classes/BtrixElement";
17+
import { textSeparator } from "@/layouts/separator";
1718
import { RouteNamespace } from "@/routes";
1819
import type { PublicCollection } from "@/types/collection";
20+
import { pluralOf } from "@/utils/pluralize";
1921
import { tw } from "@/utils/tailwind";
2022

2123
/**
@@ -45,7 +47,7 @@ export class CollectionsGrid extends BtrixElement {
4547
pagination!: Node[];
4648

4749
render() {
48-
const gridClassNames = tw`grid flex-1 grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`;
50+
const gridClassNames = tw`grid flex-1 grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-3`;
4951

5052
if (!this.collections || !this.slug) {
5153
const thumb = html`
@@ -114,14 +116,22 @@ export class CollectionsGrid extends BtrixElement {
114116
></sl-icon>`
115117
: nothing}
116118
<strong
117-
class="text-base font-medium leading-tight text-stone-700 transition-colors group-hover:text-cyan-600"
119+
class="text-base font-medium leading-tight text-stone-800 transition-colors group-hover:text-cyan-600"
118120
>
119121
${collection.name}
120122
</strong>
123+
<div class="mt-1.5 flex gap-2 leading-tight text-stone-400">
124+
<div>
125+
${this.localize.number(collection.pageCount)}
126+
${pluralOf("pages", collection.pageCount)}
127+
</div>
128+
${textSeparator()}
129+
<div>${this.localize.bytes(collection.totalSize)}</div>
130+
</div>
121131
${collection.caption &&
122132
html`
123133
<p
124-
class="mt-1.5 text-pretty leading-relaxed text-stone-500 transition-colors group-hover:text-cyan-600"
134+
class="mt-1.5 text-pretty leading-relaxed text-stone-500"
125135
>
126136
${collection.caption}
127137
</p>
@@ -180,7 +190,7 @@ export class CollectionsGrid extends BtrixElement {
180190
</div>
181191
`;
182192

183-
private renderDateBadge(collection: PublicCollection) {
193+
renderDateBadge(collection: PublicCollection) {
184194
if (!collection.dateEarliest || !collection.dateLatest) return;
185195

186196
const earliestYear = this.localize.date(collection.dateEarliest, {
@@ -190,10 +200,32 @@ export class CollectionsGrid extends BtrixElement {
190200
year: "numeric",
191201
});
192202

203+
let date = "";
204+
205+
if (earliestYear === latestYear) {
206+
const earliestMonth = new Date(collection.dateEarliest).getMonth();
207+
const latestMonth = new Date(collection.dateLatest).getMonth();
208+
209+
if (earliestMonth === latestMonth) {
210+
date = this.localize.date(collection.dateEarliest, {
211+
month: "long",
212+
year: "numeric",
213+
});
214+
} else {
215+
date = `${this.localize.date(collection.dateEarliest, {
216+
month: "short",
217+
})}${this.localize.date(collection.dateLatest, {
218+
month: "short",
219+
year: "numeric",
220+
})}`;
221+
}
222+
} else {
223+
date = `${earliestYear}${latestYear} `;
224+
}
225+
193226
return html`
194227
<btrix-badge variant="primary" class="absolute right-3 top-3">
195-
${earliestYear}
196-
${latestYear !== earliestYear ? html` – ${latestYear} ` : nothing}
228+
${date}
197229
</btrix-badge>
198230
`;
199231
}

frontend/src/layouts/collections/metadataColumn.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export function metadataColumn(collection?: Collection | PublicCollection) {
5252
render: (col) =>
5353
`${localize.number(col.pageCount)} ${pluralOf("pages", col.pageCount)}`,
5454
})}
55+
${metadataItem({
56+
label: metadata.totalSize,
57+
render: (col) => `${localize.bytes(col.totalSize)}`,
58+
})}
5559
</btrix-desc-list>
5660
`;
5761
}

frontend/src/layouts/pageHeader.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import clsx from "clsx";
44
import { html, nothing, type TemplateResult } from "lit";
55
import { ifDefined } from "lit/directives/if-defined.js";
66

7+
import { textSeparator } from "./separator";
8+
79
import { NavigateController } from "@/controllers/navigate";
810
import { tw } from "@/utils/tailwind";
911

@@ -26,15 +28,7 @@ function navigateBreadcrumb(e: MouseEvent, href: string) {
2628
el.dispatchEvent(evt);
2729
}
2830

29-
export function breadcrumbSeparator() {
30-
return html`<span
31-
class="font-mono font-thin text-neutral-400"
32-
role="separator"
33-
>/</span
34-
> `;
35-
}
36-
37-
const separator = breadcrumbSeparator();
31+
const breadcrumbSeparator = textSeparator();
3832
const skeleton = html`<sl-skeleton class="w-48"></sl-skeleton>`;
3933

4034
function breadcrumbLink({ href, content }: Breadcrumb, classNames?: string) {
@@ -65,11 +59,11 @@ function pageBreadcrumbs(breadcrumbs: Breadcrumb[]) {
6559
${breadcrumbs.length
6660
? breadcrumbs.map(
6761
(breadcrumb, i) => html`
68-
${i !== 0 ? separator : nothing}
62+
${i !== 0 ? breadcrumbSeparator : nothing}
6963
${breadcrumbLink(breadcrumb, tw`max-w-[30ch]`)}
7064
`,
7165
)
72-
: html`${skeleton} ${separator} ${skeleton}`}
66+
: html`${skeleton} ${breadcrumbSeparator} ${skeleton}`}
7367
</nav>
7468
`;
7569
}

frontend/src/layouts/separator.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { html } from "lit";
2+
3+
/**
4+
* For separatoring text in the same line, e.g. for breadcrumbs or item details
5+
*/
6+
export function textSeparator() {
7+
return html`<span
8+
class="font-mono font-thin text-neutral-400"
9+
role="separator"
10+
>/</span
11+
> `;
12+
}

frontend/src/strings/collections/metadata.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export const metadata = {
44
dateLatest: msg("Collection Period"),
55
uniquePageCount: msg("Unique Pages in Collection"),
66
pageCount: msg("Total Pages Crawled"),
7+
totalSize: msg("Collection Size"),
78
};

frontend/src/utils/localize.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,38 @@ export class Localize {
264264
const pluralRule = formatter.select(value);
265265
return phrases[pluralRule];
266266
};
267+
268+
/**
269+
* From https://github.com/shoelace-style/shoelace/blob/v2.18.0/src/components/format-bytes/format-bytes.component.ts
270+
*/
271+
readonly bytes = (value: number, options?: Intl.NumberFormatOptions) => {
272+
if (isNaN(value)) {
273+
return "";
274+
}
275+
276+
const opts: Intl.NumberFormatOptions = {
277+
unit: "byte",
278+
unitDisplay: "short",
279+
...options,
280+
};
281+
const bitPrefixes = ["", "kilo", "mega", "giga", "tera"]; // petabit isn't a supported unit
282+
const bytePrefixes = ["", "kilo", "mega", "giga", "tera", "peta"];
283+
const prefix = opts.unit === "bit" ? bitPrefixes : bytePrefixes;
284+
const index = Math.max(
285+
0,
286+
Math.min(Math.floor(Math.log10(value) / 3), prefix.length - 1),
287+
);
288+
const unit = prefix[index] + opts.unit;
289+
const valueToFormat = parseFloat(
290+
(value / Math.pow(1000, index)).toPrecision(3),
291+
);
292+
293+
return localize.number(valueToFormat, {
294+
style: "unit",
295+
unit,
296+
unitDisplay: opts.unitDisplay,
297+
});
298+
};
267299
}
268300

269301
const localize = new Localize(sourceLocale);

0 commit comments

Comments
 (0)