提交 5100b0f3 编写于 作者: J Jason Park

Visualize local codes

上级 c3c6dedb
...@@ -8534,6 +8534,18 @@ ...@@ -8534,6 +8534,18 @@
"prepend-http": "^1.0.0", "prepend-http": "^1.0.0",
"query-string": "^4.1.0", "query-string": "^4.1.0",
"sort-keys": "^1.0.0" "sort-keys": "^1.0.0"
},
"dependencies": {
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dev": true,
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
}
} }
}, },
"npm-conf": { "npm-conf": {
...@@ -10131,13 +10143,21 @@ ...@@ -10131,13 +10143,21 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
}, },
"query-string": { "query-string": {
"version": "4.3.4", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.2.0.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "integrity": "sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==",
"dev": true, "dev": true,
"requires": { "requires": {
"object-assign": "^4.1.0", "decode-uri-component": "^0.2.0",
"strict-uri-encode": "^1.0.0" "strict-uri-encode": "^2.0.0"
},
"dependencies": {
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"dev": true
}
} }
}, },
"querystring": { "querystring": {
......
export { default as auth } from './auth'; export { default as auth } from './auth';
export { default as algorithms } from './algorithms'; export { default as algorithms } from './algorithms';
export { default as tracers } from './tracers'; export { default as tracers } from './tracers';
export { default as visualizations } from './visualizations';
...@@ -14,7 +14,6 @@ const router = express.Router(); ...@@ -14,7 +14,6 @@ const router = express.Router();
const trace = lang => (req, res, next) => { const trace = lang => (req, res, next) => {
const { code } = req.body; const { code } = req.body;
const tempPath = path.resolve(__dirname, '..', 'public', 'codes', uuid.v4()); const tempPath = path.resolve(__dirname, '..', 'public', 'codes', uuid.v4());
const tracesPath = path.resolve(tempPath, 'traces.json');
fs.outputFile(path.resolve(tempPath, `Main.${lang}`), code) fs.outputFile(path.resolve(tempPath, `Main.${lang}`), code)
.then(() => { .then(() => {
const builder = builderMap[lang]; const builder = builderMap[lang];
...@@ -37,11 +36,13 @@ const trace = lang => (req, res, next) => { ...@@ -37,11 +36,13 @@ const trace = lang => (req, res, next) => {
throw error; throw error;
}).finally(() => clearTimeout(timer)); }).finally(() => clearTimeout(timer));
}) })
.then(() => fs.pathExists(tracesPath)) .then(() => new Promise((resolve, reject) => {
.then(exists => { const visualizationPath = path.resolve(tempPath, 'traces.json');
if (!exists) throw new Error('Traces Not Found'); res.sendFile(visualizationPath, err => {
res.sendFile(tracesPath); if (err) return reject(new Error('Visualization Not Found'));
}) resolve();
});
}))
.catch(next) .catch(next)
.finally(() => fs.remove(tempPath)); .finally(() => fs.remove(tempPath));
}; };
......
import express from 'express';
import path from 'path';
import uuid from 'uuid';
import fs from 'fs-extra';
import Promise from 'bluebird';
const router = express.Router();
const uploadPath = path.resolve(__dirname, '..', 'public', 'visualizations');
const getVisualizationPath = visualizationId => path.resolve(uploadPath, `${visualizationId}.json`);
fs.remove(uploadPath).catch(console.error);
const uploadVisualization = (req, res, next) => {
const { content } = req.body;
const visualizationId = uuid.v4();
const tracesPath = getVisualizationPath(visualizationId);
const url = `https://algorithm-visualizer.org/scratch-paper/new?visualizationId=${visualizationId}`;
fs.outputFile(tracesPath, content)
.then(() => res.send(url))
.catch(next);
};
const getVisualization = (req, res, next) => {
const { visualizationId } = req.params;
const visualizationPath = getVisualizationPath(visualizationId);
new Promise((resolve, reject) => {
res.sendFile(visualizationPath, err => {
if (err) return reject(new Error('Visualization Expired'));
resolve();
});
}).catch(next)
.finally(() => fs.remove(visualizationPath));
};
router.route('/')
.post(uploadVisualization);
router.route('/:visualizationId')
.get(getVisualization);
export default router;
...@@ -52,6 +52,10 @@ const AlgorithmApi = { ...@@ -52,6 +52,10 @@ const AlgorithmApi = {
getAlgorithm: GET('/algorithms/:categoryKey/:algorithmKey'), getAlgorithm: GET('/algorithms/:categoryKey/:algorithmKey'),
}; };
const VisualizationApi = {
getVisualization: GET('/visualizations/:visualizationId'),
};
const GitHubApi = { const GitHubApi = {
auth: token => Promise.resolve(axios.defaults.headers.common['Authorization'] = token && `token ${token}`), auth: token => Promise.resolve(axios.defaults.headers.common['Authorization'] = token && `token ${token}`),
getUser: GET('https://api.github.com/user'), getUser: GET('https://api.github.com/user'),
...@@ -73,6 +77,7 @@ const TracerApi = { ...@@ -73,6 +77,7 @@ const TracerApi = {
method: 'set', method: 'set',
args: [code], args: [code],
}]), }]),
json: ({ code }) => new Promise(resolve => resolve(JSON.parse(code))),
js: ({ code }, params, cancelToken) => new Promise((resolve, reject) => { js: ({ code }, params, cancelToken) => new Promise((resolve, reject) => {
const worker = new Worker('/api/tracers/js'); const worker = new Worker('/api/tracers/js');
if (cancelToken) { if (cancelToken) {
...@@ -97,6 +102,7 @@ const TracerApi = { ...@@ -97,6 +102,7 @@ const TracerApi = {
export { export {
AlgorithmApi, AlgorithmApi,
VisualizationApi,
GitHubApi, GitHubApi,
TracerApi, TracerApi,
}; };
...@@ -21,9 +21,21 @@ const refineGist = gist => { ...@@ -21,9 +21,21 @@ const refineGist = gist => {
return { login, gistId, title, files }; return { login, gistId, title, files };
}; };
const createFile = (name, content, contributors) => ({ name, content, contributors });
const createProjectFile = (name, content) => createFile(name, content, [{
login: 'algorithm-visualizer',
avatar_url: 'https://github.com/algorithm-visualizer.png',
}]);
const createUserFile = (name, content) => createFile(name, content, undefined);
export { export {
classes, classes,
distance, distance,
extension, extension,
refineGist, refineGist,
createFile,
createProjectFile,
createUserFile,
}; };
...@@ -4,6 +4,7 @@ import { connect } from 'react-redux'; ...@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import Promise from 'bluebird'; import Promise from 'bluebird';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import AutosizeInput from 'react-input-autosize'; import AutosizeInput from 'react-input-autosize';
import queryString from 'query-string';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import faPlus from '@fortawesome/fontawesome-free-solid/faPlus'; import faPlus from '@fortawesome/fontawesome-free-solid/faPlus';
import { import {
...@@ -16,9 +17,9 @@ import { ...@@ -16,9 +17,9 @@ import {
ToastContainer, ToastContainer,
VisualizationViewer, VisualizationViewer,
} from '/components'; } from '/components';
import { AlgorithmApi, GitHubApi } from '/apis'; import { AlgorithmApi, GitHubApi, VisualizationApi } from '/apis';
import { actions } from '/reducers'; import { actions } from '/reducers';
import { extension, refineGist } from '/common/util'; import { createUserFile, extension, refineGist } from '/common/util';
import { exts, languages } from '/common/config'; import { exts, languages } from '/common/config';
import { CONTRIBUTING_MD } from '/files'; import { CONTRIBUTING_MD } from '/files';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
...@@ -43,7 +44,9 @@ class App extends BaseComponent { ...@@ -43,7 +44,9 @@ class App extends BaseComponent {
window.signIn = this.signIn.bind(this); window.signIn = this.signIn.bind(this);
window.signOut = this.signOut.bind(this); window.signOut = this.signOut.bind(this);
this.loadAlgorithm(this.props.match.params); const { params } = this.props.match;
const { search } = this.props.location;
this.loadAlgorithm(params, queryString.parse(search));
const accessToken = Cookies.get('access_token'); const accessToken = Cookies.get('access_token');
if (accessToken) this.signIn(accessToken); if (accessToken) this.signIn(accessToken);
...@@ -64,12 +67,13 @@ class App extends BaseComponent { ...@@ -64,12 +67,13 @@ class App extends BaseComponent {
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { params } = nextProps.match; const { params } = nextProps.match;
if (params !== this.props.match.params) { const { search } = nextProps.location;
if (params !== this.props.match.params || search !== this.props.location.search) {
const { categoryKey, algorithmKey, gistId } = params; const { categoryKey, algorithmKey, gistId } = params;
const { algorithm, scratchPaper } = nextProps.current; const { algorithm, scratchPaper } = nextProps.current;
if (algorithm && algorithm.categoryKey === categoryKey && algorithm.algorithmKey === algorithmKey) return; if (algorithm && algorithm.categoryKey === categoryKey && algorithm.algorithmKey === algorithmKey) return;
if (scratchPaper && scratchPaper.gistId === gistId) return; if (scratchPaper && scratchPaper.gistId === gistId) return;
this.loadAlgorithm(params); this.loadAlgorithm(params, queryString.parse(search));
} }
} }
...@@ -138,7 +142,7 @@ class App extends BaseComponent { ...@@ -138,7 +142,7 @@ class App extends BaseComponent {
.catch(this.handleError); .catch(this.handleError);
} }
loadAlgorithm({ categoryKey, algorithmKey, gistId }) { loadAlgorithm({ categoryKey, algorithmKey, gistId }, { visualizationId }) {
const { ext } = this.props.env; const { ext } = this.props.env;
const fetch = () => { const fetch = () => {
if (window.__PRELOADED_ALGORITHM__) { if (window.__PRELOADED_ALGORITHM__) {
...@@ -147,6 +151,16 @@ class App extends BaseComponent { ...@@ -147,6 +151,16 @@ class App extends BaseComponent {
} else if (categoryKey && algorithmKey) { } else if (categoryKey && algorithmKey) {
return AlgorithmApi.getAlgorithm(categoryKey, algorithmKey) return AlgorithmApi.getAlgorithm(categoryKey, algorithmKey)
.then(({ algorithm }) => this.props.setAlgorithm(algorithm)); .then(({ algorithm }) => this.props.setAlgorithm(algorithm));
} else if (gistId === 'new' && visualizationId) {
return VisualizationApi.getVisualization(visualizationId)
.then(content => {
this.props.setScratchPaper({
login: undefined,
gistId,
title: 'Untitled',
files: [CONTRIBUTING_MD, createUserFile('traces.json', JSON.stringify(content))],
});
});
} else if (gistId === 'new') { } else if (gistId === 'new') {
const language = languages.find(language => language.ext === ext); const language = languages.find(language => language.ext === ext);
this.props.setScratchPaper({ this.props.setScratchPaper({
...@@ -175,7 +189,8 @@ class App extends BaseComponent { ...@@ -175,7 +189,8 @@ class App extends BaseComponent {
selectDefaultTab() { selectDefaultTab() {
const { ext } = this.props.env; const { ext } = this.props.env;
const { files } = this.props.current; const { files } = this.props.current;
let editorTabIndex = files.findIndex(file => extension(file.name) === ext); let editorTabIndex = files.findIndex(file => extension(file.name) === 'json');
if (!~editorTabIndex) files.findIndex(file => extension(file.name) === ext);
if (!~editorTabIndex) editorTabIndex = files.findIndex(file => exts.includes(extension(file.name))); if (!~editorTabIndex) editorTabIndex = files.findIndex(file => exts.includes(extension(file.name)));
if (!~editorTabIndex) editorTabIndex = Math.min(0, files.length - 1); if (!~editorTabIndex) editorTabIndex = Math.min(0, files.length - 1);
this.handleChangeEditorTabIndex(editorTabIndex); this.handleChangeEditorTabIndex(editorTabIndex);
......
...@@ -2,6 +2,7 @@ import React from 'react'; ...@@ -2,6 +2,7 @@ import React from 'react';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
import 'brace/mode/plain_text'; import 'brace/mode/plain_text';
import 'brace/mode/markdown'; import 'brace/mode/markdown';
import 'brace/mode/json';
import 'brace/mode/javascript'; import 'brace/mode/javascript';
import 'brace/mode/c_cpp'; import 'brace/mode/c_cpp';
import 'brace/mode/java'; import 'brace/mode/java';
...@@ -43,7 +44,10 @@ class CodeEditor extends React.Component { ...@@ -43,7 +44,10 @@ class CodeEditor extends React.Component {
const fileExt = extension(file.name); const fileExt = extension(file.name);
const language = languages.find(language => language.ext === fileExt); const language = languages.find(language => language.ext === fileExt);
const mode = language ? language.mode : fileExt === 'md' ? 'markdown' : 'plain_text'; const mode = language ? language.mode :
fileExt === 'md' ? 'markdown' :
fileExt === 'json' ? 'json' :
'plain_text';
return ( return (
<div className={classes(styles.code_editor, className)}> <div className={classes(styles.code_editor, className)}>
......
const createProjectFile = filePath => ({ import { createProjectFile, createUserFile } from '/common/util';
name: filePath.split('/').pop(),
content: require('raw-loader!./' + filePath),
contributors: [{
login: 'algorithm-visualizer',
avatar_url: 'https://github.com/algorithm-visualizer.png',
}],
});
const createUserFile = filePath => ({ const getName = filePath => filePath.split('/').pop();
name: filePath.split('/').pop(), const getContent = filePath => require('raw-loader!./' + filePath);
content: require('raw-loader!./' + filePath), const readProjectFile = filePath => createProjectFile(getName(filePath), getContent(filePath));
contributors: undefined, const readUserFile = filePath => createUserFile(getName(filePath), getContent(filePath));
});
export const CODE_CPP = createUserFile('skeletons/code.cpp'); export const CODE_CPP = readUserFile('skeletons/code.cpp');
export const CODE_JAVA = createUserFile('skeletons/code.java'); export const CODE_JAVA = readUserFile('skeletons/code.java');
export const CODE_JS = createUserFile('skeletons/code.js'); export const CODE_JS = readUserFile('skeletons/code.js');
export const README_MD = createProjectFile('algorithm-visualizer/README.md'); export const README_MD = readProjectFile('algorithm-visualizer/README.md');
export const CONTRIBUTING_MD = createProjectFile('scratch-paper/CONTRIBUTING.md'); export const CONTRIBUTING_MD = readProjectFile('scratch-paper/CONTRIBUTING.md');
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册