feat multiselection: select multiple files and act on them
fix breadcrumb: fixed breadcrumb history not working properly when clicking on "sdcard" fix dialogs/menus: fixed clicking out of menus and dialogs triggering actions other than hiding the dialog/event
@ -85,7 +85,7 @@ module.exports = function(grunt) {
|
||||
cwd: 'src',
|
||||
dest: 'build',
|
||||
src: ['index.html', 'manifest.webapp',
|
||||
'fonts/**', 'img/**', 'js/libs/**']
|
||||
'fonts/**', 'img/**', 'js/libs/**', 'icon/**']
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
@ -17,6 +17,8 @@ Please read the Features section below and issues to make sure your issue is not
|
||||
- [x] Create new files and directories
|
||||
- [x] File Size
|
||||
- [x] Directory Child Count
|
||||
- [x] Actions on multiple files (selection)
|
||||
- [ ] Copy/Cut and Paste files
|
||||
- [ ] File Preview
|
||||
- [ ] Filter Files
|
||||
- [ ] Search
|
||||
|
BIN
build/icon/Icon-128.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
build/icon/Icon-16.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
build/icon/Icon-48.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
build/icon/Icon-60.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
build/icon/Icon.png
Normal file
After Width: | Height: | Size: 60 KiB |
19
build/img/Select.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="32px" height="34px" viewBox="0 0 32 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Select</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-257.000000, -7.000000)" stroke-linecap="round" stroke="#63B0CD" stroke-width="3">
|
||||
<g sketch:type="MSLayerGroup" id="Buttons">
|
||||
<g transform="translate(26.000000, 7.000000)" sketch:type="MSShapeGroup">
|
||||
<g id="Select" transform="translate(232.000000, 2.000000)">
|
||||
<rect id="Rectangle-31" stroke-dasharray="7,8,7,8" x="0" y="1" width="29" height="29" rx="4"></rect>
|
||||
<path d="M6,17 L10,23 L24,0" id="Path-26" stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,106 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128px"
|
||||
height="128px"
|
||||
viewBox="0 0 128 128"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="0.48.2 r9819"
|
||||
sodipodi:docname="icon.svg">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1142"
|
||||
inkscape:window-height="849"
|
||||
id="namedview12"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.84375"
|
||||
inkscape:cx="-32.542373"
|
||||
inkscape:cy="64"
|
||||
inkscape:window-x="672"
|
||||
inkscape:window-y="146"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Page-1" />
|
||||
<!-- Generator: Sketch 3.0.2 (7799) - http://www.bohemiancoding.com/sketch -->
|
||||
<title
|
||||
id="title4">empty</title>
|
||||
<description
|
||||
id="description6">Created with Sketch.</description>
|
||||
<defs
|
||||
id="defs8">
|
||||
<linearGradient
|
||||
id="linearGradient3761">
|
||||
<stop
|
||||
style="stop-color:#4e748b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3763" />
|
||||
<stop
|
||||
style="stop-color:#393e3f;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3765" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3761"
|
||||
id="linearGradient3767"
|
||||
x1="129.66949"
|
||||
y1="65.8983"
|
||||
x2="129.66949"
|
||||
y2="188.59828"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:url(#linearGradient3767);fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path2991"
|
||||
sodipodi:cx="129.35593"
|
||||
sodipodi:cy="128.27118"
|
||||
sodipodi:rx="63.18644"
|
||||
sodipodi:ry="63.18644"
|
||||
d="m 192.54237,128.27118 a 63.18644,63.18644 0 1 1 -126.372883,0 63.18644,63.18644 0 1 1 126.372883,0 z"
|
||||
transform="translate(-65.355927,-64.271179)" />
|
||||
<g
|
||||
id="Page-1"
|
||||
sketch:type="MSPage"
|
||||
transform="matrix(0.9,0,0,0.9,6.4,6.4)"
|
||||
style="fill:none;stroke:none">
|
||||
<circle
|
||||
id="empty"
|
||||
sketch:type="MSShapeGroup"
|
||||
cx="64"
|
||||
cy="64"
|
||||
r="58"
|
||||
sodipodi:cx="64"
|
||||
sodipodi:cy="64"
|
||||
sodipodi:rx="58"
|
||||
sodipodi:ry="58"
|
||||
style="stroke:#bcc6c5;stroke-width:11;stroke-linecap:round;stroke-dasharray:20, 20;fill:none;fill-opacity:1"
|
||||
d="M 122,64 C 122,96.032515 96.032515,122 64,122 31.967485,122 6,96.032515 6,64 6,31.967485 31.967485,6 64,6 c 32.032515,0 58,25.967485 58,58 z" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 804 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.3 KiB |
294
build/main.js
@ -22213,7 +22213,9 @@ function rename(file, name) {
|
||||
};
|
||||
}
|
||||
|
||||
function active(file) {
|
||||
function active() {
|
||||
var file = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
|
||||
|
||||
return {
|
||||
type: _actionsTypes.ACTIVE_FILE,
|
||||
file: file
|
||||
@ -22221,6 +22223,7 @@ function active(file) {
|
||||
}
|
||||
|
||||
function deleteFile(file) {
|
||||
console.log('constructing deleteFile action', file);
|
||||
return {
|
||||
type: _actionsTypes.DELETE_FILE,
|
||||
file: file
|
||||
@ -22237,6 +22240,7 @@ exports.refresh = refresh;
|
||||
exports.toggle = toggle;
|
||||
exports.details = details;
|
||||
exports.list = list;
|
||||
exports.selectView = selectView;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
@ -22252,27 +22256,36 @@ function refresh() {
|
||||
};
|
||||
}
|
||||
|
||||
function toggle(state) {
|
||||
function toggle() {
|
||||
return {
|
||||
type: _actionsTypes.FILES_VIEW,
|
||||
view: 'toggle'
|
||||
};
|
||||
}
|
||||
|
||||
function details(state) {
|
||||
function details() {
|
||||
return {
|
||||
type: _actionsTypes.FILES_VIEW,
|
||||
view: 'details'
|
||||
};
|
||||
}
|
||||
|
||||
function list(state) {
|
||||
function list() {
|
||||
return {
|
||||
type: _actionsTypes.FILES_VIEW,
|
||||
view: 'list'
|
||||
};
|
||||
}
|
||||
|
||||
function selectView() {
|
||||
var active = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
|
||||
|
||||
return {
|
||||
type: _actionsTypes.SELECT_VIEW,
|
||||
active: active
|
||||
};
|
||||
}
|
||||
|
||||
},{"actions/types":223,"store":"store"}],219:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
@ -22407,6 +22420,7 @@ var TYPES = {
|
||||
|
||||
LIST_FILES: Symbol('LIST_FILES'),
|
||||
FILES_VIEW: Symbol('FILES_VIEW'),
|
||||
SELECT_VIEW: Symbol('SELECT_VIEW'),
|
||||
|
||||
NAVIGATION: Symbol('NAVIGATION'),
|
||||
TOGGLE: Symbol('TOGGLE'),
|
||||
@ -22422,7 +22436,7 @@ var TYPES = {
|
||||
|
||||
MENU: Symbol('MENU'),
|
||||
|
||||
DIALOG: Symbol('DEBUG'),
|
||||
DIALOG: Symbol('DIALOG'),
|
||||
|
||||
SETTINGS: Symbol('SETTINGS'),
|
||||
|
||||
@ -22540,6 +22554,14 @@ var createDirectory = _asyncToGenerator(function* () {
|
||||
|
||||
exports.createDirectory = createDirectory;
|
||||
|
||||
var remove = _asyncToGenerator(function* (file) {
|
||||
var parent = yield root();
|
||||
|
||||
return parent.remove(file);
|
||||
});
|
||||
|
||||
exports.remove = remove;
|
||||
|
||||
var move = _asyncToGenerator(function* (file, newPath) {
|
||||
var path = (file.path || '').replace(/^\//, ''); // remove starting slash
|
||||
var oldPath = path + file.name;
|
||||
@ -22667,12 +22689,13 @@ var Breadcrumb = (function (_Component) {
|
||||
_createClass(Breadcrumb, [{
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var directories = this.props.cwd.split('/');
|
||||
var directories = this.props.cwd.split('/').filter(function (a) {
|
||||
return a;
|
||||
});
|
||||
directories.unshift('sdcard');
|
||||
|
||||
var els = directories.map(function (dir, index, arr) {
|
||||
var path = arr.slice(1, index + 1).join('/');
|
||||
var slash = index > 0 ? '/' : '';
|
||||
|
||||
return _react2['default'].createElement(
|
||||
'span',
|
||||
@ -22680,18 +22703,21 @@ var Breadcrumb = (function (_Component) {
|
||||
_react2['default'].createElement(
|
||||
'i',
|
||||
null,
|
||||
slash
|
||||
'/'
|
||||
),
|
||||
dir
|
||||
);
|
||||
});
|
||||
|
||||
var lastDirectories = this.props.lwd.split('/');
|
||||
var lastDirectories = this.props.lwd.split('/').filter(function (a) {
|
||||
return a;
|
||||
});
|
||||
if (lastDirectories.length > directories.length - 1) {
|
||||
lastDirectories.splice(0, directories.length - 1);
|
||||
|
||||
var _history = lastDirectories.map(function (dir, index, arr) {
|
||||
var current = directories.slice(1).concat(arr.slice(0, index + 1));
|
||||
var path = current.join('/');
|
||||
var path = current.join('/').replace(/^\//, ''); // remove starting slash
|
||||
|
||||
return _react2['default'].createElement(
|
||||
'span',
|
||||
@ -22865,11 +22891,24 @@ var Directory = (function (_Component) {
|
||||
_createClass(Directory, [{
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var checkId = 'file-' + this.props.index;
|
||||
|
||||
var input = undefined,
|
||||
label = undefined;
|
||||
if (this.props.selectView) {
|
||||
input = _react2['default'].createElement('input', { type: 'checkbox', id: checkId, checked: this.props.selected, readOnly: true });
|
||||
label = _react2['default'].createElement('label', { htmlFor: checkId });
|
||||
}
|
||||
|
||||
var clickHandler = this.props.selectView ? this.select.bind(this) : this.peek.bind(this);
|
||||
|
||||
return _react2['default'].createElement(
|
||||
'div',
|
||||
{ className: 'directory', ref: 'container',
|
||||
onClick: this.peek.bind(this),
|
||||
onClick: clickHandler,
|
||||
onContextMenu: this.contextMenu.bind(this) },
|
||||
input,
|
||||
label,
|
||||
_react2['default'].createElement('i', null),
|
||||
_react2['default'].createElement(
|
||||
'p',
|
||||
@ -22907,6 +22946,19 @@ var Directory = (function (_Component) {
|
||||
_store2['default'].dispatch((0, _actionsMenu.show)('directoryMenu', { style: { left: left, top: top } }));
|
||||
_store2['default'].dispatch((0, _actionsFile.active)(this.props.index));
|
||||
}
|
||||
}, {
|
||||
key: 'select',
|
||||
value: function select() {
|
||||
var current = (_store2['default'].getState().get('activeFile') || []).slice(0);
|
||||
var index = this.props.index;
|
||||
|
||||
if (current.indexOf(index) > -1) {
|
||||
current.splice(current.indexOf(index), 1);
|
||||
} else {
|
||||
current.push(index);
|
||||
}
|
||||
_store2['default'].dispatch((0, _actionsFile.active)(current));
|
||||
}
|
||||
}]);
|
||||
|
||||
return Directory;
|
||||
@ -22973,29 +23025,20 @@ var FileList = (function (_Component) {
|
||||
_createClass(FileList, [{
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var files = this.props.files;
|
||||
var _props = this.props;
|
||||
var files = _props.files;
|
||||
var selectView = _props.selectView;
|
||||
var activeFile = _props.activeFile;
|
||||
|
||||
activeFile = activeFile || [];
|
||||
var settings = _store2['default'].getState().get('settings');
|
||||
|
||||
if (settings.showDirectoriesFirst) {
|
||||
files = files.sort(function (a, b) {
|
||||
if ((0, _utils.type)(a) === 'Directory') return -1;
|
||||
if ((0, _utils.type)(b) === 'Directory') return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!settings.showHiddenFiles) {
|
||||
files = files.filter(function (file) {
|
||||
return file.name[0] !== '.';
|
||||
});
|
||||
}
|
||||
|
||||
var els = files.map(function (file, index) {
|
||||
var selected = activeFile.indexOf(index) > -1;
|
||||
if ((0, _utils.type)(file) === 'File') {
|
||||
return _react2['default'].createElement(_file2['default'], { key: index, index: index, name: file.name, size: file.size });
|
||||
return _react2['default'].createElement(_file2['default'], { selectView: selectView, selected: selected, key: index, index: index, name: file.name, size: file.size });
|
||||
} else {
|
||||
return _react2['default'].createElement(_directory2['default'], { key: index, index: index, name: file.name, children: file.children });
|
||||
return _react2['default'].createElement(_directory2['default'], { selectView: selectView, selected: selected, key: index, index: index, name: file.name, children: file.children });
|
||||
}
|
||||
});
|
||||
|
||||
@ -23016,7 +23059,9 @@ exports['default'] = FileList;
|
||||
|
||||
function props(state) {
|
||||
return {
|
||||
files: state.get('files')
|
||||
files: state.get('files'),
|
||||
selectView: state.get('selectView'),
|
||||
activeFile: state.get('activeFile')
|
||||
};
|
||||
}
|
||||
|
||||
@ -23069,10 +23114,24 @@ var File = (function (_Component) {
|
||||
_createClass(File, [{
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var checkId = 'file-' + this.props.index;
|
||||
|
||||
var input = undefined,
|
||||
label = undefined;
|
||||
if (this.props.selectView) {
|
||||
input = _react2['default'].createElement('input', { type: 'checkbox', id: checkId, defaultChecked: this.props.selected, readOnly: true });
|
||||
label = _react2['default'].createElement('label', { htmlFor: checkId });
|
||||
}
|
||||
|
||||
var clickHandler = this.props.selectView ? this.select.bind(this) : null;
|
||||
|
||||
return _react2['default'].createElement(
|
||||
'div',
|
||||
{ className: 'file', ref: 'container',
|
||||
onClick: clickHandler,
|
||||
onContextMenu: this.contextMenu.bind(this) },
|
||||
input,
|
||||
label,
|
||||
_react2['default'].createElement('i', null),
|
||||
_react2['default'].createElement(
|
||||
'p',
|
||||
@ -23102,6 +23161,19 @@ var File = (function (_Component) {
|
||||
_store2['default'].dispatch((0, _actionsMenu.show)('fileMenu', { style: { left: left, top: top } }));
|
||||
_store2['default'].dispatch((0, _actionsFile.active)(this.props.index));
|
||||
}
|
||||
}, {
|
||||
key: 'select',
|
||||
value: function select() {
|
||||
var current = (_store2['default'].getState().get('activeFile') || []).slice(0);
|
||||
var index = this.props.index;
|
||||
|
||||
if (current.indexOf(index) > -1) {
|
||||
current.splice(current.indexOf(index), 1);
|
||||
} else {
|
||||
current.push(index);
|
||||
}
|
||||
_store2['default'].dispatch((0, _actionsFile.active)(current));
|
||||
}
|
||||
}]);
|
||||
|
||||
return File;
|
||||
@ -23482,6 +23554,9 @@ var FileMenu = (0, _reactRedux.connect)(function (state) {
|
||||
var DirectoryMenu = (0, _reactRedux.connect)(function (state) {
|
||||
return state.get('directoryMenu');
|
||||
})(_componentsMenu2['default']);
|
||||
var MoreMenu = (0, _reactRedux.connect)(function (state) {
|
||||
return state.get('moreMenu');
|
||||
})(_componentsMenu2['default']);
|
||||
|
||||
var RenameDialog = (0, _reactRedux.connect)(function (state) {
|
||||
return state.get('renameDialog');
|
||||
@ -23518,6 +23593,7 @@ var Root = (function (_Component) {
|
||||
_react2['default'].createElement(_componentsToolbar2['default'], null),
|
||||
_react2['default'].createElement(FileMenu, null),
|
||||
_react2['default'].createElement(DirectoryMenu, null),
|
||||
_react2['default'].createElement(MoreMenu, null),
|
||||
_react2['default'].createElement(RenameDialog, null),
|
||||
_react2['default'].createElement(DeleteDialog, null),
|
||||
_react2['default'].createElement(ErrorDialog, null),
|
||||
@ -23527,7 +23603,12 @@ var Root = (function (_Component) {
|
||||
}, {
|
||||
key: 'touchStart',
|
||||
value: function touchStart(e) {
|
||||
if (!e.target.closest('.menu')) {
|
||||
var active = document.querySelector('.active');
|
||||
var inside = e.target.closest('.menu') || e.target.closest('.dialog');
|
||||
if (!inside && active) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
_store2['default'].dispatch((0, _actionsMenu.hideAll)());
|
||||
_store2['default'].dispatch((0, _actionsDialog.hideAll)());
|
||||
}
|
||||
@ -23565,10 +23646,14 @@ var _actionsFilesView = require('actions/files-view');
|
||||
|
||||
var _actionsDialog = require('actions/dialog');
|
||||
|
||||
var _actionsMenu = require('actions/menu');
|
||||
|
||||
var _store = require('store');
|
||||
|
||||
var _store2 = _interopRequireDefault(_store);
|
||||
|
||||
var _menu = require('./menu');
|
||||
|
||||
var Toolbar = (function (_Component) {
|
||||
_inherits(Toolbar, _Component);
|
||||
|
||||
@ -23587,13 +23672,25 @@ var Toolbar = (function (_Component) {
|
||||
_react2['default'].createElement('button', { className: 'icon-plus', onClick: this.newFile }),
|
||||
_react2['default'].createElement('button', { className: 'icon-view', onClick: (0, _store.bind)((0, _actionsFilesView.toggle)()) }),
|
||||
_react2['default'].createElement('button', { className: 'icon-refresh', onClick: (0, _store.bind)((0, _actionsFilesView.refresh)()) }),
|
||||
_react2['default'].createElement('button', { className: 'icon-share', onClick: this.share }),
|
||||
_react2['default'].createElement('button', { className: 'icon-more', onClick: this.showMore })
|
||||
_react2['default'].createElement('button', { className: 'icon-select', onClick: (0, _store.bind)((0, _actionsFilesView.selectView)('toggle')) }),
|
||||
_react2['default'].createElement('button', { className: 'icon-more', onClick: this.showMore.bind(this), ref: 'more' })
|
||||
);
|
||||
}
|
||||
}, {
|
||||
key: 'showMore',
|
||||
value: function showMore() {}
|
||||
value: function showMore() {
|
||||
var rect = _react2['default'].findDOMNode(this.refs.more).getBoundingClientRect();
|
||||
var x = rect.x;
|
||||
var y = rect.y;
|
||||
var width = rect.width;
|
||||
var height = rect.height;
|
||||
|
||||
var left = x + width - _menu.MENU_WIDTH,
|
||||
top = y + height;
|
||||
|
||||
var transform = 'translate(0, -100%)';
|
||||
_store2['default'].dispatch((0, _actionsMenu.show)('moreMenu', { style: { left: left, top: top, transform: transform } }));
|
||||
}
|
||||
}, {
|
||||
key: 'newFile',
|
||||
value: function newFile() {
|
||||
@ -23611,7 +23708,7 @@ var Toolbar = (function (_Component) {
|
||||
exports['default'] = Toolbar;
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"actions/dialog":216,"actions/files-view":218,"react":205,"store":"store"}],235:[function(require,module,exports){
|
||||
},{"./menu":231,"actions/dialog":216,"actions/files-view":218,"actions/menu":220,"react":205,"store":"store"}],235:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
@ -23646,6 +23743,7 @@ exports['default'] = {
|
||||
var action = (0, _actionsFile.create)(cwd + input.value);
|
||||
this.props.dispatch(action);
|
||||
this.props.dispatch((0, _actionsDialog.hideAll)());
|
||||
this.props.dispatch((0, _actionsFile.active)());
|
||||
}
|
||||
}, {
|
||||
text: 'Directory',
|
||||
@ -23656,6 +23754,7 @@ exports['default'] = {
|
||||
var action = (0, _actionsFile.create)(cwd + input.value, true);
|
||||
this.props.dispatch(action);
|
||||
this.props.dispatch((0, _actionsDialog.hideAll)());
|
||||
this.props.dispatch((0, _actionsFile.active)());
|
||||
}
|
||||
}]
|
||||
},
|
||||
@ -23674,6 +23773,7 @@ exports['default'] = {
|
||||
var activeFile = _store2['default'].getState().get('activeFile');
|
||||
this.props.dispatch((0, _actionsFile.rename)(activeFile, input.value));
|
||||
this.props.dispatch((0, _actionsDialog.hideAll)());
|
||||
this.props.dispatch((0, _actionsFile.active)());
|
||||
},
|
||||
className: 'success'
|
||||
}]
|
||||
@ -23690,6 +23790,7 @@ exports['default'] = {
|
||||
var activeFile = _store2['default'].getState().get('activeFile');
|
||||
this.props.dispatch((0, _actionsFile.deleteFile)(activeFile));
|
||||
this.props.dispatch((0, _actionsDialog.hideAll)());
|
||||
this.props.dispatch((0, _actionsFile.active)());
|
||||
},
|
||||
className: 'success'
|
||||
}]
|
||||
@ -23774,9 +23875,32 @@ var entryMenu = {
|
||||
}]
|
||||
};
|
||||
|
||||
var moreMenu = {
|
||||
items: [{
|
||||
name: 'Delete',
|
||||
action: function action() {
|
||||
var files = _store2['default'].getState().get('files');
|
||||
var active = _store2['default'].getState().get('activeFile');
|
||||
|
||||
var description = undefined;
|
||||
if (active.length) {
|
||||
var count = active.length;
|
||||
description = 'Are you sure you want to remove ' + count + ' files?';
|
||||
} else {
|
||||
var _name = files[active].name;
|
||||
description = 'Are you sure you want to remove ' + _name + '?';
|
||||
}
|
||||
|
||||
_store2['default'].dispatch((0, _actionsMenu.hideAll)());
|
||||
_store2['default'].dispatch((0, _actionsDialog.show)('deleteDialog', { description: description }));
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
exports['default'] = {
|
||||
fileMenu: Object.assign({}, entryMenu),
|
||||
directoryMenu: Object.assign({}, entryMenu)
|
||||
directoryMenu: Object.assign({}, entryMenu),
|
||||
moreMenu: moreMenu
|
||||
};
|
||||
module.exports = exports['default'];
|
||||
|
||||
@ -23790,7 +23914,7 @@ Object.defineProperty(exports, '__esModule', {
|
||||
var _actionsTypes = require('actions/types');
|
||||
|
||||
exports['default'] = function (state, action) {
|
||||
if (state === undefined) state = -1;
|
||||
if (state === undefined) state = null;
|
||||
|
||||
if (action.type === _actionsTypes.ACTIVE_FILE) {
|
||||
return action.file;
|
||||
@ -23846,6 +23970,10 @@ var _settings = require('./settings');
|
||||
|
||||
var _settings2 = _interopRequireDefault(_settings);
|
||||
|
||||
var _selectView = require('./select-view');
|
||||
|
||||
var _selectView2 = _interopRequireDefault(_selectView);
|
||||
|
||||
exports['default'] = function (state, action) {
|
||||
if (state === undefined) state = new _immutable2['default'].Map();
|
||||
|
||||
@ -23854,11 +23982,13 @@ exports['default'] = function (state, action) {
|
||||
lwd: (0, _lwd2['default'])(state, action), // last working directory
|
||||
cwd: (0, _cwd2['default'])(state.get('cwd'), action),
|
||||
files: (0, _files2['default'])(state.get('files'), action),
|
||||
selectView: (0, _selectView2['default'])(state.get('selectView'), action),
|
||||
activeFile: (0, _activeFile2['default'])(state.get('activeFile'), action),
|
||||
navigation: (0, _navigation2['default'])(state.get('navigation'), action),
|
||||
settings: (0, _settings2['default'])(state.get('settings'), action),
|
||||
fileMenu: (0, _menu2['default'])(state, action, 'fileMenu'),
|
||||
directoryMenu: (0, _menu2['default'])(state, action, 'directoryMenu'),
|
||||
moreMenu: (0, _menu2['default'])(state, action, 'moreMenu'),
|
||||
renameDialog: (0, _dialog2['default'])(state, action, 'renameDialog'),
|
||||
deleteDialog: (0, _dialog2['default'])(state, action, 'deleteDialog'),
|
||||
errorDialog: (0, _dialog2['default'])(state, action, 'errorDialog'),
|
||||
@ -23868,7 +23998,7 @@ exports['default'] = function (state, action) {
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"./active-file":238,"./cwd":240,"./dialog":241,"./files":242,"./lwd":243,"./menu":244,"./navigation":245,"./settings":246,"immutable":247}],240:[function(require,module,exports){
|
||||
},{"./active-file":238,"./cwd":240,"./dialog":241,"./files":242,"./lwd":243,"./menu":244,"./navigation":245,"./select-view":246,"./settings":247,"immutable":248}],240:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
@ -23958,7 +24088,7 @@ exports['default'] = function (state, action, id) {
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"actions/types":223,"immutable":247,"lodash/object/omit":37}],242:[function(require,module,exports){
|
||||
},{"actions/types":223,"immutable":248,"lodash/object/omit":37}],242:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
@ -23987,6 +24117,22 @@ exports['default'] = function (state, action) {
|
||||
if (state === undefined) state = [];
|
||||
|
||||
if (action.type === _actionsTypes.LIST_FILES) {
|
||||
|
||||
var settings = _store2['default'].getState().get('settings');
|
||||
|
||||
if (settings.showDirectoriesFirst) {
|
||||
action.files = action.files.sort(function (a, b) {
|
||||
if ((0, _utils.type)(a) === 'Directory') return -1;
|
||||
if ((0, _utils.type)(a) === 'File') return 1;
|
||||
});
|
||||
}
|
||||
|
||||
if (!settings.showHiddenFiles) {
|
||||
action.files = action.files.filter(function (file) {
|
||||
return file.name[0] !== '.';
|
||||
});
|
||||
}
|
||||
|
||||
return action.files;
|
||||
}
|
||||
|
||||
@ -24006,17 +24152,52 @@ exports['default'] = function (state, action) {
|
||||
}
|
||||
|
||||
if (action.type === _actionsTypes.DELETE_FILE) {
|
||||
var file = state[action.file];
|
||||
|
||||
(0, _apiFiles.sdcard)()['delete']((file.path || '') + '/' + file.name);
|
||||
var copy = state.slice(0);
|
||||
|
||||
if (action.file.length) {
|
||||
var _iteratorNormalCompletion = true;
|
||||
var _didIteratorError = false;
|
||||
var _iteratorError = undefined;
|
||||
|
||||
try {
|
||||
for (var _iterator = action.file[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||
var index = _step.value;
|
||||
|
||||
del(state, index);
|
||||
}
|
||||
} catch (err) {
|
||||
_didIteratorError = true;
|
||||
_iteratorError = err;
|
||||
} finally {
|
||||
try {
|
||||
if (!_iteratorNormalCompletion && _iterator['return']) {
|
||||
_iterator['return']();
|
||||
}
|
||||
} finally {
|
||||
if (_didIteratorError) {
|
||||
throw _iteratorError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copy = copy.filter(function (a, i) {
|
||||
return action.file.indexOf(i) === -1;
|
||||
});
|
||||
} else {
|
||||
del(state, action.file);
|
||||
copy.splice(action.file, 1);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
function del(state, index) {
|
||||
var file = state[index];
|
||||
return (0, _apiFiles.remove)((file.path || '') + '/' + file.name)['catch'](_utils.reportError);
|
||||
}
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"actions/dialog":216,"actions/files-view":218,"actions/types":223,"api/files":224,"store":"store","utils":"utils"}],243:[function(require,module,exports){
|
||||
@ -24081,7 +24262,7 @@ exports['default'] = function (state, action, id) {
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"actions/types":223,"immutable":247,"lodash/object/omit":37}],245:[function(require,module,exports){
|
||||
},{"actions/types":223,"immutable":248,"lodash/object/omit":37}],245:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
@ -24109,6 +24290,27 @@ Object.defineProperty(exports, '__esModule', {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _actionsTypes = require('actions/types');
|
||||
|
||||
exports['default'] = function (state, action) {
|
||||
if (state === undefined) state = false;
|
||||
|
||||
if (action.type === _actionsTypes.SELECT_VIEW) {
|
||||
return action.active === 'toggle' ? !state : action.active;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"actions/types":223}],247:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
value: true
|
||||
});
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var _actionsTypes = require('actions/types');
|
||||
@ -24134,7 +24336,7 @@ exports['default'] = function (state, action) {
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
},{"actions/types":223,"lodash/object/omit":37}],247:[function(require,module,exports){
|
||||
},{"actions/types":223,"lodash/object/omit":37}],248:[function(require,module,exports){
|
||||
/**
|
||||
* Copyright (c) 2014-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
@ -29109,7 +29311,7 @@ function bind(action) {
|
||||
|
||||
exports['default'] = store;
|
||||
|
||||
},{"./dialogs":235,"./menus":237,"actions/changedir":215,"immutable":247,"reducers/all":239,"redux":207}],"utils":[function(require,module,exports){
|
||||
},{"./dialogs":235,"./menus":237,"actions/changedir":215,"immutable":248,"reducers/all":239,"redux":207}],"utils":[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
@ -29168,15 +29370,13 @@ var sizes = {
|
||||
};
|
||||
|
||||
function humanSize(size) {
|
||||
console.log(size);
|
||||
for (var key in sizes) {
|
||||
var value = sizes[key];
|
||||
|
||||
console.log(value);
|
||||
if (size > value) {
|
||||
return Math.round(size / value) + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},{"actions/dialog":216,"store":"store"}]},{},[215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,"store","utils"]);
|
||||
},{"actions/dialog":216,"store":"store"}]},{},[215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,"store","utils"]);
|
||||
|
@ -4,10 +4,10 @@
|
||||
"description": "Keep an eye on your files with a full-featured file-manager",
|
||||
"launch_path": "/index.html",
|
||||
"icons": {
|
||||
"16": "/img/icons/icon16x16.png",
|
||||
"48": "/img/icons/icon48x48.png",
|
||||
"60": "/img/icons/icon60x60.png",
|
||||
"128": "/img/icons/icon128x128.png"
|
||||
"16": "/icon/Icon-16.png",
|
||||
"48": "/icon/Icon-48.png",
|
||||
"60": "/icon/Icon-60.png",
|
||||
"128": "/icon/Icon-128.png"
|
||||
},
|
||||
"developer": {
|
||||
"name": "Mahdi Dibaiee",
|
||||
|
@ -31,11 +31,11 @@
|
||||
width: 26px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon-share {
|
||||
.icon-select {
|
||||
display: block;
|
||||
background: url(/img/Share.svg) no-repeat;
|
||||
width: 25px;
|
||||
height: 27px;
|
||||
background: url(/img/Select.svg) no-repeat;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
}
|
||||
.icon-more {
|
||||
display: block;
|
||||
@ -104,21 +104,26 @@ input {
|
||||
font-weight: 200;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
label {
|
||||
clear: left;
|
||||
}
|
||||
input[type="checkbox"]::before {
|
||||
label::after {
|
||||
content: '';
|
||||
display: block;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: transparent;
|
||||
float: right;
|
||||
margin-right: 13px;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
border: 1px solid #9b9b93;
|
||||
}
|
||||
input[type="checkbox"]::before:checked {
|
||||
input[type='checkbox'] {
|
||||
clear: right;
|
||||
float: right;
|
||||
display: none;
|
||||
}
|
||||
input:checked + label::after {
|
||||
background: #63b0cd;
|
||||
}
|
||||
.file,
|
||||
@ -259,28 +264,6 @@ nav li:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
nav li label {
|
||||
clear: left;
|
||||
}
|
||||
nav li label::after {
|
||||
content: '';
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 13px;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: transparent;
|
||||
border: 1px solid #9b9b93;
|
||||
}
|
||||
nav li input {
|
||||
clear: right;
|
||||
float: right;
|
||||
display: none;
|
||||
}
|
||||
nav li input:checked + label::after {
|
||||
background: #63b0cd;
|
||||
}
|
||||
nav i {
|
||||
display: block;
|
||||
position: fixed;
|
||||
|
BIN
src/icon/Icon-128.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/icon/Icon-16.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
src/icon/Icon-48.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/icon/Icon-60.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/icon/Icon.png
Normal file
After Width: | Height: | Size: 60 KiB |
19
src/img/Select.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="32px" height="34px" viewBox="0 0 32 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Select</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-257.000000, -7.000000)" stroke-linecap="round" stroke="#63B0CD" stroke-width="3">
|
||||
<g sketch:type="MSLayerGroup" id="Buttons">
|
||||
<g transform="translate(26.000000, 7.000000)" sketch:type="MSShapeGroup">
|
||||
<g id="Select" transform="translate(232.000000, 2.000000)">
|
||||
<rect id="Rectangle-31" stroke-dasharray="7,8,7,8" x="0" y="1" width="29" height="29" rx="4"></rect>
|
||||
<path d="M6,17 L10,23 L24,0" id="Path-26" stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -21,7 +21,7 @@ export function rename(file, name) {
|
||||
}
|
||||
}
|
||||
|
||||
export function active(file) {
|
||||
export function active(file = null) {
|
||||
return {
|
||||
type: ACTIVE_FILE,
|
||||
file
|
||||
@ -29,6 +29,7 @@ export function active(file) {
|
||||
}
|
||||
|
||||
export function deleteFile(file) {
|
||||
console.log('constructing deleteFile action', file);
|
||||
return {
|
||||
type: DELETE_FILE,
|
||||
file
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { LIST_FILES, FILES_VIEW, REFRESH } from 'actions/types';
|
||||
import { LIST_FILES, FILES_VIEW, SELECT_VIEW, REFRESH } from 'actions/types';
|
||||
import store from 'store';
|
||||
|
||||
export function refresh() {
|
||||
@ -7,23 +7,30 @@ export function refresh() {
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle(state) {
|
||||
export function toggle() {
|
||||
return {
|
||||
type: FILES_VIEW,
|
||||
view: 'toggle'
|
||||
}
|
||||
}
|
||||
|
||||
export function details(state) {
|
||||
export function details() {
|
||||
return {
|
||||
type: FILES_VIEW,
|
||||
view: 'details'
|
||||
}
|
||||
}
|
||||
|
||||
export function list(state) {
|
||||
export function list() {
|
||||
return {
|
||||
type: FILES_VIEW,
|
||||
view: 'list'
|
||||
}
|
||||
}
|
||||
|
||||
export function selectView(active = true) {
|
||||
return {
|
||||
type: SELECT_VIEW,
|
||||
active
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ const TYPES = {
|
||||
|
||||
LIST_FILES: Symbol('LIST_FILES'),
|
||||
FILES_VIEW: Symbol('FILES_VIEW'),
|
||||
SELECT_VIEW: Symbol('SELECT_VIEW'),
|
||||
|
||||
NAVIGATION: Symbol('NAVIGATION'),
|
||||
TOGGLE: Symbol('TOGGLE'),
|
||||
@ -18,7 +19,7 @@ const TYPES = {
|
||||
|
||||
MENU: Symbol('MENU'),
|
||||
|
||||
DIALOG: Symbol('DEBUG'),
|
||||
DIALOG: Symbol('DIALOG'),
|
||||
|
||||
SETTINGS: Symbol('SETTINGS'),
|
||||
|
||||
|
@ -75,6 +75,12 @@ export async function createDirectory(...args) {
|
||||
return parent.createDirectory(...args);
|
||||
}
|
||||
|
||||
export async function remove(file) {
|
||||
let parent = await root();
|
||||
|
||||
return parent.remove(file);
|
||||
}
|
||||
|
||||
export async function move(file, newPath) {
|
||||
let path = (file.path || '').replace(/^\//, ''); // remove starting slash
|
||||
let oldPath = path + file.name;
|
||||
|
@ -7,26 +7,26 @@ import { bind } from 'store';
|
||||
@connect(props)
|
||||
export default class Breadcrumb extends Component {
|
||||
render() {
|
||||
let directories = this.props.cwd.split('/');
|
||||
let directories = this.props.cwd.split('/').filter(a => a);
|
||||
directories.unshift('sdcard');
|
||||
|
||||
let els = directories.map((dir, index, arr) => {
|
||||
let path = arr.slice(1, index + 1).join('/');
|
||||
let slash = index > 0 ? '/' : '';
|
||||
|
||||
return (
|
||||
<span key={index} onClick={bind(changedir(path))}>
|
||||
<i>{slash}</i>{dir}
|
||||
<i>/</i>{dir}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
let lastDirectories = this.props.lwd.split('/');
|
||||
let lastDirectories = this.props.lwd.split('/').filter(a => a);
|
||||
if (lastDirectories.length > directories.length - 1) {
|
||||
lastDirectories.splice(0, directories.length - 1);
|
||||
|
||||
let history = lastDirectories.map((dir, index, arr) => {
|
||||
let current = directories.slice(1).concat(arr.slice(0, index + 1));
|
||||
let path = current.join('/');
|
||||
let path = current.join('/').replace(/^\//, ''); // remove starting slash
|
||||
|
||||
return (
|
||||
<span key={directories.length + index} className='history' onClick={bind(changedir(path))}>
|
||||
|
@ -9,10 +9,25 @@ const MENU_TOP_SPACE = 20;
|
||||
|
||||
export default class Directory extends Component {
|
||||
render() {
|
||||
let checkId = `file-${this.props.index}`;
|
||||
|
||||
let input, label;
|
||||
if (this.props.selectView) {
|
||||
input = <input type='checkbox' id={checkId} checked={this.props.selected} readOnly />;
|
||||
label = <label htmlFor={checkId}></label>;
|
||||
}
|
||||
|
||||
let clickHandler = this.props.selectView ? this.select.bind(this)
|
||||
: this.peek.bind(this);
|
||||
|
||||
return (
|
||||
<div className='directory' ref='container'
|
||||
onClick={this.peek.bind(this)}
|
||||
onClick={clickHandler}
|
||||
onContextMenu={this.contextMenu.bind(this)}>
|
||||
|
||||
{input}
|
||||
{label}
|
||||
|
||||
<i></i>
|
||||
<p>{this.props.name}</p>
|
||||
<span>{this.props.children} items</span>
|
||||
@ -37,4 +52,16 @@ export default class Directory extends Component {
|
||||
store.dispatch(show('directoryMenu', {style: {left, top}}));
|
||||
store.dispatch(active(this.props.index));
|
||||
}
|
||||
|
||||
select() {
|
||||
let current = (store.getState().get('activeFile') || []).slice(0);
|
||||
let index = this.props.index;
|
||||
|
||||
if (current.indexOf(index) > -1) {
|
||||
current.splice(current.indexOf(index), 1);
|
||||
} else {
|
||||
current.push(index)
|
||||
}
|
||||
store.dispatch(active(current));
|
||||
}
|
||||
}
|
||||
|
@ -12,29 +12,16 @@ export default class FileList extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { files } = this.props;
|
||||
|
||||
let { files, selectView, activeFile } = this.props;
|
||||
activeFile = activeFile || [];
|
||||
let settings = store.getState().get('settings');
|
||||
|
||||
if (settings.showDirectoriesFirst) {
|
||||
files = files.sort((a, b) => {
|
||||
if (type(a) === 'Directory') return -1;
|
||||
if (type(b) === 'Directory') return 1;
|
||||
return 0;
|
||||
})
|
||||
}
|
||||
|
||||
if (!settings.showHiddenFiles) {
|
||||
files = files.filter(file => {
|
||||
return file.name[0] !== '.';
|
||||
})
|
||||
}
|
||||
|
||||
let els = files.map((file, index) => {
|
||||
let selected = activeFile.indexOf(index) > -1;
|
||||
if (type(file) === 'File') {
|
||||
return <File key={index} index={index} name={file.name} size={file.size} />;
|
||||
return <File selectView={selectView} selected={selected} key={index} index={index} name={file.name} size={file.size} />;
|
||||
} else {
|
||||
return <Directory key={index} index={index} name={file.name} children={file.children} />
|
||||
return <Directory selectView={selectView} selected={selected} key={index} index={index} name={file.name} children={file.children} />
|
||||
}
|
||||
});
|
||||
|
||||
@ -48,7 +35,9 @@ export default class FileList extends Component {
|
||||
|
||||
function props(state) {
|
||||
return {
|
||||
files: state.get('files')
|
||||
files: state.get('files'),
|
||||
selectView: state.get('selectView'),
|
||||
activeFile: state.get('activeFile')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,25 @@ export default class File extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let checkId = `file-${this.props.index}`;
|
||||
|
||||
let input, label;
|
||||
if (this.props.selectView) {
|
||||
input = <input type='checkbox' id={checkId} defaultChecked={this.props.selected} readOnly />;
|
||||
label = <label htmlFor={checkId}></label>;
|
||||
}
|
||||
|
||||
let clickHandler = this.props.selectView ? this.select.bind(this)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className='file' ref='container'
|
||||
onClick={clickHandler}
|
||||
onContextMenu={this.contextMenu.bind(this)}>
|
||||
|
||||
{input}
|
||||
{label}
|
||||
|
||||
<i></i>
|
||||
<p>{this.props.name}</p>
|
||||
<span>{humanSize(this.props.size)}</span>
|
||||
@ -34,4 +50,16 @@ export default class File extends Component {
|
||||
store.dispatch(show('fileMenu', {style: {left, top}}));
|
||||
store.dispatch(active(this.props.index));
|
||||
}
|
||||
|
||||
select() {
|
||||
let current = (store.getState().get('activeFile') || []).slice(0);
|
||||
let index = this.props.index;
|
||||
|
||||
if (current.indexOf(index) > -1) {
|
||||
current.splice(current.indexOf(index), 1);
|
||||
} else {
|
||||
current.push(index)
|
||||
}
|
||||
store.dispatch(active(current));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ window.changedir = changedir;
|
||||
|
||||
let FileMenu = connect(state => state.get('fileMenu'))(Menu);
|
||||
let DirectoryMenu = connect(state => state.get('directoryMenu'))(Menu);
|
||||
let MoreMenu = connect(state => state.get('moreMenu'))(Menu);
|
||||
|
||||
let RenameDialog = connect(state => state.get('renameDialog'))(Dialog);
|
||||
let DeleteDialog = connect(state => state.get('deleteDialog'))(Dialog);
|
||||
@ -36,6 +37,7 @@ export default class Root extends Component {
|
||||
|
||||
<FileMenu />
|
||||
<DirectoryMenu />
|
||||
<MoreMenu />
|
||||
|
||||
<RenameDialog />
|
||||
<DeleteDialog />
|
||||
@ -46,7 +48,12 @@ export default class Root extends Component {
|
||||
}
|
||||
|
||||
touchStart(e) {
|
||||
if (!e.target.closest('.menu')) {
|
||||
let active = document.querySelector('.active');
|
||||
let inside = e.target.closest('.menu') || e.target.closest('.dialog');
|
||||
if (!inside && active) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
store.dispatch(hideAllMenus());
|
||||
store.dispatch(hideAllDialogs());
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { toggle as toggleView, refresh } from 'actions/files-view';
|
||||
import { toggle as toggleView, refresh, selectView } from 'actions/files-view';
|
||||
import { show as showDialog } from 'actions/dialog';
|
||||
import { show as showMenu } from 'actions/menu';
|
||||
import store, { bind } from 'store';
|
||||
import { MENU_WIDTH } from './menu';
|
||||
|
||||
export default class Toolbar extends Component {
|
||||
render() {
|
||||
@ -10,14 +12,21 @@ export default class Toolbar extends Component {
|
||||
<button className='icon-plus' onClick={this.newFile} />
|
||||
<button className='icon-view' onClick={bind(toggleView())} />
|
||||
<button className='icon-refresh' onClick={bind(refresh())} />
|
||||
<button className='icon-share' onClick={this.share} />
|
||||
<button className='icon-more' onClick={this.showMore} />
|
||||
<button className='icon-select' onClick={bind(selectView('toggle'))} />
|
||||
<button className='icon-more' onClick={this.showMore.bind(this)} ref='more' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
showMore() {
|
||||
let rect = React.findDOMNode(this.refs.more).getBoundingClientRect();
|
||||
let {x, y, width, height} = rect;
|
||||
|
||||
let left = x + width - MENU_WIDTH,
|
||||
top = y + height;
|
||||
|
||||
let transform = 'translate(0, -100%)';
|
||||
store.dispatch(showMenu('moreMenu', {style: {left, top, transform}}));
|
||||
}
|
||||
|
||||
newFile() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { hide, hideAll } from 'actions/dialog';
|
||||
import { rename, deleteFile, create } from 'actions/file';
|
||||
import { rename, deleteFile, create, active } from 'actions/file';
|
||||
import store, { bind } from 'store';
|
||||
|
||||
export default {
|
||||
@ -18,6 +18,7 @@ export default {
|
||||
let action = create(cwd + input.value);
|
||||
this.props.dispatch(action);
|
||||
this.props.dispatch(hideAll());
|
||||
this.props.dispatch(active());
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -29,6 +30,7 @@ export default {
|
||||
let action = create(cwd + input.value, true);
|
||||
this.props.dispatch(action);
|
||||
this.props.dispatch(hideAll());
|
||||
this.props.dispatch(active());
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -50,6 +52,7 @@ export default {
|
||||
let activeFile = store.getState().get('activeFile');
|
||||
this.props.dispatch(rename(activeFile, input.value))
|
||||
this.props.dispatch(hideAll());
|
||||
this.props.dispatch(active());
|
||||
},
|
||||
className: 'success'
|
||||
}
|
||||
@ -69,6 +72,7 @@ export default {
|
||||
let activeFile = store.getState().get('activeFile');
|
||||
this.props.dispatch(deleteFile(activeFile));
|
||||
this.props.dispatch(hideAll());
|
||||
this.props.dispatch(active());
|
||||
},
|
||||
className: 'success'
|
||||
}
|
||||
|
1571
src/js/libs/l10n.js
@ -30,7 +30,32 @@ const entryMenu = {
|
||||
]
|
||||
};
|
||||
|
||||
const moreMenu = {
|
||||
items: [
|
||||
{
|
||||
name: 'Delete',
|
||||
action() {
|
||||
let files = store.getState().get('files');
|
||||
let active = store.getState().get('activeFile');
|
||||
|
||||
let description;
|
||||
if (active.length) {
|
||||
const count = active.length;
|
||||
description = `Are you sure you want to remove ${count} files?`;
|
||||
} else {
|
||||
const name = files[active].name;
|
||||
description = `Are you sure you want to remove ${name}?`;
|
||||
}
|
||||
|
||||
store.dispatch(hideAll());
|
||||
store.dispatch(show('deleteDialog', {description}));
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default {
|
||||
fileMenu: Object.assign({}, entryMenu),
|
||||
directoryMenu: Object.assign({}, entryMenu)
|
||||
directoryMenu: Object.assign({}, entryMenu),
|
||||
moreMenu
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ACTIVE_FILE } from 'actions/types';
|
||||
|
||||
export default function(state = -1, action) {
|
||||
export default function(state = null, action) {
|
||||
if (action.type === ACTIVE_FILE) {
|
||||
return action.file;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import activeFile from './active-file';
|
||||
import menu from './menu';
|
||||
import dialog from './dialog';
|
||||
import settings from './settings';
|
||||
import selectView from './select-view';
|
||||
|
||||
export default function(state = new Immutable.Map(), action) {
|
||||
console.log('action', action);
|
||||
@ -14,14 +15,16 @@ export default function(state = new Immutable.Map(), action) {
|
||||
lwd: lwd(state, action), // last working directory
|
||||
cwd: cwd(state.get('cwd'), action),
|
||||
files: files(state.get('files'), action),
|
||||
selectView: selectView(state.get('selectView'), action),
|
||||
activeFile: activeFile(state.get('activeFile'), action),
|
||||
navigation: navigation(state.get('navigation'), action),
|
||||
settings: settings(state.get('settings'), action),
|
||||
fileMenu: menu(state, action, 'fileMenu'),
|
||||
directoryMenu: menu(state, action, 'directoryMenu'),
|
||||
moreMenu: menu(state, action, 'moreMenu'),
|
||||
renameDialog: dialog(state, action, 'renameDialog'),
|
||||
deleteDialog: dialog(state, action, 'deleteDialog'),
|
||||
errorDialog: dialog(state, action, 'errorDialog'),
|
||||
createDialog: dialog(state, action, 'createDialog')
|
||||
createDialog: dialog(state, action, 'createDialog'),
|
||||
});
|
||||
}
|
||||
|
@ -1,14 +1,30 @@
|
||||
import { LIST_FILES, RENAME_FILE, DELETE_FILE, CREATE_FILE } from 'actions/types';
|
||||
import { refresh } from 'actions/files-view';
|
||||
import { move, sdcard, createFile, createDirectory } from 'api/files';
|
||||
import { move, remove, sdcard, createFile, createDirectory } from 'api/files';
|
||||
import { show } from 'actions/dialog';
|
||||
import store, { bind } from 'store';
|
||||
import { reportError } from 'utils';
|
||||
import { reportError, type } from 'utils';
|
||||
|
||||
let boundRefresh = bind(refresh());
|
||||
|
||||
export default function(state = [], action) {
|
||||
if (action.type === LIST_FILES) {
|
||||
|
||||
let settings = store.getState().get('settings');
|
||||
|
||||
if (settings.showDirectoriesFirst) {
|
||||
action.files = action.files.sort((a, b) => {
|
||||
if (type(a) === 'Directory') return -1;
|
||||
if (type(a) === 'File') return 1;
|
||||
});
|
||||
}
|
||||
|
||||
if (!settings.showHiddenFiles) {
|
||||
action.files = action.files.filter(file => {
|
||||
return file.name[0] !== '.';
|
||||
})
|
||||
}
|
||||
|
||||
return action.files;
|
||||
}
|
||||
|
||||
@ -28,13 +44,26 @@ export default function(state = [], action) {
|
||||
}
|
||||
|
||||
if (action.type === DELETE_FILE) {
|
||||
let file = state[action.file];
|
||||
|
||||
sdcard().delete((file.path || '') + '/' + file.name);
|
||||
let copy = state.slice(0);
|
||||
|
||||
if (action.file.length) {
|
||||
for (let index of action.file) {
|
||||
del(state, index);
|
||||
}
|
||||
|
||||
copy = copy.filter((a, i) => action.file.indexOf(i) === -1);
|
||||
} else {
|
||||
del(state, action.file);
|
||||
copy.splice(action.file, 1);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function del(state, index) {
|
||||
let file = state[index];
|
||||
return remove((file.path || '') + '/' + file.name).catch(reportError);
|
||||
}
|
||||
|
9
src/js/reducers/select-view.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { SELECT_VIEW } from 'actions/types';
|
||||
|
||||
export default function(state = false, action) {
|
||||
if (action.type === SELECT_VIEW) {
|
||||
return action.active === 'toggle' ? !state : action.active;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
@ -37,11 +37,9 @@ const sizes = {
|
||||
'B': -1
|
||||
}
|
||||
export function humanSize(size) {
|
||||
console.log(size);
|
||||
for (let key in sizes) {
|
||||
let value = sizes[key];
|
||||
|
||||
console.log(value);
|
||||
if (size > value) {
|
||||
return Math.round(size / value) + key;
|
||||
}
|
||||
|
@ -52,39 +52,6 @@ nav {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
label {
|
||||
clear: left;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
float: right;
|
||||
|
||||
margin-right: 13px;
|
||||
|
||||
border-radius: 50%;
|
||||
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
background: transparent;
|
||||
|
||||
border: 1px solid @overlay;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
clear: right;
|
||||
float: right;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
input:checked + label::after {
|
||||
background: @blue;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
|
@ -37,11 +37,11 @@
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.icon-share {
|
||||
.icon-select {
|
||||
.icon;
|
||||
background: url(/img/Share.svg) no-repeat;
|
||||
width: 25px;
|
||||
height: 27px;
|
||||
background: url(/img/Select.svg) no-repeat;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.icon-more {
|
||||
|
@ -14,27 +14,35 @@ input {
|
||||
.light-medium;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
label {
|
||||
clear: left;
|
||||
|
||||
input[type="checkbox"]::before {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
border: 1px solid @gray;
|
||||
float: right;
|
||||
|
||||
background: transparent;
|
||||
margin-right: 13px;
|
||||
|
||||
border-radius: 50%;
|
||||
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
|
||||
&:checked {
|
||||
background: @blue;
|
||||
border: 1px solid @overlay;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
clear: right;
|
||||
float: right;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
input:checked + label::after {
|
||||
background: @blue;
|
||||
}
|
||||
|
@ -4,10 +4,10 @@
|
||||
"description": "Keep an eye on your files with a full-featured file-manager",
|
||||
"launch_path": "/index.html",
|
||||
"icons": {
|
||||
"16": "/img/icons/icon16x16.png",
|
||||
"48": "/img/icons/icon48x48.png",
|
||||
"60": "/img/icons/icon60x60.png",
|
||||
"128": "/img/icons/icon128x128.png"
|
||||
"16": "/icon/Icon-16.png",
|
||||
"48": "/icon/Icon-48.png",
|
||||
"60": "/icon/Icon-60.png",
|
||||
"128": "/icon/Icon-128.png"
|
||||
},
|
||||
"developer": {
|
||||
"name": "Mahdi Dibaiee",
|
||||
|