Skip to content

Commit ad4d38c

Browse files
authored
Merge pull request #438 from processing/feat/libraries-directory
Add directory for all libraries
2 parents eccabb7 + 722d513 commit ad4d38c

File tree

5 files changed

+158
-12
lines changed

5 files changed

+158
-12
lines changed

src/components/Button/index.astro

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
interface Props {
3+
href: string
4+
selected?: boolean
5+
}
6+
const { href, selected = false } = Astro.props;
7+
---
8+
9+
<a href={href} class={`rounded-button ${selected ? 'selected' : ''}`}>
10+
<slot />
11+
</a>
12+
13+
<style lang="scss">
14+
.rounded-button {
15+
display: inline-block;
16+
margin: var(--spacing-md);
17+
margin-left: 0;
18+
padding: var(--spacing-xs) var(--spacing-sm);
19+
border: 1px solid var(--type-black);
20+
color: var(--type-black);
21+
border-radius: 999px;
22+
}
23+
.rounded-button.selected {
24+
color: var(--type-black);
25+
background: var(--accent-color);
26+
}
27+
</style>
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
import { getCurrentLocale } from "@/src/i18n/utils";
3+
import { getLibraryLink } from "@/src/pages/_utils";
4+
import Image from "@components/Image/index.astro";
5+
import type { CollectionEntry } from "astro:content";
6+
7+
interface Props {
8+
item: CollectionEntry<"libraries">;
9+
}
10+
11+
const { item } = Astro.props;
12+
13+
const authorsFormatter = new Intl.ListFormat(
14+
getCurrentLocale(Astro.url.pathname),
15+
{
16+
style: "long",
17+
type: "conjunction",
18+
}
19+
);
20+
const authorsString = authorsFormatter.format(
21+
item.data.author.map((a) => a.name)
22+
);
23+
let description = item.data.description.trim();
24+
// If the description didn't end with punctuation, add it, since we will be
25+
// appending another sentence afterwards.
26+
if (!/[.!]\)?$/.test(description)) {
27+
description += ".";
28+
}
29+
---
30+
31+
<a class="group hover:no-underline flex mt-sm items-center" href={getLibraryLink(item)}>
32+
<Image
33+
src={item.data.featuredImage}
34+
alt={item.data.featuredImageAlt}
35+
width="80"
36+
class="mr-2"
37+
/>
38+
<div class="flex-1">
39+
<!-- visible alt text class keeps the alt text within
40+
the narrower image width given in class prop -->
41+
<p
42+
class="text-xl mt-0 text-wrap break-words break-keep group-hover:underline"
43+
>
44+
{item.data.name}
45+
</p>
46+
<p class="text-body-caption mt-xxs">
47+
{description} By {authorsString}
48+
</p>
49+
</div>
50+
</a>

src/content/ui/en.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ calloutTitles:
186186
"Try this!": "Try this!"
187187
Tip: Tip
188188
Note: Note
189+
LibrariesLayout:
190+
View All: View All
191+
Featured: Featured
192+
Everything: Everything
189193
experimentalApi:
190194
title: This API is experimental
191195
description: Its behavior may change in a future version of p5.js.

src/layouts/LibrariesLayout.astro

