Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9cd4afa
package upgrades
msawired Apr 17, 2026
13efc4e
Initial integration of OpenProcessing API client and adapters
msawired Apr 24, 2026
1b16d0f
Cookie Consent is now sessionStorage based
msawired Apr 24, 2026
ab03cd6
unused cookie-consent endpoint removed
msawired May 1, 2026
b5939bd
OP API support added for simple sketch data
msawired May 1, 2026
5f316f2
Revert "Cookie Consent is now sessionStorage based"
msawired May 1, 2026
1efae78
CookieConsent is now only cookie based.
msawired May 1, 2026
ef9e919
renamed variable "ApiTokenReady" for clarity
msawired May 1, 2026
ec05376
bug: sketches now saved as "html" instead of "p5js"
msawired May 1, 2026
fd55b9b
code preselection now based on latest updatedOn date
msawired May 1, 2026
192554a
sketch listing updated to use X-Total-Count header
msawired May 8, 2026
ff6387f
Debounce sketch list API requests and cancel stale searches.
msawired May 8, 2026
32362e5
S3 and File List management support
msawired May 15, 2026
ba8cac1
added claude folder to gitignore
msawired May 21, 2026
9b10bd4
OP-Authorization and Social Logins Refactored
msawired Jun 3, 2026
d1f39c8
Back editor collections with OpenProcessing curations API
msawired Jun 4, 2026
f64b5ce
Merge pull request #2 from processing/develop
msawired Jun 10, 2026
5471bb0
Merge branch 'develop' into OP-backend/main
msawired Jun 10, 2026
4dd6153
initial pruning
msawired Jun 11, 2026
78c22cb
bug: auth request didn't include private access
msawired Jun 11, 2026
4b661ab
JSZip library added to create zip files on client
msawired Jun 18, 2026
6db86b5
Merge pull request #3 from processing/develop
msawired Jun 18, 2026
cf3c21d
Merge remote-tracking branch 'origin/develop' into OP-backend/main
msawired Jun 18, 2026
ffea67a
Add "Copy Path" option to S3-hosted file dropdowns
msawired Jul 13, 2026
1a3c336
oAuth URL update
msawired Jul 27, 2026
ac66ee0
Add API_TOKEN override and remove stale mongo/s3/mailgun env vars
msawired Jul 28, 2026
4ee2742
bug: user sketches list wasn't visible to guests.
msawired Jul 28, 2026
1ce935e
Handle 404s via notFoundRedirect
msawired Jul 28, 2026
d132265
Fetch sketch and collection sub-resources only once the parent resolves
msawired Jul 28, 2026
e146032
Removed unconsumed environment variables
msawired Jul 28, 2026
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
46 changes: 20 additions & 26 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
API_URL=/editor
AWS_ACCESS_KEY=<your-aws-access-key>
AWS_REGION=<your-aws-region>
AWS_SECRET_KEY=<your-aws-secret-key>
CORS_ALLOW_LOCALHOST=true
EMAIL_SENDER=<transactional-email-sender>
EMAIL_VERIFY_SECRET_TOKEN=whatever_you_want_this_to_be_it_only_matters_for_production
EXAMPLE_USER_EMAIL=examples@p5js.org
EXAMPLE_USER_PASSWORD=hellop5js
GG_EXAMPLES_USERNAME=generativedesign
GG_EXAMPLES_EMAIL=benedikt.gross@generative-gestaltung.de
GG_EXAMPLES_PASS=generativedesign
GITHUB_ID=<your-github-client-id>
GITHUB_SECRET=<your-github-client-secret>
GOOGLE_ID=<your-google-client-id> (use google+ api)
GOOGLE_SECRET=<your-google-client-secret> (use google+ api)
MAILGUN_DOMAIN=<your-mailgun-domain>
MAILGUN_KEY=<your-mailgun-api-key>
ML5_LIBRARY_USERNAME=ml5
ML5_LIBRARY_EMAIL=examples@ml5js.org
ML5_LIBRARY_PASS=helloml5
MONGO_URL=mongodb://localhost:27017/p5js-web-editor
PORT=8000
PREVIEW_PORT=8002
EDITOR_URL=http://localhost:8000
PREVIEW_URL=http://localhost:8002
S3_BUCKET=<your-s3-bucket>
S3_BUCKET_URL_BASE=<alt-for-s3-url>
SESSION_SECRET=whatever_you_want_this_to_be_it_only_matters_for_production
CORS_ALLOW_LOCALHOST=true
TRANSLATIONS_ENABLED=true
UI_ACCESS_TOKEN_ENABLED=false
UPLOAD_LIMIT=250000000

# OpenProcessing variables
API_URL=https://openprocessing.org/api

# Optional deployment-wide override token. When set, it is used as the bearer
# for every OP API request and the per-user OAuth login flow is bypassed
# entirely. It is bundled into the page, so anyone who can load the editor can
# read it — only use this for self-hosted / single-user / embedded / CI setups,
# never on a shared public deployment. Leave unset to use normal OAuth login.
# You can generate an API token at https://openprocessing.org/@<your-username>/#edit
# API_TOKEN=op_<your-api-token>

