1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-13 05:39:59 +00:00

Add related documents UI (#2577)

This commit is contained in:
王健辉
2018-12-28 14:25:25 +08:00
committed by Daniel Pan
parent 8637abde8d
commit dd15cd0ea8
12 changed files with 436 additions and 12 deletions

View File

@@ -0,0 +1,102 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import FileChooser from '../file-chooser/file-chooser';
const propTypes = {
repoID: PropTypes.string.isRequired,
filePath: PropTypes.string.isRequired,
toggleCancel: PropTypes.func.isRequired,
onRelatedFileChange: PropTypes.func.isRequired,
};
class AddRelatedFileDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
repo: null,
selectedPath: '',
isShowFile: true,
errMessage: '',
};
}
handleSubmit = () => {
this.onAddRelatedFile();
}
onAddRelatedFile = () => {
let { repo, selectedPath } = this.state;
let oRepoID = this.props.repoID;
let rRepoID = repo.repo_id;
let oFilePath = this.props.filePath;
let rFilePath = selectedPath;
if (oRepoID === rRepoID && oFilePath === rFilePath) {
let message = gettext('Can not select self as a related file.');
this.setState({errMessage: message});
return;
}
seafileAPI.addRelatedFile(oRepoID, rRepoID, oFilePath, rFilePath).then((res) => {
this.props.onRelatedFileChange();
this.toggle();
}).catch((error) => {
if (error.response) {
this.setState({
errMessage: error.response.data.error_msg
});
}
});
}
toggle = () => {
this.props.toggleCancel();
}
onDirentItemClick = (repo, selectedPath, dirent) => {
if(dirent.type === 'file') {
this.setState({
repo: repo,
selectedPath: selectedPath,
});
}
else {
this.setState({
repo: null,
selectedPath: '',
});
}
}
render() {
return (
<Modal isOpen={true} className="sf-add-related-file">
<ModalHeader toggle={this.toggle}>{gettext('Select File')}</ModalHeader>
<ModalBody>
<FileChooser
isShowFile={this.state.isShowFile}
repoID={this.props.repoID}
onDirentItemClick={this.onDirentItemClick}
/>
{this.state.errMessage && <Alert color="danger">{this.state.errMessage}</Alert>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
{ this.state.selectedPath ?
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
:
<Button color="primary" disabled>{gettext('Submit')}</Button>
}
</ModalFooter>
</Modal>
);
}
}
AddRelatedFileDialog.propTypes = propTypes;
export default AddRelatedFileDialog;

View File

@@ -0,0 +1,155 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap';
import Dirent from '../../models/dirent';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
const propTypes = {
relatedFiles: PropTypes.array.isRequired,
repoID: PropTypes.string.isRequired,
filePath: PropTypes.string.isRequired,
toggleCancel: PropTypes.func.isRequired,
addRelatedFileToggle: PropTypes.func.isRequired,
onRelatedFileChange: PropTypes.func.isRequired,
};
class ListRelatedFileDialog extends React.Component {
constructor(props) {
super(props);
this.state = ({
direntList: [],
});
}
onDeleteRelatedFile = (item) => {
let filePath = this.props.filePath;
let repoID = this.props.repoID;
let relatedID = item.related_id;
seafileAPI.deleteRelatedFile(repoID, filePath, relatedID).then((res) => {
this.props.onRelatedFileChange();
});
}
toggle = () => {
this.props.toggleCancel();
}
getDirentList = (relatedFiles) => {
if (relatedFiles.length === 0) {
this.setState({
direntList: [],
});
return;
}
let direntList = [];
relatedFiles.map((item) => {
seafileAPI.getFileInfo(item.repo_id, item.path).then(res => {
let dirent = new Dirent(res.data);
dirent['related_id'] = item.related_id;
dirent['link'] = item.link;
direntList.push(dirent);
this.setState({
direntList: direntList
});
});
});
}
componentWillMount() {
this.getDirentList(this.props.relatedFiles);
}
componentWillReceiveProps(nextProps) {
if (nextProps.relatedFiles.length !== this.props.relatedFiles.length) {
this.getDirentList(nextProps.relatedFiles);
}
}
render() {
return (
<Modal isOpen={true} size={this.state.direntList.length > 0 ? 'lg' : 'sm'}>
<ModalHeader toggle={this.toggle}>{gettext('Related Files')}</ModalHeader>
<ModalBody className={this.state.direntList.length > 0 ? 'list-related-file-body' : ''}>
{
this.state.direntList.length > 0 ?
<Table hover size="sm" className="list-related-file-table">
<thead>
<tr>
<th width='50%'>{gettext('Name')}</th>
<th width='25%'>{gettext('Size')}</th>
<th width='20%'>{gettext('Last Update')}</th>
<th width='5%'></th>
</tr>
</thead>
<tbody>
{
this.state.direntList.map((relatedFile, index) => {
return (
<React.Fragment key={index}>
<RelatedFile relatedFile={relatedFile} onDeleteRelatedFile={this.onDeleteRelatedFile}/>
</React.Fragment>
);
})
}
</tbody>
</Table>
:
<div className="no-related-file">{gettext('No related file yet.')}</div>
}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.props.addRelatedFileToggle}>{gettext('Add File')}</Button>
</ModalFooter>
</Modal>
);
}
}
ListRelatedFileDialog.propTypes = propTypes;
const RelatedFilePropTypes = {
relatedFile: PropTypes.object,
onDeleteRelatedFile: PropTypes.func.isRequired,
};
class RelatedFile extends React.Component {
constructor(props) {
super(props);
this.state = ({
active: false,
});
}
onMouseEnter = () => {
this.setState({
active: true
});
}
onMouseLeave = () => {
this.setState({
active: false
});
}
render() {
let className = this.state.active ? 'action-icon sf2-icon-x3' : 'action-icon vh sf2-icon-x3';
const relatedFile = this.props.relatedFile;
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><a href={relatedFile.link} target='_blank'>{relatedFile.name}</a></td>
<td>{relatedFile.size}</td>
<td>{relatedFile.mtime}</td>
<td><i className={className} onClick={this.props.onDeleteRelatedFile.bind(this, relatedFile)}></i></td>
</tr>
);
}
}
RelatedFile.propTypes = RelatedFilePropTypes;
export default ListRelatedFileDialog;

