mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-28 03:10:45 +00:00
parent
5c2f05ee8d
commit
6ed1fe58bc
51
frontend/src/components/dirent-detail/detail-item/index.css
Normal file
51
frontend/src/components/dirent-detail/detail-item/index.css
Normal file
@ -0,0 +1,51 @@
|
||||
.dirent-detail-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name-container {
|
||||
width: 160px;
|
||||
padding: 7px 6px;
|
||||
min-height: 34px;
|
||||
height: fit-content;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name-container .sf-metadata-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 14px;
|
||||
fill: #999;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-value {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
padding: 7px 6px;
|
||||
min-height: 34px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-value.editable:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name-container:hover,
|
||||
.dirent-detail-item .dirent-detail-item-value:hover {
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 3px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-value .text-formatter,
|
||||
.dirent-detail-item .dirent-detail-item-value .ctime-formatter {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dirent-detail-item-value .creator-formatter {
|
||||
height: 20px;
|
||||
}
|
36
frontend/src/components/dirent-detail/detail-item/index.js
Normal file
36
frontend/src/components/dirent-detail/detail-item/index.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Formatter, Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import classnames from 'classnames';
|
||||
import { CellType, COLUMNS_ICON_CONFIG } from '../../../metadata/metadata-view/_basic';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DetailItem = ({ field, value, valueId, valueClick, children, ...params }) => {
|
||||
const icon = useMemo(() => {
|
||||
if (field.type === 'size') return COLUMNS_ICON_CONFIG[CellType.NUMBER];
|
||||
return COLUMNS_ICON_CONFIG[field.type];
|
||||
}, [field]);
|
||||
|
||||
return (
|
||||
<div className="dirent-detail-item">
|
||||
<div className="dirent-detail-item-name-container">
|
||||
<Icon iconName={icon} />
|
||||
<span className="dirent-detail-item-name">{field.name}</span>
|
||||
</div>
|
||||
<div className={classnames('dirent-detail-item-value', { 'editable': valueClick })} id={valueId} onClick={valueClick}>
|
||||
{children ? children : (<Formatter { ...params } field={field} value={value}/>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DetailItem.propTypes = {
|
||||
field: PropTypes.object.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
|
||||
children: PropTypes.any,
|
||||
valueId: PropTypes.string,
|
||||
};
|
||||
|
||||
export default DetailItem;
|
||||
|
@ -44,6 +44,14 @@ class DetailListView extends React.Component {
|
||||
return position;
|
||||
};
|
||||
|
||||
getDirentPath = () => {
|
||||
if (Utils.isMarkdownFile(this.props.path)) {
|
||||
return this.props.path; // column mode: view file
|
||||
}
|
||||
let { dirent, path } = this.props;
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
};
|
||||
|
||||
onEditFileTagToggle = () => {
|
||||
this.setState({
|
||||
isEditFileTagShow: !this.state.isEditFileTagShow
|
||||
@ -55,14 +63,6 @@ class DetailListView extends React.Component {
|
||||
this.props.onFileTagChanged(this.props.dirent, direntPath);
|
||||
};
|
||||
|
||||
getDirentPath = () => {
|
||||
if (Utils.isMarkdownFile(this.props.path)) {
|
||||
return this.props.path; // column mode: view file
|
||||
}
|
||||
let { dirent, path } = this.props;
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
};
|
||||
|
||||
toggleExtraMetadataPropertiesDialog = () => {
|
||||
this.setState({ isShowMetadataExtraProperties: !this.state.isShowMetadataExtraProperties });
|
||||
};
|
||||
|
@ -1,158 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { siteRoot, enableVideoThumbnail } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import Dirent from '../../models/dirent';
|
||||
import DetailListView from './detail-list-view';
|
||||
|
||||
import '../../css/dirent-detail.css';
|
||||
|
||||
const propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
dirent: PropTypes.object,
|
||||
path: PropTypes.string.isRequired,
|
||||
currentRepoInfo: PropTypes.object.isRequired,
|
||||
onItemDetailsClose: PropTypes.func.isRequired,
|
||||
onFileTagChanged: PropTypes.func.isRequired,
|
||||
direntDetailPanelTab: PropTypes.string,
|
||||
repoTags: PropTypes.array,
|
||||
fileTags: PropTypes.array,
|
||||
};
|
||||
|
||||
class DirentDetail extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
direntType: '',
|
||||
direntDetail: '',
|
||||
folderDirent: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let { dirent, path, repoID } = this.props;
|
||||
this.loadDirentInfo(dirent, path, repoID);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
let { dirent, path, repoID } = nextProps;
|
||||
if (this.props.dirent !== nextProps.dirent) {
|
||||
this.loadDirentInfo(dirent, path, repoID);
|
||||
}
|
||||
}
|
||||
|
||||
loadDirentInfo = (dirent, path, repoID) => {
|
||||
if (dirent) {
|
||||
let direntPath = Utils.joinPath(path, dirent.name);
|
||||
this.updateDetailView(dirent, direntPath);
|
||||
} else {
|
||||
let dirPath = Utils.getDirName(path);
|
||||
seafileAPI.listDir(repoID, dirPath).then(res => {
|
||||
let direntList = res.data.dirent_list;
|
||||
let folderDirent = null;
|
||||
for (let i = 0; i < direntList.length; i++) {
|
||||
let dirent = direntList[i];
|
||||
if (dirent.parent_dir + dirent.name === path) {
|
||||
folderDirent = new Dirent(dirent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setState({ folderDirent: folderDirent });
|
||||
this.updateDetailView(folderDirent, path);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateDetailView = (dirent, direntPath) => {
|
||||
let repoID = this.props.repoID;
|
||||
if (dirent.type === 'file') {
|
||||
seafileAPI.getFileInfo(repoID, direntPath).then(res => {
|
||||
this.setState({
|
||||
direntType: 'file',
|
||||
direntDetail: res.data,
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
} else {
|
||||
seafileAPI.getDirInfo(repoID, direntPath).then(res => {
|
||||
this.setState({
|
||||
direntType: 'dir',
|
||||
direntDetail: res.data
|
||||
});
|
||||
}).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.onItemDetailsClose}></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, folderDirent) => {
|
||||
const { dirent, fileTags } = 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">
|
||||
<DetailListView
|
||||
repoInfo={this.props.currentRepoInfo}
|
||||
path={this.props.path}
|
||||
repoID={this.props.repoID}
|
||||
dirent={this.props.dirent || folderDirent}
|
||||
direntType={this.state.direntType}
|
||||
direntDetail={this.state.direntDetail}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTagList={dirent ? dirent.file_tags : fileTags}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
let { dirent, repoID, path } = this.props;
|
||||
let { folderDirent } = this.state;
|
||||
if (!dirent && !folderDirent) {
|
||||
return '';
|
||||
}
|
||||
let smallIconUrl = dirent ? Utils.getDirentIcon(dirent) : Utils.getDirentIcon(folderDirent);
|
||||
let bigIconUrl = dirent ? Utils.getDirentIcon(dirent, true) : Utils.getDirentIcon(folderDirent, true);
|
||||
const isImg = dirent ? Utils.imageCheck(dirent.name) : Utils.imageCheck(folderDirent.name);
|
||||
const isVideo = dirent ? Utils.videoCheck(dirent.name) : Utils.videoCheck(folderDirent.name);
|
||||
if (isImg || (enableVideoThumbnail && isVideo)) {
|
||||
bigIconUrl = `${siteRoot}thumbnail/${repoID}/1024` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`);
|
||||
}
|
||||
let direntName = dirent ? dirent.name : folderDirent.name;
|
||||
return (
|
||||
<div className="detail-container">
|
||||
{this.renderHeader(smallIconUrl, direntName)}
|
||||
{this.renderDetailBody(bigIconUrl, folderDirent)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirentDetail.propTypes = propTypes;
|
||||
|
||||
export default DirentDetail;
|
@ -0,0 +1,40 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getDirentPath, getDirentPosition } from './utils';
|
||||
import DetailItem from '../detail-item';
|
||||
import { CellType } from '../../../metadata/metadata-view/_basic';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import EditMetadata from './edit-metadata';
|
||||
|
||||
const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }) => {
|
||||
const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]);
|
||||
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailItem field={{ type: CellType.TEXT, name: gettext('File location') }} value={position} />
|
||||
<DetailItem field={{ type: 'size', name: gettext('Size') }} value={repoInfo.size} />
|
||||
<DetailItem field={{ type: CellType.CREATOR, name: gettext('Creator') }} value={repoInfo.owner_email} collaborators={[{
|
||||
name: repoInfo.owner_name,
|
||||
contact_email: repoInfo.owner_contact_email,
|
||||
email: repoInfo.owner_email,
|
||||
avatar_url: repoInfo.owner_avatar,
|
||||
}]} />
|
||||
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={direntDetail.mtime} />
|
||||
{direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && (
|
||||
<EditMetadata repoID={repoID} direntPath={direntPath} direntType={direntType} direntDetail={direntDetail} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DirDetails.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
repoInfo: PropTypes.object,
|
||||
dirent: PropTypes.object,
|
||||
direntType: PropTypes.string,
|
||||
path: PropTypes.string,
|
||||
direntDetail: PropTypes.object,
|
||||
};
|
||||
|
||||
export default DirDetails;
|
@ -0,0 +1,31 @@
|
||||
.detail-edit-metadata-btn {
|
||||
height: 34px;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
padding: 0 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-edit-metadata-btn .seafile-multicolor-icon {
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
fill: #999;
|
||||
}
|
||||
|
||||
.detail-edit-metadata-btn:hover {
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.detail-edit-metadata-btn .detail-edit-metadata-btn-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ExtraMetadataAttributesDialog from '../../../dialog/extra-metadata-attributes-dialog';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import Icon from '../../../icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const EditMetadata = ({ repoID, direntPath, direntType, direntDetail }) => {
|
||||
const [isShowDialog, setShowDialog] = useState(false);
|
||||
const onToggle = useCallback(() => {
|
||||
setShowDialog(!isShowDialog);
|
||||
}, [isShowDialog]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="detail-edit-metadata-btn" onClick={onToggle}>
|
||||
<Icon symbol="add-table" />
|
||||
<span className="detail-edit-metadata-btn-title">{gettext('Edit metadata properties')}</span>
|
||||
</div>
|
||||
{isShowDialog && (
|
||||
<ExtraMetadataAttributesDialog
|
||||
repoID={repoID}
|
||||
filePath={direntPath}
|
||||
direntType={direntType}
|
||||
direntDetail={direntDetail}
|
||||
onToggle={onToggle}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditMetadata.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
direntPath: PropTypes.string,
|
||||
direntType: PropTypes.string,
|
||||
direntDetail: PropTypes.object,
|
||||
};
|
||||
|
||||
export default EditMetadata;
|
@ -0,0 +1,74 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { getDirentPath, getDirentPosition } from './utils';
|
||||
import DetailItem from '../detail-item';
|
||||
import { CellType } from '../../../metadata/metadata-view/_basic';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import EditMetadata from './edit-metadata';
|
||||
import EditFileTagPopover from '../../popover/edit-filetag-popover';
|
||||
import FileTagList from '../../file-tag-list';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const FileDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => {
|
||||
const [isEditFileTagShow, setEditFileTagShow] = useState(false);
|
||||
|
||||
const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]);
|
||||
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
|
||||
const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []);
|
||||
|
||||
const onEditFileTagToggle = useCallback(() => {
|
||||
setEditFileTagShow(!isEditFileTagShow);
|
||||
}, [isEditFileTagShow]);
|
||||
|
||||
const fileTagChanged = useCallback(() => {
|
||||
onFileTagChanged(dirent, direntPath);
|
||||
}, [dirent, direntPath, onFileTagChanged]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailItem field={{ type: CellType.TEXT, name: gettext('File location') }} value={position} />
|
||||
<DetailItem field={{ type: 'size', name: gettext('Size') }} value={Utils.bytesToSize(direntDetail.size)} />
|
||||
<DetailItem field={{ type: CellType.CREATOR, name: gettext('Creator') }} value={direntDetail.last_modifier_email} collaborators={[{
|
||||
name: direntDetail.last_modifier_name,
|
||||
contact_email: direntDetail.last_modifier_contact_email,
|
||||
email: direntDetail.last_modifier_email,
|
||||
avatar_url: direntDetail.last_modifier_avatar,
|
||||
}]} />
|
||||
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={direntDetail.last_modified} />
|
||||
<DetailItem field={{ type: CellType.SINGLE_SELECT, name: gettext('Tags') }} valueId={tagListTitleID} valueClick={onEditFileTagToggle} >
|
||||
{Array.isArray(fileTagList) && fileTagList.length > 0 ? (
|
||||
<FileTagList fileTagList={fileTagList} />
|
||||
) : (
|
||||
<span className="empty-tip-text">{gettext('Empty')}</span>
|
||||
)}
|
||||
</DetailItem>
|
||||
{direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && (
|
||||
<EditMetadata repoID={repoID} direntPath={direntPath} direntType={direntType} direntDetail={direntDetail} />
|
||||
)}
|
||||
{isEditFileTagShow &&
|
||||
<EditFileTagPopover
|
||||
repoID={repoID}
|
||||
repoTags={repoTags}
|
||||
filePath={direntPath}
|
||||
fileTagList={fileTagList}
|
||||
toggleCancel={onEditFileTagToggle}
|
||||
onFileTagChanged={fileTagChanged}
|
||||
target={tagListTitleID}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FileDetails.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
repoInfo: PropTypes.object,
|
||||
dirent: PropTypes.object,
|
||||
direntType: PropTypes.string,
|
||||
path: PropTypes.string,
|
||||
direntDetail: PropTypes.object,
|
||||
onFileTagChanged: PropTypes.func,
|
||||
};
|
||||
|
||||
export default FileDetails;
|
@ -0,0 +1,29 @@
|
||||
.detail-container .detail-image-thumbnail {
|
||||
height: 144px;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-container .detail-image-thumbnail .thumbnail {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
float: none;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.detail-container .empty-tip-text {
|
||||
color: #666
|
||||
}
|
121
frontend/src/components/dirent-detail/dirent-details/index.js
Normal file
121
frontend/src/components/dirent-detail/dirent-details/index.js
Normal file
@ -0,0 +1,121 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { siteRoot } from '../../../utils/constants';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import toaster from '../../toast';
|
||||
import Dirent from '../../../models/dirent';
|
||||
import Header from '../header';
|
||||
import DirDetails from './dir-details';
|
||||
import FileDetails from './file-details';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DirentDetails = ({ dirent, path, repoID, currentRepoInfo, repoTags, fileTags, onItemDetailsClose, onFileTagChanged }) => {
|
||||
const [direntType, setDirentType] = useState('');
|
||||
const [direntDetail, setDirentDetail] = useState('');
|
||||
const [folderDirent, setFolderDirent] = useState(null);
|
||||
const direntRef = useRef(null);
|
||||
|
||||
const updateDetailView = useCallback((repoID, dirent, direntPath) => {
|
||||
const apiName = dirent.type === 'file' ? 'getFileInfo' : 'getDirInfo';
|
||||
seafileAPI[apiName](repoID, direntPath).then(res => {
|
||||
setDirentType(dirent.type === 'file' ? 'file' : 'dir');
|
||||
setDirentDetail(res.data);
|
||||
}).catch(error => {
|
||||
const errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (direntRef.current && dirent === direntRef.current) return;
|
||||
direntRef.current = dirent;
|
||||
if (dirent) {
|
||||
const direntPath = Utils.joinPath(path, dirent.name);
|
||||
updateDetailView(repoID, dirent, direntPath);
|
||||
return;
|
||||
}
|
||||
const dirPath = Utils.getDirName(path);
|
||||
seafileAPI.listDir(repoID, dirPath).then(res => {
|
||||
const direntList = res.data.dirent_list;
|
||||
let folderDirent = null;
|
||||
for (let i = 0; i < direntList.length; i++) {
|
||||
let dirent = direntList[i];
|
||||
if (dirent.parent_dir + dirent.name === path) {
|
||||
folderDirent = new Dirent(dirent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setFolderDirent(folderDirent);
|
||||
updateDetailView(repoID, folderDirent, path);
|
||||
}).catch(error => {
|
||||
const errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dirent, path, repoID]);
|
||||
|
||||
if (!dirent && !folderDirent) return '';
|
||||
const direntName = dirent ? dirent.name : folderDirent.name;
|
||||
const smallIconUrl = dirent ? Utils.getDirentIcon(dirent) : Utils.getDirentIcon(folderDirent);
|
||||
// let bigIconUrl = dirent ? Utils.getDirentIcon(dirent, true) : Utils.getDirentIcon(folderDirent, true);
|
||||
let bigIconUrl = '';
|
||||
const isImg = dirent ? Utils.imageCheck(dirent.name) : Utils.imageCheck(folderDirent.name);
|
||||
// const isVideo = dirent ? Utils.videoCheck(dirent.name) : Utils.videoCheck(folderDirent.name);
|
||||
if (isImg) {
|
||||
bigIconUrl = `${siteRoot}thumbnail/${repoID}/1024` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`);
|
||||
}
|
||||
return (
|
||||
<div className="detail-container">
|
||||
<Header title={direntName} icon={smallIconUrl} onClose={onItemDetailsClose} />
|
||||
<div className="detail-body dirent-info">
|
||||
{isImg && (
|
||||
<div className="detail-image-thumbnail">
|
||||
<img src={bigIconUrl} alt="" className="thumbnail" />
|
||||
</div>
|
||||
)}
|
||||
{direntDetail && (
|
||||
<div className="detail-content">
|
||||
{direntType === 'dir' ? (
|
||||
<DirDetails
|
||||
repoID={repoID}
|
||||
repoInfo={currentRepoInfo}
|
||||
dirent={dirent || folderDirent}
|
||||
direntType={direntType}
|
||||
direntDetail={direntDetail}
|
||||
path={path}
|
||||
/>
|
||||
) : (
|
||||
<FileDetails
|
||||
repoID={repoID}
|
||||
repoInfo={currentRepoInfo}
|
||||
dirent={dirent || folderDirent}
|
||||
direntType={direntType}
|
||||
path={path}
|
||||
direntDetail={direntDetail}
|
||||
repoTags={repoTags}
|
||||
fileTagList={dirent ? dirent.file_tags : fileTags}
|
||||
onFileTagChanged={onFileTagChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DirentDetails.propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
dirent: PropTypes.object,
|
||||
path: PropTypes.string.isRequired,
|
||||
currentRepoInfo: PropTypes.object.isRequired,
|
||||
onItemDetailsClose: PropTypes.func.isRequired,
|
||||
onFileTagChanged: PropTypes.func.isRequired,
|
||||
direntDetailPanelTab: PropTypes.string,
|
||||
repoTags: PropTypes.array,
|
||||
fileTags: PropTypes.array,
|
||||
};
|
||||
|
||||
export default DirentDetails;
|
@ -0,0 +1,14 @@
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
export const getDirentPath = (dirent, path) => {
|
||||
if (Utils.isMarkdownFile(path)) return path; // column mode: view file
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
};
|
||||
|
||||
export const getDirentPosition = (repoInfo, dirent, path) => {
|
||||
const direntPath = getDirentPath(dirent, path);
|
||||
const position = repoInfo.repo_name;
|
||||
if (direntPath === '/') return position;
|
||||
const index = direntPath.lastIndexOf('/');
|
||||
return position + direntPath.slice(0, index);
|
||||
};
|
45
frontend/src/components/dirent-detail/header/index.css
Normal file
45
frontend/src/components/dirent-detail/header/index.css
Normal file
@ -0,0 +1,45 @@
|
||||
.detail-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
line-height: 2.5rem;
|
||||
background-color: #f9f9f9;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
height: 48px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.detail-header .detail-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
width: 0; /* prevent strut flex layout */
|
||||
}
|
||||
|
||||
.detail-header .detail-title .name {
|
||||
margin: 0 0.5rem 0 6px;
|
||||
line-height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
font-size: 1rem;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.detail-header .detail-control {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.detail-header .detail-control .detail-control-close {
|
||||
font-size: 16px;
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.detail-header .detail-control:hover {
|
||||
background-color: #EFEFEF;
|
||||
border-radius: 3px;
|
||||
}
|
28
frontend/src/components/dirent-detail/header/index.js
Normal file
28
frontend/src/components/dirent-detail/header/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from '../../icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Header = ({ title, icon, onClose }) => {
|
||||
|
||||
return (
|
||||
<div className="detail-header">
|
||||
<div className="detail-title dirent-title">
|
||||
<img src={icon} width="32" height="32" alt="" />
|
||||
<span className="name ellipsis" title={title}>{title}</span>
|
||||
</div>
|
||||
<div className="detail-control" onClick={onClose}>
|
||||
<Icon symbol="close" className="detail-control-close" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Header.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Header;
|
33
frontend/src/components/dirent-detail/index.css
Normal file
33
frontend/src/components/dirent-detail/index.css
Normal file
@ -0,0 +1,33 @@
|
||||
.detail-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-container .detail-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.dirent-info .img {
|
||||
height: 10rem;
|
||||
padding: 0.5rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dirent-info .img .thumbnail {
|
||||
max-width: calc(100% - 4px);
|
||||
max-height: 100%;
|
||||
display: inline-block;
|
||||
}
|
9
frontend/src/components/dirent-detail/index.js
Normal file
9
frontend/src/components/dirent-detail/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import LibDetail from './lib-details';
|
||||
import DirentDetail from './dirent-details';
|
||||
|
||||
import './index.css';
|
||||
|
||||
export {
|
||||
LibDetail,
|
||||
DirentDetail,
|
||||
};
|
@ -1,82 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import toaster from '../toast';
|
||||
import '../../css/dirent-detail.css';
|
||||
|
||||
const propTypes = {
|
||||
currentRepo: PropTypes.object.isRequired,
|
||||
closeDetails: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class LibDetail extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
fileCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let repo = this.props.currentRepo;
|
||||
this.getFileCounts(repo);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.currentRepo.repo_id !== this.props.currentRepo.repo_id) {
|
||||
this.getFileCounts(nextProps.currentRepo);
|
||||
}
|
||||
}
|
||||
|
||||
getFileCounts = (repo) => {
|
||||
seafileAPI.getRepoInfo(repo.repo_id).then(res => {
|
||||
this.setState({ fileCount: res.data.file_count });
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let repo = this.props.currentRepo;
|
||||
let smallIconUrl = Utils.getLibIconUrl(repo);
|
||||
let bigIconUrl = Utils.getLibIconUrl(repo, true);
|
||||
|
||||
return (
|
||||
<div className="detail-container">
|
||||
<div className="detail-header">
|
||||
<div className="detail-control sf2-icon-x1" onClick={this.props.closeDetails}></div>
|
||||
<div className="detail-title dirent-title">
|
||||
<img src={smallIconUrl} width="24" height="24" alt="" />{' '}
|
||||
<span className="name ellipsis" title={repo.repo_name}>{repo.repo_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="detail-body dirent-info">
|
||||
<div className="img">
|
||||
<img src={bigIconUrl} height="96" alt="" />
|
||||
</div>
|
||||
<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('Files')}</th><td>{this.state.fileCount}</td></tr>
|
||||
<tr><th>{gettext('Size')}</th><td>{repo.size}</td></tr>
|
||||
<tr><th>{gettext('Last Update')}</th><td>{ moment(repo.last_modified).fromNow()}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LibDetail.propTypes = propTypes;
|
||||
|
||||
export default LibDetail;
|
62
frontend/src/components/dirent-detail/lib-details/index.js
Normal file
62
frontend/src/components/dirent-detail/lib-details/index.js
Normal file
@ -0,0 +1,62 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import toaster from '../../toast';
|
||||
import Header from '../header';
|
||||
import Repo from '../../../models/repo';
|
||||
import Loading from '../../loading';
|
||||
import DetailItem from '../detail-item';
|
||||
import { CellType } from '../../../metadata/metadata-view/_basic';
|
||||
|
||||
const LibDetail = React.memo(({ currentRepo, closeDetails }) => {
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [repo, setRepo] = useState({});
|
||||
const smallIconUrl = useMemo(() => Utils.getLibIconUrl(currentRepo), [currentRepo]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
seafileAPI.getRepoInfo(currentRepo.repo_id).then(res => {
|
||||
const repo = new Repo(res.data);
|
||||
setRepo(repo);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}, [currentRepo.repo_id]);
|
||||
|
||||
return (
|
||||
<div className="detail-container">
|
||||
<Header title={currentRepo.repo_name} icon={smallIconUrl} onClose={closeDetails} />
|
||||
<div className="detail-body dirent-info">
|
||||
{isLoading ? (
|
||||
<div className="w-100 h-100 d-flex algin-items-center justify-content-center"><Loading /></div>
|
||||
) : (
|
||||
<div className="detail-content">
|
||||
<DetailItem field={{ type: CellType.NUMBER, name: gettext('Files') }} value={repo.file_count} />
|
||||
<DetailItem field={{ type: 'size', name: gettext('Size') }} value={repo.size} />
|
||||
<DetailItem field={{ type: CellType.CREATOR, name: gettext('Creator') }} value={repo.owner_email} collaborators={[{
|
||||
name: repo.owner_name,
|
||||
contact_email: repo.owner_contact_email,
|
||||
email: repo.owner_email,
|
||||
avatar_url: repo.owner_avatar,
|
||||
}]} />
|
||||
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={repo.last_modified} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
}, (props, nextProps) => {
|
||||
return props.currentRepo.repo_id === nextProps.currentRepo.repo_id;
|
||||
});
|
||||
|
||||
LibDetail.propTypes = {
|
||||
currentRepo: PropTypes.object.isRequired,
|
||||
closeDetails: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LibDetail;
|
@ -2,45 +2,6 @@
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 2.5rem;
|
||||
background-color: #f9f9f9;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.detail-header .detail-control {
|
||||
padding-left: 0.5rem;
|
||||
font-size: 16px;
|
||||
color: #b9b9b9;
|
||||
}
|
||||
|
||||
.detail-header .detail-control:hover {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.detail-header .detail-title {
|
||||
margin-left: 0.25rem;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 0; /* prevent strut flex layout */
|
||||
}
|
||||
|
||||
.detail-header .detail-title .name {
|
||||
margin: 0 0.5rem 0 0.25rem;
|
||||
line-height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
font-size: 1rem;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.detail-body {
|
||||
|
@ -183,7 +183,7 @@
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
width: 300px;
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
box-shadow: -1px 0 3px 0 #ccc;
|
||||
animation: move .5s ease-in-out 1;
|
||||
|
@ -33,7 +33,6 @@ class GroupContainerRight extends Component {
|
||||
isExpanded={isExpanded}
|
||||
columns={columns}
|
||||
summaryConfigs={summaryConfigs}
|
||||
getTableContentRect={this.props.getTableContentRect}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -50,7 +49,6 @@ GroupContainerRight.propTypes = {
|
||||
height: PropTypes.number,
|
||||
groupOffsetLeft: PropTypes.number,
|
||||
lastFrozenColumnKey: PropTypes.string,
|
||||
getTableContentRect: PropTypes.func,
|
||||
};
|
||||
|
||||
export default GroupContainerRight;
|
||||
|
@ -10,9 +10,8 @@ class GroupHeaderCell extends React.PureComponent {
|
||||
fixedFrozenDOMs = (scrollLeft, scrollTop) => {
|
||||
if (this.headerCell) {
|
||||
const { firstColumnWidth, groupOffsetLeft } = this.props;
|
||||
const tableContentLeft = this.props.getTableContentRect();
|
||||
this.headerCell.style.position = 'fixed';
|
||||
this.headerCell.style.marginLeft = (SEQUENCE_COLUMN_WIDTH + firstColumnWidth + groupOffsetLeft + tableContentLeft) + 'px';
|
||||
this.headerCell.style.marginLeft = (SEQUENCE_COLUMN_WIDTH + firstColumnWidth + groupOffsetLeft) + 'px';
|
||||
this.headerCell.style.marginTop = (-scrollTop) + 'px';
|
||||
}
|
||||
};
|
||||
@ -64,7 +63,6 @@ GroupHeaderCell.propTypes = {
|
||||
groupOffsetLeft: PropTypes.number,
|
||||
summary: PropTypes.object,
|
||||
summaryMethod: PropTypes.string,
|
||||
getTableContentRect: PropTypes.func,
|
||||
};
|
||||
|
||||
export default GroupHeaderCell;
|
||||
|
@ -57,7 +57,6 @@ class GroupHeaderRight extends Component {
|
||||
isExpanded={isExpanded}
|
||||
summary={summary}
|
||||
summaryMethod={summaryMethod}
|
||||
getTableContentRect={this.props.getTableContentRect}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -79,7 +78,6 @@ GroupHeaderRight.propTypes = {
|
||||
lastFrozenColumnKey: PropTypes.string,
|
||||
columns: PropTypes.array,
|
||||
summaryConfigs: PropTypes.object,
|
||||
getTableContentRect: PropTypes.func,
|
||||
};
|
||||
|
||||
export default GroupHeaderRight;
|
||||
|
@ -133,7 +133,6 @@ class GroupContainer extends Component {
|
||||
lastFrozenColumnKey={lastFrozenColumnKey}
|
||||
columns={columns}
|
||||
summaryConfigs={summaryConfigs}
|
||||
getTableContentRect={this.props.getTableContentRect}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -157,7 +156,6 @@ GroupContainer.propTypes = {
|
||||
scrollLeft: PropTypes.number,
|
||||
maxLevel: PropTypes.number,
|
||||
summaryConfigs: PropTypes.object,
|
||||
getTableContentRect: PropTypes.func,
|
||||
onExpandGroupToggle: PropTypes.func,
|
||||
updateSummaryConfig: PropTypes.func,
|
||||
};
|
||||
|
@ -790,7 +790,6 @@ class GroupBody extends Component {
|
||||
isExpanded={isExpanded}
|
||||
folding={folding}
|
||||
lastFrozenColumnKey={lastFrozenColumnKey}
|
||||
getTableContentRect={this.props.getTableContentRect}
|
||||
onExpandGroupToggle={this.onExpandGroupToggle}
|
||||
/>
|
||||
);
|
||||
|
@ -136,7 +136,7 @@ class RecordsFooter extends React.Component {
|
||||
const recordWidth = (isLoadingMore || hasMore ? SEQUENCE_COLUMN_WIDTH + columns[0].width : SEQUENCE_COLUMN_WIDTH) + groupOffsetLeft;
|
||||
|
||||
return (
|
||||
<div className="sf-metadata-result-footer" style={{ zIndex: Z_INDEX.GRID_FOOTER, transform: 'translateZ(1000px)' }} ref={ref => this.ref = ref}>
|
||||
<div className="sf-metadata-result-footer" style={{ zIndex: Z_INDEX.GRID_FOOTER }} ref={ref => this.ref = ref}>
|
||||
<div className="rows-record d-flex text-nowrap" style={{ width: recordWidth }}>
|
||||
<span>{this.getRecord()}</span>
|
||||
{!isLoadingMore && hasMore &&
|
||||
|
@ -65,8 +65,7 @@ const ViewToolBar = ({ metadataViewId }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='sf-metadata-tool'
|
||||
// style={{ zIndex: Z_INDEX.TABLE_HEADER, transform: 'translateZ(1000px)' }}
|
||||
className="sf-metadata-tool"
|
||||
onClick={onHeaderClick}
|
||||
>
|
||||
<div className="sf-metadata-tool-left-operations">
|
||||
|
@ -10,6 +10,8 @@ class RepoInfo {
|
||||
this.owner_name = object.owner_name;
|
||||
this.owner_email = object.owner_email;
|
||||
this.owner_contact_email = object.owner_contact_email;
|
||||
this.owner_avatar = object.owner_avatar || '';
|
||||
|
||||
// is repo shared admin;
|
||||
// is repo shared admin && is one of current ordinary group's admins;
|
||||
// is one of current group owned group's admins;
|
||||
|
@ -4,17 +4,26 @@ class Repo {
|
||||
constructor(object) {
|
||||
this.repo_id = object.repo_id;
|
||||
this.repo_name = object.repo_name;
|
||||
this.repo_type = object.repo_type;
|
||||
this.permission = object.permission;
|
||||
this.size_original = object.size;
|
||||
this.size = Utils.bytesToSize(object.size);
|
||||
|
||||
// owner info
|
||||
this.owner_name = object.owner_name;
|
||||
this.owner_email = object.owner_email;
|
||||
this.owner_contact_email = object.owner_contact_email;
|
||||
this.owner_avatar = object.owner_avatar || '';
|
||||
|
||||
this.encrypted = object.encrypted;
|
||||
|
||||
// last_modified: last modified time
|
||||
this.last_modified = object.last_modified;
|
||||
this.modifier_contact_email = object.modifier_contact_email;
|
||||
this.modifier_email = object.modifier_email;
|
||||
this.modifier_name = object.modifier_name;
|
||||
this.modifier_avatar = object.modifier_avatar;
|
||||
|
||||
this.type = object.type;
|
||||
this.starred = object.starred;
|
||||
this.monitored = object.monitored;
|
||||
@ -23,6 +32,11 @@ class Repo {
|
||||
if (object.is_admin != undefined) {
|
||||
this.is_admin = object.is_admin;
|
||||
}
|
||||
this.file_count = object.file_count || 0;
|
||||
this.has_been_shared_out = object.has_been_shared_out;
|
||||
this.is_virtual = object.is_virtual;
|
||||
this.lib_need_decrypt = object.lib_need_decrypt;
|
||||
this.no_quota = object.no_quota;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,7 @@ import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import CurDirPath from '../../components/cur-dir-path';
|
||||
import DirentDetail from '../../components/dirent-detail/dirent-details';
|
||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
||||
import { LibDetail, DirentDetail } from '../../components/dirent-detail';
|
||||
import DirColumnView from '../../components/dir-view-mode/dir-column-view';
|
||||
import ToolbarForSelectedDirents from '../../components/toolbar/selected-dirents-toolbar';
|
||||
|
||||
@ -318,31 +317,29 @@ class LibContentContainer extends React.Component {
|
||||
isDirentDetailShow={this.props.isDirentDetailShow}
|
||||
/>
|
||||
)}
|
||||
{this.props.isDirentDetailShow && (
|
||||
<div className="cur-view-detail">
|
||||
{(this.props.path === '/' && !this.state.currentDirent) ?
|
||||
<LibDetail
|
||||
currentRepo={this.props.currentRepoInfo}
|
||||
closeDetails={this.props.closeDirentDetail}
|
||||
/> :
|
||||
<DirentDetail
|
||||
repoID={repoID}
|
||||
path={this.props.path}
|
||||
dirent={this.state.currentDirent}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTags={this.props.isViewFile ? this.props.fileTags : []}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
onItemDetailsClose={this.props.closeDirentDetail}
|
||||
direntDetailPanelTab={this.props.direntDetailPanelTab}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{this.props.isDirentDetailShow &&
|
||||
<Fragment>
|
||||
<div className="cur-view-detail">
|
||||
{(this.props.path === '/' && !this.state.currentDirent) ?
|
||||
<LibDetail
|
||||
currentRepo={this.props.currentRepoInfo}
|
||||
closeDetails={this.props.closeDirentDetail}
|
||||
/> :
|
||||
<DirentDetail
|
||||
repoID={repoID}
|
||||
path={this.props.path}
|
||||
dirent={this.state.currentDirent}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTags={this.props.isViewFile ? this.props.fileTags : []}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
onItemDetailsClose={this.props.closeDirentDetail}
|
||||
direntDetailPanelTab={this.props.direntDetailPanelTab}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ class OrgInfo extends Component {
|
||||
<span>{Utils.bytesToSize(user_default_quota)}</span>
|
||||
<span
|
||||
title={gettext('Edit')}
|
||||
className={`sf3-font sf3-font-rename attr-action-icon`}
|
||||
className="sf3-font sf3-font-rename attr-action-icon"
|
||||
onClick={this.toggleSetUserDefaultQuotaDialog}>
|
||||
</span>
|
||||
|
||||
|
@ -612,8 +612,8 @@ a, a:hover { color: #ec8000; }
|
||||
.side-nav {
|
||||
flex:auto;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
justify-content:space-between; /* make .side-nav-footer on the bottom */
|
||||
flex-direction: column;
|
||||
justify-content: space-between; /* make .side-nav-footer on the bottom */
|
||||
overflow:hidden; /* for ff */
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ from seahub.utils import is_org_context, is_pro_version
|
||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
||||
from seahub.utils.repo import get_repo_owner, is_repo_admin, \
|
||||
repo_has_been_shared_out, normalize_repo_status_code
|
||||
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
|
||||
|
||||
from seahub.settings import ENABLE_STORAGE_CLASSES
|
||||
|
||||
@ -116,7 +117,7 @@ class ReposView(APIView):
|
||||
|
||||
if is_wiki_repo(r):
|
||||
continue
|
||||
|
||||
url, _, _ = api_avatar_url(email, int(24))
|
||||
|
||||
repo_info = {
|
||||
"type": "mine",
|
||||
@ -125,6 +126,7 @@ class ReposView(APIView):
|
||||
"owner_email": email,
|
||||
"owner_name": email2nickname(email),
|
||||
"owner_contact_email": email2contact_email(email),
|
||||
"owner_avatar": url,
|
||||
"last_modified": timestamp_to_isoformat_timestr(r.last_modify),
|
||||
"modifier_email": r.last_modifier,
|
||||
"modifier_name": nickname_dict.get(r.last_modifier, ''),
|
||||
@ -189,6 +191,7 @@ class ReposView(APIView):
|
||||
|
||||
owner_name = group_name if is_group_owned_repo else nickname_dict.get(owner_email, '')
|
||||
owner_contact_email = '' if is_group_owned_repo else contact_email_dict.get(owner_email, '')
|
||||
url, _, _ = api_avatar_url(owner_email, int(24))
|
||||
|
||||
repo_info = {
|
||||
"type": "shared",
|
||||
@ -201,6 +204,7 @@ class ReposView(APIView):
|
||||
"owner_email": owner_email,
|
||||
"owner_name": owner_name,
|
||||
"owner_contact_email": owner_contact_email,
|
||||
"owner_avatar": url,
|
||||
"size": r.size,
|
||||
"encrypted": r.encrypted,
|
||||
"permission": r.permission,
|
||||
@ -299,6 +303,7 @@ class ReposView(APIView):
|
||||
continue
|
||||
|
||||
repo_owner = repo_id_owner_dict[r.repo_id]
|
||||
url, _, _ = api_avatar_url(repo_owner, int(24))
|
||||
repo_info = {
|
||||
"type": "public",
|
||||
"repo_id": r.repo_id,
|
||||
@ -310,6 +315,7 @@ class ReposView(APIView):
|
||||
"owner_email": repo_owner,
|
||||
"owner_name": nickname_dict.get(repo_owner, ''),
|
||||
"owner_contact_email": contact_email_dict.get(repo_owner, ''),
|
||||
"owner_avatar": url,
|
||||
"size": r.size,
|
||||
"encrypted": r.encrypted,
|
||||
"permission": r.permission,
|
||||
@ -363,13 +369,14 @@ class RepoView(APIView):
|
||||
lib_need_decrypt = True
|
||||
|
||||
repo_owner = get_repo_owner(request, repo_id)
|
||||
url, _, _ = api_avatar_url(repo_owner, int(24))
|
||||
|
||||
try:
|
||||
has_been_shared_out = repo_has_been_shared_out(request, repo_id)
|
||||
except Exception as e:
|
||||
has_been_shared_out = False
|
||||
logger.error(e)
|
||||
|
||||
|
||||
result = {
|
||||
"repo_id": repo.id,
|
||||
"repo_name": repo.name,
|
||||
@ -377,6 +384,7 @@ class RepoView(APIView):
|
||||
"owner_email": repo_owner,
|
||||
"owner_name": email2nickname(repo_owner),
|
||||
"owner_contact_email": email2contact_email(repo_owner),
|
||||
"owner_avatar": url,
|
||||
|
||||
"size": repo.size,
|
||||
"encrypted": repo.encrypted,
|
||||
|
@ -3390,6 +3390,9 @@ class FileDetailView(APIView):
|
||||
entry["last_modifier_email"] = latest_contributor
|
||||
entry["last_modifier_name"] = email2nickname(latest_contributor)
|
||||
entry["last_modifier_contact_email"] = email2contact_email(latest_contributor)
|
||||
if latest_contributor:
|
||||
url, _, _ = api_avatar_url(latest_contributor, int(24))
|
||||
entry["last_modifier_avatar"] = url
|
||||
|
||||
try:
|
||||
file_size = get_file_size(real_repo_id, repo.version, obj_id)
|
||||
|
Loading…
Reference in New Issue
Block a user