feat(archive): ability to archive / extract files in zip format

fix(selectview): clear active files when user taps on select-view button in toolbar

resolve #10
resolve #12
This commit is contained in:
Mahdi Dibaiee 2015-10-24 18:46:17 +03:30
parent 44340abb61
commit dfb7d8aa72
10 changed files with 11547 additions and 297 deletions

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,9 @@
"node": ">=0.12.0" "node": ">=0.12.0"
}, },
"homepage": "https://github.com/mdibaiee/", "homepage": "https://github.com/mdibaiee/",
"dependencies": {}, "dependencies": {
"jszip": "2.5.0"
},
"devDependencies": { "devDependencies": {
"babel": "^5.8.23", "babel": "^5.8.23",
"babelify": "^6.2.0", "babelify": "^6.2.0",

View File

@ -0,0 +1,15 @@
import { COMPRESS, DECOMPRESS } from './types';
export function compress(file) {
return {
type: COMPRESS,
file
}
}
export function decompress(file) {
return {
type: DECOMPRESS,
file
}
}

View 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'),

View File

@ -70,6 +70,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,6 +91,16 @@ export async function readFile(path) {
}); });
} }
export async function writeFile(path, content) {
let request = sdcard().addNamed(content, path);
return new Promise((resolve, reject) => {
request.onsuccess = resolve;
request.onerror = reject;
request.onabort = reject;
});
}
export async function createFile(...args) { export async function createFile(...args) {
let parent = await root(); let parent = await root();
@ -147,11 +163,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;
});
} }
} }

View File

@ -37,7 +37,6 @@ 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> <span key={key} className='history' onClick={bind(changedir(path))} style={style}>{dir}</span>

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
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/active-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';
@ -13,7 +14,7 @@ export default class Toolbar extends Component {
<button className='icon-back tour-item' onClick={this.goUp} /> <button className='icon-back tour-item' onClick={this.goUp} />
<button className='icon-plus tour-item' onClick={this.newFile} /> <button className='icon-plus tour-item' onClick={this.newFile} />
<button className='icon-refresh tour-item' onClick={bind(refresh())} /> <button className='icon-refresh tour-item' onClick={bind(refresh())} />
<button className='icon-select tour-item' onClick={bind(selectView('toggle'))} /> <button className='icon-select tour-item' onClick={this.selectView} />
<button className='icon-more tour-item' onClick={this.showMore.bind(this)} ref='more' /> <button className='icon-more tour-item' onClick={this.showMore.bind(this)} ref='more' />
</div> </div>
); );
@ -39,6 +40,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', {

View File

@ -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,28 @@ const entryMenu = {
blob blob
}); });
} }
},
{
name: 'Extract',
enabled() {
let active = store.getState().get('activeFile');
if (active) console.log(active[0].name);
return active && active[0].name.indexOf('.zip') > -1;
},
action() {
let active = store.getState().get('activeFile');
store.dispatch(decompress(active));
}
},
{
name: 'Archive',
action() {
let active = store.getState().get('activeFile');
store.dispatch(compress(active));
}
} }
] ]
}; };
@ -142,6 +165,14 @@ const moreMenu = {
}) })
} }
}, },
{
name: 'Archive',
action() {
let active = store.getState().get('activeFile');
store.dispatch(compress(active));
}
}
] ]
} }

View File

@ -1,13 +1,9 @@
import { ACTIVE_FILE, CHANGE_DIRECTORY } 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) {
return action.file; return action.file;
} }
if (action.type === CHANGE_DIRECTORY) {
return null;
}
return state; return state;
} }

View File

@ -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 { move, remove, sdcard, createFile, readFile, writeFile, createDirectory, getFile, copy, children } from 'api/files';
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());
@ -74,7 +75,7 @@ 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 remove(path, true);
})) }))
@ -82,6 +83,69 @@ export default function(state = [], action) {
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) {
console.log('addFile', file);
let path = normalize((file.path || '') + file.name);
let archivePath = path.slice(cwd.length);
// directory
if (!(file instanceof Blob)) {
let folder = archive.folder(file.name);
return children(path).then(files => {
return Promise.all(files.map(child => {
return addFile(child);
// return readFile(childPath).then(content => {
// let blob = new Blob([content]);
// folder.file(child.name, blob);
// });
}));
})
}
return readFile(path).then(content => {
archive.file(archivePath + '/' + file.name, content);
});
}))
all.then(() => {
let buffer = archive.generate({ type: 'nodebuffer' });
console.log(buffer);
let blob = new Blob([buffer], { type: 'application/zip' });
let cwd = store.getState().get('cwd');
let path = normalize(cwd + '/archive.zip');
return 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);
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 writeFile(filePath, blob);
}));
all.then(boundRefresh, reportError);
});
}
return state; return state;
} }