diff --git a/package-lock.json b/package-lock.json index 94fac7a7ec6da5d2553d9c7cae5f0e880879067a..a2a9518e7b7a7220102e1eecc501db14a9adc1a3 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 43c156bec69c758c55e2bea047f7ecd2e36100df..7e6f53aeef6cb6f7e136c5190d0a06d3ac8ac1bf 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 98da90355dd3ace5dd5f03d599bc919806d15756..c628fa83befe99c90c2e72ec34f651341272c629 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 dfff0d02847b07b9c48e07433c926c39125936f8..0415667971560fbf90de91053f7dc0d3fc8c93ff 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 3d3a727d6e1c249564c1bfb84d703487909f767d..6668130e120b511ffa14cb0068dafce98d42a201 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 0000000000000000000000000000000000000000..19ecd281d186e04c572963075c1405f46d0c62ad --- /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 5e78a318038be99144d9f35d4bbbb09ce5fc5312..ce7d052ac56fce177f80da89c7634b9b3bdf79ee 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 f843aa5dcbd07bd8503e334328addf4b05845c3d..9aafba52b41914ecbd8a55f6b2ecdac7969cc1f0 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 329367bd301ab54ae8aeda761efb94b03de78da4..fde46b2e1474f4d34213ca23c7a87bb65a9b3de9 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 16b7ebac6beeb1811dedd934b0753afe2cd1bcb6..18f899ac64edd77b4f720ce7f9f0de401426db9b 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 14c25cb2e4cdf783c179d8b273982bb6c8b9f2a3..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..ff5c79602cf4f4964d15b6230c551f9d260ab007 --- /dev/null +++ b/src/frontend/files/algorithm-visualizer/README.md @@ -0,0 +1 @@ +../../../../README.md \ No newline at end of file