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:
62
frontend/src/components/cur-dir-path/dir-path.js
Normal file
62
frontend/src/components/cur-dir-path/dir-path.js
Normal 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;
|
@@ -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;
|
30
frontend/src/components/cur-dir-path/index.js
Normal file
30
frontend/src/components/cur-dir-path/index.js
Normal 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;
|
@@ -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}>
|
||||||
|
@@ -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}>
|
||||||
|
@@ -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}
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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}
|
||||||
|
@@ -16,7 +16,7 @@ const MainContentWrapper = (WrapperedComponent) => {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export default MainContentWrapper;
|
export default MainContentWrapper;
|
||||||
|
35
frontend/src/components/modal-portal.js
Normal file
35
frontend/src/components/modal-portal.js
Normal 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;
|
19
frontend/src/components/toolbar/dir-operation-toolbar.js
Normal file
19
frontend/src/components/toolbar/dir-operation-toolbar.js
Normal 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;
|
@@ -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;
|
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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});
|
||||||
|
@@ -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 = {
|
||||||
|
Reference in New Issue
Block a user