View File

@@ -4,6 +4,8 @@ import moment from 'moment';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import EditFileTagDialog from '../dialog/edit-filetag-dialog';
import ListRelatedFileDialog from '../dialog/list-related-file-dialog';
import AddRelatedFileDialog from '../dialog/add-related-file-dialog';
const propTypes = {
repoInfo: PropTypes.object.isRequired,
@@ -13,7 +15,9 @@ const propTypes = {
direntDetail: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
fileTagList: PropTypes.array.isRequired,
relatedFiles: PropTypes.array.isRequired,
onFileTagChanged: PropTypes.func.isRequired,
onRelatedFileChange: PropTypes.func.isRequired,
};
class DetailListView extends React.Component {
@@ -22,6 +26,8 @@ class DetailListView extends React.Component {
super(props);
this.state = {
isEditFileTagShow: false,
isListRelatedFileShow: false,
isAddRelatedFileShow: false,
};
}
@@ -53,8 +59,21 @@ class DetailListView extends React.Component {
return Utils.joinPath(path, dirent.name);
}
onListRelatedFileToggle = () => {
this.setState({
isListRelatedFileShow: !this.state.isListRelatedFileShow
});
}
onAddRelatedFileToggle = () => {
this.setState({
isListRelatedFileShow: !this.state.isListRelatedFileShow,
isAddRelatedFileShow: !this.state.isAddRelatedFileShow
});
}
render() {
let { direntType, direntDetail, fileTagList } = this.props;
let { direntType, direntDetail, fileTagList, relatedFiles } = this.props;
let position = this.getDirentPostion();
let direntPath = this.getDirentPath();
if (direntType === 'dir') {
@@ -79,7 +98,8 @@ class DetailListView extends React.Component {
<tr><th>{gettext('Size')}</th><td>{direntDetail.size}</td></tr>
<tr><th>{gettext('Position')}</th><td>{position}</td></tr>
<tr><th>{gettext('Last Update')}</th><td>{moment(direntDetail.last_modified).fromNow()}</td></tr>
<tr className="file-tag-container"><th>{gettext('Tags')}</th>
<tr className="file-tag-container">
<th>{gettext('Tags')}</th>
<td>
<ul className="file-tag-list">
{fileTagList.map((fileTag) => {
@@ -94,8 +114,43 @@ class DetailListView extends React.Component {
<i className='fa fa-pencil tag-edit-icon' onClick={this.onEditFileTagToggle}></i>
</td>
</tr>
<tr className="file-related-files">
<th>{gettext('Related Files')}</th>
<td>
<ul>
{relatedFiles.map((relatedFile, index) => {
return (
<li key={index}>
<a href={relatedFile.link} target='_blank'>{relatedFile.name}</a>
</li>
);
})}
</ul>
<i className='fa fa-pencil tag-edit-icon' onClick={this.onListRelatedFileToggle}></i>
</td>
</tr>
</tbody>
</table>
{
this.state.isAddRelatedFileShow &&
<AddRelatedFileDialog
filePath={direntPath}
repoID={this.props.repoID}
toggleCancel={this.onAddRelatedFileToggle}
onRelatedFileChange={this.props.onRelatedFileChange}
/>
}
{
this.state.isListRelatedFileShow &&
<ListRelatedFileDialog
relatedFiles={relatedFiles}
repoID={this.props.repoID}
filePath={direntPath}
toggleCancel={this.onListRelatedFileToggle}
addRelatedFileToggle={this.onAddRelatedFileToggle}
onRelatedFileChange={this.props.onRelatedFileChange}
/>
}
{
this.state.isEditFileTagShow &&
<EditFileTagDialog

View File

@@ -25,6 +25,7 @@ class DirentDetail extends React.Component {
direntDetail: '',
repoInfo: null,
fileTagList: [],
relatedFiles: [],
};
}
@@ -61,6 +62,21 @@ class DirentDetail extends React.Component {
});
this.setState({fileTagList: fileTagList});
});
seafileAPI.listRelatedFiles(repoID, direntPath).then(res => {
let relatedFiles = [];
res.data.related_files.map((relatedFile) => {
relatedFiles.push(relatedFile);
});
this.setState({
relatedFiles: relatedFiles,
});
}).catch((error) => {
if (error.response.status === 500) {
this.setState({
relatedFiles: []
});
}
});
} else {
seafileAPI.getDirInfo(repoID, direntPath).then(res => {
this.setState({
@@ -71,6 +87,12 @@ class DirentDetail extends React.Component {
}
}
onRelatedFileChange = () => {
let { dirent, path } = this.props;
let direntPath = Utils.joinPath(path, dirent.name);
this.updateDetailView(dirent, direntPath);
}
render() {
let { dirent } = this.props;
return (
@@ -96,7 +118,9 @@ class DirentDetail extends React.Component {
direntType={this.state.direntType}
direntDetail={this.state.direntDetail}
fileTagList={this.state.fileTagList}
relatedFiles={this.state.relatedFiles}
onFileTagChanged={this.props.onFileTagChanged}
onRelatedFileChange={this.onRelatedFileChange}
/>
</div>
}

View File

@@ -4,6 +4,7 @@ import { seafileAPI } from '../../utils/seafile-api';
import Dirent from '../../models/dirent';
const propTypes = {
isShowFile: PropTypes.bool,
filePath: PropTypes.string,
selectedPath: PropTypes.string,
dirent: PropTypes.object.isRequired,
@@ -28,7 +29,7 @@ class DirentListItem extends React.Component {
}
onItemClick = () => {
this.props.onDirentItemClick(this.state.filePath);
this.props.onDirentItemClick(this.state.filePath, this.props.dirent);
}
onToggleClick = () => {
@@ -36,7 +37,11 @@ class DirentListItem extends React.Component {
seafileAPI.listDir(this.props.repo.repo_id, this.state.filePath).then(res => {
let direntList = [];
res.data.forEach(item => {
if (item.type === 'dir') {
if (this.props.isShowFile === true) {
let dirent = new Dirent(item);
direntList.push(dirent);
}
else if (item.type === 'dir') {
let dirent = new Dirent(item);
direntList.push(dirent);
}
@@ -71,6 +76,7 @@ class DirentListItem extends React.Component {
onItemClick={this.onItemClick}
selectedPath={this.props.selectedPath}
onDirentItemClick={this.props.onDirentItemClick}
isShowFile={this.props.isShowFile}
/>
);
})}
@@ -82,11 +88,11 @@ class DirentListItem extends React.Component {
return (
<li className="file-chooser-item">
{
this.state.hasChildren &&
this.state.hasChildren && this.props.dirent.type !== 'file' &&
<span className={`item-toggle fa ${this.state.isShowChildren ? 'fa-caret-down' : 'fa-caret-right'}`} onClick={this.onToggleClick}></span>
}
<span className={`item-info ${this.state.filePath === this.props.selectedPath ? 'item-active' : ''}`} onClick={this.onItemClick}>
<span className="icon far fa-folder"></span>
<span className={`icon far ${this.props.dirent.type === 'dir' ? 'fa-folder' : 'fa-file'}`}></span>
<span className="name">{this.props.dirent && this.props.dirent.name}</span>
</span>
{this.state.isShowChildren && this.renderChildren()}

View File

@@ -5,6 +5,7 @@ import Dirent from '../../models/dirent';
import DirentListItem from './dirent-list-item';
const propTypes = {
isShowFile: PropTypes.bool,
selectedPath: PropTypes.string,
repo: PropTypes.object.isRequired,
isShowChildren: PropTypes.bool.isRequired,
@@ -25,7 +26,11 @@ class DirentListView extends React.Component {
seafileAPI.listDir(repo.repo_id, '/').then(res => {
let direntList = [];
res.data.forEach(item => {
if (item.type === 'dir') {
if (this.props.isShowFile === true) {
let dirent = new Dirent(item);
direntList.push(dirent);
}
else if (item.type === 'dir') {
let dirent = new Dirent(item);
direntList.push(dirent);
}
@@ -48,6 +53,7 @@ class DirentListView extends React.Component {
dirent={dirent}
onDirentItemClick={this.props.onDirentItemClick}
selectedPath={this.props.selectedPath}
isShowFile={this.props.isShowFile}
/>
);
})}

View File

@@ -8,6 +8,7 @@ import RepoInfo from '../../models/repo-info';
import '../../css/file-chooser.css';
const propTypes = {
isShowFile: PropTypes.bool,
repoID: PropTypes.string.isRequired,
onDirentItemClick: PropTypes.func,
onRepoItemClick: PropTypes.func,
@@ -71,8 +72,8 @@ class FileChooser extends React.Component {
this.setState({isCurrentRepoShow: !this.state.isCurrentRepoShow})
]
onDirentItemClick = (repo, filePath) => {
this.props.onDirentItemClick(repo, filePath);
onDirentItemClick = (repo, filePath, dirent) => {
this.props.onDirentItemClick(repo, filePath, dirent);
this.setState({
selectedRepo: repo,
selectedPath: filePath
@@ -80,7 +81,9 @@ class FileChooser extends React.Component {
}
onRepoItemClick = (repo) => {
this.props.onRepoItemClick(repo);
if (this.props.onRepoItemClick) {
this.props.onRepoItemClick(repo);
}
this.setState({
selectedRepo: repo,
selectedPath: '',
@@ -104,6 +107,7 @@ class FileChooser extends React.Component {
selectedPath={this.state.selectedPath}
onRepoItemClick={this.onRepoItemClick}
onDirentItemClick={this.onDirentItemClick}
isShowFile={this.props.isShowFile}
/>
}
</div>
@@ -121,6 +125,7 @@ class FileChooser extends React.Component {
selectedPath={this.state.selectedPath}
onRepoItemClick={this.onRepoItemClick}
onDirentItemClick={this.onDirentItemClick}
isShowFile={this.props.isShowFile}
/>
}
</div>

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import DirentListView from './dirent-list-view';
const propTypes = {
isShowFile: PropTypes.bool,
selectedPath: PropTypes.string,
selectedRepo: PropTypes.object,
repo: PropTypes.object.isRequired,
@@ -24,9 +25,9 @@ class RepoListItem extends React.Component {
this.setState({isShowChildren: !this.state.isShowChildren});
}
onDirentItemClick = (filePath) => {
onDirentItemClick = (filePath, dirent) => {
let repo = this.props.repo;
this.props.onDirentItemClick(repo, filePath);
this.props.onDirentItemClick(repo, filePath, dirent);
}
onRepoItemClick = () => {
@@ -52,6 +53,7 @@ class RepoListItem extends React.Component {
isShowChildren={this.state.isShowChildren}
onDirentItemClick={this.onDirentItemClick}
selectedPath={this.props.selectedPath}
isShowFile={this.props.isShowFile}
/>
)}
</li>

View File

@@ -4,6 +4,8 @@ import RepoListItem from './repo-list-item';
const propTypes = {
currentRepoInfo: PropTypes.object,
isShowFile: PropTypes.bool,
repo: PropTypes.object,
repoList: PropTypes.array,
selectedRepo: PropTypes.object,
initToShowChildren: PropTypes.bool.isRequired,
@@ -32,6 +34,7 @@ class RepoListView extends React.Component {
selectedPath={this.props.selectedPath}
onRepoItemClick={this.props.onRepoItemClick}
onDirentItemClick={this.props.onDirentItemClick}
isShowFile={this.props.isShowFile}
/>
);
})}