diff --git a/src/components/LibrarySourceLink/LinkCopier.tsx b/src/components/LibrarySourceLink/LinkCopier.tsx new file mode 100644 index 0000000000..faa40215c0 --- /dev/null +++ b/src/components/LibrarySourceLink/LinkCopier.tsx @@ -0,0 +1,20 @@ +import { useState } from "preact/hooks" + +export const LinkCopier = (props: { link: string; children: any }) => { + const [copied, setCopied] = useState(false); + const onClick = () => { + navigator.clipboard.writeText( + `` + ); + setCopied(true); + } + + return ( + <> + + {props.children} + + {copied && Copied ✔️} + + ) +} diff --git a/src/components/LibrarySourceLink/index.astro b/src/components/LibrarySourceLink/index.astro new file mode 100644 index 0000000000..648725c2e2 --- /dev/null +++ b/src/components/LibrarySourceLink/index.astro @@ -0,0 +1,8 @@ +--- +const { props } = Astro; +import { LinkCopier } from './LinkCopier' +--- + + + + diff --git a/src/content/libraries/config.ts b/src/content/libraries/config.ts index 6518478e69..9d57d84a66 100644 --- a/src/content/libraries/config.ts +++ b/src/content/libraries/config.ts @@ -1,7 +1,7 @@ import { z, defineCollection } from "astro:content"; import { author } from "../shared"; -const categories = [ +export const categories = [ "drawing", "color", "ui", @@ -22,6 +22,27 @@ const categories = [ "utils", ] as const; +export const categoryNames: { [key in (typeof categories)[number]]: string } = { + drawing: "Drawing", + color: "Color", + ui: "User Interface", + math: "Math", + physics: "Physics", + algorithms: "Algorithms", + "3d": "3D", + "ai-ml-cv": "AI, ML, and CV", + animation: "Animation", + shaders: "Shaders", + language: "Language", + hardware: "Hardware", + sound: "Sound", + data: "Data", + teaching: "Teaching", + networking: "Networking", + export: "Export", + utils: "Utilities", +}; + /** * Content collection for the Libraries section of the site. */ diff --git a/src/content/libraries/en/concaveHull.yaml b/src/content/libraries/en/concaveHull.yaml index 007899529e..7eb01dc77b 100644 --- a/src/content/libraries/en/concaveHull.yaml +++ b/src/content/libraries/en/concaveHull.yaml @@ -8,4 +8,4 @@ author: name: Mark Roland url: https://markroland.com npm: "@markroland/concave-hull" -license: Creative Commons Attribution-ShareAlike 4.0 International License +license: CC BY-SA 4.0 diff --git a/src/content/libraries/en/p5.teach.js.yaml b/src/content/libraries/en/p5.teach.js.yaml index 2080792f4e..9194cd67a2 100644 --- a/src/content/libraries/en/p5.teach.js.yaml +++ b/src/content/libraries/en/p5.teach.js.yaml @@ -1,6 +1,6 @@ name: p5.teach.js description: A beginner friendly math animation library for p5.js -category: math +category: teaching sourceUrl: https://github.com/two-ticks/p5.teach.js featuredImage: ../images/p5.teach.js.png featuredImageAlt: Wave equation on the upper left corner and right upper corner has a graph of cosine function plus some polynomial terms. A logo and description of 'p5.teach.js' library are placed in the center. diff --git a/src/layouts/LibrariesLayout.astro b/src/layouts/LibrariesLayout.astro index fb49675b6f..76d9f7565f 100644 --- a/src/layouts/LibrariesLayout.astro +++ b/src/layouts/LibrariesLayout.astro @@ -3,41 +3,125 @@ import { getLibraryLink } from "@pages/_utils"; import type { CollectionEntry } from "astro:content"; import BaseLayout from "./BaseLayout.astro"; +import { categories, categoryNames } from '../content/libraries/config'; +import LibrarySourceLink from '../components/LibrarySourceLink/index.astro'; import Image from "@components/Image/index.astro"; +type LibraryEntry = CollectionEntry<"libraries"> interface Props { - entries: CollectionEntry<"libraries">[]; + entries: LibraryEntry[]; title: string; } const { entries } = Astro.props; + +async function npmInfo(lib: LibraryEntry) { + try { + const res = await fetch(`https://registry.npmjs.org/${lib.data.npm}`); + const data = await res.json(); + return data; + } catch (e) { + console.error(`Could not fetch ${lib.data.name} on npm via ${lib.data.npm}!`) + throw e; + } +} + +function cdnLink(lib: LibraryEntry, data: any) { + const latestVersion = data['dist-tags'].latest + let link = `https://cdn.jsdelivr.net/npm/${lib.data.npm}@${latestVersion}`; + if (lib.data.npmFilePath) { + link += `/${lib.data.npmFilePath}`; + } + return link; +} + +function strCompare(a: string, b: string) { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; +} + +function descriptionString(lib: LibraryEntry) { + let result = lib.data.description.trim(); + if (/\w$/.test(result)) { + result += ', by'; + } else if (/[\.!?]$/.test(result)) { + result += ' By'; + } else { + result += '. By'; + } + return result; +} + +const libraryTag = 'whitespace-nowrap py-1 px-2 rounded-lg bg-[var(--accent-color)] mb-1 mr-1 block text-xs' +const libraryInfo = 'whitespace-nowrap py-1 px-2 mb-1 mr-1 block text-xs' + +const sections = await Promise.all(categories.map(async (slug) => { + const name = categoryNames[slug]; + const sectionEntries = await Promise.all( + entries + .filter((e: LibraryEntry) => e.data.category === slug) + .sort((a: LibraryEntry, b: LibraryEntry) => strCompare(a.data.name.toLowerCase(), b.data.name.toLowerCase())) + .map(async (lib: LibraryEntry) => { + if (lib.data.npm) { + const data = await npmInfo(lib); + const modifiedDate = new Date(data.time.modified); + const npmData = { + link: cdnLink(lib, data), + lastUpdated: `${modifiedDate.toLocaleString('default', { month: 'short' })} ${modifiedDate.getFullYear()}`, + }; + const license = lib.data.license || data.license; + return { lib, npmData, license }; + } else { + return { lib, npmData: undefined, license: lib.data.license }; + } + }) + ); + + return { slug, name, sectionEntries }; +})); --- - + {sections.map(({ slug, name, sectionEntries }) => ( + <> +

{name}

+ + + ))}