Compare commits
43 commits
Author | SHA1 | Date | |
---|---|---|---|
|
61fd46777c | ||
|
6ee11bc076 | ||
|
07c4f9e899 | ||
|
6f2095eede | ||
|
4f0cb957a2 | ||
|
7acb164f93 | ||
|
b2665fddf1 | ||
|
dc20a7238f | ||
|
14a4425dc2 | ||
|
fa5e7cceb1 | ||
|
b231b6b7e3 | ||
|
f181bc6cb7 | ||
|
c1810ed47f | ||
|
bc83555cab | ||
|
8ebe30af90 | ||
|
9e90aada94 | ||
|
f1a60d4dee | ||
|
27b4eea796 | ||
|
cebfae6430 | ||
|
a6594f142b | ||
|
aa2d324cab | ||
|
a3c41c785d | ||
|
d437cde6da | ||
|
8247385779 | ||
|
af1dfe1a0b | ||
|
3c1760c3f6 | ||
|
baf1e0bc03 | ||
|
d163e2a83a | ||
|
c1f22570e8 | ||
|
d589e37a4e | ||
|
f7a54b2e17 | ||
|
fce40f28bb | ||
|
37d60fd576 | ||
|
cef703104b | ||
|
058a656d13 | ||
|
377c60ac92 | ||
|
10ed1de810 | ||
|
793d7e6521 | ||
|
21a5d698b1 | ||
|
a548e6937c | ||
|
dc5fba2e5a | ||
|
8d2c3601d5 | ||
|
bd8391bcf4 |
|
@ -19,12 +19,13 @@ CMD ["npm", "start"]
|
||||||
|
|
||||||
FROM development as build
|
FROM development as build
|
||||||
ENV NODE_ENV production
|
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
|
ENV NODE_ENV=production
|
||||||
COPY package.json package-lock.json index.js ./
|
COPY package.json package-lock.json index.js ./
|
||||||
RUN npm install --production
|
RUN npm install --production
|
||||||
RUN npm rebuild node-sass
|
RUN npm rebuild node-sass
|
||||||
COPY --from=build $APP_HOME/dist ./dist
|
# COPY --from=build $APP_HOME/dist ./dist
|
||||||
CMD ["npm", "run", "start:prod"]
|
CMD ["npm", "run", "start:prod"]
|
||||||
|
|
31
README.ruben.md
Normal 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
|
4
app.json
|
@ -38,6 +38,10 @@
|
||||||
"description": "A secret key for...? Not sure where used.",
|
"description": "A secret key for...? Not sure where used.",
|
||||||
"generator": "secret"
|
"generator": "secret"
|
||||||
},
|
},
|
||||||
|
"EXAMPLE_USERNAME": {
|
||||||
|
"description": "Username of the default account.",
|
||||||
|
"value": "p5"
|
||||||
|
},
|
||||||
"EXAMPLE_USER_EMAIL": {
|
"EXAMPLE_USER_EMAIL": {
|
||||||
"description": "The email address for the account holding the default Example sketches",
|
"description": "The email address for the account holding the default Example sketches",
|
||||||
"value": "examples@p5js.org"
|
"value": "examples@p5js.org"
|
||||||
|
|
|
@ -17,7 +17,8 @@ import { metaKeyName, } from '../utils/metaKey';
|
||||||
|
|
||||||
import CaretLeftIcon from '../images/left-arrow.svg';
|
import CaretLeftIcon from '../images/left-arrow.svg';
|
||||||
import TriangleIcon from '../images/down-filled-triangle.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 {
|
class Nav extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -351,7 +352,7 @@ class Nav extends React.PureComponent {
|
||||||
{ getConfig('EXAMPLES_ENABLED') &&
|
{ getConfig('EXAMPLES_ENABLED') &&
|
||||||
<li className="nav__dropdown-item">
|
<li className="nav__dropdown-item">
|
||||||
<Link
|
<Link
|
||||||
to="/p5/sketches"
|
to={`/${getConfig('EXAMPLE_USERNAME')}/sketches`}
|
||||||
onFocus={this.handleFocusForFile}
|
onFocus={this.handleFocusForFile}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
onClick={this.setDropdownForNone}
|
onClick={this.setDropdownForNone}
|
||||||
|
|
1
client/images/dp-logo-line-drawing.svg
Normal file
After Width: | Height: | Size: 7.7 KiB |
|
@ -127,6 +127,7 @@ function getSynchedProject(currentState, responseProject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveProject(selectedFile = null, autosave = false, mobile = false) {
|
export function saveProject(selectedFile = null, autosave = false, mobile = false) {
|
||||||
|
console.trace('saving inproject.js');
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
if (state.project.isSaving) {
|
if (state.project.isSaving) {
|
||||||
|
@ -261,7 +262,7 @@ function generateNewIdsForChildren(file, files) {
|
||||||
export function cloneProject(id) {
|
export function cloneProject(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(setUnsavedChanges(false));
|
dispatch(setUnsavedChanges(false));
|
||||||
new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
resolve(getState());
|
resolve(getState());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -326,6 +326,12 @@ class Editor extends React.Component {
|
||||||
'editor-holder--hidden': this.props.file.fileType === 'folder' || this.props.file.url
|
'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 (
|
return (
|
||||||
<section className={editorSectionClass} >
|
<section className={editorSectionClass} >
|
||||||
<header className="editor__header">
|
<header className="editor__header">
|
||||||
|
@ -360,6 +366,7 @@ class Editor extends React.Component {
|
||||||
</header>
|
</header>
|
||||||
<article ref={(element) => { this.codemirrorContainer = element; }} className={editorHolderClass} >
|
<article ref={(element) => { this.codemirrorContainer = element; }} className={editorHolderClass} >
|
||||||
</article>
|
</article>
|
||||||
|
{preview}
|
||||||
<EditorAccessibility
|
<EditorAccessibility
|
||||||
lintMessages={this.props.lintMessages}
|
lintMessages={this.props.lintMessages}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -23,6 +23,8 @@ import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
|
||||||
from '../../../utils/consoleUtils';
|
from '../../../utils/consoleUtils';
|
||||||
|
|
||||||
|
|
||||||
|
// let lastUpdate = null;
|
||||||
|
|
||||||
const shouldRenderSketch = (props, prevProps = undefined) => {
|
const shouldRenderSketch = (props, prevProps = undefined) => {
|
||||||
const { isPlaying, previewIsRefreshing, fullView } = props;
|
const { isPlaying, previewIsRefreshing, fullView } = props;
|
||||||
|
|
||||||
|
@ -57,6 +59,9 @@ class PreviewFrame extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.isPlaying === true && this.props.isPlaying === false) {
|
||||||
|
console.trace('update', this.props, prevProps);
|
||||||
|
}
|
||||||
if (shouldRenderSketch(this.props, prevProps)) this.renderSketch();
|
if (shouldRenderSketch(this.props, prevProps)) this.renderSketch();
|
||||||
// small bug - if autorefresh is on, and the usr changes files
|
// small bug - if autorefresh is on, and the usr changes files
|
||||||
// in the sketch, preview will reload
|
// in the sketch, preview will reload
|
||||||
|
@ -327,16 +332,91 @@ class PreviewFrame extends React.Component {
|
||||||
|
|
||||||
renderSketch() {
|
renderSketch() {
|
||||||
const doc = this.iframeElement;
|
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) {
|
if (this.props.isPlaying) {
|
||||||
this.props.clearConsole();
|
doc.removeAttribute('srcdoc');
|
||||||
srcDoc.set(doc, localFiles);
|
|
||||||
|
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) {
|
if (this.props.endSketchRefresh) {
|
||||||
this.props.endSketchRefresh();
|
this.props.endSketchRefresh();
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
doc.srcdoc = '';
|
|
||||||
srcDoc.set(doc, ' ');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +464,18 @@ PreviewFrame.propTypes = {
|
||||||
setBlobUrl: PropTypes.func.isRequired,
|
setBlobUrl: PropTypes.func.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
expandConsole: 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({
|
cmController: PropTypes.shape({
|
||||||
getContent: PropTypes.func
|
getContent: PropTypes.func
|
||||||
}),
|
}),
|
||||||
|
@ -392,7 +483,8 @@ PreviewFrame.propTypes = {
|
||||||
|
|
||||||
PreviewFrame.defaultProps = {
|
PreviewFrame.defaultProps = {
|
||||||
fullView: false,
|
fullView: false,
|
||||||
cmController: {}
|
unsavedChanges: false,
|
||||||
|
cmController: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PreviewFrame;
|
export default PreviewFrame;
|
||||||
|
|
|
@ -58,9 +58,14 @@ class Toolbar extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const canEditProjectName = this.canEditProjectName();
|
||||||
|
|
||||||
const playButtonClass = classNames({
|
const playButtonClass = classNames({
|
||||||
'toolbar__play-button': true,
|
'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({
|
const stopButtonClass = classNames({
|
||||||
'toolbar__stop-button': true,
|
'toolbar__stop-button': true,
|
||||||
|
@ -75,8 +80,6 @@ class Toolbar extends React.Component {
|
||||||
'toolbar__project-name-container--editing': this.props.project.isEditingName
|
'toolbar__project-name-container--editing': this.props.project.isEditingName
|
||||||
});
|
});
|
||||||
|
|
||||||
const canEditProjectName = this.canEditProjectName();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="toolbar">
|
<div className="toolbar">
|
||||||
<button
|
<button
|
||||||
|
@ -176,6 +179,8 @@ class Toolbar extends React.Component {
|
||||||
|
|
||||||
Toolbar.propTypes = {
|
Toolbar.propTypes = {
|
||||||
isPlaying: PropTypes.bool.isRequired,
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
|
unsavedChanges: PropTypes.bool.isRequired,
|
||||||
|
isSaved: PropTypes.bool.isRequired,
|
||||||
preferencesIsVisible: PropTypes.bool.isRequired,
|
preferencesIsVisible: PropTypes.bool.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
setProjectName: PropTypes.func.isRequired,
|
setProjectName: PropTypes.func.isRequired,
|
||||||
|
@ -215,8 +220,10 @@ function mapStateToProps(state) {
|
||||||
infiniteLoop: state.ide.infiniteLoop,
|
infiniteLoop: state.ide.infiniteLoop,
|
||||||
isPlaying: state.ide.isPlaying,
|
isPlaying: state.ide.isPlaying,
|
||||||
owner: state.project.owner,
|
owner: state.project.owner,
|
||||||
|
isSaved: state.project.updatedAt !== '',
|
||||||
preferencesIsVisible: state.ide.preferencesIsVisible,
|
preferencesIsVisible: state.ide.preferencesIsVisible,
|
||||||
project: state.project,
|
project: state.project,
|
||||||
|
unsavedChanges: state.ide.unsavedChanges,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,23 @@ class IDEView extends React.Component {
|
||||||
clearTimeout(this.autosaveInterval);
|
clearTimeout(this.autosaveInterval);
|
||||||
this.autosaveInterval = null;
|
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) {
|
handleGlobalKeydown(e) {
|
||||||
// 83 === s
|
// 83 === s
|
||||||
if (
|
if (
|
||||||
|
@ -181,16 +198,7 @@ class IDEView extends React.Component {
|
||||||
) {
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (
|
this.saveProject();
|
||||||
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');
|
|
||||||
}
|
|
||||||
// 13 === enter
|
// 13 === enter
|
||||||
} else if (
|
} else if (
|
||||||
e.keyCode === 13 &&
|
e.keyCode === 13 &&
|
||||||
|
@ -387,8 +395,11 @@ class IDEView extends React.Component {
|
||||||
setBlobUrl={this.props.setBlobUrl}
|
setBlobUrl={this.props.setBlobUrl}
|
||||||
expandConsole={this.props.expandConsole}
|
expandConsole={this.props.expandConsole}
|
||||||
clearConsole={this.props.clearConsole}
|
clearConsole={this.props.clearConsole}
|
||||||
|
saveProject={this.saveProject.bind(this)}
|
||||||
|
project={this.props.project}
|
||||||
cmController={this.cmController}
|
cmController={this.cmController}
|
||||||
language={this.props.preferences.language}
|
language={this.props.preferences.language}
|
||||||
|
unsavedChanges={this.props.ide.unsavedChanges}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import objectID from 'bson-objectid';
|
import objectID from 'bson-objectid';
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
const defaultSketch = `function setup() {
|
const defaultSketch = `// liveUpdate
|
||||||
createCanvas(400, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw() {
|
function drawMask(detection) {
|
||||||
background(220);
|
stroke('red')
|
||||||
|
strokeWeight(2)
|
||||||
|
rect(detection.x, detection.y, detection.width, detection.height)
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const defaultHTML =
|
const defaultHTML =
|
||||||
`<!DOCTYPE html>
|
`<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
|
<script src="/assets/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/ml5.js"></script>
|
||||||
|
<script src="/previewScripts.js"></script>
|
||||||
|
<script src="/assets/webcam.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
|
@ -32,6 +34,8 @@ const defaultCSS =
|
||||||
}
|
}
|
||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -147,8 +151,21 @@ const files = (state, action) => {
|
||||||
return file;
|
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:
|
case ActionTypes.SET_BLOB_URL:
|
||||||
return state.map((file) => {
|
return state.map((file) => {
|
||||||
if (file.id !== action.id) {
|
if (file.id !== action.id) {
|
||||||
|
@ -173,7 +190,8 @@ const files = (state, action) => {
|
||||||
content: action.content,
|
content: action.content,
|
||||||
url: action.url,
|
url: action.url,
|
||||||
children: action.children,
|
children: action.children,
|
||||||
fileType: action.fileType || 'file'
|
fileType: action.fileType || 'file',
|
||||||
|
changed: false,
|
||||||
}];
|
}];
|
||||||
return newState.map((file) => {
|
return newState.map((file) => {
|
||||||
if (file.id === action.parentId) {
|
if (file.id === action.parentId) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import FooterTabSwitcher from '../../components/mobile/TabSwitcher';
|
||||||
import FooterTab from '../../components/mobile/Tab';
|
import FooterTab from '../../components/mobile/Tab';
|
||||||
import Loader from '../App/components/loader';
|
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
|
// @ghalestrilo 08/13/2020: I'm sorry
|
||||||
const ContentWrapper = styled(Content)`
|
const ContentWrapper = styled(Content)`
|
||||||
|
|
|
@ -60,7 +60,7 @@ class AccountView extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<SocialLoginPanel {...this.props} />
|
{/* <SocialLoginPanel {...this.props} /> */}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<APIKeyForm {...this.props} />
|
<APIKeyForm {...this.props} />
|
||||||
|
|
|
@ -42,11 +42,11 @@ class LoginView extends React.Component {
|
||||||
<div className="form-container__content">
|
<div className="form-container__content">
|
||||||
<h2 className="form-container__title">{this.props.t('LoginView.Login')}</h2>
|
<h2 className="form-container__title">{this.props.t('LoginView.Login')}</h2>
|
||||||
<LoginForm {...this.props} />
|
<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">
|
<div className="form-container__stack">
|
||||||
<SocialAuthButton service={SocialAuthButton.services.github} />
|
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||||
<SocialAuthButton service={SocialAuthButton.services.google} />
|
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||||
</div>
|
</div> */}
|
||||||
<p className="form__navigation-options">
|
<p className="form__navigation-options">
|
||||||
{this.props.t('LoginView.DontHaveAccount')}
|
{this.props.t('LoginView.DontHaveAccount')}
|
||||||
<Link className="form__signup-button" to="/signup">{this.props.t('LoginView.SignUp')}</Link>
|
<Link className="form__signup-button" to="/signup">{this.props.t('LoginView.SignUp')}</Link>
|
||||||
|
|
|
@ -34,11 +34,11 @@ class SignupView extends React.Component {
|
||||||
<div className="form-container__content">
|
<div className="form-container__content">
|
||||||
<h2 className="form-container__title">{this.props.t('SignupView.Description')}</h2>
|
<h2 className="form-container__title">{this.props.t('SignupView.Description')}</h2>
|
||||||
<SignupForm {...this.props} />
|
<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">
|
<div className="form-container__stack">
|
||||||
<SocialAuthButton service={SocialAuthButton.services.github} />
|
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||||
<SocialAuthButton service={SocialAuthButton.services.google} />
|
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||||
</div>
|
</div> */}
|
||||||
<p className="form__navigation-options">
|
<p className="form__navigation-options">
|
||||||
{this.props.t('SignupView.AlreadyHave')}
|
{this.props.t('SignupView.AlreadyHave')}
|
||||||
<Link className="form__login-button" to="/login">{this.props.t('SignupView.Login')}</Link>
|
<Link className="form__login-button" to="/login">{this.props.t('SignupView.Login')}</Link>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
$base-font-size: 12;
|
$base-font-size: 12;
|
||||||
|
|
||||||
//colors
|
//colors
|
||||||
$p5js-pink: #ed225d;
|
$p5js-pink: #FFE117; /*DP Colours*/
|
||||||
$processing-blue: #007BBB;
|
$processing-blue: #007BBB;
|
||||||
$p5js-active-pink: #f10046;
|
$p5js-active-pink: #f10046;
|
||||||
$white: #fff;
|
$white: #fff;
|
||||||
|
|
|
@ -166,12 +166,23 @@
|
||||||
|
|
||||||
.nav__item-logo {
|
.nav__item-logo {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: #{42 / $base-font-size}rem;
|
height: #{(42 - 1) / $base-font-size}rem;
|
||||||
width: #{56 / $base-font-size}rem;
|
width: #{56 / $base-font-size}rem;
|
||||||
|
background:$p5js-pink;
|
||||||
|
|
||||||
& span {
|
& span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg{
|
||||||
|
width: 100%;
|
||||||
|
height: 90%;
|
||||||
|
padding-top: 5%;
|
||||||
|
|
||||||
|
path{
|
||||||
|
stroke: $dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.svg__logo g > path {
|
.svg__logo g > path {
|
||||||
@include themify() {
|
@include themify() {
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
.toolbar__play-button {
|
.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() {
|
@include themify() {
|
||||||
@extend %toolbar-button;
|
@extend %toolbar-button;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const Theme = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const colors = {
|
export const colors = {
|
||||||
p5jsPink: '#ed225d',
|
p5jsPink: '#FFE117', // DP Colours
|
||||||
processingBlue: '#007BBB',
|
processingBlue: '#007BBB',
|
||||||
p5jsActivePink: '#f10046',
|
p5jsActivePink: '#f10046',
|
||||||
white: '#fff',
|
white: '#fff',
|
||||||
|
|
|
@ -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`.
|
_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.
|
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/) this repository using git onto your local computer.
|
||||||
3. [Clone](https://help.github.com/articles/cloning-a-repository/) your new fork of the repository from GitHub 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
|
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.
|
5. Navigate into the project folder and install all its necessary dependencies with npm.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd p5.js-web-editor
|
$ cd p5.js-web-editor
|
||||||
$ npm install
|
$ npm install
|
||||||
```
|
```
|
||||||
6. Install MongoDB and make sure it is running
|
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`
|
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.
|
8. (For DigitalPlayground) use provided .env file
|
||||||
9. `$ npm run fetch-examples` - this downloads the example sketches into a user called 'p5'
|
9. Run `npm run build:server && npm run build:client` to make sure all script are built and bundled.
|
||||||
10. `$ npm start`
|
11. use the provided service file to start the server through systemd
|
||||||
11. Navigate to [http://localhost:8000](http://localhost:8000) in your browser
|
- edit `p5.js-web-editor.service` so it has the right files & folders according to you installation
|
||||||
12. Install the [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
|
- `cp /home/dp/p5.js-web-editor/p5.js-web-editor.service /etc/systemd/system/`
|
||||||
13. Open and close the Redux DevTools using `ctrl+h`, and move them with `ctrl+w`
|
- `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
|
## Docker Installation
|
||||||
|
|
||||||
|
|
26
digitalplayground-apache.conf
Normal 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
|
@ -0,0 +1 @@
|
||||||
|
Model weights from https://github.com/justadudewhohacks/face-api.js
|
BIN
dist/static/assets/faceapi/age_gender_model-shard1
vendored
Normal file
1
dist/static/assets/faceapi/age_gender_model-weights_manifest.json
vendored
Normal file
BIN
dist/static/assets/faceapi/face_expression_model-shard1
vendored
Normal file
1
dist/static/assets/faceapi/face_expression_model-weights_manifest.json
vendored
Normal file
BIN
dist/static/assets/faceapi/face_landmark_68_model-shard1
vendored
Normal file
1
dist/static/assets/faceapi/face_landmark_68_model-weights_manifest.json
vendored
Normal file
BIN
dist/static/assets/faceapi/face_landmark_68_tiny_model-shard1
vendored
Normal file
1
dist/static/assets/faceapi/face_landmark_68_tiny_model-weights_manifest.json
vendored
Normal 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"]}]
|
BIN
dist/static/assets/faceapi/face_recognition_model-shard1
vendored
Normal file
6
dist/static/assets/faceapi/face_recognition_model-shard2
vendored
Normal file
1
dist/static/assets/faceapi/face_recognition_model-weights_manifest.json
vendored
Normal file
BIN
dist/static/assets/faceapi/mtcnn_model-shard1
vendored
Normal file
1
dist/static/assets/faceapi/mtcnn_model-weights_manifest.json
vendored
Normal 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]}]}]
|
BIN
dist/static/assets/faceapi/ssd_mobilenetv1_model-shard1
vendored
Normal file
137
dist/static/assets/faceapi/ssd_mobilenetv1_model-shard2
vendored
Normal file
1
dist/static/assets/faceapi/ssd_mobilenetv1_model-weights_manifest.json
vendored
Normal file
BIN
dist/static/assets/faceapi/tiny_face_detector_model-shard1
vendored
Normal file
1
dist/static/assets/faceapi/tiny_face_detector_model-weights_manifest.json
vendored
Normal 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
After Width: | Height: | Size: 2 KiB |
25
dist/static/assets/images.json
vendored
Normal 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
After Width: | Height: | Size: 14 KiB |
BIN
dist/static/assets/images/game1.png
vendored
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
dist/static/assets/images/game10.png
vendored
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
dist/static/assets/images/game11.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
dist/static/assets/images/game12.png
vendored
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
dist/static/assets/images/game2.png
vendored
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
dist/static/assets/images/game3.png
vendored
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
dist/static/assets/images/game4.png
vendored
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
dist/static/assets/images/game5.png
vendored
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
dist/static/assets/images/game6.png
vendored
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
dist/static/assets/images/game7.png
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
dist/static/assets/images/game8.png
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
dist/static/assets/images/game9.png
vendored
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
dist/static/assets/images/glasses1.png
vendored
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
dist/static/assets/images/glasses2.png
vendored
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
dist/static/assets/images/glasses3.png
vendored
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
dist/static/assets/images/hat1.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
dist/static/assets/images/hat2.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
dist/static/assets/images/mustache1.png
vendored
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
dist/static/assets/images/mustache2.png
vendored
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
dist/static/assets/images/mustache3.png
vendored
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
dist/static/assets/images/mustache4.png
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
dist/static/assets/images/mustache5.png
vendored
Normal file
After Width: | Height: | Size: 3.2 KiB |
125828
dist/static/assets/ml5.js
vendored
Normal file
81
dist/static/assets/opencv.js
vendored
Normal file
595
dist/static/assets/webcam.js
vendored
Normal 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
|
@ -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
After Width: | Height: | Size: 14 KiB |
|
@ -6,47 +6,53 @@ services:
|
||||||
- dbdata:/data/db
|
- dbdata:/data/db
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: production
|
target: production
|
||||||
# uncomment the following line to pull the image from docker hub
|
# uncomment the following line to pull the image from docker hub
|
||||||
# image: index.docker.io/catarak/p5.js-web-editor:latest
|
# image: index.docker.io/catarak/p5.js-web-editor:latest
|
||||||
# uncomment the following lines if you don't want export all of the variables
|
# uncomment the following lines if you don't want export all of the variables
|
||||||
# defined in your .env file for testing
|
# defined in your .env file for testing
|
||||||
# env_file:
|
env_file:
|
||||||
# - "$PWD/.env.production"
|
- "$PWD/.env.production"
|
||||||
environment:
|
# environment:
|
||||||
- API_URL
|
# - API_URL
|
||||||
- MONGO_URL
|
# - AWS_ACCESS_KEY
|
||||||
- PORT
|
# - AWS_REGION
|
||||||
- SESSION_SECRET
|
# - AWS_SECRET_KEY
|
||||||
- AWS_ACCESS_KEY
|
# - CORS_ALLOW_LOCALHOST
|
||||||
- AWS_SECRET_KEY
|
# - EMAIL_SENDER
|
||||||
- S3_BUCKET
|
# - EMAIL_VERIFY_SECRET_TOKEN
|
||||||
- AWS_REGION
|
# - EXAMPLE_USERNAME
|
||||||
- GITHUB_ID
|
# - EXAMPLE_USER_EMAIL
|
||||||
- GITHUB_SECRET
|
# - EXAMPLE_USER_PASSWORD
|
||||||
- MAILGUN_DOMAIN
|
# - GG_EXAMPLES_USERNAME
|
||||||
- MAILGUN_KEY
|
# - GG_EXAMPLES_EMAIL
|
||||||
- EMAIL_SENDER
|
# - GG_EXAMPLES_PASS
|
||||||
- EMAIL_VERIFY_SECRET_TOKEN
|
# - ML5_EXAMPLES_USERNAME
|
||||||
- S3_BUCKET_URL_BASE
|
# - ML5_EXAMPLES_EMAIL
|
||||||
- GG_EXAMPLES_USERNAME
|
# - ML5_EXAMPLES_PASS
|
||||||
- GG_EXAMPLES_PASS
|
# - GITHUB_ID
|
||||||
- GG_EXAMPLES_EMAIL
|
# - GITHUB_SECRET
|
||||||
- GOOGLE_ID
|
# - GOOGLE_ID
|
||||||
- GOOGLE_SECRET
|
# - GOOGLE_SECRET
|
||||||
- EXAMPLE_USER_EMAIL
|
# - MAILGUN_DOMAIN
|
||||||
- EXAMPLE_USER_PASSWORD
|
# - MAILGUN_KEY
|
||||||
- ML5_EXAMPLES_USERNAME
|
# - MONGO_URL
|
||||||
- ML5_EXAMPLES_PASS
|
# - PORT
|
||||||
- ML5_EXAMPLES_EMAIL
|
# - 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
|
# you can either set this in your .env or as an environment variables
|
||||||
# or here YOU CHOOSE
|
# or here YOU CHOOSE
|
||||||
# - MONGO_URL=mongodb://mongo:27017/p5js-web-editor
|
# - MONGO_URL=mongodb://mongo:27017/p5js-web-editor
|
||||||
volumes:
|
volumes:
|
||||||
- .:/opt/node/app
|
- .:/usr/src/app
|
||||||
- /opt/node/app/node_modules
|
- /usr/src/app/node_modules
|
||||||
ports:
|
ports:
|
||||||
- '8000:8000'
|
- '8000:8000'
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
2
index.js
|
@ -1,3 +1,5 @@
|
||||||
|
console.log('environment:',process.env.NODE_ENV);
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
process.env.webpackAssets = JSON.stringify(require('./dist/static/manifest.json'));
|
process.env.webpackAssets = JSON.stringify(require('./dist/static/manifest.json'));
|
||||||
require('./dist/server.bundle.js');
|
require('./dist/server.bundle.js');
|
||||||
|
|
16
p5.js-web-editor.service
Normal 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
|
||||||
|
|
|
@ -3,9 +3,9 @@ import friendlyWords from 'friendly-words';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import GitHubStrategy from 'passport-github';
|
// import GitHubStrategy from 'passport-github';
|
||||||
import LocalStrategy from 'passport-local';
|
import LocalStrategy from 'passport-local';
|
||||||
import GoogleStrategy from 'passport-google-oauth20';
|
// import GoogleStrategy from 'passport-google-oauth20';
|
||||||
import { BasicStrategy } from 'passport-http';
|
import { BasicStrategy } from 'passport-http';
|
||||||
|
|
||||||
import User from '../models/user';
|
import User from '../models/user';
|
||||||
|
@ -82,101 +82,101 @@ const getPrimaryEmail = githubEmails => (
|
||||||
/**
|
/**
|
||||||
* Sign in with GitHub.
|
* Sign in with GitHub.
|
||||||
*/
|
*/
|
||||||
passport.use(new GitHubStrategy({
|
// passport.use(new GitHubStrategy({
|
||||||
clientID: process.env.GITHUB_ID,
|
// clientID: process.env.GITHUB_ID,
|
||||||
clientSecret: process.env.GITHUB_SECRET,
|
// clientSecret: process.env.GITHUB_SECRET,
|
||||||
callbackURL: '/auth/github/callback',
|
// callbackURL: '/auth/github/callback',
|
||||||
passReqToCallback: true,
|
// passReqToCallback: true,
|
||||||
scope: ['user:email'],
|
// scope: ['user:email'],
|
||||||
}, (req, accessToken, refreshToken, profile, done) => {
|
// }, (req, accessToken, refreshToken, profile, done) => {
|
||||||
User.findOne({ github: profile.id }, (findByGithubErr, existingUser) => {
|
// User.findOne({ github: profile.id }, (findByGithubErr, existingUser) => {
|
||||||
if (existingUser) {
|
// if (existingUser) {
|
||||||
done(null, existingUser);
|
// done(null, existingUser);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const emails = getVerifiedEmails(profile.emails);
|
// const emails = getVerifiedEmails(profile.emails);
|
||||||
const primaryEmail = getPrimaryEmail(profile.emails);
|
// const primaryEmail = getPrimaryEmail(profile.emails);
|
||||||
|
|
||||||
User.findByEmail(emails, (findByEmailErr, existingEmailUser) => {
|
// User.findByEmail(emails, (findByEmailErr, existingEmailUser) => {
|
||||||
if (existingEmailUser) {
|
// if (existingEmailUser) {
|
||||||
existingEmailUser.email = existingEmailUser.email || primaryEmail;
|
// existingEmailUser.email = existingEmailUser.email || primaryEmail;
|
||||||
existingEmailUser.github = profile.id;
|
// existingEmailUser.github = profile.id;
|
||||||
existingEmailUser.username = existingEmailUser.username || profile.username;
|
// existingEmailUser.username = existingEmailUser.username || profile.username;
|
||||||
existingEmailUser.tokens.push({ kind: 'github', accessToken });
|
// existingEmailUser.tokens.push({ kind: 'github', accessToken });
|
||||||
existingEmailUser.name = existingEmailUser.name || profile.displayName;
|
// existingEmailUser.name = existingEmailUser.name || profile.displayName;
|
||||||
existingEmailUser.verified = User.EmailConfirmation.Verified;
|
// existingEmailUser.verified = User.EmailConfirmation.Verified;
|
||||||
existingEmailUser.save(saveErr => done(null, existingEmailUser));
|
// existingEmailUser.save(saveErr => done(null, existingEmailUser));
|
||||||
} else {
|
// } else {
|
||||||
const user = new User();
|
// const user = new User();
|
||||||
user.email = primaryEmail;
|
// user.email = primaryEmail;
|
||||||
user.github = profile.id;
|
// user.github = profile.id;
|
||||||
user.username = profile.username;
|
// user.username = profile.username;
|
||||||
user.tokens.push({ kind: 'github', accessToken });
|
// user.tokens.push({ kind: 'github', accessToken });
|
||||||
user.name = profile.displayName;
|
// user.name = profile.displayName;
|
||||||
user.verified = User.EmailConfirmation.Verified;
|
// user.verified = User.EmailConfirmation.Verified;
|
||||||
user.save(saveErr => done(null, user));
|
// user.save(saveErr => done(null, user));
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Sign in with Google.
|
// * Sign in with Google.
|
||||||
*/
|
// */
|
||||||
passport.use(new GoogleStrategy({
|
// passport.use(new GoogleStrategy({
|
||||||
clientID: process.env.GOOGLE_ID,
|
// clientID: process.env.GOOGLE_ID,
|
||||||
clientSecret: process.env.GOOGLE_SECRET,
|
// clientSecret: process.env.GOOGLE_SECRET,
|
||||||
callbackURL: '/auth/google/callback',
|
// callbackURL: '/auth/google/callback',
|
||||||
passReqToCallback: true,
|
// passReqToCallback: true,
|
||||||
scope: ['openid email'],
|
// scope: ['openid email'],
|
||||||
}, (req, accessToken, refreshToken, profile, done) => {
|
// }, (req, accessToken, refreshToken, profile, done) => {
|
||||||
User.findOne({ google: profile._json.emails[0].value }, (findByGoogleErr, existingUser) => {
|
// User.findOne({ google: profile._json.emails[0].value }, (findByGoogleErr, existingUser) => {
|
||||||
if (existingUser) {
|
// if (existingUser) {
|
||||||
done(null, existingUser);
|
// done(null, existingUser);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const primaryEmail = profile._json.emails[0].value;
|
// const primaryEmail = profile._json.emails[0].value;
|
||||||
|
|
||||||
User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => {
|
// User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => {
|
||||||
let username = profile._json.emails[0].value.split('@')[0];
|
// let username = profile._json.emails[0].value.split('@')[0];
|
||||||
User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => {
|
// User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => {
|
||||||
if (existingUsernameUser) {
|
// if (existingUsernameUser) {
|
||||||
const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
|
// const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
|
||||||
username = slugify(`${username} ${adj}`);
|
// username = slugify(`${username} ${adj}`);
|
||||||
}
|
// }
|
||||||
// what if a username is already taken from the display name too?
|
// // what if a username is already taken from the display name too?
|
||||||
// then, append a random friendly word?
|
// // then, append a random friendly word?
|
||||||
if (existingEmailUser) {
|
// if (existingEmailUser) {
|
||||||
existingEmailUser.email = existingEmailUser.email || primaryEmail;
|
// existingEmailUser.email = existingEmailUser.email || primaryEmail;
|
||||||
existingEmailUser.google = profile._json.emails[0].value;
|
// existingEmailUser.google = profile._json.emails[0].value;
|
||||||
existingEmailUser.username = existingEmailUser.username || username;
|
// existingEmailUser.username = existingEmailUser.username || username;
|
||||||
existingEmailUser.tokens.push({ kind: 'google', accessToken });
|
// existingEmailUser.tokens.push({ kind: 'google', accessToken });
|
||||||
existingEmailUser.name = existingEmailUser.name || profile._json.displayName;
|
// existingEmailUser.name = existingEmailUser.name || profile._json.displayName;
|
||||||
existingEmailUser.verified = User.EmailConfirmation.Verified;
|
// existingEmailUser.verified = User.EmailConfirmation.Verified;
|
||||||
existingEmailUser.save((saveErr) => {
|
// existingEmailUser.save((saveErr) => {
|
||||||
if (saveErr) {
|
// if (saveErr) {
|
||||||
console.log(saveErr);
|
// console.log(saveErr);
|
||||||
}
|
// }
|
||||||
done(null, existingEmailUser);
|
// done(null, existingEmailUser);
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
const user = new User();
|
// const user = new User();
|
||||||
user.email = primaryEmail;
|
// user.email = primaryEmail;
|
||||||
user.google = profile._json.emails[0].value;
|
// user.google = profile._json.emails[0].value;
|
||||||
user.username = username;
|
// user.username = username;
|
||||||
user.tokens.push({ kind: 'google', accessToken });
|
// user.tokens.push({ kind: 'google', accessToken });
|
||||||
user.name = profile._json.displayName;
|
// user.name = profile._json.displayName;
|
||||||
user.verified = User.EmailConfirmation.Verified;
|
// user.verified = User.EmailConfirmation.Verified;
|
||||||
user.save((saveErr) => {
|
// user.save((saveErr) => {
|
||||||
if (saveErr) {
|
// if (saveErr) {
|
||||||
console.log(saveErr);
|
// console.log(saveErr);
|
||||||
}
|
// }
|
||||||
done(null, user);
|
// done(null, user);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}));
|
// }));
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Project from '../models/project';
|
||||||
import User from '../models/user';
|
import User from '../models/user';
|
||||||
import { resolvePathToFile } from '../utils/filePath';
|
import { resolvePathToFile } from '../utils/filePath';
|
||||||
import generateFileSystemSafeName from '../utils/generateFileSystemSafeName';
|
import generateFileSystemSafeName from '../utils/generateFileSystemSafeName';
|
||||||
|
import mime from 'mime-types';
|
||||||
|
|
||||||
export { default as createProject, apiCreateProject } from './project.controller/createProject';
|
export { default as createProject, apiCreateProject } from './project.controller/createProject';
|
||||||
export { default as deleteProject } from './project.controller/deleteProject';
|
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' });
|
return res.status(404).send({ message: 'Asset does not exist' });
|
||||||
}
|
}
|
||||||
if (!resolvedFile.url) {
|
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);
|
return res.send(resolvedFile.content);
|
||||||
}
|
}
|
||||||
request({ method: 'GET', url: resolvedFile.url, encoding: null }, (innerErr, response, body) => {
|
request({ method: 'GET', url: resolvedFile.url, encoding: null }, (innerErr, response, body) => {
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
const defaultSketch = `function setup() {
|
const defaultSketch = `// liveUpdate
|
||||||
createCanvas(400, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw() {
|
function drawMask(detection) {
|
||||||
background(220);
|
stroke('red')
|
||||||
}`;
|
strokeWeight(2)
|
||||||
|
rect(detection.x, detection.y, detection.width, detection.height)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const defaultHTML =
|
const defaultHTML =
|
||||||
`<!DOCTYPE html>
|
`<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
|
<script src="/assets/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/ml5.js"></script>
|
||||||
|
<script src="/assets/webcam.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
|
@ -29,6 +31,8 @@ const defaultCSS =
|
||||||
}
|
}
|
||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ import embedRoutes from './routes/embed.routes';
|
||||||
import assetRoutes from './routes/asset.routes';
|
import assetRoutes from './routes/asset.routes';
|
||||||
import { requestsOfTypeJSON } from './utils/requestsOfType';
|
import { requestsOfTypeJSON } from './utils/requestsOfType';
|
||||||
|
|
||||||
|
import User from './models/user';
|
||||||
|
|
||||||
import { renderIndex } from './views/index';
|
import { renderIndex } from './views/index';
|
||||||
import { get404Sketch } from './views/404Page';
|
import { get404Sketch } from './views/404Page';
|
||||||
|
|
||||||
|
@ -48,6 +50,8 @@ if (process.env.BASIC_USERNAME && process.env.BASIC_PASSWORD) {
|
||||||
|
|
||||||
const allowedCorsOrigins = [
|
const allowedCorsOrigins = [
|
||||||
/p5js\.org$/,
|
/p5js\.org$/,
|
||||||
|
/digitalplayground\.nl$/,
|
||||||
|
/rubenvandeven\.com$/
|
||||||
];
|
];
|
||||||
|
|
||||||
// to allow client-only development
|
// to allow client-only development
|
||||||
|
@ -90,6 +94,18 @@ app.use(
|
||||||
app.use(Express.static(path.resolve(__dirname, '../dist/static'), {
|
app.use(Express.static(path.resolve(__dirname, '../dist/static'), {
|
||||||
maxAge: process.env.STATIC_MAX_AGE || (process.env.NODE_ENV === 'production' ? '1d' : '0')
|
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.urlencoded({ limit: '50mb', extended: true }));
|
||||||
app.use(bodyParser.json({ limit: '50mb' }));
|
app.use(bodyParser.json({ limit: '50mb' }));
|
||||||
|
@ -135,15 +151,15 @@ app.use('/', serverRoutes);
|
||||||
app.use(assetRoutes);
|
app.use(assetRoutes);
|
||||||
|
|
||||||
app.use('/', embedRoutes);
|
app.use('/', embedRoutes);
|
||||||
app.get('/auth/github', passport.authenticate('github'));
|
// app.get('/auth/github', passport.authenticate('github'));
|
||||||
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
|
// app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
|
||||||
res.redirect('/');
|
// res.redirect('/');
|
||||||
});
|
// });
|
||||||
|
|
||||||
app.get('/auth/google', passport.authenticate('google'));
|
// app.get('/auth/google', passport.authenticate('google'));
|
||||||
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => {
|
// app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => {
|
||||||
res.redirect('/');
|
// res.redirect('/');
|
||||||
});
|
// });
|
||||||
|
|
||||||
// configure passport
|
// configure passport
|
||||||
require('./config/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.
|
// Handle missing routes.
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
res.status(404);
|
res.status(404);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import mg from 'nodemailer-mailgun-transport';
|
// import mg from 'nodemailer-mailgun-transport';
|
||||||
|
|
||||||
const auth = {
|
const auth = {
|
||||||
api_key: process.env.MAILGUN_KEY,
|
api_key: process.env.MAILGUN_KEY,
|
||||||
|
@ -12,7 +12,10 @@ const auth = {
|
||||||
|
|
||||||
class Mail {
|
class Mail {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.client = nodemailer.createTransport(mg({ auth }));
|
this.client = nodemailer.createTransport({
|
||||||
|
streamTransport: true,
|
||||||
|
// newline: 'windows'
|
||||||
|
});
|
||||||
this.sendOptions = {
|
this.sendOptions = {
|
||||||
from: process.env.EMAIL_SENDER,
|
from: process.env.EMAIL_SENDER,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function renderIndex() {
|
||||||
${process.env.NODE_ENV === 'production' ? `<link rel='stylesheet' href='${assetsManifest['/app.css']}' />` : ''}
|
${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=Inconsolata' rel='stylesheet' type='text/css'>
|
||||||
<link href='https://fonts.googleapis.com/css?family=Montserrat:400,700' 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>
|
<script>
|
||||||
if (!window.process) {
|
if (!window.process) {
|
||||||
window.process = {};
|
window.process = {};
|
||||||
|
@ -29,6 +29,7 @@ export function renderIndex() {
|
||||||
window.process.env.CLIENT = true;
|
window.process.env.CLIENT = true;
|
||||||
window.process.env.LOGIN_ENABLED = ${process.env.LOGIN_ENABLED === 'false' ? false : 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.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_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.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};
|
window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined};
|
||||||
|
@ -42,13 +43,6 @@ export function renderIndex() {
|
||||||
</div>
|
</div>
|
||||||
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
|
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
|
||||||
<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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|