diff --git a/ui/component/fileRenderFloating/view.jsx b/ui/component/fileRenderFloating/view.jsx index 2373e0f27..c9fa8b68c 100644 --- a/ui/component/fileRenderFloating/view.jsx +++ b/ui/component/fileRenderFloating/view.jsx @@ -375,9 +375,9 @@ export default function FileRenderFloating(props: Props) { return ( HEADER_HEIGHT_MOBILE; + + if (draggingBelowHeader) { + const root = drawerRoot.current; + if (root) { + root.setAttribute('style', `transform: none !important`); + } + + if (paperRef.current) { + paperRef.current.setAttribute('style', `transform: translateY(${touchPosY}px) !important`); + } + + // makes the backdrop lighter/darker based on how high/low the drawer is + const backdrop = backdropRef.current; + if (backdrop) { + const isDraggingAboveVideo = touchPosY < playerHeight + HEADER_HEIGHT_MOBILE; + let backdropTop = HEADER_HEIGHT_MOBILE + playerHeight; + // $FlowFixMe + let backdropHeight = document.documentElement.getBoundingClientRect().height - backdropTop; + let opacity = ((touchPosY - HEADER_HEIGHT_MOBILE) / backdropHeight) * -1 + 1; + + // increase the backdrop height so it also covers the video when pulling the drawer up + if (isDraggingAboveVideo) { + backdropTop = HEADER_HEIGHT_MOBILE; + backdropHeight = playerHeight; + opacity = ((touchPosY - HEADER_HEIGHT_MOBILE) / backdropHeight) * -1 + 1; + } + + backdrop.setAttribute('style', `top: ${backdropTop}px; opacity: ${opacity}`); + } + } + } + + function handleTouchEnd() { + // set by touchMove + if (!touchPos.current) return; + + const root = drawerRoot.current; + + if (root) { + const middleOfVideo = HEADER_HEIGHT_MOBILE + playerHeight / 2; + const drawerMovedFullscreen = touchPos.current < middleOfVideo; + // $FlowFixMe + const restOfPage = document.documentElement.clientHeight - playerHeight - HEADER_HEIGHT_MOBILE; + const draggedBeforeCloseLimit = touchPos.current - playerHeight - HEADER_HEIGHT_MOBILE < restOfPage * 0.2; + const backdrop = backdropRef.current; + + if (draggedBeforeCloseLimit) { + const minDrawerHeight = HEADER_HEIGHT_MOBILE + playerHeight; + const positionToStop = drawerMovedFullscreen ? HEADER_HEIGHT_MOBILE : minDrawerHeight; + + if (paperRef.current) { + paperRef.current.setAttribute('style', `transform: none !important; transition: transform ${TRANSITION_STR}`); + } + root.setAttribute( + 'style', + `transform: translateY(${positionToStop}px) !important; transition: transform ${TRANSITION_STR}` + ); + + setTimeout(() => { + root.style.height = `calc(100% - ${positionToStop}px)`; + }, TRANSITION_MS); + + if (backdrop) { + backdrop.setAttribute('style', 'opacity: 0'); + + setTimeout(() => { + backdrop.setAttribute('style', `transition: opacity ${TRANSITION_STR}; opacity: 1`); + }, TRANSITION_MS); + } + + // Pause video if drawer made fullscreen (above the player) + const playerElement = document.querySelector('.content__viewer--inline'); + const videoParent = playerElement && playerElement.querySelector('.video-js'); + const isLivestream = videoParent && videoParent.classList.contains('livestreamPlayer'); + const videoNode = videoParent && videoParent.querySelector('.vjs-tech'); + // $FlowFixMe + const isPlaying = videoNode && !videoNode.paused; + + if (videoNode && !isLivestream && isPlaying && drawerMovedFullscreen) { + // $FlowFixMe + videoNode.pause(); + pausedByDrawer.current = true; + } else { + handleUnpausePlayer(); + } + } else { + handleCloseDrawer(); + + if (backdrop) { + backdrop.setAttribute('style', 'opacity: 0'); + } + } + } + + // clear if not being touched anymore + touchPos.current = undefined; + } + + function handleUnpausePlayer() { + // Unpause on close and was paused by the drawer + const videoParent = document.querySelector('.video-js'); + const videoNode = videoParent && videoParent.querySelector('.vjs-tech'); + + if (videoNode && pausedByDrawer.current) { + // $FlowFixMe + videoNode.play(); + pausedByDrawer.current = false; + } + } + + function handleCloseDrawer() { + handleUnpausePlayer(); + toggleDrawer(); + } const handleResize = React.useCallback(() => { const element = @@ -43,19 +170,16 @@ export default function SwipeableDrawer(props: Props) { if (!element) return; const rect = element.getBoundingClientRect(); - setCoverHeight(rect.height); + setPlayerHeight(rect.height); }, []); React.useEffect(() => { // Drawer will follow the cover image on resize, so it's always visible - if (open) { - handleResize(); + handleResize(); - window.addEventListener('resize', handleResize); - - return () => window.removeEventListener('resize', handleResize); - } - }, [handleResize, open]); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [handleResize]); // Reset scroll position when opening: avoid broken position where // the drawer is lower than the video @@ -66,57 +190,85 @@ export default function SwipeableDrawer(props: Props) { } }, [open]); + React.useEffect(() => { + return () => { + if (openPrev.current) { + handleCloseDrawer(); + } + }; + + // close drawer on unmount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const drawerElemRef = React.useCallback( + (node) => { + if (node) { + const isFullscreenDrawer = node.style.transform.includes(`translateY(${HEADER_HEIGHT_MOBILE}px)`); + const openStateChanged = openPrev.current !== open; // so didn't run because of window resize + + if (!isFullscreenDrawer || openStateChanged) { + node.setAttribute( + 'style', + `transform: translateY(${HEADER_HEIGHT_MOBILE + playerHeight}px); height: calc(100% - ${ + HEADER_HEIGHT_MOBILE + playerHeight + }px);` + ); + } + + drawerRoot.current = node; + openPrev.current = open; + } + }, + [open, playerHeight] + ); + return ( <> - + - {open && (
- +
)} {children} -
+ ); } type GlobalStylesProps = { open: boolean, - videoHeight: number, }; const DrawerGlobalStyles = (props: GlobalStylesProps) => { - const { open, videoHeight } = props; + const { open } = props; return ( .MuiPaper-root': { - overflow: 'visible', - color: 'var(--color-text)', - position: 'absolute', - height: `calc(100% - ${DRAWER_PULLER_HEIGHT}px)`, + maxHeight: open ? '100%' : 'unset', }, }} /> @@ -139,24 +291,25 @@ type HeaderProps = { title: any, hasSubtitle?: boolean, actions?: any, - toggleDrawer: () => void, + handleClose: () => void, }; const HeaderContents = (props: HeaderProps) => { - const { title, hasSubtitle, actions, toggleDrawer } = props; + const { title, hasSubtitle, actions, handleClose, ...divProps } = props; return (
{title}
{actions} -
); diff --git a/ui/scss/component/_livestream-chat.scss b/ui/scss/component/_livestream-chat.scss index 4b386958b..a0f29f6ed 100644 --- a/ui/scss/component/_livestream-chat.scss +++ b/ui/scss/component/_livestream-chat.scss @@ -28,6 +28,9 @@ $recent-msg-button__height: 2rem; @media (min-width: $breakpoint-small) { top: calc(var(--header-height) + var(--spacing-m)) !important; position: sticky; + resize: horizontal; + overflow: hidden; + max-width: 30vw; } @media (min-width: $breakpoint-medium) { @@ -154,6 +157,8 @@ $recent-msg-button__height: 2rem; height: 100vh !important; top: 0 !important; overflow: hidden; + resize: none; + max-width: unset; .livestreamComments__wrapper { height: 95vh !important; @@ -488,7 +493,6 @@ $recent-msg-button__height: 2rem; @media (min-width: $breakpoint-small) { padding: var(--spacing-xs); - width: var(--livestream-comments-width); } } @@ -578,7 +582,6 @@ $recent-msg-button__height: 2rem; @media (min-width: $breakpoint-small) { padding: var(--spacing-xs); - width: var(--livestream-comments-width); } } diff --git a/ui/scss/component/_main.scss b/ui/scss/component/_main.scss index 59bb51d17..4db0b219b 100644 --- a/ui/scss/component/_main.scss +++ b/ui/scss/component/_main.scss @@ -592,7 +592,10 @@ body { max-width: var(--page-max-width--filepage); position: relative; display: grid; - grid-template-columns: 1fr minmax(0, var(--livestream-comments-width)); + + $file-min-width: calc(var(--page-max-width--filepage) - var(--livestream-comments-width)); + grid-template-columns: minmax($file-min-width, 0) 1fr; + justify-content: space-between; justify-items: end; gap: var(--spacing-m);