1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-21 18:32:37 +00:00
seahub/frontend/src/components/dirent-list-view/dirent-list-item.js

594 lines
19 KiB
JavaScript
Raw Normal View History

import React, { Fragment } from 'react';
2018-10-13 09:07:54 +00:00
import PropTypes from 'prop-types';
2018-12-18 01:02:16 +00:00
import MD5 from 'MD5';
import { UncontrolledTooltip } from 'reactstrap';
import { gettext, siteRoot, mediaUrl } from '../../utils/constants';
2018-11-30 03:52:19 +00:00
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
import DirentMenu from './dirent-menu';
import Rename from '../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';
import ShareDialog from '../dialog/share-dialog';
import toaster from '../toast';
2018-10-13 09:07:54 +00:00
2018-12-28 03:12:24 +00:00
import '../../css/dirent-list-item.css';
2018-10-13 09:07:54 +00:00
const propTypes = {
2018-11-22 03:26:00 +00:00
path: PropTypes.string.isRequired,
2018-11-28 04:41:49 +00:00
repoID: PropTypes.string.isRequired,
2018-10-13 09:07:54 +00:00
isItemFreezed: PropTypes.bool.isRequired,
showShareBtn: PropTypes.bool.isRequired,
2018-10-13 09:07:54 +00:00
dirent: PropTypes.object.isRequired,
onItemClick: PropTypes.func.isRequired,
onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired,
2018-11-23 12:19:42 +00:00
onItemRenameToggle: PropTypes.func.isRequired,
onItemSelected: PropTypes.func.isRequired,
2018-10-13 09:07:54 +00:00
onItemDelete: PropTypes.func.isRequired,
onItemRename: PropTypes.func.isRequired,
onItemMove: PropTypes.func.isRequired,
onItemCopy: PropTypes.func.isRequired,
onDirentClick: PropTypes.func.isRequired,
2018-11-22 03:26:00 +00:00
updateDirent: PropTypes.func.isRequired,
2019-03-11 03:14:49 +00:00
showImagePopup: PropTypes.func.isRequired,
2018-12-18 09:21:01 +00:00
currentRepoInfo: PropTypes.object,
2018-11-01 10:40:18 +00:00
isRepoOwner: PropTypes.bool,
2019-03-11 03:14:49 +00:00
isAdmin: PropTypes.bool.isRequired,
repoEncrypted: PropTypes.bool.isRequired,
isGroupOwnedRepo: PropTypes.bool.isRequired,
onItemMouseDown: PropTypes.func.isRequired,
onItemContextMenu: PropTypes.func.isRequired,
2018-10-13 09:07:54 +00:00
};
class DirentListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false,
highlight: false,
progress: 0,
isProgressDialogShow: false,
isMoveDialogShow: false,
isCopyDialogShow: false,
isShareDialogShow: false,
isMutipleOperation: false,
2018-12-18 01:02:16 +00:00
isShowTagTooltip: false,
2019-03-27 03:25:27 +00:00
isDragTipShow: false,
isDropTipshow: false,
2018-10-13 09:07:54 +00:00
};
this.zipToken = null;
2018-10-13 09:07:54 +00:00
}
componentWillReceiveProps(nextProps) {
if (!nextProps.isItemFreezed) {
this.setState({
highlight: false,
isOperationShow: false,
});
}
}
2018-10-13 09:07:54 +00:00
//UI Interactive
onMouseEnter = () => {
if (!this.props.isItemFreezed) {
this.setState({
highlight: true,
isOperationShow: true,
});
}
this.setState({isDragTipShow: true});
2018-10-13 09:07:54 +00:00
}
onMouseOver = () => {
if (!this.props.isItemFreezed) {
this.setState({
highlight: true,
isOperationShow: true,
});
}
this.setState({isDragTipShow: true});
2018-10-13 09:07:54 +00:00
}
onMouseLeave = () => {
if (!this.props.isItemFreezed) {
this.setState({
highlight: false,
isOperationShow: false,
});
}
this.setState({isDragTipShow: false});
2018-10-13 09:07:54 +00:00
}
2019-01-16 09:44:44 +00:00
onUnfreezedItem = () => {
let dirent = this.props.dirent;
// scenes 1: dirent isSelected --> this have Highest level
// scenes 2: dirent contextmenu show
// scenes 3: dirent operation menu show
if (dirent.isSelected) {
return;
}
2018-10-13 09:07:54 +00:00
this.setState({
2019-01-16 09:44:44 +00:00
highlight: false,
2018-10-13 09:07:54 +00:00
isOperationShow: false,
});
this.props.onUnfreezedItem();
2018-10-13 09:07:54 +00:00
}
//buiness handler
onItemSelected = () => {
this.props.onFreezedItem();
2018-11-23 12:19:42 +00:00
this.props.onItemSelected(this.props.dirent);
2018-10-13 09:07:54 +00:00
}
2018-11-22 03:26:00 +00:00
2018-10-13 09:07:54 +00:00
onItemStarred = () => {
let dirent = this.props.dirent;
2018-11-28 04:41:49 +00:00
let repoID = this.props.repoID;
let filePath = this.getDirentPath(dirent);
2019-02-18 12:26:55 +00:00
if (dirent.starred) {
2019-03-27 03:28:00 +00:00
seafileAPI.unstarItem(repoID, filePath).then(() => {
this.props.updateDirent(this.props.dirent, 'starred', false);
});
} else {
2019-02-18 12:26:55 +00:00
seafileAPI.starItem(repoID, filePath).then(() => {
this.props.updateDirent(this.props.dirent, 'starred', true);
});
}
2018-10-13 09:07:54 +00:00
}
2018-11-22 03:26:00 +00:00
// on '<tr>'
onDirentClick = (e) => {
// '<td>' is clicked
if (e.target.tagName == 'TD') {
this.props.onDirentClick(this.props.dirent);
}
}
2018-11-30 03:52:19 +00:00
onItemClick = (e) => {
e.preventDefault();
const dirent = this.props.dirent;
if (Utils.imageCheck(dirent.name)) {
this.props.showImagePopup(dirent);
} else {
this.props.onItemClick(dirent);
}
}
onItemDelete = (e) => {
e.nativeEvent.stopImmediatePropagation(); //for document event
2018-11-22 03:26:00 +00:00
this.props.onItemDelete(this.props.dirent);
}
onItemShare = (e) => {
e.nativeEvent.stopImmediatePropagation(); //for document event
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
}
closeSharedDialog = () => {
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
}
onMenuItemClick = (operation, event) => {
switch(operation) {
case 'Download':
this.onItemDownload(event);
break;
case 'Share':
this.onItemShare(event);
break;
case 'Delete':
this.onItemDelete(event);
break;
case 'Rename':
this.onItemRenameToggle();
break;
case 'Move':
this.onItemMoveToggle();
break;
case 'Copy':
this.onItemCopyToggle();
break;
case 'Permission':
this.onPermissionItem();
break;
case 'Unlock':
this.onUnlockItem();
break;
case 'Lock':
this.onLockItem();
break;
case 'Comment':
this.onComnentItem();
break;
case 'History':
this.onHistory();
break;
case 'Access Log':
this.onAccessLog();
break;
case 'Open via Client':
this.onOpenViaClient();
break;
default:
break;
}
}
2018-11-23 12:19:42 +00:00
onItemRenameToggle = () => {
this.props.onItemRenameToggle(this.props.dirent);
this.setState({
isOperationShow: false,
isRenameing: true,
});
}
onRenameConfirm = (newName) => {
2018-11-22 03:26:00 +00:00
this.props.onItemRename(this.props.dirent, newName);
this.onRenameCancel();
}
onRenameCancel = () => {
2019-01-16 09:44:44 +00:00
this.setState({isRenameing: false});
this.onUnfreezedItem();
}
2018-11-22 03:26:00 +00:00
2018-11-23 12:19:42 +00:00
onItemMoveToggle = () => {
this.setState({isMoveDialogShow: !this.state.isMoveDialogShow});
}
2018-11-23 12:19:42 +00:00
onItemCopyToggle = () => {
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow});
2018-10-13 09:07:54 +00:00
}
onPermissionItem = () => {
2018-10-13 09:07:54 +00:00
}
onLockItem = () => {
2018-11-28 04:41:49 +00:00
let repoID = this.props.repoID;
let filePath = this.getDirentPath(this.props.dirent);
seafileAPI.lockfile(repoID, filePath).then(() => {
this.props.updateDirent(this.props.dirent, 'is_locked', true);
this.props.updateDirent(this.props.dirent, 'locked_by_me', true);
});
}
onUnlockItem = () => {
2018-11-28 04:41:49 +00:00
let repoID = this.props.repoID;
let filePath = this.getDirentPath(this.props.dirent);
seafileAPI.unlockfile(repoID, filePath).then(() => {
this.props.updateDirent(this.props.dirent, 'is_locked', false);
this.props.updateDirent(this.props.dirent, 'locked_by_me', false);
});
}
onComnentItem = () => {
}
onHistory = () => {
2018-11-28 04:41:49 +00:00
let repoID = this.props.repoID;
let filePath = this.getDirentPath(this.props.dirent);
let url = URLDecorator.getUrl({type: 'file_revisions', repoID: repoID, filePath: filePath});
location.href = url;
}
onAccessLog = () => {
}
onOpenViaClient = () => {
2018-11-28 04:41:49 +00:00
let repoID = this.props.repoID;
let filePath = this.getDirentPath(this.props.dirent);
let url = URLDecorator.getUrl({type: 'open_via_client', repoID: repoID, filePath: filePath});
location.href = url;
}
onItemDownload = (e) => {
e.nativeEvent.stopImmediatePropagation();
let dirent = this.props.dirent;
2018-11-28 04:41:49 +00:00
let repoID = this.props.repoID;
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((error) => {
clearInterval(this.interval);
this.setState({isProgressDialogShow: false});
let errorMessage = error.response.data.error_msg;
toaster.danger(errorMessage);
});
} 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 => {
clearInterval(this.interval);
this.setState({
isProgressDialogShow: false,
});
});
}
getDirentPath = (dirent) => {
2018-11-22 03:26:00 +00:00
let path = this.props.path;
return path === '/' ? path + dirent.name : path + '/' + dirent.name;
2018-10-13 09:07:54 +00:00
}
2018-12-18 01:02:16 +00:00
onTagTooltipToggle = (e) => {
e.stopPropagation();
this.setState({isShowTagTooltip: !this.state.isShowTagTooltip});
2018-12-18 01:02:16 +00:00
}
2019-03-27 03:25:27 +00:00
onItemMove = (destRepo, dirent, selectedPath, currentPath) => {
this.props.onItemMove(destRepo, dirent, selectedPath, currentPath);
}
onItemDragStart = (e) => {
let nodeRootPath = '';
nodeRootPath = this.props.path === '/' ? `${this.props.path}${this.props.dirent.name}` : this.props.path;
let dragStartItemData = {nodeDirent: this.props.dirent, nodeParentPath: this.props.path, nodeRootPath: nodeRootPath};
dragStartItemData = JSON.stringify(dragStartItemData);
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setDragImage(this.refs.drag_icon, 15, 15);
e.dataTransfer.setData('applicaiton/drag-item-info', dragStartItemData);
}
onItemDragEnter = () => {
if (this.props.dirent.type === 'dir') {
this.setState({isDropTipshow: true});
}
}
onItemDragOver = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
onItemDragLeave = () => {
this.setState({isDropTipshow: false});
}
onItemDragDrop = (e) => {
this.setState({isDropTipshow: false});
if (e.dataTransfer.files.length) { // uploaded files
return;
}
let dragStartItemData = e.dataTransfer.getData('applicaiton/drag-item-info');
dragStartItemData = JSON.parse(dragStartItemData);
let {nodeDirent, nodeParentPath, nodeRootPath} = dragStartItemData;
let dropItemData = this.props.dirent;
if (nodeDirent.name === dropItemData.name) {
return;
}
if (dropItemData.type !== 'dir') {
return;
}
// copy the dirent to it's child. eg: A/B -> A/B/C
if (dropItemData.type === 'dir' && nodeDirent.type === 'dir') {
if (nodeParentPath !== this.props.path) {
if (this.props.path.indexOf(nodeRootPath) !== -1) {
return;
}
}
}
let selectedPath = Utils.joinPath(this.props.path, this.props.dirent.name);
this.onItemMove(this.props.currentRepoInfo, nodeDirent, selectedPath, nodeParentPath);
}
onItemMouseDown = (event) => {
this.props.onItemMouseDown(event);
}
onItemContextMenu = (event) => {
let dirent = this.props.dirent;
this.props.onItemContextMenu(event, dirent);
}
2018-10-13 09:07:54 +00:00
render() {
2018-11-30 03:52:19 +00:00
let { path, dirent } = this.props;
let direntPath = Utils.joinPath(path, dirent.name);
let dirHref = '';
if (this.props.currentRepoInfo) {
dirHref = siteRoot + 'library/' + this.props.repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath);
}
2019-01-02 03:52:44 +00:00
let fileHref = siteRoot + 'lib/' + this.props.repoID + '/file' + Utils.encodePath(direntPath);
2018-12-18 01:02:16 +00:00
let toolTipID = MD5(dirent.name).slice(0, 7);
2018-12-17 10:43:18 +00:00
let tagTitle = '';
2018-12-18 01:02:16 +00:00
if (dirent.file_tags && dirent.file_tags.length > 0) {
2018-12-17 10:43:18 +00:00
dirent.file_tags.forEach(item => {
tagTitle += item.name + ' ';
});
}
let iconUrl = Utils.getDirentIcon(dirent);
let trClass = this.state.highlight ? 'tr-highlight ' : '';
trClass += this.state.isDropTipshow ? 'tr-drop-effect' : '';
2018-10-13 09:07:54 +00:00
return (
<Fragment>
<tr
className={trClass}
draggable="true"
onMouseEnter={this.onMouseEnter}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
onClick={this.onDirentClick}
onDragStart={this.onItemDragStart}
onDragEnter={this.onItemDragEnter}
onDragOver={this.onItemDragOver}
onDragLeave={this.onItemDragLeave}
onDrop={this.onItemDragDrop}
onMouseDown={this.onItemMouseDown}
onContextMenu={this.onItemContextMenu}
>
<td className={`pl10 ${this.state.isDragTipShow ? 'tr-drag-effect' : ''}`}>
<input type="checkbox" className="vam" onChange={this.onItemSelected} checked={dirent.isSelected}/>
</td>
<td className="pl10">
{dirent.starred !== undefined && !dirent.starred && <i className="far fa-star star-empty cursor-pointer" onClick={this.onItemStarred}></i>}
{dirent.starred !== undefined && dirent.starred && <i className="fas fa-star cursor-pointer" onClick={this.onItemStarred}></i>}
</td>
<td className="pl10">
<div className="dir-icon">
{dirent.encoded_thumbnail_src ?
2019-03-27 03:25:27 +00:00
<img ref='drag_icon' src={`${siteRoot}${dirent.encoded_thumbnail_src}`} className="thumbnail cursor-pointer" onClick={this.onItemClick} alt="" /> :
<img ref='drag_icon' src={iconUrl} width="24" alt='' />
}
2019-03-05 10:21:19 +00:00
{dirent.is_locked && <img className="locked" src={mediaUrl + 'img/file-locked-32.png'} alt={gettext('locked')} title={dirent.lock_owner_name}/>}
</div>
</td>
2018-11-30 03:52:19 +00:00
<td className="name">
{this.state.isRenameing ?
<Rename hasSuffix={dirent.type !== 'dir'} name={dirent.name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
2019-01-02 03:52:44 +00:00
<a href={dirent.type === 'dir' ? dirHref : fileHref} onClick={this.onItemClick}>{dirent.name}</a>
}
</td>
<td className="tag-list-title">
2018-12-18 01:02:16 +00:00
{(dirent.type !== 'dir' && dirent.file_tags) && (
<Fragment>
<div id={`tag-list-title-${toolTipID}`} className="dirent-item tag-list tag-list-stacked">
{dirent.file_tags.map((fileTag, index) => {
let length = dirent.file_tags.length;
return (
<span className="file-tag" key={fileTag.id} style={{zIndex:length - index, backgroundColor:fileTag.color}}></span>
2018-12-18 01:02:16 +00:00
);
})}
</div>
<UncontrolledTooltip target={`tag-list-title-${toolTipID}`} placement="bottom">
{tagTitle}
</UncontrolledTooltip>
</Fragment>
)}
</td>
<td className="operation">
{
this.state.isOperationShow &&
<div className="operations">
<ul className="operation-group">
<li className="operation-group-item">
2018-12-28 03:12:24 +00:00
<i className="op-icon sf2-icon-download" title={gettext('Download')} onClick={this.onItemDownload}></i>
</li>
{this.props.showShareBtn &&
<li className="operation-group-item">
2018-12-28 03:12:24 +00:00
<i className="op-icon sf2-icon-share" title={gettext('Share')} onClick={this.onItemShare}></i>
</li>
2019-01-29 02:06:26 +00:00
}
<li className="operation-group-item">
2018-12-28 03:12:24 +00:00
<i className="op-icon sf2-icon-delete" title={gettext('Delete')} onClick={this.onItemDelete}></i>
</li>
<li className="operation-group-item">
2019-01-16 09:44:44 +00:00
<DirentMenu
dirent={this.props.dirent}
onMenuItemClick={this.onMenuItemClick}
currentRepoInfo={this.props.currentRepoInfo}
isRepoOwner={this.props.isRepoOwner}
onFreezedItem={this.props.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
/>
</li>
</ul>
</div>
}
</td>
<td className="file-size">{dirent.size && dirent.size}</td>
2019-01-04 05:53:30 +00:00
<td className="last-update">{dirent.mtime_relative}</td>
</tr>
{this.state.isMoveDialogShow &&
<ModalPortal>
<MoveDirentDialog
path={this.props.path}
2018-11-28 04:41:49 +00:00
repoID={this.props.repoID}
dirent={this.props.dirent}
isMutipleOperation={this.state.isMutipleOperation}
onItemMove={this.props.onItemMove}
onCancelMove={this.onItemMoveToggle}
2019-03-11 03:14:49 +00:00
repoEncrypted={this.props.repoEncrypted}
/>
</ModalPortal>
}
{this.state.isCopyDialogShow &&
<ModalPortal>
<CopyDirentDialog
path={this.props.path}
2018-11-28 04:41:49 +00:00
repoID={this.props.repoID}
dirent={this.props.dirent}
isMutipleOperation={this.state.isMutipleOperation}
onItemCopy={this.props.onItemCopy}
onCancelCopy={this.onItemCopyToggle}
2019-03-11 03:14:49 +00:00
repoEncrypted={this.props.repoEncrypted}
/>
</ModalPortal>
}
{this.state.isProgressDialogShow &&
<ModalPortal>
<ZipDownloadDialog
progress={this.state.progress}
onCancelDownload={this.onCancelDownload}
/>
</ModalPortal>
}
{this.state.isShareDialogShow &&
<ModalPortal>
<ShareDialog
2018-12-14 07:09:07 +00:00
itemType={dirent.type}
itemName={dirent.name}
2018-12-14 07:09:07 +00:00
itemPath={direntPath}
2019-01-29 02:06:26 +00:00
userPerm={dirent.permission}
repoID={this.props.repoID}
2019-01-29 02:06:26 +00:00
repoEncrypted={false}
enableDirPrivateShare={this.props.enableDirPrivateShare}
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
toggleDialog={this.closeSharedDialog}
/>
</ModalPortal>
}
</Fragment>
2018-10-13 09:07:54 +00:00
);
}
}
DirentListItem.propTypes = propTypes;
export default DirentListItem;