Skip to content
Permalink
Browse files

Get basic editing + saving workflow working

  • Loading branch information...
thesephist committed Mar 25, 2019
1 parent 85957e3 commit bd36cfb17157e6f1fcdbea1c681ec78d7a8d0855
Showing with 225 additions and 54 deletions.
  1. +2 −1 .eslintrc.js
  2. +1 −1 package.json
  3. +14 −0 src/api.js
  4. +16 −24 src/index.js
  5. +8 −4 src/models.js
  6. +0 −14 src/views.js
  7. +2 −1 static/editor.html
  8. +182 −2 static/js/main.js
  9. +0 −7 templates/base.js
@@ -97,7 +97,7 @@ module.exports = {
],
'function-paren-newline': 'off',
'generator-star-spacing': 'error',
'global-require': 'error',
'global-require': 'off', // monaco uses requirejs
'guard-for-in': 'off',
'handle-callback-err': 'error',
'id-blacklist': 'error',
@@ -299,6 +299,7 @@ module.exports = {
'single',
{
'avoidEscape': true,
'allowTemplateLiterals': true,
},
],
'radix': 'error',
@@ -8,7 +8,7 @@
"license": "MIT",
"scripts": {
"start": "node src/index.js",
"lint": "eslint ./src/*.js ./static/js/*.js templates/*.js"
"lint": "eslint ./src/*.js ./static/js/*.js"
},
"dependencies": {
"body-parser": "^1.18.3",
@@ -13,4 +13,18 @@ api.frame.post = async (_params, _query, body) => {
return frameHash;
}

api.frame.getPage = async params => {
const htmlFrame = await store.getFromFS(params.htmlFrameHash);
const jsFrame = await store.getFromFS(params.jsFrameHash);
return `
<!DOCTYPE html>
<html>
<head><title>Frame ${params.htmlFrameHash}/${params.jsFrameHash} CodeFrame</title></head>
<body>
${htmlFrame}
<script>${jsFrame}</script>
</body>
</html>`;
}

module.exports = api;
@@ -10,11 +10,11 @@ app.use(bodyParser.text({
}));

const api = require('./api.js');
const views = require('./views.js');

// STATIC ASSETS
const STATIC_PATHS = {
'/': 'index.html',
'/new': 'editor.html',
'/h/:htmlFrameHash/j/:jsFrameHash/edit': 'editor.html',
}
const respondWith = (res, static_path) => {
@@ -40,32 +40,16 @@ for (const [uri, path] of Object.entries(STATIC_PATHS)) {
}
app.use('/static', express.static('static'));

// VIEWS
const VIEW_PATHS = {
'/base': views.baseView,
'/h/:htmlFrameHash/j/:jsFrameHash/': views.liveFrameView,
}
for (const [uri, renderer] of Object.entries(VIEW_PATHS)) {
app.get(uri, (req, res) => {
try {
res.set('Content-Type', 'text/html');
const html = renderer(req.params);
if (html !== false) {
res.send(html);
} else {
respondWith(res, '404.html');
}
} catch (e) {
console.error(e);
respondWith(res, '500.html');
}
})
}

// API
const CONTENT_TYPES = {
'.js': 'text/javascript',
'.html': 'text/html',
}
const API_PATHS = {
'GET /api/frame/:frameHash': api.frame.get,
'POST /api/frame/': api.frame.post,

'GET /f/:htmlFrameHash/:jsFrameHash.html': api.frame.getPage,
}
const METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
for (const [spec, handler] of Object.entries(API_PATHS)) {
@@ -77,9 +61,17 @@ for (const [spec, handler] of Object.entries(API_PATHS)) {
throw new Error(`Method ${method} for route ${route} is not valid`);
}

let contentType = 'application/json';
for (const [ending, type] of Object.entries(CONTENT_TYPES)) {
if (route.endsWith(ending)) {
contentType = type;
}
}

appMethod(route, async (req, res) => {
try {
res.set('Content-Type', 'application/json');
res.set('Content-Type', contentType);
res.set('X-Frame-Options', 'SAMEORIGIN');
const result = await handler(req.params, req.query, req.body);
if (typeof result === 'string') {
res.send(result);
@@ -8,7 +8,7 @@ const hashFile = contents => {
const hash = crypto.createHash('sha256');
hash.update(contents);
// first 12 chars of the hex digest
return 'cf_' + hash.digest('base64').substr(0, 12);
return hash.digest('hex').substr(0, 12);
}

class SourceFileStore {
@@ -20,8 +20,12 @@ class SourceFileStore {
}
}

getPathFromHash(hash) {
return path.join(this.basePath, `cf_${hash}.frame`);
}

getHashedFilePath(contents) {
return path.join(this.basePath, `${hashFile(contents)}.frame`);
return this.getPathFromHash(hashFile(contents));
}

has(sourceFilePath) {
@@ -32,9 +36,9 @@ class SourceFileStore {
});
}

getFromFS(sourceFilePath) {
getFromFS(frameHash) {
return new Promise((res, rej) => {
fs.readFile(sourceFilePath, (err, data) => {
fs.readFile(this.getPathFromHash(frameHash), 'utf8', (err, data) => {
if (err) {
rej(err);
} else {

This file was deleted.

@@ -10,7 +10,8 @@
</head>

<body>
<script src="https://unpkg.com/torus-dom@0.4.1/dist/index.min.js"></script>
<script src="https://unpkg.com/monaco-editor/min/vs/loader.js"></script>
<script src="https://unpkg.com/torus-dom/dist/index.min.js"></script>
<script src="/static/js/main.js"></script>
</body>

@@ -1,8 +1,11 @@
const {
StyledComponent,
Record,
Router,
} = Torus;

const TEST_HASH = 'tHcltW+0gIR7';

const cfFetch = (uri, options) => {
return fetch(uri, {
credentials: 'same-origin',
@@ -21,16 +24,193 @@ const api = {
}),
}

const PreviewPane = (htmlFrameHash, jsFrameHash) => {
return jdom`<iframe
style="
height: 100%;
width: 100%;
flex-grow: 1;
"
src="/f/${htmlFrameHash}/${jsFrameHash}.html"
/>`;
}

class Editor extends StyledComponent {

init(frameRecord) {
this.mode = '';
this.frames = {
html: '<h1>\n\tHello, World!\n</h1>',
javascript: `console.log('Hello, World!');`,
}
this.initMonaco();
this.switchMode('html');

this.bind(frameRecord, data => this.render(data));
}

initMonaco() {
this.monacoContainer = document.createElement('div');
this.monacoContainer.classList.add('editorContainer');
require.config({
paths: {
vs: 'https://unpkg.com/monaco-editor/min/vs',
},
});
require(['vs/editor/editor.main'], () => {
this.monacoEditor = monaco.editor.create(this.monacoContainer, {
language: this.mode,
value: this.frames[this.mode],
});
this.monacoEditor.layout();
});
}

switchMode(mode) {
if (this.monacoEditor) {
this.frames[this.mode] = this.monacoEditor.getValue();
}
this.mode = mode;
if (this.monacoEditor) {
monaco.editor.setModelLanguage(this.monacoEditor.getModel(), mode);
this.monacoEditor.setValue(this.frames[mode]);
}
}

async saveFrames() {
this.frames[this.mode] = this.monacoEditor.getValue();
const hashes = {
html: '',
js: '',
}
await Promise.all([
api.post(`/frame/`, this.frames.html).then(resp => {
return resp.text();
}).then(hash => hashes.html = hash),
api.post(`/frame/`, this.frames.javascript).then(resp => {
return resp.text();
}).then(hash => hashes.js = hash),
]);
router.go(`/h/${hashes.html}/j/${hashes.js}/edit`);
}

styles() {
return css`
height: 100%;
width: 100%;
flex-grow: 1;
.editorContainer {
height: 100%;
width: 100%;
}
.top-bar {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
`;
}

compose(data) {
return jdom`<div class="editor">
<div class="top-bar">
<div class="tabs">
<button class="tab-html" onclick="${() => this.switchMode('html')}">HTML</button>
<button class="tab-js" onclick="${() => this.switchMode('javascript')}">JAVASCRIPT</button>
</div>
<button onclick="${() => this.saveFrames()}">Save</button>
</div>
${this.monacoContainer}
</div>`;
}

}

class Workspace extends StyledComponent {

init(frameRecord) {
this.editor = new Editor(frameRecord);

this.bind(frameRecord, data => this.render(data));
}

styles() {
return css`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
main {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
flex-grow: 1;
overflow: hidden;
}
`;
}

compose(data) {
return jdom`<div class="workspace">
<header>
<div class="logo">
<a href="/">Codeframe</a>
</div>
</header>
<main>
${PreviewPane(data.htmlFrameHash, data.jsFrameHash)}
${this.editor.node}
</main>
</div>`;
}

}

class App extends StyledComponent {

init(router) {
this.frameRecord = new Record({
htmlFrameHash: '',
jsFrameHash: '',
});
this.workspace = new Workspace(this.frameRecord);

this.bind(router, ([name, params]) => {
switch (name) {
case 'edit':
this.frameRecord.update({
htmlFrameHash: params.htmlFrameHash,
jsFrameHash: params.jsFrameHash,
});
break;
default:
// open blank files?
router.go(`/h/${TEST_HASH}/j/${TEST_HASH}/edit`);
break;
}
})
}

styles() {
return css`
width: 100%;
height: 100vh;
`;
}

compose() {
return jdom`<h1>editor</h1>`;
return jdom`<div id="root">
${this.workspace.node}
</div>`;
}

}

const router = new Router({
default: '/',
edit: '/h/:htmlFrameHash/j/:jsFrameHash/edit',
default: '/new',
});

const app = new App(router);

This file was deleted.

0 comments on commit bd36cfb

Please sign in to comment.
You can’t perform that action at this time.