From 73517b10d095b1c47cf9261b95b396164d17f411 Mon Sep 17 00:00:00 2001 From: Jason Park Date: Fri, 7 Dec 2018 20:04:21 -0500 Subject: [PATCH] Cancel old Tracer API when page moves --- package-lock.json | 6 -- package.json | 1 - src/frontend/apis/index.js | 59 ++++++++++--------- src/frontend/common/util.js | 6 -- src/frontend/components/App/index.jsx | 18 +++--- .../components/BaseComponent/index.jsx | 22 +++++++ src/frontend/components/Header/index.jsx | 10 ++-- src/frontend/components/Player/index.jsx | 40 +++++++++---- .../components/VisualizationViewer/index.jsx | 8 +-- src/frontend/components/index.js | 1 + .../files/algorithm-visualizer/README.md | 23 +------- 11 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 src/frontend/components/BaseComponent/index.jsx mode change 100644 => 120000 src/frontend/files/algorithm-visualizer/README.md diff --git a/package-lock.json b/package-lock.json index 94fac7a..a2a9518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -690,12 +690,6 @@ "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": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.1.tgz", diff --git a/package.json b/package.json index 43c156b..7e6f53a 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "@fortawesome/fontawesome-svg-core": "^1.2.0", "@fortawesome/react-fontawesome": "0.1.0", "autoprefixer": "latest", - "axios-progress-bar": "^1.1.8", "babel-core": "^6.18.0", "babel-loader": "^7.1.2", "babel-plugin-transform-builtin-extend": "^1.1.2", diff --git a/src/frontend/apis/index.js b/src/frontend/apis/index.js index 98da903..c628fa8 100644 --- a/src/frontend/apis/index.js +++ b/src/frontend/apis/index.js @@ -1,58 +1,49 @@ import Promise from 'bluebird'; import axios from 'axios'; -axios.interceptors.response.use( - response => response.data, - error => { - const { data } = error.response; - const message = typeof data === 'string' ? data : JSON.stringify(data); - return Promise.reject(new Error(message)); - }, -); +axios.interceptors.response.use(response => response.data); const request = (url, process) => { const tokens = url.split('/'); const baseURL = /^https?:\/\//i.test(url) ? '' : '/api'; return (...args) => { - return new Promise((resolve, reject) => { - const mappedURL = baseURL + tokens.map((token, i) => token.startsWith(':') ? args.shift() : token).join('/'); - return resolve(process(mappedURL, args)); - }); + const mappedURL = baseURL + tokens.map((token, i) => token.startsWith(':') ? args.shift() : token).join('/'); + return Promise.resolve(process(mappedURL, args)); }; }; const GET = URL => { return request(URL, (mappedURL, args) => { - const [params] = args; - return axios.get(mappedURL, { params }); + const [params, cancelToken] = args; + return axios.get(mappedURL, { params, cancelToken }); }); }; const DELETE = URL => { return request(URL, (mappedURL, args) => { - const [params] = args; - return axios.delete(mappedURL, { params }); + const [params, cancelToken] = args; + return axios.delete(mappedURL, { params, cancelToken }); }); }; const POST = URL => { return request(URL, (mappedURL, args) => { - const [body, params] = args; - return axios.post(mappedURL, body, { params }); + const [body, params, cancelToken] = args; + return axios.post(mappedURL, body, { params, cancelToken }); }); }; const PUT = URL => { return request(URL, (mappedURL, args) => { - const [body, params] = args; - return axios.put(mappedURL, body, { params }); + const [body, params, cancelToken] = args; + return axios.put(mappedURL, body, { params, cancelToken }); }); }; const PATCH = URL => { return request(URL, (mappedURL, args) => { - const [body, params] = args; - return axios.patch(mappedURL, body, { params }); + const [body, params, cancelToken] = args; + return axios.patch(mappedURL, body, { params, cancelToken }); }); }; @@ -72,7 +63,6 @@ const GitHubApi = { forkGist: POST('https://api.github.com/gists/:id/forks'), }; -let jsWorker = null; const TracerApi = { md: ({ code }) => Promise.resolve([{ tracerKey: '0-MarkdownTracer-Markdown', @@ -83,12 +73,23 @@ const TracerApi = { method: 'set', args: [code], }]), - js: ({ code }) => new Promise((resolve, reject) => { - if (jsWorker) jsWorker.terminate(); - jsWorker = new Worker('/api/tracers/js'); - jsWorker.onmessage = e => resolve(e.data); - jsWorker.onerror = reject; - jsWorker.postMessage(code); + js: ({ code }, params, cancelToken) => new Promise((resolve, reject) => { + const worker = new Worker('/api/tracers/js'); + if (cancelToken) { + cancelToken.promise.then(cancel => { + worker.terminate(); + reject(cancel); + }); + } + worker.onmessage = e => { + worker.terminate(); + resolve(e.data); + }; + worker.onerror = error => { + worker.terminate(); + reject(error); + }; + worker.postMessage(code); }), cpp: POST('/tracers/cpp'), java: POST('/tracers/java'), diff --git a/src/frontend/common/util.js b/src/frontend/common/util.js index dfff0d0..0415667 100644 --- a/src/frontend/common/util.js +++ b/src/frontend/common/util.js @@ -21,15 +21,9 @@ const refineGist = gist => { return { login, gistId, title, files }; }; -const handleError = function (error) { - console.error(error); - this.props.showErrorToast(error.message); -}; - export { classes, distance, extension, refineGist, - handleError, }; diff --git a/src/frontend/components/App/index.jsx b/src/frontend/components/App/index.jsx index 3d3a727..6668130 100644 --- a/src/frontend/components/App/index.jsx +++ b/src/frontend/components/App/index.jsx @@ -6,9 +6,8 @@ import { Helmet } from 'react-helmet'; import AutosizeInput from 'react-input-autosize'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import faPlus from '@fortawesome/fontawesome-free-solid/faPlus'; -import { loadProgressBar } from 'axios-progress-bar'; -import 'axios-progress-bar/dist/nprogress.css'; import { + BaseComponent, CodeEditor, Header, Navigator, @@ -19,15 +18,13 @@ import { } from '/components'; import { AlgorithmApi, GitHubApi } from '/apis'; import { actions } from '/reducers'; -import { extension, handleError, refineGist } from '/common/util'; +import { extension, refineGist } from '/common/util'; import { exts, languages } from '/common/config'; import { SCRATCH_PAPER_MD } from '/files'; import styles from './stylesheet.scss'; -loadProgressBar(); - @connect(({ current, env }) => ({ current, env }), actions) -class App extends React.Component { +class App extends BaseComponent { constructor(props) { super(props); @@ -51,9 +48,10 @@ class App extends React.Component { AlgorithmApi.getCategories() .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?'; }); } @@ -117,7 +115,7 @@ class App extends React.Component { }); return paginateGists() .then(scratchPapers => this.props.setScratchPapers(scratchPapers)) - .catch(handleError.bind(this)); + .catch(this.handleError); } loadAlgorithm({ categoryKey, algorithmKey, gistId }) { @@ -146,7 +144,7 @@ class App extends React.Component { }; fetch() .catch(error => { - handleError.bind(this)(error); + this.handleError(error); this.props.setHome(); }) .finally(() => { diff --git a/src/frontend/components/BaseComponent/index.jsx b/src/frontend/components/BaseComponent/index.jsx new file mode 100644 index 0000000..19ecd28 --- /dev/null +++ b/src/frontend/components/BaseComponent/index.jsx @@ -0,0 +1,22 @@ +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; diff --git a/src/frontend/components/Header/index.jsx b/src/frontend/components/Header/index.jsx index 5e78a31..ce7d052 100644 --- a/src/frontend/components/Header/index.jsx +++ b/src/frontend/components/Header/index.jsx @@ -16,15 +16,15 @@ import faSave from '@fortawesome/fontawesome-free-solid/faSave'; import faFacebook from '@fortawesome/fontawesome-free-brands/faFacebook'; import faStar from '@fortawesome/fontawesome-free-solid/faStar'; import { GitHubApi } from '/apis'; -import { classes, handleError, refineGist } from '/common/util'; +import { classes, refineGist } from '/common/util'; import { actions } from '/reducers'; 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'; @withRouter @connect(({ current, env }) => ({ current, env }), actions) -class Header extends React.Component { +class Header extends BaseComponent { handleClickFullScreen() { if (screenfull.enabled) { if (screenfull.isFullscreen) { @@ -80,7 +80,7 @@ class Header extends React.Component { } }) .then(this.props.loadScratchPapers) - .catch(handleError.bind(this)); + .catch(this.handleError); } hasPermission() { @@ -107,7 +107,7 @@ class Header extends React.Component { this.props.history.push('/'); }) .then(this.props.loadScratchPapers) - .catch(handleError.bind(this)); + .catch(this.handleError); } } diff --git a/src/frontend/components/Player/index.jsx b/src/frontend/components/Player/index.jsx index f843aa5..9aafba5 100644 --- a/src/frontend/components/Player/index.jsx +++ b/src/frontend/components/Player/index.jsx @@ -1,20 +1,20 @@ import React from 'react'; import { connect } from 'react-redux'; -import Promise from 'bluebird'; import InputRange from 'react-input-range'; +import axios from 'axios'; import faPlay from '@fortawesome/fontawesome-free-solid/faPlay'; import faChevronLeft from '@fortawesome/fontawesome-free-solid/faChevronLeft'; import faChevronRight from '@fortawesome/fontawesome-free-solid/faChevronRight'; import faPause from '@fortawesome/fontawesome-free-solid/faPause'; 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 { actions } from '/reducers'; -import { Button, ProgressBar } from '/components'; +import { BaseComponent, Button, ProgressBar } from '/components'; import styles from './stylesheet.scss'; @connect(({ player }) => ({ player }), actions) -class Player extends React.Component { +class Player extends BaseComponent { constructor(props) { super(props); @@ -24,6 +24,8 @@ class Player extends React.Component { building: false, }; + this.tracerApiSource = null; + this.reset(); } @@ -65,17 +67,31 @@ class Player extends React.Component { } build(file) { + this.reset(); if (!file) return; + + if (this.tracerApiSource) this.tracerApiSource.cancel(); + this.tracerApiSource = axios.CancelToken.source(); this.setState({ building: true }); - this.reset(); + const ext = extension(file.name); - (ext in TracerApi ? - TracerApi[ext]({ code: file.content }) : - Promise.reject(new Error('Language Not Supported'))) - .then(traces => this.reset(traces)) - .then(() => this.next()) - .catch(handleError.bind(this)) - .finally(() => this.setState({ building: false })); + if (ext in TracerApi) { + TracerApi[ext]({ code: file.content }, undefined, this.tracerApiSource.token) + .then(traces => { + this.tracerApiSource = null; + this.setState({ building: false }); + this.reset(traces); + 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) { diff --git a/src/frontend/components/VisualizationViewer/index.jsx b/src/frontend/components/VisualizationViewer/index.jsx index 329367b..fde46b2 100644 --- a/src/frontend/components/VisualizationViewer/index.jsx +++ b/src/frontend/components/VisualizationViewer/index.jsx @@ -1,13 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; -import { classes, handleError } from '/common/util'; -import { ResizableContainer } from '/components'; +import { classes } from '/common/util'; +import { BaseComponent, ResizableContainer } from '/components'; import { actions } from '/reducers'; import styles from './stylesheet.scss'; import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData, MarkdownData } from '/core/datas'; @connect(({ player }) => ({ player }), actions) -class VisualizationViewer extends React.Component { +class VisualizationViewer extends BaseComponent { constructor(props) { super(props); @@ -82,7 +82,7 @@ class VisualizationViewer extends React.Component { data[method](...args); } } catch (error) { - handleError.bind(this)(error); + this.handleError(error); } } diff --git a/src/frontend/components/index.js b/src/frontend/components/index.js index 16b7eba..18f899a 100644 --- a/src/frontend/components/index.js +++ b/src/frontend/components/index.js @@ -1,4 +1,5 @@ export { default as App } from './App'; +export { default as BaseComponent } from './BaseComponent'; export { default as Button } from './Button'; export { default as CodeEditor } from './CodeEditor'; export { default as Divider } from './Divider'; diff --git a/src/frontend/files/algorithm-visualizer/README.md b/src/frontend/files/algorithm-visualizer/README.md deleted file mode 100644 index 14c25cb..0000000 --- a/src/frontend/files/algorithm-visualizer/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# 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. diff --git a/src/frontend/files/algorithm-visualizer/README.md b/src/frontend/files/algorithm-visualizer/README.md new file mode 120000 index 0000000..ff5c796 --- /dev/null +++ b/src/frontend/files/algorithm-visualizer/README.md @@ -0,0 +1 @@ +../../../../README.md \ No newline at end of file -- GitLab