UNPKG

feeles-ide

Version:

The hackable and serializable IDE to make learning material

709 lines (596 loc) 24.4 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf3 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _typestyle = require("typestyle"); var _moment = _interopRequireDefault(require("moment")); var _reactDndHtml5Backend = _interopRequireDefault(require("react-dnd-html5-backend")); var _reactDndTouchBackend = _interopRequireDefault(require("react-dnd-touch-backend")); var _urlSearchParams = _interopRequireDefault(require("url-search-params")); var _reactDnd = require("react-dnd"); var _styles = require("@material-ui/core/styles"); var _grey = _interopRequireDefault(require("@material-ui/core/colors/grey")); var _transitions = _interopRequireDefault(require("@material-ui/core/styles/transitions")); var _database = require("../database/"); var _File = require("../File/"); var _localization = _interopRequireDefault(require("../localization/")); var _getCustomTheme = _interopRequireDefault(require("../js/getCustomTheme")); var _Main = _interopRequireDefault(require("./Main")); var _LaunchDialog = _interopRequireDefault(require("./LaunchDialog")); var _ErrorBoundary = _interopRequireDefault(require("./ErrorBoundary")); var _MaterialUIJssProvider = _interopRequireDefault(require("./MaterialUIJssProvider")); var _fetchPonyfill = _interopRequireDefault(require("fetch-ponyfill")); var fetch = window.fetch || // for IE11 (0, _fetchPonyfill.default)({ // TODO: use babel-runtime to rewrite this into require("babel-runtime/core-js/promise") Promise: Promise }).fetch; var seedToFile = function seedToFile(seed) { if ((0, _File.validateType)('blob', seed.type)) { return new _File.BinaryFile(seed); } else { return new _File.SourceFile(seed); } }; var RootComponent = /*#__PURE__*/ function (_Component) { (0, _inherits2.default)(RootComponent, _Component); function RootComponent() { var _getPrototypeOf2; var _this; (0, _classCallCheck2.default)(this, RootComponent); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = (0, _possibleConstructorReturn2.default)(this, (_getPrototypeOf2 = (0, _getPrototypeOf3.default)(RootComponent)).call.apply(_getPrototypeOf2, [this].concat(args))); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "state", { cardProps: null, last: Infinity, files: [], // An object has project info project: null, localization: null, muiTheme: (0, _getCustomTheme.default)({}), openDialog: false, retryCount: 0, errorText: null }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "launchIDE", /*#__PURE__*/ function () { var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee(_ref) { var id, title, titleIsRequired, _ref3, project, query, length; return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: id = _ref.id, title = _ref.title; if (!id && !title) { // Required unique title of project to proxy it titleIsRequired = _this.localization.cloneDialog.titleIsRequired; _this.setState({ errorText: titleIsRequired }); console.info(titleIsRequired); } _context.next = 4; return id ? (0, _database.findProject)(id) : (0, _database.readProject)(title); case 4: _ref3 = _context.sent; project = _ref3.project; query = _ref3.query; length = _ref3.length; _this.setState({ last: length, files: [], project: project }); query.each(function (value) { var seed = value.serializedFile; var file = seedToFile(seed); _this.progress(file); }); case 10: case "end": return _context.stop(); } } }, _callee, this); })); return function (_x) { return _ref2.apply(this, arguments); }; }()); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "launchFromElements", /*#__PURE__*/ (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee2() { var query, elements, _arr, _i, item; return _regenerator.default.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".concat(query)); _this.setState({ last: elements.length }); _arr = Array.from(elements); _i = 0; case 5: if (!(_i < _arr.length)) { _context2.next = 16; break; } item = _arr[_i]; _context2.t0 = _this; _context2.next = 10; return (0, _File.makeFromElement)(item); case 10: _context2.t1 = _context2.sent; _context2.next = 13; return _context2.t0.progress.call(_context2.t0, _context2.t1); case 13: _i++; _context2.next = 5; break; case 16: case "end": return _context2.stop(); } } }, _callee2, this); }))); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "launchFromURL", /*#__PURE__*/ function () { var _ref5 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee3(url) { var text, response, delay, seeds, errorText, _errorText, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, seed, file; return _regenerator.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: _context3.prev = 0; _context3.next = 3; return fetch(url); case 3: response = _context3.sent; _context3.next = 6; return response.text(); case 6: text = _context3.sent; _context3.next = 14; break; case 9: _context3.prev = 9; _context3.t0 = _context3["catch"](0); _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 14: seeds = []; _context3.prev = 15; seeds = JSON.parse(text); _context3.next = 26; break; case 19: _context3.prev = 19; _context3.t1 = _context3["catch"](15); console.log(text); errorText = "".concat(url, " is not valid JSON. Check the text in console."); console.info(errorText); _this.setState({ errorText: errorText }); return _context3.abrupt("return"); case 26: if (Array.isArray(seeds)) { _context3.next = 32; 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 32: _this.setState({ last: seeds.length, files: [] }); _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; _context3.prev = 36; _iterator = seeds[Symbol.iterator](); case 38: if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { _context3.next = 46; break; } seed = _step.value; file = seedToFile(seed); _context3.next = 43; return _this.progress(file); case 43: _iteratorNormalCompletion = true; _context3.next = 38; break; case 46: _context3.next = 52; break; case 48: _context3.prev = 48; _context3.t2 = _context3["catch"](36); _didIteratorError = true; _iteratorError = _context3.t2; case 52: _context3.prev = 52; _context3.prev = 53; if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } case 55: _context3.prev = 55; if (!_didIteratorError) { _context3.next = 58; break; } throw _iteratorError; case 58: return _context3.finish(55); case 59: return _context3.finish(52); case 60: case "end": return _context3.stop(); } } }, _callee3, this, [[0, 9], [15, 19], [36, 48, 52, 60], [53,, 55, 59]]); })); return function (_x2) { return _ref5.apply(this, arguments); }; }()); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "defaultLaunch", /*#__PURE__*/ (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee4() { return _regenerator.default.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, this); }))); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "setLocalization", function (langs) { langs = [].concat(langs); var localization = (0, _localization.default)(langs); if (localization) { _this.setState({ localization: localization }); _moment.default.locale(localization.ll_CC); } else { throw new TypeError("setLocalization: Cannot parse ".concat(langs.join())); } }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "setMuiTheme", function (theme) { return _this.setState({ muiTheme: (0, _getCustomTheme.default)(theme) }); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "closeDialog", function () { return _this.setState({ openDialog: false }); }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "setCardProps", function (prevPropsToProps) { if (_this.props.onCardPropsChange) { // onCardPropsChange がある場合, 変更は上位の component に委ねる // cardProps が与えられていれば RootComponent はステートレスにふるまう var nextProps = prevPropsToProps(_this.props.cardProps); _this.props.onCardPropsChange(nextProps); } else { // onCardPropsChange がない場合, ここで一時的に state を変更する _this.setState({ cardProps: prevPropsToProps(_this.state.cardProps) }); } }); (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_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: _grey.default['700'], 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.default.createElement("div", { className: styles.root }, _react.default.createElement("h1", { className: styles.header }, title ? title.getAttribute('content') : document.title || '❤️'), errorText && _react.default.createElement("span", { className: styles.errorText }, errorText), retryCount > 0 ? _react.default.createElement("div", null, Math.pow(2, retryCount), "\u79D2\u5F8C\u306B\u3082\u3046\u4E00\u5EA6\u63A5\u7D9A\u3057\u307E\u3059...") : null, author && _react.default.createElement("h2", { className: styles.header }, author.getAttribute('content')), last < Infinity ? _react.default.createElement("span", { className: styles.count }, indicator(files.length, last)) : null, _react.default.createElement("span", { className: styles.header }, "Made with Feeles"), _react.default.createElement(_LaunchDialog.default, { open: _this.state.openDialog, localization: _this.localization, launchIDE: _this.launchIDE, fallback: _this.defaultLaunch, onRequestClose: _this.closeDialog }), _react.default.createElement("style", null, "\n html, body {\n background-color: ".concat(_grey.default['300'], ";\n transition: ").concat(_transitions.default.create(['all'], { duration: 4000 }), ";\n }\n "))); }); return _this; } (0, _createClass2.default)(RootComponent, [{ key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { // onCardPropsChange があるのに cardProps がないということは // 上位 component が props を変更してもここには来ないということなので, エラー if (this.props.onCardPropsChange && !this.props.cardProps) { throw new TypeError('Props onCardPropsChange was given but missing cardProps!'); } // 基本的に cardProps は props で与えられたものをそのまま使う (Stateless) // onCardPropsChange が与えられなかった場合のみ内部的に state を変える if (prevProps.cardProps !== this.props.cardProps) { this.setState({ cardProps: this.props.cardProps }); } if (this.state.last === 0 && this.state.cardProps === null) { // ファイルのロードが終了したので, cardProps を設定する if (this.props.cardProps) { // 上位の component から props が与えられている this.setState({ cardProps: this.props.cardProps }); } else { // 与えられていないので、feeles/card.json を探す var card = this.state.files.find(function (file) { return file.name === 'feeles/card.json'; }); if (!card) { throw new Error('Missing feeles/card.json'); } else { this.setState({ cardProps: card.json }); } } } } }, { key: "componentDidMount", value: function componentDidMount() { var _this$props = this.props, title = _this$props.title, seeds = _this$props.seeds, disableLocalSave = _this$props.disableLocalSave; this.setLocalization(this.langs); 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 _progress = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee5(file) { return _regenerator.default.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 _progress.apply(this, arguments); } return progress; }() }, { key: "render", value: function render() { var rootElement = this.props.rootElement; return _react.default.createElement(_ErrorBoundary.default, null, _react.default.createElement(_MaterialUIJssProvider.default, null, _react.default.createElement(_styles.MuiThemeProvider, { theme: this.state.muiTheme }, this.state.last > 0 ? this.renderLoading() : this.state.cardProps ? _react.default.createElement(_Main.default, { cardProps: this.state.cardProps, setCardProps: this.setCardProps, openSidebar: this.props.openSidebar, mini: this.props.mini, files: this.state.files, rootElement: rootElement, rootStyle: getComputedStyle(rootElement), project: this.state.project, launchIDE: this.launchIDE, localization: this.localization, setLocalization: this.setLocalization, muiTheme: this.state.muiTheme, setMuiTheme: this.setMuiTheme, onChange: this.props.onChange, onMessage: this.props.onMessage, onThumbnailChange: this.props.onThumbnailChange, disableLocalSave: this.props.disableLocalSave, asset: this.props.asset }) : _react.default.createElement("div", null)))); } }, { key: "langs", get: function get() { return [].concat(new _urlSearchParams.default(location.search).getAll('lang')) // ?lang=ll_CC .concat(navigator.languages || navigator.language); // browser settings } }, { key: "localization", get: function get() { return this.state.localization || (0, _localization.default)(this.langs); } }]); return RootComponent; }(_react.Component); (0, _defineProperty2.default)(RootComponent, "propTypes", { // Props of cardProps cardProps: _propTypes.default.object, // Handler of changing cardProps onCardPropsChange: _propTypes.default.func, // Toggle left menubar openSidebar: _propTypes.default.bool, // Disappear top navbar mini: _propTypes.default.bool, // Root element rootElement: _propTypes.default.object.isRequired, // Array of seed object seeds: _propTypes.default.array, // A string as title of project opened title: _propTypes.default.string, // An URL string as JSON file provided jsonURL: _propTypes.default.string, // Handle file change onChange: _propTypes.default.func, // Handle message from iframe onMessage: _propTypes.default.func, // Handle screenshot image change onThumbnailChange: _propTypes.default.func, // For using external DB disableLocalSave: _propTypes.default.bool, // Asset definition as a module asset: _propTypes.default.object }); (0, _defineProperty2.default)(RootComponent, "defaultProps", { openSidebar: false, mini: false, disableLocalSave: false }); var dndBackend = 'ontouchend' in document ? _reactDndTouchBackend.default : _reactDndHtml5Backend.default; var _default = (0, _reactDnd.DragDropContext)(dndBackend)(RootComponent); exports.default = _default; 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); }