Skip to content

Commit

Permalink
Merge pull request #438 from processing/feat/libraries-directory
Browse files Browse the repository at this point in the history
Add directory for all libraries
  • Loading branch information
davepagurek authored Nov 6, 2024
2 parents eccabb7 + 722d513 commit ad4d38c
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 12 deletions.
27 changes: 27 additions & 0 deletions src/components/Button/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
interface Props {
href: string
selected?: boolean
}
const { href, selected = false } = Astro.props;
---

<a href={href} class={`rounded-button ${selected ? 'selected' : ''}`}>
<slot />
</a>

<style lang="scss">
.rounded-button {
display: inline-block;
margin: var(--spacing-md);
margin-left: 0;
padding: var(--spacing-xs) var(--spacing-sm);
border: 1px solid var(--type-black);
color: var(--type-black);
border-radius: 999px;
}
.rounded-button.selected {
color: var(--type-black);
background: var(--accent-color);
}
</style>
50 changes: 50 additions & 0 deletions src/components/LibraryListing/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
import { getCurrentLocale } from "@/src/i18n/utils";
import { getLibraryLink } from "@/src/pages/_utils";
import Image from "@components/Image/index.astro";
import type { CollectionEntry } from "astro:content";
interface Props {
item: CollectionEntry<"libraries">;
}
const { item } = Astro.props;
const authorsFormatter = new Intl.ListFormat(
getCurrentLocale(Astro.url.pathname),
{
style: "long",
type: "conjunction",
}
);
const authorsString = authorsFormatter.format(
item.data.author.map((a) => a.name)
);
let description = item.data.description.trim();
// If the description didn't end with punctuation, add it, since we will be
// appending another sentence afterwards.
if (!/[.!]\)?$/.test(description)) {
description += ".";
}
---

<a class="group hover:no-underline flex mt-sm items-center" href={getLibraryLink(item)}>
<Image
src={item.data.featuredImage}
alt={item.data.featuredImageAlt}
width="80"
class="mr-2"
/>
<div class="flex-1">
<!-- visible alt text class keeps the alt text within
the narrower image width given in class prop -->
<p
class="text-xl mt-0 text-wrap break-words break-keep group-hover:underline"
>
{item.data.name}
</p>
<p class="text-body-caption mt-xxs">
{description} By {authorsString}
</p>
</div>
</a>
4 changes: 4 additions & 0 deletions src/content/ui/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ calloutTitles:
"Try this!": "Try this!"
Tip: Tip
Note: Note
LibrariesLayout:
View All: View All
Featured: Featured
Everything: Everything
experimentalApi:
title: This API is experimental
description: Its behavior may change in a future version of p5.js.
81 changes: 69 additions & 12 deletions src/layouts/LibrariesLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ import type { CollectionEntry } from "astro:content";
import Head from "@components/Head/index.astro";
import BaseLayout from "./BaseLayout.astro";
import GridItemLibrary from "@components/GridItem/Library.astro";
import LibraryListing from "@components/LibraryListing/index.astro";
import { setJumpToState, type JumpToLink } from "../globals/state";
import { getCurrentLocale, getUiTranslator } from "../i18n/utils";
import { categories } from "../content/libraries/config";
import Button from "@components/Button/index.astro";
import _ from "lodash";
interface Props {
entries: CollectionEntry<"libraries">[];
title: string;
full?: boolean;
}
type LibraryEntry = CollectionEntry<"libraries">;
const currentLocale = getCurrentLocale(Astro.url.pathname);
const t = await getUiTranslator(currentLocale);
const { entries } = Astro.props;
const { entries, full } = Astro.props;
function strCompare(a: string, b: string) {
if (a < b) {
Expand All @@ -28,19 +32,56 @@ function strCompare(a: string, b: string) {
return 0;
}
const sections = categories
let sections = categories
.map((slug) => {
const name = t("libraryCategories", slug);
const sectionEntries = entries
let sectionEntries = entries
.filter((e: LibraryEntry) => e.data.category === slug)
.sort((a: LibraryEntry, b: LibraryEntry) =>
strCompare(a.data.name.toLowerCase(), b.data.name.toLowerCase())
);
return { slug, name, sectionEntries };
return { slug, name, sectionEntries, allEntries: sectionEntries };
})
.filter((section) => section.sectionEntries.length > 0);
if (!full) {
// On the featured libraries page, we want to show as close to 4 entries
// per section as possible, while also trying to give all contributors
// approximately equal footing of being featured. To do this, we don't
// let contributors show up >3x on the featured page, and we try a
// Monte Carlo approach to try to get as close to this as possible.
const targetEntriesPerSection = 4
let minScore = 1000
let bestSections = sections
for (let attempt = 0; attempt < 100; attempt++) {
const entriesByAuthor = _.groupBy(entries, (e: LibraryEntry) => e.data.author[0].name)
const toRemove = new Set()
for (const key in entriesByAuthor) {
if (entriesByAuthor[key].length > 3) {
for (const entry of _.shuffle(entriesByAuthor[key]).slice(3)) {
toRemove.add(entry.id)
}
}
}
const candidateSections = sections.map((s) => ({
...s,
sectionEntries: s.sectionEntries.filter((e) => !toRemove.has(e.id)).slice(0, targetEntriesPerSection),
allEntries: s.sectionEntries,
}));
const score = candidateSections
.map((s) => Math.abs(s.sectionEntries.length - targetEntriesPerSection))
.reduce((acc, next) => acc + next, 0);
if (score < minScore) {
minScore = score;
bestSections = candidateSections;
}
}
sections = bestSections;
}
const pageJumpToLinks = categories.map((category) => ({
url: `/libraries#${category}`,
label: t("libraryCategories", category),
Expand All @@ -60,17 +101,33 @@ setJumpToState({
variant="item"
topic="community"
>

<div class="flex">
<Button selected={!full} href="/libraries/">{t("LibrariesLayout", "Featured")}</Button>
<Button selected={full} href="/libraries/directory/">{t("LibrariesLayout", "Everything")}</Button>
</div>
{
sections.map(({ slug, name, sectionEntries }) => (
sections.map(({ slug, name, sectionEntries, allEntries }) => (
<section>
<h2 id={slug}>{name}</h2>
<ul class="content-grid-simple">
{sectionEntries.map((entry: LibraryEntry) => (
<li>
<GridItemLibrary item={entry} />
</li>
))}
</ul>
{full ? (
<>
{sectionEntries.map((entry: LibraryEntry) => (
<LibraryListing item={entry} />
))}
</>
) : (
<>
<ul class="content-grid-simple">
{sectionEntries.map((entry: LibraryEntry) => (
<li>
<GridItemLibrary item={entry} />
</li>
))}
</ul>
<Button href={`/libraries/directory/#${slug}`}>{t("LibrariesLayout", "View All")} ({allEntries.length})</Button>
</>
)}
</section>
))
}
Expand Down
8 changes: 8 additions & 0 deletions src/pages/libraries/directory/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
import { getCollectionInDefaultLocale } from "../../_utils";
import LibrariesLayout from "@/src/layouts/LibrariesLayout.astro";
const libraries = await getCollectionInDefaultLocale("libraries");
---

<LibrariesLayout full title="Libraries" entries={libraries} />

0 comments on commit ad4d38c

Please sign in to comment.