Compare commits

...

43 commits

Author SHA1 Message Date
Ruben van de Ven
61fd46777c Update docker 2020-12-16 10:58:18 +01:00
Ruben van de Ven
6ee11bc076 Update docker 2020-12-16 08:49:12 +01:00
Ruben van de Ven
07c4f9e899 Info for installation 2020-11-02 15:11:27 +01:00
Ruben van de Ven
6f2095eede Add dp image, consisten punctuation in default files, and overview of all users under '/users' 2020-10-27 21:45:41 +01:00
Ruben van de Ven
4f0cb957a2 Preliminary 'flip' variable, but not really compatible with faceApi points, so disabled by default 2020-10-25 15:26:11 +01:00
Ruben van de Ven
7acb164f93 Fix invalid images.json 2020-10-25 14:14:00 +01:00
Ruben van de Ven
b2665fddf1 Change default files to reflect box removal and change canvas sizing 2020-10-25 13:01:15 +01:00
Ruben van de Ven
dc20a7238f Set content type of assets based on name 2020-10-25 12:56:07 +01:00
Ruben van de Ven
14a4425dc2 Changes for usability: less images, detection.box is deprecated, controls to show points and grid on all detections. Button to take screenshot. 2020-10-25 12:18:13 +01:00
Ruben van de Ven
fa5e7cceb1 Fix cloning when editing someone else's project 2020-10-12 17:25:26 +02:00
Ruben van de Ven
b231b6b7e3 Remove console.logs 2020-10-10 20:37:59 +02:00
Ruben van de Ven
f181bc6cb7 Fix save on pressing play with new project -- show save button first time around (as isPlaying get reset when URL is change on save) 2020-10-09 19:06:19 +02:00
Ruben van de Ven
c1810ed47f loadImages in preload of basic images 2020-10-09 18:20:01 +02:00
Ruben van de Ven
bc83555cab fix crash on save-and-play when not logged in 2020-10-09 17:21:16 +02:00
Ruben van de Ven
8ebe30af90 fix console log 2020-09-28 13:25:45 +02:00
Ruben van de Ven
9e90aada94 test frame chaching 2020-09-28 13:18:32 +02:00
Ruben van de Ven
f1a60d4dee improve error handling and logging 2020-09-28 12:17:32 +02:00
Ruben van de Ven
27b4eea796 improve error handling and logging 2020-09-28 12:05:16 +02:00
Ruben van de Ven
cebfae6430 improve error handling and logging 2020-09-28 11:29:45 +02:00
Ruben van de Ven
a6594f142b improve error handling and logging 2020-09-28 11:27:03 +02:00
Ruben van de Ven
aa2d324cab improve error handling and logging 2020-09-28 11:21:00 +02:00
Ruben van de Ven
a3c41c785d Default files for webcam 2020-09-28 11:12:08 +02:00
Ruben van de Ven
d437cde6da Default files for webcam and attempt for error log 2020-09-28 11:04:10 +02:00
Ruben van de Ven
8247385779 DP favicon 2020-09-28 10:41:24 +02:00
Ruben van de Ven
af1dfe1a0b DP favicon 2020-09-28 10:21:39 +02:00
Ruben van de Ven
3c1760c3f6 fix flipped rotation 2020-09-25 19:08:54 +02:00
Ruben van de Ven
baf1e0bc03 fix error with transformDetection() call 2020-09-25 18:58:23 +02:00
Ruben van de Ven
d163e2a83a fix error with transformDetection() call 2020-09-25 18:57:37 +02:00
Ruben van de Ven
c1f22570e8 fix error with transformDetection() call 2020-09-25 18:56:29 +02:00
Ruben van de Ven
d589e37a4e transition to drawMask() for more easy rotated drawing of face points 2020-09-25 18:53:19 +02:00
Ruben van de Ven
f7a54b2e17 setupAssets to load assets after setup() 2020-09-25 16:20:30 +02:00
Ruben van de Ven
fce40f28bb update webcam.js 2020-09-25 13:19:01 +02:00
Ruben van de Ven
37d60fd576 Add center to detection 2020-09-22 09:39:59 +02:00
Ruben van de Ven
cef703104b landmarks renamed to points 2020-09-22 09:20:45 +02:00
Ruben van de Ven
058a656d13 Some more helper functions 2020-09-22 09:11:45 +02:00
Ruben van de Ven
377c60ac92 Some helper function for drawing 2020-09-22 08:26:54 +02:00
Ruben van de Ven
10ed1de810 fixes to draw the face canvas 2020-09-21 14:57:54 +02:00
Ruben van de Ven
793d7e6521 WIP live reloading editor 2020-09-21 12:22:19 +02:00
Ruben van de Ven
21a5d698b1 DP logo and colours 2020-09-09 12:13:51 +02:00
Ruben van de Ven
a548e6937c Nav supports EXAMPLE_USERNAME (part of code only, rest in server/views/index.js) 2020-09-09 11:54:03 +02:00
Ruben van de Ven
dc5fba2e5a fix origin glitch 2020-09-07 15:00:21 +02:00
Ruben van de Ven
8d2c3601d5 add assets 2020-09-07 14:31:01 +02:00
Ruben van de Ven
bd8391bcf4 WIP to run without mailgun and social logins 2020-09-07 13:59:15 +02:00
79 changed files with 127336 additions and 213 deletions

View file

@ -19,12 +19,13 @@ CMD ["npm", "start"]
FROM development as build
ENV NODE_ENV production
RUN npm run build
RUN npm run build:server
RUN npm run build:client
FROM base as production
FROM build as production
ENV NODE_ENV=production
COPY package.json package-lock.json index.js ./
RUN npm install --production
RUN npm rebuild node-sass
COPY --from=build $APP_HOME/dist ./dist
# COPY --from=build $APP_HOME/dist ./dist
CMD ["npm", "run", "start:prod"]

31
README.ruben.md Normal file
View file

@ -0,0 +1,31 @@
# Usage
There is a hidden link to have an overview of all users registered on the server: `/users`.
# Development
`docker-compose -f docker-compose-development.yml up`
# Deployment
* Push to git (in case linting fails on commit use `git commit --no-verify`)
* Pull on server.
* `service p5.js-web-editor stop`
* `npm run build:server`
* `npm run build:client`
* `service p5.js-web-editor start`
Gebruik s3cmd om bucket in te stellen.
## Make bucket:
s3cmd mb s3://digitalplayground-p5
## Set the CORS rules
s3cmd -c .s3cfg setcors CORS.xml s3://digitalplayground-p5
## Delete the CORS rules
s3cmd -c .s3cfg delcors s3://digitalplayground-p5
## Get bucket info including CORS rules
s3cmd -c .s3cfg info s3://digitalplayground-p5

View file

