1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-04 00:20:07 +00:00

Optimized wiki code (#2566)

* optimized path component

* abstract component

* repair copy bug

* move copy&move&download to list-item

* add modal portal
This commit is contained in:
杨顺强
2018-11-27 14:47:19 +08:00
committed by Daniel Pan
parent 30fa874c9f
commit 5ca5fb9cee
15 changed files with 496 additions and 317 deletions

View File

@@ -0,0 +1,62 @@
import React from 'react';
import PropTypes from 'prop-types';
import { siteRoot, gettext } from '../../utils/constants';
const propTypes = {
repoName: PropTypes.string.isRequired,
currentPath: PropTypes.string.isRequired,
onPathClick: PropTypes.func.isRequired,
};
class DirPath extends React.Component {
onPathClick = (e) => {
let path = e.target.dataset.path;
this.props.onPathClick(path);
}
turnPathToLink = (path) => {
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
let pathList = path.split('/');
let nodePath = '';
let pathElem = pathList.map((item, index) => {
if (item === '') {
return;
}
if (index === (pathList.length - 1)) {
return (
<span key={index}><span className="path-split">/</span>{item}</span>
);
} else {
nodePath += '/' + item;
return (
<span key={index} >
<span className="path-split">/</span>
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
</span>
);
}
});
return pathElem;
}
render() {
let { currentPath, repoName } = this.props;
let pathElem = this.turnPathToLink(currentPath);
return (
<div className="path-containter">
<a href={siteRoot + '#common/'} className="normal">{gettext('Libraries')}</a>
<span className="path-split">/</span>
{currentPath === '/' ?
<span>{repoName}</span>:
<a className="path-link" data-path="/" onClick={this.onPathClick}>{repoName}</a>
}
{pathElem}
</div>
);
}
}
DirPath.propTypes = propTypes;
export default DirPath;

View File

@@ -7,10 +7,10 @@ import CreateTagDialog from '../dialog/create-tag-dialog';
import UpdateTagDialog from '../dialog/update-tag-dialog'; import UpdateTagDialog from '../dialog/update-tag-dialog';
const propTypes = { const propTypes = {
path: PropTypes.string.isRequired currentPath: PropTypes.string.isRequired
}; };
class PathToolbar extends React.Component { class DirTool extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -47,8 +47,9 @@ class PathToolbar extends React.Component {
} }
render() { render() {
let isFile = this.isMarkdownFile(this.props.path); let { currentPath } = this.props;
let name = Utils.getFileName(this.props.path); let isFile = this.isMarkdownFile(currentPath);
let name = Utils.getFileName(currentPath);
let trashUrl = siteRoot + 'repo/recycle/' + repoID + '/?referer=' + encodeURIComponent(location.href); let trashUrl = siteRoot + 'repo/recycle/' + repoID + '/?referer=' + encodeURIComponent(location.href);
let historyUrl = siteRoot + 'repo/history/' + repoID + '/?referer=' + encodeURIComponent(location.href); let historyUrl = siteRoot + 'repo/history/' + repoID + '/?referer=' + encodeURIComponent(location.href);
if ( (name === slug || name === '') && !isFile && permission) { if ( (name === slug || name === '') && !isFile && permission) {
@@ -89,7 +90,7 @@ class PathToolbar extends React.Component {
</ul> </ul>
); );
} else if (permission) { } else if (permission) {
historyUrl = siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + Utils.encodePath(this.props.path) + '&referer=' + encodeURIComponent(location.href); historyUrl = siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + Utils.encodePath(currentPath) + '&referer=' + encodeURIComponent(location.href);
return ( return (
<ul className="path-toolbar"> <ul className="path-toolbar">
<li className="toolbar-item"><a className="op-link sf2-icon-history" href={historyUrl} title={gettext('History')} aria-label={gettext('History')}></a></li> <li className="toolbar-item"><a className="op-link sf2-icon-history" href={historyUrl} title={gettext('History')} aria-label={gettext('History')}></a></li>
@@ -100,6 +101,6 @@ class PathToolbar extends React.Component {
} }
} }
PathToolbar.propTypes = propTypes; DirTool.propTypes = propTypes;
export default PathToolbar; export default DirTool;

View File

@@ -0,0 +1,30 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import DirPath from './dir-path';
import DirTool from './dir-tool';
const propTypes = {
repoName: PropTypes.string.isRequired,
currentPath: PropTypes.string.isRequired,
onPathClick: PropTypes.func.isRequired,
};
class CurDirPath extends React.Component {
render() {
return (
<Fragment>
<DirPath
repoName={this.props.repoName}
currentPath={this.props.currentPath}
onPathClick={this.props.onPathClick}
/>
<DirTool currentPath={this.props.currentPath} />
</Fragment>
);
}
}
CurDirPath.propTypes = propTypes;
export default CurDirPath;

View File

@@ -7,12 +7,11 @@ import FileChooser from '../file-chooser/file-chooser';
const propTypes = { const propTypes = {
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
direntPath: PropTypes.string,
dirent: PropTypes.object, dirent: PropTypes.object,
selectedDirentList: PropTypes.array,
isMutipleOperation: PropTypes.bool.isRequired, isMutipleOperation: PropTypes.bool.isRequired,
selectedDirentList: PropTypes.array.isRequired, onItemCopy: PropTypes.func,
onItemCopy: PropTypes.func.isRequired, onItemsCopy: PropTypes.func,
onItemsCopy: PropTypes.func.isRequired,
onCancelCopy: PropTypes.func.isRequired, onCancelCopy: PropTypes.func.isRequired,
}; };
@@ -47,7 +46,7 @@ class CopyDirent extends React.Component {
let { repo, selectedPath } = this.state; let { repo, selectedPath } = this.state;
let message = gettext('Invalid destination path'); let message = gettext('Invalid destination path');
if (!repo || (repo.repo_id === repoID) && selectedPath === '') { if (!repo || selectedPath === '') {
this.setState({errMessage: message}); this.setState({errMessage: message});
return; return;
} }
@@ -60,13 +59,13 @@ class CopyDirent extends React.Component {
}); });
// copy dirents to one of them. eg: A/B, A/C -> A/B // copy dirents to one of them. eg: A/B, A/C -> A/B
if (direntPaths.some(direntPath => { return direntPath === selectedPath})) { if (direntPaths.some(direntPath => { return direntPath === selectedPath;})) {
this.setState({errMessage: message}); this.setState({errMessage: message});
return; return;
} }
// copy dirents to current path // copy dirents to current path
if (selectedPath && selectedPath === this.props.path) { if (selectedPath && selectedPath === this.props.path && repo.repo_id === repoID) {
this.setState({errMessage: message}); this.setState({errMessage: message});
return; return;
} }
@@ -79,7 +78,7 @@ class CopyDirent extends React.Component {
copyDirentPath = direntPath; copyDirentPath = direntPath;
} }
return flag; return flag;
}) });
if (isChildPath) { if (isChildPath) {
message = gettext('Can not move directory %(src)s to its subdirectory %(des)s'); message = gettext('Can not move directory %(src)s to its subdirectory %(des)s');
@@ -94,8 +93,8 @@ class CopyDirent extends React.Component {
} }
copyItem = () => { copyItem = () => {
let { direntPath } = this.props;
let { repo, selectedPath } = this.state; let { repo, selectedPath } = this.state;
let direntPath = Utils.joinPath(this.props.path, this.props.dirent.name);
let message = 'Invalid destination path'; let message = 'Invalid destination path';
if (!repo || (repo.repo_id === repoID && selectedPath === '')) { if (!repo || (repo.repo_id === repoID && selectedPath === '')) {
@@ -153,7 +152,7 @@ class CopyDirent extends React.Component {
if (!this.props.isMutipleOperation) { if (!this.props.isMutipleOperation) {
title = title.replace('{placeholder}', '<span class="sf-font">' + Utils.HTMLescape(this.props.dirent.name) + '</span>'); title = title.replace('{placeholder}', '<span class="sf-font">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
} else { } else {
title = gettext("Copy selected item(s) to:"); title = gettext('Copy selected item(s) to:');
} }
return ( return (
<Modal isOpen={true} toggle={this.toggle}> <Modal isOpen={true} toggle={this.toggle}>

View File

@@ -7,12 +7,11 @@ import FileChooser from '../file-chooser/file-chooser';
const propTypes = { const propTypes = {
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
direntPath: PropTypes.string,
dirent: PropTypes.object, dirent: PropTypes.object,
selectedDirentList: PropTypes.array,
isMutipleOperation: PropTypes.bool.isRequired, isMutipleOperation: PropTypes.bool.isRequired,
selectedDirentList: PropTypes.array.isRequired, onItemMove: PropTypes.func,
onItemMove: PropTypes.func.isRequired, onItemsMove: PropTypes.func,
onItemsMove: PropTypes.func.isRequired,
onCancelMove: PropTypes.func.isRequired, onCancelMove: PropTypes.func.isRequired,
}; };
@@ -47,7 +46,7 @@ class MoveDirent extends React.Component {
let { repo, selectedPath } = this.state; let { repo, selectedPath } = this.state;
let message = gettext('Invalid destination path'); let message = gettext('Invalid destination path');
if (!repo || (repo.repo_id === repoID) && selectedPath === '') { if (!repo || selectedPath === '') {
this.setState({errMessage: message}); this.setState({errMessage: message});
return; return;
} }
@@ -60,13 +59,13 @@ class MoveDirent extends React.Component {
}); });
// copy dirents to one of them. eg: A/B, A/C -> A/B // copy dirents to one of them. eg: A/B, A/C -> A/B
if (direntPaths.some(direntPath => { return direntPath === selectedPath})) { if (direntPaths.some(direntPath => { return direntPath === selectedPath;})) {
this.setState({errMessage: message}); this.setState({errMessage: message});
return; return;
} }
// copy dirents to current path // copy dirents to current path
if (selectedPath && selectedPath === this.props.path) { if (selectedPath && selectedPath === this.props.path && (repo.repo_id === repoID)) {
this.setState({errMessage: message}); this.setState({errMessage: message});
return; return;
} }
@@ -79,7 +78,7 @@ class MoveDirent extends React.Component {
moveDirentPath = direntPath; moveDirentPath = direntPath;
} }
return flag; return flag;
}) });
if (isChildPath) { if (isChildPath) {
message = gettext('Can not move directory %(src)s to its subdirectory %(des)s'); message = gettext('Can not move directory %(src)s to its subdirectory %(des)s');
@@ -94,8 +93,8 @@ class MoveDirent extends React.Component {
} }
moveItem = () => { moveItem = () => {
let { direntPath } = this.props;
let { repo, selectedPath } = this.state; let { repo, selectedPath } = this.state;
let direntPath = Utils.joinPath(this.props.path, this.props.dirent.name);
let message = gettext('Invalid destination path'); let message = gettext('Invalid destination path');
if (!repo || (repo.repo_id === repoID && selectedPath === '')) { if (!repo || (repo.repo_id === repoID && selectedPath === '')) {
@@ -153,7 +152,7 @@ class MoveDirent extends React.Component {
if (!this.props.isMutipleOperation) { if (!this.props.isMutipleOperation) {
title = title.replace('{placeholder}', '<span class="sf-font">' + Utils.HTMLescape(this.props.dirent.name) + '</span>'); title = title.replace('{placeholder}', '<span class="sf-font">' + Utils.HTMLescape(this.props.dirent.name) + '</span>');
} else { } else {
title = gettext("Move selected item(s) to:"); title = gettext('Move selected item(s) to:');
} }
return ( return (
<Modal isOpen={true} toggle={this.toggle}> <Modal isOpen={true} toggle={this.toggle}>

View File

@@ -84,6 +84,7 @@ class DirentDetail extends React.Component {
<div className="dirent-table-container"> <div className="dirent-table-container">
<DetailListView <DetailListView
repo={this.state.repo} repo={this.state.repo}
dirent={this.props.dirent}
direntPath={this.props.direntPath} direntPath={this.props.direntPath}
direntType={this.state.direntType} direntType={this.state.direntType}
direntDetail={this.state.direntDetail} direntDetail={this.state.direntDetail}

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { serviceUrl, gettext, repoID } from '../../utils/constants'; import { serviceUrl, gettext, repoID } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
@@ -6,6 +6,10 @@ import URLDecorator from '../../utils/url-decorator';
import Toast from '../toast'; import Toast from '../toast';
import DirentMenu from './dirent-menu'; import DirentMenu from './dirent-menu';
import DirentRename from './dirent-rename'; import DirentRename from './dirent-rename';
import ModalPortal from '../modal-portal';
import ZipDownloadDialog from '../dialog/zip-download-dialog';
import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
const propTypes = { const propTypes = {
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
@@ -18,9 +22,8 @@ const propTypes = {
onItemSelected: PropTypes.func.isRequired, onItemSelected: PropTypes.func.isRequired,
onItemDelete: PropTypes.func.isRequired, onItemDelete: PropTypes.func.isRequired,
onItemRename: PropTypes.func.isRequired, onItemRename: PropTypes.func.isRequired,
onItemDownload: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired,
onItemMoveToggle: PropTypes.func.isRequired, onItemCopy: PropTypes.func.isRequired,
onItemCopyToggle: PropTypes.func.isRequired,
onItemDetails: PropTypes.func.isRequired, onItemDetails: PropTypes.func.isRequired,
updateDirent: PropTypes.func.isRequired, updateDirent: PropTypes.func.isRequired,
currentRepo: PropTypes.object, currentRepo: PropTypes.object,
@@ -36,7 +39,13 @@ class DirentListItem extends React.Component {
highlight: false, highlight: false,
isItemMenuShow: false, isItemMenuShow: false,
menuPosition: {top: 0, left: 0 }, menuPosition: {top: 0, left: 0 },
progress: 0,
isProgressDialogShow: false,
isMoveDialogShow: false,
isCopyDialogShow: false,
isMutipleOperation: false,
}; };
this.zipToken = null;
} }
componentDidMount() { componentDidMount() {
@@ -131,12 +140,6 @@ class DirentListItem extends React.Component {
this.props.onItemClick(direntPath); this.props.onItemClick(direntPath);
} }
onItemDownload = (e) => {
e.nativeEvent.stopImmediatePropagation();
let direntPath = this.getDirentPath(this.props.dirent);
this.props.onItemDownload(this.props.dirent, direntPath);
}
onItemDelete = (e) => { onItemDelete = (e) => {
e.nativeEvent.stopImmediatePropagation(); //for document event e.nativeEvent.stopImmediatePropagation(); //for document event
this.props.onItemDelete(this.props.dirent); this.props.onItemDelete(this.props.dirent);
@@ -224,14 +227,12 @@ class DirentListItem extends React.Component {
} }
onItemMoveToggle = () => { onItemMoveToggle = () => {
let direntPath = this.getDirentPath(this.props.dirent); this.setState({isMoveDialogShow: !this.state.isMoveDialogShow});
this.props.onItemMoveToggle(this.props.dirent, direntPath);
this.onItemMenuHide(); this.onItemMenuHide();
} }
onItemCopyToggle = () => { onItemCopyToggle = () => {
let direntPath = this.getDirentPath(this.props.dirent); this.setState({isCopyDialogShow: !this.state.isCopyDialogShow});
this.props.onItemCopyToggle(this.props.dirent, direntPath);
this.onItemMenuHide(); this.onItemMenuHide();
} }
@@ -300,6 +301,58 @@ class DirentListItem extends React.Component {
this.onItemMenuHide(); this.onItemMenuHide();
} }
onItemDownload = (e) => {
e.nativeEvent.stopImmediatePropagation();
let dirent = this.props.dirent;
let direntPath = this.getDirentPath(dirent);
if (dirent.type === 'dir') {
this.setState({isProgressDialogShow: true, progress: 0});
seafileAPI.zipDownload(repoID, this.props.path, dirent.name).then(res => {
this.zipToken = res.data['zip_token'];
this.addDownloadAnimation();
this.interval = setInterval(this.addDownloadAnimation, 1000);
}).catch(() => {
clearInterval(this.interval);
// Toast.error(gettext(''));
//todo;
});
} else {
let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: direntPath});
location.href = url;
}
}
addDownloadAnimation = () => {
let _this = this;
let token = this.zipToken;
seafileAPI.queryZipProgress(token).then(res => {
let data = res.data;
let progress = data.total === 0 ? 100 : (data.zipped / data.total * 100).toFixed(0);
this.setState({progress: parseInt(progress)});
if (data['total'] === data['zipped']) {
this.setState({
progress: 100
});
clearInterval(this.interval);
location.href = URLDecorator.getUrl({type: 'download_dir_zip_url', token: token});
setTimeout(function() {
_this.setState({isProgressDialogShow: false});
}, 500);
}
});
}
onCancelDownload = () => {
let zipToken = this.zipToken;
seafileAPI.cancelZipTask(zipToken).then(res => {
this.setState({
isProgressDialogShow: false,
});
});
}
getDirentPath = (dirent) => { getDirentPath = (dirent) => {
let path = this.props.path; let path = this.props.path;
return path === '/' ? path + dirent.name : path + '/' + dirent.name; return path === '/' ? path + dirent.name : path + '/' + dirent.name;
@@ -308,69 +361,101 @@ class DirentListItem extends React.Component {
render() { render() {
let { dirent } = this.props; let { dirent } = this.props;
return ( return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}> <Fragment>
<td className="select"> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
<input type="checkbox" className="vam" onChange={this.onItemSelected} checked={dirent.isSelected}/> <td className="select">
</td> <input type="checkbox" className="vam" onChange={this.onItemSelected} checked={dirent.isSelected}/>
<td className="star" onClick={this.onItemStarred}> </td>
{dirent.starred !== undefined && !dirent.starred && <i className="far fa-star empty"></i>} <td className="star" onClick={this.onItemStarred}>
{dirent.starred !== undefined && dirent.starred && <i className="fas fa-star"></i>} {dirent.starred !== undefined && !dirent.starred && <i className="far fa-star empty"></i>}
</td> {dirent.starred !== undefined && dirent.starred && <i className="fas fa-star"></i>}
<td className="icon"> </td>
<div className="dir-icon"> <td className="icon">
<img src={dirent.type === 'dir' ? serviceUrl + '/media/img/folder-192.png' : serviceUrl + '/media/img/file/192/txt.png'} alt={gettext('file icon')}></img> <div className="dir-icon">
{dirent.is_locked && <img className="locked" src={serviceUrl + '/media/img/file-locked-32.png'} alt={gettext('locked')}></img>} <img src={dirent.type === 'dir' ? serviceUrl + '/media/img/folder-192.png' : serviceUrl + '/media/img/file/192/txt.png'} alt={gettext('file icon')}></img>
</div> {dirent.is_locked && <img className="locked" src={serviceUrl + '/media/img/file-locked-32.png'} alt={gettext('locked')}></img>}
</td>
<td className="name a-simulate">
{this.state.isRenameing ?
<DirentRename dirent={dirent} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
<span onClick={this.onItemClick}>{dirent.name}</span>
}
</td>
<td>
<div className="dirent-item tag-list tag-list-stacked ">
{ dirent.type !== 'dir' && dirent.file_tags.map((fileTag) => {
return (
<span className={`file-tag bg-${fileTag.color}`} key={fileTag.id} title={fileTag.name}></span>
);
})}
</div>
</td>
<td className="operation">
{
this.state.isOperationShow &&
<div className="operations">
<ul className="operation-group">
<li className="operation-group-item">
<i className="sf2-icon-download" title={gettext('Download')} onClick={this.onItemDownload}></i>
</li>
<li className="operation-group-item">
<i className="sf2-icon-share" title={gettext('Share')} onClick={this.onItemShare}></i>
</li>
<li className="operation-group-item">
<i className="sf2-icon-delete" title={gettext('Delete')} onClick={this.onItemDelete}></i>
</li>
<li className="operation-group-item">
<i className="sf2-icon-caret-down sf-dropdown-toggle" title={gettext('More Operation')} onClick={this.onItemMenuToggle}></i>
</li>
</ul>
{
this.state.isItemMenuShow &&
<DirentMenu
dirent={this.props.dirent}
menuPosition={this.state.menuPosition}
onMenuItemClick={this.onMenuItemClick}
currentRepo={this.props.currentRepo}
isRepoOwner={this.props.isRepoOwner}
/>
}
</div> </div>
} </td>
</td> <td className="name a-simulate">
<td className="file-size">{dirent.size && dirent.size}</td> {this.state.isRenameing ?
<td className="last-update" dangerouslySetInnerHTML={{__html: dirent.mtime}}></td> <DirentRename dirent={dirent} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
</tr> <span onClick={this.onItemClick}>{dirent.name}</span>
}
</td>
<td>
<div className="dirent-item tag-list tag-list-stacked ">
{ dirent.type !== 'dir' && dirent.file_tags.map((fileTag) => {
return (
<span className={`file-tag bg-${fileTag.color}`} key={fileTag.id} title={fileTag.name}></span>
);
})}
</div>
</td>
<td className="operation">
{
this.state.isOperationShow &&
<div className="operations">
<ul className="operation-group">
<li className="operation-group-item">
<i className="sf2-icon-download" title={gettext('Download')} onClick={this.onItemDownload}></i>
</li>
<li className="operation-group-item">
<i className="sf2-icon-share" title={gettext('Share')} onClick={this.onItemShare}></i>
</li>
<li className="operation-group-item">
<i className="sf2-icon-delete" title={gettext('Delete')} onClick={this.onItemDelete}></i>
</li>
<li className="operation-group-item">
<i className="sf2-icon-caret-down sf-dropdown-toggle" title={gettext('More Operation')} onClick={this.onItemMenuToggle}></i>
</li>
</ul>
{
this.state.isItemMenuShow &&
<DirentMenu
dirent={this.props.dirent}
menuPosition={this.state.menuPosition}
onMenuItemClick={this.onMenuItemClick}
currentRepo={this.props.currentRepo}
isRepoOwner={this.props.isRepoOwner}
/>
}
</div>
}
</td>
<td className="file-size">{dirent.size && dirent.size}</td>
<td className="last-update" dangerouslySetInnerHTML={{__html: dirent.mtime}}></td>
</tr>
{this.state.isMoveDialogShow &&
<ModalPortal>
<MoveDirentDialog
path={this.props.path}
dirent={this.props.dirent}
isMutipleOperation={this.state.isMutipleOperation}
onItemMove={this.props.onItemMove}
onCancelMove={this.onItemMoveToggle}
/>
</ModalPortal>
}
{this.state.isCopyDialogShow &&
<ModalPortal>
<CopyDirentDialog
path={this.props.path}
dirent={this.props.dirent}
isMutipleOperation={this.state.isMutipleOperation}
onItemCopy={this.props.onItemCopy}
onCancelCopy={this.onItemCopyToggle}
/>
</ModalPortal>
}
{this.state.isProgressDialogShow &&
<ModalPortal>
<ZipDownloadDialog
progress={this.state.progress}
onCancelDownload={this.onCancelDownload}
/>
</ModalPortal>
}
</Fragment>
); );
} }
} }

View File

@@ -12,8 +12,8 @@ const propTypes = {
onItemSelected: PropTypes.func.isRequired, onItemSelected: PropTypes.func.isRequired,
onItemRename: PropTypes.func.isRequired, onItemRename: PropTypes.func.isRequired,
onItemClick: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired,
onItemMoveToggle: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired,
onItemCopyToggle: PropTypes.func.isRequired, onItemCopy: PropTypes.func.isRequired,
onItemDetails: PropTypes.func.isRequired, onItemDetails: PropTypes.func.isRequired,
updateDirent: PropTypes.func.isRequired, updateDirent: PropTypes.func.isRequired,
isDirentListLoading: PropTypes.bool.isRequired, isDirentListLoading: PropTypes.bool.isRequired,
@@ -85,9 +85,8 @@ class DirentListView extends React.Component {
onItemSelected={this.props.onItemSelected} onItemSelected={this.props.onItemSelected}
onItemDelete={this.props.onItemDelete} onItemDelete={this.props.onItemDelete}
onItemRename={this.props.onItemRename} onItemRename={this.props.onItemRename}
onItemMoveToggle={this.props.onItemMoveToggle} onItemMove={this.props.onItemMove}
onItemCopyToggle={this.props.onItemCopyToggle} onItemCopy={this.props.onItemCopy}
onItemDownload={this.props.onItemDownload}
updateDirent={this.props.updateDirent} updateDirent={this.props.updateDirent}
isItemFreezed={this.state.isItemFreezed} isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}

View File

@@ -16,7 +16,7 @@ const MainContentWrapper = (WrapperedComponent) => {
</Fragment> </Fragment>
); );
} }
} };
} };
export default MainContentWrapper; export default MainContentWrapper;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
const propTypes = {
children: PropTypes.object.isRequired,
};
const modalRoot = document.getElementById('modal-wrapper');
class ModalPortal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
ModalPortal.propTypes = propTypes;
export default ModalPortal;

View File

@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
};
class DirOperationToolbar extends React.Component {
render() {
return (
<div></div>
);
}
}
DirOperationToolbar.propTypes = propTypes;
export default DirOperationToolbar;

View File

@@ -0,0 +1,132 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
import ZipDownloadDialog from '../dialog/zip-download-dialog';
import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
const propTypes = {
path: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
selectedDirentList: PropTypes.array.isRequired,
onItemsMove: PropTypes.func.isRequired,
onItemsCopy: PropTypes.func.isRequired,
onItemsDelete: PropTypes.func.isRequired,
};
class MutipleDirOperationToolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
progress: 0,
isProgressDialogShow: false,
isMoveDialogShow: false,
isCopyDialogShow: false,
isMutipleOperation: true,
};
this.zipToken = null;
}
onMoveToggle = () => {
this.setState({isMoveDialogShow: !this.state.isMoveDialogShow});
}
onCopyToggle = () => {
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow});
}
onItemsDelete = () => {
this.props.onItemsDelete();
}
onItemsDownload = () => {
let { path, repoID, selectedDirentList } = this.props;
if (selectedDirentList.length) {
if (selectedDirentList.length === 1 && !selectedDirentList[0].isDir()) {
let direntPath = Utils.joinPath(path, selectedDirentList[0].name);
let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: direntPath});
location.href= url;
return;
}
let selectedDirentNames = selectedDirentList.map(dirent => {
return dirent.name;
});
this.setState({isProgressDialogShow: true, progress: 0});
seafileAPI.zipDownload(repoID, path, selectedDirentNames).then(res => {
this.zipToken = res.data['zip_token'];
this.addDownloadAnimation();
this.interval = setInterval(this.addDownloadAnimation, 1000);
});
}
}
addDownloadAnimation = () => {
let _this = this;
let token = this.zipToken;
seafileAPI.queryZipProgress(token).then(res => {
let data = res.data;
let progress = data.total === 0 ? 100 : (data.zipped / data.total * 100).toFixed(0);
this.setState({progress: parseInt(progress)});
if (data['total'] === data['zipped']) {
this.setState({
progress: 100
});
clearInterval(this.interval);
location.href = URLDecorator.getUrl({type: 'download_dir_zip_url', token: token});
setTimeout(function() {
_this.setState({isProgressDialogShow: false});
}, 500);
}
});
}
onCancelDownload = () => {
seafileAPI.cancelZipTask(this.zipToken).then(() => {
this.setState({isProgressDialogShow: false});
});
}
render() {
return (
<Fragment>
<div className="operation mutiple-dirents-operation">
<button className="btn btn-secondary operation-item op-icon sf2-icon-move" title={gettext('Move')} onClick={this.onMoveToggle}></button>
<button className="btn btn-secondary operation-item op-icon sf2-icon-copy" title={gettext('Copy')} onClick={this.onCopyToggle}></button>
<button className="btn btn-secondary operation-item op-icon sf2-icon-delete" title={gettext('Delete')} onClick={this.props.onItemsDelete}></button>
<button className="btn btn-secondary operation-item op-icon sf2-icon-download" title={gettext('Download')} onClick={this.onItemsDownload}></button>
</div>
{this.state.isMoveDialogShow &&
<MoveDirentDialog
path={this.props.path}
isMutipleOperation={this.state.isMutipleOperation}
selectedDirentList={this.props.selectedDirentList}
onItemsMove={this.props.onItemsMove}
onCancelMove={this.onMoveToggle}
/>
}
{this.state.isCopyDialogShow &&
<CopyDirentDialog
path={this.props.path}
selectedDirentList={this.props.selectedDirentList}
isMutipleOperation={this.state.isMutipleOperation}
onItemsCopy={this.props.onItemsCopy}
onCancelCopy={this.onCopyToggle}
/>
}
{this.state.isProgressDialogShow &&
<ZipDownloadDialog progress={this.state.progress} onCancelDownload={this.onCancelDownload} />
}
</Fragment>
);
}
}
MutipleDirOperationToolbar.propTypes = propTypes;
export default MutipleDirOperationToolbar;

