Compare commits

..

4 commits

Author SHA1 Message Date
Ruben van de Ven
10ed1de810 fixes to draw the face canvas 2020-09-21 14:57:54 +02:00
Ruben van de Ven
793d7e6521 WIP live reloading editor 2020-09-21 12:22:19 +02:00
Ruben van de Ven
21a5d698b1 DP logo and colours 2020-09-09 12:13:51 +02:00
Ruben van de Ven
a548e6937c Nav supports EXAMPLE_USERNAME (part of code only, rest in server/views/index.js) 2020-09-09 11:54:03 +02:00
11 changed files with 306 additions and 219 deletions

View file

@ -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}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -321,7 +321,6 @@ class Editor extends React.Component {
'editor--options': this.props.editorOptionsVisible 'editor--options': this.props.editorOptionsVisible
}); });
console.log(this.props.file);
const editorHolderClass = classNames({ const editorHolderClass = classNames({
'editor-holder': true, 'editor-holder': true,
'editor-holder--hidden': this.props.file.fileType === 'folder' || this.props.file.url 'editor-holder--hidden': this.props.file.fileType === 'folder' || this.props.file.url

View file

@ -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;
@ -327,17 +329,77 @@ class PreviewFrame extends React.Component {
renderSketch() { renderSketch() {
const doc = this.iframeElement; const doc = this.iframeElement;
const localFiles = this.injectLocalFiles(); // const localFiles = this.injectLocalFiles();
if (this.props.isPlaying) { console.log('renderSketch()', this.props);
this.props.clearConsole();
srcDoc.set(doc, localFiles); const changedFiles = this.props.files.filter(file => file.changed);
if (this.props.endSketchRefresh) { const filesRequiringReload = changedFiles.filter(file => !file.content.startsWith('// liveUpdate'));
this.props.endSketchRefresh(); const filesToHotSwap = changedFiles.filter(file => filesRequiringReload.indexOf(file) === -1);
} console.log('changed and requiring reload:', changedFiles, filesRequiringReload, filesToHotSwap);
let saving;
if (changedFiles.length > 0) {
saving = this.props.saveProject();
} else { } else {
doc.srcdoc = ''; // can be done pretier: a promise that always resolves
srcDoc.set(doc, ' '); saving = new Promise((resolve, err) => resolve());
} }
saving.catch(() => {
console.log('Error saving... not authenticated?');
window.location.href = '/login';
});
saving.then(() => {
if (this.props.isPlaying) {
this.props.clearConsole();
doc.removeAttribute('srcdoc');
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}`;
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;
}
// lastUpdate = new Date();
if (this.props.endSketchRefresh) {
this.props.endSketchRefresh();
}
} else {
doc.removeAttribute('src');
doc.srcdoc = '';
srcDoc.set(doc, ' ');
}
});
} }
render() { render() {
@ -384,7 +446,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,
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.isRequired, clearConsole: PropTypes.func.isRequired,
unsavedChanges: PropTypes.bool,
cmController: PropTypes.shape({ cmController: PropTypes.shape({
getContent: PropTypes.func getContent: PropTypes.func
}), }),
@ -392,6 +465,7 @@ PreviewFrame.propTypes = {
PreviewFrame.defaultProps = { PreviewFrame.defaultProps = {
fullView: false, fullView: false,
unsavedChanges: false,
cmController: {} cmController: {}
}; };

View file

@ -173,6 +173,21 @@ class IDEView extends React.Component {
clearTimeout(this.autosaveInterval); clearTimeout(this.autosaveInterval);
this.autosaveInterval = null; this.autosaveInterval = null;
} }
saveProject() {
// return a Promise to save or null
if (
isUserOwner(this.props) ||
(this.props.user.authenticated && !this.props.project.owner)
) {
console.log('project to save:', this.props.project);
return this.props.saveProject(this.cmController.getContent());
} else if (this.props.user.authenticated) {
return this.props.cloneProject();
}
this.props.showErrorModal('forceAuthentication');
return null;
}
handleGlobalKeydown(e) { handleGlobalKeydown(e) {
// 83 === s // 83 === s
if ( if (
@ -181,16 +196,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 +393,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>

View file

@ -147,8 +147,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 +186,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) {

View file

@ -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;

View file

@ -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() {

View file

@ -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',

View file

@ -1,110 +1,113 @@
console.log('p5 version:', p5); console.log('p5 version:', p5);
console.log('ml5 version:', ml5); console.log('ml5 version:', ml5);
console.log(location.origin);
let assets = {}; let assets = {};
var draw = function () { var draw = function () {
// //test
// background(parseInt(Math.random()*255),parseInt(Math.random()*255),parseInt(Math.random()*255));
// image(video, -width/2, -height/2, width, height);
// console.log(detections)
}; };
var gotResults = function(err, result) { // var gotResults = function(err, result) {
if (err) { // if (err) {
console.log(err) // console.log(err)
return // return
} // }
}; // };
function code_error(type, error) { // function code_error(type, error) {
window.parent.postMessage({ // window.parent.postMessage({
'type': type, // 'type': type,
'error': error.message, // 'error': error.message,
'name': error.name, // 'name': error.name,
'line': error.lineNumber - 2, // seems it giveswrong line numbers // 'line': error.lineNumber - 2, // seems it giveswrong line numbers
'column': error.columnNumber // 'column': error.columnNumber
}, '*'); // }, '*');
} // }
function no_code_error(type){ // function no_code_error(type){
window.parent.postMessage({ // window.parent.postMessage({
'type': type, // 'type': type,
'error': null // 'error': null
}, '*'); // }, '*');
} // }
window.addEventListener("message", function (e) { // window.addEventListener("message", function (e) {
if (event.origin !== window.location.origin) { // if (event.origin !== window.location.origin) {
console.error("Invalid origin of message. Ignored"); // console.error("Invalid origin of message. Ignored");
return; // return;
} // }
console.debug("receive", e.data); // console.debug("receive", e.data);
switch (e.data.action) { // switch (e.data.action) {
case 'asset': // case 'asset':
if(e.data.content === null){ // if(e.data.content === null){
delete assets[e.data.id]; // delete assets[e.data.id];
} else { // } else {
assets[e.data.id] = loadImage(e.data.content); // assets[e.data.id] = loadImage(e.data.content);
} // }
break; // break;
case 'code': // case 'code':
let f = new Function(""); // let f = new Function("");
try { // try {
f = new Function(e.data.draw); // f = new Function(e.data.draw);
no_code_error('syntax'); // no_code_error('syntax');
} catch (error) { // } catch (error) {
code_error('syntax', error); // code_error('syntax', error);
// window.parent.postMessage({'syntax': error.lineNumber}); // // window.parent.postMessage({'syntax': error.lineNumber});
} // }
handleResults = f; // handleResults = f;
break; // break;
default: // default:
console.error("Invalid action", e.data.action); // console.error("Invalid action", e.data.action);
break; // break;
} // }
}); // });
let faceapi; let faceapi;
var video; var video;
var detections; var lastFrame;
var graphics; var detections = [];
let running = true;
function pause() { // function pause() {
if (running) // if (running)
running = false; // running = false;
else { // else {
running = true; // running = true;
faceapi.detect(gotResults); // faceapi.detect(gotResults);
} // }
} // }
// by default all options are set to true // by default all options are set to true
const detection_options = { const detection_options = {
withLandmarks: true, withLandmarks: true,
withDescriptors: false, withDescriptors: false,
minConfidence: 0.5, minConfidence: 0.5,
Mobilenetv1Model: '/assets/faceapi', Mobilenetv1Model: window.parent.location.origin + '/assets/faceapi',
FaceLandmarkModel: '/assets/faceapi', FaceLandmarkModel: window.parent.location.origin + '/assets/faceapi',
FaceLandmark68TinyNet: '/assets/faceapi', FaceLandmark68TinyNet: window.parent.location.origin + '/assets/faceapi',
FaceRecognitionModel: '/assets/faceapi', FaceRecognitionModel: window.parent.location.origin + '/assets/faceapi',
TinyFaceDetectorModel: window.parent.location.origin + '/assets/faceapi',
} }
function setup() { function setup() {
createCanvas(1280,720, WEBGL); // createCanvas(1280,720, WEBGL);
createCanvas(540,420, WEBGL);
smooth(); smooth();
noFill(); noFill();
push();
translate(-width/2, -height/2);
let constraints = { let constraints = {
video: { video: {
width: { min: 720 }, width: { min: 720 },
@ -113,9 +116,9 @@ function setup() {
audio: false audio: false
}; };
// graphics = createGraphics();
video = createCapture(constraints); video = createCapture(constraints);
console.log(video.videoWidth); lastFrame = createGraphics(video.width, video.height);
console.log(video); console.log(video);
// HeadGazeSetup(video); // HeadGazeSetup(video);
// video.size(width, height); // video.size(width, height);
@ -133,90 +136,27 @@ var handleResults = function(){
background((millis()/100)%255,0,0); background((millis()/100)%255,0,0);
image(video, -width/2 + 10, -height/2 + 10, width - 20, height -20); image(video, -width/2 + 10, -height/2 + 10, width - 20, height -20);
}; };
gotResults = function(err, result) { gotResults = function(err, result) {
if (err) { if (err) {
console.log(err) console.log(err)
return return
} }
// console.log(result)
detections = result;
try{
push();
translate(-width/2, -height/2);
handleResults();
pop();
no_code_error('runtime');
}catch(error){code_error('runtime', error);}
// // background(220);
// background(255);
// push();
// // with WEBGL, the coordinate system is 0,0 in the center.
// translate(-width / 2, -height / 2, 0);
// image(video, 0, 0, width, height);
// // image(video, 0,0, width, height)
// if (detections) {
// if (detections.length > 0) {
// // console.log(detections)
// drawBox(detections)
// drawLandmarks(detections)
// for (let detection of detections) {
// let t = HeadGazeDetect(detection);
// let rot = vecToRotation(t.rotation);
// document.getElementById('yaw').value = rot[0];
// document.getElementById('roll').value = rot[1];
// document.getElementById('pitch').value = rot[2];
// // let gaze = getMappedVectors()
// // noFill();
// // stroke(161, 255, 0,100);
// // strokeWeight(2);
// // beginShape();
// // vertex(gaze[0].x,gaze[0].y);
// // vertex(gaze[1].x,gaze[1].y);
// // endShape();
// // stroke(255, 255, 0,100);
// // beginShape();
// // vertex(gaze[0].x,gaze[0].y);
// // vertex(gaze[2].x,gaze[2].y);
// // endShape();
// // stroke(0, 0, 255,100);
// // beginShape();
// // vertex(gaze[0].x,gaze[0].y);
// // vertex(gaze[3].x,gaze[3].y);
// // endShape();
// // normalMaterial();
// push();
// console.log('translate', t.translation.data64F);
// // texture(graphics);
// translate(width/2, height/2, 10);
// // plane(70);
// // translate(t.translation.data64F[0], t.translation.data64F[1], t.translation.data64F[2])
// // rotateX(-rot[2]);
// rotateY(rot[0]);
// // rotateZ(rot[1]);
// stroke(255, 0, 0);
// // texture(graphics);
// plane(70);
// pop();
// }
// }
// }
// pop();
if (running) // store data for async draw function
faceapi.detect(gotResults); detections = result;
// 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);
}
// lastFrame.background('red');
lastFrame.image(video, 0,0, video.width, video.height);
faceapi.detect(gotResults);
} }
function drawBox(detections) { function drawBox(detections) {
@ -236,36 +176,36 @@ function drawBox(detections) {
} }
function drawLandmarks(detections) { function drawLandmarks(detection) {
// noFill(); // for (let i = 0; i < detections.length; i++) {
// stroke(161, 95, 251) const mouth = detection.parts.mouth;
// strokeWeight(2) 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;
for (let i = 0; i < detections.length; i++) { drawPart(mouth, true);
const mouth = detections[i].parts.mouth; drawPart(nose, true);
const nose = detections[i].parts.nose; drawPart(leftEye, true);
const leftEye = detections[i].parts.leftEye; drawPart(leftEyeBrow, false);
const rightEye = detections[i].parts.rightEye; drawPart(rightEye, true);
const rightEyeBrow = detections[i].parts.rightEyeBrow; drawPart(rightEyeBrow, false);
const leftEyeBrow = detections[i].parts.leftEyeBrow; drawPart(jawOutline, false);
const jawOutline = detections[i].parts.jawOutline;
drawPart(mouth, true); // }
drawPart(nose, true);
drawPart(leftEye, true);
drawPart(leftEyeBrow, false);
drawPart(rightEye, true);
drawPart(rightEyeBrow, false);
drawPart(jawOutline, false);
}
} }
function drawPart(feature, closed) { function drawPart(feature, closed) {
beginShape(); beginShape();
const factor_x = width / video.width;
const factor_y = height / video.height;
for (let i = 0; i < feature.length; i++) { for (let i = 0; i < feature.length; i++) {
const x = feature[i]._x const x = feature[i]._x *factor_x;
const y = feature[i]._y const y = feature[i]._y * factor_y;
vertex(x, y) vertex(x, y)
} }
@ -275,4 +215,36 @@ function drawPart(feature, closed) {
endShape(); endShape();
} }
}
/**
* Wrapper around p5.js color class
* @param {*} c color, either as array, or string (css name or HEX string)
*/
function getColor(c) {
if(!Array.isArray(c)) c = [c];
return new p5.Color(p5.instance, c);
}
var colors = {
red: getColor('red'),
blue: getColor('blue'),
green: getColor('green'),
};
function faceDistance(face1, face2){
// distance between faces, in pixels, not meters.. for now
}
function getBoundingBox(){
// arguments contains points, or sets of points. Find bbox
console.log(arguments)
// return {
// top:
// left:
// width:
// height:
// }
} }

View file

@ -6,7 +6,7 @@ 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
@ -17,36 +17,42 @@ services:
# - "$PWD/.env.production" # - "$PWD/.env.production"
environment: environment:
- API_URL - API_URL
- MONGO_URL
- PORT
- SESSION_SECRET
- AWS_ACCESS_KEY - AWS_ACCESS_KEY
- AWS_SECRET_KEY
- S3_BUCKET
- AWS_REGION - AWS_REGION
- GITHUB_ID - AWS_SECRET_KEY
- GITHUB_SECRET - CORS_ALLOW_LOCALHOST
- MAILGUN_DOMAIN
- MAILGUN_KEY
- EMAIL_SENDER - EMAIL_SENDER
- EMAIL_VERIFY_SECRET_TOKEN - EMAIL_VERIFY_SECRET_TOKEN
- S3_BUCKET_URL_BASE - EXAMPLE_USERNAME
- GG_EXAMPLES_USERNAME
- GG_EXAMPLES_PASS
- GG_EXAMPLES_EMAIL
- GOOGLE_ID
- GOOGLE_SECRET
- EXAMPLE_USER_EMAIL - EXAMPLE_USER_EMAIL
- EXAMPLE_USER_PASSWORD - EXAMPLE_USER_PASSWORD
- GG_EXAMPLES_USERNAME
- GG_EXAMPLES_EMAIL
- GG_EXAMPLES_PASS
- ML5_EXAMPLES_USERNAME - ML5_EXAMPLES_USERNAME
- ML5_EXAMPLES_PASS
- ML5_EXAMPLES_EMAIL - ML5_EXAMPLES_EMAIL
- ML5_EXAMPLES_PASS
- GITHUB_ID
- GITHUB_SECRET
- GOOGLE_ID
- GOOGLE_SECRET
- MAILGUN_DOMAIN
- MAILGUN_KEY
- MONGO_URL
- PORT
- S3_BUCKET
- S3_BUCKET_URL_BASE
- SESSION_SECRET
- UI_ACCESS_TOKEN_ENABLED
- UPLOAD_LIMIT
- MOBILE_ENABLED
# you can either set this in your .env or as an environment variables # 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: