// @flow import 'scss/component/_swipeable-drawer.scss'; // $FlowFixMe import { Global } from '@emotion/react'; // $FlowFixMe import { grey } from '@mui/material/colors'; import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderFloating/view'; import { PRIMARY_PLAYER_WRAPPER_CLASS, PRIMARY_IMAGE_WRAPPER_CLASS } from 'page/file/view'; import { getMaxLandscapeHeight } from 'component/fileRenderFloating/helper-functions'; import Drawer from '@mui/material/Drawer'; import * as ICONS from 'constants/icons'; import * as React from 'react'; import Button from 'component/button'; import classnames from 'classnames'; const DRAWER_PULLER_HEIGHT = 42; const TRANSITION_MS = 225; const TRANSITION_STR = `${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) 0ms`; type Props = { children: Node, title: any, hasSubtitle?: boolean, actions?: any, // -- redux -- open: boolean, theme: string, toggleDrawer: () => void, }; export default function SwipeableDrawer(props: Props) { const { title, hasSubtitle, children, open, theme, actions, toggleDrawer } = props; const drawerRoot = React.useRef(); const backdropRef = React.useRef(); const paperRef = React.useRef(); const pausedByDrawer = React.useRef(false); const touchPos = React.useRef(); const openPrev = React.useRef(open); const [playerHeight, setPlayerHeight] = React.useState(getMaxLandscapeHeight()); function handleTouchMove(e) { const touchPosY = e.touches[0].clientY; touchPos.current = touchPosY; const draggingBelowHeader = touchPosY > 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 = document.querySelector(`.${PRIMARY_IMAGE_WRAPPER_CLASS}`) || document.querySelector(`.${PRIMARY_PLAYER_WRAPPER_CLASS}`); if (!element) return; const rect = element.getBoundingClientRect(); setPlayerHeight(rect.height); }, []); React.useEffect(() => { // Drawer will follow the cover image on resize, so it's always visible handleResize(); 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 React.useEffect(() => { if (open) { const htmlEl = document.querySelector('html'); if (htmlEl) htmlEl.scrollTop = 0; } }, [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, }; const DrawerGlobalStyles = (props: GlobalStylesProps) => { const { open } = props; return ( ); }; type PullerProps = { theme: string, }; const Puller = (props: PullerProps) => { const { theme } = props; return ( ); }; type HeaderProps = { title: any, hasSubtitle?: boolean, actions?: any, handleClose: () => void, }; const HeaderContents = (props: HeaderProps) => { const { title, hasSubtitle, actions, handleClose, ...divProps } = props; return (
{title}
{actions}
); };