mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-25 06:33:48 +00:00
feat: add resize bar for comment and detail panel (#8213)
This commit is contained in:
@@ -6,10 +6,14 @@ import { Utils } from '../../utils/utils';
|
|||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
import CommentList from './comment-widget/comment-list';
|
import CommentList from './comment-widget/comment-list';
|
||||||
import ReplyList from './comment-widget/reply-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';
|
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 = {
|
const CommentPanelPropTypes = {
|
||||||
toggleCommentPanel: PropTypes.func.isRequired,
|
toggleCommentPanel: PropTypes.func.isRequired,
|
||||||
@@ -28,10 +32,27 @@ class CommentPanel extends React.Component {
|
|||||||
participants: null,
|
participants: null,
|
||||||
relatedUsers: null,
|
relatedUsers: null,
|
||||||
currentComment: null,
|
currentComment: null,
|
||||||
|
width: MIN_PANEL_WIDTH,
|
||||||
};
|
};
|
||||||
this.toBeAddedParticipant = [];
|
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 = () => {
|
forceUpdate = () => {
|
||||||
this.listComments();
|
this.listComments();
|
||||||
this.getParticipants();
|
this.getParticipants();
|
||||||
@@ -163,6 +184,15 @@ class CommentPanel extends React.Component {
|
|||||||
this.listComments();
|
this.listComments();
|
||||||
this.getParticipants();
|
this.getParticipants();
|
||||||
this.listRepoRelatedUsers();
|
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) => {
|
onClickComment = (currentComment) => {
|
||||||
@@ -173,10 +203,21 @@ class CommentPanel extends React.Component {
|
|||||||
this.setState({ currentComment: null });
|
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() {
|
render() {
|
||||||
const { commentsList } = this.state;
|
const { commentsList } = this.state;
|
||||||
return (
|
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 ?
|
this.state.currentComment ?
|
||||||
<ReplyList
|
<ReplyList
|
||||||
|
@@ -16,6 +16,8 @@ import EmbeddedFileDetails from '../dirent-detail/embedded-file-details';
|
|||||||
import { MetadataMiddlewareProvider, MetadataStatusProvider } from '../../hooks';
|
import { MetadataMiddlewareProvider, MetadataStatusProvider } from '../../hooks';
|
||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
import WebSocketClient from '../../utils/websocket-service';
|
import WebSocketClient from '../../utils/websocket-service';
|
||||||
|
import ResizeWidth from './resize-width';
|
||||||
|
import LocalStorage from '../../utils/local-storage-utils';
|
||||||
|
|
||||||
import '../../css/file-view.css';
|
import '../../css/file-view.css';
|
||||||
|
|
||||||
@@ -33,6 +35,8 @@ const { isStarred, isLocked, lockedByMe,
|
|||||||
repoID, fileUuid, filePath, filePerm, enableWatermark, userNickName,
|
repoID, fileUuid, filePath, filePerm, enableWatermark, userNickName,
|
||||||
fileName, repoEncrypted, isRepoAdmin, fileType
|
fileName, repoEncrypted, isRepoAdmin, fileType
|
||||||
} = window.app.pageOptions;
|
} = window.app.pageOptions;
|
||||||
|
const MIN_PANEL_WIDTH = 360;
|
||||||
|
const MAX_PANEL_WIDTH = 620;
|
||||||
|
|
||||||
class FileView extends React.Component {
|
class FileView extends React.Component {
|
||||||
|
|
||||||
@@ -47,6 +51,7 @@ class FileView extends React.Component {
|
|||||||
isHeaderShown: (storedIsHeaderShown === null) || (storedIsHeaderShown == 'true'),
|
isHeaderShown: (storedIsHeaderShown === null) || (storedIsHeaderShown == 'true'),
|
||||||
isDetailsPanelOpen: false,
|
isDetailsPanelOpen: false,
|
||||||
isCommentUpdated: false,
|
isCommentUpdated: false,
|
||||||
|
width: MIN_PANEL_WIDTH,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socketManager = new WebSocketClient(this.onMessageCallback, repoID);
|
this.socketManager = new WebSocketClient(this.onMessageCallback, repoID);
|
||||||
@@ -55,6 +60,15 @@ class FileView extends React.Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const fileIcon = Utils.getFileIconUrl(fileName);
|
const fileIcon = Utils.getFileIconUrl(fileName);
|
||||||
document.getElementById('favicon').href = fileIcon;
|
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) => {
|
onMessageCallback = (data) => {
|
||||||
@@ -144,6 +158,31 @@ class FileView extends React.Component {
|
|||||||
this.commentPanelRef = ref;
|
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() {
|
render() {
|
||||||
const { isOnlyofficeFile = false } = this.props;
|
const { isOnlyofficeFile = false } = this.props;
|
||||||
const { isDetailsPanelOpen, isHeaderShown } = this.state;
|
const { isDetailsPanelOpen, isHeaderShown } = this.state;
|
||||||
@@ -209,13 +248,17 @@ class FileView extends React.Component {
|
|||||||
{isDetailsPanelOpen && (
|
{isDetailsPanelOpen && (
|
||||||
<MetadataStatusProvider repoID={repoID} repoInfo={repoInfo}>
|
<MetadataStatusProvider repoID={repoID} repoInfo={repoInfo}>
|
||||||
<MetadataMiddlewareProvider repoID={repoID} repoInfo={repoInfo}>
|
<MetadataMiddlewareProvider repoID={repoID} repoInfo={repoInfo}>
|
||||||
|
<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
|
<EmbeddedFileDetails
|
||||||
repoID={repoID}
|
repoID={repoID}
|
||||||
path={filePath}
|
path={filePath}
|
||||||
dirent={{ 'name': fileName, type: 'file' }}
|
dirent={{ 'name': fileName, type: 'file' }}
|
||||||
repoInfo={repoInfo}
|
repoInfo={repoInfo}
|
||||||
onClose={this.toggleDetailsPanel}
|
onClose={this.toggleDetailsPanel}
|
||||||
|
width={this.state.width}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</MetadataMiddlewareProvider>
|
</MetadataMiddlewareProvider>
|
||||||
</MetadataStatusProvider>
|
</MetadataStatusProvider>
|
||||||
)}
|
)}
|
||||||
|
148
frontend/src/components/file-view/resize-width.js
Normal file
148
frontend/src/components/file-view/resize-width.js
Normal 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;
|
@@ -2,6 +2,7 @@
|
|||||||
background: var(--bs-body-bg);
|
background: var(--bs-body-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seafile-comment-title {
|
.seafile-comment-title {
|
||||||
|
@@ -154,6 +154,11 @@ body {
|
|||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.seafile-file-detail-right-panel-wrapper {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes move {
|
@keyframes move {
|
||||||
from {
|
from {
|
||||||
right: -500px;
|
right: -500px;
|
||||||
|
37
frontend/src/css/resize-width.css
Normal file
37
frontend/src/css/resize-width.css
Normal 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;
|
||||||
|
}
|
Reference in New Issue
Block a user