feeles-ide
Version:
The hackable and serializable IDE to make learning material
436 lines (371 loc) • 13.5 kB
JavaScript
import _toConsumableArray from 'babel-runtime/helpers/toConsumableArray';
import _regeneratorRuntime from 'babel-runtime/regenerator';
import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator';
import _getIterator from 'babel-runtime/core-js/get-iterator';
import _toArray from 'babel-runtime/helpers/toArray';
import _Map from 'babel-runtime/core-js/map';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _createClass from 'babel-runtime/helpers/createClass';
import includes from 'lodash/includes';
import { putFile as _putFile, deleteFile as _deleteFile } from './';
/**
* ファイルの状態を In Memory & IndexedDB で保有するストア
* ファイルの探索を手助けする Indexer でもある
* Redux は使わず、 FileView インスタンスが setState の
* 参照をもち、外から状態を更新する
*/
var FileView = function () {
function FileView(files) {
_classCallCheck(this, FileView);
this.files = files;
this._changed = true; // files 変更フラグ
}
_createClass(FileView, [{
key: 'install',
value: function install(component) {
this.component = component;
/*
互換性保持のため, つねに最新のステートを参照するAPIをのこす
いずれ this.state.fileView を直接 propergate させたい
*/
component.addFile = this.addFile.bind(this);
component.putFile = this.putFile.bind(this);
component.deleteFile = this.deleteFile.bind(this);
component.findFile = this.findFile.bind(this);
}
}, {
key: 'uninstall',
value: function uninstall() {
this.component = null;
}
}, {
key: 'setState',
value: function setState(_ref) {
var files = _ref.files;
this._changed = true; // Index するフラグ
if (!files) throw 'Cannot set other than files';
if (!this.component) throw 'Component is not been set';
var fileView = new FileView(files);
fileView.install(this.component);
return this.component.setStatePromise({
fileView: fileView
});
}
/**
* ファイルパスでファイルを取得
* @param {String} path 取得したいファイルのパス
* @return {SourceFile|BinalyFile|null} ファイルまたは null
*/
}, {
key: 'getFileByFullPath',
value: function getFileByFullPath(path) {
this.updateIndex();
return this.pathToFileMap.get(path);
}
/**
* 任意の拡張子をもつすべてのファイルを取得
* @param {String} ext 取得したいファイルのパス
* @return {Array<SourceFile|BinalyFile>} ファイルの配列
*/
}, {
key: 'getFilesByExtention',
value: function getFilesByExtention(ext) {
this.updateIndex();
return this.extToFilesMap.get(ext) || [];
}
/**
* Map を用いてファイル名をインデックス
* files が変わったら適宜呼び出す
*/
}, {
key: 'updateIndex',
value: function updateIndex() {
var _this = this;
// files が変更されていればあらたにインデックス
if (!this._changed) return;
this._changed = false;
this.pathToFileMap = new _Map();
this.extToFilesMap = new _Map();
var append = function append(name, file) {
// name => File
_this.pathToFileMap.set(name, file);
// extention => File
var _name$split = name.split('.'),
_name$split2 = _toArray(_name$split),
extArray = _name$split2.slice(1);
if (!extArray.length) return;
var ext = extArray.join('.');
if (_this.extToFilesMap.has(ext)) {
var files = _this.extToFilesMap.get(ext);
_this.extToFilesMap.set(ext, files.concat(file));
} else {
_this.extToFilesMap.set(ext, [file]);
}
};
// i18n 以外
var i18n = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = _getIterator(this.files), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var file = _step.value;
if (!file.name.startsWith('i18n/')) {
append(file.name, file);
} else {
i18n.push(file);
}
}
// i18n/{ll_CC} を追加
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var ll_CC = this.component.props.localization.ll_CC;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = _getIterator(i18n), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _file = _step2.value;
var _file$name$split = _file.name.split('/'),
_file$name$split2 = _toArray(_file$name$split),
locale = _file$name$split2[1],
virtualPath = _file$name$split2.slice(2);
if (locale === ll_CC) {
append(virtualPath.join('/'), _file);
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
}, {
key: 'forceUpdate',
value: function forceUpdate() {
var fileView = new FileView(this.files);
fileView.install(this.component);
return this.component.setStatePromise({
fileView: fileView
});
}
/**
* ファイルを検索して取得する (後方互換性)
* @param {String|Function} name ファイル名
* @param {Boolean} multiple 全件取得フラグ
*/
}, {
key: 'findFile',
value: function findFile(name) {
var multiple = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (typeof name === 'string') {
name = name.replace(/^(\.\/|\/)*/, '');
}
var i18nName = 'i18n/' + this.component.props.localization.ll_CC + '/' + name;
var pred = typeof name === 'function' ? name : function (file) {
return !file.options.isTrashed && (
// 言語設定による動的ファイルパス解決
file.name === i18nName || file.moduleName === i18nName ||
// 通常のファイルパス解決
file.name === name || file.moduleName === name);
};
return multiple ? this.files.filter(pred) : this.files.find(pred) || null;
}
/**
* ファイルをひとつ追加する
* @param {SourceFile|BinalyFile} file 追加するファイル
*/
}, {
key: 'addFile',
value: function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(file) {
var remove, files;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
remove = this.inspection(file);
if (!(file === remove)) {
_context.next = 3;
break;
}
return _context.abrupt('return', file);
case 3:
files = this.files.concat(file).filter(function (item) {
return item !== remove;
});
_context.next = 6;
return this.setState({
files: files
});
case 6:
_context.next = 8;
return this.component.resetConfig(file.name);
case 8:
if (!this.component.state.project) {
_context.next = 11;
break;
}
_context.next = 11;
return _putFile(this.component.state.project.id, file.serialize());
case 11:
return _context.abrupt('return', file);
case 12:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function addFile(_x2) {
return _ref2.apply(this, arguments);
}
return addFile;
}()
/**
* ファイルを置き換える
* @param {SourceFile|BinalyFile} prevFile 削除するファイル
* @param {SourceFile|BinalyFile} nextFile 追加するファイル
*/
}, {
key: 'putFile',
value: function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(prevFile, nextFile) {
var remove, files;
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
remove = this.inspection(nextFile);
if (!(remove === nextFile)) {
_context2.next = 3;
break;
}
return _context2.abrupt('return', prevFile);
case 3:
files = this.files.filter(function (item) {
return item !== remove && item.key !== prevFile.key;
}).concat(nextFile);
_context2.next = 6;
return this.setState({
files: files
});
case 6:
this.component.resetConfig(prevFile.name);
if (!this.component.state.project) {
_context2.next = 10;
break;
}
_context2.next = 10;
return _putFile(this.component.state.project.id, nextFile.serialize());
case 10:
return _context2.abrupt('return', nextFile);
case 11:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function putFile(_x3, _x4) {
return _ref3.apply(this, arguments);
}
return putFile;
}()
/**
* 任意個数のファイルを削除する
* @param {Array<SourceFile|BinalyFile>} targets 削除するファイル
*/
}, {
key: 'deleteFile',
value: function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
for (var _len = arguments.length, targets = Array(_len), _key = 0; _key < _len; _key++) {
targets[_key] = arguments[_key];
}
var keys, files, fileNames;
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
keys = targets.map(function (item) {
return item.key;
});
files = this.files.filter(function (item) {
return !includes(keys, item.key);
});
_context3.next = 4;
return this.setState({
files: files
});
case 4:
if (!this.component.state.project) {
_context3.next = 8;
break;
}
fileNames = targets.map(function (item) {
return item.name;
});
_context3.next = 8;
return _deleteFile.apply(undefined, [this.component.state.project.id].concat(_toConsumableArray(fileNames)));
case 8:
case 'end':
return _context3.stop();
}
}
}, _callee3, this);
}));
function deleteFile() {
return _ref4.apply(this, arguments);
}
return deleteFile;
}()
/**
* ファイル名の衝突をしらべる. TODO: FileDialog で実現すべき
* https://trello.com/c/Y4CbIH81/244-conflict-%E3%81%AF%E3%83%80%E3%82%A4%E3%82%A2%E3%83%AD%E3%82%B0%E3%81%AE%E3%81%A8%E3%81%93%E3%82%8D%E3%81%A7%E5%88%A4%E5%AE%9A%E3%81%99%E3%82%8B-filestore-%E3%81%A7%E3%81%AF%E7%84%A1%E8%A6%96
* @param {Array<SourceFile|BinalyFile>} newFile 追加予定のファイル
*/
}, {
key: 'inspection',
value: function inspection(newFile) {
var conflict = this.files.find(function (file) {
return !file.options.isTrashed && file.key !== newFile.key && file.name === newFile.name;
});
if (conflict) {
// TODO: FileDialog instead of.
// 一時的に "強制上書き" にする
console.log(newFile);
return conflict;
// if (confirm(this.component.props.localization.common.conflict)) {
// return conflict;
// } else {
// return newFile;
// }
}
return null;
}
}]);
return FileView;
}();
export default FileView;