Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate the 404 page using React on the frontend. #2168

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions client/modules/IDE/pages/NotFound.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import axios from 'axios';
import React, { useEffect, useMemo, useState } from 'react';
import Helmet from 'react-helmet';
import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import RootPage from '../../../components/RootPage';
import { remSize } from '../../../theme';
import EmbedFrame from '../../Preview/EmbedFrame';
import Nav from '../components/Header/Nav';

const NotFoundMain = styled.main`
& .header {
height: ${remSize(200)};
width: 100%;
z-index: 1;
background: white;
color: #ed225d;
font-family: Montserrat, sans-serif;
text-align: center;
display: table;
}
& h1 {
font-size: ${remSize(32)};
}
& .message-container {
display: table-cell;
vertical-align: middle;
}
& .message {
color: #6b6b6b;
margin: ${remSize(10)};
font-weight: bold;
}
& .home-link {
color: #b5b5b5;
text-decoration: none;
}
& .sketch-info {
height: ${remSize(30)};
display: flex;
padding-inline-start: ${remSize(10)};
}
`;

async function getRandomProject(username) {
// Find example projects
const response = await axios.get(`/editor/${username}/projects`);
const projects = response.data;
if (!projects.length) return null;

// Choose a random sketch
const randomIndex = Math.floor(Math.random() * projects.length);
return projects[randomIndex];
}

const username = 'p5';

