1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-24 21:07:17 +00:00

feat: add resize bar for comment and detail panel (#8213)

This commit is contained in:
Guodong SU
2025-09-16 10:43:24 +08:00
committed by GitHub
parent 2faf146c87
commit cffb6072a0
6 changed files with 285 additions and 10 deletions

View File

@@ -6,10 +6,14 @@ import { Utils } from '../../utils/utils';
import toaster from '../toast';
import CommentList from './comment-widget/comment-list';
import ReplyList from './comment-widget/reply-list';
import LocalStorage from '../../utils/local-storage-utils';
import ResizeWidth from './resize-width';
import '../../css/comments-list.css';
const { username, repoID, filePath, fileUuid } = window.app.pageOptions;
const { username, repoID, filePath, fileUuid, fileName } = window.app.pageOptions;
const MIN_PANEL_WIDTH = 360;
const MAX_PANEL_WIDTH = 620;
const CommentPanelPropTypes = {
toggleCommentPanel: PropTypes.func.isRequired,
@@ -28,10 +32,27 @@ class CommentPanel extends React.Component {
participants: null,
relatedUsers: null,
currentComment: null,
width: MIN_PANEL_WIDTH,
};
this.toBeAddedParticipant = [];
}
panelWrapperStyle = () => {
let style = {
width: this.state.width,
zIndex: 101,
};
if (!style.width || style.width < MIN_PANEL_WIDTH) {
style.width = MIN_PANEL_WIDTH;
} else if (style.width > MAX_PANEL_WIDTH) {
style.width = MAX_PANEL_WIDTH;
}
return style;
};
forceUpdate = () => {
this.listComments();
this.getParticipants();
@@ -163,6 +184,15 @@ class CommentPanel extends React.Component {
this.listComments();
this.getParticipants();
this.listRepoRelatedUsers();
const newfileType = fileName?.split('.').pop();
const settings = LocalStorage.getItem(`${newfileType}_comment_storage`) || {};
const { panelWidth } = settings;
const width = Math.max(
MIN_PANEL_WIDTH,
Math.min(parseInt(panelWidth, 10) || MIN_PANEL_WIDTH, MAX_PANEL_WIDTH)
);
this.setState({ width });
}
onClickComment = (currentComment) => {
@@ -173,10 +203,21 @@ class CommentPanel extends React.Component {
this.setState({ currentComment: null });
};
resizeWidth = (width) => {
this.setState({ width: width });
};
resizeWidthEnd = (width) => {
const newfileType = fileName?.split('.').pop();
const settings = LocalStorage.getItem(`${newfileType}_comment_storage`) || {};
LocalStorage.setItem(`${newfileType}_comment_storage`, JSON.stringify({ ...settings, panelWidth: width }));
};
render() {
const { commentsList } = this.state;
return (
<div className="seafile-comment">
<div className="seafile-comment" style={this.panelWrapperStyle()}>
<ResizeWidth minWidth={MIN_PANEL_WIDTH} maxWidth={MAX_PANEL_WIDTH} resizeWidth={this.resizeWidth} resizeWidthEnd={this.resizeWidthEnd} />
{
this.state.currentComment ?
<ReplyList

View File

@@ -16,6 +16,8 @@ import EmbeddedFileDetails from '../dirent-detail/embedded-file-details';
import { MetadataMiddlewareProvider, MetadataStatusProvider } from '../../hooks';
import Loading from '../loading';
import WebSocketClient from '../../utils/websocket-service';
import ResizeWidth from './resize-width';
import LocalStorage from '../../utils/local-storage-utils';
import '../../css/file-view.css';
@@ -33,6 +35,8 @@ const { isStarred, isLocked, lockedByMe,
repoID, fileUuid, filePath, filePerm, enableWatermark, userNickName,
fileName, repoEncrypted, isRepoAdmin, fileType
} = window.app.pageOptions;
const MIN_PANEL_WIDTH = 360;
const MAX_PANEL_WIDTH = 620;
class FileView extends React.Component {
@@ -47,6 +51,7 @@ class FileView extends React.Component {
isHeaderShown: (storedIsHeaderShown === null) || (storedIsHeaderShown == 'true'),
isDetailsPanelOpen: false,
isCommentUpdated: false,
width: MIN_PANEL_WIDTH,
};
this.socketManager = new WebSocketClient(this.onMessageCallback, repoID);
@@ -55,6 +60,15 @@ class FileView extends React.Component {
componentDidMount() {
const fileIcon = Utils.getFileIconUrl(fileName);
document.getElementById('favicon').href = fileIcon;
const newfileType = fileName?.split('.').pop();
const settings = LocalStorage.getItem(`${newfileType}_detail_storage`) || {};
const { panelWidth } = settings;
const width = Math.max(
MIN_PANEL_WIDTH,
Math.min(parseInt(panelWidth, 10) || MIN_PANEL_WIDTH, MAX_PANEL_WIDTH)
);
this.setState({ width });
}
onMessageCallback = (data) => {
@@ -144,6 +158,31 @@ class FileView extends React.Component {
this.commentPanelRef = ref;
};
panelWrapperStyle = () => {
let style = {
width: this.state.width,
zIndex: 101,
};
if (!style.width || style.width < MIN_PANEL_WIDTH) {
style.width = MIN_PANEL_WIDTH;
} else if (style.width > MAX_PANEL_WIDTH) {
style.width = MAX_PANEL_WIDTH;
}
return style;
};
resizeWidth = (width) => {
this.setState({ width: width });
};
resizeWidthEnd = (width) => {
const newfileType = fileName?.split('.').pop();
const settings = LocalStorage.getItem(`${newfileType}_detail_storage`) || {};
LocalStorage.setItem(`${newfileType}_detail_storage`, JSON.stringify({ ...settings, panelWidth: width }));
};
render() {
const { isOnlyofficeFile = false } = this.props;
const { isDetailsPanelOpen, isHeaderShown } = this.state;
@@ -209,13 +248,17 @@ class FileView extends React.Component {
{isDetailsPanelOpen && (
<MetadataStatusProvider repoID={repoID} repoInfo={repoInfo}>
<MetadataMiddlewareProvider repoID={repoID} repoInfo={repoInfo}>
<EmbeddedFileDetails
repoID={repoID}
path={filePath}
dirent={{ 'name': fileName, type: 'file' }}
repoInfo={repoInfo}
onClose={this.toggleDetailsPanel}
/>
<div className='seafile-file-detail-right-panel-wrapper' style={this.panelWrapperStyle()}>
<ResizeWidth minWidth={MIN_PANEL_WIDTH} maxWidth={MAX_PANEL_WIDTH} resizeWidth={this.resizeWidth} resizeWidthEnd={this.resizeWidthEnd} />
<EmbeddedFileDetails
repoID={repoID}
path={filePath}
dirent={{ 'name': fileName, type: 'file' }}
repoInfo={repoInfo}
onClose={this.toggleDetailsPanel}
width={this.state.width}
/>
</div>
</MetadataMiddlewareProvider>
</MetadataStatusProvider>
)}

View File

@@ -0,0 +1,148 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import '../../css/resize-width.css';
const ResizeWidth = ({ minWidth, maxWidth, resizeWidth: resizeWidthAPI, resizeWidthEnd }) => {
const [isShowHandlerBar, setIsShowHandlerBar] = useState(false);
const [drag, setDrag] = useState(null);
const handlerRef = useRef(null);
const handlerBarRef = useRef(null);
const setHandlerBarTop = (handlerTop) => {
if (!handlerBarRef.current || handlerTop < 0) return;
handlerBarRef.current.style.top = handlerTop + 'px';
};
const setHandlerBarPosition = (event) => {
if (!handlerRef.current) return;
const { top } = handlerRef.current.getBoundingClientRect();
const handlerTop = event.pageY - top - 26 / 2;
setHandlerBarTop(handlerTop);
};
const getWidthFromMouseEvent = (event) => {
return event.pageX || (event.touches && event.touches[0] && event.touches[0].pageX) ||
(event.changedTouches && event.changedTouches[event.changedTouches.length - 1].pageX);
};
const calculateResizedWidth = (event) => {
const width = getWidthFromMouseEvent(event);
const resizedWidth = document.body.clientWidth - width;
if ((minWidth && resizedWidth < minWidth) || (maxWidth && resizedWidth > maxWidth)) return -1;
return resizedWidth;
};
const onResizeWidth = (event) => {
const resizedWidth = calculateResizedWidth(event);
if (resizedWidth < 0) return;
if (resizeWidthAPI) {
resizeWidthAPI(resizedWidth);
}
};
const onDrag = (event) => {
onResizeWidth(event);
};
const onDragStart = useCallback((event) => {
if (event && event.dataTransfer && event.dataTransfer.setData) {
event.dataTransfer.setData('text/plain', 'dummy');
}
}, []);
const onDragEnd = (event) => {
onResizeWidth(event);
};
const onMouseLeave = () => {
setIsShowHandlerBar(false);
};
const onMouseEnter = (event) => {
setIsShowHandlerBar(true);
setHandlerBarPosition(event);
if (handlerRef.current) {
handlerRef.current.addEventListener('mouseleave', onMouseLeave);
}
};
const onMouseOver = (event) => {
setHandlerBarPosition(event);
};
const onMouseDown = (event) => {
event.preventDefault && event.preventDefault();
const currDrag = onDragStart(event);
if (currDrag === null && event.button !== 0) return;
window.addEventListener('mouseup', onMouseUp);
window.addEventListener('mousemove', onMouseMove);
if (handlerRef.current) {
handlerRef.current.removeEventListener('mouseleave', onMouseLeave);
}
setDrag(currDrag);
};
const onMouseMove = (event) => {
event.preventDefault && event.preventDefault();
if (!drag === null) return;
onDrag(event);
};
const onMouseUp = (event) => {
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mousemove', onMouseMove);
onDragEnd(event, drag);
setHandlerBarTop(-9999);
setDrag(null);
setIsShowHandlerBar(false);
if (resizeWidthEnd) {
const resizeWidth = calculateResizedWidth(event);
if (resizeWidth < 0) return;
resizeWidthEnd(resizeWidth);
}
};
useEffect(() => {
return () => {
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mousemove', onMouseMove);
};
// eslint-disable-next-line
}, []);
return (
<div
className="seafile-resize-width-handler resize-handler-placement-right"
ref={handlerRef}
onMouseDown={onMouseDown}
onMouseOver={onMouseOver}
onMouseEnter={onMouseEnter}
onDrag={onDrag}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
style={{ zIndex: 102 }}
>
<div className="seafile-resize-width-handler-content">
{isShowHandlerBar && (
<div className="seafile-resize-width-handler-bar" ref={handlerBarRef} style={{ height: 26 }}></div>
)}
</div>
</div>
);
};
ResizeWidth.propTypes = {
minWidth: PropTypes.number,
maxWidth: PropTypes.number,
resizeWidth: PropTypes.func,
resizeWidthEnd: PropTypes.func,
};
export default ResizeWidth;

View File

@@ -2,6 +2,7 @@
background: var(--bs-body-bg);
display: flex;
flex-direction: column;
position: relative;
}
.seafile-comment-title {
@@ -19,7 +20,7 @@
font-weight: 500;
}
.seafile-comment-title .sdoc-icon-btn {
.seafile-comment-title .sdoc-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;

View File

@@ -154,6 +154,11 @@ body {
z-index: 50;
}
.seafile-file-detail-right-panel-wrapper {
display: flex;
position: relative;
}
@keyframes move {
from {
right: -500px;

View File

@@ -0,0 +1,37 @@
.seafile-resize-width-handler {
height: 100%;
position: absolute;
right: 0;
top: 0;
width: 6px;
}
.seafile-resize-width-handler.resize-handler-placement-right {
left: 0;
right: auto;
}
.seafile-resize-width-handler:hover {
cursor: col-resize;
}
.seafile-resize-width-handler .seafile-resize-width-handler-content {
background-color: initial;
height: 100%;
position: relative;
width: 2px;
}
.seafile-resize-width-handler:hover .seafile-resize-width-handler-content {
background-color: #ccc;
}
.seafile-resize-width-handler .seafile-resize-width-handler-bar {
background-color: #2d7ff9;
border-radius: 3px;
content: "";
left: 50%;
margin-left: -3px;
position: absolute;
width: 6px;
}