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