2018-12-19 03:41:45 +00:00
|
|
|
import React, { Fragment } from 'react';
|
2018-10-13 09:07:54 +00:00
|
|
|
import PropTypes from 'prop-types';
|
2019-02-27 05:53:36 +00:00
|
|
|
import { siteRoot, gettext, thumbnailSizeForOriginal, username } from '../../utils/constants';
|
2019-01-16 09:45:46 +00:00
|
|
|
import { Utils } from '../../utils/utils';
|
2018-10-25 05:36:06 +00:00
|
|
|
import Loading from '../loading';
|
2018-10-13 09:07:54 +00:00
|
|
|
import DirentListItem from './dirent-list-item';
|
2018-12-19 03:41:45 +00:00
|
|
|
import ModalPortal from '../modal-portal';
|
|
|
|
import CreateFile from '../../components/dialog/create-file-dialog';
|
|
|
|
|
2019-01-16 09:45:46 +00:00
|
|
|
import Lightbox from 'react-image-lightbox';
|
|
|
|
import 'react-image-lightbox/style.css';
|
|
|
|
|
2018-12-19 03:41:45 +00:00
|
|
|
import '../../css/tip-for-new-md.css';
|
2019-01-25 07:44:04 +00:00
|
|
|
import toaster from '../toast';
|
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-12-18 09:21:01 +00:00
|
|
|
currentRepoInfo: PropTypes.object,
|
2018-11-28 04:41:49 +00:00
|
|
|
isAllItemSelected: PropTypes.bool.isRequired,
|
|
|
|
isDirentListLoading: PropTypes.bool.isRequired,
|
2018-10-13 09:07:54 +00:00
|
|
|
direntList: PropTypes.array.isRequired,
|
2019-01-04 09:15:15 +00:00
|
|
|
sortBy: PropTypes.string.isRequired,
|
|
|
|
sortOrder: PropTypes.string.isRequired,
|
|
|
|
sortItems: PropTypes.func.isRequired,
|
2018-12-19 03:41:45 +00:00
|
|
|
onAddFile: PropTypes.func.isRequired,
|
2018-10-13 09:07:54 +00:00
|
|
|
onItemDelete: PropTypes.func.isRequired,
|
2018-11-23 12:19:42 +00:00
|
|
|
onAllItemSelected: PropTypes.func.isRequired,
|
|
|
|
onItemSelected: PropTypes.func.isRequired,
|
2018-10-25 05:36:06 +00:00
|
|
|
onItemRename: PropTypes.func.isRequired,
|
2018-10-13 09:07:54 +00:00
|
|
|
onItemClick: PropTypes.func.isRequired,
|
2018-11-27 06:47:19 +00:00
|
|
|
onItemMove: PropTypes.func.isRequired,
|
|
|
|
onItemCopy: PropTypes.func.isRequired,
|
2019-01-17 09:05:08 +00:00
|
|
|
onDirentClick: PropTypes.func.isRequired,
|
2018-10-25 05:36:06 +00:00
|
|
|
onItemDetails: PropTypes.func.isRequired,
|
2018-11-22 03:26:00 +00:00
|
|
|
updateDirent: PropTypes.func.isRequired,
|
2019-04-08 03:35:46 +00:00
|
|
|
switchAnotherMenuToShow: PropTypes.func,
|
|
|
|
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
|
2018-10-13 09:07:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class DirentListView extends React.Component {
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
isItemFreezed: false,
|
2019-01-16 09:45:46 +00:00
|
|
|
|
|
|
|
isImagePopupOpen: false,
|
|
|
|
imageItems: [],
|
|
|
|
imageIndex: 0,
|
|
|
|
|
2018-12-19 03:41:45 +00:00
|
|
|
isCreateFileDialogShow: false,
|
2019-04-08 03:35:46 +00:00
|
|
|
fileType: '',
|
2018-10-13 09:07:54 +00:00
|
|
|
};
|
2019-02-27 05:53:36 +00:00
|
|
|
|
|
|
|
this.isRepoOwner = props.currentRepoInfo.owner_email === username;
|
|
|
|
this.isAdmin = props.currentRepoInfo.is_admin;
|
|
|
|
this.repoEncrypted = props.currentRepoInfo.encrypted;
|
2018-10-13 09:07:54 +00:00
|
|
|
}
|
|
|
|
|
2019-04-08 03:35:46 +00:00
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
if (nextProps.appMenuType === 'item_op_menu' || nextProps.appMenuType === 'tree_contextmenu') {
|
|
|
|
this.setState({isItemFreezed: false});
|
|
|
|
} else {
|
|
|
|
this.setState({isItemFreezed: true});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate() {
|
|
|
|
let thead = document.querySelector('thead');
|
|
|
|
if (thead) {
|
|
|
|
thead.addEventListener('contextmenu', (e) => {
|
|
|
|
e.stopPropagation();
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-25 05:36:06 +00:00
|
|
|
onFreezedItem = () => {
|
2018-10-13 09:07:54 +00:00
|
|
|
this.setState({isItemFreezed: true});
|
|
|
|
}
|
2018-11-22 03:26:00 +00:00
|
|
|
|
2018-10-25 05:36:06 +00:00
|
|
|
onUnfreezedItem = () => {
|
2018-10-13 09:07:54 +00:00
|
|
|
this.setState({isItemFreezed: false});
|
|
|
|
}
|
|
|
|
|
2019-01-25 07:44:04 +00:00
|
|
|
onItemRename = (dirent, newName) => {
|
|
|
|
let isDuplicated = this.props.direntList.some(item => {
|
|
|
|
return item.name === newName;
|
|
|
|
});
|
|
|
|
if (isDuplicated) {
|
2019-01-29 03:32:54 +00:00
|
|
|
let errMessage = gettext('The name "{name}" is already taken. Please choose a different name.');
|
2019-01-25 07:44:04 +00:00
|
|
|
errMessage = errMessage.replace('{name}', Utils.HTMLescape(newName));
|
|
|
|
toaster.danger(errMessage);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
this.props.onItemRename(dirent, newName);
|
|
|
|
}
|
|
|
|
|
2018-11-23 12:19:42 +00:00
|
|
|
onItemRenameToggle = () => {
|
2018-10-25 05:36:06 +00:00
|
|
|
this.onFreezedItem();
|
2018-10-13 09:07:54 +00:00
|
|
|
}
|
|
|
|
|
2018-11-29 09:55:14 +00:00
|
|
|
onItemDetails = (dirent) => {
|
|
|
|
this.props.onItemDetails(dirent);
|
2018-10-25 05:36:06 +00:00
|
|
|
}
|
|
|
|
|
2018-12-19 03:41:45 +00:00
|
|
|
onCreateFileToggle = () => {
|
|
|
|
this.setState({
|
|
|
|
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
|
|
|
fileType: ''
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-03-29 05:40:24 +00:00
|
|
|
onCreateNewFile = (suffix) => {
|
2018-12-19 03:41:45 +00:00
|
|
|
this.setState({
|
|
|
|
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
2019-03-29 05:40:24 +00:00
|
|
|
fileType: suffix
|
2018-12-19 03:41:45 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onAddFile = (filePath, isDraft) => {
|
|
|
|
this.setState({isCreateFileDialogShow: false});
|
|
|
|
this.props.onAddFile(filePath, isDraft);
|
|
|
|
}
|
|
|
|
|
2019-01-04 07:06:27 +00:00
|
|
|
sortByName = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
const sortBy = 'name';
|
|
|
|
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
|
|
|
this.props.sortItems(sortBy, sortOrder);
|
|
|
|
}
|
|
|
|
|
|
|
|
sortByTime = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
const sortBy = 'time';
|
|
|
|
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
|
|
|
this.props.sortItems(sortBy, sortOrder);
|
|
|
|
}
|
|
|
|
|
2019-01-16 09:45:46 +00:00
|
|
|
// for image popup
|
|
|
|
prepareImageItems = () => {
|
|
|
|
let items = this.props.direntList.filter((item) => {
|
|
|
|
return Utils.imageCheck(item.name);
|
|
|
|
});
|
|
|
|
|
2019-02-27 05:53:36 +00:00
|
|
|
const useThumbnail = !this.repoEncrypted;
|
2019-01-16 09:45:46 +00:00
|
|
|
let prepareItem = (item) => {
|
|
|
|
const name = item.name;
|
|
|
|
|
|
|
|
const fileExt = name.substr(name.lastIndexOf('.') + 1).toLowerCase();
|
|
|
|
const isGIF = fileExt == 'gif';
|
|
|
|
|
|
|
|
const path = Utils.encodePath(Utils.joinPath(this.props.path, name));
|
|
|
|
const repoID = this.props.repoID;
|
|
|
|
let src;
|
|
|
|
if (useThumbnail && !isGIF) {
|
|
|
|
src = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`;
|
|
|
|
} else {
|
|
|
|
src = `${siteRoot}repo/${repoID}/raw${path}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
'name': name,
|
|
|
|
'url': `${siteRoot}lib/${repoID}/file${path}`,
|
|
|
|
'src': src
|
|
|
|
};
|
2019-01-31 09:37:02 +00:00
|
|
|
};
|
2019-01-16 09:45:46 +00:00
|
|
|
|
|
|
|
return items.map((item) => { return prepareItem(item); });
|
|
|
|
}
|
|
|
|
|
|
|
|
showImagePopup = (dirent) => {
|
|
|
|
let items = this.props.direntList.filter((item) => {
|
|
|
|
return Utils.imageCheck(item.name);
|
|
|
|
});
|
|
|
|
this.setState({
|
|
|
|
isImagePopupOpen: true,
|
|
|
|
imageItems: this.prepareImageItems(),
|
|
|
|
imageIndex: items.indexOf(dirent)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
moveToPrevImage = () => {
|
|
|
|
const imageItemsLength = this.state.imageItems.length;
|
|
|
|
this.setState((prevState) => ({
|
|
|
|
imageIndex: (prevState.imageIndex + imageItemsLength - 1) % imageItemsLength
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
moveToNextImage = () => {
|
|
|
|
const imageItemsLength = this.state.imageItems.length;
|
|
|
|
this.setState((prevState) => ({
|
|
|
|
imageIndex: (prevState.imageIndex + 1) % imageItemsLength
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
closeImagePopup = () => {
|
|
|
|
this.setState({
|
|
|
|
isImagePopupOpen: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-01-31 03:32:10 +00:00
|
|
|
checkDuplicatedName = (newName) => {
|
|
|
|
let direntList = this.props.direntList;
|
|
|
|
let isDuplicated = direntList.some(object => {
|
|
|
|
return object.name === newName;
|
|
|
|
});
|
|
|
|
return isDuplicated;
|
|
|
|
}
|
|
|
|
|
2018-10-13 09:07:54 +00:00
|
|
|
render() {
|
2019-01-04 07:06:27 +00:00
|
|
|
const { direntList, sortBy, sortOrder } = this.props;
|
2018-10-25 05:36:06 +00:00
|
|
|
|
|
|
|
if (this.props.isDirentListLoading) {
|
|
|
|
return (<Loading />);
|
|
|
|
}
|
|
|
|
|
2018-12-19 03:41:45 +00:00
|
|
|
if (this.props.path == '/' && !direntList.length) {
|
|
|
|
return (
|
|
|
|
<Fragment>
|
2019-03-29 05:40:24 +00:00
|
|
|
<div className="tip-for-new-file">
|
|
|
|
<p className="text-center">{gettext('This folder has no content at this time.')}</p>
|
|
|
|
<p className="text-center">{gettext('You can create files quickly')}{' +'}</p>
|
|
|
|
<div className="big-new-file-button-group">
|
|
|
|
<div className="d-flex justify-content-center">
|
|
|
|
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.md')}>
|
2019-04-03 10:01:23 +00:00
|
|
|
{'+ Markdown'}</button>
|
2019-03-29 05:40:24 +00:00
|
|
|
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.ppt')}>
|
2019-04-03 10:01:23 +00:00
|
|
|
{'+ PPT'}</button>
|
2019-03-29 05:40:24 +00:00
|
|
|
</div>
|
|
|
|
<div className="d-flex justify-content-center">
|
|
|
|
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.doc')}>
|
2019-04-03 10:01:23 +00:00
|
|
|
{'+ Word'}</button>
|
2019-03-29 05:40:24 +00:00
|
|
|
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.xls')}>
|
2019-04-03 10:01:23 +00:00
|
|
|
{'+ Excel'}</button>
|
2019-03-29 05:40:24 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2018-12-19 03:41:45 +00:00
|
|
|
</div>
|
|
|
|
{this.state.isCreateFileDialogShow && (
|
|
|
|
<ModalPortal>
|
|
|
|
<CreateFile
|
|
|
|
parentPath={this.props.path}
|
|
|
|
fileType={this.state.fileType}
|
|
|
|
onAddFile={this.onAddFile}
|
2019-01-31 03:32:10 +00:00
|
|
|
checkDuplicatedName={this.checkDuplicatedName}
|
2018-12-19 03:41:45 +00:00
|
|
|
addFileCancel={this.onCreateFileToggle}
|
|
|
|
/>
|
|
|
|
</ModalPortal>
|
|
|
|
)}
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-04 07:06:27 +00:00
|
|
|
// sort
|
|
|
|
const sortByName = sortBy == 'name';
|
|
|
|
const sortByTime = sortBy == 'time';
|
|
|
|
const sortIcon = sortOrder == 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
|
|
|
|
2019-01-16 09:45:46 +00:00
|
|
|
// for image popup
|
|
|
|
const imageItems = this.state.imageItems;
|
|
|
|
const imageIndex = this.state.imageIndex;
|
|
|
|
const imageItemsLength = imageItems.length;
|
|
|
|
const imageCaption = imageItemsLength && (
|
|
|
|
<Fragment>
|
2019-01-31 09:37:02 +00:00
|
|
|
<span>{gettext('%curr% of %total%').replace('%curr%', imageIndex + 1).replace('%total%', imageItemsLength)}</span>
|
2019-01-16 09:45:46 +00:00
|
|
|
<br />
|
2019-01-31 09:37:02 +00:00
|
|
|
<a href={imageItems[imageIndex].url} target="_blank">{gettext('Open in New Tab')}</a>
|
2019-01-16 09:45:46 +00:00
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
|
2018-10-13 09:07:54 +00:00
|
|
|
return (
|
2019-01-16 09:45:46 +00:00
|
|
|
<Fragment>
|
2019-01-31 09:37:02 +00:00
|
|
|
<table>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
2019-04-08 03:35:46 +00:00
|
|
|
<th width="3%" className="pl10">
|
2019-01-31 09:37:02 +00:00
|
|
|
<input type="checkbox" className="vam" onChange={this.props.onAllItemSelected} checked={this.props.isAllItemSelected}/>
|
|
|
|
</th>
|
2019-03-28 05:32:22 +00:00
|
|
|
<th width="3%" className="pl10">{/*icon */}</th>
|
|
|
|
<th width="5%" className="pl10">{/*star */}</th>
|
2019-01-31 09:37:02 +00:00
|
|
|
<th width="39%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a></th>
|
|
|
|
<th width="6%">{/*tag */}</th>
|
2019-02-20 09:08:00 +00:00
|
|
|
<th width="18%">{/*operation */}</th>
|
2019-01-31 09:37:02 +00:00
|
|
|
<th width="11%">{gettext('Size')}</th>
|
2019-02-20 09:08:00 +00:00
|
|
|
<th width="15%"><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {sortByTime && sortIcon}</a></th>
|
2019-01-31 09:37:02 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
{
|
|
|
|
direntList.length !== 0 && direntList.map((dirent, index) => {
|
|
|
|
return (
|
|
|
|
<DirentListItem
|
|
|
|
key={index}
|
|
|
|
dirent={dirent}
|
|
|
|
path={this.props.path}
|
|
|
|
repoID={this.props.repoID}
|
|
|
|
currentRepoInfo={this.props.currentRepoInfo}
|
2019-02-27 05:53:36 +00:00
|
|
|
isAdmin={this.isAdmin}
|
|
|
|
isRepoOwner={this.isRepoOwner}
|
|
|
|
repoEncrypted={this.repoEncrypted}
|
|
|
|
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
|
|
|
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
2019-01-31 09:37:02 +00:00
|
|
|
direntList={this.props.direntList}
|
|
|
|
onItemClick={this.props.onItemClick}
|
|
|
|
onItemRenameToggle={this.onItemRenameToggle}
|
|
|
|
onItemSelected={this.props.onItemSelected}
|
|
|
|
onItemDelete={this.props.onItemDelete}
|
|
|
|
onItemRename={this.onItemRename}
|
|
|
|
onItemMove={this.props.onItemMove}
|
|
|
|
onItemCopy={this.props.onItemCopy}
|
|
|
|
updateDirent={this.props.updateDirent}
|
|
|
|
isItemFreezed={this.state.isItemFreezed}
|
|
|
|
onFreezedItem={this.onFreezedItem}
|
|
|
|
onUnfreezedItem={this.onUnfreezedItem}
|
|
|
|
onDirentClick={this.props.onDirentClick}
|
|
|
|
onItemDetails={this.onItemDetails}
|
|
|
|
showImagePopup={this.showImagePopup}
|
2019-04-08 03:35:46 +00:00
|
|
|
switchAnotherMenuToShow={this.props.switchAnotherMenuToShow}
|
|
|
|
appMenuType={this.props.appMenuType}
|
|
|
|
itemIndex={index}
|
2019-01-31 09:37:02 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
{this.state.isImagePopupOpen && (
|
|
|
|
<Lightbox
|
|
|
|
mainSrc={imageItems[imageIndex].src}
|
|
|
|
imageTitle={imageItems[imageIndex].name}
|
|
|
|
imageCaption={imageCaption}
|
|
|
|
nextSrc={imageItems[(imageIndex + 1) % imageItemsLength].src}
|
|
|
|
prevSrc={imageItems[(imageIndex + imageItemsLength - 1) % imageItemsLength].src}
|
|
|
|
onCloseRequest={this.closeImagePopup}
|
|
|
|
onMovePrevRequest={this.moveToPrevImage}
|
|
|
|
onMoveNextRequest={this.moveToNextImage}
|
|
|
|
imagePadding={70}
|
|
|
|
imageLoadErrorMessage={gettext('The image could not be loaded.')}
|
|
|
|
prevLabel={gettext('Previous (Left arrow key)')}
|
|
|
|
nextLabel={gettext('Next (Right arrow key)')}
|
|
|
|
closeLabel={gettext('Close (Esc)')}
|
|
|
|
zoomInLabel={gettext('Zoom in')}
|
|
|
|
zoomOutLabel={gettext('Zoom out')}
|
|
|
|
/>
|
|
|
|
)}
|
2019-01-16 09:45:46 +00:00
|
|
|
</Fragment>
|
2018-10-13 09:07:54 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DirentListView.propTypes = propTypes;
|
|
|
|
|
|
|
|
export default DirentListView;
|