+69-12
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@ import type { CollectionEntry } from "astro:content";
33
import Head from "@components/Head/index.astro";
44
import BaseLayout from "./BaseLayout.astro";
55
import GridItemLibrary from "@components/GridItem/Library.astro";
6+
import LibraryListing from "@components/LibraryListing/index.astro";
67
import { setJumpToState, type JumpToLink } from "../globals/state";
78
import { getCurrentLocale, getUiTranslator } from "../i18n/utils";
89
import { categories } from "../content/libraries/config";
10+
import Button from "@components/Button/index.astro";
11+
import _ from "lodash";
912
1013
interface Props {
1114
entries: CollectionEntry<"libraries">[];
1215
title: string;
16+
full?: boolean;
1317
}
1418
type LibraryEntry = CollectionEntry<"libraries">;
1519
1620
const currentLocale = getCurrentLocale(Astro.url.pathname);
1721
const t = await getUiTranslator(currentLocale);
1822
19-
const { entries } = Astro.props;
23+
const { entries, full } = Astro.props;
2024
2125
function strCompare(a: string, b: string) {
2226
if (a < b) {
@@ -28,19 +32,56 @@ function strCompare(a: string, b: string) {
2832
return 0;
2933
}
3034
31-
const sections = categories
35+
let sections = categories
3236
.map((slug) => {
3337
const name = t("libraryCategories", slug);
34-
const sectionEntries = entries
38+
let sectionEntries = entries
3539
.filter((e: LibraryEntry) => e.data.category === slug)
3640
.sort((a: LibraryEntry, b: LibraryEntry) =>
3741
strCompare(a.data.name.toLowerCase(), b.data.name.toLowerCase())
3842
);
3943
40-
return { slug, name, sectionEntries };
44+
45+
return { slug, name, sectionEntries, allEntries: sectionEntries };
4146
})
4247
.filter((section) => section.sectionEntries.length > 0);
4348
49+
if (!full) {
50+
// On the featured libraries page, we want to show as close to 4 entries
51+
// per section as possible, while also trying to give all contributors
52+
// approximately equal footing of being featured. To do this, we don't
53+
// let contributors show up >3x on the featured page, and we try a
54+
// Monte Carlo approach to try to get as close to this as possible.
55+
const targetEntriesPerSection = 4
56+
57+
let minScore = 1000
58+
let bestSections = sections
59+
for (let attempt = 0; attempt < 100; attempt++) {
60+
const entriesByAuthor = _.groupBy(entries, (e: LibraryEntry) => e.data.author[0].name)
61+
const toRemove = new Set()
62+
for (const key in entriesByAuthor) {
63+
if (entriesByAuthor[key].length > 3) {
64+
for (const entry of _.shuffle(entriesByAuthor[key]).slice(3)) {
65+
toRemove.add(entry.id)
66+
}
67+
}
68+
}
69+
const candidateSections = sections.map((s) => ({
70+
...s,
71+
sectionEntries: s.sectionEntries.filter((e) => !toRemove.has(e.id)).slice(0, targetEntriesPerSection),
72+
allEntries: s.sectionEntries,
73+
}));
74+
const score = candidateSections
75+
.map((s) => Math.abs(s.sectionEntries.length - targetEntriesPerSection))
76+
.reduce((acc, next) => acc + next, 0);
77+
if (score < minScore) {
78+
minScore = score;
79+
bestSections = candidateSections;
80+
}
81+
}
82+
sections = bestSections;
83+
}
84+
4485
const pageJumpToLinks = categories.map((category) => ({
4586
url: `/libraries#${category}`,
4687
label: t("libraryCategories", category),
@@ -60,17 +101,33 @@ setJumpToState({
60101
variant="item"
61102
topic="community"
62103
>
104+
105+
<div class="flex">
106+
<Button selected={!full} href="/libraries/">{t("LibrariesLayout", "Featured")}</Button>
107+
<Button selected={full} href="/libraries/directory/">{t("LibrariesLayout", "Everything")}</Button>
108+
</div>
63109
{
64-
sections.map(({ slug, name, sectionEntries }) => (
110+
sections.map(({ slug, name, sectionEntries, allEntries }) => (
65111
<section>
66112
<h2 id={slug}>{name}</h2>
67-
<ul class="content-grid-simple">
68-
{sectionEntries.map((entry: LibraryEntry) => (
69-
<li>
70-
<GridItemLibrary item={entry} />
71-
</li>
72-
))}
73-
</ul>
113+
{full ? (
114+
<>
115+
{sectionEntries.map((entry: LibraryEntry) => (
116+
<LibraryListing item={entry} />
117+
))}
118+
</>
119+
) : (
120+
<>
121+
<ul class="content-grid-simple">
122+
{sectionEntries.map((entry: LibraryEntry) => (
123+
<li>
124+
<GridItemLibrary item={entry} />
125+
</li>
126+
))}
127+
</ul>
128+
<Button href={`/libraries/directory/#${slug}`}>{t("LibrariesLayout", "View All")} ({allEntries.length})</Button>
129+
</>
130+
)}
74131
</section>
75132
))
76133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
import { getCollectionInDefaultLocale } from "../../_utils";
3+
import LibrariesLayout from "@/src/layouts/LibrariesLayout.astro";
4+
5+
const libraries = await getCollectionInDefaultLocale("libraries");
6+
---
7+
8+
<LibrariesLayout full title="Libraries" entries={libraries} />

0 commit comments

Comments
 (0)