feeles-ide
Version:
The hackable and serializable IDE to make learning material
709 lines (596 loc) • 24.4 kB
JavaScript
"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);
}