提交 73517b10 编写于 作者: J Jason Park

Cancel old Tracer API when page moves

上级 f15bad25
...@@ -690,12 +690,6 @@ ...@@ -690,12 +690,6 @@
"is-buffer": "^1.1.5" "is-buffer": "^1.1.5"
} }
}, },
"axios-progress-bar": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/axios-progress-bar/-/axios-progress-bar-1.2.0.tgz",
"integrity": "sha512-PEgWb/b2SMyHnKJ/cxA46OdCuNeVlo8eqL0HxXPtz+6G/Jtpyo49icPbW+jpO1wUeDEjbqpseMoCyWxESxf5pA==",
"dev": true
},
"axobject-query": { "axobject-query": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.1.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.1.tgz",
......
import Promise from 'bluebird'; import Promise from 'bluebird';
import axios from 'axios'; import axios from 'axios';
axios.interceptors.response.use( axios.interceptors.response.use(response => response.data);
response => response.data,
error => {
const { data } = error.response;
const message = typeof data === 'string' ? data : JSON.stringify(data);
return Promise.reject(new Error(message));
},
);
const request = (url, process) => { const request = (url, process) => {
const tokens = url.split('/'); const tokens = url.split('/');
const baseURL = /^https?:\/\//i.test(url) ? '' : '/api'; const baseURL = /^https?:\/\//i.test(url) ? '' : '/api';
return (...args) => { return (...args) => {
return new Promise((resolve, reject) => {
const mappedURL = baseURL + tokens.map((token, i) => token.startsWith(':') ? args.shift() : token).join('/'); const mappedURL = baseURL + tokens.map((token, i) => token.startsWith(':') ? args.shift() : token).join('/');
return resolve(process(mappedURL, args)); return Promise.resolve(process(mappedURL, args));
});
}; };
}; };
const GET = URL => { const GET = URL => {
return request(URL, (mappedURL, args) => { return request(URL, (mappedURL, args) => {
const [params] = args; const [params, cancelToken] = args;
return axios.get(mappedURL, { params }); return axios.get(mappedURL, { params, cancelToken });
}); });
}; };
const DELETE = URL => { const DELETE = URL => {
return request(URL, (mappedURL, args) => { return request(URL, (mappedURL, args) => {
const [params] = args; const [params, cancelToken] = args;
return axios.delete(mappedURL, { params }); return axios.delete(mappedURL, { params, cancelToken });
}); });
}; };
const POST = URL => { const POST = URL => {
return request(URL, (mappedURL, args) => { return request(URL, (mappedURL, args) => {
const [body, params] = args; const [body, params, cancelToken] = args;
return axios.post(mappedURL, body, { params }); return axios.post(mappedURL, body, { params, cancelToken });
}); });
}; };
const PUT = URL => { const PUT = URL => {
return request(URL, (mappedURL, args) => { return request(URL, (mappedURL, args) => {
const [body, params] = args; const [body, params, cancelToken] = args;
return axios.put(mappedURL, body, { params }); return axios.put(mappedURL, body, { params, cancelToken });
}); });
}; };
const PATCH = URL => { const PATCH = URL => {
return request(URL, (mappedURL, args) => { return request(URL, (mappedURL, args) => {
const [body, params] = args; const [body, params, cancelToken] = args;
return axios.patch(mappedURL, body, { params }); return axios.patch(mappedURL, body, { params, cancelToken });
}); });
}; };
...@@ -72,7 +63,6 @@ const GitHubApi = { ...@@ -72,7 +63,6 @@ const GitHubApi = {
forkGist: POST('https://api.github.com/gists/:id/forks'), forkGist: POST('https://api.github.com/gists/:id/forks'),
}; };
let jsWorker = null;
const TracerApi = { const TracerApi = {
md: ({ code }) => Promise.resolve([{ md: ({ code }) => Promise.resolve([{
tracerKey: '0-MarkdownTracer-Markdown', tracerKey: '0-MarkdownTracer-Markdown',
...@@ -83,12 +73,23 @@ const TracerApi = { ...@@ -83,12 +73,23 @@ const TracerApi = {
method: 'set', method: 'set',
args: [code], args: [code],
}]), }]),
js: ({ code }) => new Promise((resolve, reject) => { js: ({ code }, params, cancelToken) => new Promise((resolve, reject) => {
if (jsWorker) jsWorker.terminate(); const worker = new Worker('/api/tracers/js');
jsWorker = new Worker('/api/tracers/js'); if (cancelToken) {
jsWorker.onmessage = e => resolve(e.data); cancelToken.promise.then(cancel => {
jsWorker.onerror = reject; worker.terminate();
jsWorker.postMessage(code); reject(cancel);
});
}
worker.onmessage = e => {
worker.terminate();
resolve(e.data);
};
worker.onerror = error => {
worker.terminate();
reject(error);
};
worker.postMessage(code);
}), }),
cpp: POST('/tracers/cpp'), cpp: POST('/tracers/cpp'),
java: POST('/tracers/java'), java: POST('/tracers/java'),
......
...@@ -21,15 +21,9 @@ const refineGist = gist => { ...@@ -21,15 +21,9 @@ const refineGist = gist => {
return { login, gistId, title, files }; return { login, gistId, title, files };
}; };
const handleError = function (error) {
console.error(error);
this.props.showErrorToast(error.message);
};
export { export {
classes, classes,
distance, distance,
extension, extension,
refineGist, refineGist,
handleError,
}; };
...@@ -6,9 +6,8 @@ import { Helmet } from 'react-helmet'; ...@@ -6,9 +6,8 @@ import { Helmet } from 'react-helmet';
import AutosizeInput from 'react-input-autosize'; import AutosizeInput from 'react-input-autosize';
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 { loadProgressBar } from 'axios-progress-bar';
import 'axios-progress-bar/dist/nprogress.css';
import { import {
BaseComponent,
CodeEditor, CodeEditor,
Header, Header,
Navigator, Navigator,
...@@ -19,15 +18,13 @@ import { ...@@ -19,15 +18,13 @@ import {
} from '/components'; } from '/components';
import { AlgorithmApi, GitHubApi } from '/apis'; import { AlgorithmApi, GitHubApi } from '/apis';
import { actions } from '/reducers'; import { actions } from '/reducers';
import { extension, handleError, refineGist } from '/common/util'; import { extension, refineGist } from '/common/util';
import { exts, languages } from '/common/config'; import { exts, languages } from '/common/config';
import { SCRATCH_PAPER_MD } from '/files'; import { SCRATCH_PAPER_MD } from '/files';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
loadProgressBar();
@connect(({ current, env }) => ({ current, env }), actions) @connect(({ current, env }) => ({ current, env }), actions)
class App extends React.Component { class App extends BaseComponent {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -51,9 +48,10 @@ class App extends React.Component { ...@@ -51,9 +48,10 @@ class App extends React.Component {
AlgorithmApi.getCategories() AlgorithmApi.getCategories()
.then(({ categories }) => this.props.setCategories(categories)) .then(({ categories }) => this.props.setCategories(categories))
.catch(handleError.bind(this)); .catch(this.handleError);
this.props.history.block(() => { this.props.history.block((location) => {
if (location.pathname === this.props.location.pathname) return;
if (!this.isSaved()) return 'Are you sure want to discard changes?'; if (!this.isSaved()) return 'Are you sure want to discard changes?';
}); });
} }
...@@ -117,7 +115,7 @@ class App extends React.Component { ...@@ -117,7 +115,7 @@ class App extends React.Component {
}); });
return paginateGists() return paginateGists()
.then(scratchPapers => this.props.setScratchPapers(scratchPapers)) .then(scratchPapers => this.props.setScratchPapers(scratchPapers))
.catch(handleError.bind(this)); .catch(this.handleError);
} }
loadAlgorithm({ categoryKey, algorithmKey, gistId }) { loadAlgorithm({ categoryKey, algorithmKey, gistId }) {
...@@ -146,7 +144,7 @@ class App extends React.Component { ...@@ -146,7 +144,7 @@ class App extends React.Component {
}; };
fetch() fetch()
.catch(error => { .catch(error => {
handleError.bind(this)(error); this.handleError(error);
this.props.setHome(); this.props.setHome();
}) })
.finally(() => { .finally(() => {
......
import React from 'react';
class BaseComponent extends React.Component {
constructor(props) {
super(props);
this.handleError = this.handleError.bind(this);
}
handleError(error) {
console.error(error);
if (error.response) {
const { data } = error.response;
const message = typeof data === 'string' ? data : JSON.stringify(data);
this.props.showErrorToast(message);
} else {
this.props.showErrorToast(error.message);
}
}
}
export default BaseComponent;
...@@ -16,15 +16,15 @@ import faSave from '@fortawesome/fontawesome-free-solid/faSave'; ...@@ -16,15 +16,15 @@ import faSave from '@fortawesome/fontawesome-free-solid/faSave';
import faFacebook from '@fortawesome/fontawesome-free-brands/faFacebook'; import faFacebook from '@fortawesome/fontawesome-free-brands/faFacebook';
import faStar from '@fortawesome/fontawesome-free-solid/faStar'; import faStar from '@fortawesome/fontawesome-free-solid/faStar';
import { GitHubApi } from '/apis'; import { GitHubApi } from '/apis';
import { classes, handleError, refineGist } from '/common/util'; import { classes, refineGist } from '/common/util';
import { actions } from '/reducers'; import { actions } from '/reducers';
import { languages } from '/common/config'; import { languages } from '/common/config';
import { Button, Ellipsis, ListItem, Player } from '/components'; import { BaseComponent, Button, Ellipsis, ListItem, Player } from '/components';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
@withRouter @withRouter
@connect(({ current, env }) => ({ current, env }), actions) @connect(({ current, env }) => ({ current, env }), actions)
class Header extends React.Component { class Header extends BaseComponent {
handleClickFullScreen() { handleClickFullScreen() {
if (screenfull.enabled) { if (screenfull.enabled) {
if (screenfull.isFullscreen) { if (screenfull.isFullscreen) {
...@@ -80,7 +80,7 @@ class Header extends React.Component { ...@@ -80,7 +80,7 @@ class Header extends React.Component {
} }
}) })
.then(this.props.loadScratchPapers) .then(this.props.loadScratchPapers)
.catch(handleError.bind(this)); .catch(this.handleError);
} }
hasPermission() { hasPermission() {
...@@ -107,7 +107,7 @@ class Header extends React.Component { ...@@ -107,7 +107,7 @@ class Header extends React.Component {
this.props.history.push('/'); this.props.history.push('/');
}) })
.then(this.props.loadScratchPapers) .then(this.props.loadScratchPapers)
.catch(handleError.bind(this)); .catch(this.handleError);
} }
} }
......
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Promise from 'bluebird';
import InputRange from 'react-input-range'; import InputRange from 'react-input-range';
import axios from 'axios';
import faPlay from '@fortawesome/fontawesome-free-solid/faPlay'; import faPlay from '@fortawesome/fontawesome-free-solid/faPlay';
import faChevronLeft from '@fortawesome/fontawesome-free-solid/faChevronLeft'; import faChevronLeft from '@fortawesome/fontawesome-free-solid/faChevronLeft';
import faChevronRight from '@fortawesome/fontawesome-free-solid/faChevronRight'; import faChevronRight from '@fortawesome/fontawesome-free-solid/faChevronRight';
import faPause from '@fortawesome/fontawesome-free-solid/faPause'; import faPause from '@fortawesome/fontawesome-free-solid/faPause';
import faWrench from '@fortawesome/fontawesome-free-solid/faWrench'; import faWrench from '@fortawesome/fontawesome-free-solid/faWrench';
import { classes, extension, handleError } from '/common/util'; import { classes, extension } from '/common/util';
import { TracerApi } from '/apis'; import { TracerApi } from '/apis';
import { actions } from '/reducers'; import { actions } from '/reducers';
import { Button, ProgressBar } from '/components'; import { BaseComponent, Button, ProgressBar } from '/components';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
@connect(({ player }) => ({ player }), actions) @connect(({ player }) => ({ player }), actions)
class Player extends React.Component { class Player extends BaseComponent {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -24,6 +24,8 @@ class Player extends React.Component { ...@@ -24,6 +24,8 @@ class Player extends React.Component {
building: false, building: false,
}; };
this.tracerApiSource = null;
this.reset(); this.reset();
} }
...@@ -65,17 +67,31 @@ class Player extends React.Component { ...@@ -65,17 +67,31 @@ class Player extends React.Component {
} }
build(file) { build(file) {
this.reset();
if (!file) return; if (!file) return;
if (this.tracerApiSource) this.tracerApiSource.cancel();
this.tracerApiSource = axios.CancelToken.source();
this.setState({ building: true }); this.setState({ building: true });
this.reset();
const ext = extension(file.name); const ext = extension(file.name);
(ext in TracerApi ? if (ext in TracerApi) {
TracerApi[ext]({ code: file.content }) : TracerApi[ext]({ code: file.content }, undefined, this.tracerApiSource.token)
Promise.reject(new Error('Language Not Supported'))) .then(traces => {
.then(traces => this.reset(traces)) this.tracerApiSource = null;
.then(() => this.next()) this.setState({ building: false });
.catch(handleError.bind(this)) this.reset(traces);
.finally(() => this.setState({ building: false })); this.next();
})
.catch(error => {
if (axios.isCancel(error)) return;
this.tracerApiSource = null;
this.setState({ building: false });
this.handleError(error);
});
} else {
this.handleError(new Error('Language Not Supported'));
}
} }
isValidCursor(cursor) { isValidCursor(cursor) {
......
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { classes, handleError } from '/common/util'; import { classes } from '/common/util';
import { ResizableContainer } from '/components'; import { BaseComponent, ResizableContainer } from '/components';
import { actions } from '/reducers'; import { actions } from '/reducers';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData, MarkdownData } from '/core/datas'; import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData, MarkdownData } from '/core/datas';
@connect(({ player }) => ({ player }), actions) @connect(({ player }) => ({ player }), actions)
class VisualizationViewer extends React.Component { class VisualizationViewer extends BaseComponent {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -82,7 +82,7 @@ class VisualizationViewer extends React.Component { ...@@ -82,7 +82,7 @@ class VisualizationViewer extends React.Component {
data[method](...args); data[method](...args);
} }
} catch (error) { } catch (error) {
handleError.bind(this)(error); this.handleError(error);
} }
} }
......
export { default as App } from './App'; export { default as App } from './App';
export { default as BaseComponent } from './BaseComponent';
export { default as Button } from './Button'; export { default as Button } from './Button';
export { default as CodeEditor } from './CodeEditor'; export { default as CodeEditor } from './CodeEditor';
export { default as Divider } from './Divider'; export { default as Divider } from './Divider';
......
# Algorithm Visualizer
> Algorithm Visualizer is an interactive online platform that visualizes algorithms from code.
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/algorithm-visualizer)
[![GitHub contributors](https://img.shields.io/github/contributors/algorithm-visualizer/algorithm-visualizer.svg?style=flat-square)](https://github.com/algorithm-visualizer/algorithm-visualizer/graphs/contributors)
[![GitHub](https://img.shields.io/github/license/algorithm-visualizer/algorithm-visualizer.svg?style=flat-square)](https://github.com/algorithm-visualizer/algorithm-visualizer/blob/master/LICENSE)
Learning algorithms from text and static images is quite boring. For that, there have been many great websites that view animations of various algorithms. However, for us being coders, nothing can be more comprehensible than visualizing the actual working code. So here we introduce Algorithm Visualizer.
[![Screenshot](https://raw.githubusercontent.com/algorithm-visualizer/algorithm-visualizer/master/branding/screenshot.png)](https://algorithm-visualizer.org/)
## Contributing
The project [algorithm-visualizer](https://github.com/algorithm-visualizer) is composed of the following 3 repositories.
* [algorithm-visualizer/algorithms](https://github.com/algorithm-visualizer/algorithms): contains public algorithms shown on the sidebar. [Contribute](https://github.com/algorithm-visualizer/algorithms/blob/master/CONTRIBUTING.md)
* [algorithm-visualizer/tracers](https://github.com/algorithm-visualizer/tracers): contains visualization libraries written in each supported language. [Contribute](https://github.com/algorithm-visualizer/tracers/blob/master/CONTRIBUTING.md)
* [algorithm-visualizer/algorithm-visualizer](https://github.com/algorithm-visualizer/algorithm-visualizer): contains the front-end written in React.js and the back-end written in Node.js. [Contribute](https://github.com/algorithm-visualizer/algorithm-visualizer/blob/master/CONTRIBUTING.md)
Take a moment to read `CONTRIBUTING.md` in the repository you want to contribute to.
../../../../README.md
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册