* 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:
Enrique Piqueras 2017-01-06 10:08:03 -08:00 committed by Cassie Tarakajian
parent 9886e53a7c
commit 5e4b076b93
9 changed files with 3006 additions and 12 deletions

View 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

View file

@ -25,6 +25,7 @@ window.HTMLHint = HTMLHint;
const beepUrl = require('../../../sounds/audioAlert.mp3');
import InlineSVG from 'react-inlinesvg';
const downArrowUrl = require('../../../images/down-arrow.svg');
const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg');
import classNames from 'classnames';
import { debounce } from 'lodash';
@ -207,8 +208,10 @@ class Editor extends React.Component {
<InlineSVG src={rightArrowUrl} />
</button>
<div className="editor__file-name">
<span>{this.props.file.name}
{this.props.unsavedChanges ? '*' : null}</span>
<span>
{this.props.file.name}
{this.props.unsavedChanges ? <InlineSVG src={unsavedChangesDotUrl} /> : null}
</span>
<Timer
projectSavedTime={this.props.projectSavedTime}
/>

View file

@ -5,6 +5,7 @@ import moment from 'moment';
import { Link, browserHistory } from 'react-router';
import * as SketchActions from '../actions/projects';
import * as ProjectActions from '../actions/project';
import * as ToastActions from '../actions/toast';
import InlineSVG from 'react-inlinesvg';
const exitUrl = require('../../../images/exit.svg');
const trashCan = require('../../../images/trash-can.svg');
@ -20,6 +21,13 @@ class SketchList extends React.Component {
document.getElementById('sketchlist').focus();
}
componentDidUpdate() {
if (this.props.sketches.length === 0) {
this.props.setToastText('No sketches were found.');
this.props.showToast(3000);
}
}
closeSketchList() {
browserHistory.push(this.props.previousPath);
}
@ -84,7 +92,9 @@ SketchList.propTypes = {
sketches: PropTypes.array.isRequired,
username: PropTypes.string,
deleteProject: PropTypes.func.isRequired,
previousPath: PropTypes.string.isRequired
previousPath: PropTypes.string.isRequired,
showToast: PropTypes.func.isRequired,
setToastText: PropTypes.func.isRequired
};
function mapStateToProps(state) {
@ -95,7 +105,7 @@ function mapStateToProps(state) {
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions), dispatch);
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions), dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);

File diff suppressed because it is too large Load diff

View file

@ -89,17 +89,21 @@ export function getProjects(req, res) {
export function getProjectsForUser(req, res) {
if (req.params.username) {
User.findOne({ username: req.params.username }, (err, user) => {
Project.find({ user: user._id }) // eslint-disable-line no-underscore-dangle
.sort('-createdAt')
.select('name files id createdAt updatedAt')
.exec((err, projects) => {
res.json(projects);
});
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
.sort('-createdAt')
.select('name files id createdAt updatedAt')
.exec((err, projects) => res.json(projects));
}
return null;
});
} else {
// could just move this to client side
return res.json([]);
}
return null;
}
function buildZip(project, req, res) {
@ -153,4 +157,3 @@ export function downloadProjectAsZip(req, res) {
buildZip(project, req, res);
});
}

View file

@ -164,3 +164,9 @@ export function updatePassword(req, res) {
// eventually send email that the password has been reset
}
export function userExists(username, callback) {
User.findOne({ username }, (err, user) => (
user ? callback(true) : callback(false)
));
}

View file

@ -2,6 +2,8 @@ import { Router } from 'express';
const router = new Router();
import path from 'path';
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
// until i figure out isomorphic rendering
@ -47,7 +49,9 @@ router.route('/about').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;

View file

@ -33,6 +33,7 @@ import serverRoutes from './routes/server.routes';
import embedRoutes from './routes/embed.routes';
import { renderIndex } from './views/index';
import { get404Sketch } from './views/404Page';
// Body parser, cookie parser, sessions, serve public assets
@ -88,6 +89,20 @@ app.get('/', (req, res) => {
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
app.listen(serverConfig.port, (error) => {
if (!error) {

114
server/views/404Page.js Normal file
View 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);
});
}
});
}