# Optional: public OpenProcessing sketch (visualID) used as the animated
# background on the 404 page. Leave unset for a plain 404 page.
OP_404_SKETCH_ID=

# Path to the OpenProcessing collection shown as the editor's "Examples" menu
# item, e.g. /username/collections/collectionId. Leave unset to hide Examples.
EXAMPLES_ENDPOINT=
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ duplicates.json

coverage

*.tsbuildinfo
*.tsbuildinfo
/docs
/.claude
6 changes: 6 additions & 0 deletions .verify_boot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
process.env.NODE_ENV='test'; process.env.PORT='0'; process.env.PREVIEW_PORT='0';
process.env.API_URL='https://example.org/api'; process.env.EDITOR_URL='http://localhost:8000';
require('@babel/register')({extensions:['.js','.jsx','.ts','.tsx'],presets:['@babel/preset-env','@babel/preset-typescript']});
require('regenerator-runtime/runtime');
try { require('./server/server.js'); require('./server/previewServer.js'); console.log('OK: both servers boot'); }
catch(e){ console.error('BOOT ERROR:', e.message); process.exit(1); }
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: MONGO_URL=$MONGO_URI MAILGUN_KEY=$MAILGUN_API_KEY npm run start:prod
web: npm run start:prod
81 changes: 2 additions & 79 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,13 @@
"repository": "https://github.com/processing/p5.js-web-editor",
"logo": "https://p5js.org/assets/img/p5js.svg",
"keywords": ["processing", "p5js", "p5.js"],
"addons": [
{
"plan": "mongolab:sandbox",
"as": "MONGO"
},
{
"plan": "mailgun:starter",
"as": "MAILGUN"
}
],
"env": {
"API_URL": {
"value": "/editor"
},
"AWS_ACCESS_KEY": {
"description": "AWS Access Key",
"value": "placeholder"
},
"AWS_SECRET_KEY": {
"description": "AWS Secret Key",
"value": "placeholder"
},
"AWS_REGION": {
"description": "AWS Region for the S3_BUCKET",
"value": "placeholder"
},
"EMAIL_SENDER": {
"description": "The sending email address for transactional emails.",
"value": "no-reply@mydomain.com"
},
"EMAIL_VERIFY_SECRET_TOKEN": {
"description": "A secret key for...? Not sure where used.",
"generator": "secret"
},
"EXAMPLE_USER_EMAIL": {
"description": "The email address for the account holding the default Example sketches",
"value": "examples@p5js.org"
},
"EXAMPLE_USER_PASSWORD": {
"value": "hellop5js"
},
"GG_EXAMPLES_EMAIL": {
"description": "The email address for the account holding the Generative Design Example sketches",
"value": "benedikt.gross@generative-gestaltung.de"
},
"GG_EXAMPLES_PASS": {
"value": "generativedesign"
},
"GG_EXAMPLES_USERNAME": {
"value": "generative-design"
},
"GITHUB_ID": {
"description": "The GitHub Client Id for sign in with GitHub support",
"value": "placeholder"
},
"GITHUB_SECRET": {
"description": "The GitHub Client Secret",
"value": "placeholder"
},
"GOOGLE_ID": {
"description": "The Google Client Id for sign in with Google support",
"value": "placeholder"
},
"GOOGLE_SECRET": {
"description": "The Google Client Secret",
"value": "placeholder"
"description": "Base URL of the OpenProcessing API the editor reads from.",
"value": "https://openprocessing.org/api"
},
"NODE_ENV": {
"value": "production"
},
"S3_BUCKET": {
"desription": "Name of the S3 bucket for asset storage",
"value": "placeholder"
},
"S3_BUCKET_URL_BASE": {
"description": "S3 bucket URL base",
"required": false
},
"SESSION_SECRET": {
"description": "A secret key for verifying the integrity of signed cookies.",
"generator": "secret"
}
},
"scripts": {
"postdeploy": "MONGO_URL=$MONGO_URI MAILGUN_KEY=$MAILGUN_API_KEY npm run fetch-examples:prod"
}
}
18 changes: 2 additions & 16 deletions client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ export const AUTH_USER = 'AUTH_USER';
export const UNAUTH_USER = 'UNAUTH_USER';
export const AUTH_ERROR = 'AUTH_ERROR';

export const SETTINGS_UPDATED = 'SETTINGS_UPDATED';

export const API_KEY_CREATED = 'API_KEY_CREATED';
export const API_KEY_REMOVED = 'API_KEY_REMOVED';

export const SET_PROJECT_NAME = 'SET_PROJECT_NAME';
export const RENAME_PROJECT = 'RENAME_PROJECT';

Expand All @@ -37,6 +32,7 @@ export const SET_PROJECTS_FOR_COLLECTION_LIST =
'SET_PROJECTS_FOR_COLLECTION_LIST';

export const SET_COLLECTIONS = 'SET_COLLECTIONS';
export const SET_COLLECTION = 'SET_COLLECTION';
export const CREATE_COLLECTION = 'CREATED_COLLECTION';
export const DELETE_COLLECTION = 'DELETE_COLLECTION';