export default function NotFound() {
const { t } = useTranslation();

const [sketch, setSketch] = useState(null);

useEffect(() => {
getRandomProject(username).then((project) => setSketch(project));
}, []);

const files = useMemo(() => {
const instanceMode = (sketch?.files || [])
.find((file) => file.name === 'sketch.js')
?.content?.includes('Instance Mode');

return (sketch?.files || []).map((file) => ({
...file,
// Change canvas size
content: file.content.replace(
/createCanvas\(\d+, ?\d+/g,
instanceMode
? 'createCanvas(p.windowWidth, p.windowHeight'
: 'createCanvas(windowWidth, windowHeight'
)
}));
}, [sketch]);

return (
<RootPage>
<Nav layout="dashboard" />
<Helmet>
<title>{t('NotFound.Title')}</title>
</Helmet>
<NotFoundMain>
<div className="header">
<div className="message-container">
<h1>{t('NotFound.404PageNotFound')}</h1>
<h6 className="message">{t('NotFound.DoesNotExist')}</h6>
<h6 className="message">
<Trans
i18nKey="NotFound.CheckURL"
components={[<Link to="/" className="home-link" />]}
/>
</h6>
</div>
</div>
{sketch ? (
<div className="sketch-info nav preview-nav">
<div className="nav__items-left">
<Link
className="nav__item"
to={`/${username}/sketches/${sketch.id}`}
>
{sketch.name}
</Link>
<span className="toolbar__project-owner">{t('Toolbar.By')}</span>
<Link className="nav__item" to={`/${username}/sketches/`}>
{username}
</Link>
</div>
</div>
) : null}
</NotFoundMain>
{sketch ? (
<div
style={{
position: 'relative',
width: '100%',
height: window.innerHeight - (200 + 42 + 30)
}}
>
<EmbedFrame
files={files}
textOutput={false}
gridOutput={false}
sendMessages={false}
isPlaying
basePath={window.location.pathname}
/>
</div>
) : null}
</RootPage>
);
}
56 changes: 34 additions & 22 deletions client/modules/Preview/EmbedFrame.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,7 @@ function addLoopProtect(sketchDoc) {
}

function injectLocalFiles(files, htmlFile, options) {
const { basePath, gridOutput, textOutput } = options;
let scriptOffs = [];
const { basePath, gridOutput, textOutput, sendMessages } = options;
objectUrls = {};
objectPaths = {};
const resolvedFiles = resolveJSAndCSSLinks(files);
Expand Down Expand Up @@ -247,24 +246,28 @@ function injectLocalFiles(files, htmlFile, options) {
}
}

const previewScripts = sketchDoc.createElement('script');
previewScripts.src = `${window.location.origin}${getConfig(
'PREVIEW_SCRIPTS_URL'
)}`;
previewScripts.setAttribute('crossorigin', '');
sketchDoc.body.appendChild(previewScripts);
if (sendMessages) {
const previewScripts = sketchDoc.createElement('script');
previewScripts.src = `${window.location.origin}${getConfig(
'PREVIEW_SCRIPTS_URL'
)}`;
previewScripts.setAttribute('crossorigin', '');
sketchDoc.body.appendChild(previewScripts);

const sketchDocString = `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
scriptOffs = getAllScriptOffsets(sketchDocString);
const consoleErrorsScript = sketchDoc.createElement('script');
consoleErrorsScript.innerHTML = `
window.offs = ${JSON.stringify(scriptOffs)};
window.objectUrls = ${JSON.stringify(objectUrls)};
window.objectPaths = ${JSON.stringify(objectPaths)};
window.editorOrigin = '${getConfig('EDITOR_URL')}';
`;
addLoopProtect(sketchDoc);
sketchDoc.head.prepend(consoleErrorsScript);
const sketchDocString = `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
const scriptOffs = getAllScriptOffsets(sketchDocString);
const consoleErrorsScript = sketchDoc.createElement('script');
consoleErrorsScript.innerHTML = `
window.offs = ${JSON.stringify(scriptOffs)};
window.objectUrls = ${JSON.stringify(objectUrls)};
window.objectPaths = ${JSON.stringify(objectPaths)};
window.editorOrigin = '${getConfig('EDITOR_URL')}';
`;
addLoopProtect(sketchDoc);
sketchDoc.head.prepend(consoleErrorsScript);
} else {
addLoopProtect(sketchDoc);
}

return `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
}
Expand All @@ -273,7 +276,14 @@ function getHtmlFile(files) {
return files.filter((file) => file.name.match(/.*\.html$/i))[0];
}

function EmbedFrame({ files, isPlaying, basePath, gridOutput, textOutput }) {
function EmbedFrame({
files,
isPlaying,
basePath,
gridOutput,
textOutput,
sendMessages
}) {
const iframe = useRef();
const htmlFile = useMemo(() => getHtmlFile(files), [files]);

Expand All @@ -293,7 +303,8 @@ function EmbedFrame({ files, isPlaying, basePath, gridOutput, textOutput }) {
const htmlDoc = injectLocalFiles(files, htmlFile, {
basePath,
gridOutput,
textOutput
textOutput,
sendMessages
});
const generatedHtmlFile = {
name: 'index.html',
Expand Down Expand Up @@ -331,7 +342,8 @@ EmbedFrame.propTypes = {
isPlaying: PropTypes.bool.isRequired,
basePath: PropTypes.string.isRequired,
gridOutput: PropTypes.bool.isRequired,
textOutput: PropTypes.bool.isRequired
textOutput: PropTypes.bool.isRequired,
sendMessages: PropTypes.bool.isRequired
};

export default EmbedFrame;
1 change: 1 addition & 0 deletions client/modules/Preview/previewIndex.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const App = () => {
basePath={basePath}
gridOutput={gridOutput}
textOutput={textOutput}
sendMessages
/>
</React.Fragment>
);
Expand Down
5 changes: 5 additions & 0 deletions client/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import createRedirectWithUsername from './components/createRedirectWithUsername'
import MobileDashboardView from './modules/Mobile/MobileDashboardView';
// import PrivacyPolicy from './modules/IDE/pages/PrivacyPolicy';
// import TermsOfUse from './modules/IDE/pages/TermsOfUse';
import NotFound from './modules/IDE/pages/NotFound';
import Legal from './modules/Legal/pages/Legal';
import { getUser } from './modules/User/actions';
import {
Expand Down Expand Up @@ -53,6 +54,9 @@ Route.propTypes.component = PropTypes.elementType.isRequired;

const routes = (
<Switch>
{window.__SERVER_404__ ? (
<Route path={window.__SERVER_404__} component={NotFound} />
) : null}
<Route exact path="/" component={mobileFirst(MobileIDEView, IDEView)} />
<Route
path="/login"
Expand Down Expand Up @@ -123,6 +127,7 @@ const routes = (
<Route path="/privacy-policy" component={Legal} />
<Route path="/terms-of-use" component={Legal} />
<Route path="/code-of-conduct" component={Legal} />
<Route path="*" component={NotFound} />
</Switch>
);

Expand Down
4 changes: 2 additions & 2 deletions server/controllers/embed.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import {
resolveScripts,
resolveStyles
} from '../utils/previewGeneration';
import { get404Sketch } from '../views/404Page';
import sendHtml from '../views/index';

export function serveProject(req, res) {
const projectId = req.params.project_id;
Project.findOne(
{ $or: [{ _id: projectId }, { slug: projectId }] },
(err, project) => {
if (err || !project) {
get404Sketch((html) => res.send(html));
sendHtml(req, res, false);
return;
}
// TODO this does not parse html
Expand Down
34 changes: 10 additions & 24 deletions server/routes/server.routes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Router } from 'express';
import { renderIndex } from '../views/index';
import { get404Sketch } from '../views/404Page';
import sendHtml, { renderIndex } from '../views/index';
import { userExists } from '../controllers/user.controller';
import {
projectExists,
Expand All @@ -10,9 +9,6 @@ import { collectionForUserExists } from '../controllers/collection.controller';

const router = new Router();

// const fallback404 = (res) => (exists) =>
// exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html));

// this is intended to be a temporary file
// until i figure out isomorphic rendering

Expand All @@ -28,39 +24,33 @@ router.get('/signup', (req, res) => {
});

router.get('/projects/:project_id', (req, res) => {
projectExists(req.params.project_id, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
);
projectExists(req.params.project_id, (exists) => sendHtml(req, res, exists));
});

router.get('/:username/sketches/:project_id/add-to-collection', (req, res) => {
projectForUserExists(req.params.username, req.params.project_id, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
sendHtml(req, res, exists)
);
});

router.get('/:username/sketches/:project_id', (req, res) => {
projectForUserExists(req.params.username, req.params.project_id, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
sendHtml(req, res, exists)
);
});

router.get('/:username/sketches', (req, res) => {
userExists(req.params.username, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
);
userExists(req.params.username, (exists) => sendHtml(req, res, exists));
});

router.get('/:username/full/:project_id', (req, res) => {
projectForUserExists(req.params.username, req.params.project_id, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
sendHtml(req, res, exists)
);
});

router.get('/full/:project_id', (req, res) => {
projectExists(req.params.project_id, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
);
projectExists(req.params.project_id, (exists) => sendHtml(req, res, exists));
});

router.get('/login', (req, res) => {
Expand Down Expand Up @@ -103,9 +93,7 @@ router.get('/:username/assets', (req, res) => {
const isLoggedInUser =
req.user && req.user.username === req.params.username;
const canAccess = exists && isLoggedInUser;
return canAccess
? res.send(renderIndex())
: get404Sketch((html) => res.send(html));
return sendHtml(req, res, canAccess);
});
});

Expand All @@ -123,14 +111,12 @@ router.get('/about', (req, res) => {

router.get('/:username/collections/:id', (req, res) => {
collectionForUserExists(req.params.username, req.params.id, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
sendHtml(req, res, exists)
);
});

router.get('/:username/collections', (req, res) => {
userExists(req.params.username, (exists) =>
exists ? res.send(renderIndex()) : get404Sketch((html) => res.send(html))
);
userExists(req.params.username, (exists) => sendHtml(req, res, exists));
});

router.get('/privacy-policy', (req, res) => {
Expand Down
5 changes: 2 additions & 3 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ import redirectEmbedRoutes from './routes/redirectEmbed.routes';
import passportRoutes from './routes/passport.routes';
import { requestsOfTypeJSON } from './utils/requestsOfType';

import { renderIndex } from './views/index';
import { get404Sketch } from './views/404Page';
import sendHtml, { renderIndex } from './views/index';

const app = new Express();
const MongoStore = connectMongo(session);
Expand Down Expand Up @@ -183,7 +182,7 @@ app.use('/api', (error, req, res, next) => {
app.get('*', (req, res) => {
res.status(404);
if (req.accepts('html')) {
get404Sketch((html) => res.send(html));
sendHtml(req, res, false);
return;
}
if (req.accepts('json')) {
Expand Down
Loading
Loading