mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 07:27:04 +00:00
File view op (#4836)
* [file view] added 'Details' & 'open via client' * [file view] adjusted places of operations * [file view] 'open via client': use a new icon * [file view] 'open via client': updated the icon
This commit is contained in:
127
frontend/src/components/dirent-detail/file-details.js
Normal file
127
frontend/src/components/dirent-detail/file-details.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import FileTag from '../../models/file-tag';
|
||||
|
||||
import '../../css/dirent-detail.css';
|
||||
|
||||
const propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
repoName: PropTypes.string.isRequired,
|
||||
dirent: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
togglePanel: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class FileDetails extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
direntType: '',
|
||||
direntDetail: '',
|
||||
fileTagList: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let { dirent, path, repoID } = this.props;
|
||||
let direntPath = Utils.joinPath(path, dirent.name);
|
||||
seafileAPI.getFileInfo(repoID, direntPath).then(res => {
|
||||
this.setState({
|
||||
direntType: 'file',
|
||||
direntDetail: res.data
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
seafileAPI.listFileTags(repoID, direntPath).then(res => {
|
||||
let fileTagList = [];
|
||||
res.data.file_tags.forEach(item => {
|
||||
let file_tag = new FileTag(item);
|
||||
fileTagList.push(file_tag);
|
||||
});
|
||||
this.setState({fileTagList: fileTagList});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
renderHeader = (smallIconUrl, direntName) => {
|
||||
return (
|
||||
<div className="detail-header">
|
||||
<div className="detail-control sf2-icon-x1" onClick={this.props.togglePanel}></div>
|
||||
<div className="detail-title dirent-title">
|
||||
<img src={smallIconUrl} width="24" height="24" alt="" />{' '}
|
||||
<span className="name ellipsis" title={direntName}>{direntName}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDetailBody = (bigIconUrl) => {
|
||||
const { direntDetail, fileTagList } = this.state;
|
||||
const { repoName, path } = this.props;
|
||||
return (
|
||||
<div className="detail-body dirent-info">
|
||||
<div className="img"><img src={bigIconUrl} className="thumbnail" alt="" /></div>
|
||||
{this.state.direntDetail &&
|
||||
<div className="dirent-table-container">
|
||||
<table className="table-thead-hidden">
|
||||
<thead>
|
||||
<tr><th width="35%"></th><th width="65%"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>{gettext('Size')}</th><td>{Utils.bytesToSize(direntDetail.size)}</td></tr>
|
||||
<tr><th>{gettext('Location')}</th><td>{repoName + path}</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>
|
||||
<td>
|
||||
<ul className="file-tag-list">
|
||||
{fileTagList.map((fileTag) => {
|
||||
return (
|
||||
<li key={fileTag.id} className="file-tag-item">
|
||||
<span className="file-tag" style={{backgroundColor:fileTag.color}}></span>
|
||||
<span className="tag-name" title={fileTag.name}>{fileTag.name}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { dirent, repoID, path } = this.props;
|
||||
|
||||
const smallIconUrl = Utils.getFileIconUrl(dirent.name);
|
||||
let bigIconUrl = Utils.getFileIconUrl(dirent.name, 192);
|
||||
const isImg = Utils.imageCheck(dirent.name);
|
||||
if (isImg) {
|
||||
bigIconUrl = `${siteRoot}thumbnail/${repoID}/1024` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`);
|
||||
}
|
||||
return (
|
||||
<div className="detail-container file-details-container">
|
||||
{this.renderHeader(smallIconUrl, dirent.name)}
|
||||
{this.renderDetailBody(bigIconUrl)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FileDetails.propTypes = propTypes;
|
||||
|
||||
export default FileDetails;
|
@@ -1,6 +1,6 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ButtonGroup, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import { ButtonGroup, ButtonDropdown, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import IconButton from '../icon-button';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
@@ -14,11 +14,12 @@ const propTypes = {
|
||||
isSaving: PropTypes.bool,
|
||||
needSave: PropTypes.bool,
|
||||
toggleLockFile: PropTypes.func.isRequired,
|
||||
toggleCommentPanel: PropTypes.func.isRequired
|
||||
toggleCommentPanel: PropTypes.func.isRequired,
|
||||
toggleDetailsPanel: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const {
|
||||
canLockUnlockFile, canGenerateShareLink,
|
||||
canLockUnlockFile,
|
||||
repoID, repoName, repoEncrypted, parentDir, filePerm, filePath,
|
||||
fileName,
|
||||
canEditFile, err,
|
||||
@@ -32,7 +33,8 @@ class FileToolbar extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
dropdownOpen: false,
|
||||
isShareDialogOpen: false
|
||||
moreDropdownOpen: false,
|
||||
isShareDialogOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,14 +42,25 @@ class FileToolbar extends React.Component {
|
||||
this.setState({isShareDialogOpen: !this.state.isShareDialogOpen});
|
||||
}
|
||||
|
||||
toggleMoreOpMenu = () => {
|
||||
this.setState({
|
||||
moreDropdownOpen: !this.state.moreDropdownOpen
|
||||
});
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.setState({
|
||||
dropdownOpen: !this.state.dropdownOpen
|
||||
});
|
||||
}
|
||||
|
||||
openFileViaClient = () => {
|
||||
location.href = `seafile://openfile?repo_id=${encodeURIComponent(repoID)}&path=${encodeURIComponent(filePath)}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLocked, lockedByMe } = this.props;
|
||||
const { moreDropdownOpen } = this.state;
|
||||
let showLockUnlockBtn = false;
|
||||
let lockUnlockText, lockUnlockIcon;
|
||||
if (canLockUnlockFile) {
|
||||
@@ -95,15 +108,7 @@ class FileToolbar extends React.Component {
|
||||
onClick={this.toggleShareDialog}
|
||||
/>
|
||||
)}
|
||||
{filePerm == 'rw' && (
|
||||
<IconButton
|
||||
id="history"
|
||||
icon="fa fa-history"
|
||||
text={gettext('History')}
|
||||
tag="a"
|
||||
href={`${siteRoot}repo/file_revisions/${repoID}/?p=${encodeURIComponent(filePath)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(canEditFile && !err) &&
|
||||
( this.props.isSaving ?
|
||||
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
|
||||
@@ -132,14 +137,40 @@ class FileToolbar extends React.Component {
|
||||
href="?dl=1"
|
||||
/>
|
||||
)}
|
||||
{enableComment && (
|
||||
<IconButton
|
||||
id="file-details"
|
||||
icon='fas fa-info'
|
||||
text={gettext('Details')}
|
||||
onClick={this.props.toggleDetailsPanel}
|
||||
/>
|
||||
{filePerm == 'rw' && (
|
||||
<IconButton
|
||||
id="comment"
|
||||
icon="fa fa-comments"
|
||||
text={gettext('Comment')}
|
||||
onClick={this.props.toggleCommentPanel}
|
||||
id="open-via-client"
|
||||
icon="sf3-font sf3-font-desktop"
|
||||
text={gettext('Open via Client')}
|
||||
tag="a"
|
||||
href={`seafile://openfile?repo_id=${encodeURIComponent(repoID)}&path=${encodeURIComponent(filePath)}`}
|
||||
/>
|
||||
)}
|
||||
<ButtonDropdown isOpen={moreDropdownOpen} toggle={this.toggleMoreOpMenu}>
|
||||
<DropdownToggle>
|
||||
<span className="fas fa-ellipsis-v"></span>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right={true}>
|
||||
{enableComment && (
|
||||
<DropdownItem onClick={this.props.toggleCommentPanel}>
|
||||
{gettext('Comment')}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{filePerm == 'rw' && (
|
||||
<DropdownItem>
|
||||
<a href={`${siteRoot}repo/file_revisions/${repoID}/?p=${encodeURIComponent(filePath)}&referer=${encodeURIComponent(location.href)}`} className="text-inherit">
|
||||
{gettext('History')}
|
||||
</a>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</ButtonDropdown>
|
||||
</ButtonGroup>
|
||||
|
||||
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.toggle} className="d-block d-md-none">
|
||||
@@ -202,6 +233,7 @@ class FileToolbar extends React.Component {
|
||||
{gettext('Comment')}
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem onClick={this.props.toggleDetailsPanel}>{gettext('Details')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
|
||||
|
@@ -8,6 +8,7 @@ import toaster from '../toast';
|
||||
import FileInfo from './file-info';
|
||||
import FileToolbar from './file-toolbar';
|
||||
import CommentPanel from './comment-panel';
|
||||
import FileDetails from '../dirent-detail/file-details';
|
||||
|
||||
import '../../css/file-view.css';
|
||||
|
||||
@@ -21,7 +22,8 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const { isStarred, isLocked, lockedByMe,
|
||||
repoID, filePath, enableWatermark, userNickName
|
||||
repoID, filePath, enableWatermark, userNickName,
|
||||
repoName, parentDir, fileName
|
||||
} = window.app.pageOptions;
|
||||
|
||||
|
||||
@@ -34,9 +36,14 @@ class FileView extends React.Component {
|
||||
isLocked: isLocked,
|
||||
lockedByMe: lockedByMe,
|
||||
isCommentPanelOpen: false,
|
||||
isDetailsPanelOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleDetailsPanel = () => {
|
||||
this.setState({isDetailsPanelOpen: !this.state.isDetailsPanelOpen});
|
||||
}
|
||||
|
||||
toggleCommentPanel = () => {
|
||||
this.setState({
|
||||
isCommentPanelOpen: !this.state.isCommentPanelOpen
|
||||
@@ -90,6 +97,7 @@ class FileView extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isDetailsPanelOpen } = this.state;
|
||||
return (
|
||||
<div className="h-100 d-flex flex-column">
|
||||
<div className="file-view-header d-flex justify-content-between align-items-center">
|
||||
@@ -106,6 +114,7 @@ class FileView extends React.Component {
|
||||
needSave={this.props.needSave}
|
||||
toggleLockFile={this.toggleLockFile}
|
||||
toggleCommentPanel={this.toggleCommentPanel}
|
||||
toggleDetailsPanel={this.toggleDetailsPanel}
|
||||
/>
|
||||
</div>
|
||||
<div className="file-view-body flex-auto d-flex o-hidden">
|
||||
@@ -117,6 +126,15 @@ class FileView extends React.Component {
|
||||
onParticipantsChange={this.props.onParticipantsChange}
|
||||
/>
|
||||
}
|
||||
{isDetailsPanelOpen &&
|
||||
<FileDetails
|
||||
repoID={repoID}
|
||||
repoName={repoName}
|
||||
path={parentDir}
|
||||
dirent={{'name': fileName, type: 'file'}}
|
||||
togglePanel={this.toggleDetailsPanel}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -29,7 +29,7 @@ class IconButton extends React.Component {
|
||||
const className = 'btn-icon';
|
||||
const btnContent = (
|
||||
<React.Fragment>
|
||||
<span className={this.props.icon}></span>
|
||||
<i className={this.props.icon}></i>
|
||||
<Tooltip
|
||||
toggle={this.toggle}
|
||||
delay={{show: 0, hide: 0}}
|
||||
|
@@ -40,6 +40,27 @@ body {
|
||||
.tip {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.file-details-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
box-shadow: -1px 0 3px 0 #ccc;
|
||||
animation: move .5s ease-in-out 1;
|
||||
z-index: 50;
|
||||
}
|
||||
@keyframes move {
|
||||
from {
|
||||
right: -500px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
to {
|
||||
right: 0px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
/* for mobile */
|
||||
.file-view-body .seafile-comment {
|
||||
width: 100%;
|
||||
|
Reference in New Issue
Block a user