Expand Down Expand Up @@ -104,15 +100,6 @@ export const END_SKETCH_REFRESH = 'END_SKETCH_REFRESH';
export const DETECT_INFINITE_LOOPS = 'DETECT_INFINITE_LOOPS';
export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS';

export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE';
export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET';
export const INVALID_RESET_PASSWORD_TOKEN = 'INVALID_RESET_PASSWORD_TOKEN';

export const EMAIL_VERIFICATION_INITIATE = 'EMAIL_VERIFICATION_INITIATE';
export const EMAIL_VERIFICATION_VERIFY = 'EMAIL_VERIFICATION_VERIFY';
export const EMAIL_VERIFICATION_VERIFIED = 'EMAIL_VERIFICATION_VERIFIED';
export const EMAIL_VERIFICATION_INVALID = 'EMAIL_VERIFICATION_INVALID';

// eventually, handle errors more specifically and better
export const ERROR = 'ERROR';

Expand All @@ -121,6 +108,7 @@ export const RESET_JUST_OPENED_PROJECT = 'RESET_JUST_OPENED_PROJECT';

export const SET_PROJECT_SAVED_TIME = 'SET_PROJECT_SAVED_TIME';
export const RESET_PROJECT_SAVED_TIME = 'RESET_PROJECT_SAVED_TIME';
export const SET_SAVED_CODE_TITLES = 'SET_SAVED_CODE_TITLES';
export const SET_PREVIOUS_PATH = 'SET_PREVIOUS_PATH';
export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL';
export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL';
Expand All @@ -140,7 +128,5 @@ export const CLOSE_SKETCHLIST_MODAL = 'CLOSE_SKETCHLIST_MODAL';
export const START_SAVING_PROJECT = 'START_SAVING_PROJECT';
export const END_SAVING_PROJECT = 'END_SAVING_PROJECT';

export const SET_COOKIE_CONSENT = 'SET_COOKIE_CONSENT';

export const CONSOLE_EVENT = 'CONSOLE_EVENT';
export const CLEAR_CONSOLE = 'CLEAR_CONSOLE';
74 changes: 65 additions & 9 deletions client/modules/IDE/actions/assets.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,69 @@
import { apiClient } from '../../../utils/apiClient';
import { opApiClient } from '../../../utils/opApiClient';
import * as ActionTypes from '../../../constants';
import { startLoader, stopLoader } from '../reducers/loading';
import { assetsActions } from '../reducers/assets';
import { showToast } from './toast';

const { setAssets, deleteAsset } = assetsActions;

function encodeFilePath(filePath) {
return filePath.split('/').map(encodeURIComponent).join('/');
}

function getTransactionErrorMessage(error, fallbackMessage) {
const data = error?.response?.data;
return (
data?.message ||
data?.error ||
(typeof data === 'string' ? data : undefined) ||
error?.message ||
fallbackMessage
);
}

function normalizeOpAsset(asset) {
const visualID = asset.visualID == null ? null : String(asset.visualID);
return {
key: `${visualID ?? 'unknown'}:${asset.name}:${asset.url}`,
name: asset.name,
url: asset.url,
size: asset.size,
lastModified: asset.lastModified,
visualID,
sketchId: visualID,
sketchName: asset.visualTitle
};
}

export function getAssets() {
return async (dispatch) => {
return async (dispatch, getState) => {
const { user } = getState();
const userID = user?.id;

// Wait until auth hydration (/whoami) provides the current user's ID.
if (!userID) {
return;
}

dispatch(startLoader());
try {
const response = await apiClient.get('/S3/objects');
const response = await opApiClient.get(`/user/${userID}/files`);
const assets = response.data.map(normalizeOpAsset);

const assetData = {
assets: response.data.assets,
totalSize: response.data.totalSize
assets,
totalSize: assets.reduce((total, asset) => total + asset.size, 0)
};

dispatch(setAssets(assetData));
dispatch(stopLoader());
} catch (error) {
dispatch(
showToast(
getTransactionErrorMessage(error, 'Failed to load assets.'),
5000
)
);
dispatch({
type: ActionTypes.ERROR
});
Expand All @@ -28,14 +73,25 @@ export function getAssets() {
}

export function deleteAssetRequest(assetKey) {
return async (dispatch) => {
return async (dispatch, getState) => {
try {
const path = assetKey.split('/').pop();
await apiClient.delete(
`/S3/delete?objectKey=${encodeURIComponent(path)}`
const asset = getState().assets.list.find(
(item) => item.key === assetKey
);
if (!asset?.visualID) {
throw new Error('Only sketch files can be deleted.');
}
await opApiClient.delete(
`/sketch/${asset.visualID}/files/${encodeFilePath(asset.name)}`
);
dispatch(deleteAsset(assetKey));
} catch (error) {
dispatch(
showToast(
getTransactionErrorMessage(error, 'Failed to delete asset.'),
5000
)
);
dispatch({
type: ActionTypes.ERROR
});
Expand Down
Loading