diff --git a/src/components/navbars/appNavBar.tsx b/src/components/navbars/appNavBar.tsx index ecdd9f7..6c37725 100644 --- a/src/components/navbars/appNavBar.tsx +++ b/src/components/navbars/appNavBar.tsx @@ -23,14 +23,14 @@ import { useTheme } from '@mui/material'; -const currTheme = createTheme({ - palette: { - primary: { - main: '#ffffff', - contrastText: '#000000' - }, - }, -}); +// const currTheme = createTheme({ +// palette: { +// primary: { +// main: '#ffffff', +// contrastText: '#000000' +// }, +// }, +// }); export default function AppNavBar(props) { const [isDrawerOpen, setDrawerOpen] = React.useState(false); @@ -41,6 +41,81 @@ export default function AppNavBar(props) { const apiDisplayName = API_Name(apiStatus.currentApi); const accentColor = displayStatus.secondaryColor; + const TOP_TRIGGER_ZONE = 48; // px from top to reveal + const HIDE_TIMEOUT_MS = 2500; + const [topbarLocked, setTopbarLocked] = React.useState(false); + const [topbarVisible, setTopbarVisible] = React.useState(true); + const hideRef = React.useRef(null); + const pointerStartY = React.useRef(null); + const pointerId = React.useRef(null); + + const showTopbar = React.useCallback(() => { + setTopbarVisible(true); + if (hideRef.current) {window.clearTimeout(hideRef.current); hideRef.current = null;} + if (!topbarLocked) { + hideRef.current = window.setTimeout(() => setTopbarVisible(false), HIDE_TIMEOUT_MS); + } + }, [topbarLocked]); + + React.useEffect(() => {showTopbar();}, [showTopbar]); //to activate immediately + + React.useEffect(() => { + const onPointerDown = (e: PointerEvent) => { + if(pointerId.current !== null) return; + + const y = e.clientY; + pointerId.current = e.pointerId; + pointerStartY.current = y; + + if (y <= TOP_TRIGGER_ZONE) { + showTopbar(); + } + }; + + const onPointerMove = (e: PointerEvent) => { + // proximity check + if (e.clientY <= TOP_TRIGGER_ZONE) { + showTopbar(); + } + // swipe down check + if (e.pointerId !== pointerId.current || pointerStartY.current === null) { + return; + } + const y = e.clientY + const delta = y - (pointerStartY.current ?? 0); + const SWIPE_THRESHOLD = 40; //how far to swipe + + if(pointerStartY.current <= TOP_TRIGGER_ZONE && delta >= SWIPE_THRESHOLD) { + showTopbar(); + + // reset + pointerStartY.current = null; + pointerId.current = null; + } + }; + + const onPointerUp = (e: PointerEvent) => { + if(e.pointerId === pointerId.current) { + pointerId.current = null; + pointerStartY.current = null; + } + }; + + window.addEventListener('pointerdown', onPointerDown, {passive: true }); + window.addEventListener('pointermove', onPointerMove, {passive: true }); + window.addEventListener('pointerup', onPointerUp); + window.addEventListener('pointercancel', onPointerUp); + + return () => { + window.removeEventListener('pointerdown', onPointerDown); + window.removeEventListener('pointermove', onPointerMove); + window.removeEventListener('pointerup', onPointerUp); + window.removeEventListener('pointercancel', onPointerUp); + }; + }, [showTopbar]); + + React.useEffect(() => () => {if (hideRef.current) window.clearTimeout(hideRef.current); }, []); + const theme = useTheme(); // const isMobile = useMediaQuery(theme.breakpoints.down('sm')); @@ -54,7 +129,7 @@ export default function AppNavBar(props) { return ( - + @@ -89,6 +164,15 @@ export default function AppNavBar(props) { apiDisplayName={apiDisplayName} listening={controlStatus.listening} menuVisible={displayStatus.menuVisible} + + topbarLocked={topbarLocked} + onTopBarToggle= {(v) => { + setTopbarLocked(v); + if(v) { + setTopbarVisible(true); + if (hideRef.current) {window.clearTimeout(hideRef.current); hideRef.current=null; } + } + }} /> diff --git a/src/components/navbars/topbar/apiDropdown.tsx b/src/components/navbars/topbar/apiDropdown.tsx index 8d76c2b..e36b9c4 100644 --- a/src/components/navbars/topbar/apiDropdown.tsx +++ b/src/components/navbars/topbar/apiDropdown.tsx @@ -107,11 +107,11 @@ export default function ApiDropdown(props) { {isWhisperActive ? : null} {props.apiDisplayName} - - - {open ? : } - - + + + {open ? : } + + {props.listening ? - : - + : + } diff --git a/src/components/navbars/topbar/lock.tsx b/src/components/navbars/topbar/lock.tsx new file mode 100644 index 0000000..d99391f --- /dev/null +++ b/src/components/navbars/topbar/lock.tsx @@ -0,0 +1,37 @@ +import { LockIcon, LockOpenIcon, ThemeProvider, IconButton, Tooltip } from "../../../muiImports" +import * as React from 'react'; +import Theme from '../../theme' + +interface Props { + locked?: boolean; + onToggle?: (locked: boolean) => void; +} + +export default function LockToggle(props: Props) { + const {locked: lockedProp, onToggle} = props; + const [locked, setLocked] = React.useState(!!lockedProp); + + React.useEffect(()=>{ + if(typeof lockedProp === 'boolean') setLocked(lockedProp); + }, [lockedProp]); + + const handleClick = () => { + const next = !locked; + setLocked(next); + onToggle?.(next); + }; + const {myTheme} = Theme() + // color "primary" comes from Theme() + return ( +
+ + + + {locked ? : } + + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/navbars/topbar/topBar.tsx b/src/components/navbars/topbar/topBar.tsx index de3a20f..386cc19 100644 --- a/src/components/navbars/topbar/topBar.tsx +++ b/src/components/navbars/topbar/topBar.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; -import { Grid, Box } from '../../../muiImports'; +import { /*Grid,*/ Box } from '../../../muiImports'; import ApiDropdown from './apiDropdown'; import Fullscreen from './fullScreen'; import Listening from './listening'; import QRCodeScreen from './qrCodeScreen'; -import MenuHider from './menuHider'; +import LockToggle from './lock'; +// import MenuHider from './menuHider'; import TranscriptDownload from './transcriptDownload'; import { @@ -20,6 +21,8 @@ import { const iconSize = isMobile ? "small" : "medium"; + const {topbarLocked, onTopBarToggle } = props; + return ( } {/* {!isMobile && } */} + {} {} { } {!isMobile && }