mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 15:38:15 +00:00
Grid view mode four edition
This commit is contained in:
125
frontend/src/components/dialog/rename-grid-item-dialog.js
Normal file
125
frontend/src/components/dialog/rename-grid-item-dialog.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Alert } from 'reactstrap';
|
||||
|
||||
const propTypes = {
|
||||
onRename: PropTypes.func.isRequired,
|
||||
toggleCancel: PropTypes.func.isRequired,
|
||||
checkDuplicatedName: PropTypes.func.isRequired,
|
||||
dirent: PropTypes.object,
|
||||
};
|
||||
|
||||
class Rename extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
newName: '',
|
||||
errMessage: '',
|
||||
};
|
||||
this.newInput = React.createRef();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({newName: this.props.dirent.name});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let {dirent} = this.props;
|
||||
this.changeState(dirent);
|
||||
this.newInput.focus();
|
||||
let type = dirent.type;
|
||||
if (type === 'file') {
|
||||
var endIndex = dirent.name.lastIndexOf('.md');
|
||||
this.newInput.setSelectionRange(0, endIndex, 'forward');
|
||||
} else {
|
||||
this.newInput.setSelectionRange(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.changeState(nextProps.dirent);
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({newName: e.target.value});
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
let { isValid, errMessage } = this.validateInput();
|
||||
if (!isValid) {
|
||||
this.setState({errMessage : errMessage});
|
||||
} else {
|
||||
let isDuplicated = this.checkDuplicatedName();
|
||||
if (isDuplicated) {
|
||||
let errMessage = gettext('The name "{name}" is already taken. Please choose a different name.');
|
||||
errMessage = errMessage.replace('{name}', Utils.HTMLescape(this.state.newName));
|
||||
this.setState({errMessage: errMessage});
|
||||
} else {
|
||||
this.props.onRename(this.state.newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggleCancel();
|
||||
}
|
||||
|
||||
changeState = (dirent) => {
|
||||
let name = dirent.name;
|
||||
this.setState({newName: name});
|
||||
}
|
||||
|
||||
validateInput = () => {
|
||||
let newName = this.state.newName.trim();
|
||||
let isValid = true;
|
||||
let errMessage = '';
|
||||
if (!newName) {
|
||||
isValid = false;
|
||||
errMessage = gettext('Name is required.');
|
||||
return { isValid, errMessage };
|
||||
}
|
||||
|
||||
if (newName.indexOf('/') > -1) {
|
||||
isValid = false;
|
||||
errMessage = gettext('Name should not include ' + '\'/\'' + '.');
|
||||
return { isValid, errMessage };
|
||||
}
|
||||
|
||||
return { isValid, errMessage };
|
||||
}
|
||||
|
||||
checkDuplicatedName = () => {
|
||||
let isDuplicated = this.props.checkDuplicatedName(this.state.newName);
|
||||
return isDuplicated;
|
||||
}
|
||||
|
||||
render() {
|
||||
let type = this.props.dirent.type;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{type === 'file' ? gettext('Rename File') : gettext('Rename Folder') }</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>{type === 'file' ? gettext('New file name'): gettext('New folder name')}</p>
|
||||
<Input onKeyPress={this.handleKeyPress} innerRef={input => {this.newInput = input;}} placeholder="newName" value={this.state.newName} onChange={this.handleChange} />
|
||||
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Rename.propTypes = propTypes;
|
||||
|
||||
export default Rename;
|
@@ -1,14 +1,81 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import RepoInfoBar from '../../components/repo-info-bar';
|
||||
import DirentGridView from '../../components/dirent-grid-view/dirent-grid-view';
|
||||
import DirentNoneView from '../../components/dirent-list-view/dirent-none-view';
|
||||
|
||||
const propTypes = {
|
||||
|
||||
path: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
currentRepoInfo: PropTypes.object.isRequired,
|
||||
readmeMarkdown: PropTypes.object,
|
||||
draftCounts: PropTypes.number,
|
||||
usedRepoTags: PropTypes.array.isRequired,
|
||||
updateUsedRepoTags: PropTypes.func.isRequired,
|
||||
direntList: PropTypes.array.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
onGridItemClick: PropTypes.func,
|
||||
onAddFile: PropTypes.func.isRequired,
|
||||
onItemDelete: PropTypes.func.isRequired,
|
||||
onItemMove: PropTypes.func.isRequired,
|
||||
onItemCopy: PropTypes.func.isRequired,
|
||||
onRenameNode: PropTypes.func.isRequired,
|
||||
isGroupOwnedRepo: PropTypes.bool.isRequired,
|
||||
isRepoInfoBarShow: PropTypes.bool.isRequired,
|
||||
isDirentListLoading: PropTypes.bool.isRequired,
|
||||
isDirentDetailShow: PropTypes.bool.isRequired,
|
||||
enableDirPrivateShare: PropTypes.bool.isRequired,
|
||||
updateDirent: PropTypes.func.isRequired,
|
||||
showShareBtn: PropTypes.bool.isRequired,
|
||||
showDirentDetail: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class DirGridView extends React.Component {
|
||||
|
||||
render() {
|
||||
if (this.props.path === '/' && this.props.direntList.length === 0) {
|
||||
return (
|
||||
<div>表格布局</div>
|
||||
<DirentNoneView
|
||||
path={this.props.path}
|
||||
isDirentListLoading={this.props.isDirentListLoading}
|
||||
onAddFile={this.props.onAddFile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
{this.props.isRepoInfoBarShow && (
|
||||
<RepoInfoBar
|
||||
repoID={this.props.repoID}
|
||||
currentPath={this.props.path}
|
||||
readmeMarkdown={this.props.readmeMarkdown}
|
||||
draftCounts={this.props.draftCounts}
|
||||
usedRepoTags={this.props.usedRepoTags}
|
||||
updateUsedRepoTags={this.props.updateUsedRepoTags}
|
||||
/>
|
||||
)}
|
||||
<DirentGridView
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
direntList={this.props.direntList}
|
||||
onAddFile={this.props.onAddFile}
|
||||
onItemClick={this.props.onItemClick}
|
||||
onItemDelete={this.props.onItemDelete}
|
||||
onItemMove={this.props.onItemMove}
|
||||
onItemCopy={this.props.onItemCopy}
|
||||
isDirentListLoading={this.props.isDirentListLoading}
|
||||
updateDirent={this.props.updateDirent}
|
||||
showShareBtn={this.props.showShareBtn}
|
||||
onRenameNode={this.props.onRenameNode}
|
||||
showDirentDetail={this.props.showDirentDetail}
|
||||
onGridItemClick={this.props.onGridItemClick}
|
||||
isDirentDetailShow={this.props.isDirentDetailShow}
|
||||
onItemRename={this.props.onItemRename}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
119
frontend/src/components/dirent-grid-view/dirent-grid-item.js
Normal file
119
frontend/src/components/dirent-grid-view/dirent-grid-item.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, siteRoot, mediaUrl } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
dirent: PropTypes.object.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
showImagePopup: PropTypes.func.isRequired,
|
||||
handleContextClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class DirentGridItem extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
gridDragImage: '',
|
||||
isDropTipshow: false,
|
||||
}
|
||||
}
|
||||
|
||||
onItemClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dirent = this.props.dirent;
|
||||
if (Utils.imageCheck(dirent.name)) {
|
||||
this.props.showImagePopup(dirent);
|
||||
} else {
|
||||
this.props.onItemClick(dirent);
|
||||
}
|
||||
}
|
||||
|
||||
getFileUrl = (url) => {
|
||||
let fileUrlArr = url.split('/');
|
||||
if (fileUrlArr.indexOf('48') !== -1) {
|
||||
fileUrlArr.splice(fileUrlArr.indexOf('48'), 1, '192');
|
||||
}
|
||||
let fileUrl = fileUrlArr.join('/');
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
gridDargStart = (e) => {
|
||||
// todo
|
||||
}
|
||||
|
||||
gridDragEnter = (e) => {
|
||||
// todo
|
||||
console.log(123)
|
||||
}
|
||||
|
||||
gridDragOver = (e) => {
|
||||
// todo
|
||||
e.preventDefault();
|
||||
|
||||
}
|
||||
|
||||
gridDragLeave = (e) => {
|
||||
// todo
|
||||
console.log(456)
|
||||
}
|
||||
|
||||
gridDrop = (e) => {
|
||||
// todo
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onItemContextMenu = (event) => {
|
||||
this.handleContextClick(event);
|
||||
}
|
||||
|
||||
handleContextClick = (event) => {
|
||||
this.props.handleContextClick(event, this.props.dirent);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { dirent, path } = this.props;
|
||||
let direntPath = Utils.joinPath(path, dirent.name);
|
||||
let iconUrl = Utils.getDirentIcon(dirent);
|
||||
let fileUrl = dirent.encoded_thumbnail_src ? this.getFileUrl(dirent.encoded_thumbnail_src) : '';
|
||||
|
||||
let dirHref = '';
|
||||
if (this.props.currentRepoInfo) {
|
||||
dirHref = siteRoot + 'library/' + this.props.repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath);
|
||||
}
|
||||
let fileHref = siteRoot + 'lib/' + this.props.repoID + '/file' + Utils.encodePath(direntPath);
|
||||
|
||||
|
||||
return(
|
||||
<Fragment>
|
||||
<li className="grid-item" onContextMenu={this.onItemContextMenu}>
|
||||
<div
|
||||
className="grid-file-img-link cursor-pointer"
|
||||
// draggable="true"
|
||||
onClick={this.onItemClick}
|
||||
// onDragStart={this.gridDargStart}
|
||||
onDragEnter={this.gridDragEnter}
|
||||
onDragOver={this.gridDragOver}
|
||||
onDragLeave={this.gridDragLeave}
|
||||
onDrop={this.gridDrop}
|
||||
>
|
||||
{dirent.encoded_thumbnail_src ?
|
||||
<img src={`${siteRoot}${fileUrl}`} ref={this.gridIcon} className="thumbnail" onClick={this.onItemClick} alt=""/> :
|
||||
<img src={iconUrl} ref={this.gridIcon} width="96" alt='' />
|
||||
}
|
||||
{dirent.is_locked && <img className="grid-file-locked-icon" src={mediaUrl + 'img/file-locked-32.png'} alt={gettext('locked')} title={dirent.lock_owner_name}/>}
|
||||
</div>
|
||||
<div className="grid-file-name">
|
||||
<a className="grid-file-name-link" href={dirent.type === 'dir' ? dirHref : fileHref} onClick={this.onItemClick}>{dirent.name}</a>
|
||||
</div>
|
||||
</li>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DirentGridItem.propTypes = propTypes;
|
||||
export default DirentGridItem;
|
538
frontend/src/components/dirent-grid-view/dirent-grid-view.js
Normal file
538
frontend/src/components/dirent-grid-view/dirent-grid-view.js
Normal file
@@ -0,0 +1,538 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { siteRoot, thumbnailSizeForOriginal, username, isPro, enableFileComment, fileAuditEnabled, folderPermEnabled } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import URLDecorator from '../../utils/url-decorator';
|
||||
import Loading from '../loading';
|
||||
import ModalPortal from '../modal-portal';
|
||||
import ImageDialog from '../../components/dialog/image-dialog';
|
||||
import DirentGridItem from '../../components/dirent-grid-view/dirent-grid-item';
|
||||
import ContextMenu from '../context-menu/context-menu';
|
||||
import { hideMenu, showMenu } from '../context-menu/actions';
|
||||
import TextTranslation from '../../utils/text-translation';
|
||||
import MoveDirentDialog from '../dialog/move-dirent-dialog';
|
||||
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
|
||||
import ShareDialog from '../dialog/share-dialog';
|
||||
import ZipDownloadDialog from '../dialog/zip-download-dialog';
|
||||
import Rename from '../../components/dialog/rename-grid-item-dialog';
|
||||
|
||||
import '../../css/grid-view.css';
|
||||
|
||||
const propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
currentRepoInfo: PropTypes.object,
|
||||
direntList: PropTypes.array.isRequired,
|
||||
onAddFile: PropTypes.func,
|
||||
onItemDelete: PropTypes.func,
|
||||
onItemCopy: PropTypes.func.isRequired,
|
||||
onItemMove: PropTypes.func.isRequired,
|
||||
onRenameNode: PropTypes.func.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
isDirentListLoading: PropTypes.bool.isRequired,
|
||||
isGroupOwnedRepo: PropTypes.bool.isRequired,
|
||||
showShareBtn: PropTypes.bool.isRequired,
|
||||
enableDirPrivateShare: PropTypes.bool.isRequired,
|
||||
updateDirent: PropTypes.func.isRequired,
|
||||
isDirentDetailShow: PropTypes.bool.isRequired,
|
||||
onGridItemClick: PropTypes.func,
|
||||
};
|
||||
|
||||
class DirentGridView extends React.Component{
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
isImagePopupOpen: false,
|
||||
imageItems: [],
|
||||
imageIndex: 0,
|
||||
isCreateFileDialogShow: false,
|
||||
// onmenuClick
|
||||
isShareDialogShow: false,
|
||||
isMoveDialogShow: false,
|
||||
isCopyDialogShow: false,
|
||||
isZipDialogOpen: false,
|
||||
isRenameDialogShow: false,
|
||||
|
||||
isMutipleOperation: false,
|
||||
dirent: '',
|
||||
}
|
||||
this.isRepoOwner = props.currentRepoInfo.owner_email === username;
|
||||
|
||||
}
|
||||
|
||||
onCreateFileToggle = () => {
|
||||
this.setState({
|
||||
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
||||
fileType: ''
|
||||
});
|
||||
}
|
||||
|
||||
onCreateNewFile = (suffix) => {
|
||||
this.setState({
|
||||
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
||||
fileType: suffix
|
||||
});
|
||||
}
|
||||
|
||||
onMoveToggle = () => {
|
||||
this.setState({isMoveDialogShow: !this.state.isMoveDialogShow});
|
||||
}
|
||||
|
||||
onCopyToggle = () => {
|
||||
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow});
|
||||
}
|
||||
|
||||
onAddFile = (filePath, isDraft) => {
|
||||
this.setState({isCreateFileDialogShow: false});
|
||||
this.props.onAddFile(filePath, isDraft);
|
||||
}
|
||||
|
||||
onItemShare = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation(); //for document event
|
||||
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
|
||||
}
|
||||
|
||||
closeSharedDialog = () => {
|
||||
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
|
||||
}
|
||||
|
||||
onItemDelete = (currentObject, e) => {
|
||||
e.nativeEvent.stopImmediatePropagation(); //for document event
|
||||
this.props.onItemDelete(currentObject);
|
||||
}
|
||||
|
||||
onMenuItemClick = (operation, currentObject, event) => {
|
||||
hideMenu();
|
||||
switch(operation) {
|
||||
case 'Download':
|
||||
this.onItemDownload(currentObject, event);
|
||||
break;
|
||||
case 'Share':
|
||||
this.onItemShare(event);
|
||||
break;
|
||||
case 'Delete':
|
||||
this.onItemDelete(currentObject, 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(currentObject);
|
||||
break;
|
||||
case 'Lock':
|
||||
this.onLockItem(currentObject);
|
||||
break;
|
||||
case 'Comment':
|
||||
this.onComnentItem();
|
||||
break;
|
||||
case 'History':
|
||||
this.onHistory(currentObject);
|
||||
break;
|
||||
case 'Details':
|
||||
this.onDetails(currentObject);
|
||||
break;
|
||||
case 'Access Log':
|
||||
this.onAccessLog();
|
||||
break;
|
||||
case 'Open via Client':
|
||||
this.onOpenViaClient(currentObject);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getDirentPath = (dirent) => {
|
||||
let path = this.props.path;
|
||||
return path === '/' ? path + dirent.name : path + '/' + dirent.name;
|
||||
}
|
||||
|
||||
closeZipDialog = () => {
|
||||
this.setState({
|
||||
isZipDialogOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
onDetails = (dirent) => {
|
||||
this.props.onGridItemClick(dirent)
|
||||
this.props.showDirentDetail();
|
||||
}
|
||||
|
||||
onItemDownload = (currentObject, e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
let dirent = currentObject;
|
||||
let repoID = this.props.repoID;
|
||||
let direntPath = this.getDirentPath(dirent);
|
||||
if (dirent.type === 'dir') {
|
||||
this.setState({
|
||||
isZipDialogOpen: true
|
||||
});
|
||||
} else {
|
||||
let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: direntPath});
|
||||
location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
onItemRenameToggle = () => {
|
||||
this.setState({
|
||||
isRenameDialogShow: !this.state.isRenameDialogShow,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onItemMoveToggle = () => {
|
||||
this.setState({isMoveDialogShow: !this.state.isMoveDialogShow});
|
||||
}
|
||||
|
||||
onItemCopyToggle = () => {
|
||||
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow});
|
||||
}
|
||||
|
||||
onPermissionItem = () => {
|
||||
|
||||
}
|
||||
|
||||
onLockItem = (currentObject) => {
|
||||
let repoID = this.props.repoID;
|
||||
let filePath = this.getDirentPath(currentObject);
|
||||
seafileAPI.lockfile(repoID, filePath).then(() => {
|
||||
this.props.updateDirent(currentObject, 'is_locked', true);
|
||||
this.props.updateDirent(currentObject, 'locked_by_me', true);
|
||||
});
|
||||
}
|
||||
|
||||
onUnlockItem = (currentObject) => {
|
||||
let repoID = this.props.repoID;
|
||||
let filePath = this.getDirentPath(currentObject);
|
||||
seafileAPI.unlockfile(repoID, filePath).then(() => {
|
||||
this.props.updateDirent(currentObject, 'is_locked', false);
|
||||
this.props.updateDirent(currentObject, 'locked_by_me', false);
|
||||
});
|
||||
}
|
||||
|
||||
onComnentItem = () => {
|
||||
|
||||
}
|
||||
|
||||
onHistory = (currentObject) => {
|
||||
let repoID = this.props.repoID;
|
||||
let filePath = this.getDirentPath(currentObject);
|
||||
let url = URLDecorator.getUrl({type: 'file_revisions', repoID: repoID, filePath: filePath});
|
||||
location.href = url;
|
||||
}
|
||||
|
||||
onAccessLog = () => {
|
||||
|
||||
}
|
||||
|
||||
onOpenViaClient = (currentObject) => {
|
||||
let repoID = this.props.repoID;
|
||||
let filePath = this.getDirentPath(currentObject);
|
||||
let url = URLDecorator.getUrl({type: 'open_via_client', repoID: repoID, filePath: filePath});
|
||||
location.href = url;
|
||||
}
|
||||
|
||||
onItemRename = (newName) => {
|
||||
this.setState({
|
||||
isRenameDialogShow: !this.state.isRenameDialogShow,
|
||||
});
|
||||
this.props.onItemRename(this.state.dirent, newName)
|
||||
}
|
||||
|
||||
|
||||
prepareImageItem = (item) => {
|
||||
const useThumbnail = !this.repoEncrypted;
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
showImagePopup = (curItem) => {
|
||||
let items = this.props.direntList.filter((item) => {
|
||||
return Utils.imageCheck(item.name);
|
||||
});
|
||||
|
||||
const imageItems = items.map((item) => {
|
||||
return this.prepareImageItem(item);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isImagePopupOpen: true,
|
||||
imageItems: imageItems,
|
||||
imageIndex: items.indexOf(curItem)
|
||||
});
|
||||
}
|
||||
|
||||
closeImagePopup = () => {
|
||||
this.setState({isImagePopupOpen: false})
|
||||
}
|
||||
|
||||
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
|
||||
}));
|
||||
}
|
||||
|
||||
checkDuplicatedName = (newName) => {
|
||||
let direntList = this.props.direntList;
|
||||
let isDuplicated = direntList.some(object => {
|
||||
return object.name === newName;
|
||||
});
|
||||
return isDuplicated;
|
||||
}
|
||||
|
||||
gridContainerClick = () => {
|
||||
if (!this.props.isDirentDetailShow) {
|
||||
this.props.onGridItemClick(null);
|
||||
}
|
||||
}
|
||||
|
||||
handleContextClick = (event, currentObject) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let x = event.clientX || (event.touches && event.touches[0].pageX);
|
||||
let y = event.clientY || (event.touches && event.touches[0].pageY);
|
||||
|
||||
if (this.props.posX) {
|
||||
x -= this.props.posX;
|
||||
}
|
||||
if (this.props.posY) {
|
||||
y -= this.props.posY;
|
||||
}
|
||||
|
||||
hideMenu();
|
||||
|
||||
let menuList = this.getDirentItemMenuList(currentObject, true);
|
||||
|
||||
this.setState({dirent: currentObject});
|
||||
|
||||
let showMenuConfig = {
|
||||
id: 'grid-item-contextmenu',
|
||||
position: { x, y },
|
||||
target: event.target,
|
||||
currentObject: currentObject,
|
||||
menuList: menuList,
|
||||
};
|
||||
|
||||
showMenu(showMenuConfig);
|
||||
}
|
||||
|
||||
getDirentItemMenuList = (dirent, isContextmenu) => {
|
||||
|
||||
let isRepoOwner = this.isRepoOwner;
|
||||
let currentRepoInfo = this.props.currentRepoInfo;
|
||||
let can_set_folder_perm = folderPermEnabled && ((isRepoOwner && currentRepoInfo.has_been_shared_out) || currentRepoInfo.is_admin);
|
||||
|
||||
let type = dirent.type;
|
||||
let permission = dirent.permission;
|
||||
|
||||
let menuList = [];
|
||||
let contextmenuList = [];
|
||||
if (isContextmenu) {
|
||||
let { SHARE, DOWNLOAD, DELETE } = TextTranslation;
|
||||
contextmenuList = this.props.showShareBtn ? [SHARE, DOWNLOAD, DELETE, 'Divider'] : [DOWNLOAD, DELETE, 'Divider'];
|
||||
}
|
||||
|
||||
let { RENAME, MOVE, COPY, PERMISSION, DETAILS, OPEN_VIA_CLIENT, LOCK, UNLOCK, COMMENT, HISTORY, ACCESS_LOG } = TextTranslation;
|
||||
if (type === 'dir' && permission === 'rw') {
|
||||
if (can_set_folder_perm) {
|
||||
menuList = [...contextmenuList, RENAME, MOVE, COPY, 'Divider', PERMISSION, DETAILS, 'Divider', OPEN_VIA_CLIENT];
|
||||
} else {
|
||||
menuList = [...contextmenuList, RENAME, MOVE, COPY, 'Divider', DETAILS, 'Divider', OPEN_VIA_CLIENT];
|
||||
}
|
||||
return menuList;
|
||||
}
|
||||
|
||||
if (type === 'dir' && permission === 'r') {
|
||||
menuList = currentRepoInfo.encrypted ? [...contextmenuList, COPY, DETAILS] : [DETAILS];
|
||||
return menuList;
|
||||
}
|
||||
|
||||
if (type === 'file' && permission === 'rw') {
|
||||
menuList = [...contextmenuList];
|
||||
if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) {
|
||||
menuList.push(RENAME);
|
||||
menuList.push(MOVE);
|
||||
}
|
||||
menuList.push(COPY);
|
||||
if (isPro) {
|
||||
if (dirent.is_locked) {
|
||||
if (dirent.locked_by_me || (dirent.lock_owner === 'OnlineOffice' && permission === 'rw')) {
|
||||
menuList.push(UNLOCK);
|
||||
}
|
||||
} else {
|
||||
menuList.push(LOCK);
|
||||
}
|
||||
}
|
||||
menuList.push('Divider');
|
||||
if (enableFileComment) {
|
||||
menuList.push(COMMENT);
|
||||
}
|
||||
menuList.push(HISTORY);
|
||||
if (fileAuditEnabled) {
|
||||
menuList.push(ACCESS_LOG);
|
||||
}
|
||||
menuList.push(DETAILS);
|
||||
menuList.push('Divider');
|
||||
menuList.push(OPEN_VIA_CLIENT);
|
||||
return menuList;
|
||||
}
|
||||
|
||||
if (type === 'file' && permission === 'r') {
|
||||
menuList = [...contextmenuList];
|
||||
if (!currentRepoInfo.encrypted) {
|
||||
menuList.push(COPY);
|
||||
}
|
||||
if (enableFileComment) {
|
||||
menuList.push(COMMENT);
|
||||
}
|
||||
menuList.push(HISTORY);
|
||||
menuList.push(DETAILS);
|
||||
return menuList;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let {direntList, path} = this.props;
|
||||
let dirent = this.state.dirent;
|
||||
let direntPath = Utils.joinPath(path, dirent.name);
|
||||
|
||||
if (this.props.isDirentListLoading) {
|
||||
return (<Loading />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ul className="grid-view" onClick={this.gridContainerClick}>
|
||||
{
|
||||
direntList.length !== 0 && direntList.map((dirent, index) => {
|
||||
return (
|
||||
<DirentGridItem
|
||||
key={index}
|
||||
dirent={dirent}
|
||||
repoID={this.props.repoID}
|
||||
path={this.props.path}
|
||||
onItemClick={this.props.onItemClick}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
showImagePopup={this.showImagePopup}
|
||||
handleContextClick={this.handleContextClick}
|
||||
onItemMove={this.props.onItemMove}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<ContextMenu
|
||||
id={'grid-item-contextmenu'}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
/>
|
||||
{this.state.isMoveDialogShow &&
|
||||
<MoveDirentDialog
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
repoEncrypted={this.props.currentRepoInfo.encrypted}
|
||||
isMutipleOperation={this.state.isMutipleOperation}
|
||||
onItemMove={this.props.onItemMove}
|
||||
onCancelMove={this.onMoveToggle}
|
||||
dirent={this.state.dirent}
|
||||
/>
|
||||
}
|
||||
{this.state.isZipDialogOpen &&
|
||||
<ModalPortal>
|
||||
<ZipDownloadDialog
|
||||
repoID={this.props.repoID}
|
||||
path={this.props.path}
|
||||
target={dirent.name}
|
||||
toggleDialog={this.closeZipDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
{this.state.isCopyDialogShow &&
|
||||
<CopyDirentDialog
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
repoEncrypted={this.props.currentRepoInfo.encrypted}
|
||||
isMutipleOperation={this.state.isMutipleOperation}
|
||||
onItemCopy={this.props.onItemCopy}
|
||||
onCancelCopy={this.onCopyToggle}
|
||||
dirent={this.state.dirent}
|
||||
/>
|
||||
}
|
||||
{this.state.isShareDialogShow &&
|
||||
<ModalPortal>
|
||||
<ShareDialog
|
||||
itemType={dirent.type}
|
||||
itemName={dirent.name}
|
||||
itemPath={direntPath}
|
||||
userPerm={dirent.permission}
|
||||
repoID={this.props.repoID}
|
||||
repoEncrypted={false}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
toggleDialog={this.closeSharedDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
{this.state.isRenameDialogShow && (
|
||||
<ModalPortal>
|
||||
<Rename
|
||||
dirent={this.state.dirent}
|
||||
onRename={this.onItemRename}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
toggleCancel={this.onItemRenameToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isImagePopupOpen && (
|
||||
<ModalPortal>
|
||||
<ImageDialog
|
||||
imageItems={this.state.imageItems}
|
||||
imageIndex={this.state.imageIndex}
|
||||
closeImagePopup={this.closeImagePopup}
|
||||
moveToPrevImage={this.moveToPrevImage}
|
||||
moveToNextImage={this.moveToNextImage}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DirentGridView.propTypes = propTypes;
|
||||
export default DirentGridView;
|
@@ -24,7 +24,7 @@ class ViewModeToolbar extends React.Component {
|
||||
<React.Fragment>
|
||||
<div className="view-mode btn-group">
|
||||
<button className={`${baseClass} sf2-icon-list-view ${this.props.currentMode === 'list' ? 'current-mode' : ''}`} id='list' title={gettext('List')} onClick={this.switchViewMode}></button>
|
||||
{/* <button className={`${baseClass} sf2-icon-grid-view ${this.props.currentMode === 'grid' ? 'current-mode' : ''}`} id='grid' title={gettext('Grid')} onClick={this.switchViewMode}></button> */}
|
||||
<button className={`${baseClass} sf2-icon-grid-view ${this.props.currentMode === 'grid' ? 'current-mode' : ''}`} id='grid' title={gettext('Grid')} onClick={this.switchViewMode}></button>
|
||||
<button className={`${baseClass} sf2-icon-two-columns ${this.props.currentMode === 'column' ? 'current-mode' : ''}`} id='column' title={gettext('Column')} onClick={this.switchViewMode}></button>
|
||||
</div>
|
||||
<div className="detail-btn btn-group">
|
||||
|
74
frontend/src/css/grid-view.css
Normal file
74
frontend/src/css/grid-view.css
Normal file
@@ -0,0 +1,74 @@
|
||||
.grid-view {
|
||||
padding-top: 10px;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 134px;
|
||||
padding: 10px 4px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.grid-item:hover .grid-file-img-link {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.grid-item:hover a {
|
||||
color: #eb8205;
|
||||
}
|
||||
|
||||
.grid-file-img-link {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0 auto 6px;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
font-size: 0;
|
||||
text-align: center;
|
||||
line-height: 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.grid-file-img-link .thumbnail {
|
||||
max-width: 96px;
|
||||
max-height: 96px;
|
||||
padding: 1px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.grid-file-img-link::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grid-file-name {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 17px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.grid-file-name-link {
|
||||
color: #333;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.grid-file-locked-icon {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 10px;
|
||||
width: 16px;
|
||||
}
|
@@ -114,6 +114,10 @@ class LibContentContainer extends React.Component {
|
||||
this.props.closeDirentDetail();
|
||||
}
|
||||
|
||||
onGridItemClick = (dirent) => {
|
||||
this.setState({currentDirent: dirent});
|
||||
}
|
||||
|
||||
// on '<tr>'
|
||||
onDirentClick = (dirent) => {
|
||||
this.setState({currentDirent: dirent});
|
||||
@@ -203,6 +207,32 @@ class LibContentContainer extends React.Component {
|
||||
)}
|
||||
{this.props.currentMode === 'grid' && (
|
||||
<DirGridView
|
||||
path={this.props.path}
|
||||
repoID={repoID}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
repoPermission={this.props.repoPermission}
|
||||
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
|
||||
enableDirPrivateShare={this.props.enableDirPrivateShare}
|
||||
onRenameNode={this.props.onRenameNode}
|
||||
isRepoInfoBarShow={isRepoInfoBarShow}
|
||||
usedRepoTags={this.props.usedRepoTags}
|
||||
readmeMarkdown={this.props.readmeMarkdown}
|
||||
draftCounts={this.props.draftCounts}
|
||||
updateUsedRepoTags={this.props.updateUsedRepoTags}
|
||||
isDirentListLoading={this.props.isDirentListLoading}
|
||||
direntList={this.props.direntList}
|
||||
onAddFile={this.props.onAddFile}
|
||||
onItemClick={this.onItemClick}
|
||||
onItemDelete={this.props.onItemDelete}
|
||||
onItemMove={this.props.onItemMove}
|
||||
onItemCopy={this.props.onItemCopy}
|
||||
updateDirent={this.props.updateDirent}
|
||||
onAddFolder={this.props.onAddFolder}
|
||||
showShareBtn={this.props.showShareBtn}
|
||||
showDirentDetail={this.props.showDirentDetail}
|
||||
onGridItemClick={this.onGridItemClick}
|
||||
isDirentDetailShow={this.props.isDirentDetailShow}
|
||||
onItemRename={this.props.onItemRename}
|
||||
/>
|
||||
)}
|
||||
{this.props.currentMode === 'column' && (
|
||||
|
Reference in New Issue
Block a user