From 193a8f48a151ffb611656dae1840da72178dfee5 Mon Sep 17 00:00:00 2001 From: EnixCoda Date: Thu, 23 Dec 2021 14:02:27 +0800 Subject: [PATCH] feat: optimize resize and enable drag on Firefox --- src/components/ResizeHandler.tsx | 112 +++++++++++++++----------- src/components/SideBarBodyWrapper.tsx | 12 +-- src/components/ToggleShowButton.tsx | 63 +++++++-------- 3 files changed, 102 insertions(+), 85 deletions(-) diff --git a/src/components/ResizeHandler.tsx b/src/components/ResizeHandler.tsx index 177de70..05692f9 100644 --- a/src/components/ResizeHandler.tsx +++ b/src/components/ResizeHandler.tsx @@ -1,10 +1,10 @@ import { Icon } from 'components/Icon' import * as React from 'react' -import { Size } from './SideBarBodyWrapper' +import { Size2D } from './SideBarBodyWrapper' type Props = { - size: Size - onResize(size: Size): void + size: Size2D + onResize(size: Size2D): void onResetSize?(): void onResizeStateChange?(state: ResizeState): void style?: React.CSSProperties @@ -12,55 +12,13 @@ type Props = { type ResizeState = 'idle' | 'resizing' -export function HorizontalResizeHandler({ - onResize, - onResetSize, - onResizeStateChange, - size, - style, -}: Props) { - const pointerDown = React.useRef(false) - const startX = React.useRef(0) - const baseSize = React.useRef(size) - const latestPropSize = React.useRef(size) - - React.useEffect(() => { - latestPropSize.current = size - }, [size]) - - const onPointerDown = React.useCallback(({ clientX }: React.MouseEvent) => { - startX.current = clientX - pointerDown.current = true - baseSize.current = latestPropSize.current - onResizeStateChange?.('resizing') - }, []) - - React.useEffect(() => { - const onPointerMove = ({ clientX }: MouseEvent) => { - if (!pointerDown.current) return - const shift = clientX - startX.current - onResize(baseSize.current + shift) - } - window.addEventListener('mousemove', onPointerMove) - return () => window.removeEventListener('mousemove', onPointerMove) - }, [onResize]) - - React.useEffect(() => { - const onPointerUp = () => { - if (pointerDown.current) { - pointerDown.current = false - baseSize.current = latestPropSize.current - onResizeStateChange?.('idle') - } - } - window.addEventListener('mouseup', onPointerUp) - return () => window.removeEventListener('mouseup', onPointerUp) - }, []) +export function ResizeHandler({ onResize, onResetSize, onResizeStateChange, size, style }: Props) { + const { onPointerDown } = useResizeHandler(size, onResize, { onResizeStateChange }) return (
@@ -68,3 +26,61 @@ export function HorizontalResizeHandler({
) } + +export function useResizeHandler( + size: Size2D, + onResize: (size: Size2D) => void, + { + onResizeStateChange, + onClick, + }: Partial<{ + onResizeStateChange: (state: ResizeState) => void + onClick: (e: PointerEvent) => void + }> = {}, +) { + const pointerDown = React.useRef(false) + const pointerMoved = React.useRef(false) + const initialSizeRef = React.useRef([0, 0]) + const baseSize = React.useRef(size) + const latestPropSize = React.useRef(size) + + React.useEffect(() => { + latestPropSize.current = size + }, [size]) + + React.useEffect(() => { + const onPointerMove = ({ clientX, clientY }: PointerEvent) => { + if (!pointerDown.current) return + pointerMoved.current = true + const [x, y] = baseSize.current + onResize([x + clientX - initialSizeRef.current[0], y + clientY - initialSizeRef.current[1]]) + } + window.addEventListener('pointermove', onPointerMove) + return () => window.removeEventListener('pointermove', onPointerMove) + }, [onResize]) + + React.useEffect(() => { + const onPointerUp = (e: PointerEvent) => { + if (pointerDown.current) { + pointerDown.current = false + + if (!pointerMoved.current) onClick?.(e) + pointerMoved.current = false + + baseSize.current = latestPropSize.current + onResizeStateChange?.('idle') + } + } + window.addEventListener('pointerup', onPointerUp) + return () => window.removeEventListener('pointerup', onPointerUp) + }, []) + + const onPointerDown = React.useCallback(({ clientX, clientY }: React.PointerEvent) => { + pointerDown.current = true + initialSizeRef.current = [clientX, clientY] + baseSize.current = latestPropSize.current + onResizeStateChange?.('resizing') + }, []) + + return { onPointerDown } +} diff --git a/src/components/SideBarBodyWrapper.tsx b/src/components/SideBarBodyWrapper.tsx index 9d05b46..63dd118 100644 --- a/src/components/SideBarBodyWrapper.tsx +++ b/src/components/SideBarBodyWrapper.tsx @@ -1,4 +1,4 @@ -import { HorizontalResizeHandler } from 'components/ResizeHandler' +import { ResizeHandler } from 'components/ResizeHandler' import { useConfigs } from 'containers/ConfigsContext' import * as React from 'react' import { useDebounce, useWindowSize } from 'react-use' @@ -7,7 +7,8 @@ import { cx } from 'utils/cx' import { setCSSVariable } from 'utils/DOMHelper' import * as features from 'utils/features' -export type Size = number +type Size = number +export type Size2D = [Size, Size] type Props = { baseSize: Size className?: string @@ -67,7 +68,7 @@ export function SideBarBodyWrapper({ const onResize = React.useMemo(() => { let sizeToApply: number, applied = true - return (size: number) => { + return ([size]: number[]) => { // do NOT merge this with the above similar effect, side bar will jump otherwise sizeToApply = getSafeSize(size, width) setSize(sizeToApply) @@ -94,6 +95,7 @@ export function SideBarBodyWrapper({ [onLeave], ) + const newLocal: [number, number] = React.useMemo(() => [size, size], [size]) return (
{children}
{features.resize && ( - { setSize(defaultConfigs.sideBarWidth) @@ -111,7 +113,7 @@ export function SideBarBodyWrapper({ onResizeStateChange={state => { blockLeaveRef.current = state === 'resizing' }} - size={size} + size={newLocal} /> )}
diff --git a/src/components/ToggleShowButton.tsx b/src/components/ToggleShowButton.tsx index 85fdb4e..fada7ec 100644 --- a/src/components/ToggleShowButton.tsx +++ b/src/components/ToggleShowButton.tsx @@ -4,30 +4,31 @@ import * as React from 'react' import { useDebounce, useWindowSize } from 'react-use' import { cx } from 'utils/cx' import { Icon } from './Icon' +import { useResizeHandler } from './ResizeHandler' type Props = { error?: string | null className?: React.HTMLAttributes['className'] onHover?: React.HTMLAttributes['onMouseEnter'] -} & Pick, 'onClick'> + onClick?: (e: PointerEvent) => void +} + +const buttonHeight = 42 + +function getSafeDistance(y: number, height: number) { + return Math.max(0, Math.min(y, height - buttonHeight)) +} export function ToggleShowButton({ error, className, onClick, onHover }: Props) { const ref = React.useRef(null) const config = useConfigs() const [distance, setDistance] = React.useState(config.value.toggleButtonVerticalDistance) const { height } = useWindowSize() - const buttonHeight = 42 React.useEffect(() => { // make sure it is inside viewport - if (height - buttonHeight < distance) { - setDistance(Math.max(0, height - buttonHeight)) - } + const safeDistance = getSafeDistance(distance, height) + if (safeDistance !== distance) setDistance(safeDistance) }, [height, distance]) - React.useLayoutEffect(() => { - if (ref.current) { - ref.current.style.top = distance + 'px' - } - }, [distance]) // updating context useDebounce( @@ -36,28 +37,34 @@ export function ToggleShowButton({ error, className, onClick, onHover }: Props) [distance], ) - const toggleIconMode = config.value.toggleButtonContent + React.useEffect(() => { + if (ref.current) { + ref.current.style.top = distance + 'px' + } + }, []) + const { onPointerDown } = useResizeHandler( + [distance, distance], + ([, y]) => { + const distance = getSafeDistance(y, height) + setDistance(distance) + if (ref.current) { + ref.current.style.top = distance + 'px' + } + }, + { onClick }, + ) + return (
) } - -function hideDragPreview(event: React.DragEvent) { - const img = new Image() - const EMPTY_IMAGE_BASE64 = - 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=' - img.src = EMPTY_IMAGE_BASE64 - event.dataTransfer.setDragImage(img, 0, 0) -}