fix(compatibility): make it compatible with the new Firefox OS sdcard API (tested on Sony Z3C)

fix(toolbar): increase tap area of buttons for easier access
fix(touch): improve touch sensitivity for ease of use
This commit is contained in:
Mahdi Dibaiee 2016-04-08 17:31:24 +04:30
parent 2eaf2ac1f0
commit 7bcf39bdb7
27 changed files with 10529 additions and 10080 deletions

File diff suppressed because one or more lines are too long

View File

@ -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",

View File

@ -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.

View File

@ -1,6 +1,6 @@
{ {
"name": "hawk", "name": "hawk",
"version": "1.1.2", "version": "1.2.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@ -26,7 +26,12 @@
}, },
"homepage": "https://github.com/mdibaiee/", "homepage": "https://github.com/mdibaiee/",
"dependencies": { "dependencies": {
"jszip": "2.5.0" "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",
@ -44,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.2.0.zip Normal file

Binary file not shown.

View File

@ -51,6 +51,13 @@ 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) { for (let child of childs) {
@ -58,6 +65,13 @@ export async function children(dir, gatherInfo) {
value: type(child), value: type(child),
enumerable: true 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) {
@ -114,7 +128,6 @@ export async function writeFile(path, content) {
} }
let request = sdcard().addNamed(content, path); let request = sdcard().addNamed(content, path);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -124,27 +137,55 @@ export async function writeFile(path, content) {
}); });
} }
export async function createFile(...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 await parent.createFile(...args); if (!parent.createFile) {
parent = await root();
filename = path;
}
CACHE[parentPath] = null;
return await parent.createFile(filename);
} }
export async function createDirectory(...args) { export async function createDirectory(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.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) {

View File

@ -1,15 +1,14 @@
import { refresh } from 'actions/files-view'; import { refresh } from 'actions/files-view';
import { bind } from 'store'; import { bind } from 'store';
import Eventconnection from 'events'; import EventEmitter from 'events';
import { humanSize, reportError, normalize, type } from 'utils'; import { humanSize, reportError, normalize, type, getLength } from 'utils';
export let FTP_CACHE = {}; export let FTP_CACHE = {};
let socket; let socket;
let connection = new Eventconnection(); let connection = new EventEmitter();
connection.setMaxListeners(99); connection.setMaxListeners(99);
let wd = '';
let currentRequest; export let queue = Object.assign([], EventEmitter.prototype);
let queue = 0;
export async function connect(properties = {}) { export async function connect(properties = {}) {
let { host, port, username, password } = properties; let { host, port, username, password } = properties;
@ -63,7 +62,6 @@ export function send(command, ...args) {
export async function cwd(dir = '') { export async function cwd(dir = '') {
send('CWD', dir); send('CWD', dir);
wd = dir;
} }
const PWD_REGEX = /257 "(.*)"/; const PWD_REGEX = /257 "(.*)"/;
@ -76,6 +74,7 @@ export async function pwd() {
connection.removeListener('data', listener); connection.removeListener('data', listener);
}); });
send('PWD'); send('PWD');
}); });
} }
@ -90,7 +89,7 @@ export async function pasv() {
connection.removeListener('data', listener); connection.removeListener('data', listener);
return resolve(port); resolve(port);
}); });
send('EPSV'); send('EPSV');
@ -99,7 +98,7 @@ export async function pasv() {
const LIST_EXTRACTOR = /(.*?)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\d+:?\d+)+\s+(.*)/; const LIST_EXTRACTOR = /(.*?)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\d+:?\d+)+\s+(.*)/;
export async function list(dir = '') { export async function list(dir = '') {
return pasv().then(port => { let index = queue.push(port => {
return secondary({ host: socket.host, port }).then(({data}) => { return secondary({ host: socket.host, port }).then(({data}) => {
send('LIST', dir); send('LIST', dir);
@ -110,7 +109,7 @@ export async function list(dir = '') {
let match = item.match(LIST_EXTRACTOR); let match = item.match(LIST_EXTRACTOR);
return { return {
path: normalize(wd) + '/', path: normalize(dir + '/'),
type: match[1][0] === 'd' ? 'Directory' : 'File', type: match[1][0] === 'd' ? 'Directory' : 'File',
permissions: match[1].slice(1), permissions: match[1].slice(1),
links: +match[2], links: +match[2],
@ -128,16 +127,20 @@ export async function list(dir = '') {
}, reportError) }, reportError)
}); });
}); });
return handleQueue(index - 1);
} }
export async function namelist(dir = '') { export async function namelist(dir = '') {
return pasv().then(port => { let index = queue.push(port => {
return secondary({ host: socket.host, port }).then(({data}) => { return secondary({ host: socket.host, port }).then(({data}) => {
send('NLST', dir); send('NLST', dir);
return data.then(names => names.split('\n'), reportError); return data.then(names => names.split('\n'), reportError);
}); });
}) });
return handleQueue(index - 1);
} }
export async function secondary(properties = {}) { export async function secondary(properties = {}) {
@ -145,19 +148,23 @@ export async function secondary(properties = {}) {
let url = encodeURI(host); let url = encodeURI(host);
send('TYPE', 'I');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let alt = navigator.mozTCPSocket.open(url, port); let alt = navigator.mozTCPSocket.open(url, port);
alt.onopen = () => { alt.onopen = e => {
let data = new Promise((resolve, reject) => { let data = new Promise((resolve, reject) => {
let d = '';
alt.ondata = e => { alt.ondata = e => {
resolve(e.data); d += e.data;
} }
alt.onerror = e => { alt.onerror = e => {
reject(e.data); reject(e.data);
} }
alt.onclose = e => { alt.onclose = e => {
resolve(''); console.log('<<', d);
resolve(d);
} }
}); });
resolve({data}); resolve({data});
@ -165,20 +172,36 @@ export async function secondary(properties = {}) {
}) })
} }
const BUFFER_SIZE = 32000;
export async function secondaryWrite(properties = {}, content) { export async function secondaryWrite(properties = {}, content) {
let { host, port } = properties; let { host, port } = properties;
let url = encodeURI(host); let url = encodeURI(host);
send('TYPE', 'I');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let alt = navigator.mozTCPSocket.open(url, port); let alt = navigator.mozTCPSocket.open(url, port);
alt.onopen = () => { alt.onopen = () => {
alt.send(content); console.log('>>', content);
let step = 0;
setImmediate(() => { (function send() {
alt.close(); 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 = () => { alt.onclose = () => {
@ -191,7 +214,7 @@ export async function children(dir = '', gatherInfo) {
dir = normalize(dir); dir = normalize(dir);
if (FTP_CACHE[dir]) return FTP_CACHE[dir]; if (FTP_CACHE[dir]) return FTP_CACHE[dir];
let childs = gatherInfo ? await list(dir) : await namelist(); let childs = gatherInfo ? await list(dir) : await namelist(dir);
FTP_CACHE[dir] = childs; FTP_CACHE[dir] = childs;
@ -213,23 +236,45 @@ export async function isDirectory(path = '') {
export async function readFile(path = '') { export async function readFile(path = '') {
path = normalize(path); path = normalize(path);
return pasv().then(port => { let index = queue.push(port => {
return secondary({ host: socket.host, port }).then(({data}) => { return secondary({ host: socket.host, port }).then(({data}) => {
send('RETR', path); send('RETR', path);
return data; return data;
}); });
}).catch(reportError); })
return handleQueue(index - 1);
} }
export async function writeFile(path = '', content) { export async function writeFile(path = '', content) {
let index;
path = normalize(path); path = normalize(path);
return pasv().then(port => { 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); send('STOR', path);
return secondaryWrite({ host: socket.host, port }, content).then(() => {
secondaryWrite({ host: socket.host, port }, reader.result)
.then(resolve, reject);
});
reader.readAsBinaryString(content);
}) })
}).catch(reportError); });
} else {
index = queue.push(port => {
send('STOR', path);
return secondaryWrite({ host: socket.host, port }, content);
});
}
return handleQueue(index - 1);
} }
export async function createFile(path = '') { export async function createFile(path = '') {
@ -245,23 +290,49 @@ export async function createDirectory(path = '') {
export async function remove(path = '') { export async function remove(path = '') {
path = normalize(path); path = normalize(path);
send('RMD', path); let ls = await list(path);
send('DELE', path); send('DELE', path);
send('DELE', path + '/*.*');
send('RMD', path);
} }
export async function move(path = '', newPath = '') { export async function move(file, newPath = '') {
path = normalize(path); let path = normalize(file.path + file.name);
newPath = normalize(newPath); newPath = normalize(newPath);
send('RNFR', path); send('RNFR', path);
send('RNTO', newPath); send('RNTO', newPath);
} }
export async function copy(path = '', newPath = '') { export async function copy(file, newPath = '') {
path = normalize(path); let path = normalize(file.path + file.name);
newPath = normalize(newPath); newPath = normalize(newPath);
let content = await readFile(path); let content = await readFile(path);
console.log(content);
return writeFile(newPath, 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);
});
});
}

View File

@ -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 {
@ -24,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>
); );
})); }));
@ -38,7 +42,9 @@ export default class Breadcrumb extends Component {
let style = { zIndex: arr.length - index}; let style = { zIndex: arr.length - index};
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>
) )
}); });
@ -56,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;

