UNPKG

feeles-ide

Version:

The hackable and serializable IDE to make learning material

642 lines (549 loc) 20.6 kB
import _Array$from from 'babel-runtime/core-js/array/from'; import _getIterator from 'babel-runtime/core-js/get-iterator'; import _regeneratorRuntime from 'babel-runtime/regenerator'; import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator'; 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, { Component } from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import HTML5Backend from 'react-dnd-html5-backend'; import TouchBackend from 'react-dnd-touch-backend'; import URLSearchParams from 'url-search-params'; import { DragDropContext } from 'react-dnd'; import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import { grey300, grey700 } from 'material-ui/styles/colors'; import transitions from 'material-ui/styles/transitions'; import { readProject, findProject } from '../database/'; import { makeFromElement, BinaryFile, SourceFile, validateType } from '../File/'; import getLocalization from '../localization/'; import getCustomTheme from '../js/getCustomTheme'; import Main from './Main'; import LaunchDialog from './LaunchDialog'; 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 seedToFile = function seedToFile(seed) { if (validateType('blob', seed.type)) { return new BinaryFile(seed); } else { return new SourceFile(seed); } }; var RootComponent = function (_Component) { _inherits(RootComponent, _Component); function RootComponent() { var _ref, _this2 = this; var _temp, _this, _ret; _classCallCheck(this, RootComponent); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = RootComponent.__proto__ || _Object$getPrototypeOf(RootComponent)).call.apply(_ref, [this].concat(args))), _this), _this.state = { last: Infinity, files: [], // An object has project info project: null, localization: null, muiTheme: getCustomTheme({}), openDialog: false, // continuous deploying URL (if null, do first deployment) deployURL: null, retryCount: 0, errorText: null }, _this.launchIDE = function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref3) { var id = _ref3.id, title = _ref3.title; var titleIsRequired, _ref4, project, query, length; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!id && !title) { // Required unique title of project to proxy it titleIsRequired = _this.state.localization.cloneDialog.titleIsRequired; _this.setState({ errorText: titleIsRequired }); console.info(titleIsRequired); } _context.next = 3; return id ? findProject(id) : readProject(title); case 3: _ref4 = _context.sent; project = _ref4.project; query = _ref4.query; length = _ref4.length; _this.setState({ last: length, files: [], project: project, deployURL: project.deployURL }); query.each(function (value) { var seed = value.serializedFile; var file = seedToFile(seed); _this.progress(file); }); case 9: case 'end': return _context.stop(); } } }, _callee, _this2); })); return function (_x) { return _ref2.apply(this, arguments); }; }(), _this.launchFromElements = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { var query, elements, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, item; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: // from script elements query = _this.props.rootElement.getAttribute('data-target'); elements = document.querySelectorAll('script' + query); _this.setState({ last: elements.length }); _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; _context2.prev = 6; _iterator = _getIterator(_Array$from(elements)); case 8: if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { _context2.next = 19; break; } item = _step.value; _context2.t0 = _this; _context2.next = 13; return makeFromElement(item); case 13: _context2.t1 = _context2.sent; _context2.next = 16; return _context2.t0.progress.call(_context2.t0, _context2.t1); case 16: _iteratorNormalCompletion = true; _context2.next = 8; break; case 19: _context2.next = 25; break; case 21: _context2.prev = 21; _context2.t2 = _context2['catch'](6); _didIteratorError = true; _iteratorError = _context2.t2; case 25: _context2.prev = 25; _context2.prev = 26; if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } case 28: _context2.prev = 28; if (!_didIteratorError) { _context2.next = 31; break; } throw _iteratorError; case 31: return _context2.finish(28); case 32: return _context2.finish(25); case 33: case 'end': return _context2.stop(); } } }, _callee2, _this2, [[6, 21, 25, 33], [26,, 28, 32]]); })), _this.launchFromURL = function () { var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(url) { var text, response, delay, seeds, errorText, _errorText, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, seed, file; return _regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: // from json file URL text = void 0; _context3.prev = 1; _context3.next = 4; return fetch(url); case 4: response = _context3.sent; _context3.next = 7; return response.text(); case 7: text = _context3.sent; _context3.next = 15; break; case 10: _context3.prev = 10; _context3.t0 = _context3['catch'](1); _this.setState(function (prevState) { return { retryCount: prevState.retryCount + 1, errorText: _context3.t0.message }; }); // Auto retry delay = Math.pow(2, _this.state.retryCount + 1); return _context3.abrupt('return', new _Promise(function (resolve, reject) { window.setTimeout(function () { _this.launchFromURL(url).then(resolve, reject); }, delay * 1000); })); case 15: seeds = []; _context3.prev = 16; seeds = JSON.parse(text); _context3.next = 27; break; case 20: _context3.prev = 20; _context3.t1 = _context3['catch'](16); console.log(text); errorText = url + ' is not valid JSON. Check the text in console.'; console.info(errorText); _this.setState({ errorText: errorText }); return _context3.abrupt('return'); case 27: if (Array.isArray(seeds)) { _context3.next = 33; break; } console.log(seeds); _errorText = 'Source JSON file must be an array. Check the value in cosole.'; console.info(_errorText); _this.setState({ errorText: _errorText }); return _context3.abrupt('return'); case 33: _this.setState({ last: seeds.length, files: [] }); _iteratorNormalCompletion2 = true; _didIteratorError2 = false; _iteratorError2 = undefined; _context3.prev = 37; _iterator2 = _getIterator(seeds); case 39: if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) { _context3.next = 47; break; } seed = _step2.value; file = seedToFile(seed); _context3.next = 44; return _this.progress(file); case 44: _iteratorNormalCompletion2 = true; _context3.next = 39; break; case 47: _context3.next = 53; break; case 49: _context3.prev = 49; _context3.t2 = _context3['catch'](37); _didIteratorError2 = true; _iteratorError2 = _context3.t2; case 53: _context3.prev = 53; _context3.prev = 54; if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } case 56: _context3.prev = 56; if (!_didIteratorError2) { _context3.next = 59; break; } throw _iteratorError2; case 59: return _context3.finish(56); case 60: return _context3.finish(53); case 61: case 'end': return _context3.stop(); } } }, _callee3, _this2, [[1, 10], [16, 20], [37, 49, 53, 61], [54,, 56, 60]]); })); return function (_x2) { return _ref6.apply(this, arguments); }; }(), _this.defaultLaunch = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() { return _regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: if (!(typeof _this.props.jsonURL === 'string')) { _context4.next = 5; break; } _context4.next = 3; return _this.launchFromURL(_this.props.jsonURL); case 3: _context4.next = 7; break; case 5: _context4.next = 7; return _this.launchFromElements(); case 7: case 'end': return _context4.stop(); } } }, _callee4, _this2); })), _this.setLocalization = function (langs) { langs = [].concat(langs); var localization = getLocalization(langs); if (localization) { _this.setState({ localization: localization }); moment.locale(localization.ll_CC); } else { throw new TypeError('setLocalization: Cannot parse ' + langs.join()); } }, _this.setMuiTheme = function (theme) { return _this.setState({ muiTheme: getCustomTheme(theme) }); }, _this.closeDialog = function () { return _this.setState({ openDialog: false }); }, _this.renderLoading = function () { var _this$state = _this.state, last = _this$state.last, files = _this$state.files, errorText = _this$state.errorText, retryCount = _this$state.retryCount; var styles = { root: { display: 'flex', width: '100%', height: '100%', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }, header: { marginTop: 0, fontWeight: 100, color: 'white', fontFamily: '"Apple Chancery", cursive' }, errorText: { color: 'red' }, count: { color: grey700, fontSize: '.5rem', fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', wordBreak: 'break-all', marginBottom: '1.5rem' } }; var author = document.querySelector('meta[name="og:author"],meta[property="og:author"]'); var title = document.querySelector('meta[name="og:title"],meta[property="og:title"]'); return React.createElement( 'div', { style: styles.root }, React.createElement( 'h1', { style: styles.header }, title ? title.getAttribute('content') : document.title || '❤️' ), errorText && React.createElement( 'span', { style: styles.errorText }, errorText ), retryCount > 0 ? React.createElement( 'div', null, Math.pow(2, retryCount), '\u79D2\u5F8C\u306B\u3082\u3046\u4E00\u5EA6\u63A5\u7D9A\u3057\u307E\u3059...' ) : null, author && React.createElement( 'h2', { style: styles.header }, author.getAttribute('content') ), last < Infinity ? React.createElement( 'span', { style: styles.count }, indicator(files.length, last) ) : null, React.createElement( 'span', { style: styles.header }, 'Made with Feeles' ), React.createElement(LaunchDialog, { open: _this.state.openDialog, localization: _this.state.localization, launchIDE: _this.launchIDE, fallback: _this.defaultLaunch, onRequestClose: _this.closeDialog }), React.createElement( 'style', null, '\n html, body {\n background-color: ' + grey300 + ';\n transition: ' + transitions.easeOut('4000ms') + ';\n }\n ' ) ); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(RootComponent, [{ key: 'componentWillMount', value: function componentWillMount() { var _props = this.props, title = _props.title, seeds = _props.seeds, disableLocalSave = _props.disableLocalSave; var langs = [].concat(new URLSearchParams(location.search).getAll('lang')) // ?lang=ll_CC .concat(navigator.languages || navigator.language); // browser settings this.setLocalization(langs); var deployInfo = document.querySelector('script[x-feeles-deploy]'); if (deployInfo) { this.setState({ deployURL: deployInfo.getAttribute('x-feeles-deploy') }); } if (Array.isArray(seeds)) { this.setState({ last: 0, files: seeds.map(seedToFile) }); } else if (typeof title === 'string') { // From indexedDB this.launchIDE({ title: title }); } else if (disableLocalSave) { // Use Feeles as a module. Do not access any local data. this.defaultLaunch(); } else { if (process.env.NODE_ENV !== 'production') { if (this.props.jsonURL) { this.launchFromURL(this.props.jsonURL); return; } } this.setState({ openDialog: true }); } } }, { key: 'progress', value: function () { var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(file) { return _regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: if (!(Math.random() < 0.1 || this.state.last === 1)) { _context5.next = 3; break; } _context5.next = 3; return new _Promise(function (resolve) { requestAnimationFrame(resolve); }); case 3: this.setState(function (prevState) { return { last: prevState.last - 1, files: prevState.files.concat(file) }; }); case 4: case 'end': return _context5.stop(); } } }, _callee5, this); })); function progress(_x3) { return _ref8.apply(this, arguments); } return progress; }() }, { key: 'render', value: function render() { var _this3 = this; var rootElement = this.props.rootElement; return React.createElement( MuiThemeProvider, { muiTheme: this.state.muiTheme }, this.state.last > 0 ? this.renderLoading() : React.createElement(Main, { files: this.state.files, rootElement: rootElement, rootStyle: getComputedStyle(rootElement), project: this.state.project, launchIDE: this.launchIDE, localization: this.state.localization, setLocalization: this.setLocalization, muiTheme: this.state.muiTheme, setMuiTheme: this.setMuiTheme, deployURL: this.state.deployURL, setDeployURL: function setDeployURL(deployURL) { return _this3.setState({ deployURL: deployURL }); }, onChange: this.props.onChange, onMessage: this.props.onMessage, onThumbnailChange: this.props.onThumbnailChange, disableLocalSave: this.props.disableLocalSave, disableScreenShotCard: this.props.disableScreenShotCard }) ); } }]); return RootComponent; }(Component); RootComponent.propTypes = { rootElement: PropTypes.object.isRequired, // Array of seed object seeds: PropTypes.array, // A string as title of project opened title: PropTypes.string, // An URL string as JSON file provided jsonURL: PropTypes.string, // An URL string to continuous deploying deployURL: PropTypes.string, // Handle file change onChange: PropTypes.func, // Handle message from iframe onMessage: PropTypes.func, // Handle screenshot image change onThumbnailChange: PropTypes.func, // For using external DB disableLocalSave: PropTypes.bool, // For using external thumbnail manager disableScreenShotCard: PropTypes.bool }; RootComponent.defaultProps = { disableLocalSave: false, disableScreenShotCard: false }; var dndBackend = 'ontouchend' in document ? TouchBackend : HTML5Backend; export default DragDropContext(dndBackend)(RootComponent); function indicator(val, last) { var length = 32; var sum = Math.max(1, val + last) + 0.00001; var progress = Math.floor(val / sum * length); return '='.repeat(progress) + '+' + '-'.repeat(length - 1 - progress); }