View File

@@ -1,20 +1,17 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { gettext, repoID, serviceUrl, slug, siteRoot } from '../../utils/constants'; import { gettext, repoID, serviceUrl, slug } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import URLDecorator from '../../utils/url-decorator';
import Repo from '../../models/repo'; import Repo from '../../models/repo';
import CommonToolbar from '../../components/toolbar/common-toolbar'; import CommonToolbar from '../../components/toolbar/common-toolbar';
import PathToolbar from '../../components/toolbar/path-toolbar'; import MutipleDirentsOperationToolbar from '../../components/toolbar/mutilple-dir-operation-toolbar';
import CurDirPath from '../../components/cur-dir-path';
import MarkdownViewer from '../../components/markdown-viewer'; import MarkdownViewer from '../../components/markdown-viewer';
import DirentListView from '../../components/dirent-list-view/dirent-list-view'; import DirentListView from '../../components/dirent-list-view/dirent-list-view';
import DirentDetail from '../../components/dirent-detail/dirent-details'; import DirentDetail from '../../components/dirent-detail/dirent-details';
import CreateFolder from '../../components/dialog/create-folder-dialog'; import CreateFolder from '../../components/dialog/create-folder-dialog';
import CreateFile from '../../components/dialog/create-file-dialog'; import CreateFile from '../../components/dialog/create-file-dialog';
import ZipDownloadDialog from '../../components/dialog/zip-download-dialog';
import MoveDirentDialog from '../../components/dialog/move-dirent-dialog';
import CopyDirentDialog from '../../components/dialog/copy-dirent-dialog';
import FileUploader from '../../components/file-uploader/file-uploader'; import FileUploader from '../../components/file-uploader/file-uploader';
const propTypes = { const propTypes = {
@@ -69,13 +66,7 @@ class MainPanel extends Component {
direntPath: '', direntPath: '',
currentRepo: null, currentRepo: null,
isRepoOwner: false, isRepoOwner: false,
progress: 0,
isProgressDialogShow: false,
isMoveDialogShow: false,
isCopyDialogShow: false,
isMutipleOperation: false,
}; };
this.zip_token = null;
} }
componentDidMount() { componentDidMount() {
@@ -110,8 +101,8 @@ class MainPanel extends Component {
this.props.onSideNavMenuClick(); this.props.onSideNavMenuClick();
} }
onMainNavBarClick = (e) => { onMainNavBarClick = (path) => {
this.props.onMainNavBarClick(e.target.dataset.path); this.props.onMainNavBarClick(path);
} }
onEditClick = (e) => { onEditClick = (e) => {
@@ -222,149 +213,7 @@ class MainPanel extends Component {
} }
onSelectedMoveToggle = () => {
this.setState({
isMutipleOperation: true,
isMoveDialogShow: true,
currentDirent: null,
direntPath: '',
});
}
onSelectedCopyToggle = () => {
this.setState({
isMutipleOperation: true,
isCopyDialogShow: true,
currentDirent: null,
direntPath: '',
});
}
onItemMoveToggle = (dirent, direntPath) => {
this.setState({
isMutipleOperation: false,
isMoveDialogShow: true,
currentDirent: dirent,
direntPath: direntPath,
});
}
onItemCopyToggle = (dirent, direntPath) => {
this.setState({
isMutipleOperation: false,
isCopyDialogShow: true,
currentDirent: dirent,
direntPath: direntPath
});
}
onCancelMove = () => {
this.setState({isMoveDialogShow: false});
}
onCancelCopy = () => {
this.setState({isCopyDialogShow: false});
}
onItemsDownload = () => {
let selectedDirentList = this.props.selectedDirentList;
if (selectedDirentList.length) {
if (selectedDirentList.length === 1 && !selectedDirentList[0].isDir()) {
let direntPath = Utils.joinPath(this.props.path, selectedDirentList[0].name);
let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: direntPath});
location.href= url;
return;
}
let selectedDirentNames = selectedDirentList.map(dirent => {
return dirent.name;
});
this.setState({isProgressDialogShow: true, progress: 0});
seafileAPI.zipDownload(repoID, this.props.path, selectedDirentNames).then(res => {
this.zip_token = res.data['zip_token'];
this.addDownloadAnimation();
this.interval = setInterval(this.addDownloadAnimation, 1000);
});
}
}
onItemDownload = (dirent, direntPath) => {
if (dirent.type === 'dir') {
this.setState({isProgressDialogShow: true, progress: 0});
seafileAPI.zipDownload(repoID, this.props.path, dirent.name).then(res => {
this.zip_token = res.data['zip_token'];
this.addDownloadAnimation();
this.interval = setInterval(this.addDownloadAnimation, 1000);
}).catch(() => {
clearInterval(this.interval);
// Toast.error(gettext(''));
//todo;
});
} else {
let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: direntPath});
location.href = url;
}
}
addDownloadAnimation = () => {
let _this = this;
let token = this.zip_token;
seafileAPI.queryZipProgress(token).then(res => {
let data = res.data;
let progress = data.total === 0 ? 100 : (data.zipped / data.total * 100).toFixed(0);
this.setState({progress: parseInt(progress)});
if (data['total'] === data['zipped']) {
this.setState({
progress: 100
});
clearInterval(this.interval);
location.href = URLDecorator.getUrl({type: 'download_dir_zip_url', token: token});
setTimeout(function() {
_this.setState({isProgressDialogShow: false});
}, 500);
}
});
}
onCancelDownload = () => {
let zip_token = this.zip_token;
seafileAPI.cancelZipTask(zip_token).then(res => {
this.setState({
isProgressDialogShow: false,
});
});
}
render() { render() {
let path = this.props.path;
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
let pathList = path.split('/');
let nodePath = '';
let pathElem = pathList.map((item, index) => {
if (item === '') {
return;
}
if (index === (pathList.length - 1)) {
return (
<span key={index}><span className="path-split">/</span>{item}</span>
);
} else {
nodePath += '/' + item;
return (
<span key={index} >
<span className="path-split">/</span>
<a
className="path-link"
data-path={nodePath}
onClick={this.onMainNavBarClick}>
{item}
</a>
</span>
);
}
});
return ( return (
<div className="main-panel wiki-main-panel o-hidden"> <div className="main-panel wiki-main-panel o-hidden">
<div className="main-panel-top panel-top"> <div className="main-panel-top panel-top">
@@ -372,12 +221,14 @@ class MainPanel extends Component {
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title={gettext('Side Nav Menu')} onClick={this.onSideNavMenuClick}></span> <span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title={gettext('Side Nav Menu')} onClick={this.onSideNavMenuClick}></span>
<div className="dir-operation"> <div className="dir-operation">
{this.props.isDirentSelected && {this.props.isDirentSelected &&
<div className="operation mutiple-dirents-operation"> <MutipleDirentsOperationToolbar
<button className="btn btn-secondary operation-item op-icon sf2-icon-move" title={gettext('Move')} onClick={this.onSelectedMoveToggle}></button> repoID={repoID}
<button className="btn btn-secondary operation-item op-icon sf2-icon-copy" title={gettext('Copy')} onClick={this.onSelectedCopyToggle}></button> path={this.props.path}
<button className="btn btn-secondary operation-item op-icon sf2-icon-delete" title={gettext('Delete')} onClick={this.props.onItemsDelete}></button> selectedDirentList={this.props.selectedDirentList}
<button className="btn btn-secondary operation-item op-icon sf2-icon-download" title={gettext('Download')} onClick={this.onItemsDownload}></button> onItemsMove={this.props.onItemsMove}
</div> onItemsCopy={this.props.onItemsCopy}
onItemsDelete={this.props.onItemsDelete}
/>
} }
{!this.props.isDirentSelected && {!this.props.isDirentSelected &&
<div className="operation"> <div className="operation">
@@ -427,16 +278,7 @@ class MainPanel extends Component {
<div className="main-panel-center flex-direction-row"> <div className="main-panel-center flex-direction-row">
<div className="cur-view-container"> <div className="cur-view-container">
<div className="cur-view-path"> <div className="cur-view-path">
<div className="path-containter"> <CurDirPath currentPath={this.props.path} repoName={slug} onPathClick={this.onMainNavBarClick}/>
<a href={siteRoot + '#common/'} className="normal">{gettext('Libraries')}</a>
<span className="path-split">/</span>
{this.props.path === '/' ?
<span>{slug}</span> :
<a className="path-link" data-path="/" onClick={this.onMainNavBarClick}>{slug}</a>
}
{pathElem}
</div>
<PathToolbar path={this.props.path}/>
</div> </div>
<div className="cur-view-content"> <div className="cur-view-content">
{ !this.props.pathExist ? { !this.props.pathExist ?
@@ -457,9 +299,8 @@ class MainPanel extends Component {
onItemClick={this.props.onItemClick} onItemClick={this.props.onItemClick}
onItemDelete={this.props.onItemDelete} onItemDelete={this.props.onItemDelete}
onItemRename={this.props.onItemRename} onItemRename={this.props.onItemRename}
onItemDownload={this.onItemDownload} onItemMove={this.props.onItemMove}
onItemMoveToggle={this.onItemMoveToggle} onItemCopy={this.props.onItemCopy}
onItemCopyToggle={this.onItemCopyToggle}
onItemDetails={this.onItemDetails} onItemDetails={this.onItemDetails}
isDirentListLoading={this.props.isDirentListLoading} isDirentListLoading={this.props.isDirentListLoading}
updateDirent={this.props.updateDirent} updateDirent={this.props.updateDirent}
@@ -508,34 +349,6 @@ class MainPanel extends Component {
onAddFolder={this.onAddFolder} onAddFolder={this.onAddFolder}
/> />
} }
{this.state.isMoveDialogShow &&
<MoveDirentDialog
path={this.props.path}
isMutipleOperation={this.state.isMutipleOperation}
selectedDirentList={this.props.selectedDirentList}
dirent={this.state.currentDirent}
direntPath={this.state.direntPath}
onItemMove={this.props.onItemMove}
onItemsMove={this.props.onItemsMove}
onCancelMove={this.onCancelMove}
/>
}
{this.state.isCopyDialogShow &&
<CopyDirentDialog
path={this.props.path}
isMutipleOperation={this.state.isMutipleOperation}
selectedDirentList={this.props.selectedDirentList}
dirent={this.state.currentDirent}
direntPath={this.state.direntPath}
onItemCopy={this.props.onItemCopy}
onItemsCopy={this.props.onItemsCopy}
onCancelCopy={this.onCancelCopy}
/>
}
{this.state.isProgressDialogShow &&
<ZipDownloadDialog progress={this.state.progress} onCancelDownload={this.onCancelDownload}
/>
}
</div> </div>
); );
} }

View File

@@ -381,7 +381,7 @@ class Wiki extends Component {
isAllDirentSelected: false, isAllDirentSelected: false,
direntList: direntList, direntList: direntList,
selectedDirentList: [] selectedDirentList: []
}) });
} }
} }
@@ -500,7 +500,7 @@ class Wiki extends Component {
let message = gettext('Failed to move %(name)s'); let message = gettext('Failed to move %(name)s');
message = message.replace('%(name)s', dirNames); message = message.replace('%(name)s', dirNames);
Toast.error(message); Toast.error(message);
}) });
} }
onCopyItems = (destRepo, destDirentPath) => { onCopyItems = (destRepo, destDirentPath) => {
@@ -518,7 +518,7 @@ class Wiki extends Component {
let message = gettext('Failed to copy %(name)s'); let message = gettext('Failed to copy %(name)s');
message = message.replace('%(name)s', dirNames); message = message.replace('%(name)s', dirNames);
Toast.error(message); Toast.error(message);
}) });
} }
onDeleteItems = () => { onDeleteItems = () => {
@@ -635,7 +635,10 @@ class Wiki extends Component {
this.setState({treeData: tree}); this.setState({treeData: tree});
} }
copyTreeNode = (nodePath, copyToPath) => { copyTreeNode = (nodePath, copyToPath, destRepo) => {
if (repoID !== destRepo.repo_id) {
return;
}
let tree = this.state.treeData.clone(); let tree = this.state.treeData.clone();
tree.moveNodeByPath(nodePath, copyToPath, false); tree.moveNodeByPath(nodePath, copyToPath, false);
this.setState({treeData: tree}); this.setState({treeData: tree});

View File

@@ -19,6 +19,7 @@
<body> <body>
<div id="wrapper" class="{{ LANGUAGE_CODE }}"></div> <div id="wrapper" class="{{ LANGUAGE_CODE }}"></div>
<div id="modal-wrapper" class="{{ LANGUAGE_CODE }}"></div>
<script type="text/javascript"> <script type="text/javascript">
window.app = { window.app = {