UNPKG

feeles-ide

Version:

The hackable and serializable IDE to make learning material

693 lines (619 loc) 23.4 kB
import _slicedToArray from 'babel-runtime/helpers/slicedToArray'; import _Object$entries from 'babel-runtime/core-js/object/entries'; import _regeneratorRuntime from 'babel-runtime/regenerator'; import _extends from 'babel-runtime/helpers/extends'; import _JSON$stringify from 'babel-runtime/core-js/json/stringify'; 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, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import AppBar from 'material-ui/AppBar'; import IconButton from 'material-ui/IconButton'; import IconMenu from 'material-ui/IconMenu'; import MenuItem from 'material-ui/MenuItem'; import FlatButton from 'material-ui/FlatButton'; import Drawer from 'material-ui/Drawer'; import Snackbar from 'material-ui/Snackbar'; import CircularProgress from 'material-ui/CircularProgress'; import Toggle from 'material-ui/Toggle'; import FileDownload from 'material-ui/svg-icons/file/file-download'; import FileCloudUpload from 'material-ui/svg-icons/file/cloud-upload'; import ActionLanguage from 'material-ui/svg-icons/action/language'; import ActionHistory from 'material-ui/svg-icons/action/history'; import ActionAccountCircle from 'material-ui/svg-icons/action/account-circle'; import ActionAutorenew from 'material-ui/svg-icons/action/autorenew'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import NavigationMenu from 'material-ui/svg-icons/navigation/menu'; import ArrowDropRight from 'material-ui/svg-icons/navigation-arrow-drop-right'; import { emphasize } from 'material-ui/utils/colorManipulator'; import TwitterIcon from '../utils/TwitterIcon'; import FacebookIcon from '../utils/FacebookIcon'; import GoogleIcon from '../utils/GoogleIcon'; import { acceptedLanguages } from '../localization/'; import CloneDialog from './CloneDialog'; import MetaDialog from './MetaDialog'; import { updateProject } from '../database/'; import organization from '../organization'; import debugWindow from '../utils/debugWindow'; import open from '../utils/open'; 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 getStyles = function getStyles(props, context) { var palette = context.muiTheme.palette; return { root: { flex: '0 0 auto', display: 'flex', flexWrap: 'wrap', alignItems: 'center', zIndex: null }, leftIcon: { display: props.showAll ? 'block' : 'none', marginTop: 0 }, button: { marginLeft: 20, zIndex: 2 }, projectName: { color: palette.alternateTextColor, fontSize: '.8rem', fontWeight: 600 }, hidden: { opacity: 0, width: 1, height: 1 }, progress: { marginLeft: 20, padding: 12 }, visits: { color: palette.alternateTextColor, fontSize: '.8rem' }, twitter: { color: '#FFFFFF', backgroundColor: '#1DA1F2' }, line: { color: '#FFFFFF', backgroundColor: '#00C300' }, facebook: { color: '#FFFFFF', backgroundColor: '#3B5998' }, google: { color: 'rgba(0,0,0,0.54)', backgroundColor: '#FFFFFF' }, toggle: { width: 'initial', filter: 'contrast(40%)' }, toggleLabel: { color: palette.alternateTextColor } }; }; var Menu = function (_PureComponent) { _inherits(Menu, _PureComponent); function Menu() { var _ref, _this2 = this; var _temp, _this, _ret; _classCallCheck(this, Menu); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Menu.__proto__ || _Object$getPrototypeOf(Menu)).call.apply(_ref, [this].concat(args))), _this), _this.state = { overrideTitle: null, open: false, isDeploying: false, notice: null }, _this.handleClone = function () { _this.props.openFileDialog(CloneDialog, { files: _this.props.files, project: _this.props.project, setProject: _this.props.setProject, launchIDE: _this.props.launchIDE, deployURL: _this.props.deployURL }); }, _this.handleDeploy = function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(withOAuth, isUpdate) { var localization, result, composed, actionURL, body, ogp, feelesrc, response, text, _JSON$parse, search, api, deployURL; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: localization = _this.props.localization; _context.next = 3; return _this.props.openFileDialog(MetaDialog, { getConfig: _this.props.getConfig, setConfig: _this.props.setConfig, findFile: _this.props.findFile }); case 3: result = _context.sent; if (result) { _context.next = 6; break; } return _context.abrupt('return'); case 6: _this.setState({ isDeploying: true }); _context.prev = 7; _context.next = 10; return _Promise.all(_this.filesForPublishing.map(function (item) { return item.collect(); })); case 10: composed = _context.sent; // isUpdate の場合は PUT products/:search, そうでない場合は POST products actionURL = isUpdate ? _this.props.deployURL : organization.api.deploy; body = new URLSearchParams(); body.append('json', _JSON$stringify(composed)); body.append('script_src', 'https://unpkg.com/feeles-ide@latest/umd/index.js'); if (withOAuth) { body.append('oauth_id', _this.props.oAuthId); } if (isUpdate) { body.append('_method', 'PUT'); // Update og:url ogp = _this.props.getConfig('ogp'); if (ogp['og:url'] !== _this.shareURL) { _this.props.setConfig('ogp', _extends({}, ogp, { 'og:url': _this.shareURL })); } } body.append('ogp', _JSON$stringify(_this.props.getConfig('ogp'))); // .feelesrc.yml の情報を付与 feelesrc = _this.props.loadConfig('feelesrc'); if (!isUpdate && feelesrc.kitIdentifier) { // キットの識別子 e.g. com.feeles.hack-rpg body.append('kit_identifier', feelesrc.kitIdentifier); } // store/update action _context.next = 22; return fetch(actionURL, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }, // [Safari Bug] URLSearchParams not supported in bodyInit body: body.toString(), mode: 'cors' }); case 22: response = _context.sent; if (!response.ok) { _context.next = 37; break; } _context.next = 26; return response.text(); case 26: text = _context.sent; _JSON$parse = JSON.parse(text), search = _JSON$parse.search; api = new URL(actionURL); deployURL = api.origin + '/api/v1/products/' + search; _this.props.setDeployURL(deployURL); if (!_this.props.project) { _context.next = 34; break; } _context.next = 34; return updateProject(_this.props.project.id, { deployURL: deployURL }); case 34: _this.setState({ notice: { message: localization.menu.published, action: localization.menu.goToSee, autoHideDuration: 20000, onActionClick: function onActionClick() { return window.open(api.origin + '/p/' + search); } } }); _context.next = 39; break; case 37: alert(localization.menu.failedToDeploy); debugWindow(response); case 39: _context.next = 44; break; case 41: _context.prev = 41; _context.t0 = _context['catch'](7); console.info(_context.t0); case 44: _this.setState({ isDeploying: false }); case 45: case 'end': return _context.stop(); } } }, _callee, _this2, [[7, 41]]); })); return function (_x, _x2) { return _ref2.apply(this, arguments); }; }(), _this.handleLogout = function () { _this.props.setOAuthId(); _this.handleRequestClose(); }, _this.handleRequestClose = function () { _this.setState({ notice: null }); }, _this.handleToggleDrawer = function () { return _this.setState({ open: !_this.state.open }); }, _this.handleSetTitle = function (event) { _this.setState({ overrideTitle: event.data.value }); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(Menu, [{ key: 'handleLoginWithOAuth', value: function handleLoginWithOAuth(url) { var _this3 = this; var localization = this.props.localization; var win = open(url); var callback = function callback(oAuthId) { _this3.props.setOAuthId(oAuthId); _this3.setState({ notice: { message: localization.menu.loggedIn, action: localization.menu.logout, autoHideDuration: 20000, onActionClick: _this3.handleLogout } }); }; window.addEventListener('message', function task(event) { if (event.source === win) { window.removeEventListener('message', task); callback(event.data.id); } }); } }, { key: 'componentDidMount', value: function componentDidMount() { this.props.globalEvent.on('message.menuTitle', this.handleSetTitle); } }, { key: 'render', value: function render() { var _this4 = this; var _props = this.props, localization = _props.localization, setLocalization = _props.setLocalization; var styles = getStyles(this.props, this.context); var alternateTextColor = this.context.muiTheme.palette.alternateTextColor; var isLoggedin = this.props.oAuthId !== null; var title = this.props.project && (this.props.project.title ? React.createElement( 'div', { style: styles.projectName }, this.props.project.title ) : React.createElement(FlatButton, { label: localization.cloneDialog.setTitle, labelStyle: { color: alternateTextColor }, onClick: this.handleClone })); return React.createElement( AppBar, { title: this.state.overrideTitle || title, style: styles.root, titleStyle: { flex: null }, iconStyleLeft: styles.leftIcon, iconElementLeft: React.createElement( IconButton, { onClick: this.handleToggleDrawer }, React.createElement(NavigationMenu, null) ) }, React.createElement('div', { style: { flex: 1 } }), React.createElement(Toggle, { label: this.props.showAll ? '' : localization.menu.showAll, toggled: this.props.showAll, onToggle: this.props.toggleShowAll, style: styles.toggle, labelStyle: styles.toggleLabel }), this.props.showAll ? React.createElement( IconButton, { tooltip: localization.menu.clone, onClick: this.handleClone, style: styles.button }, React.createElement(FileDownload, { color: alternateTextColor }) ) : null, this.state.isDeploying ? React.createElement(CircularProgress, { size: 24, style: styles.progress, color: alternateTextColor }) : null, !this.state.isDeploying && this.props.showAll ? React.createElement( IconMenu, { iconButtonElement: React.createElement( IconButton, { tooltip: localization.menu.you }, React.createElement(ActionAccountCircle, { color: alternateTextColor }) ), anchorOrigin: { horizontal: 'right', vertical: 'top' }, targetOrigin: { horizontal: 'right', vertical: 'bottom' }, style: styles.button }, isLoggedin ? null : React.createElement(MenuItem, { primaryText: localization.menu.deployAnonymous, leftIcon: React.createElement(FileCloudUpload, null), onClick: function onClick() { return _this4.handleDeploy(false, false); } }), isLoggedin ? React.createElement(MenuItem, { primaryText: localization.menu.deploySelf, rightIcon: React.createElement(ArrowDropRight, null), menuItems: [React.createElement(MenuItem, { key: '1', primaryText: localization.menu.update, disabled: !this.props.deployURL, leftIcon: React.createElement(ActionAutorenew, null), onClick: function onClick() { return _this4.handleDeploy(true, true); } }), React.createElement(MenuItem, { key: '2', primaryText: localization.menu.create, leftIcon: React.createElement(FileCloudUpload, null), onClick: function onClick() { return _this4.handleDeploy(true, false); } })] }) : React.createElement(MenuItem, { primaryText: localization.menu.login, disabled: isLoggedin, leftIcon: React.createElement(ActionAccountCircle, null), rightIcon: React.createElement(ArrowDropRight, null), menuItems: [React.createElement(HoverMenuItem, { key: '1', primaryText: localization.menu.withGoogle, leftIcon: React.createElement(GoogleIcon, null), style: styles.google, onClick: function onClick() { return _this4.handleLoginWithOAuth(organization.api.google); } }), React.createElement(HoverMenuItem, { key: '2', primaryText: localization.menu.withFacebook, leftIcon: React.createElement(FacebookIcon, null), style: styles.facebook, onClick: function onClick() { return _this4.handleLoginWithOAuth(organization.api.facebook); } }), React.createElement(HoverMenuItem, { key: '3', primaryText: localization.menu.withTwitter, leftIcon: React.createElement(TwitterIcon, null), style: styles.twitter, onClick: function onClick() { return _this4.handleLoginWithOAuth(organization.api.twitter); } })] }), isLoggedin ? React.createElement(MenuItem, { primaryText: localization.menu.logout, onClick: this.handleLogout }) : null ) : null, React.createElement( IconMenu, { iconButtonElement: React.createElement( IconButton, { tooltip: localization.menu.language }, React.createElement(ActionLanguage, { color: alternateTextColor }) ), anchorOrigin: { horizontal: 'right', vertical: 'top' }, targetOrigin: { horizontal: 'right', vertical: 'bottom' }, style: styles.button }, acceptedLanguages.map(function (lang) { return React.createElement(MenuItem, { key: lang.accept[0], primaryText: lang.native, onClick: function onClick() { return setLocalization(lang.accept[0]); } }); }) ), React.createElement( Drawer, { open: this.state.open, docked: false, onRequestChange: function onRequestChange(open) { return _this4.setState({ open: open }); } }, React.createElement(AppBar, { iconElementLeft: React.createElement( IconButton, { onClick: this.handleToggleDrawer }, React.createElement(NavigationArrowBack, null) ) }), this.state.open ? _Object$entries(this.props.cards).map(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), name = _ref4[0], card = _ref4[1]; return _extends({ name: name }, card); }).map(function (item) { return React.createElement(MenuItem, { key: item.name, primaryText: localization[lowerCaseAtFirst(item.name)].title, leftIcon: _this4.props.cardIcons && _this4.props.cardIcons[item.name] ? _this4.props.cardIcons[item.name]() : null, onClick: function onClick() { _this4.props.updateCard(item.name, { visible: true }); _this4.handleToggleDrawer(); } }); }) : null, React.createElement(MenuItem, { primaryText: localization.menu.version, leftIcon: React.createElement(ActionHistory, null), onClick: function onClick() { _this4.handleAbout(); _this4.handleToggleDrawer(); } }) ), React.createElement(Snackbar, _extends({ open: this.state.notice !== null, message: '', autoHideDuration: 4000, style: styles.snackbar, onRequestClose: this.handleRequestClose }, this.state.notice)) ); } }, { key: 'shareURL', get: function get() { if (!this.props.deployURL) { return location.href; } var url = new URL(this.props.deployURL); var origin = url.origin, pathname = url.pathname; return origin + '/p/' + pathname.split('/').reverse()[0]; } }, { key: 'filesForPublishing', get: function get() { // i18n 設定の固定: // 1. i18n/ll_CC/ 以下のファイルを取得 var prefix = 'i18n/' + this.props.localization.ll_CC + '/'; var currentLocales = this.props.files.filter(function (file) { return file.name.startsWith(prefix); }); // 2. i18n/ 以下のファイルをすべて削除し、 var withoutI18n = this.props.files.filter(function (file) { return !file.name.startsWith('i18n/'); }); // 3. i18n/ll_CC/ 以下のファイルをルートに追加する var intoRoot = currentLocales.map(function (file) { var _file$name$split = file.name.split(prefix), _file$name$split2 = _slicedToArray(_file$name$split, 2), name = _file$name$split2[1]; return file.set({ name: name }); }); return withoutI18n.concat(intoRoot); } }]); return Menu; }(PureComponent); Menu.propTypes = { cards: PropTypes.object.isRequired, cardIcons: PropTypes.object, files: PropTypes.array.isRequired, openFileDialog: PropTypes.func.isRequired, localization: PropTypes.object.isRequired, setLocalization: PropTypes.func.isRequired, getConfig: PropTypes.func.isRequired, setConfig: PropTypes.func.isRequired, loadConfig: PropTypes.func.isRequired, findFile: PropTypes.func.isRequired, project: PropTypes.object, setProject: PropTypes.func.isRequired, updateCard: PropTypes.func.isRequired, launchIDE: PropTypes.func.isRequired, deployURL: PropTypes.string, setDeployURL: PropTypes.func.isRequired, oAuthId: PropTypes.string, setOAuthId: PropTypes.func.isRequired, showAll: PropTypes.bool.isRequired, toggleShowAll: PropTypes.func.isRequired, globalEvent: PropTypes.object.isRequired }; Menu.contextTypes = { muiTheme: PropTypes.object.isRequired }; export default Menu; function lowerCaseAtFirst(string) { return string[0].toLowerCase() + string.substr(1); } var HoverMenuItem = function (_PureComponent2) { _inherits(HoverMenuItem, _PureComponent2); function HoverMenuItem() { var _ref5; var _temp2, _this5, _ret2; _classCallCheck(this, HoverMenuItem); for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } return _ret2 = (_temp2 = (_this5 = _possibleConstructorReturn(this, (_ref5 = HoverMenuItem.__proto__ || _Object$getPrototypeOf(HoverMenuItem)).call.apply(_ref5, [this].concat(args))), _this5), _this5.state = { hover: false }, _temp2), _possibleConstructorReturn(_this5, _ret2); } _createClass(HoverMenuItem, [{ key: 'render', value: function render() { var _this6 = this; var style = this.props.style; if (this.state.hover) { style = _extends({}, style, { backgroundColor: emphasize(style.backgroundColor, 0.14) }); } return React.createElement(MenuItem, _extends({}, this.props, { style: style, onMouseEnter: function onMouseEnter() { return _this6.setState({ hover: true }); }, onMouseLeave: function onMouseLeave() { return _this6.setState({ hover: false }); } })); } }]); return HoverMenuItem; }(PureComponent); HoverMenuItem.propTypes = { style: PropTypes.object.isRequired };