@ -38,6 +38,10 @@
"description": "A secret key for...? Not sure where used.",
"generator": "secret"
},
"EXAMPLE_USERNAME": {
"description": "Username of the default account.",
"value": "p5"
},
"EXAMPLE_USER_EMAIL": {
"description": "The email address for the account holding the default Example sketches",
"value": "examples@p5js.org"

View file

@ -17,7 +17,8 @@ import { metaKeyName, } from '../utils/metaKey';
import CaretLeftIcon from '../images/left-arrow.svg';
import TriangleIcon from '../images/down-filled-triangle.svg';
import LogoIcon from '../images/p5js-logo-small.svg';
// import LogoIcon from '../images/p5js-logo-small.svg';
import LogoIcon from '../images/dp-logo-line-drawing.svg';
class Nav extends React.PureComponent {
constructor(props) {
@ -351,7 +352,7 @@ class Nav extends React.PureComponent {
{ getConfig('EXAMPLES_ENABLED') &&
<li className="nav__dropdown-item">
<Link
to="/p5/sketches"
to={`/${getConfig('EXAMPLE_USERNAME')}/sketches`}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -127,6 +127,7 @@ function getSynchedProject(currentState, responseProject) {
}
export function saveProject(selectedFile = null, autosave = false, mobile = false) {
console.trace('saving inproject.js');
return (dispatch, getState) => {
const state = getState();
if (state.project.isSaving) {
@ -261,7 +262,7 @@ function generateNewIdsForChildren(file, files) {
export function cloneProject(id) {
return (dispatch, getState) => {
dispatch(setUnsavedChanges(false));
new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
if (!id) {
resolve(getState());
} else {

View file

@ -326,6 +326,12 @@ class Editor extends React.Component {
'editor-holder--hidden': this.props.file.fileType === 'folder' || this.props.file.url
});
let preview = '';
if (this.props.file.fileType === 'file' && this.props.file.url) {
// TODO check if it's an image
preview = (<div><img src={this.props.file.url} alt="preview" /></div>);
}
return (
<section className={editorSectionClass} >
<header className="editor__header">
@ -360,6 +366,7 @@ class Editor extends React.Component {
</header>
<article ref={(element) => { this.codemirrorContainer = element; }} className={editorHolderClass} >
</article>
{preview}
<EditorAccessibility
lintMessages={this.props.lintMessages}
/>

View file

@ -23,6 +23,8 @@ import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
from '../../../utils/consoleUtils';
// let lastUpdate = null;
const shouldRenderSketch = (props, prevProps = undefined) => {
const { isPlaying, previewIsRefreshing, fullView } = props;
@ -57,6 +59,9 @@ class PreviewFrame extends React.Component {
}
componentDidUpdate(prevProps) {
if (prevProps.isPlaying === true && this.props.isPlaying === false) {
console.trace('update', this.props, prevProps);
}
if (shouldRenderSketch(this.props, prevProps)) this.renderSketch();
// small bug - if autorefresh is on, and the usr changes files
// in the sketch, preview will reload
@ -327,16 +332,91 @@ class PreviewFrame extends React.Component {
renderSketch() {
const doc = this.iframeElement;
const localFiles = this.injectLocalFiles();
// const localFiles = this.injectLocalFiles();
// console.trace('renderSketch()', this.props);
const changedFiles = this.props.files.filter(file => file.changed);
const filesRequiringReload = changedFiles.filter(file => !file.content.startsWith('// liveUpdate'));
const filesToHotSwap = changedFiles.filter(file => filesRequiringReload.indexOf(file) === -1);
// console.log('changed and requiring reload:', changedFiles, filesRequiringReload, filesToHotSwap);
let saving;
console.log('is saving...', this.props);
if (changedFiles.length > 0 || this.props.project.updatedAt === '') {
saving = this.props.saveProject();
} else {
// can be done pretier: a promise that always resolves
saving = new Promise((resolve, err) => resolve());
}
console.log('saving:', saving);
if (saving === null) {
console.log('Error saving... not authenticated?');
this.props.stopSketch(); // prevent crazy loop of renderSketch() through componentDidUpdate()
} else {
saving.catch(() => {
console.log('Error when saving... not authenticated? Redirect!');
this.props.stopSketch(); // prevent crazy loop of renderSketch() through componentDidUpdate()
// this.props.clearConsole(); // introduces bug: causes nested compnonentDidUpdate()
});
saving.then(() => {
console.log('play!', this.props.isPlaying);
if (this.props.isPlaying) {
this.props.clearConsole();
srcDoc.set(doc, localFiles);
doc.removeAttribute('srcdoc');
if (typeof this.props.project.owner === 'undefined') {
console.error('no owner for project?', this.props.project);
} else {
const source = `${window.location.origin}/${this.props.project.owner.username}/sketches/${this.props.project.id}/index.html`;
// console.log('FILES', this.props.files, doc.src, source, lastUpdate);
if (doc.src === source) {
// const newFiles = this.props.files.filter(file => new Date(file.updatedAt) > lastUpdate);
if (this.props.unsavedChanges) {
// console.log('unsaved changes');
}
// console.log('doc', doc);
// we need a hard reload
if (filesRequiringReload.length > 0) {
doc.src = source; // for now...
} else {
// if (doc.contentWindow.document.querySelector('script[src="/assets/hotswap.js"]') == null) {
// const headEl = doc.contentWindow.document.querySelector('head');
// const srcEl = doc.contentWindow.document.createElement('script');
// srcEl.src = '/assets/hotswap.js';
// headEl.appendChild(srcEl);
// }
console.log('Hot swap (..append):', filesToHotSwap);
const headEl = doc.contentWindow.document.querySelector('head');
const updatevar = Date.now();
filesToHotSwap.forEach((file) => {
// doc.contentWindow.postMessage({ 'action': 'code', 'contents': file.content }, '*');
const srcEl = doc.contentWindow.document.createElement('script');
srcEl.src = `${file.name}?changed=${updatevar}`;
srcEl.onload = 'setupAssets();'; // (re)load assets
headEl.appendChild(srcEl);
});
}
// if ( this.props.htmlFile.content === doc.contentWindow)
// TODO: don't set, but update only (will be hard... :-P)
} else {
doc.src = source;
}
}
} else {
doc.removeAttribute('src');
doc.srcdoc = '';
srcDoc.set(doc, ' ');
}
// prevent looping
if (this.props.endSketchRefresh) {
this.props.endSketchRefresh();
}
} else {
doc.srcdoc = '';
srcDoc.set(doc, ' ');
});
}
}
@ -384,7 +464,18 @@ PreviewFrame.propTypes = {
setBlobUrl: PropTypes.func.isRequired,
stopSketch: PropTypes.func.isRequired,
expandConsole: PropTypes.func.isRequired,
clearConsole: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
project: PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string.isRequired,
owner: PropTypes.shape({
username: PropTypes.string,
id: PropTypes.string,
}),
updatedAt: PropTypes.string,
}).isRequired,
// clearConsole: PropTypes.func,
unsavedChanges: PropTypes.bool,
cmController: PropTypes.shape({
getContent: PropTypes.func
}),
@ -392,7 +483,8 @@ PreviewFrame.propTypes = {
PreviewFrame.defaultProps = {
fullView: false,
cmController: {}
unsavedChanges: false,
cmController: {},
};
export default PreviewFrame;

View file

@ -58,9 +58,14 @@ class Toolbar extends React.Component {
}
render() {
const canEditProjectName = this.canEditProjectName();
const playButtonClass = classNames({
'toolbar__play-button': true,
'toolbar__play-button--selected': this.props.isPlaying
'toolbar__play-button--selected': this.props.isPlaying,
'toolbar__play-button--saved': this.props.isSaved,
'toolbar__play-button--unsaved': !this.props.isSaved,
'toolbar__play-button--clone': this.props.unsavedChanges && !canEditProjectName
});
const stopButtonClass = classNames({
'toolbar__stop-button': true,
@ -75,8 +80,6 @@ class Toolbar extends React.Component {
'toolbar__project-name-container--editing': this.props.project.isEditingName
});
const canEditProjectName = this.canEditProjectName();
return (
<div className="toolbar">
<button
@ -176,6 +179,8 @@ class Toolbar extends React.Component {
Toolbar.propTypes = {
isPlaying: PropTypes.bool.isRequired,
unsavedChanges: PropTypes.bool.isRequired,
isSaved: PropTypes.bool.isRequired,
preferencesIsVisible: PropTypes.bool.isRequired,
stopSketch: PropTypes.func.isRequired,
setProjectName: PropTypes.func.isRequired,
@ -215,8 +220,10 @@ function mapStateToProps(state) {
infiniteLoop: state.ide.infiniteLoop,
isPlaying: state.ide.isPlaying,
owner: state.project.owner,
isSaved: state.project.updatedAt !== '',
preferencesIsVisible: state.ide.preferencesIsVisible,
project: state.project,
unsavedChanges: state.ide.unsavedChanges,
};
}

View file

@ -173,6 +173,23 @@ class IDEView extends React.Component {
clearTimeout(this.autosaveInterval);
this.autosaveInterval = null;
}
saveProject() {
console.trace('saving!');
// return a Promise to save or null
if (
isUserOwner(this.props) ||
(this.props.user.authenticated && !this.props.project.owner)
) {
console.trace('project to save:', this.props.project);
return this.props.saveProject(this.cmController.getContent());
} else if (this.props.user.authenticated) {
console.log('cloning!');
return this.props.cloneProject();
}
this.props.showErrorModal('forceAuthentication');
return null;
}
handleGlobalKeydown(e) {
// 83 === s
if (
@ -181,16 +198,7 @@ class IDEView extends React.Component {
) {
e.preventDefault();
e.stopPropagation();
if (
isUserOwner(this.props) ||
(this.props.user.authenticated && !this.props.project.owner)
) {
this.props.saveProject(this.cmController.getContent());
} else if (this.props.user.authenticated) {
this.props.cloneProject();
} else {
this.props.showErrorModal('forceAuthentication');
}
this.saveProject();
// 13 === enter
} else if (
e.keyCode === 13 &&
@ -387,8 +395,11 @@ class IDEView extends React.Component {
setBlobUrl={this.props.setBlobUrl}
expandConsole={this.props.expandConsole}
clearConsole={this.props.clearConsole}
saveProject={this.saveProject.bind(this)}
project={this.props.project}
cmController={this.cmController}
language={this.props.preferences.language}
unsavedChanges={this.props.ide.unsavedChanges}
/>
</div>
</section>

View file

@ -1,20 +1,22 @@
import objectID from 'bson-objectid';
import * as ActionTypes from '../../../constants';
const defaultSketch = `function setup() {
createCanvas(400, 400);
}
const defaultSketch = `// liveUpdate
function draw() {
background(220);
function drawMask(detection) {
stroke('red')
strokeWeight(2)
rect(detection.x, detection.y, detection.width, detection.height)
}`;
const defaultHTML =
`<!DOCTYPE html>
<html lang="en">
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/addons/p5.sound.min.js"></script>
<script src="/assets/p5.js"></script>
<script src="/assets/ml5.js"></script>
<script src="/previewScripts.js"></script>
<script src="/assets/webcam.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
@ -32,6 +34,8 @@ const defaultCSS =
}
canvas {
display: block;
width: 100% !important;
height: auto !important;
}
`;
@ -147,8 +151,21 @@ const files = (state, action) => {
return file;
}
return Object.assign({}, file, { content: action.content });
return Object.assign({}, file, { content: action.content, changed: true });
});
case ActionTypes.SET_UNSAVED_CHANGES:
if (action.value) {
// ignore.
return state;
}
return state.map((file) => {
if (file.changed) {
return Object.assign({}, file, { changed: false });
}
return file;
});
// return Object.assign({}, state, { unsavedChanges: action.value });
case ActionTypes.SET_BLOB_URL:
return state.map((file) => {
if (file.id !== action.id) {
@ -173,7 +190,8 @@ const files = (state, action) => {
content: action.content,
url: action.url,
children: action.children,
fileType: action.fileType || 'file'
fileType: action.fileType || 'file',
changed: false,
}];
return newState.map((file) => {
if (file.id === action.parentId) {

View file

@ -22,7 +22,7 @@ import FooterTabSwitcher from '../../components/mobile/TabSwitcher';
import FooterTab from '../../components/mobile/Tab';
import Loader from '../App/components/loader';
const EXAMPLE_USERNAME = 'p5';
const EXAMPLE_USERNAME = process.env.EXAMPLE_USERNAME || 'p5';
// @ghalestrilo 08/13/2020: I'm sorry
const ContentWrapper = styled(Content)`

View file

@ -60,7 +60,7 @@ class AccountView extends React.Component {
</div>
</TabList>
<TabPanel>
<SocialLoginPanel {...this.props} />
{/* <SocialLoginPanel {...this.props} /> */}
</TabPanel>
<TabPanel>
<APIKeyForm {...this.props} />

View file

@ -42,11 +42,11 @@ class LoginView extends React.Component {
<div className="form-container__content">
<h2 className="form-container__title">{this.props.t('LoginView.Login')}</h2>
<LoginForm {...this.props} />
<h2 className="form-container__divider">{this.props.t('LoginView.LoginOr')}</h2>
{/* <h2 className="form-container__divider">{this.props.t('LoginView.LoginOr')}</h2>
<div className="form-container__stack">
<SocialAuthButton service={SocialAuthButton.services.github} />
<SocialAuthButton service={SocialAuthButton.services.google} />
</div>
</div> */}
<p className="form__navigation-options">
{this.props.t('LoginView.DontHaveAccount')}
<Link className="form__signup-button" to="/signup">{this.props.t('LoginView.SignUp')}</Link>

View file

@ -34,11 +34,11 @@ class SignupView extends React.Component {
<div className="form-container__content">
<h2 className="form-container__title">{this.props.t('SignupView.Description')}</h2>
<SignupForm {...this.props} />
<h2 className="form-container__divider">{this.props.t('SignupView.Or')}</h2>
{/* <h2 className="form-container__divider">{this.props.t('SignupView.Or')}</h2>
<div className="form-container__stack">
<SocialAuthButton service={SocialAuthButton.services.github} />
<SocialAuthButton service={SocialAuthButton.services.google} />
</div>
</div> */}
<p className="form__navigation-options">
{this.props.t('SignupView.AlreadyHave')}
<Link className="form__login-button" to="/login">{this.props.t('SignupView.Login')}</Link>

View file

@ -1,7 +1,7 @@
$base-font-size: 12;
//colors
$p5js-pink: #ed225d;
$p5js-pink: #FFE117; /*DP Colours*/
$processing-blue: #007BBB;
$p5js-active-pink: #f10046;
$white: #fff;

View file

@ -166,12 +166,23 @@
.nav__item-logo {
position: relative;
height: #{42 / $base-font-size}rem;
height: #{(42 - 1) / $base-font-size}rem;
width: #{56 / $base-font-size}rem;
background:$p5js-pink;
& span {
position: absolute;
}
svg{
width: 100%;
height: 90%;
padding-top: 5%;
path{
stroke: $dark;
}
}
}
.svg__logo g > path {
@include themify() {

View file

@ -1,4 +1,16 @@
.toolbar__play-button {
&--unsaved, &--clone {
// for some reason, I cannot manage to have PreviewFrame save _and_ actuall yload the project (it stops the playback)
// pressing twice works. So just show a floppy the first time around :-)
svg {
display: none;
}
&::after{
content: '\1F4BE'; // save floppy
}
// background-color: blue !important;
}
@include themify() {
@extend %toolbar-button;
display: flex;

View file

@ -7,7 +7,7 @@ export const Theme = {
};
export const colors = {
p5jsPink: '#ed225d',
p5jsPink: '#FFE117', // DP Colours
processingBlue: '#007BBB',
p5jsActivePink: '#f10046',
white: '#fff',

View file

@ -7,30 +7,29 @@ Follow these instructions to set up your development environment, which you need
_Note_: The installation steps assume you are using a Unix-like shell. If you are using Windows, you will need to use `copy` instead of `cp`.
1. Install Node.js. The recommended way is to Node through [nvm](https://github.com/nvm-sh/nvm). You can also install [node.js](https://nodejs.org/download/release/v12.16.1/) version 12.16.1 directly from the Node.js website.
2. [Fork](https://help.github.com/articles/fork-a-repo) the [p5.js Web Editor repository](https://github.com/processing/p5.js-web-editor) into your own GitHub account.
3. [Clone](https://help.github.com/articles/cloning-a-repository/) your new fork of the repository from GitHub onto your local computer.
3. [Clone](https://help.github.com/articles/cloning-a-repository/) this repository using git onto your local computer.
```
$ git clone https://github.com/YOUR_USERNAME/p5.js-web-editor.git
$ git clone https://git.rubenvandeven.com/r/p5.js-web-editor/
```
4. If you are using nvm, run `$ nvm use` to set your Node version to 12.16.1
5. Navigate into the project folder and install all its necessary dependencies with npm.
```
$ cd p5.js-web-editor
$ npm install
```
6. Install MongoDB and make sure it is running
* For Mac OSX with [homebrew](http://brew.sh/): `brew tap mongodb/brew` then `brew install mongodb-community` and finally start the server with `brew services start mongodb-community` or you can visit the installation guide here [Installation Guide For MacOS](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/)
* For Windows and Linux: [MongoDB Installation](https://docs.mongodb.com/manual/installation/)
7. `$ cp .env.example .env`
8. (Optional) Update `.env` with necessary keys to enable certain app behaviors, i.e. add Github ID and Github Secret if you want to be able to log in with Github.
9. `$ npm run fetch-examples` - this downloads the example sketches into a user called 'p5'
10. `$ npm start`
11. Navigate to [http://localhost:8000](http://localhost:8000) in your browser
12. Install the [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
13. Open and close the Redux DevTools using `ctrl+h`, and move them with `ctrl+w`
8. (For DigitalPlayground) use provided .env file
9. Run `npm run build:server && npm run build:client` to make sure all script are built and bundled.
11. use the provided service file to start the server through systemd
- edit `p5.js-web-editor.service` so it has the right files & folders according to you installation
- `cp /home/dp/p5.js-web-editor/p5.js-web-editor.service /etc/systemd/system/`
- `systemctl deamon-reload`
- `systemctl start p5.js-web-editor.service`
12. (Optionally) Set the `PORT` and `NODE_PORT` variables in the p5.js-web-editor.service and .env files to the desired port OR use an apache server (or something else) as https-proxy server. For an example see `digitalplayground-apache.conf`.
## Docker Installation

View file

@ -0,0 +1,26 @@
# Forward to IPv6-only host
<VirtualHost *:80>
Servername dp.rubenvandeven.com
RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
Servername dp.rubenvandeven.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://digitalplayground.rubenvandeven.com:8000/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://digitalplayground.rubenvandeven.com:8000/$1 [P,L]
ProxyPass / http://digitalplayground.rubenvandeven.com:8000/
ProxyPassReverse / http://digitalplayground.rubenvandeven.com:8000/
ProxyPreserveHost On
SSLCertificateFile /etc/letsencrypt/live/dp.rubenvandeven.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/dp.rubenvandeven.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

1
dist/static/assets/faceapi/README vendored Normal file
View file

@ -0,0 +1 @@
Model weights from https://github.com/justadudewhohacks/face-api.js

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1 @@
[{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008194216092427571,"min":-0.9423348506291708}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006839508168837603,"min":-0.8412595047670252}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009194007106855804,"min":-1.2779669878529567}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0036026100317637128,"min":-0.3170296827952067}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.000740380117706224,"min":-0.06367269012273527}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037702228508743585,"min":-0.6220867703942692}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0033707996209462483,"min":-0.421349952618281}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014611541991140328,"min":-1.8556658328748217}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002832523046755323,"min":-0.30307996600281956}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006593170586754294,"min":-0.6329443763284123}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.012215249211180444,"min":-1.6001976466646382}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002384825547536214,"min":-0.3028728445370992}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005859645441466687,"min":-0.7617539073906693}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013121426806730382,"min":-1.7845140457153321}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032247188044529336,"min":-0.46435950784122243}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002659512618008782,"min":-0.32977956463308894}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015499923743453681,"min":-1.9839902391620712}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032450980999890497,"min":-0.522460794098237}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005911862382701799,"min":-0.792189559282041}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021025861478319356,"min":-2.2077154552235325}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00349616945958605,"min":-0.46149436866535865}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008104994250278847,"min":-1.013124281284856}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.029337059282789044,"min":-3.5791212325002633}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0038808938334969913,"min":-0.4230174278511721}},{"name":"fc/weights","shape":[128,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014016061670639936,"min":-1.8921683255363912}},{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029505149698724935,"min":0.088760145008564}}],"paths":["face_landmark_68_tiny_model-shard1"]}]

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1 @@
[{"paths":["mtcnn_model-shard1"],"weights":[{"dtype":"float32","name":"pnet/conv1/weights","shape":[3,3,3,10]},{"dtype":"float32","name":"pnet/conv1/bias","shape":[10]},{"dtype":"float32","name":"pnet/prelu1_alpha","shape":[10]},{"dtype":"float32","name":"pnet/conv2/weights","shape":[3,3,10,16]},{"dtype":"float32","name":"pnet/conv2/bias","shape":[16]},{"dtype":"float32","name":"pnet/prelu2_alpha","shape":[16]},{"dtype":"float32","name":"pnet/conv3/weights","shape":[3,3,16,32]},{"dtype":"float32","name":"pnet/conv3/bias","shape":[32]},{"dtype":"float32","name":"pnet/prelu3_alpha","shape":[32]},{"dtype":"float32","name":"pnet/conv4_1/weights","shape":[1,1,32,2]},{"dtype":"float32","name":"pnet/conv4_1/bias","shape":[2]},{"dtype":"float32","name":"pnet/conv4_2/weights","shape":[1,1,32,4]},{"dtype":"float32","name":"pnet/conv4_2/bias","shape":[4]},{"dtype":"float32","name":"rnet/conv1/weights","shape":[3,3,3,28]},{"dtype":"float32","name":"rnet/conv1/bias","shape":[28]},{"dtype":"float32","name":"rnet/prelu1_alpha","shape":[28]},{"dtype":"float32","name":"rnet/conv2/weights","shape":[3,3,28,48]},{"dtype":"float32","name":"rnet/conv2/bias","shape":[48]},{"dtype":"float32","name":"rnet/prelu2_alpha","shape":[48]},{"dtype":"float32","name":"rnet/conv3/weights","shape":[2,2,48,64]},{"dtype":"float32","name":"rnet/conv3/bias","shape":[64]},{"dtype":"float32","name":"rnet/prelu3_alpha","shape":[64]},{"dtype":"float32","name":"rnet/fc1/weights","shape":[576,128]},{"dtype":"float32","name":"rnet/fc1/bias","shape":[128]},{"dtype":"float32","name":"rnet/prelu4_alpha","shape":[128]},{"dtype":"float32","name":"rnet/fc2_1/weights","shape":[128,2]},{"dtype":"float32","name":"rnet/fc2_1/bias","shape":[2]},{"dtype":"float32","name":"rnet/fc2_2/weights","shape":[128,4]},{"dtype":"float32","name":"rnet/fc2_2/bias","shape":[4]},{"dtype":"float32","name":"onet/conv1/weights","shape":[3,3,3,32]},{"dtype":"float32","name":"onet/conv1/bias","shape":[32]},{"dtype":"float32","name":"onet/prelu1_alpha","shape":[32]},{"dtype":"float32","name":"onet/conv2/weights","shape":[3,3,32,64]},{"dtype":"float32","name":"onet/conv2/bias","shape":[64]},{"dtype":"float32","name":"onet/prelu2_alpha","shape":[64]},{"dtype":"float32","name":"onet/conv3/weights","shape":[3,3,64,64]},{"dtype":"float32","name":"onet/conv3/bias","shape":[64]},{"dtype":"float32","name":"onet/prelu3_alpha","shape":[64]},{"dtype":"float32","name":"onet/conv4/weights","shape":[2,2,64,128]},{"dtype":"float32","name":"onet/conv4/bias","shape":[128]},{"dtype":"float32","name":"onet/prelu4_alpha","shape":[128]},{"dtype":"float32","name":"onet/fc1/weights","shape":[1152,256]},{"dtype":"float32","name":"onet/fc1/bias","shape":[256]},{"dtype":"float32","name":"onet/prelu5_alpha","shape":[256]},{"dtype":"float32","name":"onet/fc2_1/weights","shape":[256,2]},{"dtype":"float32","name":"onet/fc2_1/bias","shape":[2]},{"dtype":"float32","name":"onet/fc2_2/weights","shape":[256,4]},{"dtype":"float32","name":"onet/fc2_2/bias","shape":[4]},{"dtype":"float32","name":"onet/fc2_3/weights","shape":[256,10]},{"dtype":"float32","name":"onet/fc2_3/bias","shape":[10]}]}]

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1 @@
[{"weights":[{"name":"conv0/filters","shape":[3,3,3,16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009007044399485869,"min":-1.2069439495311063}},{"name":"conv0/bias","shape":[16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005263455241334205,"min":-0.9211046672334858}},{"name":"conv1/depthwise_filter","shape":[3,3,16,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004001977630690033,"min":-0.5042491814669441}},{"name":"conv1/pointwise_filter","shape":[1,1,16,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013836609615999109,"min":-1.411334180831909}},{"name":"conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0015159862590771096,"min":-0.30926119685173037}},{"name":"conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002666276225856706,"min":-0.317286870876948}},{"name":"conv2/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015265831292844286,"min":-1.6792414422128714}},{"name":"conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0020280554598453,"min":-0.37113414915168985}},{"name":"conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006100742489683862,"min":-0.8907084034938438}},{"name":"conv3/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016276211832083907,"min":-2.0508026908425725}},{"name":"conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394414279975143,"min":-0.7637432129944072}},{"name":"conv4/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006716050119961009,"min":-0.8059260143953211}},{"name":"conv4/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021875603993733724,"min":-2.8875797271728514}},{"name":"conv4/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0041141652009066415,"min":-0.8187188749804216}},{"name":"conv5/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008423839597141042,"min":-0.9013508368940915}},{"name":"conv5/pointwise_filter","shape":[1,1,256,512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.030007277283014035,"min":-3.8709387695088107}},{"name":"conv5/bias","shape":[512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008402082966823203,"min":-1.4871686851277068}},{"name":"conv8/filters","shape":[1,1,512,25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.028336129469030042,"min":-4.675461362389957}},{"name":"conv8/bias","shape":[25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002268134028303857,"min":-0.41053225912299807}}],"paths":["tiny_face_detector_model-shard1"]}]

BIN
dist/static/assets/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

25
dist/static/assets/images.json vendored Normal file
View file

@ -0,0 +1,25 @@
{
"game1": "/assets/images/game1.png",
"game10": "/assets/images/game10.png",
"game11": "/assets/images/game11.png",
"game12": "/assets/images/game12.png",
"game2": "/assets/images/game2.png",
"game3": "/assets/images/game3.png",
"game4": "/assets/images/game4.png",
"game5": "/assets/images/game5.png",
"game6": "/assets/images/game6.png",
"game7": "/assets/images/game7.png",
"game8": "/assets/images/game8.png",
"game9": "/assets/images/game9.png",
"glasses1": "/assets/images/glasses1.png",
"glasses2": "/assets/images/glasses2.png",
"glasses3": "/assets/images/glasses3.png",
"hat1": "/assets/images/hat1.png",
"hat2": "/assets/images/hat2.png",
"mustache1": "/assets/images/mustache1.png",
"mustache2": "/assets/images/mustache2.png",
"mustache3": "/assets/images/mustache3.png",
"mustache4": "/assets/images/mustache4.png",
"mustache5": "/assets/images/mustache5.png",
"dp": "/assets/images/dp.png"
}

BIN
dist/static/assets/images/dp.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
dist/static/assets/images/game1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
dist/static/assets/images/game10.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
dist/static/assets/images/game11.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
dist/static/assets/images/game12.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
dist/static/assets/images/game2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
dist/static/assets/images/game3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
dist/static/assets/images/game4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
dist/static/assets/images/game5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
dist/static/assets/images/game6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
dist/static/assets/images/game7.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
dist/static/assets/images/game8.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
dist/static/assets/images/game9.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
dist/static/assets/images/glasses1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
dist/static/assets/images/glasses2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
dist/static/assets/images/glasses3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
dist/static/assets/images/hat1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
dist/static/assets/images/hat2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dist/static/assets/images/mustache1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

BIN
dist/static/assets/images/mustache2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
dist/static/assets/images/mustache3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
dist/static/assets/images/mustache4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
dist/static/assets/images/mustache5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

125828
dist/static/assets/ml5.js vendored Normal file

File diff suppressed because one or more lines are too long

81
dist/static/assets/opencv.js vendored Normal file

File diff suppressed because one or more lines are too long

595
dist/static/assets/webcam.js vendored Normal file
View file

@ -0,0 +1,595 @@
// console.log('p5 version:', p5);
// console.log('ml5 version:', ml5);
// console.log(location.origin);
let assets = {};
var draw = function () {
// Begin met het tekenen van de video
// plaats hem op position x = 0, y = 0.
// vul de hele breedte en hoogte
image(lastFrame, 0,0, width, height);
for(let detection of detections) {
push();
let transformed = transformDetection(detection);
translate(transformed.origin.x, transformed.origin.y);
rotate(transformed.angle);
try {
if( overlayGrid ) {
drawGridOverlay(transformed)
}
if( overlayLandmarks ) {
drawLandmarkOverlay(transformed);
}
drawMask(transformed);
} catch (error) {
console.error(error);
}
pop();
}
};
function drawLandmarkOverlay(detection) {
for(let nr in detection.points) {
p = detection.points[nr]
stroke('red')
strokeWeight(5)
point(p.x, p.y)
noStroke();
textSize(12);
fill('white');
text(nr, p.x, p.y);
}
noFill();
}
function drawGridOverlay(detection) {
textSize(20)
stroke(100,100,100)
strokeWeight(1)
for (let y = 0; y < detection.height; y+=10) {
if(y%100 === 0) {
strokeWeight(3)
text(y, detection.x - 10, y);
} else {
strokeWeight(1)
}
line(detection.x, detection.y + y, detection.x+detection.width, detection.y + y);
}
for (let x = 0; x < detection.width; x+=10) {
if(x != 0 && x%100 === 0) { // 0 already drawn for y
strokeWeight(3)
text(x, x, detection.y - 10);
} else {
strokeWeight(1)
}
line(detection.x + x, detection.y, detection.x + x, detection.y + detection.height);
}
}
var drawMask = function(detection) {
};
// var gotResults = function(err, result) {
// if (err) {
// console.log(err)
// return
// }
// };
// function code_error(type, error) {
// window.parent.postMessage({
// 'type': type,
// 'error': error.message,
// 'name': error.name,
// 'line': error.lineNumber - 2, // seems it giveswrong line numbers
// 'column': error.columnNumber
// }, '*');
// }
// function no_code_error(type){
// window.parent.postMessage({
// 'type': type,
// 'error': null
// }, '*');
// }
// window.addEventListener("message", function (e) {
// if (event.origin !== window.location.origin) {
// console.error("Invalid origin of message. Ignored");
// return;
// }
// console.debug("receive", e.data);
// switch (e.data.action) {
// case 'asset':
// if(e.data.content === null){
// delete assets[e.data.id];
// } else {
// assets[e.data.id] = loadImage(e.data.content);
// }
// break;
// case 'code':
// let f = new Function("");
// try {
// f = new Function(e.data.draw);
// no_code_error('syntax');
// } catch (error) {
// code_error('syntax', error);
// // window.parent.postMessage({'syntax': error.lineNumber});
// }
// handleResults = f;
// break;
// default:
// console.error("Invalid action", e.data.action);
// break;
// }
// });
let faceapi;
var video;
var lastFrame;
var frameToDetect;
var detections = [];
var factor_x, factor_y;
var flip = false; // mirror mode, disabled, because it's tricky to enable with faceApi
// function pause() {
// if (running)
// running = false;
// else {
// running = true;
// faceapi.detect(gotResults);
// }
// }
// by default all options are set to true
const detection_options = {
withLandmarks: true,
withDescriptors: false,
minConfidence: 0.5,
Mobilenetv1Model: window.parent.location.origin + '/assets/faceapi',
FaceLandmarkModel: window.parent.location.origin + '/assets/faceapi',
FaceLandmark68TinyNet: window.parent.location.origin + '/assets/faceapi',
FaceRecognitionModel: window.parent.location.origin + '/assets/faceapi',
TinyFaceDetectorModel: window.parent.location.origin + '/assets/faceapi',
}
function setupAssets(){
// placeholder. Override in patch...
}
let images = {};
function preload() {
const req = new Request('/assets/images.json');
fetch(req).then(
response => response.json()
).then(data => {
for(let id in data) {
images[id] = loadImage(data[id]);
}
// console.log('images', data, images);
});
// console.log(images);
}
var overlayLandmarks = false;
var overlayGrid = false;
function setup() {
// createCanvas(1280,720, WEBGL);
createCanvas(540,420);
smooth();
noFill();
push();
translate(-width/2, -height/2);
let constraints = {
video: {
width: { min: 720 },
height: { min: 540 }
},
audio: false
};
video = createCapture(constraints);
lastFrame = createGraphics(video.width, video.height);
frameToDetect = createGraphics(video.width, video.height);
// console.log(video);
// HeadGazeSetup(video);
// video.size(width, height);
video.hide(); // Hide the video element, and just show the canvas
faceapi = ml5.faceApi(video, detection_options, modelReady);
textAlign(RIGHT);
setupAssets();
let controlEl = document.createElement('div');
controlEl.classList.add('controls');
let label1El = document.createElement('label');
let check1El = document.createElement('input');
check1El.type = 'checkbox';
check1El.addEventListener('change', (e) => overlayLandmarks = e.target.checked);
let text1Node = document.createTextNode("Show points")
label1El.appendChild(check1El);
label1El.appendChild(text1Node);
controlEl.appendChild(label1El);
let label2El = document.createElement('label');
let check2El = document.createElement('input');
check2El.type = 'checkbox';
check2El.addEventListener('change', (e) => overlayGrid = e.target.checked);
let text2Node = document.createTextNode("Show coordinates")
label2El.appendChild(check2El);
label2El.appendChild(text2Node);
controlEl.appendChild(label2El);
let downloadBtn = document.createElement('button');
downloadBtn.innerHTML = 'screenshot';
downloadBtn.style.float = 'right';
// Convert canvas to image
downloadBtn.addEventListener("click", function(e) {
const canvas = document.querySelector('canvas');
const dataURL = canvas.toDataURL("image/png", 1.0);
let a = document.createElement('a');
a.href = dataURL;
a.download = 'screenshot.png';
document.body.appendChild(a);
a.click();
});
controlEl.appendChild(downloadBtn);
document.body.appendChild(controlEl);
}
function modelReady() {
frameToDetect.image(video, 0,0, video.width, video.height);
faceapi.detect(gotResults);
}
// var handleResults = function(){
// // background(parseInt(Math.random()*255),parseInt(Math.random()*255),parseInt(Math.random()*255));
// background((millis()/100)%255,0,0);
// image(video, -width/2 + 10, -height/2 + 10, width - 20, height -20);
// };
gotResults = function(err, result) {
if (err) {
console.error(err)
return
}
// store data for async draw function
// TODO results to more compatible format
// translate(width,0); // move to far corner
if (flip) {
lastFrame.push();
lastFrame.scale(-1.0,1.0); // flip x-axis backwards
lastFrame.image(frameToDetect, -lastFrame.width, 0, lastFrame.width, lastFrame.height);
lastFrame.pop();
} else {
lastFrame.image(frameToDetect, 0, 0, lastFrame.width, lastFrame.height);
}
detections = parseDetectionResults(result, flip, width);
// size of video becomes known only after camera approval
if(lastFrame.width != video.width || lastFrame.height != video.height){
// console.log('Resizing canvas');
lastFrame.resizeCanvas(video.width, video.height);
frameToDetect.resizeCanvas(video.width, video.height);
}
// lastFrame.background('red');
frameToDetect.image(video, 0,0, video.width, video.height);
factor_x = width / video.width;
factor_y = height / video.height;
faceapi.detect(gotResults);
}
function drawBox(detections) {
for (let i = 0; i < detections.length; i++) {
const alignedRect = detections[i].alignedRect;
const x = alignedRect._box._x
const y = alignedRect._box._y
const boxWidth = alignedRect._box._width
const boxHeight = alignedRect._box._height
noFill();
stroke(161, 95, 251);
strokeWeight(2);
rect(x, y, boxWidth, boxHeight);
}
}
function drawLandmarks(detection) {
// for (let i = 0; i < detections.length; i++) {
const mouth = detection.parts.mouth;
const nose = detection.parts.nose;
const leftEye = detection.parts.leftEye;
const rightEye = detection.parts.rightEye;
const rightEyeBrow = detection.parts.rightEyeBrow;
const leftEyeBrow = detection.parts.leftEyeBrow;
const jawOutline = detection.parts.jawOutline;
strokePoints(mouth, CLOSE);
strokePoints(nose, CLOSE);
strokePoints(leftEye, CLOSE);
strokePoints(leftEyeBrow, OPEN);
strokePoints(rightEye, CLOSE);
strokePoints(rightEyeBrow, OPEN);
strokePoints(jawOutline, OPEN);
// }
}
function strokePoints(points, closed) {
beginShape();
for (let i = 0; i < points.length; i++) {
const x = points[i].x;
const y = points[i].y;
vertex(x, y)
}
if(typeof closed === 'undefined') {
closed = CLOSE;
}
endShape(closed)
}
function drawPoints(points, radius) {
if(typeof radius === 'undefined') {
radius = 2;
}
for (let i = 0; i < points.length; i++) {
const x = points[i].x;
const y = points[i].y;
circle(x, y, radius);
}
}
function faceDistance(face1, face2){
// distance between faces, in pixels, not meters.. for now
// we cheat a little: take centers, visualise circle with r = max(width, height)
// and find distance between these circles
box1 = (face1.x, face1.x + face1.width)
box2 = (face2.x, face2.x + face2.width)
c1 = {
x: face1.x + face1.width / 2,
y: face1.y + face1.height / 2,
}
c2 = {
x: face2.x + face2.width / 2,
y: face2.y + face2.height / 2,
}
r1 = Math.max(face1.width, face1.height) / 2;
r2 = Math.max(face2.width, face2.height) / 2;
dx = c1.x - c2.x;
dy = c1.y - c2.y;
return Math.sqrt( Math.pow(dx, 2) + Math.pow(dy, 2) ) - r1 - r2;
}
function mergePoints() {
// a points should be {x: , y: }
// collect all points in the arguments:
let points = [];
for(let arg of arguments) {
if(Array.isArray(arg)) {
points.push(...arg);
} else {
points.push(arg);
}
}
return points;
}
function getBoundingBox(){
// arguments contains points, or sets of points. Find bbox
const points = mergePoints(...arguments);
const xs = points.map((point) => point.x);
const ys = points.map((point) => point.y);
const minx = Math.min(...xs);
const miny = Math.min(...ys);
return {
x: minx,
y: miny,
width: Math.max(...xs) - minx,
height: Math.max(...ys) - miny,
}
}
function parseDetectionResults(results, flip, frameWidth) {
let detections = [];
for(let result of results) {
const landmarks = result.landmarks._positions.map((pos) => parseCoordinate(pos, flip, frameWidth));
let x = result.alignedRect._box._x * factor_x;
if(flip) {
x *= -1;
x += frameWidth;
}
let detection = {
'points': landmarks,
// TODO: rotation
'parts': {},
x: x,
y: result.alignedRect._box._y * factor_y,
width: result.alignedRect._box._width * factor_x,
height: result.alignedRect._box._height * factor_y,
}
// for(let idx in result.parts) {
// detection.parts[idx] = result.parts[idx].map((pos) => parseCoordinate(pos));
// }
detection['center'] = {
x: detection.x + detection.width / 2,
y: detection.y + detection.height / 2,
}
detections.push(detection);
}
return detections;
}
/**
* face api detector returns coordinates with _x and _y attributes.
* We convert this to the canvas's coordinates
* @param Object {_x: , _y: }
*/
function parseCoordinate(position, flip, frameWidth) {
let x = position._x * factor_x;
if (flip) {
x *= -1;
x += frameWidth;
}
return {
x: x,
y: position._y * factor_y,
}
}
function transformDetection(original) {
const b = original.points[36]; // outer point on left eye
const a = original.points[45]; // outer point on right eye
const angle = atan2(a.y - b.y, a.x - b.x);
// let cx =a.x/2 + b.x/2
// let cy = a.y/2 + b.y/2
const cx = original.x
const cy = original.y
let detection = {
'points': original.points.map(p => transformPoint(p, cx, cy, angle)),
'origin': {x:cx, y:cy},
'angle': angle,
'original': original
}
const bbox = getBoundingBox(detection.points);
padding_x = bbox.width * .1;
padding_y = bbox.height * .1;
detection['x'] = bbox.x - padding_x,
detection['y'] = bbox.y - padding_y,
detection['width'] = bbox.width * 1.2,
detection['height'] = bbox.height * 1.2
// detection['x'] = original.x - cx
// detection['y'] = original.y - cy
// detection['width'] = original.width
// detection['height'] = original.height
return detection;
}
function transformPoint(p, cx, cy, angle) {
const px = p.x-cx;
const py = p.y-cy;
return {
x: px * cos(-angle) - py * sin(-angle),
y: px * sin(-angle) + py * cos(-angle)
}
}
// error handling from consoleUtils.js::hijackConsoleErrorsScript
function getScriptOff(line) {
var offs = 0;
var l = 0;
var file = '';
for (var i=0; i<offs.length; i++) {
var n = offs[i][0];
if (n < line && n > l) {
l = n;
file = offs[i][1];
}
}
return [line - l, file];
}
// catch reference errors, via http://stackoverflow.com/a/12747364/2994108
window.onerror = function (msg, url, lineNumber, columnNo, error) {
var string = msg.toLowerCase();
var substring = "script error";
var data = {};
// if (url.match(${EXTERNAL_LINK_REGEX}) !== null && error.stack){
// var errorNum = error.stack.split('about:srcdoc:')[1].split(':')[0];
// var fileInfo = getScriptOff(errorNum);
// data = msg + ' (' + fileInfo[1] + ': line ' + fileInfo[0] + ')';
// } else {
// var fileInfo = getScriptOff(lineNumber);
data = msg + ' (' + error.fileName + ': line ' + error.lineNumber + ')';
// }
window.parent.postMessage([{
log: [{
method: 'error',
data: [data],
id: Date.now().toString()
}],
source: error.fileName
}], '*');
return false;
};
// catch rejected promises
window.onunhandledrejection = function (event) {
if (event.reason && event.reason.message && event.reason.stack){
// var errorNum = event.reason.stack.split('about:srcdoc:')[1].split(':')[0];
// var fileInfo = getScriptOff(errorNum);
var data = event.reason.message + ' (' + event.reason.stack + ': line ' + event.reason.stack.split("\n")[0] + ')';
window.parent.postMessage([{
log: [{
method: 'error',
data: [data],
id: Date.now().toString()
}],
source: event.reason.stack.split("\n")[0]
}], '*');
}
};

157
doc/cheatsheet.md Normal file
View file

@ -0,0 +1,157 @@
---
title: Cheatsheet
documentclass: scrartcl
classoption:
- twocolumn
# - landscape
---
## Punten op het gezicht
![](dlib_face_points.png)
detection.points
: Alle bovenstaande punten. Ieder punt bestaat uit een `x` en een `y` coördinaat. Bijvoorbeeld `detection.points[33].x` is het x-coördinaat van het puntje van de neus.
: `x` is de afstand vanaf links. De horizontale positie.
: `y` is de afstand vanaf boven. De verticale positie.
detection.x
: x coordinaat van de bounding box van het gezicht. Dit is het punt linkboven.
detection.y
: y coordinaat van de bounding box van het gezicht. Dit is het punt linkboven.
detection.width
: Breedte van de bounding box van het gezicht.
detection.height
: Hoogte van de bounding box van het gezicht.
detection.angle
: Hoeveel het gezicht gedraaid is (oftewel de 'roll').
## Tekenen
drawPoints(\[punt1, punt2, ...])
: Teken punten
strokePoints(\[punt1, punt2, ...])
: Teken lijnen tussen een set punten
strokePoints(\[punt1, punt2, ...], OPEN)
: Teken lijnen tussen een set punten, maar de eindpunten worden _niet_ met elkaar verbonden.
rect(x, y, breedte, hoogte)
: teken een rechthoek. _x_ en _y_ bepalen de linker bovenhoek.
circle(cx, cy, radius)
: Teken een cirkel. _cx_ en _cy_ bepalen het midden. Radius is de straal.
image(naam, x, y, breedte, hoogte)
: Toon een ingeladen afbeelding. _x_ en _y_ bepalen waar de linker bovenhoek wordt geplaatst.
: Zie hieronder voor alle beschikbare afbeeldingen
let box = getBoundingBox(punten)
: Bepaald het rechthoek waarbinnen de punten vallen.
: Te gebruiken als `box.x`, `box.y`, `box.width`, `box.height`.
## Opmaak
fill(kleur)
: de kleur van de vulling van wat je gaat tekenen.
noFill()
: geen vulling
stroke(kleur)
: de kleur van de omlijning.
strokeWeight(dikte)
: de dikte van de omlijning
noStroke()
: geen omlijning
### Kleuren
Kleuren kunnen op verschillende manieren worden opgeven:
* als engelse naam. Bijvoorbeeld `fill('orange')`, `fill('darkblue')`.
* Op basis van drie getallen tussen 0 en 255. Dit zijn de rood, groen, en blauwe componenten van de kleur. Bijvoorbeeld `fill(0, 0, 255)` geeft een blauwe vulling. `fill(100,100,100)` is grijs.
* Op basis van _vier_ getallen. Dit werkt hetzelfde als met drie. Het vierde getal bepaald transparantie. 0 is volledig tranparant, 255 helemaal geen transparantie. Dus `fill(0, 0, 255, 130)` geeft een half transparante blauwe vulling.
https://genekogan.com/code/p5js-transformations/
## Afbeeldingen
Deze afbeeldingen kun je gebruiken met `image()`.
Bijvoorbeeld: `image(images.game1, 10, 10, 50, 50)` tekent het plaatje `images.game1`, waarbij de linker bovenhoek 10 pixels van links en 10 pixels vanaf rechts wordt geplaatst. En het plaatje 50 bij 50 pixels is.
images.game1
![](../dist/static/assets/images/game1.png)
images.game10
![](../dist/static/assets/images/game10.png)
images.game11
![](../dist/static/assets/images/game11.png)
images.game12
![](../dist/static/assets/images/game12.png)
images.game2
![](../dist/static/assets/images/game2.png)
images.game3
![](../dist/static/assets/images/game3.png)
images.game4
![](../dist/static/assets/images/game4.png)
images.game5
![](../dist/static/assets/images/game5.png)
images.game6
![](../dist/static/assets/images/game6.png)
images.game7
![](../dist/static/assets/images/game7.png)
images.game8
![](../dist/static/assets/images/game8.png)
images.game9
![](../dist/static/assets/images/game9.png)
images.glasses1
![](../dist/static/assets/images/glasses1.png)
images.glasses2
![](../dist/static/assets/images/glasses2.png)
images.glasses3
![](../dist/static/assets/images/glasses3.png)
images.hat1
![](../dist/static/assets/images/hat1.png)
images.hat2
![](../dist/static/assets/images/hat2.png)
images.mustache1
![](../dist/static/assets/images/mustache1.png)
images.mustache3
![](../dist/static/assets/images/mustache3.png)
images.mustache4
![](../dist/static/assets/images/mustache4.png)
images.mustache5
![](../dist/static/assets/images/mustache5.png)

BIN
doc/dlib_face_points.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -6,47 +6,53 @@ services:
- dbdata:/data/db
app:
build:
context: .
context: ./
dockerfile: Dockerfile
target: production
# uncomment the following line to pull the image from docker hub
# image: index.docker.io/catarak/p5.js-web-editor:latest
# uncomment the following lines if you don't want export all of the variables
# defined in your .env file for testing
# env_file:
# - "$PWD/.env.production"
environment:
- API_URL
- MONGO_URL
- PORT
- SESSION_SECRET
- AWS_ACCESS_KEY
- AWS_SECRET_KEY
- S3_BUCKET
- AWS_REGION
- GITHUB_ID
- GITHUB_SECRET
- MAILGUN_DOMAIN
- MAILGUN_KEY
- EMAIL_SENDER
- EMAIL_VERIFY_SECRET_TOKEN
- S3_BUCKET_URL_BASE
- GG_EXAMPLES_USERNAME
- GG_EXAMPLES_PASS
- GG_EXAMPLES_EMAIL
- GOOGLE_ID
- GOOGLE_SECRET
- EXAMPLE_USER_EMAIL
- EXAMPLE_USER_PASSWORD
- ML5_EXAMPLES_USERNAME
- ML5_EXAMPLES_PASS
- ML5_EXAMPLES_EMAIL
env_file:
- "$PWD/.env.production"
# environment:
# - API_URL
# - AWS_ACCESS_KEY
# - AWS_REGION
# - AWS_SECRET_KEY
# - CORS_ALLOW_LOCALHOST
# - EMAIL_SENDER
# - EMAIL_VERIFY_SECRET_TOKEN
# - EXAMPLE_USERNAME
# - EXAMPLE_USER_EMAIL
# - EXAMPLE_USER_PASSWORD
# - GG_EXAMPLES_USERNAME
# - GG_EXAMPLES_EMAIL
# - GG_EXAMPLES_PASS
# - ML5_EXAMPLES_USERNAME
# - ML5_EXAMPLES_EMAIL
# - ML5_EXAMPLES_PASS
# - GITHUB_ID
# - GITHUB_SECRET
# - GOOGLE_ID
# - GOOGLE_SECRET
# - MAILGUN_DOMAIN
# - MAILGUN_KEY
# - MONGO_URL
# - PORT
# - S3_BUCKET
# - S3_BUCKET_URL_BASE
# - SESSION_SECRET
# - UI_ACCESS_TOKEN_ENABLED
# - UPLOAD_LIMIT
# - MOBILE_ENABLED
# you can either set this in your .env or as an environment variables
# or here YOU CHOOSE
# - MONGO_URL=mongodb://mongo:27017/p5js-web-editor
volumes:
- .:/opt/node/app
- /opt/node/app/node_modules
- .:/usr/src/app
- /usr/src/app/node_modules
ports:
- '8000:8000'
depends_on:

View file

@ -1,3 +1,5 @@
console.log('environment:',process.env.NODE_ENV);
if (process.env.NODE_ENV === 'production') {
process.env.webpackAssets = JSON.stringify(require('./dist/static/manifest.json'));
require('./dist/server.bundle.js');

16
p5.js-web-editor.service Normal file
View file

@ -0,0 +1,16 @@
[Unit]
Description=p5.js web editor
After=network.target
[Service]
Environment=NODE_PORT=8000
Type=simple
User=YOUR_USER
EnvironmentFile=/home/YOUR_USER/p5.js-web-editor/.env
WorkingDirectory=/home/YOUR_USER/p5.js-web-editor
ExecStart=/usr/bin/npm run start:prod
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -3,9 +3,9 @@ import friendlyWords from 'friendly-words';
import lodash from 'lodash';
import passport from 'passport';
import GitHubStrategy from 'passport-github';
// import GitHubStrategy from 'passport-github';
import LocalStrategy from 'passport-local';
import GoogleStrategy from 'passport-google-oauth20';
// import GoogleStrategy from 'passport-google-oauth20';
import { BasicStrategy } from 'passport-http';
import User from '../models/user';
@ -82,101 +82,101 @@ const getPrimaryEmail = githubEmails => (
/**
* Sign in with GitHub.
*/
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
callbackURL: '/auth/github/callback',
passReqToCallback: true,
scope: ['user:email'],
}, (req, accessToken, refreshToken, profile, done) => {
User.findOne({ github: profile.id }, (findByGithubErr, existingUser) => {
if (existingUser) {
done(null, existingUser);
return;
}
// passport.use(new GitHubStrategy({
// clientID: process.env.GITHUB_ID,
// clientSecret: process.env.GITHUB_SECRET,
// callbackURL: '/auth/github/callback',
// passReqToCallback: true,
// scope: ['user:email'],
// }, (req, accessToken, refreshToken, profile, done) => {
// User.findOne({ github: profile.id }, (findByGithubErr, existingUser) => {
// if (existingUser) {
// done(null, existingUser);
// return;
// }
const emails = getVerifiedEmails(profile.emails);
const primaryEmail = getPrimaryEmail(profile.emails);
// const emails = getVerifiedEmails(profile.emails);
// const primaryEmail = getPrimaryEmail(profile.emails);
User.findByEmail(emails, (findByEmailErr, existingEmailUser) => {
if (existingEmailUser) {
existingEmailUser.email = existingEmailUser.email || primaryEmail;
existingEmailUser.github = profile.id;
existingEmailUser.username = existingEmailUser.username || profile.username;
existingEmailUser.tokens.push({ kind: 'github', accessToken });
existingEmailUser.name = existingEmailUser.name || profile.displayName;
existingEmailUser.verified = User.EmailConfirmation.Verified;
existingEmailUser.save(saveErr => done(null, existingEmailUser));
} else {
const user = new User();
user.email = primaryEmail;
user.github = profile.id;
user.username = profile.username;
user.tokens.push({ kind: 'github', accessToken });
user.name = profile.displayName;
user.verified = User.EmailConfirmation.Verified;
user.save(saveErr => done(null, user));
}
});
});
}));
// User.findByEmail(emails, (findByEmailErr, existingEmailUser) => {
// if (existingEmailUser) {
// existingEmailUser.email = existingEmailUser.email || primaryEmail;
// existingEmailUser.github = profile.id;
// existingEmailUser.username = existingEmailUser.username || profile.username;
// existingEmailUser.tokens.push({ kind: 'github', accessToken });
// existingEmailUser.name = existingEmailUser.name || profile.displayName;
// existingEmailUser.verified = User.EmailConfirmation.Verified;
// existingEmailUser.save(saveErr => done(null, existingEmailUser));
// } else {
// const user = new User();
// user.email = primaryEmail;
// user.github = profile.id;
// user.username = profile.username;
// user.tokens.push({ kind: 'github', accessToken });
// user.name = profile.displayName;
// user.verified = User.EmailConfirmation.Verified;
// user.save(saveErr => done(null, user));
// }
// });
// });
// }));
/**
* Sign in with Google.
*/
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: '/auth/google/callback',
passReqToCallback: true,
scope: ['openid email'],
}, (req, accessToken, refreshToken, profile, done) => {
User.findOne({ google: profile._json.emails[0].value }, (findByGoogleErr, existingUser) => {
if (existingUser) {
done(null, existingUser);
return;
}
// /**
// * Sign in with Google.
// */
// passport.use(new GoogleStrategy({
// clientID: process.env.GOOGLE_ID,
// clientSecret: process.env.GOOGLE_SECRET,
// callbackURL: '/auth/google/callback',
// passReqToCallback: true,
// scope: ['openid email'],
// }, (req, accessToken, refreshToken, profile, done) => {
// User.findOne({ google: profile._json.emails[0].value }, (findByGoogleErr, existingUser) => {
// if (existingUser) {
// done(null, existingUser);
// return;
// }
const primaryEmail = profile._json.emails[0].value;
// const primaryEmail = profile._json.emails[0].value;
User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => {
let username = profile._json.emails[0].value.split('@')[0];
User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => {
if (existingUsernameUser) {
const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
username = slugify(`${username} ${adj}`);
}
// what if a username is already taken from the display name too?
// then, append a random friendly word?
if (existingEmailUser) {
existingEmailUser.email = existingEmailUser.email || primaryEmail;
existingEmailUser.google = profile._json.emails[0].value;
existingEmailUser.username = existingEmailUser.username || username;
existingEmailUser.tokens.push({ kind: 'google', accessToken });
existingEmailUser.name = existingEmailUser.name || profile._json.displayName;
existingEmailUser.verified = User.EmailConfirmation.Verified;
existingEmailUser.save((saveErr) => {
if (saveErr) {
console.log(saveErr);
}
done(null, existingEmailUser);
});
} else {
const user = new User();
user.email = primaryEmail;
user.google = profile._json.emails[0].value;
user.username = username;
user.tokens.push({ kind: 'google', accessToken });
user.name = profile._json.displayName;
user.verified = User.EmailConfirmation.Verified;
user.save((saveErr) => {
if (saveErr) {
console.log(saveErr);
}
done(null, user);
});
}
});
});
});
}));
// User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => {
// let username = profile._json.emails[0].value.split('@')[0];
// User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => {
// if (existingUsernameUser) {
// const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
// username = slugify(`${username} ${adj}`);
// }
// // what if a username is already taken from the display name too?
// // then, append a random friendly word?
// if (existingEmailUser) {
// existingEmailUser.email = existingEmailUser.email || primaryEmail;
// existingEmailUser.google = profile._json.emails[0].value;
// existingEmailUser.username = existingEmailUser.username || username;
// existingEmailUser.tokens.push({ kind: 'google', accessToken });
// existingEmailUser.name = existingEmailUser.name || profile._json.displayName;
// existingEmailUser.verified = User.EmailConfirmation.Verified;
// existingEmailUser.save((saveErr) => {
// if (saveErr) {
// console.log(saveErr);
// }
// done(null, existingEmailUser);
// });
// } else {
// const user = new User();
// user.email = primaryEmail;
// user.google = profile._json.emails[0].value;
// user.username = username;
// user.tokens.push({ kind: 'google', accessToken });
// user.name = profile._json.displayName;
// user.verified = User.EmailConfirmation.Verified;
// user.save((saveErr) => {
// if (saveErr) {
// console.log(saveErr);
// }
// done(null, user);
// });
// }
// });
// });
// });
// }));

View file

@ -9,6 +9,7 @@ import Project from '../models/project';
import User from '../models/user';
import { resolvePathToFile } from '../utils/filePath';
import generateFileSystemSafeName from '../utils/generateFileSystemSafeName';
import mime from 'mime-types';
export { default as createProject, apiCreateProject } from './project.controller/createProject';
export { default as deleteProject } from './project.controller/deleteProject';
@ -112,6 +113,11 @@ export function getProjectAsset(req, res) {
return res.status(404).send({ message: 'Asset does not exist' });
}
if (!resolvedFile.url) {
// set the content type based on the filename
const mimetype = mime.lookup(resolvedFile.name);
if(mimetype) {
res.type(mimetype);
}
return res.send(resolvedFile.content);
}
request({ method: 'GET', url: resolvedFile.url, encoding: null }, (innerErr, response, body) => {

View file

@ -1,17 +1,19 @@
const defaultSketch = `function setup() {
createCanvas(400, 400);
}
const defaultSketch = `// liveUpdate
function draw() {
background(220);
}`;
function drawMask(detection) {
stroke('red')
strokeWeight(2)
rect(detection.x, detection.y, detection.width, detection.height)
}
`;
const defaultHTML =
`<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/addons/p5.sound.min.js"></script>
<script src="/assets/p5.js"></script>
<script src="/assets/ml5.js"></script>
<script src="/assets/webcam.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
@ -29,6 +31,8 @@ const defaultCSS =
}
canvas {
display: block;
width: 100% !important;
height: auto !important;
}
`;

View file

@ -28,6 +28,8 @@ import embedRoutes from './routes/embed.routes';
import assetRoutes from './routes/asset.routes';
import { requestsOfTypeJSON } from './utils/requestsOfType';
import User from './models/user';
import { renderIndex } from './views/index';
import { get404Sketch } from './views/404Page';
@ -48,6 +50,8 @@ if (process.env.BASIC_USERNAME && process.env.BASIC_PASSWORD) {
const allowedCorsOrigins = [
/p5js\.org$/,
/digitalplayground\.nl$/,
/rubenvandeven\.com$/
];
// to allow client-only development
@ -90,6 +94,18 @@ app.use(
app.use(Express.static(path.resolve(__dirname, '../dist/static'), {
maxAge: process.env.STATIC_MAX_AGE || (process.env.NODE_ENV === 'production' ? '1d' : '0')
}));
app.use(
'/assets',
Express.static(
path.resolve(__dirname, '../dist/static/assets'),
{
// Browsers must revalidate for changes to the locale files
// It doesn't actually mean "don't cache this file"
// See: https://jakearchibald.com/2016/caching-best-practices/
setHeaders: res => res.setHeader('Cache-Control', 'no-cache')
}
)
);
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(bodyParser.json({ limit: '50mb' }));
@ -135,15 +151,15 @@ app.use('/', serverRoutes);
app.use(assetRoutes);
app.use('/', embedRoutes);
app.get('/auth/github', passport.authenticate('github'));
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
res.redirect('/');
});
// app.get('/auth/github', passport.authenticate('github'));
// app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
// res.redirect('/');
// });
app.get('/auth/google', passport.authenticate('google'));
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => {
res.redirect('/');
});
// app.get('/auth/google', passport.authenticate('google'));
// app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => {
// res.redirect('/');
// });
// configure passport
require('./config/passport');
@ -173,6 +189,22 @@ app.use('/api', (error, req, res, next) => {
});
// overview of users:
app.get('/users', (req, res) => {
// let results = [];
User.find({}).sort({ createdAt: -1 }).exec()
.then((users) => {
const usernames = users.map((user) => user.username);
let names = "<ul>";
usernames.forEach((username) => names += `<li><a href="/${username}/sketches">${username}</a></li>`);
names += "</ul>";
res.send(names);
});
// User.find()
// res.send({'testing': true});
});
// Handle missing routes.
app.get('*', (req, res) => {
res.status(404);

View file

@ -3,7 +3,7 @@
*/
import nodemailer from 'nodemailer';
import mg from 'nodemailer-mailgun-transport';
// import mg from 'nodemailer-mailgun-transport';
const auth = {
api_key: process.env.MAILGUN_KEY,
@ -12,7 +12,10 @@ const auth = {
class Mail {
constructor() {
this.client = nodemailer.createTransport(mg({ auth }));
this.client = nodemailer.createTransport({
streamTransport: true,
// newline: 'windows'
});
this.sendOptions = {
from: process.env.EMAIL_SENDER,
};

View file

@ -12,7 +12,7 @@ export function renderIndex() {
${process.env.NODE_ENV === 'production' ? `<link rel='stylesheet' href='${assetsManifest['/app.css']}' />` : ''}
<link href='https://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Montserrat:400,700' rel='stylesheet' type='text/css'>
<link rel='shortcut icon' href='https://raw.githubusercontent.com/processing/p5.js-website-OLD/master/favicon.ico' type='image/x-icon' / >
<link rel='icon' href='/assets/favicon.png' type='image/png' / >
<script>
if (!window.process) {
window.process = {};
@ -29,6 +29,7 @@ export function renderIndex() {
window.process.env.CLIENT = true;
window.process.env.LOGIN_ENABLED = ${process.env.LOGIN_ENABLED === 'false' ? false : true};
window.process.env.EXAMPLES_ENABLED = ${process.env.EXAMPLES_ENABLED === 'false' ? false : true};
window.process.env.EXAMPLE_USERNAME = '${process.env.EXAMPLE_USERNAME || 'p5'}';
window.process.env.UI_ACCESS_TOKEN_ENABLED = ${process.env.UI_ACCESS_TOKEN_ENABLED === 'false' ? false : true};
window.process.env.UI_COLLECTIONS_ENABLED = ${process.env.UI_COLLECTIONS_ENABLED === 'false' ? false : true};
window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined};
@ -42,13 +43,6 @@ export function renderIndex() {
</div>
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-53383000-1', 'auto');
ga('send', 'pageview');
</script>
</body>