import { MouseEventHandler , Reducer , useCallback , useEffect , useReducer , useRef } from 'react' ; import { debounce , throttle } from '@proton/shared/lib/helpers/function' ; import { useMailSettings } from '@proton/components' ; import { COMPOSER_MODE } from '@proton/shared/lib/constants' ; import { COMPOSER_GUTTER , COMPOSER_WIDTH , computeRightPosition } from '../../helpers/composerPositioning' ; interface Props { windowWidth : number ; minimized : boolean ; maximized : boolean ; isNarrow : boolean ; totalComposers : number ; composerIndex : number ; } interface State { isDragging : boolean ; initialCursorPosition : number | null ; offset : number ; lastOffset : number ; } type Action = { type : 'start' ; payload : Pick < State , 'isDragging' | 'initialCursorPosition' > ; } | { type : 'stop' ; payload : Pick < State , 'isDragging' | 'initialCursorPosition' > ; } | { type : 'move' ; payload : Pick < State , 'offset' > ; } | { type : 'reset-offset' ; } ; const moveReducer = ( state , action ) => { switch ( action . type ) { case 'start' : case 'move' : return { ... state , ... action . payload } ; case 'stop' : return { ... state , lastOffset : state . offset , ... action . payload } ; case 'reset-offset' : return { ... state , offset : 0 } ; default : throw new Error ( 'This action does not exist' ) ; } } ; const useComposerDrag = ( { windowWidth , maximized , minimized , totalComposers , composerIndex } ) => { const [ mailSettings ] = useMailSettings ( ) ; const prevMinimized = useRef ( minimized ) ; const prevMaximized = useRef ( maximized ) ; const composerRightStyle = computeRightPosition ( composerIndex , totalComposers , windowWidth ) ; const [ { isDragging , initialCursorPosition , offset , lastOffset } , dispatch ] = useReducer < Reducer < State , Action > > ( moveReducer , { isDragging : false , initialCursorPosition : null , offset : 0 , lastOffset : 0 , } ) ; const handleStartDragging = ( e ) => { dispatch ( { type : 'start' , payload : { isDragging : true , initialCursorPosition : e . clientX } } ) ; } ; const handleStopDragging = ( ) => { dispatch ( { type : 'stop' , payload : { isDragging : false , initialCursorPosition : null } } ) ; } ; const handleMouseMove = useCallback ( ( e ) => { if ( ! initialCursorPosition || ! isDragging || ( maximized && ! minimized ) ) { return ; } const composerWidth = COMPOSER_WIDTH ; const prevOffset = offset ; const cursorMoveOffset = e . clientX - initialCursorPosition ; let finalOffset = prevOffset + cursorMoveOffset ; const composerRightCornerPos = windowWidth + finalOffset - composerRightStyle ; const composerLeftCornerPos = windowWidth + finalOffset - composerRightStyle - composerWidth ; if ( composerLeftCornerPos < COMPOSER_GUTTER ) { const remainingWidth = windowWidth - composerRightStyle - composerWidth ; finalOffset = - remainingWidth + COMPOSER_GUTTER ; } if ( composerRightCornerPos > windowWidth - COMPOSER_GUTTER ) { finalOffset = composerRightStyle - COMPOSER_GUTTER ; } dispatch ( { type : 'move' , payload : { offset : finalOffset } } ) ; } , [ isDragging , initialCursorPosition , maximized ] ) ; useEffect ( ( ) => { const throttledMouseMove = throttle ( handleMouseMove , 20 ) ; const debouncedStopDragging = debounce ( handleStopDragging , 50 ) ; if ( isDragging ) { document . addEventListener ( 'mousemove' , throttledMouseMove ) ; document . addEventListener ( 'mouseup' , debouncedStopDragging ) ; document . addEventListener ( 'mouseleave' , debouncedStopDragging ) ; } return ( ) => { document . removeEventListener ( 'mousemove' , throttledMouseMove ) ; document . removeEventListener ( 'mouseup' , debouncedStopDragging ) ; document . removeEventListener ( 'mouseleave' , debouncedStopDragging ) ; } ; } , [ isDragging ] ) ; useEffect ( ( ) => { dispatch ( { type : 'reset-offset' } ) ; } , [ composerRightStyle , totalComposers ] ) ; useEffect ( ( ) => { if ( mailSettings ?. ComposerMode === COMPOSER_MODE . MAXIMIZED ) { dispatch ( { type : 'reset-offset' } ) ; } } , [ mailSettings ?. ComposerMode ] ) ; useEffect ( ( ) => { if ( minimized === false && maximized === true && prevMaximized . current === true && prevMinimized . current === true ) { dispatch ( { type : 'reset-offset' } ) ; } prevMinimized . current = minimized ; } , [ minimized ] ) ; useEffect ( ( ) => { if ( minimized === false && maximized === false && prevMaximized . current === true && prevMinimized . current === true ) { prevMaximized . current = maximized ; return ; } dispatch ( { type : 'reset-offset' } ) ; prevMaximized . current = maximized ; } , [ maximized ] ) ; return { start : handleStartDragging , offset , isDragging : isDragging === true && ( offset > lastOffset + 10 || offset < lastOffset - 10 ) , } ; } ; export default useComposerDrag ;