1- // HELPER FUNCTIONS TO USE THE OPENPROCESSING API
2- // SEE https://documenter.getpostman.com/view/16936458/2s9YC1Xa6X#intro
3-
41import type { AnyEntryMap , CollectionEntry } from "astro:content" ;
5- import memoize from "lodash/memoize" ;
2+ import { readFile , access } from "node:fs/promises" ;
3+ import { constants as FS } from "node:fs" ;
4+ import path from "node:path" ;
65
7- const openProcessingEndpoint = "https://openprocessing.org/api/" ;
8- /**
9- * ID of the OpenProcessing Curation we pull sketches from.
10- * Currently a placeholder (https://openprocessing.org/curation/78544/)
11- */
12- const curationId = "87649" ;
13- const newCurationId = "89576" ;
6+ const DATA_DIR = path . join ( process . cwd ( ) , "src" , "cached-data" ) ;
7+
8+ const CURATION_2024_FILE = path . join ( DATA_DIR , "openprocessing-curation-87649-sketches.json" ) ;
9+ const CURATION_2025_FILE = path . join ( DATA_DIR , "openprocessing-curation-89576-sketches.json" ) ;
10+ const SKETCH_FILE = ( id : number ) => path . join ( DATA_DIR , "openprocessing-sketches" , `${ id } .json` ) ;
1411
1512/**
16- * API Response from a call to the Curation Sketches endpoint
17- *
13+ * API Response from a call to the Curation Sketches endpoint, cached in the above files
1814 * see https://documenter.getpostman.com/view/16936458/2s9YC1Xa6X#7cd344f6-6e87-426a-969b-2b4a79701dd1
1915 */
2016export type OpenProcessingCurationResponse = Array < {
@@ -33,53 +29,6 @@ export type OpenProcessingCurationResponse = Array<{
3329 fullname : string ;
3430} > ;
3531
36- // Selected Sketches from the 2025 curation
37- export const priorityIds = [ '2690038' , '2484739' , '2688829' , '2689119' , '2690571' , '2690405' , '2684408' , '2693274' , '2693345' , '2691712' ]
38-
39- /**
40- * Get basic info for the sketches contained in a Curation
41- * from the OpenProcessing API
42- *
43- * @param limit max number of sketches to return
44- * @returns sketches
45- */
46- export const getCurationSketches = memoize ( async (
47- limit ?: number ,
48- ) : Promise < OpenProcessingCurationResponse > => {
49- const limitParam = limit ? `limit=${ limit } ` : "" ;
50- const response1 = await fetch (
51- `${ openProcessingEndpoint } curation/${ curationId } /sketches?${ limitParam } ` ,
52- ) ;
53- if ( ! response1 . ok ) {
54- throw new Error ( `getCurationSketches: ${ response1 . status } ${ response1 . statusText } ` )
55- }
56- const payload1 = await response1 . json ( ) ;
57-
58- const response2 = await fetch (
59- `${ openProcessingEndpoint } curation/${ newCurationId } /sketches?${ limitParam } ` ,
60- ) ;
61- if ( ! response2 . ok ) {
62- throw new Error ( `getCurationSketches: ${ response2 . status } ${ response2 . statusText } ` )
63- }
64- const payload2 = await response2 . json ( ) ;
65-
66-
67-
68- const prioritySketches = payload2 . filter (
69- ( sketch : OpenProcessingCurationResponse [ number ] ) => priorityIds . includes ( String ( sketch . visualID ) ) )
70- . sort ( ( a : OpenProcessingCurationResponse [ number ] , b : OpenProcessingCurationResponse [ number ] ) => priorityIds . indexOf ( String ( a . visualID ) ) - priorityIds . indexOf ( String ( b . visualID ) ) ) ;
71-
72-
73- const finalSketches = [
74- ...prioritySketches . map ( ( sketch : OpenProcessingCurationResponse [ number ] ) => ( { ...sketch , curation : '2025' } ) ) ,
75- ...payload1 . map ( ( sketch : OpenProcessingCurationResponse [ number ] ) => ( { ...sketch , curation : '2024' } ) ) ,
76- ] ;
77-
78- return [
79- ...finalSketches ,
80- ] as OpenProcessingCurationResponse ;
81- } ) ;
82-
8332/**
8433 * API Response from a call to the Sketch endpoint
8534 *
@@ -98,72 +47,62 @@ export type OpenProcessingSketchResponse = {
9847 submittedOn : string ;
9948 createdOn : string ;
10049 mode : string ;
50+ /* This is extracted from /code */
51+ width : number ;
52+ height : number ;
10153} ;
10254
103- /**
104- * Get info about a specific sketch from the OpenProcessing API
105- * First checks if the sketch is in the memoized curated sketches and returns the data if so,
106- * Otherwise calls OpenProcessing API for this specific sketch
107- *
108- * https://documenter.getpostman.com/view/16936458/2s9YC1Xa6X#7cd344f6-6e87-426a-969b-2b4a79701dd1
109- * @param id
110- * @returns
111- */
112- export const getSketch = memoize (
113- async ( id : number ) : Promise < OpenProcessingSketchResponse > => {
114- // check for memoized sketch in curation sketches
115- const curationSketches = await getCurationSketches ( ) ;
116- const memoizedSketch = curationSketches . find ( ( el ) => el . visualID === id ) ;
117- if ( memoizedSketch ) {
118- return {
119- ...memoizedSketch ,
120- license : "" ,
121- } as OpenProcessingSketchResponse ;
122- }
55+ // Selected Sketches from the 2025 curation
56+ export const priorityIds = [ '2690038' , '2484739' , '2688829' , '2689119' , '2690571' , '2690405' , '2684408' , '2693274' , '2693345' , '2691712' ]
12357
124- // check for sketch data in Open Processing API
125- const response = await fetch ( `${ openProcessingEndpoint } sketch/${ id } ` ) ;
126- if ( ! response . ok ) {
127- throw new Error ( `getSketch: ${ id } ${ response . status } ${ response . statusText } ` )
58+ async function exists ( p : string ) {
59+ try {
60+ await access ( p , FS . F_OK ) ;
61+ return true ;
62+ } catch {
63+ return false ;
12864 }
129- const payload = await response . json ( ) ;
130- return payload as OpenProcessingSketchResponse ;
131- } ) ;
65+ }
66+
67+ async function readJson < T > ( filePath : string ) : Promise < T > {
68+ const text = await readFile ( filePath , "utf8" ) ;
69+ return JSON . parse ( text ) as T ;
70+ }
13271
13372/**
134- * Note: this currently calls `/api/sketch/:id/code`
135- * But only uses the width and height properties from this call
136- * Width and height should instead be added to properties for `/api/sketch/:id` or `api/curation/:curationId/sketches` instead
73+ * Get basic info for the sketches contained in a Curation
74+ * from the OpenProcessing API
75+ *
76+ * @param limit max number of sketches to return
77+ * @returns sketches
13778 */
138- export const getSketchSize = memoize ( async ( id : number ) => {
139- const sketch = await getSketch ( id )
140- if ( sketch . mode !== 'p5js' ) {
141- return { width : undefined , height : undefined } ;
142- }
79+ export async function getCurationSketches ( ) : Promise < OpenProcessingCurationResponse > {
80+ const payload2024 = await readJson < OpenProcessingCurationResponse > ( CURATION_2024_FILE ) ;
81+ const payload2025 = await readJson < OpenProcessingCurationResponse > ( CURATION_2025_FILE ) ;
14382
144- const response = await fetch ( `${ openProcessingEndpoint } sketch/${ id } /code` ) ;
145- if ( ! response . ok ) {
146- throw new Error ( `getSketchSize: ${ id } ${ response . status } ${ response . statusText } ` )
147- }
148- const payload = await response . json ( ) ;
149-
150- for ( const tab of payload ) {
151- if ( ! tab . code ) continue ;
152- const match = / c r e a t e C a n v a s \( \s * ( \w + ) , \s * ( \w + ) \s * (?: , \s * (?: P 2 D | W E B G L ) \s * ) ? \) / m. exec ( tab . code ) ;
153- if ( match ) {
154- if ( match [ 1 ] === 'windowWidth' && match [ 2 ] === 'windowHeight' ) {
155- return { width : undefined , height : undefined } ;
156- }
157-
158- const width = parseFloat ( match [ 1 ] ) ;
159- const height = parseFloat ( match [ 2 ] ) ;
160- if ( width && height ) {
161- return { width, height } ;
162- }
163- }
83+ const prioritySketches = payload2025 . filter (
84+ ( sketch : OpenProcessingCurationResponse [ number ] ) => priorityIds . includes ( String ( sketch . visualID ) ) )
85+ . sort ( ( a : OpenProcessingCurationResponse [ number ] , b : OpenProcessingCurationResponse [ number ] ) => priorityIds . indexOf ( String ( a . visualID ) ) - priorityIds . indexOf ( String ( b . visualID ) ) ) ;
86+
87+
88+ const allSketches = [
89+ ...prioritySketches . map ( ( sketch : OpenProcessingCurationResponse [ number ] ) => ( { ...sketch , curation : '2025' } ) ) ,
90+ ...payload2024 . map ( ( sketch : OpenProcessingCurationResponse [ number ] ) => ( { ...sketch , curation : '2024' } ) ) ,
91+ ] ;
92+
93+ const availableSketches : OpenProcessingCurationResponse = [ ] ;
94+ for ( const sketch of allSketches ) {
95+ if ( await exists ( SKETCH_FILE ( sketch . visualID ) ) ) availableSketches . push ( sketch ) ;
16496 }
165- return { width : undefined , height : undefined } ;
166- } ) ;
97+
98+ return [
99+ ...availableSketches ,
100+ ] as OpenProcessingCurationResponse ;
101+ } ;
102+
103+ export async function getSketch ( id : number ) : Promise < OpenProcessingSketchResponse > {
104+ return await readJson < OpenProcessingSketchResponse > ( SKETCH_FILE ( id ) ) ;
105+ }
167106
168107export const makeSketchLinkUrl = ( id : number ) =>
169108 `https://openprocessing.org/sketch/${ id } ` ;
@@ -196,12 +135,14 @@ export function isCurationResponse<C extends keyof AnyEntryMap>(
196135 return "visualID" in ( item as any ) ;
197136}
198137
199- export const getRandomCurationSketches = memoize ( async ( num = 4 ) => {
138+ export async function getRandomCurationSketches ( num = 4 ) {
200139 const curationSketches = await getCurationSketches ( ) ;
201140 const result : OpenProcessingCurationResponse = [ ] ;
202141 const usedIndices : Set < number > = new Set ( ) ;
203142
204- while ( result . length < num ) {
143+ const n = Math . min ( num , curationSketches . length ) ;
144+
145+ while ( result . length < n ) {
205146 const randomIndex = Math . floor ( Math . random ( ) * curationSketches . length ) ;
206147 if ( ! usedIndices . has ( randomIndex ) ) {
207148 result . push ( curationSketches [ randomIndex ] ) ;
@@ -210,4 +151,4 @@ export const getRandomCurationSketches = memoize(async (num = 4) => {
210151 }
211152
212153 return result ;
213- } ) ;
154+ }
0 commit comments