UNPKG

feeles-ide

Version:

The hackable and serializable IDE to make learning material

981 lines (828 loc) 29.9 kB
import _Object$assign from 'babel-runtime/core-js/object/assign'; import _getIterator from 'babel-runtime/core-js/get-iterator'; import _Map from 'babel-runtime/core-js/map'; import _Object$values from 'babel-runtime/core-js/object/values'; import _toConsumableArray from 'babel-runtime/helpers/toConsumableArray'; import _slicedToArray from 'babel-runtime/helpers/slicedToArray'; import _extends from 'babel-runtime/helpers/extends'; import _regeneratorRuntime from 'babel-runtime/regenerator'; import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator'; import _JSON$stringify from 'babel-runtime/core-js/json/stringify'; import _Object$getPrototypeOf from 'babel-runtime/core-js/object/get-prototype-of'; import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import _createClass from 'babel-runtime/helpers/createClass'; import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn'; import _inherits from 'babel-runtime/helpers/inherits'; import _Promise from 'babel-runtime/core-js/promise'; import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import Popout from '../../jsx/ReactPopout'; import { SourceFile, makeFromFile } from '../../File/'; import composeEnv from '../../File/composeEnv'; import Screen from './Screen'; import setSrcDoc from './setSrcDoc'; import registerHTML from './registerHTML'; import uniqueId from '../../utils/uniqueId'; import { getPrimaryUser } from '../../database/'; import fetchPonyfill from 'fetch-ponyfill'; var fetch = window.fetch || // for IE11 fetchPonyfill({ // TODO: use babel-runtime to rewrite this into require("babel-runtime/core-js/promise") Promise: _Promise }).fetch; var ConnectionTimeout = 1000; var popoutTemplate = '<!DOCTYPE html>\n<html>\n <head>\n <meta charset="utf-8">\n <style media="screen">\n body {\n margin: 0;\n }\n #popout-content-container {\n width: 100vw;\n height: 100vh;\n display: flex;\n }\n </style>\n </head>\n <body>\n </body>\n</html>\n'; var popoutURL = URL.createObjectURL(new Blob([popoutTemplate], { type: 'text/html' })); var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; var webkitSpeechGrammarList = window.webkitSpeechGrammarList; var getStyle = function getStyle(props, context) { var transitions = context.muiTheme.transitions; var fullScreen = function fullScreen(yes, no) { return props.isFullScreen ? yes : no; }; return { root: { position: fullScreen('fixed', 'relative'), width: '100%', height: '100%', left: 0, top: 0, boxSizing: 'border-box', opacity: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', overflow: 'hidden', zIndex: 300, transition: transitions.easeOut() }, swap: { position: 'absolute', right: 0, zIndex: 2 } }; }; var Monitor = function (_PureComponent) { _inherits(Monitor, _PureComponent); function Monitor() { var _ref; var _temp, _this, _ret; _classCallCheck(this, Monitor); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Monitor.__proto__ || _Object$getPrototypeOf(Monitor)).call.apply(_ref, [this].concat(args))), _this), _initialiseProps.call(_this), _temp), _possibleConstructorReturn(_this, _ret); } _createClass(Monitor, [{ key: 'componentWillMount', value: function componentWillMount() { // feeles.github.io/sample/#/path/to/index.html window.addEventListener('hashchange', this.handleHashChanged); if (/^#\//.test(location.hash)) { this.handleHashChanged(); } else { // default href で起動 this.props.setLocation(); } var globalEvent = this.props.globalEvent; var on = globalEvent.on.bind(globalEvent); on('postMessage', this.handlePostMessage); on('message.fetch', this.handleFetch); on('message.resolve', this.handleResolve); on('message.fetchDataURL', this.handleFetchDataURL); on('message.saveAs', this.handleSaveAs); on('message.reload', this.handleReload); on('message.replace', this.handleReplace); on('message.error', this.handleError); on('message.ipcRenderer.*', this.handleIpcRenderer); on('message.api.SpeechRecognition', this.handleSpeechRecognition); on('message.setTimeout', this.handleSetTimeout); on('message.clearTimeout', this.handleClearTimeout); on('message.setInterval', this.handleSetInterval); on('message.clearInterval', this.handleClearInterval); on('message.openWindow', this.handleOpenWindow); } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { var _this2 = this; if (prevProps.reboot && !this.props.reboot) { if (this.props.isPopout || this.popoutClosed) { // react-popoutがpopoutWindowにDOMをrenderした後でstartする必要がある // renderを補足するのは難しい&updateの度に何度もrenderされる=>delayを入れる setTimeout(function () { return _this2.start(); }, 500); this.popoutClosed = false; } else { this.start(); } } if (prevProps.isPopout && !this.props.isPopout) { this.popoutClosed = true; // Use delay } } }, { key: 'componentDidMount', value: function componentDidMount() { var _this3 = this; if (window.ipcRenderer) { this._emit = window.ipcRenderer.emit; // あとで戻せるようオリジナルを保持 var self = this; window.ipcRenderer.emit = function () { for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } // ipcRenderer.emit をオーバーライドし, 全ての postMessage で送る if (self.state && self.state.port) { self.state.port.postMessage({ query: 'ipcRenderer.emit', value: JSON.parse(_JSON$stringify(args)) }); } _this3._emit.apply(_this3, args); }; } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { window.removeEventListener('hashchange', this.handleHashChanged); if (window.ipcRenderer) { // オリジナルの参照を戻す. Monitor が複数 mount されることはない(はず) window.ipcRenderer.emit = this._emit; } } }, { key: 'start', value: function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() { var _this4 = this; var _prevent; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _prevent = this.prevent; this.prevent = _prevent.then(function () { return _this4.startProcess(); }).catch(function (error) { if (error) { _this4.setState({ error: error }); } else if (_this4.props.isPopout) { _this4.start(); } }); _context.next = 4; return _prevent; case 4: this.setState({ error: null, port: null }); case 5: case 'end': return _context.stop(); } } }, _callee, this); })); function start() { return _ref2.apply(this, arguments); } return start; }() }, { key: 'startProcess', value: function () { var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { var _this5 = this; var _props, getConfig, findFile, env, versionUUIDFile, element, htmlFile, html, tryLoading, _ref4, port1, port2; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _props = this.props, getConfig = _props.getConfig, findFile = _props.findFile; // env env = composeEnv(getConfig('env')); versionUUIDFile = findFile('feeles/.uuid'); if (versionUUIDFile) { env.VERSION_UUID = versionUUIDFile.text; } else { // Backward compatibility element = document.querySelector('meta[name="version_uuid"]'); if (element) { env.VERSION_UUID = element.getAttribute('content'); } } _context2.next = 6; return getPrimaryUser(); case 6: env.USER_UUID = _context2.sent.uuid; htmlFile = this.props.findFile(this.props.href) || SourceFile.html(); _context2.next = 10; return registerHTML(htmlFile.text, this.props.findFile, env); case 10: html = _context2.sent; tryLoading = function tryLoading() { return new _Promise(function (resolve, reject) { // iframe.srcdoc, onload => resolve setSrcDoc(_this5.iframe, html, resolve); // Connection timeouted, then retry setTimeout(reject, ConnectionTimeout); }).catch(function () { return tryLoading(); }); }; _context2.next = 14; return tryLoading(); case 14: _ref4 = new MessageChannel(), port1 = _ref4.port1, port2 = _ref4.port2; port1.addEventListener('message', function (event) { var reply = function reply(params) { params = _extends({ id: event.data.id }, params || {}); port1.postMessage(params); }; var type = event.type, data = event.data; var name = data.query ? 'message.' + data.query : 'message'; _this5.props.globalEvent.emit(name, { type: type, data: data, reply: reply }); }); port1.start(); this.setState({ port: port1 }); this.iframe.contentWindow.postMessage({ env: env }, '*', [port2]); case 19: case 'end': return _context2.stop(); } } }, _callee2, this); })); function startProcess() { return _ref3.apply(this, arguments); } return startProcess; }() }, { key: 'render', value: function render() { var _this6 = this; var error = this.state.error; var _props2 = this.props, isPopout = _props2.isPopout, reboot = _props2.reboot; var popout = isPopout && !reboot ? React.createElement( Popout, { url: popoutURL, title: 'app', options: this.popoutOptions, window: { open: this.handlePopoutOpen, addEventListener: window.addEventListener.bind(window), removeEventListener: window.removeEventListener.bind(window) }, onClosing: this.handlePopoutClose }, React.createElement(Screen, { display: true, frameRef: this.handleFrame, handleReload: function handleReload() { return _this6.props.setLocation(); }, reboot: reboot, error: error, width: this.props.frameWidth, height: this.props.frameHeight }) ) : null; var styles = getStyle(this.props, this.context, this.state); return React.createElement( 'div', { style: styles.root, onClick: this.handleTouch }, popout, React.createElement(Screen, { animation: true, display: !isPopout, frameRef: this.handleFrame, reboot: reboot, error: error, width: this.props.frameWidth, height: this.props.frameHeight, isFullScreen: this.props.isFullScreen }) ); } }, { key: 'iframe', get: function get() { return this.props.isPopout ? this.popoutFrame : this.inlineFrame; } }]); return Monitor; }(PureComponent); Monitor.propTypes = { files: PropTypes.array.isRequired, cards: PropTypes.object.isRequired, isPopout: PropTypes.bool.isRequired, isFullScreen: PropTypes.bool.isRequired, reboot: PropTypes.bool.isRequired, href: PropTypes.string.isRequired, togglePopout: PropTypes.func.isRequired, toggleFullScreen: PropTypes.func.isRequired, localization: PropTypes.object.isRequired, getConfig: PropTypes.func.isRequired, addFile: PropTypes.func.isRequired, findFile: PropTypes.func.isRequired, putFile: PropTypes.func.isRequired, setLocation: PropTypes.func.isRequired, frameWidth: PropTypes.number.isRequired, frameHeight: PropTypes.number.isRequired, globalEvent: PropTypes.object.isRequired }; Monitor.contextTypes = { muiTheme: PropTypes.object.isRequired }; var _initialiseProps = function _initialiseProps() { var _this7 = this; this.state = { error: null, port: null }; this.popoutOptions = { width: 300, height: 150, // means innerHeight of browser expecting Safari. left: 50, top: 50 }; this.popoutClosed = false; this.prevent = _Promise.resolve(); this.handlePostMessage = function (value) { // emitAsync('postMessage', value) var port = _this7.state.port; if (!port) return; // reply を receive するための id value = _extends({ id: uniqueId() }, value); return new _Promise(function (resolve) { // catch reply message (once) var task = function task(event) { if (!event.data || event.data.id !== value.id) return; if (port) port.removeEventListener('message', task); resolve(event.data); }; port.addEventListener('message', task); // post message to frame port.postMessage(value); }); }; this.handleFetch = function () { var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(_ref6) { var data = _ref6.data, reply = _ref6.reply; var file, response, blob; return _regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: file = _this7.props.findFile(data.value); if (!file) { _context3.next = 5; break; } reply({ value: file.blob }); _context3.next = 22; break; case 5: if (!(data.value.indexOf('http') === 0)) { _context3.next = 21; break; } _context3.prev = 6; _context3.next = 9; return fetch(data.value); case 9: response = _context3.sent; _context3.next = 12; return response.blob(); case 12: blob = _context3.sent; reply({ value: blob }); _context3.next = 19; break; case 16: _context3.prev = 16; _context3.t0 = _context3['catch'](6); reply({ error: _context3.t0 }); case 19: _context3.next = 22; break; case 21: reply({ error: true }); case 22: case 'end': return _context3.stop(); } } }, _callee3, _this7, [[6, 16]]); })); return function (_x) { return _ref5.apply(this, arguments); }; }(); this.handleResolve = function () { var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref8) { var data = _ref8.data, reply = _ref8.reply; var file, babelrc, result, response, text; return _regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: file = _this7.props.findFile(data.value + '.js') || _this7.props.findFile(data.value); if (!file) { _context4.next = 9; break; } babelrc = _this7.props.getConfig('babelrc'); _context4.next = 5; return file.babel(babelrc, function (e) { reply({ error: e }); }); case 5: result = _context4.sent; if (result) { reply({ value: result.text }); } _context4.next = 26; break; case 9: if (!(data.value.indexOf('http') === 0)) { _context4.next = 25; break; } _context4.prev = 10; _context4.next = 13; return fetch(data.value); case 13: response = _context4.sent; _context4.next = 16; return response.text(); case 16: text = _context4.sent; reply({ value: text }); _context4.next = 23; break; case 20: _context4.prev = 20; _context4.t0 = _context4['catch'](10); reply({ error: _context4.t0 }); case 23: _context4.next = 26; break; case 25: reply({ error: true }); case 26: case 'end': return _context4.stop(); } } }, _callee4, _this7, [[10, 20]]); })); return function (_x2) { return _ref7.apply(this, arguments); }; }(); this.handleFetchDataURL = function () { var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(_ref10) { var data = _ref10.data, reply = _ref10.reply; var file, response, blob, fileReader; return _regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: file = _this7.props.findFile(data.value); if (!file) { _context5.next = 10; break; } _context5.t0 = reply; _context5.next = 5; return file.toDataURL(); case 5: _context5.t1 = _context5.sent; _context5.t2 = { value: _context5.t1 }; (0, _context5.t0)(_context5.t2); _context5.next = 29; break; case 10: if (!(data.value.indexOf('http') === 0)) { _context5.next = 28; break; } _context5.prev = 11; _context5.next = 14; return fetch(data.value); case 14: response = _context5.sent; _context5.next = 17; return response.blob(); case 17: blob = _context5.sent; fileReader = new FileReader(); fileReader.onload = function () { reply({ value: fileReader.result }); }; fileReader.readAsDataURL(blob); _context5.next = 26; break; case 23: _context5.prev = 23; _context5.t3 = _context5['catch'](11); reply({ error: _context5.t3 }); case 26: _context5.next = 29; break; case 28: reply({ error: true }); case 29: case 'end': return _context5.stop(); } } }, _callee5, _this7, [[11, 23]]); })); return function (_x3) { return _ref9.apply(this, arguments); }; }(); this.handleSaveAs = function () { var _ref11 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(_ref12) { var data = _ref12.data, reply = _ref12.reply; var _data$value, blob, name, file3, exist, key; return _regeneratorRuntime.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: _data$value = _slicedToArray(data.value, 2), blob = _data$value[0], name = _data$value[1]; _context6.next = 3; return makeFromFile(blob); case 3: file3 = _context6.sent; exist = _this7.props.findFile(name); if (!exist) { _context6.next = 11; break; } key = exist.key; _context6.next = 9; return _this7.props.putFile(exist, file3.set({ key: key, name: name })); case 9: _context6.next = 13; break; case 11: _context6.next = 13; return _this7.props.addFile(file3.set({ name: name })); case 13: reply(); case 14: case 'end': return _context6.stop(); } } }, _callee6, _this7); })); return function (_x4) { return _ref11.apply(this, arguments); }; }(); this.handleReload = function () { _this7.props.setLocation(); }; this.handleReplace = function (_ref13) { var data = _ref13.data; location.hash = data.value.replace(/^\/*/, '/'); }; this.handleError = function (_ref14) { var data = _ref14.data; if (!_this7.state.error) { _this7.setState({ error: new Error(data.value) }); } }; this.handleIpcRenderer = function (_ref15) { var data = _ref15.data; if (window.ipcRenderer) { var _window$ipcRenderer; (_window$ipcRenderer = window.ipcRenderer).sendToHost.apply(_window$ipcRenderer, _toConsumableArray(_Object$values(data.value))); } else { console.warn('window.ipcRenderer is not defined'); } }; this.setTimeoutId = new _Map(); this.handleSetTimeout = function (_ref16) { var data = _ref16.data, reply = _ref16.reply; var _data$value2 = data.value, timeoutId = _data$value2.timeoutId, delay = _data$value2.delay; _this7.setTimeoutId.set(timeoutId, setTimeout(reply, delay)); }; this.handleClearTimeout = function (_ref17) { var data = _ref17.data; clearInterval(_this7.setTimeoutId.get(data.value.timeoutId)); }; this.setIntervalId = new _Map(); this.handleSetInterval = function (_ref18) { var data = _ref18.data, reply = _ref18.reply; var _data$value3 = data.value, intervalId = _data$value3.intervalId, delay = _data$value3.delay; _this7.setIntervalId.set(intervalId, setInterval(reply, delay)); }; this.handleClearInterval = function (_ref19) { var data = _ref19.data; clearInterval(_this7.setIntervalId.get(data.value.intervalId)); }; this.handleOpenWindow = function (_ref20) { var value = _ref20.data.value; // value.url が相対パスかどうかを調べる var a = document.createElement('a'); a.href = value.url; if (a.host === location.host) { window.open(value.url, value.target, value.features, value.replace); } else { throw new Error('Cannot open ' + value.url); } }; this.handleSpeechRecognition = function (_ref21) { var data = _ref21.data, reply = _ref21.reply; var recognition = new SpeechRecognition(); var _arr = ['lang', 'continuous', 'interimResults', 'maxAlternatives', 'serviceURI']; for (var _i = 0; _i < _arr.length; _i++) { var prop = _arr[_i]; if (data.value[prop] !== undefined) { recognition[prop] = data.value[prop]; } } if (Array.isArray(data.value.grammars)) { recognition.grammars = new webkitSpeechGrammarList(); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = _getIterator(data.value.grammars), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _ref23 = _step.value; var src = _ref23.src, weight = _ref23.weight; recognition.grammars.addFromString(src, weight); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } recognition.onresult = function (event) { var results = []; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = _getIterator(event.results), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var items = _step2.value; var result = []; result.isFinal = items.isFinal; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = _getIterator(items), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var _ref25 = _step3.value; var confidence = _ref25.confidence, transcript = _ref25.transcript; result.push({ confidence: confidence, transcript: transcript }); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } results.push(result); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } reply({ type: 'result', event: { results: results } }); }; recognition.onerror = function (event) { reply({ type: 'error', event: { error: event.error } }); }; ['audioend', 'audiostart', 'end', 'nomatch', 'soundend', 'soundstart', 'speechend', 'speechstart', 'start'].forEach(function (type) { recognition['on' + type] = function () { reply({ type: type }); }; }); recognition.start(); }; this.handlePopoutOpen = function () { for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { args[_key3] = arguments[_key3]; } _this7.parent = window.open.apply(window, args); if (_this7.parent) { _this7.parent.addEventListener('load', function () { var out = _this7.popoutOptions.height !== _this7.parent.innerHeight; _this7.parent.addEventListener('resize', function () { _this7.popoutOptions = _Object$assign({}, _this7.popoutOptions, { width: _this7.parent.innerWidth, height: out ? _this7.parent.outerHeight : _this7.parent.innerHeight }); }); var popoutMove = setInterval(function () { if (_this7.parent.screenX === _this7.popoutOptions.left && _this7.parent.screenY === _this7.popoutOptions.top) { return; } _this7.popoutOptions = _Object$assign({}, _this7.popoutOptions, { left: _this7.parent.screenX, top: _this7.parent.screenY }); }, 100); _this7.parent.addEventListener('beforeunload', function () { clearInterval(popoutMove); }); }); } return _this7.parent; }; this.handlePopoutClose = function () { if (_this7.props.isPopout && !_this7.props.reboot) { _this7.props.togglePopout(); } }; this.handleFrame = function (ref) { if (!_this7.props.isPopout) { _this7.inlineFrame = ref; } else { _this7.popoutFrame = ref; } }; this.handleTouch = function () { if (_this7.props.isFullScreen) { _this7.props.toggleFullScreen(); } }; this.handleHashChanged = function () { if (/^#\//.test(location.hash)) { var href = location.hash.substr(2); _this7.props.setLocation(href); } }; }; export default Monitor;