mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-27 21:44:21 -04:00

Fix Splitter issues * Replace code with hook useAdjustedRelativeSplitValue * Add e2e tests for splitter Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
148 lines
5 KiB
TypeScript
148 lines
5 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react'
|
|
import { ShowIf } from '../../common/show-if/show-if'
|
|
import { SplitDivider } from './split-divider/split-divider'
|
|
import './splitter.scss'
|
|
import { useAdjustedRelativeSplitValue } from './hooks/use-adjusted-relative-split-value'
|
|
|
|
export interface SplitterProps {
|
|
left: ReactElement
|
|
right: ReactElement
|
|
additionalContainerClassName?: string
|
|
showLeft: boolean
|
|
showRight: boolean
|
|
}
|
|
|
|
/**
|
|
* Checks if the given {@link Event} is a {@link MouseEvent}
|
|
* @param event the event to check
|
|
* @return {@code true} if the given event is a {@link MouseEvent}
|
|
*/
|
|
const isMouseEvent = (event: Event): event is MouseEvent => {
|
|
return (event as MouseEvent).buttons !== undefined
|
|
}
|
|
|
|
const isLeftMouseButtonClicked = (mouseEvent: MouseEvent): boolean => {
|
|
return mouseEvent.buttons === 1
|
|
}
|
|
|
|
/**
|
|
* Extracts the absolute horizontal position of the mouse or touch point from the event.
|
|
* If no position could be found or
|
|
*
|
|
* @param moveEvent
|
|
*/
|
|
const extractHorizontalPosition = (moveEvent: MouseEvent | TouchEvent): number => {
|
|
if (isMouseEvent(moveEvent)) {
|
|
return moveEvent.pageX
|
|
} else {
|
|
return moveEvent.touches[0]?.pageX
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a Left/Right splitter react component.
|
|
*
|
|
* @param additionalContainerClassName css classes that are added to the split container.
|
|
* @param left the react component that should be shown on the left side.
|
|
* @param right the react component that should be shown on the right side.
|
|
* @param showLeft defines if the left component should be shown or hidden. Settings this prop will hide the component with css.
|
|
* @param showRight defines if the right component should be shown or hidden. Settings this prop will hide the component with css.
|
|
* @return the created component
|
|
*/
|
|
export const Splitter: React.FC<SplitterProps> = ({
|
|
additionalContainerClassName,
|
|
left,
|
|
right,
|
|
showLeft,
|
|
showRight
|
|
}) => {
|
|
const [relativeSplitValue, setRelativeSplitValue] = useState(50)
|
|
const adjustedRelativeSplitValue = useAdjustedRelativeSplitValue(showLeft, showRight, relativeSplitValue)
|
|
const resizingInProgress = useRef(false)
|
|
const splitContainer = useRef<HTMLDivElement>(null)
|
|
|
|
/**
|
|
* Starts the splitter resizing
|
|
*/
|
|
const onStartResizing = useCallback(() => {
|
|
resizingInProgress.current = true
|
|
}, [])
|
|
|
|
/**
|
|
* Stops the splitter resizing
|
|
*/
|
|
const onStopResizing = useCallback(() => {
|
|
if (resizingInProgress.current) {
|
|
resizingInProgress.current = false
|
|
}
|
|
}, [])
|
|
|
|
/**
|
|
* Recalculates the panel split based on the absolute mouse/touch position.
|
|
*
|
|
* @param moveEvent is a {@link MouseEvent} or {@link TouchEvent} that got triggered.
|
|
*/
|
|
const onMove = useCallback((moveEvent: MouseEvent | TouchEvent) => {
|
|
if (!resizingInProgress.current || !splitContainer.current) {
|
|
return
|
|
}
|
|
if (isMouseEvent(moveEvent) && !isLeftMouseButtonClicked(moveEvent)) {
|
|
resizingInProgress.current = false
|
|
moveEvent.preventDefault()
|
|
return undefined
|
|
}
|
|
|
|
const horizontalPosition = extractHorizontalPosition(moveEvent)
|
|
const horizontalPositionInSplitContainer = horizontalPosition - splitContainer.current.offsetLeft
|
|
const newRelativeSize = horizontalPositionInSplitContainer / splitContainer.current.clientWidth
|
|
setRelativeSplitValue(newRelativeSize * 100)
|
|
moveEvent.preventDefault()
|
|
}, [])
|
|
|
|
/**
|
|
* Registers and unregisters necessary event listeners on the body so you can use the split even if the mouse isn't moving over it.
|
|
*/
|
|
useEffect(() => {
|
|
const moveHandler = onMove
|
|
const stopResizeHandler = onStopResizing
|
|
window.addEventListener('touchmove', moveHandler)
|
|
window.addEventListener('mousemove', moveHandler)
|
|
window.addEventListener('touchcancel', stopResizeHandler)
|
|
window.addEventListener('touchend', stopResizeHandler)
|
|
window.addEventListener('mouseup', stopResizeHandler)
|
|
|
|
return () => {
|
|
window.removeEventListener('touchmove', moveHandler)
|
|
window.removeEventListener('mousemove', moveHandler)
|
|
window.removeEventListener('touchcancel', stopResizeHandler)
|
|
window.removeEventListener('touchend', stopResizeHandler)
|
|
window.removeEventListener('mouseup', stopResizeHandler)
|
|
}
|
|
}, [resizingInProgress, onMove, onStopResizing])
|
|
|
|
return (
|
|
<div ref={splitContainer} className={`flex-fill flex-row d-flex ${additionalContainerClassName || ''}`}>
|
|
<div
|
|
className={`splitter left ${!showLeft ? 'd-none' : ''}`}
|
|
style={{ width: `calc(${adjustedRelativeSplitValue}% - 5px)` }}>
|
|
{left}
|
|
</div>
|
|
<ShowIf condition={showLeft && showRight}>
|
|
<div className='splitter separator'>
|
|
<SplitDivider onGrab={onStartResizing} />
|
|
</div>
|
|
</ShowIf>
|
|
<div
|
|
className={`splitter right ${!showRight ? 'd-none' : ''}`}
|
|
style={{ width: `calc(100% - ${adjustedRelativeSplitValue}%)` }}>
|
|
{right}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|