* Changed unsaved changes asterisk to an svg circle. #158 * Fixed #100 Unmatched routes are handled by react-router on the client side and a single wildcard route on server.routes.js renders the index html. When the /:username/sketches route is matched and the username is not valid, the user will be redirected to the index route and a toast will explain what happened. When the username is 'p5' (default when logged out) it will show all sketches. Maybe this should be changed to just public or 'local' sketches? * Moved unsaved changes SVG to a separate file. * User not found is now a 404 error. * Added server rendered 404 page. * Removed console.log * 404 Page now renders a random p5 sketch. TODO: make 404 sketches. * Added 404 header 404 page now fetches a random example sketch * Moved circle closer to file name * Render 404 page in SketchList route if !user
This commit is contained in:
parent
9886e53a7c
commit
5e4b076b93
9 changed files with 3006 additions and 12 deletions
4
client/images/unsaved-changes-dot.svg
Normal file
4
client/images/unsaved-changes-dot.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="9.2px" height="11px" version="1.1" fill="currentColor">
|
||||||
|
<circle cx="3" cy="3" r="2.8" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 167 B |
|
@ -25,6 +25,7 @@ window.HTMLHint = HTMLHint;
|
||||||
const beepUrl = require('../../../sounds/audioAlert.mp3');
|
const beepUrl = require('../../../sounds/audioAlert.mp3');
|
||||||
import InlineSVG from 'react-inlinesvg';
|
import InlineSVG from 'react-inlinesvg';
|
||||||
const downArrowUrl = require('../../../images/down-arrow.svg');
|
const downArrowUrl = require('../../../images/down-arrow.svg');
|
||||||
|
const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg');
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
@ -207,8 +208,10 @@ class Editor extends React.Component {
|
||||||
<InlineSVG src={rightArrowUrl} />
|
<InlineSVG src={rightArrowUrl} />
|
||||||
</button>
|
</button>
|
||||||
<div className="editor__file-name">
|
<div className="editor__file-name">
|
||||||
<span>{this.props.file.name}
|
<span>
|
||||||
{this.props.unsavedChanges ? '*' : null}</span>
|
{this.props.file.name}
|
||||||
|
{this.props.unsavedChanges ? <InlineSVG src={unsavedChangesDotUrl} /> : null}
|
||||||
|
</span>
|
||||||
<Timer
|
<Timer
|
||||||
projectSavedTime={this.props.projectSavedTime}
|
projectSavedTime={this.props.projectSavedTime}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import moment from 'moment';
|
||||||
import { Link, browserHistory } from 'react-router';
|
import { Link, browserHistory } from 'react-router';
|
||||||
import * as SketchActions from '../actions/projects';
|
import * as SketchActions from '../actions/projects';
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
|
import * as ToastActions from '../actions/toast';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
import InlineSVG from 'react-inlinesvg';
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
const exitUrl = require('../../../images/exit.svg');
|
||||||
const trashCan = require('../../../images/trash-can.svg');
|
const trashCan = require('../../../images/trash-can.svg');
|
||||||
|
@ -20,6 +21,13 @@ class SketchList extends React.Component {
|
||||||
document.getElementById('sketchlist').focus();
|
document.getElementById('sketchlist').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.props.sketches.length === 0) {
|
||||||
|
this.props.setToastText('No sketches were found.');
|
||||||
|
this.props.showToast(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closeSketchList() {
|
closeSketchList() {
|
||||||
browserHistory.push(this.props.previousPath);
|
browserHistory.push(this.props.previousPath);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +92,9 @@ SketchList.propTypes = {
|
||||||
sketches: PropTypes.array.isRequired,
|
sketches: PropTypes.array.isRequired,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
deleteProject: PropTypes.func.isRequired,
|
deleteProject: PropTypes.func.isRequired,
|
||||||
previousPath: PropTypes.string.isRequired
|
previousPath: PropTypes.string.isRequired,
|
||||||
|
showToast: PropTypes.func.isRequired,
|
||||||
|
setToastText: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
@ -95,7 +105,7 @@ function mapStateToProps(state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions), dispatch);
|
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions), dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);
|
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);
|
||||||
|
|
2835
client/styles/build/css/main.css
Normal file
2835
client/styles/build/css/main.css
Normal file
File diff suppressed because it is too large
Load diff
|
@ -89,17 +89,21 @@ export function getProjects(req, res) {
|
||||||
export function getProjectsForUser(req, res) {
|
export function getProjectsForUser(req, res) {
|
||||||
if (req.params.username) {
|
if (req.params.username) {
|
||||||
User.findOne({ username: req.params.username }, (err, user) => {
|
User.findOne({ username: req.params.username }, (err, user) => {
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ message: 'User with that username does not exist.' });
|
||||||
|
} else {
|
||||||
Project.find({ user: user._id }) // eslint-disable-line no-underscore-dangle
|
Project.find({ user: user._id }) // eslint-disable-line no-underscore-dangle
|
||||||
.sort('-createdAt')
|
.sort('-createdAt')
|
||||||
.select('name files id createdAt updatedAt')
|
.select('name files id createdAt updatedAt')
|
||||||
.exec((err, projects) => {
|
.exec((err, projects) => res.json(projects));
|
||||||
res.json(projects);
|
}
|
||||||
});
|
return null;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// could just move this to client side
|
// could just move this to client side
|
||||||
return res.json([]);
|
return res.json([]);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildZip(project, req, res) {
|
function buildZip(project, req, res) {
|
||||||
|
@ -153,4 +157,3 @@ export function downloadProjectAsZip(req, res) {
|
||||||
buildZip(project, req, res);
|
buildZip(project, req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,3 +164,9 @@ export function updatePassword(req, res) {
|
||||||
|
|
||||||
// eventually send email that the password has been reset
|
// eventually send email that the password has been reset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function userExists(username, callback) {
|
||||||
|
User.findOne({ username }, (err, user) => (
|
||||||
|
user ? callback(true) : callback(false)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { Router } from 'express';
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { renderIndex } from '../views/index';
|
import { renderIndex } from '../views/index';
|
||||||
|
import { get404Sketch } from '../views/404page';
|
||||||
|
import { userExists } from '../controllers/user.controller.js';
|
||||||
|
|
||||||
// this is intended to be a temporary file
|
// this is intended to be a temporary file
|
||||||
// until i figure out isomorphic rendering
|
// until i figure out isomorphic rendering
|
||||||
|
@ -47,7 +49,9 @@ router.route('/about').get((req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.route('/:username/sketches').get((req, res) => {
|
router.route('/:username/sketches').get((req, res) => {
|
||||||
res.send(renderIndex());
|
userExists(req.params.username, (exists) => (
|
||||||
|
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import serverRoutes from './routes/server.routes';
|
||||||
import embedRoutes from './routes/embed.routes';
|
import embedRoutes from './routes/embed.routes';
|
||||||
|
|
||||||
import { renderIndex } from './views/index';
|
import { renderIndex } from './views/index';
|
||||||
|
import { get404Sketch } from './views/404Page';
|
||||||
|
|
||||||
// Body parser, cookie parser, sessions, serve public assets
|
// Body parser, cookie parser, sessions, serve public assets
|
||||||
|
|
||||||
|
@ -88,6 +89,20 @@ app.get('/', (req, res) => {
|
||||||
res.sendFile(renderIndex());
|
res.sendFile(renderIndex());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle missing routes.
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.status(404);
|
||||||
|
if (req.accepts('html')) {
|
||||||
|
get404Sketch(html => res.send(html));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.accepts('json')) {
|
||||||
|
res.send({ error: 'Not found.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.type('txt').send('Not found.');
|
||||||
|
});
|
||||||
|
|
||||||
// start app
|
// start app
|
||||||
app.listen(serverConfig.port, (error) => {
|
app.listen(serverConfig.port, (error) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
|
|
114
server/views/404Page.js
Normal file
114
server/views/404Page.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import User from '../models/user';
|
||||||
|
import Project from '../models/project';
|
||||||
|
|
||||||
|
export function get404Sketch(callback) {
|
||||||
|
User.findOne({ username: 'p5' }, (userErr, user) => { // Find p5 user
|
||||||
|
if (userErr) {
|
||||||
|
throw userErr;
|
||||||
|
} else {
|
||||||
|
Project.find({ user: user._id }, (projErr, projects) => { // Find example projects
|
||||||
|
// Choose a random sketch
|
||||||
|
const randomIndex = Math.floor(Math.random() * projects.length);
|
||||||
|
const sketch = projects[randomIndex];
|
||||||
|
let instanceMode = false;
|
||||||
|
|
||||||
|
// Get sketch files
|
||||||
|
let htmlFile = sketch.files.filter(file => file.name.match(/.*\.html$/i))[0].content;
|
||||||
|
const jsFiles = sketch.files.filter(file => file.name.match(/.*\.js$/i));
|
||||||
|
const cssFiles = sketch.files.filter(file => file.name.match(/.*\.css$/i));
|
||||||
|
const linkedFiles = sketch.files.filter(file => file.url);
|
||||||
|
|
||||||
|
instanceMode = jsFiles.find(file => file.name === 'sketch.js').content.includes('Instance Mode');
|
||||||
|
|
||||||
|
jsFiles.forEach(file => { // Add js files as script tags
|
||||||
|
const html = htmlFile.split('</body>');
|
||||||
|
html[0] = `${html[0]}<script>${file.content}</script>`;
|
||||||
|
htmlFile = html.join('</body>');
|
||||||
|
});
|
||||||
|
|
||||||
|
cssFiles.forEach(file => { // Add css files as style tags
|
||||||
|
const html = htmlFile.split('</head>');
|
||||||
|
html[0] = `${html[0]}<style>${file.content}</style>`;
|
||||||
|
htmlFile = html.join('</head>');
|
||||||
|
});
|
||||||
|
|
||||||
|
linkedFiles.forEach(file => { // Add linked files as link tags
|
||||||
|
const html = htmlFile.split('<head>');
|
||||||
|
html[1] = `<link href=${file.url}>${html[1]}`;
|
||||||
|
htmlFile = html.join('<head>');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add 404 html and position canvas
|
||||||
|
const html = htmlFile.split('</head>');
|
||||||
|
html[0] = `
|
||||||
|
${html[0]}
|
||||||
|
<title>404 Page Not Found - p5.js Web Editor</title>
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
position: fixed;
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
background: white;
|
||||||
|
color: #ed225d;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
.message-container {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
color: #6b6b6b;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
color: #b5b5b5;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
position: fixed;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Montserrat:400,700' rel='stylesheet' type='text/css'>
|
||||||
|
<link
|
||||||
|
rel='shortcut icon'
|
||||||
|
href='https://raw.githubusercontent.com/processing/p5.js-website-OLD/master/favicon.ico'
|
||||||
|
type='image/x-icon'
|
||||||
|
>
|
||||||
|
`;
|
||||||
|
html[1] = `
|
||||||
|
<div class="header">
|
||||||
|
<div class="message-container">
|
||||||
|
<h1>404 Page Not Found</h1>
|
||||||
|
<h6 class="message">The page you are trying to reach does not exist.</h6>
|
||||||
|
<h6 class="message">
|
||||||
|
Please check the URL or return to the <a href="/" class="home-link">home page</a>.
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${html[1]}
|
||||||
|
`;
|
||||||
|
htmlFile = html.join('</head>');
|
||||||
|
|
||||||
|
// Fix links to assets
|
||||||
|
htmlFile = htmlFile.replace(/'assets/g,
|
||||||
|
"'https://rawgit.com/processing/p5.js-website/master/dist/assets/examples/assets/");
|
||||||
|
htmlFile = htmlFile.replace(/"assets/g,
|
||||||
|
'"https://rawgit.com/processing/p5.js-website/master/dist/assets/examples/assets/');
|
||||||
|
|
||||||
|
// Change canvas size
|
||||||
|
htmlFile = htmlFile.replace(/createCanvas\(\d+, ?\d+/g, instanceMode ?
|
||||||
|
'createCanvas(p.windowWidth, p.windowHeight'
|
||||||
|
:
|
||||||
|
'createCanvas(windowWidth, windowHeight');
|
||||||
|
|
||||||
|
callback(htmlFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue