1
0
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:
llj
2021-03-18 17:31:21 +08:00
committed by GitHub
parent 2405ba671e
commit 06254447e4
12 changed files with 232 additions and 27 deletions

View 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;

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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}}

View File

@@ -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%;