fix: enable floating review panel height resize
This commit is contained in:
@@ -39,6 +39,8 @@ import {
|
||||
|
||||
const MIN_REVIEW_PANEL_WIDTH = 0.24;
|
||||
const MAX_REVIEW_PANEL_WIDTH = 0.48;
|
||||
const MIN_FLOATING_PANEL_HEIGHT = 0.35;
|
||||
const MAX_FLOATING_PANEL_HEIGHT = 0.92;
|
||||
const FLOATING_PANEL_MARGIN = 12;
|
||||
|
||||
const cnHtmlPurifyOptions = {
|
||||
@@ -469,9 +471,11 @@ const ReviewPanel: React.FC = () => {
|
||||
const isPanelVisible = useReviewModeStore((state) => state.isPanelVisible);
|
||||
const isPanelPinned = useReviewModeStore((state) => state.isPanelPinned);
|
||||
const panelWidth = useReviewModeStore((state) => state.panelWidth);
|
||||
const panelHeight = useReviewModeStore((state) => state.panelHeight);
|
||||
const setPanelVisible = useReviewModeStore((state) => state.setPanelVisible);
|
||||
const togglePanelPin = useReviewModeStore((state) => state.togglePanelPin);
|
||||
const setPanelWidth = useReviewModeStore((state) => state.setPanelWidth);
|
||||
const setPanelHeight = useReviewModeStore((state) => state.setPanelHeight);
|
||||
const updateRow = useReviewModeStore((state) => state.updateRow);
|
||||
const setBookData = useReviewModeStore((state) => state.setBookData);
|
||||
const bookState = useReviewModeStore((state) =>
|
||||
@@ -588,7 +592,8 @@ const ReviewPanel: React.FC = () => {
|
||||
[clampPanelPosition],
|
||||
);
|
||||
|
||||
const toPanelWidthPercent = (fraction: number) => `${Math.round(fraction * 10000) / 100}%`;
|
||||
const toPanelPercent = (fraction: number) => `${Math.round(fraction * 10000) / 100}%`;
|
||||
const toPanelVh = (fraction: number) => `${Math.round(fraction * 10000) / 100}vh`;
|
||||
|
||||
const resizeFloatingPanel = useCallback(
|
||||
(clientX: number, rightEdge: number) => {
|
||||
@@ -597,20 +602,34 @@ const ReviewPanel: React.FC = () => {
|
||||
Math.min(MAX_REVIEW_PANEL_WIDTH, (rightEdge - clientX) / window.innerWidth),
|
||||
);
|
||||
const width = fraction * window.innerWidth;
|
||||
setPanelWidth(toPanelWidthPercent(fraction));
|
||||
setPanelWidth(toPanelPercent(fraction));
|
||||
updatePanelPosition({ x: rightEdge - width, y: panelPositionRef.current.y });
|
||||
},
|
||||
[setPanelWidth, updatePanelPosition],
|
||||
);
|
||||
|
||||
const { handleResizeStart: handleDockedResizeStart, handleResizeKeyDown: handleDockedResizeKeyDown } =
|
||||
usePanelResize({
|
||||
side: 'end',
|
||||
minWidth: MIN_REVIEW_PANEL_WIDTH,
|
||||
maxWidth: MAX_REVIEW_PANEL_WIDTH,
|
||||
getWidth: () => panelWidth,
|
||||
onResize: setPanelWidth,
|
||||
});
|
||||
const resizeFloatingPanelHeight = useCallback(
|
||||
(clientY: number, topEdge: number) => {
|
||||
const fraction = Math.max(
|
||||
MIN_FLOATING_PANEL_HEIGHT,
|
||||
Math.min(MAX_FLOATING_PANEL_HEIGHT, (clientY - topEdge) / window.innerHeight),
|
||||
);
|
||||
setPanelHeight(toPanelVh(fraction));
|
||||
requestAnimationFrame(() => updatePanelPosition(panelPositionRef.current));
|
||||
},
|
||||
[setPanelHeight, updatePanelPosition],
|
||||
);
|
||||
|
||||
const {
|
||||
handleResizeStart: handleDockedResizeStart,
|
||||
handleResizeKeyDown: handleDockedResizeKeyDown,
|
||||
} = usePanelResize({
|
||||
side: 'end',
|
||||
minWidth: MIN_REVIEW_PANEL_WIDTH,
|
||||
maxWidth: MAX_REVIEW_PANEL_WIDTH,
|
||||
getWidth: () => panelWidth,
|
||||
onResize: setPanelWidth,
|
||||
});
|
||||
|
||||
const handleResizeStart = (event: React.MouseEvent | React.TouchEvent) => {
|
||||
if (isPanelPinned || isMobile) {
|
||||
@@ -661,7 +680,7 @@ const ReviewPanel: React.FC = () => {
|
||||
const rightEdge =
|
||||
panel?.getBoundingClientRect().right ||
|
||||
panelPositionRef.current.x + currentWidth * window.innerWidth;
|
||||
setPanelWidth(toPanelWidthPercent(nextWidth));
|
||||
setPanelWidth(toPanelPercent(nextWidth));
|
||||
updatePanelPosition({
|
||||
x: rightEdge - nextWidth * window.innerWidth,
|
||||
y: panelPositionRef.current.y,
|
||||
@@ -670,6 +689,53 @@ const ReviewPanel: React.FC = () => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const handleHeightResizeStart = (event: React.MouseEvent | React.TouchEvent) => {
|
||||
if (isPanelPinned || isMobile) return;
|
||||
const panel = panelRef.current;
|
||||
if (!panel) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const topEdge = panel.getBoundingClientRect().top;
|
||||
document.body.style.pointerEvents = 'none';
|
||||
document.body.style.userSelect = 'none';
|
||||
document.documentElement.style.cursor = 'row-resize';
|
||||
|
||||
const handleMove = (moveEvent: MouseEvent | TouchEvent) => {
|
||||
const clientY =
|
||||
'touches' in moveEvent ? moveEvent.touches[0]?.clientY : (moveEvent as MouseEvent).clientY;
|
||||
if (typeof clientY === 'number') resizeFloatingPanelHeight(clientY, topEdge);
|
||||
};
|
||||
|
||||
const handleEnd = () => {
|
||||
document.body.style.pointerEvents = '';
|
||||
document.body.style.userSelect = '';
|
||||
document.documentElement.style.cursor = '';
|
||||
window.removeEventListener('mousemove', handleMove);
|
||||
window.removeEventListener('mouseup', handleEnd);
|
||||
window.removeEventListener('touchmove', handleMove);
|
||||
window.removeEventListener('touchend', handleEnd);
|
||||
};
|
||||
|
||||
window.addEventListener('mousemove', handleMove, { passive: true });
|
||||
window.addEventListener('mouseup', handleEnd);
|
||||
window.addEventListener('touchmove', handleMove, { passive: true });
|
||||
window.addEventListener('touchend', handleEnd);
|
||||
};
|
||||
|
||||
const handleHeightResizeKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (isPanelPinned || isMobile) return;
|
||||
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return;
|
||||
const currentHeight = parseFloat(panelHeight) / 100;
|
||||
const nextHeight =
|
||||
event.key === 'ArrowDown'
|
||||
? Math.min(MAX_FLOATING_PANEL_HEIGHT, currentHeight + 0.03)
|
||||
: Math.max(MIN_FLOATING_PANEL_HEIGHT, currentHeight - 0.03);
|
||||
setPanelHeight(toPanelVh(nextHeight));
|
||||
requestAnimationFrame(() => updatePanelPosition(panelPositionRef.current));
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const moveFloatingPanelToDefault = useCallback(() => {
|
||||
if (isPanelPinned || isMobile) return;
|
||||
const panel = panelRef.current;
|
||||
@@ -721,7 +787,13 @@ const ReviewPanel: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (!isPanelVisible || isMobile || !isPanelPinned) return;
|
||||
restoreReadingPositionAfterLayoutChange();
|
||||
}, [isMobile, isPanelPinned, isPanelVisible, panelWidth, restoreReadingPositionAfterLayoutChange]);
|
||||
}, [
|
||||
isMobile,
|
||||
isPanelPinned,
|
||||
isPanelVisible,
|
||||
panelWidth,
|
||||
restoreReadingPositionAfterLayoutChange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPanelPinned || isMobile) return;
|
||||
@@ -834,7 +906,8 @@ const ReviewPanel: React.FC = () => {
|
||||
className={clsx(
|
||||
'review-panel-container flex min-w-72 select-none flex-col',
|
||||
isDocked && 'shrink-0',
|
||||
'full-height font-sans text-base font-normal transition-[padding-top] duration-300 sm:text-sm',
|
||||
'font-sans text-base font-normal transition-[padding-top] duration-300 sm:text-sm',
|
||||
(isDocked || isMobile) && 'full-height',
|
||||
viewSettings?.isEink ? 'bg-base-100' : 'bg-base-200',
|
||||
appService?.hasRoundedWindow && 'rounded-window-top-right rounded-window-bottom-right',
|
||||
isDocked ? 'z-20' : 'z-[45] shadow-2xl',
|
||||
@@ -847,6 +920,11 @@ const ReviewPanel: React.FC = () => {
|
||||
style={{
|
||||
width: isMobile ? '100%' : panelWidth,
|
||||
maxWidth: isMobile ? '100%' : `${MAX_REVIEW_PANEL_WIDTH * 100}%`,
|
||||
height: !isMobile && !isPanelPinned ? panelHeight : undefined,
|
||||
maxHeight:
|
||||
!isMobile && !isPanelPinned
|
||||
? `calc(100vh - ${FLOATING_PANEL_MARGIN * 2}px)`
|
||||
: undefined,
|
||||
position: isMobile ? 'fixed' : isPanelPinned ? 'relative' : 'absolute',
|
||||
left: !isMobile && !isPanelPinned ? `${panelPosition.x}px` : undefined,
|
||||
top: !isMobile && !isPanelPinned ? `${panelPosition.y}px` : undefined,
|
||||
@@ -868,6 +946,19 @@ const ReviewPanel: React.FC = () => {
|
||||
onTouchStart={handleResizeStart}
|
||||
onKeyDown={handleResizeKeyDown}
|
||||
/>
|
||||
{!isMobile && !isPanelPinned ? (
|
||||
<div
|
||||
className='drag-bar absolute inset-x-0 -bottom-2 h-0.5 cursor-row-resize bg-transparent p-2'
|
||||
role='slider'
|
||||
tabIndex={0}
|
||||
aria-label='调整审校面板高度'
|
||||
aria-orientation='vertical'
|
||||
aria-valuenow={parseFloat(panelHeight)}
|
||||
onMouseDown={handleHeightResizeStart}
|
||||
onTouchStart={handleHeightResizeStart}
|
||||
onKeyDown={handleHeightResizeKeyDown}
|
||||
/>
|
||||
) : null}
|
||||
<header
|
||||
className={clsx(
|
||||
'border-base-300 flex min-h-12 shrink-0 items-center gap-2 border-b px-3 py-2',
|
||||
|
||||
Reference in New Issue
Block a user