diff --git a/src/components/ResizeableContent/ResizeableContent.tsx b/src/components/ResizeableContent/ResizeableContent.tsx new file mode 100644 index 0000000..7743af9 --- /dev/null +++ b/src/components/ResizeableContent/ResizeableContent.tsx @@ -0,0 +1,131 @@ +import { + PropsWithChildren, + TouchEvent as ReactTouchEvent, + useCallback, + useRef, + useState +} from 'react'; +import { ActionIcon, Box } from '@mantine/core'; + +import { DragHandleIcon } from '@/icons/icons'; +import { IconSize } from '@/types/enums'; +import { useWindowSize } from '@/windowmanagement/Window/hooks'; + +interface Props extends PropsWithChildren { + minHeight?: number; + defaultHeight: number; +} + +export function ResizeableContent({ minHeight = 40, defaultHeight, children }: Props) { + const [height, setHeight] = useState(defaultHeight); + const { pointerEvents: windowPointer } = useWindowSize(); + + const contentRef = useRef(null); + const resizerRef = useRef(null); + + const updateCursor = useCallback(() => { + document.body.style.cursor = 'row-resize'; + // Disable highlight of the window content when dragging + windowPointer.disable(); + }, [windowPointer]); + + const resetCursor = useCallback(() => { + document.body.style.removeProperty('cursor'); + // Reset the regular behaviour of the window + windowPointer.enable(); + }, [windowPointer]); + + const setHeightClamped = useCallback( + (newHeight: number) => { + setHeight(Math.max(newHeight, minHeight)); + }, + [minHeight] + ); + + const handleMouseDown = useCallback( + (x: number, y: number) => { + const startPos = { + x: x, + y: y + }; + const currentFirstHeight = contentRef.current?.clientHeight; + + const handleMouseMove = (e: MouseEvent) => { + if (!currentFirstHeight) { + return; + } + const dy = e.clientY - startPos.y; + const newHeight = currentFirstHeight + dy; + setHeightClamped(newHeight); + updateCursor(); + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + resetCursor(); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }, + [contentRef, setHeightClamped, resetCursor, updateCursor] + ); + + const handleTouchStart = useCallback( + (e: ReactTouchEvent) => { + const startPos = { + x: e.touches[0].clientX, + y: e.touches[0].clientY + }; + const currentFirstHeight = contentRef.current?.clientHeight; + + const handleTouchMove = (e: TouchEvent) => { + if (!currentFirstHeight) { + return; + } + // Prevent scrolling when dragging + e.preventDefault(); + + const [touch] = e.touches; + const dy = touch.clientY - startPos.y; + const newHeight = currentFirstHeight + dy; + setHeightClamped(newHeight); + updateCursor(); + }; + + const handleTouchEnd = () => { + document.removeEventListener('touchmove', handleTouchMove); + document.removeEventListener('touchend', handleTouchEnd); + resetCursor(); + }; + // To block scrolling we need to have an active listener, not passive + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleTouchEnd); + }, + [contentRef, setHeightClamped, resetCursor, updateCursor] + ); + + return ( + <> + + {children} + + + handleMouseDown(e.clientX, e.clientY)} + onTouchStart={handleTouchStart} + style={{ + cursor: 'row-resize' + }} + size={'xs'} + radius={0} + variant={'default'} + > + + + + + ); +} diff --git a/src/panels/ExoplanetsPanel/ExoplanetsPanel.tsx b/src/panels/ExoplanetsPanel/ExoplanetsPanel.tsx index 86ec783..eaf6c68 100644 --- a/src/panels/ExoplanetsPanel/ExoplanetsPanel.tsx +++ b/src/panels/ExoplanetsPanel/ExoplanetsPanel.tsx @@ -6,6 +6,7 @@ import { Collapsable } from '@/components/Collapsable/Collapsable'; import { FilterList } from '@/components/FilterList/FilterList'; import { wordBeginningSubString } from '@/components/FilterList/util'; import { Property } from '@/components/Property/Property'; +import { ResizeableContent } from '@/components/ResizeableContent/ResizeableContent'; import { initializeExoplanets } from '@/redux/exoplanets/exoplanetsSlice'; import { useAppDispatch, useAppSelector } from '@/redux/hooks'; import { Identifier } from '@/types/types'; @@ -87,9 +88,9 @@ export function ExoplanetsPanel() { } return ( - - - + + + data={allSystemNames} @@ -104,26 +105,24 @@ export function ExoplanetsPanel() { matcherFunc={wordBeginningSubString} /> - - - - - - - - - Added Systems - - {addedSystems.length === 0 ? ( - No active systems - ) : ( - addedSystems.map( - (hostStar) => - hostStar && - ) - )} - - - + + + + + + + + Added Systems + + {addedSystems.length === 0 ? ( + No active systems + ) : ( + addedSystems.map( + (hostStar) => + hostStar && + ) + )} + + ); } diff --git a/src/panels/GeoLocationPanel/AnchorPanels/EarthPanel/EarthPanel.tsx b/src/panels/GeoLocationPanel/AnchorPanels/EarthPanel/EarthPanel.tsx index 06cec44..393ee58 100644 --- a/src/panels/GeoLocationPanel/AnchorPanels/EarthPanel/EarthPanel.tsx +++ b/src/panels/GeoLocationPanel/AnchorPanels/EarthPanel/EarthPanel.tsx @@ -4,7 +4,6 @@ import { Button, Checkbox, Container, - Divider, Group, NumberInput, Tabs, @@ -17,6 +16,7 @@ import { import { useOpenSpaceApi } from '@/api/hooks'; import { FilterList } from '@/components/FilterList/FilterList'; import { generateMatcherFunctionByKeys } from '@/components/FilterList/util'; +import { ResizeableContent } from '@/components/ResizeableContent/ResizeableContent'; import { SettingsPopout } from '@/components/SettingsPopout/SettingsPopout'; import { MinusIcon } from '@/icons/icons'; import { useAppSelector } from '@/redux/hooks'; @@ -193,25 +193,27 @@ export function EarthPanel({ currentAnchor }: Props) { {places.length > 0 ? ( - - - - data={places} - renderElement={(place) => ( - - )} - matcherFunc={generateMatcherFunctionByKeys(['address', 'attributes'])} - /> - + + + + + data={places} + renderElement={(place) => ( + + )} + matcherFunc={generateMatcherFunctionByKeys(['address', 'attributes'])} + /> + + ) : ( Nothing found. Try another search! )} @@ -227,8 +229,6 @@ export function EarthPanel({ currentAnchor }: Props) { - - Added Nodes diff --git a/src/windowmanagement/Window/Window.tsx b/src/windowmanagement/Window/Window.tsx index b53373a..eb4f2b5 100644 --- a/src/windowmanagement/Window/Window.tsx +++ b/src/windowmanagement/Window/Window.tsx @@ -16,11 +16,28 @@ export function Window({ children }: PropsWithChildren) { window.location.reload(); } + function disablePointerEvents(): void { + if (!ref.current) { + return; + } + ref.current.style.pointerEvents = 'none'; + ref.current.style.userSelect = 'none'; + } + + function enablePointerEvents(): void { + if (!ref.current) { + return; + } + ref.current.style.removeProperty('user-select'); + ref.current.style.removeProperty('pointer-events'); + } + return ( diff --git a/src/windowmanagement/Window/WindowSizeContext.tsx b/src/windowmanagement/Window/WindowSizeContext.tsx index 294e5f5..f89a717 100644 --- a/src/windowmanagement/Window/WindowSizeContext.tsx +++ b/src/windowmanagement/Window/WindowSizeContext.tsx @@ -3,6 +3,7 @@ import { createContext } from 'react'; export interface ProviderProps { width: number; height: number; + pointerEvents: { enable: () => void; disable: () => void }; } export const WindowSizeContext = createContext(null);