Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch extra library info from NPM #158

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/components/LibrarySourceLink/LinkCopier.tsx
Original file line number Diff line number Diff line change
@@ -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(
`<script src="${props.link}"></script>`
);
setCopied(true);
}

return (
<>
<a onClick={onClick} style={{ cursor: 'pointer' }}>
{props.children}
</a>
{copied && <span style="opacity: 0.6; margin-left: 5px">Copied ✔️</span>}
</>
)
}
8 changes: 8 additions & 0 deletions src/components/LibrarySourceLink/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
const { props } = Astro;
import { LinkCopier } from './LinkCopier'
---

<LinkCopier client:load link={props.link}>
<slot />
</LinkCopier>
23 changes: 22 additions & 1 deletion src/content/libraries/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z, defineCollection } from "astro:content";
import { author } from "../shared";

const categories = [
export const categories = [
"drawing",
"color",
"ui",
Expand All @@ -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.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/content/libraries/en/concaveHull.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/content/libraries/en/p5.teach.js.yaml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
138 changes: 111 additions & 27 deletions src/layouts/LibrariesLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}));
---

<BaseLayout title="Libraries">
<ul>
{
entries.map((lib) => (
<li>
{
// TODO: Fold into GridItemLibrary as a variant
}
<a href={getLibraryLink(lib)}>
{lib.data.featuredImageAlt && lib.data.featuredImage && (
<Image
src={lib.data.featuredImage}
alt={lib.data.featuredImageAlt}
width={1500}
height={1000}
/>
)}
<span>{lib.data.name}</span>
{lib.data.description}
Created by:{" "}
{lib.data.author.map((a: { name: string }) => a.name).join(", ")}
</a>
</li>
))
}
}
</ul>
{sections.map(({ slug, name, sectionEntries }) => (
<>
<h3 id={slug}>{name}</h3>
<ul>
{sectionEntries.map(({ lib, npmData, license }) => (
<li class="mb-3">
<a href={getLibraryLink(lib)}>
{lib.data.featuredImageAlt && lib.data.featuredImage && (
<Image
src={lib.data.featuredImage}
alt={lib.data.featuredImageAlt}
width={150}
height={100}
/>
)}
<h4>{lib.data.name}</h4>
</a>
<p class="mt-1">
{descriptionString(lib)}
{lib.data.author.map((a: { name: string, url?: string }, i) => (
<>
<a href={a.url}>{a.name}</a>{i < lib.data.author.length-1 ? ', ' : ''}
</>
))}
</p>
<ul class="list-none flex mt-1 flex-wrap">
<li class={libraryTag}><a href={lib.data.sourceUrl}>Source</a></li>
{lib.data.npm && <li class={libraryTag}><a href={`https://www.npmjs.com/package/${lib.data.npm}`}>NPM</a></li>}
{npmData && <li class={libraryTag}><LibrarySourceLink link={npmData.link}>Copy Script Tag</LibrarySourceLink></li>}
{npmData && <li class={libraryInfo}>Updated {npmData.lastUpdated}</li>}
{license && <li class={libraryInfo}>{license}</li>}
</ul>
</li>
))}
</ul>
</>
))}
</BaseLayout>