* 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');
|
||||
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}
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
|
|
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) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
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