View File

@ -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;
} }

View File

@ -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));
} }
} }

View File

@ -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)
@ -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});
} }
} }

View File

@ -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
} }
}) })
} }

View File

@ -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}

View File

@ -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' : '');

View File

@ -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);

View File

@ -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>

View File

@ -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';
@ -32,8 +34,8 @@ 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 />
@ -59,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>
); );
} }

View File

@ -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)

View File

@ -1,4 +1,5 @@
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';
@ -6,22 +7,33 @@ 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={this.selectView} /> <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,

View File

@ -17,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());
@ -38,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());
@ -59,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 = '';
} }
@ -74,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 = '';
} }
@ -82,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());
@ -136,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 = '';
} }
@ -144,7 +144,7 @@ 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());
@ -171,7 +171,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 = '';
} }
@ -179,7 +179,7 @@ export default {
{ {
text: 'Create', text: 'Create',
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());

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import * as ftp from 'api/ftp'; import * as ftp from 'api/ftp';
import Root from 'components/root'; import Root from 'components/root';
import store from 'store'; import store from 'store';
@ -6,14 +7,16 @@ import { Provider } from 'react-redux';
import './activities'; import './activities';
ftp.connect({ ftp.connect({
host: '192.168.1.76', host: '192.168.1.5',
port: 21, port: 21,
username: 'mahdi', username: 'mahdi',
password: 'heater0!' password: 'heater0!'
}).then(socket => { }).then(socket => {
window.socket = socket; window.socket = socket;
window.ftp = ftp; window.ftp = ftp;
}, console.error) }, 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);

View File

@ -90,12 +90,14 @@ export default function(state = [], action) {
let all = Promise.all(action.file.map(function addFile(file) { let all = Promise.all(action.file.map(function addFile(file) {
let path = normalize((file.path || '') + file.name); let path = normalize((file.path || '') + file.name);
let archivePath = path.slice(cwd.length); let archivePath = path.slice(cwd.length);
console.log(archivePath);
// directory // directory
if (!(file instanceof Blob)) { console.log(file);
if (file.type === 'Directory') {
let folder = archive.folder(file.name); let folder = archive.folder(file.name);
return auto.children(path).then(files => { return auto.children(path).then(files => {
files = files.filter(file => file);
return Promise.all(files.map(child => { return Promise.all(files.map(child => {
return addFile(child); return addFile(child);
})); }));
@ -108,12 +110,10 @@ export default function(state = [], action) {
})) }))
all.then(() => { all.then(() => {
let buffer = archive.generate({ type: 'nodebuffer' }); let blob = archive.generate({ type: 'blob' });
let blob = new Blob([buffer], { type: 'application/zip' });
let cwd = store.getState().get('cwd'); let cwd = store.getState().get('cwd');
let path = normalize(cwd + '/' + action.name); let path = normalize(cwd + '/' + action.name);
console.log(path);
return auto.writeFile(path, blob); return auto.writeFile(path, blob);
}).then(boundRefresh).catch(reportError); }).then(boundRefresh).catch(reportError);

View File

@ -51,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;
}

View File

@ -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;
}
} }

View File

@ -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",