Compare commits
16 Commits
firefoxos-
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
d4d8b81e40 | ||
|
7bcf39bdb7 | ||
|
2eaf2ac1f0 | ||
|
9aa5bcf384 | ||
|
bec675e7ee | ||
|
4253732492 | ||
|
a9c5890c3c | ||
|
f0f6a684a7 | ||
|
629b6f7e61 | ||
|
1833a5e3c1 | ||
|
735ef7fa7b | ||
|
dfb7d8aa72 | ||
|
44340abb61 | ||
|
0018380759 | ||
|
c13315d61e | ||
|
59af3b9e10 |
@ -63,7 +63,7 @@ Version 2.0
|
|||||||
- [x] Different views (List, Grid)
|
- [x] Different views (List, Grid)
|
||||||
- [ ] Show storage usage statistics (free/used)
|
- [ ] Show storage usage statistics (free/used)
|
||||||
- [ ] Sort Files
|
- [ ] Sort Files
|
||||||
- [ ] Zip / Unzip
|
- [x] Zip / Unzip
|
||||||
- [ ] Image Thumbnails
|
- [ ] Image Thumbnails
|
||||||
- [ ] FTP Browser
|
- [ ] FTP Browser
|
||||||
- [ ] Preferences
|
- [ ] Preferences
|
||||||
|
31542
build/main.js
31542
build/main.js
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"name": "Hawk",
|
"name": "Hawk",
|
||||||
"description": "Keep an eye on your files with a full-featured file manager",
|
"description": "Keep an eye on your files with a full-featured file manager",
|
||||||
"launch_path": "/index.html",
|
"launch_path": "/index.html",
|
||||||
@ -30,6 +30,9 @@
|
|||||||
"device-storage:music": {
|
"device-storage:music": {
|
||||||
"access": "readwrite",
|
"access": "readwrite",
|
||||||
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
||||||
|
},
|
||||||
|
"tcp-socket": {
|
||||||
|
"description": "FTP Browser: Used to connect to FTP servers"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installs_allowed_from": [
|
"installs_allowed_from": [
|
||||||
|
@ -490,6 +490,11 @@ nav i {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
}
|
}
|
||||||
|
.toolbar button {
|
||||||
|
flex: 1;
|
||||||
|
width: auto;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
Binary file not shown.
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hawk",
|
"name": "hawk",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -25,7 +25,14 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/mdibaiee/",
|
"homepage": "https://github.com/mdibaiee/",
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"jszip": "2.5.0",
|
||||||
|
"mime": "1.3.4",
|
||||||
|
"react": "15.0.0",
|
||||||
|
"react-dom": "15.0.0",
|
||||||
|
"react-hammerjs": "0.4.5",
|
||||||
|
"react-redux": "1.0.1"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel": "^5.8.23",
|
"babel": "^5.8.23",
|
||||||
"babelify": "^6.2.0",
|
"babelify": "^6.2.0",
|
||||||
@ -42,7 +49,7 @@
|
|||||||
"immutable": "^3.7.5",
|
"immutable": "^3.7.5",
|
||||||
"less-plugin-clean-css": "^1.5.1",
|
"less-plugin-clean-css": "^1.5.1",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^3.10.1",
|
||||||
"react": "^0.13.3",
|
"react": "^15.0.0",
|
||||||
"react-redux": "^1.0.1",
|
"react-redux": "^1.0.1",
|
||||||
"redux": "^1.0.1",
|
"redux": "^1.0.1",
|
||||||
"redux-devtools": "^1.1.2",
|
"redux-devtools": "^1.1.2",
|
||||||
|
Binary file not shown.
BIN
releases/hawk-1.1.0.zip
Normal file
BIN
releases/hawk-1.1.0.zip
Normal file
Binary file not shown.
BIN
releases/hawk-1.1.1.zip
Normal file
BIN
releases/hawk-1.1.1.zip
Normal file
Binary file not shown.
BIN
releases/hawk-1.1.2.zip
Normal file
BIN
releases/hawk-1.1.2.zip
Normal file
Binary file not shown.
BIN
releases/hawk-1.2.0.zip
Normal file
BIN
releases/hawk-1.2.0.zip
Normal file
Binary file not shown.
15
src/js/actions/compress.js
Normal file
15
src/js/actions/compress.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { COMPRESS, DECOMPRESS } from './types';
|
||||||
|
|
||||||
|
export function compress(file, name) {
|
||||||
|
return {
|
||||||
|
type: COMPRESS,
|
||||||
|
file, name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decompress(file) {
|
||||||
|
return {
|
||||||
|
type: DECOMPRESS,
|
||||||
|
file
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,9 @@ const TYPES = {
|
|||||||
REFRESH: Symbol('REFRESH'),
|
REFRESH: Symbol('REFRESH'),
|
||||||
SORT: Symbol('SORT'),
|
SORT: Symbol('SORT'),
|
||||||
|
|
||||||
|
COMPRESS: Symbol('COMPRESS'),
|
||||||
|
DECOMPRESS: Symbol('DECOMPRESS'),
|
||||||
|
|
||||||
NEW_FILE: Symbol('NEW_FILE'),
|
NEW_FILE: Symbol('NEW_FILE'),
|
||||||
CREATE_FILE: Symbol('CREATE_FILE'),
|
CREATE_FILE: Symbol('CREATE_FILE'),
|
||||||
SHARE_FILE: Symbol('SHARE_FILE'),
|
SHARE_FILE: Symbol('SHARE_FILE'),
|
||||||
|
14
src/js/api/auto.js
Normal file
14
src/js/api/auto.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import * as ftp from './ftp';
|
||||||
|
import * as files from './files';
|
||||||
|
|
||||||
|
['getFile', 'children', 'isDirectory', 'readFile', 'writeFile',
|
||||||
|
'createFile', 'createDirectory', 'remove', 'move', 'copy'].forEach(method => {
|
||||||
|
exports[method] = (...args) => {
|
||||||
|
return window.ftpMode ? ftp[method](...args) : files[method](...args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let CACHE = files.CACHE;
|
||||||
|
let FTP_CACHE = ftp.FTP_CACHE;
|
||||||
|
|
||||||
|
export { CACHE, FTP_CACHE };
|
@ -34,7 +34,14 @@ export async function getFile(dir = '/') {
|
|||||||
|
|
||||||
if (dir === '/' || !dir) return parent;
|
if (dir === '/' || !dir) return parent;
|
||||||
|
|
||||||
return await parent.get(normalize(dir));
|
let file = await parent.get(normalize(dir));
|
||||||
|
|
||||||
|
Object.defineProperty(file, 'type', {
|
||||||
|
value: type(file),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function children(dir, gatherInfo) {
|
export async function children(dir, gatherInfo) {
|
||||||
@ -44,11 +51,32 @@ export async function children(dir, gatherInfo) {
|
|||||||
if (!parent.path) {
|
if (!parent.path) {
|
||||||
parent.path = dir.slice(0, dir.lastIndexOf('/') + 1);
|
parent.path = dir.slice(0, dir.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
|
if (parent.path.endsWith(parent.name)) {
|
||||||
|
Object.defineProperty(parent, 'path', {
|
||||||
|
value: normalize(parent.path.slice(0, -parent.name.length)),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let childs = await parent.getFilesAndDirectories();
|
let childs = await parent.getFilesAndDirectories();
|
||||||
|
|
||||||
|
for (let child of childs) {
|
||||||
|
Object.defineProperty(child, 'type', {
|
||||||
|
value: type(child),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (child.path && child.path.endsWith(child.name)) {
|
||||||
|
Object.defineProperty(child, 'path', {
|
||||||
|
value: normalize(child.path.slice(0, -child.name.length)),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (gatherInfo && !window.needsShim) {
|
if (gatherInfo && !window.needsShim) {
|
||||||
for (let child of childs) {
|
for (let child of childs) {
|
||||||
if (type(child) === 'Directory') {
|
if (child.type === 'Directory') {
|
||||||
let subchildren;
|
let subchildren;
|
||||||
try {
|
try {
|
||||||
subchildren = await shimDirectory(child).getFilesAndDirectories();
|
subchildren = await shimDirectory(child).getFilesAndDirectories();
|
||||||
@ -70,6 +98,12 @@ export async function children(dir, gatherInfo) {
|
|||||||
return childs;
|
return childs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function isDirectory(path) {
|
||||||
|
let file = await getFile(path);
|
||||||
|
|
||||||
|
return !(file instanceof Blob);
|
||||||
|
}
|
||||||
|
|
||||||
export async function readFile(path) {
|
export async function readFile(path) {
|
||||||
let file = await getFile(path);
|
let file = await getFile(path);
|
||||||
|
|
||||||
@ -85,27 +119,73 @@ export async function readFile(path) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createFile(...args) {
|
export async function writeFile(path, content) {
|
||||||
let parent = await root();
|
try {
|
||||||
|
let file = await getFile(path);
|
||||||
|
|
||||||
return await parent.createFile(...args);
|
return Promise.reject(new Error('File already exists: ' + path));
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = sdcard().addNamed(content, path);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request.onsuccess = resolve;
|
||||||
|
request.onerror = reject;
|
||||||
|
request.onabort = reject;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createDirectory(...args) {
|
export async function createFile(path = '') {
|
||||||
let parent = await root();
|
const parentPath = path.split('/').slice(0, -1).join('/');
|
||||||
|
let filename = path.slice(path.lastIndexOf('/') + 1);
|
||||||
|
let parent = await getFile(parentPath);
|
||||||
|
|
||||||
return parent.createDirectory(...args).then(() => {
|
if (!parent.createFile) {
|
||||||
|
parent = await root();
|
||||||
|
filename = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
CACHE[parentPath] = null;
|
||||||
|
return await parent.createFile(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createDirectory(path) {
|
||||||
|
const parentPath = path.split('/').slice(0, -1).join('/');
|
||||||
|
let filename = path.slice(path.lastIndexOf('/') + 1);
|
||||||
|
let parent = await getFile(parentPath);
|
||||||
|
|
||||||
|
if (!parent.createDirectory) {
|
||||||
|
parent = await root();
|
||||||
|
filename = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
CACHE[parentPath] = null;
|
||||||
|
|
||||||
|
return parent.createDirectory(filename).then(() => {
|
||||||
if (window.needsShim) {
|
if (window.needsShim) {
|
||||||
return createFile(args[0] + '/.empty');
|
return createFile(path + '/.empty');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(file, deep) {
|
export async function remove(file, deep = true) {
|
||||||
|
// const method = deep ? 'removeDeep' : 'remove';
|
||||||
|
const method = 'removeDeep';
|
||||||
let path = normalize(file);
|
let path = normalize(file);
|
||||||
let parent = await root();
|
const parentPath = path.split('/').slice(0, -1).join('/');
|
||||||
|
let filename = path.slice(path.lastIndexOf('/') + 1);
|
||||||
|
let parent = await getFile(parentPath);
|
||||||
|
|
||||||
return parent[deep ? 'removeDeep' : 'remove'](path);
|
if (!parent[method]) {
|
||||||
|
parent = await root();
|
||||||
|
filename = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
CACHE[parentPath] = null;
|
||||||
|
|
||||||
|
return parent[method](filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function move(file, newPath) {
|
export async function move(file, newPath) {
|
||||||
@ -147,11 +227,6 @@ export async function copy(file, newPath) {
|
|||||||
|
|
||||||
let blob = new Blob([content], {type: target.type});
|
let blob = new Blob([content], {type: target.type});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return writeFile(newPath, blob);
|
||||||
let request = sdcard().addNamed(blob, newPath);
|
|
||||||
request.onsuccess = resolve;
|
|
||||||
request.onerror = reject;
|
|
||||||
request.onabort = reject;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
338
src/js/api/ftp.js
Normal file
338
src/js/api/ftp.js
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
import { refresh } from 'actions/files-view';
|
||||||
|
import { bind } from 'store';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
import { humanSize, reportError, normalize, type, getLength } from 'utils';
|
||||||
|
|
||||||
|
export let FTP_CACHE = {};
|
||||||
|
let socket;
|
||||||
|
let connection = new EventEmitter();
|
||||||
|
connection.setMaxListeners(99);
|
||||||
|
|
||||||
|
export let queue = Object.assign([], EventEmitter.prototype);
|
||||||
|
|
||||||
|
export async function connect(properties = {}) {
|
||||||
|
let { host, port, username, password } = properties;
|
||||||
|
|
||||||
|
let url = encodeURI(host);
|
||||||
|
socket = navigator.mozTCPSocket.open(url, port);
|
||||||
|
|
||||||
|
socket.ondata = e => {
|
||||||
|
console.log('<', e.data);
|
||||||
|
connection.emit('data', e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = e => {
|
||||||
|
connection.emit('error', e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = e => {
|
||||||
|
connection.emit('close', e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
socket.onopen = () => {
|
||||||
|
send(`USER ${username}`);
|
||||||
|
send(`PASS ${password}`);
|
||||||
|
resolve(socket);
|
||||||
|
|
||||||
|
window.ftpMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = reject;
|
||||||
|
socket.onclose = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function disconnect() {
|
||||||
|
socket.close();
|
||||||
|
window.ftpMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listen(ev, fn) {
|
||||||
|
socket.listen(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function send(command, ...args) {
|
||||||
|
args = args.filter(arg => arg);
|
||||||
|
let cmd = command + (args.length ? ' ' : '') + args.join(' ');
|
||||||
|
|
||||||
|
console.log('>', cmd);
|
||||||
|
socket.send(cmd + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cwd(dir = '') {
|
||||||
|
send('CWD', dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PWD_REGEX = /257 "(.*)"/;
|
||||||
|
export async function pwd() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.on('data', function listener(data) {
|
||||||
|
if (data.indexOf('current directory') === -1) return;
|
||||||
|
let dir = data.match(PWD_REGEX)[1];
|
||||||
|
resolve(normalize(dir));
|
||||||
|
|
||||||
|
connection.removeListener('data', listener);
|
||||||
|
});
|
||||||
|
|
||||||
|
send('PWD');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pasv() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.on('data', function listener(data) {
|
||||||
|
if (data.indexOf('Passive') === -1) return;
|
||||||
|
|
||||||
|
// format: |||port|
|
||||||
|
let port = parseInt(data.match(/\|{3}(\d+)\|/)[1]);
|
||||||
|
|
||||||
|
connection.removeListener('data', listener);
|
||||||
|
|
||||||
|
resolve(port);
|
||||||
|
});
|
||||||
|
|
||||||
|
send('EPSV');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIST_EXTRACTOR = /(.*?)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\d+:?\d+)+\s+(.*)/;
|
||||||
|
export async function list(dir = '') {
|
||||||
|
let index = queue.push(port => {
|
||||||
|
return secondary({ host: socket.host, port }).then(({data}) => {
|
||||||
|
send('LIST', dir);
|
||||||
|
|
||||||
|
return data.then(items => {
|
||||||
|
return items.split('\n').map(item => {
|
||||||
|
if (item.indexOf('total') > -1 || !item) return;
|
||||||
|
|
||||||
|
let match = item.match(LIST_EXTRACTOR);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: normalize(dir + '/'),
|
||||||
|
type: match[1][0] === 'd' ? 'Directory' : 'File',
|
||||||
|
permissions: match[1].slice(1),
|
||||||
|
links: +match[2],
|
||||||
|
owner: match[3],
|
||||||
|
group: match[4],
|
||||||
|
size: +match[5],
|
||||||
|
lastModification: {
|
||||||
|
month: match[6],
|
||||||
|
day: match[7],
|
||||||
|
time: match[8]
|
||||||
|
},
|
||||||
|
name: match[9]
|
||||||
|
}
|
||||||
|
}).filter(item => item);
|
||||||
|
}, reportError)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return handleQueue(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function namelist(dir = '') {
|
||||||
|
let index = queue.push(port => {
|
||||||
|
return secondary({ host: socket.host, port }).then(({data}) => {
|
||||||
|
send('NLST', dir);
|
||||||
|
|
||||||
|
return data.then(names => names.split('\n'), reportError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return handleQueue(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function secondary(properties = {}) {
|
||||||
|
let { host, port } = properties;
|
||||||
|
|
||||||
|
let url = encodeURI(host);
|
||||||
|
|
||||||
|
send('TYPE', 'I');
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let alt = navigator.mozTCPSocket.open(url, port);
|
||||||
|
|
||||||
|
alt.onopen = e => {
|
||||||
|
let data = new Promise((resolve, reject) => {
|
||||||
|
let d = '';
|
||||||
|
alt.ondata = e => {
|
||||||
|
d += e.data;
|
||||||
|
}
|
||||||
|
alt.onerror = e => {
|
||||||
|
reject(e.data);
|
||||||
|
}
|
||||||
|
alt.onclose = e => {
|
||||||
|
console.log('<<', d);
|
||||||
|
resolve(d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve({data});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const BUFFER_SIZE = 32000;
|
||||||
|
export async function secondaryWrite(properties = {}, content) {
|
||||||
|
let { host, port } = properties;
|
||||||
|
|
||||||
|
let url = encodeURI(host);
|
||||||
|
|
||||||
|
send('TYPE', 'I');
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let alt = navigator.mozTCPSocket.open(url, port);
|
||||||
|
|
||||||
|
alt.onopen = () => {
|
||||||
|
console.log('>>', content);
|
||||||
|
let step = 0;
|
||||||
|
|
||||||
|
(function send() {
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
let chunk = content.slice(0, BUFFER_SIZE);
|
||||||
|
content = content.slice(BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (alt.send(chunk)) {
|
||||||
|
send();
|
||||||
|
} else {
|
||||||
|
alt.ondrain = () => {
|
||||||
|
console.log('drain');
|
||||||
|
send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
alt.onclose = () => {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function children(dir = '', gatherInfo) {
|
||||||
|
dir = normalize(dir);
|
||||||
|
if (FTP_CACHE[dir]) return FTP_CACHE[dir];
|
||||||
|
|
||||||
|
let childs = gatherInfo ? await list(dir) : await namelist(dir);
|
||||||
|
|
||||||
|
FTP_CACHE[dir] = childs;
|
||||||
|
|
||||||
|
return childs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFile(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
let ls = await list(path);
|
||||||
|
|
||||||
|
return ls[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isDirectory(path = '') {
|
||||||
|
return (await getFile(path)).type === 'Directory';
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readFile(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
let index = queue.push(port => {
|
||||||
|
return secondary({ host: socket.host, port }).then(({data}) => {
|
||||||
|
send('RETR', path);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
return handleQueue(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function writeFile(path = '', content) {
|
||||||
|
let index;
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
if (type(content) === 'Blob') {
|
||||||
|
let reader = new FileReader();
|
||||||
|
|
||||||
|
index = queue.push(port => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
reader.addEventListener('loadend', () => {
|
||||||
|
send('TYPE', 'I');
|
||||||
|
send('STOR', path);
|
||||||
|
|
||||||
|
secondaryWrite({ host: socket.host, port }, reader.result)
|
||||||
|
.then(resolve, reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.readAsBinaryString(content);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
index = queue.push(port => {
|
||||||
|
send('STOR', path);
|
||||||
|
return secondaryWrite({ host: socket.host, port }, content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleQueue(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createFile(path = '') {
|
||||||
|
return writeFile(path, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createDirectory(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
send('MKD', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
let ls = await list(path);
|
||||||
|
send('DELE', path);
|
||||||
|
send('DELE', path + '/*.*');
|
||||||
|
send('RMD', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function move(file, newPath = '') {
|
||||||
|
let path = normalize(file.path + file.name);
|
||||||
|
newPath = normalize(newPath);
|
||||||
|
|
||||||
|
send('RNFR', path);
|
||||||
|
send('RNTO', newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copy(file, newPath = '') {
|
||||||
|
let path = normalize(file.path + file.name);
|
||||||
|
newPath = normalize(newPath);
|
||||||
|
|
||||||
|
let content = await readFile(path);
|
||||||
|
console.log(content);
|
||||||
|
|
||||||
|
return writeFile(newPath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOOP_INTERVAL = 100;
|
||||||
|
(function loopQueue() {
|
||||||
|
if (queue.length) {
|
||||||
|
pasv().then(queue[0]).then(result => {
|
||||||
|
queue.emit('done', {listener: queue[0], result});
|
||||||
|
queue.splice(0, 1);
|
||||||
|
loopQueue();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(loopQueue, LOOP_INTERVAL);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
async function handleQueue(index) {
|
||||||
|
let fn = queue[index];
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
queue.on('done', ({listener, result}) => {
|
||||||
|
if (listener === fn) resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import changedir from 'actions/changedir';
|
import changedir from 'actions/changedir';
|
||||||
import { bind } from 'store';
|
import { bind } from 'store';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
@connect(props)
|
@connect(props)
|
||||||
export default class Breadcrumb extends Component {
|
export default class Breadcrumb extends Component {
|
||||||
@ -9,7 +11,6 @@ export default class Breadcrumb extends Component {
|
|||||||
let els = [];
|
let els = [];
|
||||||
|
|
||||||
if (this.props.search) {
|
if (this.props.search) {
|
||||||
console.log('search');
|
|
||||||
els = [
|
els = [
|
||||||
<span key='000'>Search: {this.props.search}</span>
|
<span key='000'>Search: {this.props.search}</span>
|
||||||
]
|
]
|
||||||
@ -25,7 +26,9 @@ export default class Breadcrumb extends Component {
|
|||||||
let style = { zIndex: sumLength - index };
|
let style = { zIndex: sumLength - index };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span key={index} onClick={bind(changedir(path))} style={style}>{dir}</span>
|
<Hammer onTap={bind(changedir(path))} key={index}>
|
||||||
|
<span style={style}>{dir}</span>
|
||||||
|
</Hammer>
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -37,10 +40,11 @@ export default class Breadcrumb extends Component {
|
|||||||
let path = current.join('/').replace(/^\//, ''); // remove starting slash
|
let path = current.join('/').replace(/^\//, ''); // remove starting slash
|
||||||
let key = directories.length + index;
|
let key = directories.length + index;
|
||||||
let style = { zIndex: arr.length - index};
|
let style = { zIndex: arr.length - index};
|
||||||
console.log('history', dir)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span key={key} className='history' onClick={bind(changedir(path))} style={style}>{dir}</span>
|
<Hammer onTap={bind(changedir(path))} key={key}>
|
||||||
|
<span className='history' style={style}>{dir}</span>
|
||||||
|
</Hammer>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,7 +62,7 @@ export default class Breadcrumb extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
let container = React.findDOMNode(this.refs.container);
|
let container = this.refs.container;
|
||||||
let currents = container.querySelectorAll('span:not(.history)');
|
let currents = container.querySelectorAll('span:not(.history)');
|
||||||
|
|
||||||
container.scrollLeft = currents[currents.length - 1].offsetLeft;
|
container.scrollLeft = currents[currents.length - 1].offsetLeft;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { template } from 'utils';
|
import { template } from 'utils';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
export default class Dialog extends Component {
|
export default class Dialog extends Component {
|
||||||
render() {
|
render() {
|
||||||
@ -8,10 +10,11 @@ export default class Dialog extends Component {
|
|||||||
|
|
||||||
let buttons = this.props.buttons.map((button, i) => {
|
let buttons = this.props.buttons.map((button, i) => {
|
||||||
return (
|
return (
|
||||||
<button className={button.className + ' btn'} key={i}
|
<Hammer onTap={button.action.bind(this)}>
|
||||||
onClick={button.action.bind(this)}>
|
<button className={button.className + ' btn'} key={i}>
|
||||||
{button.text}
|
{button.text}
|
||||||
</button>
|
</button>
|
||||||
|
</Hammer>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ export default class Dialog extends Component {
|
|||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (!this.props.value) return;
|
if (!this.props.value) return;
|
||||||
|
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
|
|
||||||
input.value = this.props.value;
|
input.value = this.props.value;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import changedir from 'actions/changedir';
|
import changedir from 'actions/changedir';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import entry from './mixins/entry';
|
import entry from './mixins/entry';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
export default class Directory extends Component {
|
export default class Directory extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -22,8 +24,8 @@ export default class Directory extends Component {
|
|||||||
: this.peek.bind(this);
|
: this.peek.bind(this);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Hammer onTap={clickHandler}>
|
||||||
<div className='directory' ref='container'
|
<div className='directory' ref='container'
|
||||||
onClick={clickHandler}
|
|
||||||
onContextMenu={this.contextMenu.bind(this)}>
|
onContextMenu={this.contextMenu.bind(this)}>
|
||||||
|
|
||||||
{input}
|
{input}
|
||||||
@ -33,6 +35,7 @@ export default class Directory extends Component {
|
|||||||
<p>{this.props.name}</p>
|
<p>{this.props.name}</p>
|
||||||
<span>{this.props.children ? this.props.children + ' items' : ''}</span>
|
<span>{this.props.children ? this.props.children + ' items' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</Hammer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +44,7 @@ export default class Directory extends Component {
|
|||||||
|
|
||||||
let file = store.getState().get('files')[this.props.index];
|
let file = store.getState().get('files')[this.props.index];
|
||||||
|
|
||||||
store.dispatch(changedir(file.path.replace(/^\//, '') + file.name));
|
const path = file.path.endsWith(file.name) ? file.path : file.path + file.name;
|
||||||
|
store.dispatch(changedir(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import File from './file';
|
import File from './file';
|
||||||
import Directory from './directory';
|
import Directory from './directory';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { type } from 'utils';
|
import { type } from 'utils';
|
||||||
import Hammer from 'hammerjs';
|
import Hammer from 'react-hammerjs';
|
||||||
import changedir from 'actions/changedir';
|
import changedir from 'actions/changedir';
|
||||||
|
|
||||||
@connect(props)
|
@connect(props)
|
||||||
@ -20,7 +21,7 @@ export default class FileList extends Component {
|
|||||||
|
|
||||||
let els = files.map((file, index) => {
|
let els = files.map((file, index) => {
|
||||||
let selected = activeFile.indexOf(file) > -1;
|
let selected = activeFile.indexOf(file) > -1;
|
||||||
if (type(file) === 'File') {
|
if (file.type === 'File') {
|
||||||
return <File selectView={selectView} selected={selected} key={index} index={index} name={file.name} size={file.size} type={file.type} />;
|
return <File selectView={selectView} selected={selected} key={index} index={index} name={file.name} size={file.size} type={file.type} />;
|
||||||
} else {
|
} else {
|
||||||
return <Directory selectView={selectView} selected={selected} key={index} index={index} name={file.name} children={file.children} type={file.type} />
|
return <Directory selectView={selectView} selected={selected} key={index} index={index} name={file.name} children={file.children} type={file.type} />
|
||||||
@ -30,24 +31,21 @@ export default class FileList extends Component {
|
|||||||
let className= `file-list ${view}`;
|
let className= `file-list ${view}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Hammer onSwipe={this.swipe} options={{ direction: Hammer.DIRECTION_RIGHT }}>
|
||||||
<div className={className} ref='container'>
|
<div className={className} ref='container'>
|
||||||
{els}
|
{els}
|
||||||
</div>
|
</div>
|
||||||
|
</Hammer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
swipe(e) {
|
||||||
let container = React.findDOMNode(this.refs.container);
|
|
||||||
let touch = Hammer(container);
|
|
||||||
|
|
||||||
touch.on('swipe', e => {
|
|
||||||
let current = store.getState().get('cwd');
|
let current = store.getState().get('cwd');
|
||||||
let up = current.split('/').slice(0, -1).join('/');
|
let up = current.split('/').slice(0, -1).join('/');
|
||||||
|
|
||||||
if (up === current) return;
|
if (up === current) return;
|
||||||
|
|
||||||
store.dispatch(changedir(up));
|
store.dispatch(changedir(up));
|
||||||
}).set({direction: Hammer.DIRECTION_RIGHT});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,10 +57,3 @@ function props(state) {
|
|||||||
view: state.get('settings').view || 'list'
|
view: state.get('settings').view || 'list'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFiles(dir) {
|
|
||||||
let storage = navigator.getDeviceStorage('sdcard');
|
|
||||||
let root = await storage.get(dir);
|
|
||||||
|
|
||||||
return await root.getFilesAndDirectories();
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { humanSize } from 'utils';
|
import { humanSize } from 'utils';
|
||||||
import entry from './mixins/entry';
|
import entry from './mixins/entry';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
import mime from 'mime';
|
||||||
|
|
||||||
export default class File extends Component {
|
export default class File extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -22,8 +25,8 @@ export default class File extends Component {
|
|||||||
: this.open.bind(this);
|
: this.open.bind(this);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Hammer onTap={clickHandler}>
|
||||||
<div className='file' ref='container'
|
<div className='file' ref='container'
|
||||||
onClick={clickHandler}
|
|
||||||
onContextMenu={this.contextMenu.bind(this)}>
|
onContextMenu={this.contextMenu.bind(this)}>
|
||||||
|
|
||||||
{input}
|
{input}
|
||||||
@ -33,6 +36,7 @@ export default class File extends Component {
|
|||||||
<p>{this.props.name}</p>
|
<p>{this.props.name}</p>
|
||||||
<span>{humanSize(this.props.size)}</span>
|
<span>{humanSize(this.props.size)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</Hammer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,12 +45,13 @@ export default class File extends Component {
|
|||||||
|
|
||||||
let file = store.getState().get('files')[this.props.index];
|
let file = store.getState().get('files')[this.props.index];
|
||||||
|
|
||||||
let name = file.type === 'application/pdf' ? 'view' : 'open';
|
const type = mime.lookup(file.name);
|
||||||
|
let name = type === 'application/pdf' ? 'view' : 'open';
|
||||||
new MozActivity({
|
new MozActivity({
|
||||||
name,
|
name,
|
||||||
data: {
|
data: {
|
||||||
type: file.type,
|
blob: file,
|
||||||
blob: file
|
type
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { toggle } from 'actions/navigation';
|
import { toggle } from 'actions/navigation';
|
||||||
import { show } from 'actions/dialog';
|
import { show } from 'actions/dialog';
|
||||||
import { search } from 'actions/files-view';
|
import { search } from 'actions/files-view';
|
||||||
import { bind } from 'store';
|
import { bind } from 'store';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
@connect(props)
|
@connect(props)
|
||||||
export default class Header extends Component {
|
export default class Header extends Component {
|
||||||
@ -11,16 +13,22 @@ export default class Header extends Component {
|
|||||||
let i;
|
let i;
|
||||||
|
|
||||||
if (this.props.search) {
|
if (this.props.search) {
|
||||||
i = <button onClick={bind(search())}><i className='icon-cross' /></button>
|
i = <Hammer onTap={bind(search())}>
|
||||||
|
<button><i className='icon-cross' /></button>
|
||||||
|
</Hammer>
|
||||||
} else {
|
} else {
|
||||||
i = <button onClick={bind(show('searchDialog'))}><i className='icon-search tour-item' /></button>
|
i = <Hammer onTap={bind(show('searchDialog'))}>
|
||||||
|
<button><i className='icon-search tour-item' /></button>
|
||||||
|
</Hammer>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<button className='drawer tour-item' onTouchStart={bind(toggle())}>
|
<Hammer onTap={bind(toggle())}>
|
||||||
|
<button className='drawer tour-item'>
|
||||||
<i className='icon-menu'></i>
|
<i className='icon-menu'></i>
|
||||||
</button>
|
</button>
|
||||||
|
</Hammer>
|
||||||
<h1>Hawk</h1>
|
<h1>Hawk</h1>
|
||||||
|
|
||||||
{i}
|
{i}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
export const MENU_WIDTH = 245;
|
export const MENU_WIDTH = 245;
|
||||||
|
|
||||||
@ -11,7 +13,11 @@ export default class Menu extends Component {
|
|||||||
let enabled = typeof item.enabled === 'function' ? item.enabled() : true
|
let enabled = typeof item.enabled === 'function' ? item.enabled() : true
|
||||||
let className = enabled ? '' : 'disabled';
|
let className = enabled ? '' : 'disabled';
|
||||||
|
|
||||||
return <li key={index} className={className} onClick={item.action.bind(this)}>{item.name}</li>
|
return (
|
||||||
|
<Hammer key={index} onTap={item.action.bind(this)}>
|
||||||
|
<li className={className}>{item.name}</li>
|
||||||
|
</Hammer>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
let className = 'menu ' + (active ? 'active' : '');
|
let className = 'menu ' + (active ? 'active' : '');
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ export default {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
let file = store.getState().get('files')[this.props.index];
|
let file = store.getState().get('files')[this.props.index];
|
||||||
let rect = React.findDOMNode(this.refs.container).getBoundingClientRect();
|
let rect = this.refs.container.getBoundingClientRect();
|
||||||
let {x, y, width, height} = rect;
|
let {x, y, width, height} = rect;
|
||||||
|
|
||||||
let left = window.innerWidth / 2 - MENU_WIDTH / 2,
|
let left = window.innerWidth / 2 - MENU_WIDTH / 2,
|
||||||
@ -34,7 +34,7 @@ export default {
|
|||||||
let current = (store.getState().get('activeFile') || []).slice(0);
|
let current = (store.getState().get('activeFile') || []).slice(0);
|
||||||
let file = store.getState().get('files')[this.props.index];
|
let file = store.getState().get('files')[this.props.index];
|
||||||
|
|
||||||
let check = React.findDOMNode(this.refs.check);
|
let check = this.refs.check;
|
||||||
|
|
||||||
if (current.indexOf(file) > -1) {
|
if (current.indexOf(file) > -1) {
|
||||||
current.splice(current.indexOf(file), 1);
|
current.splice(current.indexOf(file), 1);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { hide as hideNavigation } from 'actions/navigation';
|
import { hide as hideNavigation } from 'actions/navigation';
|
||||||
import camelCase from 'lodash/string/camelCase';
|
import camelCase from 'lodash/string/camelCase';
|
||||||
@ -69,9 +70,6 @@ export default class Navigation extends Component {
|
|||||||
<input id='showDirectoriesFirst' type='checkbox' defaultChecked={settings.showDirectoriesFirst} />
|
<input id='showDirectoriesFirst' type='checkbox' defaultChecked={settings.showDirectoriesFirst} />
|
||||||
<label htmlFor='showDirectoriesFirst'>Show Directories First</label>
|
<label htmlFor='showDirectoriesFirst'>Show Directories First</label>
|
||||||
</li>
|
</li>
|
||||||
<li className='coming-soon'>
|
|
||||||
<label>Advanced Preferences</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>External</p>
|
<p>External</p>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import FileList from 'components/file-list';
|
import FileList from 'components/file-list';
|
||||||
import Navigation from 'components/navigation';
|
import Navigation from 'components/navigation';
|
||||||
import Header from 'components/header';
|
import Header from 'components/header';
|
||||||
@ -10,6 +11,7 @@ import Spinner from 'components/spinner';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { hideAll as hideAllMenus } from 'actions/menu';
|
import { hideAll as hideAllMenus } from 'actions/menu';
|
||||||
import { hideAll as hideAllDialogs} from 'actions/dialog';
|
import { hideAll as hideAllDialogs} from 'actions/dialog';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
import tour from 'tour';
|
import tour from 'tour';
|
||||||
import changedir from 'actions/changedir';
|
import changedir from 'actions/changedir';
|
||||||
@ -27,12 +29,13 @@ let DeleteDialog = connect(state => state.get('deleteDialog'))(Dialog);
|
|||||||
let ErrorDialog = connect(state => state.get('errorDialog'))(Dialog);
|
let ErrorDialog = connect(state => state.get('errorDialog'))(Dialog);
|
||||||
let CreateDialog = connect(state => state.get('createDialog'))(Dialog);
|
let CreateDialog = connect(state => state.get('createDialog'))(Dialog);
|
||||||
let SearchDialog = connect(state => state.get('searchDialog'))(Dialog);
|
let SearchDialog = connect(state => state.get('searchDialog'))(Dialog);
|
||||||
|
let CompressDialog = connect(state => state.get('compressDialog'))(Dialog);
|
||||||
|
|
||||||
export default class Root extends Component {
|
export default class Root extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div onTouchStart={this.touchStart.bind(this)}
|
<Hammer onTap={this.onClick.bind(this)}>
|
||||||
onClick={this.onClick.bind(this)}>
|
<div onTouchStart={this.touchStart.bind(this)}>
|
||||||
<Header />
|
<Header />
|
||||||
<Breadcrumb />
|
<Breadcrumb />
|
||||||
<Navigation />
|
<Navigation />
|
||||||
@ -47,6 +50,7 @@ export default class Root extends Component {
|
|||||||
<ErrorDialog />
|
<ErrorDialog />
|
||||||
<CreateDialog />
|
<CreateDialog />
|
||||||
<SearchDialog />
|
<SearchDialog />
|
||||||
|
<CompressDialog />
|
||||||
|
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|
||||||
@ -57,6 +61,7 @@ export default class Root extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<button id='skip-tour'>Skip</button>
|
<button id='skip-tour'>Skip</button>
|
||||||
</div>
|
</div>
|
||||||
|
</Hammer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
@connect(props)
|
@connect(props)
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { refresh, selectView } from 'actions/files-view';
|
import { refresh, selectView } from 'actions/files-view';
|
||||||
import { show as showDialog } from 'actions/dialog';
|
import { show as showDialog } from 'actions/dialog';
|
||||||
import { show as showMenu } from 'actions/menu';
|
import { show as showMenu } from 'actions/menu';
|
||||||
|
import { active } from 'actions/file';
|
||||||
import settings from 'actions/settings';
|
import settings from 'actions/settings';
|
||||||
import store, { bind } from 'store';
|
import store, { bind } from 'store';
|
||||||
import { MENU_WIDTH } from './menu';
|
import { MENU_WIDTH } from './menu';
|
||||||
|
import Hammer from 'react-hammerjs';
|
||||||
|
|
||||||
export default class Toolbar extends Component {
|
export default class Toolbar extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='toolbar'>
|
<div className='toolbar'>
|
||||||
<button className='icon-back tour-item' onClick={this.goUp} />
|
<Hammer onTap={this.goUp}>
|
||||||
<button className='icon-plus tour-item' onClick={this.newFile} />
|
<button className='icon-back tour-item' />
|
||||||
<button className='icon-refresh tour-item' onClick={bind(refresh())} />
|
</Hammer>
|
||||||
<button className='icon-select tour-item' onClick={bind(selectView('toggle'))} />
|
<Hammer onTap={this.newFile}>
|
||||||
<button className='icon-more tour-item' onClick={this.showMore.bind(this)} ref='more' />
|
<button className='icon-plus tour-item'/>
|
||||||
|
</Hammer>
|
||||||
|
<Hammer onTap={bind(refresh())}>
|
||||||
|
<button className='icon-refresh tour-item'/>
|
||||||
|
</Hammer>
|
||||||
|
<Hammer onTap={this.selectView}>
|
||||||
|
<button className='icon-select tour-item'/>
|
||||||
|
</Hammer>
|
||||||
|
<Hammer onTap={this.showMore.bind(this)}>
|
||||||
|
<button className='icon-more tour-item' ref='more'/>
|
||||||
|
</Hammer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showMore() {
|
showMore() {
|
||||||
let rect = React.findDOMNode(this.refs.more).getBoundingClientRect();
|
let rect = this.refs.more.getBoundingClientRect();
|
||||||
let {x, y, width, height} = rect;
|
let {x, y, width, height} = rect;
|
||||||
|
|
||||||
let left = x + width - MENU_WIDTH,
|
let left = x + width - MENU_WIDTH,
|
||||||
@ -39,6 +52,11 @@ export default class Toolbar extends Component {
|
|||||||
store.dispatch(changedir(up));
|
store.dispatch(changedir(up));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectView() {
|
||||||
|
store.dispatch(selectView('toggle'));
|
||||||
|
store.dispatch(active());
|
||||||
|
}
|
||||||
|
|
||||||
newFile() {
|
newFile() {
|
||||||
let cwd = store.getState().get('cwd');
|
let cwd = store.getState().get('cwd');
|
||||||
let action = showDialog('createDialog', {
|
let action = showDialog('createDialog', {
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { hide, hideAll, show } from 'actions/dialog';
|
import { hide, hideAll, show } from 'actions/dialog';
|
||||||
import { rename, remove, create, active } from 'actions/file';
|
import { rename, remove, create, active } from 'actions/file';
|
||||||
import { search } from 'actions/files-view';
|
import { search } from 'actions/files-view';
|
||||||
|
import { compress } from 'actions/compress';
|
||||||
import store, { bind } from 'store';
|
import store, { bind } from 'store';
|
||||||
|
|
||||||
const INVALID_NAME = 'Please enter a valid name.';
|
const INVALID_NAME = 'Please enter a valid name.';
|
||||||
@ -16,7 +17,7 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'File',
|
text: 'File',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
|
|
||||||
if (!input.value) {
|
if (!input.value) {
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
@ -37,7 +38,7 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Directory',
|
text: 'Directory',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
|
|
||||||
if (!input.value) {
|
if (!input.value) {
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
@ -58,7 +59,7 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
@ -73,7 +74,7 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Rename',
|
text: 'Rename',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
|
|
||||||
if (!input.value) {
|
if (!input.value) {
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
@ -135,7 +136,7 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
@ -143,12 +144,13 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Search',
|
text: 'Search',
|
||||||
action() {
|
action() {
|
||||||
let input = React.findDOMNode(this.refs.input);
|
let input = this.refs.input;
|
||||||
|
|
||||||
if (!input.value) {
|
if (!input.value) {
|
||||||
this.props.dispatch(hideAll());
|
this.props.dispatch(hideAll());
|
||||||
this.props.dispatch(active());
|
this.props.dispatch(active());
|
||||||
this.props.dispatch(show('errorDialog', {description: INVALID_SEARCH}));
|
this.props.dispatch(show('errorDialog',
|
||||||
|
{description: INVALID_SEARCH}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,5 +162,45 @@ export default {
|
|||||||
className: 'success'
|
className: 'success'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
compressDialog: {
|
||||||
|
title: 'Archive',
|
||||||
|
description: 'Enter your desired archive name',
|
||||||
|
input: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Cancel',
|
||||||
|
action() {
|
||||||
|
let input = this.refs.input;
|
||||||
|
this.props.dispatch(hideAll());
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Create',
|
||||||
|
action() {
|
||||||
|
let input = this.refs.input;
|
||||||
|
|
||||||
|
if (!input.value) {
|
||||||
|
this.props.dispatch(hideAll());
|
||||||
|
this.props.dispatch(active());
|
||||||
|
this.props.dispatch(show('errorDialog',
|
||||||
|
{description: INVALID_NAME}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.value.slice(-4) !== '.zip') {
|
||||||
|
input.value += '.zip';
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeFile = store.getState().get('activeFile');
|
||||||
|
this.props.dispatch(compress(activeFile, input.value))
|
||||||
|
this.props.dispatch(hideAll());
|
||||||
|
this.props.dispatch(active());
|
||||||
|
input.value = '';
|
||||||
|
},
|
||||||
|
className: 'success'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import * as ftp from 'api/ftp';
|
||||||
import Root from 'components/root';
|
import Root from 'components/root';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import './activities';
|
import './activities';
|
||||||
|
|
||||||
|
ftp.connect({
|
||||||
|
host: '192.168.1.5',
|
||||||
|
port: 21,
|
||||||
|
username: 'mahdi',
|
||||||
|
password: 'heater0!'
|
||||||
|
}).then(socket => {
|
||||||
|
window.socket = socket;
|
||||||
|
window.ftp = ftp;
|
||||||
|
}, console.error.bind(console))
|
||||||
|
|
||||||
let wrapper = document.getElementById('wrapper');
|
let wrapper = document.getElementById('wrapper');
|
||||||
React.render(<Provider store={store}>{() => <Root />}</Provider>, wrapper);
|
ReactDOM.render(<Provider store={store}>
|
||||||
|
<Root />
|
||||||
|
</Provider>, wrapper);
|
||||||
|
@ -2,6 +2,7 @@ import { hideAll } from 'actions/menu';
|
|||||||
import { show } from 'actions/dialog';
|
import { show } from 'actions/dialog';
|
||||||
import { selectView } from 'actions/files-view';
|
import { selectView } from 'actions/files-view';
|
||||||
import { copy, move } from 'actions/file';
|
import { copy, move } from 'actions/file';
|
||||||
|
import { compress, decompress } from 'actions/compress';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
const entryMenu = {
|
const entryMenu = {
|
||||||
@ -64,6 +65,27 @@ const entryMenu = {
|
|||||||
blob
|
blob
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Extract',
|
||||||
|
enabled() {
|
||||||
|
let active = store.getState().get('activeFile');
|
||||||
|
|
||||||
|
return active && active[0].name.slice(-4) === '.zip';
|
||||||
|
},
|
||||||
|
action() {
|
||||||
|
let active = store.getState().get('activeFile');
|
||||||
|
|
||||||
|
store.dispatch(decompress(active));
|
||||||
|
store.dispatch(hideAll());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Archive',
|
||||||
|
action() {
|
||||||
|
store.dispatch(hideAll());
|
||||||
|
store.dispatch(show('compressDialog'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -142,6 +164,13 @@ const moreMenu = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Archive',
|
||||||
|
action() {
|
||||||
|
store.dispatch(hideAll());
|
||||||
|
store.dispatch(show('compressDialog'));
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ACTIVE_FILE } from 'actions/types';
|
import { ACTIVE_FILE, SELECT_VIEW } from 'actions/types';
|
||||||
|
|
||||||
export default function(state = null, action) {
|
export default function(state = null, action) {
|
||||||
if (action.type === ACTIVE_FILE) {
|
if (action.type === ACTIVE_FILE) {
|
||||||
|
@ -32,6 +32,7 @@ export default function(state = new Immutable.Map(), action) {
|
|||||||
deleteDialog: dialog(state, action, 'deleteDialog'),
|
deleteDialog: dialog(state, action, 'deleteDialog'),
|
||||||
errorDialog: dialog(state, action, 'errorDialog'),
|
errorDialog: dialog(state, action, 'errorDialog'),
|
||||||
createDialog: dialog(state, action, 'createDialog'),
|
createDialog: dialog(state, action, 'createDialog'),
|
||||||
searchDialog: dialog(state, action, 'searchDialog')
|
searchDialog: dialog(state, action, 'searchDialog'),
|
||||||
|
compressDialog: dialog(state, action, 'compressDialog')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CHANGE_DIRECTORY, REFRESH, SETTINGS } from 'actions/types';
|
import { CHANGE_DIRECTORY, REFRESH, SETTINGS } from 'actions/types';
|
||||||
import { children, CACHE } from 'api/files';
|
import { children, CACHE, FTP_CACHE } from 'api/auto';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { reportError, normalize } from 'utils';
|
import { reportError, normalize } from 'utils';
|
||||||
import { listFiles } from 'actions/files-view';
|
import { listFiles } from 'actions/files-view';
|
||||||
@ -13,6 +13,7 @@ export default function(state = '', action) {
|
|||||||
|
|
||||||
if (action.type === REFRESH) {
|
if (action.type === REFRESH) {
|
||||||
CACHE[state] = null;
|
CACHE[state] = null;
|
||||||
|
FTP_CACHE[state] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === REFRESH || action.type === SETTINGS) {
|
if (action.type === REFRESH || action.type === SETTINGS) {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { LIST_FILES, RENAME_FILE, DELETE_FILE, CREATE_FILE, MOVE_FILE, COPY_FILE, SEARCH } from 'actions/types';
|
import { LIST_FILES, RENAME_FILE, DELETE_FILE, CREATE_FILE, MOVE_FILE, COPY_FILE, SEARCH, COMPRESS, DECOMPRESS } from 'actions/types';
|
||||||
|
import zip from 'jszip';
|
||||||
import { refresh } from 'actions/files-view';
|
import { refresh } from 'actions/files-view';
|
||||||
import { move, remove, sdcard, createFile, createDirectory, copy } from 'api/files';
|
import * as auto from 'api/auto';
|
||||||
import { show } from 'actions/dialog';
|
import { show } from 'actions/dialog';
|
||||||
import store, { bind } from 'store';
|
import store, { bind } from 'store';
|
||||||
import { reportError, type } from 'utils';
|
import { reportError, type, normalize } from 'utils';
|
||||||
|
|
||||||
let boundRefresh = bind(refresh());
|
let boundRefresh = bind(refresh());
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ export default function(state = [], action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === CREATE_FILE) {
|
if (action.type === CREATE_FILE) {
|
||||||
let fn = action.directory ? createDirectory : createFile;
|
let fn = action.directory ? auto.createDirectory : auto.createFile;
|
||||||
fn(action.path).then(boundRefresh, reportError);
|
fn(action.path).then(boundRefresh, reportError);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@ -47,7 +48,7 @@ export default function(state = [], action) {
|
|||||||
if (action.type === RENAME_FILE) {
|
if (action.type === RENAME_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
let cwd = store.getState().get('cwd');
|
let cwd = store.getState().get('cwd');
|
||||||
return move(file, cwd + '/' + action.name);
|
return auto.move(file, cwd + '/' + action.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -56,7 +57,7 @@ export default function(state = [], action) {
|
|||||||
|
|
||||||
if (action.type === MOVE_FILE) {
|
if (action.type === MOVE_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
return move(file, action.newPath + '/' + file.name);
|
return auto.move(file, action.newPath + '/' + file.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -65,7 +66,7 @@ export default function(state = [], action) {
|
|||||||
|
|
||||||
if (action.type === COPY_FILE) {
|
if (action.type === COPY_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
return copy(file, action.newPath + '/' + file.name);
|
return auto.copy(file, action.newPath + '/' + file.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -74,17 +75,71 @@ export default function(state = [], action) {
|
|||||||
|
|
||||||
if (action.type === DELETE_FILE) {
|
if (action.type === DELETE_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
let path = ((file.path || '') + file.name).replace(/^\//, '');
|
let path = normalize((file.path || '') + file.name);
|
||||||
return remove(path, true);
|
return auto.remove(path, true);
|
||||||
}))
|
}))
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === COMPRESS) {
|
||||||
|
let archive = new zip();
|
||||||
|
let cwd = store.getState().get('cwd');
|
||||||
|
|
||||||
|
let all = Promise.all(action.file.map(function addFile(file) {
|
||||||
|
let path = normalize((file.path || '') + file.name);
|
||||||
|
let archivePath = path.slice(cwd.length);
|
||||||
|
// directory
|
||||||
|
console.log(file);
|
||||||
|
if (file.type === 'Directory') {
|
||||||
|
let folder = archive.folder(file.name);
|
||||||
|
|
||||||
|
return auto.children(path).then(files => {
|
||||||
|
files = files.filter(file => file);
|
||||||
|
|
||||||
|
return Promise.all(files.map(child => {
|
||||||
|
return addFile(child);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return auto.readFile(path).then(content => {
|
||||||
|
archive.file(archivePath, content);
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
|
||||||
|
all.then(() => {
|
||||||
|
let blob = archive.generate({ type: 'blob' });
|
||||||
|
|
||||||
|
let cwd = store.getState().get('cwd');
|
||||||
|
let path = normalize(cwd + '/' + action.name);
|
||||||
|
return auto.writeFile(path, blob);
|
||||||
|
}).then(boundRefresh).catch(reportError);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === DECOMPRESS) {
|
||||||
|
let file = action.file[0];
|
||||||
|
let path = normalize((file.path || '') + file.name);
|
||||||
|
auto.readFile(path).then(content => {
|
||||||
|
let archive = new zip(content);
|
||||||
|
let files = Object.keys(archive.files);
|
||||||
|
|
||||||
|
let all = Promise.all(files.map(name => {
|
||||||
|
let buffer = archive.files[name].asArrayBuffer();
|
||||||
|
let blob = new Blob([buffer]);
|
||||||
|
|
||||||
|
let cwd = store.getState().get('cwd');
|
||||||
|
let filePath = normalize(cwd + '/' + name);
|
||||||
|
|
||||||
|
return auto.writeFile(filePath, blob);
|
||||||
|
}));
|
||||||
|
|
||||||
|
all.then(boundRefresh, reportError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mov(file, newPath) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@ import { SEARCH, CHANGE_DIRECTORY, REFRESH } from 'actions/types';
|
|||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { reportError } from 'utils';
|
import { reportError } from 'utils';
|
||||||
import { listFiles } from 'actions/files-view';
|
import { listFiles } from 'actions/files-view';
|
||||||
import { children } from 'api/files';
|
import { children } from 'api/auto';
|
||||||
import { type, normalize } from 'utils';
|
import { type, normalize } from 'utils';
|
||||||
|
|
||||||
export default function(state = '', action) {
|
export default function(state = '', action) {
|
||||||
|
@ -27,7 +27,8 @@ export function getKey(object = store.getState().toJS(), key) {
|
|||||||
|
|
||||||
export function reportError(err) {
|
export function reportError(err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
let action = show('errorDialog', {description: err.message});
|
let msg = err.message || err.target.error.message;
|
||||||
|
let action = show('errorDialog', {description: msg});
|
||||||
store.dispatch(action);
|
store.dispatch(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,3 +51,17 @@ export function humanSize(size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLength(string) {
|
||||||
|
var byteLen = 0;
|
||||||
|
for (var i = 0; i < string.length; i++) {
|
||||||
|
var c = string.charCodeAt(i);
|
||||||
|
byteLen += c < (1 << 7) ? 1 :
|
||||||
|
c < (1 << 11) ? 2 :
|
||||||
|
c < (1 << 16) ? 3 :
|
||||||
|
c < (1 << 21) ? 4 :
|
||||||
|
c < (1 << 26) ? 5 :
|
||||||
|
c < (1 << 31) ? 6 : Number.NaN;
|
||||||
|
}
|
||||||
|
return byteLen;
|
||||||
|
}
|
||||||
|
@ -11,4 +11,10 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
background: @light-gray;
|
background: @light-gray;
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex: 1;
|
||||||
|
width: auto;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"name": "Hawk",
|
"name": "Hawk",
|
||||||
"description": "Keep an eye on your files with a full-featured file manager",
|
"description": "Keep an eye on your files with a full-featured file manager",
|
||||||
"launch_path": "/index.html",
|
"launch_path": "/index.html",
|
||||||
@ -30,6 +30,9 @@
|
|||||||
"device-storage:music": {
|
"device-storage:music": {
|
||||||
"access": "readwrite",
|
"access": "readwrite",
|
||||||
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
||||||
|
},
|
||||||
|
"tcp-socket": {
|
||||||
|
"description": "FTP Browser: Used to connect to FTP servers"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installs_allowed_from": [
|
"installs_allowed_from": [
|
||||||
|
Loading…
Reference in New Issue
Block a user