mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-12 21:30:39 +00:00
feat: detail resize width (#6507)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name-container {
|
||||
.dirent-detail-item .dirent-detail-item-name {
|
||||
width: 160px;
|
||||
padding: 7px 6px;
|
||||
min-height: 34px;
|
||||
@@ -16,7 +16,7 @@
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name-container .sf-metadata-icon {
|
||||
.dirent-detail-item .dirent-detail-item-name .sf-metadata-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 14px;
|
||||
fill: #999;
|
||||
@@ -34,7 +34,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name-container:hover,
|
||||
.dirent-detail-item .dirent-detail-item-name:hover,
|
||||
.dirent-detail-item .dirent-detail-item-value:hover {
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 3px;
|
||||
@@ -55,3 +55,20 @@
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* */
|
||||
.cur-view-detail-small .dirent-detail-item .dirent-detail-item-name {
|
||||
width: 44%;
|
||||
}
|
||||
|
||||
.cur-view-detail-small .dirent-detail-item .dirent-detail-item-value {
|
||||
width: 56%;
|
||||
}
|
||||
|
||||
.cur-view-detail-large .dirent-detail-item .dirent-detail-item-name {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.cur-view-detail-large .dirent-detail-item .dirent-detail-item-value {
|
||||
flex: 1;
|
||||
}
|
||||
|
@@ -15,9 +15,9 @@ const DetailItem = ({ field, value, valueId, valueClick, children, ...params })
|
||||
|
||||
return (
|
||||
<div className="dirent-detail-item">
|
||||
<div className="dirent-detail-item-name-container">
|
||||
<div className="dirent-detail-item-name">
|
||||
<Icon iconName={icon} />
|
||||
<span className="dirent-detail-item-name">{field.name}</span>
|
||||
<span className="dirent-detail-item-name-value">{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} />)}
|
||||
|
@@ -0,0 +1,8 @@
|
||||
.detail-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 16px;
|
||||
}
|
20
frontend/src/components/dirent-detail/detail/body/index.js
Normal file
20
frontend/src/components/dirent-detail/detail/body/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Body = ({ className, children }) => {
|
||||
return (
|
||||
<div className={classnames('detail-body dirent-info', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Body.propTypes = {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default Body;
|
@@ -0,0 +1,21 @@
|
||||
@keyframes move {
|
||||
from {
|
||||
right: -500px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
to {
|
||||
right: 0px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.cur-view-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
border-left: 1px solid #eee;
|
||||
animation: move .5s ease-in-out 1;
|
||||
position: relative;
|
||||
}
|
79
frontend/src/components/dirent-detail/detail/detail/index.js
Normal file
79
frontend/src/components/dirent-detail/detail/detail/index.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import ResizeBar from '../../../resize-bar';
|
||||
import { DRAG_HANDLER_HEIGHT } from '../../../resize-bar/constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Detail = ({ children, className }) => {
|
||||
const [width, setWidth] = useState(300);
|
||||
const [inResizing, setResizing] = useState(false);
|
||||
const resizeBarRef = useRef(null);
|
||||
const dragHandlerRef = useRef(null);
|
||||
|
||||
const onResizeMouseMove = useCallback((e) => {
|
||||
const newWidth = Math.max(Math.min(window.innerWidth - e.clientX, 600), 300);
|
||||
if (width === newWidth) return;
|
||||
setWidth(newWidth);
|
||||
}, [width]);
|
||||
|
||||
const onResizeMouseUp = useCallback(() => {
|
||||
window.removeEventListener('mousemove', onResizeMouseMove);
|
||||
window.removeEventListener('mouseup', onResizeMouseUp);
|
||||
inResizing && setResizing(false);
|
||||
localStorage.setItem('sf_cur_view_detail_width', width);
|
||||
}, [width, inResizing, onResizeMouseMove]);
|
||||
|
||||
const onResizeMouseDown = useCallback(() => {
|
||||
window.addEventListener('mouseup', onResizeMouseUp);
|
||||
window.addEventListener('mousemove', onResizeMouseMove);
|
||||
setResizing(true);
|
||||
}, [onResizeMouseUp, onResizeMouseMove]);
|
||||
|
||||
const setDragHandlerTop = useCallback((top) => {
|
||||
dragHandlerRef.current.style.top = top + 'px';
|
||||
}, []);
|
||||
|
||||
const onResizeMouseOver = useCallback((event) => {
|
||||
if (!dragHandlerRef.current) return;
|
||||
const { top } = resizeBarRef.current.getBoundingClientRect();
|
||||
const dragHandlerRefTop = event.pageY - top - DRAG_HANDLER_HEIGHT / 2;
|
||||
setDragHandlerTop(dragHandlerRefTop);
|
||||
}, [setDragHandlerTop]);
|
||||
|
||||
useEffect(() => {
|
||||
const width = localStorage.getItem('sf_cur_view_detail_width', 300);
|
||||
setWidth(width);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('cur-view-detail', className, {
|
||||
'cur-view-detail-small': width < 400,
|
||||
'cur-view-detail-large': width > 400
|
||||
})}
|
||||
style={{ width }}
|
||||
// onMouseMove={inResizing ? onResizeMouseMove : null}
|
||||
// onMouseUp={onResizeMouseUp}
|
||||
>
|
||||
{children}
|
||||
<ResizeBar
|
||||
resizeBarRef={resizeBarRef}
|
||||
dragHandlerRef={dragHandlerRef}
|
||||
resizeBarStyle={{ left: -1 }}
|
||||
dragHandlerStyle={{ height: DRAG_HANDLER_HEIGHT }}
|
||||
onResizeMouseDown={onResizeMouseDown}
|
||||
onResizeMouseOver={onResizeMouseOver}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
Detail.propTypes = {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default Detail;
|
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from '../../icon';
|
||||
import Icon from '../../../icon';
|
||||
|
||||
import './index.css';
|
||||
|
9
frontend/src/components/dirent-detail/detail/index.js
Normal file
9
frontend/src/components/dirent-detail/detail/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Detail from './detail';
|
||||
import Header from './header';
|
||||
import Body from './body';
|
||||
|
||||
export {
|
||||
Detail,
|
||||
Header,
|
||||
Body,
|
||||
};
|
@@ -7,12 +7,12 @@ import { gettext } from '../../../utils/constants';
|
||||
import { MetadataDetails } from '../../../metadata';
|
||||
|
||||
const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail, ...params }) => {
|
||||
const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]);
|
||||
const parent = useMemo(() => getFileParent(dirent, path), [dirent, path]);
|
||||
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailItem field={{ type: CellType.TEXT, name: gettext('Parent') }} value={parent} />
|
||||
<DetailItem field={{ type: CellType.TEXT, name: gettext('Parent folder') }} value={parent} />
|
||||
<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,
|
||||
|
@@ -14,7 +14,7 @@ import ObjectUtils from '../../../metadata/metadata-view/utils/object-utils';
|
||||
const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList, ...params }) => {
|
||||
const [isEditFileTagShow, setEditFileTagShow] = useState(false);
|
||||
|
||||
const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]);
|
||||
const parent = useMemo(() => getFileParent(dirent, path), [dirent, path]);
|
||||
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
|
||||
const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []);
|
||||
|
||||
@@ -28,7 +28,7 @@ const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail,
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailItem field={{ type: CellType.TEXT, name: gettext('Parent') }} value={parent} />
|
||||
<DetailItem field={{ type: CellType.TEXT, name: gettext('Parent folder') }} value={parent} />
|
||||
<DetailItem field={{ type: 'size', name: gettext('Size') }} value={Utils.bytesToSize(direntDetail.size)} />
|
||||
<DetailItem field={{ type: CellType.LAST_MODIFIER, name: gettext('Last modifier') }} value={direntDetail.last_modifier_email} collaborators={[{
|
||||
name: direntDetail.last_modifier_name,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.detail-container .detail-image-thumbnail {
|
||||
.detail-body .detail-image-thumbnail {
|
||||
height: 144px;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
@@ -9,7 +9,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-container .detail-image-thumbnail .thumbnail {
|
||||
.detail-body .detail-image-thumbnail .thumbnail {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
@@ -24,6 +24,6 @@
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.detail-container .empty-tip-text {
|
||||
.detail-body .empty-tip-text {
|
||||
color: #666;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ 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 { Detail, Header, Body } from '../detail';
|
||||
import DirDetails from './dir-details';
|
||||
import FileDetails from './file-details';
|
||||
import ObjectUtils from '../../../metadata/metadata-view/utils/object-utils';
|
||||
@@ -106,9 +106,9 @@ class DirentDetails extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="detail-container">
|
||||
<Detail>
|
||||
<Header title={direntName} icon={smallIconUrl} onClose={this.props.onClose} />
|
||||
<div className="detail-body dirent-info">
|
||||
<Body>
|
||||
{isImg && (
|
||||
<div className="detail-image-thumbnail">
|
||||
<img src={bigIconUrl} alt="" className="thumbnail" />
|
||||
@@ -146,8 +146,8 @@ class DirentDetails extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Body>
|
||||
</Detail>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -6,10 +6,10 @@ export const getDirentPath = (dirent, path) => {
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
};
|
||||
|
||||
export const getFileParent = (repoInfo, dirent, path) => {
|
||||
export const getFileParent = (dirent, path) => {
|
||||
const direntPath = getDirentPath(dirent, path);
|
||||
const position = repoInfo.repo_name;
|
||||
if (direntPath === '/') return position;
|
||||
if (direntPath === '/') return '/';
|
||||
const index = direntPath.lastIndexOf('/');
|
||||
return position + direntPath.slice(0, index);
|
||||
const positionPath = direntPath.slice(0, index);
|
||||
return positionPath || '/';
|
||||
};
|
||||
|
@@ -1,33 +0,0 @@
|
||||
.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;
|
||||
}
|
@@ -2,8 +2,6 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import LibDetail from './lib-details';
|
||||
import DirentDetail from './dirent-details';
|
||||
|
||||
import './index.css';
|
||||
import ObjectUtils from '../../metadata/metadata-view/utils/object-utils';
|
||||
|
||||
const Index = React.memo(({ repoID, path, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => {
|
||||
|
@@ -4,7 +4,7 @@ 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 { Detail, Header, Body } from '../detail';
|
||||
import Repo from '../../../models/repo';
|
||||
import Loading from '../../loading';
|
||||
import DetailItem from '../detail-item';
|
||||
@@ -28,9 +28,9 @@ const LibDetail = React.memo(({ currentRepoInfo, onClose }) => {
|
||||
}, [currentRepoInfo.repo_id]);
|
||||
|
||||
return (
|
||||
<div className="detail-container">
|
||||
<Detail>
|
||||
<Header title={currentRepoInfo.repo_name} icon={smallIconUrl} onClose={onClose} />
|
||||
<div className="detail-body dirent-info">
|
||||
<Body>
|
||||
{isLoading ? (
|
||||
<div className="w-100 h-100 d-flex algin-items-center justify-content-center"><Loading /></div>
|
||||
) : (
|
||||
@@ -46,8 +46,8 @@ const LibDetail = React.memo(({ currentRepoInfo, onClose }) => {
|
||||
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={repo.last_modified} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Body>
|
||||
</Detail>
|
||||
);
|
||||
|
||||
}, (props, nextProps) => {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { RESIZE_BAR } from '../../constants/zIndexes';
|
||||
|
||||
import './index.css';
|
||||
|
||||
function ResizeBar(props) {
|
||||
|
@@ -178,33 +178,6 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cur-view-detail {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
width: 400px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.cur-view-detail .detail-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* for reach/router */
|
||||
.reach-router,
|
||||
div[tabindex="-1"][role="group"] {
|
||||
|
@@ -22,9 +22,10 @@
|
||||
|
||||
.dir-column-view {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
@@ -250,7 +250,11 @@ class LibContentContainer extends React.Component {
|
||||
switchViewMode={this.props.switchViewMode}
|
||||
/>
|
||||
</div>
|
||||
<div className={`cur-view-content lib-content-container ${this.props.isTreePanelShown ? 'view-mode-container' : ''}`} onScroll={this.onItemsScroll}>
|
||||
<div
|
||||
className={`cur-view-content lib-content-container ${this.props.isTreePanelShown ? 'view-mode-container' : ''}`}
|
||||
onScroll={this.onItemsScroll}
|
||||
|
||||
>
|
||||
{!this.props.pathExist && this.errMessage}
|
||||
{this.props.pathExist && (
|
||||
<DirColumnView
|
||||
@@ -318,18 +322,16 @@ class LibContentContainer extends React.Component {
|
||||
/>
|
||||
)}
|
||||
{this.props.isDirentDetailShow && (
|
||||
<div className="cur-view-detail">
|
||||
<Detail
|
||||
path={path}
|
||||
repoID={repoID}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
dirent={this.state.currentDirent}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTags={this.props.isViewFile ? this.props.fileTags : []}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
onClose={this.props.closeDirentDetail}
|
||||
/>
|
||||
</div>
|
||||
<Detail
|
||||
path={path}
|
||||
repoID={repoID}
|
||||
currentRepoInfo={this.props.currentRepoInfo}
|
||||
dirent={this.state.currentDirent}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTags={this.props.isViewFile ? this.props.fileTags : []}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
onClose={this.props.closeDirentDetail}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user