UNPKG

esnextbin

Version:

Prototype JavaScript programs in the browser with ESNext (a.k.a ES2015) syntax and features, using modules from NPM.

350 lines (295 loc) 9.14 kB
import React from 'react'; import ReactDOM from 'react-dom'; import Mousetrap from 'mousetrap'; import Progress from 'react-progress-2'; import * as Babel from 'babel-standalone'; import querystring from 'querystring'; import prettier from 'prettier-standalone'; import Header from '../components/Header'; import Editors from '../components/Editors'; import Sandbox from '../components/Sandbox'; import * as Defaults from '../utils/DefaultsUtil'; import * as StorageUtils from '../utils/StorageUtils'; import * as GistAPIUtils from '../utils/GistAPIUtils'; class Main extends React.Component { constructor() { super(); this.query = {}; this.state = { bundle: {}, bundling: false, activeEditor: 'code', shareModal: false, autorunIsOn: false, editorsData: { code: Defaults.CODE, transpiledCode: this._transpileCode(Defaults.CODE), html: Defaults.HTML, json: Defaults.PACKAGE_JSON, error: void 0 } }; } componentDidMount() { this.query = this._parseQuery(); const gistId = this.query.gist; const sha = this.query.rev || this.query.sha; if (gistId) { StorageUtils.turnOffSession(); Progress.show(); GistAPIUtils.getGist({id: gistId, sha}, (err, gistSession) => { Progress.hide(); if (err) { console.log(err); // show special error on page return; } const { transpiledCode, error } = this._transpileCodeAndCatch(gistSession.code); const editorsData = this._updateEditorsData(Object.assign(gistSession, { transpiledCode, error })); this.setState({ editorsData }); if (this.query.execute || this.query.exec) { setTimeout(() => this.handleRunClick(), 0); } }); } else { this.checkPreviousSession(); } this.bindKeyShortcuts(); } checkPreviousSession() { const session = StorageUtils.getSession(); if (session) { const newState = {}; const { autorun, ...editorsDataSession } = session; if (autorun) { newState.autorunIsOn = autorun; } const { transpiledCode, error } = this._transpileCodeAndCatch(session.code); newState.editorsData = this._updateEditorsData(Object.assign(editorsDataSession, { transpiledCode, error })); this.setState(newState); } } bindKeyShortcuts() { const mousetrap = Mousetrap(ReactDOM.findDOMNode(this)); mousetrap.bind(['command+e', 'ctrl+e'], (e) => { e.preventDefault(); this.handleRunClick(); }); mousetrap.bind(['command+s', 'ctrl+s'], (e) => { e.preventDefault(); this.handleSaveGist('public'); }); mousetrap.bind(['ctrl+alt+f'], (e) => { e.preventDefault(); this.handlePrettierClick(); }); } handleRunClick() { if (this.state.bundling) return; const bundle = this._getBundle(); bundle && this.setState({ bundle }); } handlePrettierClick() { const code = prettier.format(this.state.editorsData.code); const editorsData = this._updateEditorsData({code}); this.setState({ editorsData }); } handleChangeEditor(activeEditor) { this.setState({ activeEditor }); } handleStartBundle() { if (this.state.bundling) return; this.progressDelay = setTimeout(() => { this.setState({bundling: true}); Progress.show(); }, 100); } handleEndBundle() { clearTimeout(this.progressDelay); if (this.triggerGist) { const status = this.triggerGist; const gistId = this.query.gist; const { editorsData } = this.state; const fn = (err, res, isFork) => { Progress.hideAll(); if (err) { console.log(err); // show special error on page return; } if (!gistId || isFork) { window.location.search = `gist=${res.body.id}`; } this.finishHandleEndBundle(); }; this.triggerGist = false; if (gistId) { GistAPIUtils.updateGist(gistId, editorsData, status, fn); } else { GistAPIUtils.createGist(editorsData, status, fn); } } else { this.finishHandleEndBundle(); } } finishHandleEndBundle() { Progress.hideAll(); this.setState({bundling: false}); } handleSaveGist(status) { Progress.show(); // talk with gist API on endBundle event of sandbox this.triggerGist = status; this.handleRunClick(); } openShareModal() { this.setState({shareModal: true}); } closeShareModal() { this.setState({shareModal: false}); } handleReset() { GistAPIUtils.unauthorize(); StorageUtils.cleanSession(); window.location.reload(); } toggleAutorun() { const autorunIsOn = !this.state.autorunIsOn; StorageUtils.saveToSession('autorun', autorunIsOn); this.setState({ autorunIsOn }); } autorunOnChange() { if (this.autorunDelay) { clearTimeout(this.autorunDelay); } this.autorunDelay = setTimeout(() => { this.handleRunClick(); }, 1000); } handleCodeChange(code) { StorageUtils.saveToSession('code', code); clearTimeout(this.errorDelay); const { transpiledCode, error } = this._transpileCodeAndCatch(code); if (error) { this.errorDelay = setTimeout(() => { const editorsData = this._updateEditorsData({error}); this.setState({ editorsData }); }, 1000); } const editorsData = this._updateEditorsData({code, transpiledCode, error: ''}); this.setState({ editorsData }); if (this.state.autorunIsOn) { this.autorunOnChange(); } } handleHTMLChange(html) { StorageUtils.saveToSession('html', html); const editorsData = this._updateEditorsData({html, error: ''}); this.setState({ editorsData }); } handlePackageChange(json) { StorageUtils.saveToSession('json', json); const editorsData = this._updateEditorsData({json, error: ''}); this.setState({ editorsData }); } handleDependencies(modules) { const { bundle } = this.state; const updatedPackage = Object.assign({}, bundle.package, { dependencies: modules.reduce((memo, mod) => { memo[mod.name] = mod.version; return memo; }, {}) }); const editorsData = this._updateEditorsData({ json: JSON.stringify(updatedPackage, null, 2) }); this.setState({ editorsData }); } handleErrorBundle(err) { console.log(err); // maybe show some popup or notification here? this.finishHandleEndBundle(); } render() { const { bundle, editorsData, activeEditor, autorunIsOn, bundling } = this.state; return ( <div className="main"> <Progress.Component /> <Header height={Defaults.HEADER_HEIGHT} activeEditor={activeEditor} isBundling={bundling} autorunIsOn={autorunIsOn} onShareClick={::this.openShareModal} onRunClick={::this.handleRunClick} onPrettierClick={::this.handlePrettierClick} onEditorClick={::this.handleChangeEditor} onSaveGistClick={::this.handleSaveGist} onResetEditors={::this.handleReset} onToggleAutorun={::this.toggleAutorun} /> <div className="content" tabIndex="-1"> <Editors active={activeEditor} code={editorsData.code} html={editorsData.html} json={editorsData.json} error={editorsData.error} headerHeight={Defaults.HEADER_HEIGHT} onCodeChange={::this.handleCodeChange} onHTMLChange={::this.handleHTMLChange} onPackageChange={::this.handlePackageChange} /> <Sandbox bundle={bundle} onModules={::this.handleDependencies} onStartBundle={::this.handleStartBundle} onErrorBundle={::this.handleErrorBundle} onEndBundle={::this.handleEndBundle} /> </div> </div> ); } _parseQuery() { return querystring.parse(window.location.search.slice(1)); } _updateEditorsData(newData) { return Object.assign({}, this.state.editorsData, newData); } _getBundle() { let { editorsData } = this.state; let json; try { json = JSON.parse(editorsData.json); } catch (error) { editorsData = this._updateEditorsData({ error }); this.setState({ editorsData }); return; } return { code: editorsData.transpiledCode, raw: editorsData.code, html: editorsData.html, package: json }; } _transpileCode(code) { return Babel.transform(code, Defaults.BABEL_OPTIONS).code; } _transpileCodeAndCatch(code) { let transpiledCode; let error; if (code) { try { transpiledCode = this._transpileCode(code); } catch (err) { if (err._babel) { transpiledCode = `/* ${err.message || 'Error while transpilation'} */`; error = err; } } } return { transpiledCode, error }; } } export default Main;