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,51 +29,6 @@ export type OpenProcessingCurationResponse = Array<{
3329 fullname : string ;
3430} > ;
3531
36- /**
37- * Get basic info for the sketches contained in a Curation
38- * from the OpenProcessing API
39- *
40- * @param limit max number of sketches to return
41- * @returns sketches
42- */
43- export const getCurationSketches = memoize ( async (
44- limit ?: number ,
45- ) : Promise < OpenProcessingCurationResponse > => {
46- const limitParam = limit ? `limit=${ limit } ` : "" ;
47- const response1 = await fetch (
48- `${ openProcessingEndpoint } curation/${ curationId } /sketches?${ limitParam } ` ,
49- ) ;
50- if ( ! response1 . ok ) { //log error instead of throwing error to not cache result in memoize
51- console . error ( 'getCurationSketches' , response1 . status , response1 . statusText )
52- }
53- const payload1 = await response1 . json ( ) ;
54-
55- const response2 = await fetch (
56- `${ openProcessingEndpoint } curation/${ newCurationId } /sketches?${ limitParam } ` ,
57- ) ;
58- if ( ! response2 . ok ) { //log error instead of throwing error to not cache result in memoize
59- console . error ( 'getCurationSketches' , response2 . status , response2 . statusText )
60- }
61- const payload2 = await response2 . json ( ) ;
62-
63- // Selected Sketches from the 2025 curation
64- const priorityIds = [ '2690038' , '2484739' , '2688829' , '2689119' , '2690571' , '2690405' , '2684408' , '2693274' , '2693345' , '2691712' ]
65-
66- const prioritySketches = payload2 . filter (
67- ( sketch : OpenProcessingCurationResponse [ number ] ) => priorityIds . includes ( String ( sketch . visualID ) ) )
68- . sort ( ( a : OpenProcessingCurationResponse [ number ] , b : OpenProcessingCurationResponse [ number ] ) => priorityIds . indexOf ( String ( a . visualID ) ) - priorityIds . indexOf ( String ( b . visualID ) ) ) ;
69-
70-
71- const finalSketches = [
72- ...prioritySketches . map ( ( sketch : OpenProcessingCurationResponse [ number ] ) => ( { ...sketch , curation : '2025' } ) ) ,
73- ...payload1 . map ( ( sketch : OpenProcessingCurationResponse [ number ] ) => ( { ...sketch , curation : '2024' } ) ) ,
74- ] ;
75-
76- return [
77- ...finalSketches ,
78- ] as OpenProcessingCurationResponse ;
79- } ) ;
80-
8132/**
8233 * API Response from a call to the Sketch endpoint
8334 *
@@ -96,73 +47,62 @@ export type OpenProcessingSketchResponse = {
9647 submittedOn : string ;
9748 createdOn : string ;
9849 mode : string ;
50+ /* This is extracted from /code */
51+ width : number ;
52+ height : number ;
9953} ;
10054
101- /**
102- * Get info about a specific sketch from the OpenProcessing API
103- * First checks if the sketch is in the memoized curated sketches and returns the data if so,
104- * Otherwise calls OpenProcessing API for this specific sketch
105- *
106- * https://documenter.getpostman.com/view/16936458/2s9YC1Xa6X#7cd344f6-6e87-426a-969b-2b4a79701dd1
107- * @param id
108- * @returns
109- */
110- export const getSketch = memoize (
111- async ( id : number ) : Promise < OpenProcessingSketchResponse > => {
112- // check for memoized sketch in curation sketches
113- const curationSketches = await getCurationSketches ( ) ;
114- const memoizedSketch = curationSketches . find ( ( el ) => el . visualID === id ) ;
115- if ( memoizedSketch ) {
116- return {
117- ...memoizedSketch ,
118- license : "" ,
119- } as OpenProcessingSketchResponse ;
120- }
55+ // Selected Sketches from the 2025 curation
56+ export const priorityIds = [ '2690038' , '2484739' , '2688829' , '2689119' , '2690571' , '2690405' , '2684408' , '2693274' , '2693345' , '2691712' ]
12157
122- // check for sketch data in Open Processing API
123- const response = await fetch ( `${ openProcessingEndpoint } sketch/${ id } ` ) ;
124- if ( ! response . ok ) {
125- //log error instead of throwing error to not cache result in memoize
126- console . 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 ;
12764 }
128- const payload = await response . json ( ) ;
129- return payload as OpenProcessingSketchResponse ;
130- } ) ;
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+ }
13171
13272/**
133- * Note: this currently calls `/api/sketch/:id/code`
134- * But only uses the width and height properties from this call
135- * 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
13678 */
137- export const getSketchSize = memoize ( async ( id : number ) => {
138- const sketch = await getSketch ( id )
139- if ( sketch . mode !== 'p5js' ) {
140- return { width : undefined , height : undefined } ;
141- }
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 ) ;
14282
143- const response = await fetch ( `${ openProcessingEndpoint } sketch/${ id } /code` ) ;
144- if ( ! response . ok ) { //log error instead of throwing error to not cache result in memoize
145- console . error ( 'getSketchSize' , id , response . status , response . statusText )
146- }
147- const payload = await response . json ( ) ;
148-
149- for ( const tab of payload ) {
150- if ( ! tab . code ) continue ;
151- 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 ) ;
152- if ( match ) {
153- if ( match [ 1 ] === 'windowWidth' && match [ 2 ] === 'windowHeight' ) {
154- return { width : undefined , height : undefined } ;
155- }
156-
157- const width = parseFloat ( match [ 1 ] ) ;
158- const height = parseFloat ( match [ 2 ] ) ;
159- if ( width && height ) {
160- return { width, height } ;
161- }
162- }
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 ) ;
16396 }
164- return { width : undefined , height : undefined } ;
165- } ) ;
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+ }
166106
167107export const makeSketchLinkUrl = ( id : number ) =>
168108 `https://openprocessing.org/sketch/${ id } ` ;
@@ -195,12 +135,14 @@ export function isCurationResponse<C extends keyof AnyEntryMap>(
195135 return "visualID" in ( item as any ) ;
196136}
197137
198- export const getRandomCurationSketches = memoize ( async ( num = 4 ) => {
138+ export async function getRandomCurationSketches ( num = 4 ) {
199139 const curationSketches = await getCurationSketches ( ) ;
200140 const result : OpenProcessingCurationResponse = [ ] ;
201141 const usedIndices : Set < number > = new Set ( ) ;
202142
203- while ( result . length < num ) {
143+ const n = Math . min ( num , curationSketches . length ) ;
144+
145+ while ( result . length < n ) {
204146 const randomIndex = Math . floor ( Math . random ( ) * curationSketches . length ) ;
205147 if ( ! usedIndices . has ( randomIndex ) ) {
206148 result . push ( curationSketches [ randomIndex ] ) ;
@@ -209,4 +151,4 @@ export const getRandomCurationSketches = memoize(async (num = 4) => {
209151 }
210152
211153 return result ;
212- } ) ;
154+ }
0 commit comments