diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx index e43683833..2f34d2acf 100644 --- a/apps/client/src/AppRouter.tsx +++ b/apps/client/src/AppRouter.tsx @@ -7,7 +7,7 @@ import withData from './features/viewers/ViewWrapper'; const Editor = lazy(() => import('./features/editors/ProtectedEditor')); const Cuesheet = lazy(() => import('./features/cuesheet/ProtectedCuesheet')); -const Operator = lazy(() => import('./features/operator/Operator')); +const Operator = lazy(() => import('./features/operator/OperatorExport')); const TimerView = lazy(() => import('./features/viewers/timer/Timer')); const MinimalTimerView = lazy(() => import('./features/viewers/minimal-timer/MinimalTimer')); diff --git a/apps/client/src/common/components/navigation-menu/FloatingNavigation.tsx b/apps/client/src/common/components/navigation-menu/FloatingNavigation.tsx new file mode 100644 index 000000000..a4b692a9c --- /dev/null +++ b/apps/client/src/common/components/navigation-menu/FloatingNavigation.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import { IoApps } from '@react-icons/all-files/io5/IoApps'; +import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; + +import { debounce } from '../../utils/debounce'; + +import style from './NavigationMenu.module.scss'; + +interface FloatingNavigationProps { + toggleMenu: () => void; + toggleSettings: () => void; +} + +export default function FloatingNavigation(props: FloatingNavigationProps) { + const { toggleMenu, toggleSettings } = props; + const [showButton, setShowButton] = useState(false); + + // show on mouse move + useEffect(() => { + let fadeOut: NodeJS.Timeout | null = null; + const setShowMenuTrue = () => { + setShowButton(true); + if (fadeOut) { + clearTimeout(fadeOut); + } + fadeOut = setTimeout(() => setShowButton(false), 3000); + }; + + const debouncedShowMenu = debounce(setShowMenuTrue, 1000); + + document.addEventListener('mousemove', debouncedShowMenu); + return () => { + document.removeEventListener('mousemove', debouncedShowMenu); + if (fadeOut) { + clearTimeout(fadeOut); + } + }; + }, []); + + return ( +
+ + +
+ ); +} diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx index d4660ec58..7a5e7aba9 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx @@ -1,89 +1,33 @@ -import { memo, PropsWithChildren, useEffect, useRef, useState } from 'react'; +import { memo, PropsWithChildren, useRef } from 'react'; import { createPortal } from 'react-dom'; -import { - Drawer, - DrawerBody, - DrawerCloseButton, - DrawerContent, - DrawerHeader, - DrawerOverlay, - useDisclosure, -} from '@chakra-ui/react'; -import { IoApps } from '@react-icons/all-files/io5/IoApps'; -import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; +import { Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerHeader, DrawerOverlay } from '@chakra-ui/react'; import useClickOutside from '../../hooks/useClickOutside'; -import { debounce } from '../../utils/debounce'; - -import style from './NavigationMenu.module.scss'; interface NavigationMenuProps { - editCallback: () => void; + isOpen: boolean; + onClose: () => void; } function NavigationMenu(props: PropsWithChildren) { - const { children, editCallback } = props; + const { children, isOpen, onClose } = props; - const [showButton, setShowButton] = useState(false); - const { isOpen, onOpen, onClose } = useDisclosure(); const menuRef = useRef(null); useClickOutside(menuRef, () => onClose); - const toggleMenu = () => (isOpen ? onClose() : onOpen()); - - // show on mouse move - useEffect(() => { - let fadeOut: NodeJS.Timeout | null = null; - const setShowMenuTrue = () => { - setShowButton(true); - if (fadeOut) { - clearTimeout(fadeOut); - } - fadeOut = setTimeout(() => setShowButton(false), 3000); - }; - - const debouncedShowMenu = debounce(setShowMenuTrue, 1000); - - document.addEventListener('mousemove', debouncedShowMenu); - return () => { - document.removeEventListener('mousemove', debouncedShowMenu); - if (fadeOut) { - clearTimeout(fadeOut); - } - }; - }, []); - return createPortal( , document.body, ); diff --git a/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx index 12ee12216..60ad24587 100644 --- a/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx @@ -15,16 +15,18 @@ import NavigationMenu from './NavigationMenu'; import style from './NavigationMenu.module.scss'; interface ProductionNavigationMenuProps { - handleSettings: () => void; + isMenuOpen: boolean; + onMenuClose: () => void; } -function ProductionNavigationMenu({ handleSettings }: ProductionNavigationMenuProps) { +function ProductionNavigationMenu(props: ProductionNavigationMenuProps) { + const { isMenuOpen, onMenuClose } = props; const location = useLocation(); const { fullscreen, toggle } = useFullscreen(); const { isOpen, onOpen, onClose } = useDisclosure(); return ( - +
{ searchParams.set('edit', 'true'); setSearchParams(searchParams); }, [searchParams, setSearchParams]); + const toggleMenu = () => (isMenuOpen ? onMenuClose() : onMenuOpen()); + return ( - - -
-
{ - isKeyEnter(event) && toggle(); - }} - > - Toggle Fullscreen - {fullscreen ? : } + <> + + + +
+
{ + isKeyEnter(event) && toggle(); + }} + > + Toggle Fullscreen + {fullscreen ? : } +
+
toggleMirror()} + onKeyDown={(event) => { + isKeyEnter(event) && toggleMirror(); + }} + > + Flip Screen + +
+
{ + isKeyEnter(event) && onRenameOpen(); + }} + > + Rename Client +
-
toggleMirror()} - onKeyDown={(event) => { - isKeyEnter(event) && toggleMirror(); - }} - > - Flip Screen - -
-
{ - isKeyEnter(event) && onOpen(); - }} - > - Rename Client -
-
-
- {navigatorConstants.map((route) => ( - - {route.label} - - - ))} - +
+ {navigatorConstants.map((route) => ( + + {route.label} + + + ))} + + ); } diff --git a/apps/client/src/common/hooks/useWindowTitle.ts b/apps/client/src/common/hooks/useWindowTitle.ts new file mode 100644 index 000000000..78603d38f --- /dev/null +++ b/apps/client/src/common/hooks/useWindowTitle.ts @@ -0,0 +1,11 @@ +import { useEffect } from 'react'; + +/** + * Sets tab title + * @param title + */ +export function useWindowTitle(title: string) { + useEffect(() => { + document.title = `ontime - ${title}`; + }, []); +} diff --git a/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss b/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss index eb82af77e..61f9a47fe 100644 --- a/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss +++ b/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss @@ -1,7 +1,7 @@ .tableWrapper { width: 100%; height: 100vh; - padding: 1rem; + padding: 1rem 0.5rem; display: grid; grid-template-rows: 3rem auto 1fr; diff --git a/apps/client/src/features/cuesheet/CuesheetWrapper.tsx b/apps/client/src/features/cuesheet/CuesheetWrapper.tsx index 9ab91dd6d..8ab2593e5 100644 --- a/apps/client/src/features/cuesheet/CuesheetWrapper.tsx +++ b/apps/client/src/features/cuesheet/CuesheetWrapper.tsx @@ -1,10 +1,14 @@ -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; +import { IconButton, useDisclosure } from '@chakra-ui/react'; +import { IoApps } from '@react-icons/all-files/io5/IoApps'; +import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; import { CustomFieldLabel, isOntimeEvent } from 'ontime-types'; import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu'; import Empty from '../../common/components/state/Empty'; import { useEventAction } from '../../common/hooks/useEventAction'; import { useCuesheet } from '../../common/hooks/useSocket'; +import { useWindowTitle } from '../../common/hooks/useWindowTitle'; import useCustomFields from '../../common/hooks-query/useCustomFields'; import { useFlatRundown } from '../../common/hooks-query/useRundown'; import Overview from '../overview/Overview'; @@ -20,16 +24,14 @@ export default function CuesheetWrapper() { // TODO: can we use the normalised rundown for the table? const { data: flatRundown, status: rundownStatus } = useFlatRundown(); const { data: customFields } = useCustomFields(); + const { isOpen: isMenuOpen, onOpen, onClose } = useDisclosure(); const { updateCustomField } = useEventAction(); const featureData = useCuesheet(); const columns = useMemo(() => makeCuesheetColumns(customFields), [customFields]); const toggleSettings = useCuesheetSettings((state) => state.toggleSettings); - // Set window title - useEffect(() => { - document.title = 'ontime - Cuesheet'; - }, []); + useWindowTitle('Cuesheet'); /** * Handles updating a field @@ -82,9 +84,24 @@ export default function CuesheetWrapper() { return (
- + + + } + onClick={onOpen} + /> + } + onClick={() => toggleSettings()} + /> + - toggleSettings()} /> import('../control/playback/TimerControlExport') const MessageControl = lazy(() => import('../control/message/MessageControlExport')); export default function Editor() { - const { isOpen, setLocation, close } = useAppSettingsNavigation(); + const { isOpen: isSettingsOpen, setLocation, close } = useAppSettingsNavigation(); const { isElectron } = useElectronEvent(); + const { isOpen: isMenuOpen, onOpen, onClose } = useDisclosure(); - const handleSettings = useCallback(() => { - if (isOpen) { + const toggleSettings = useCallback(() => { + if (isSettingsOpen) { close(); } else { setLocation('project'); } - }, [close, isOpen, setLocation]); + }, [close, isSettingsOpen, setLocation]); // Handle keyboard shortcuts const handleKeyPress = useCallback( @@ -34,13 +39,13 @@ export default function Editor() { if (event.ctrlKey || event.metaKey) { // ctrl + , (settings) if (event.key === ',') { - handleSettings(); + toggleSettings(); event.preventDefault(); event.stopPropagation(); } } }, - [handleSettings], + [toggleSettings], ); // register ctrl + , to open settings @@ -55,15 +60,28 @@ export default function Editor() { }; }, [handleKeyPress, isElectron]); - // Set window title - useEffect(() => { - document.title = 'ontime - Editor'; - }, []); + useWindowTitle('Editor'); return (
- - {isOpen ? ( + + + } + onClick={onOpen} + /> + } + onClick={toggleSettings} + /> + + {isSettingsOpen ? ( ) : (
@@ -74,7 +92,6 @@ export default function Editor() {
)} -
); } diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx index 7e8aa855d..10eb56f6c 100644 --- a/apps/client/src/features/operator/Operator.tsx +++ b/apps/client/src/features/operator/Operator.tsx @@ -3,12 +3,12 @@ import { useSearchParams } from 'react-router-dom'; import { CustomField, CustomFields, isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types'; import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils'; -import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu'; import Empty from '../../common/components/state/Empty'; import { getOperatorOptions } from '../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor'; import useFollowComponent from '../../common/hooks/useFollowComponent'; import { useOperator } from '../../common/hooks/useSocket'; +import { useWindowTitle } from '../../common/hooks/useWindowTitle'; import useCustomFields from '../../common/hooks-query/useCustomFields'; import useProjectData from '../../common/hooks-query/useProjectData'; import useRundown from '../../common/hooks-query/useRundown'; @@ -41,7 +41,7 @@ export default function Operator() { const timeoutId = useRef(null); const featureData = useOperator(); - const [searchParams, setSearchParams] = useSearchParams(); + const [searchParams] = useSearchParams(); const { data: settings } = useSettings(); const [showEditPrompt, setShowEditPrompt] = useState(false); @@ -57,10 +57,7 @@ export default function Operator() { topOffset: selectedOffset, }); - // Set window title - useEffect(() => { - document.title = 'ontime - Operator'; - }, []); + useWindowTitle('Operator'); // reset scroll if nothing is selected useEffect(() => { @@ -116,11 +113,6 @@ export default function Operator() { [searchParams], ); - const showEditFormDrawer = useCallback(() => { - searchParams.set('edit', 'true'); - setSearchParams(searchParams); - }, [searchParams, setSearchParams]); - const missingData = !data || !customFields || !projectData; const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending'; @@ -146,7 +138,6 @@ export default function Operator() { return (
- {editEvent && setEditEvent(null)} />} diff --git a/apps/client/src/features/operator/OperatorExport.tsx b/apps/client/src/features/operator/OperatorExport.tsx new file mode 100644 index 000000000..fe5e8389b --- /dev/null +++ b/apps/client/src/features/operator/OperatorExport.tsx @@ -0,0 +1,28 @@ +import { useCallback } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { useDisclosure } from '@chakra-ui/react'; + +import FloatingNavigation from '../../common/components/navigation-menu/FloatingNavigation'; +import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu'; + +import Operator from './Operator'; + +export default function OperatorExport() { + const [searchParams, setSearchParams] = useSearchParams(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const showEditFormDrawer = useCallback(() => { + searchParams.set('edit', 'true'); + setSearchParams(searchParams); + }, [searchParams, setSearchParams]); + + const toggleMenu = isOpen ? onClose : onOpen; + + return ( + <> + + + + + ); +} diff --git a/apps/client/src/features/overview/Overview.module.scss b/apps/client/src/features/overview/Overview.module.scss index 57cbd04c7..4aa87687e 100644 --- a/apps/client/src/features/overview/Overview.module.scss +++ b/apps/client/src/features/overview/Overview.module.scss @@ -1,10 +1,20 @@ .overview { grid-area: overview; + font-size: $inner-section-text-size; + display: flex; +} + +.nav { + display: flex; + gap: 0.5rem; +} + +.info { + flex: 1; + padding-inline: 1rem; display: flex; align-items: center; justify-content: space-between; - font-size: $inner-section-text-size; - padding: 0 1rem; } .title { diff --git a/apps/client/src/features/overview/Overview.tsx b/apps/client/src/features/overview/Overview.tsx index c2f6ec320..d2b138ec0 100644 --- a/apps/client/src/features/overview/Overview.tsx +++ b/apps/client/src/features/overview/Overview.tsx @@ -12,7 +12,7 @@ import style from './Overview.module.scss'; export default memo(Overview); -function Overview() { +function Overview({ children }: { children: React.ReactNode }) { const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview(); const [maybePlannedEnd, maybePlannedDaySpan] = useMemo(() => calculateEndAndDaySpan(plannedEnd), [plannedEnd]); @@ -24,15 +24,23 @@ function Overview() { return (
- -
- - -
- -
- - +
{children}
+
+ +
+ + +
+ +
+ + +
@@ -43,7 +51,7 @@ function TitlesOverview() { const { data } = useProjectData(); return ( -
+
{data.title}
{data.description}
diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 3be11f2d8..3f0e7026d 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -14,6 +14,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard'; import { getBackstageOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; @@ -57,10 +58,7 @@ export default function Backstage(props: BackstageProps) { const [blinkClass, setBlinkClass] = useState(false); const [searchParams] = useSearchParams(); - // Set window title - useEffect(() => { - document.title = 'ontime - Backstage Screen'; - }, []); + useWindowTitle('Backstage'); // blink on change useEffect(() => { diff --git a/apps/client/src/features/viewers/clock/Clock.tsx b/apps/client/src/features/viewers/clock/Clock.tsx index 35168f8e5..ec6c7ccbe 100644 --- a/apps/client/src/features/viewers/clock/Clock.tsx +++ b/apps/client/src/features/viewers/clock/Clock.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Settings, ViewSettings } from 'ontime-types'; @@ -6,6 +5,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { getClockOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { OverridableOptions } from '../../../common/models/View.types'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; @@ -25,9 +25,7 @@ export default function Clock(props: ClockProps) { const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Clock'; - }, []); + useWindowTitle('Clock'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index 0b7f42d29..3713b5903 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -7,6 +7,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { getCountdownOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; @@ -37,9 +38,7 @@ export default function Countdown(props: CountdownProps) { const [runningMessage, setRunningMessage] = useState(TimerMessage.unhandled); const [delay, setDelay] = useState(0); - useEffect(() => { - document.title = 'ontime - Countdown'; - }, []); + useWindowTitle('Countdown'); // eg. http://localhost:4001/countdown?eventId=ei0us // Check for user options diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx index 0c8f46f52..5c6b3c348 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx @@ -6,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { getPropertyValue } from '../common/viewUtils'; import './LowerThird.scss'; @@ -141,10 +142,7 @@ export default function LowerThird(props: LowerProps) { const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre'); useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); - // set window title - useEffect(() => { - document.title = 'ontime - Lower Third'; - }, []); + useWindowTitle('Lower Third'); const trigger = useMemo(() => { if (options.trigger === TriggerType.Event) { diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index d0986e301..663bbeb05 100644 --- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types'; import { MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils'; @@ -7,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { OverridableOptions } from '../../../common/models/View.types'; import { timerPlaceholder } from '../../../common/utils/styleUtils'; @@ -28,9 +28,7 @@ export default function MinimalTimer(props: MinimalTimerProps) { const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Minimal Timer'; - }, []); + useWindowTitle('Minimal Timer'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index 52895fd71..d217faac9 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import QRCode from 'react-qr-code'; import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; @@ -12,6 +11,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard'; import { getPublicOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; @@ -54,10 +54,7 @@ export default function Public(props: BackstageProps) { const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); - // set window title - useEffect(() => { - document.title = 'ontime - Public Screen'; - }, []); + useWindowTitle('Public Schedule'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index b74e28016..e48f985df 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types'; import { isOntimeEvent, Playback } from 'ontime-types'; @@ -9,6 +8,7 @@ import { getStudioClockOptions } from '../../../common/components/view-params-ed import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import useFitText from '../../../common/hooks/useFitText'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; @@ -46,9 +46,7 @@ export default function StudioClock(props: StudioClockProps) { const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Studio Clock'; - }, []); + useWindowTitle('Studio Clock'); let clock = formatTime(time.clock); let hasAmPm = ''; diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index 333606df6..2141877ad 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { @@ -19,6 +18,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard'; import { getTimerOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { timerPlaceholder } from '../../../common/utils/styleUtils'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; @@ -63,9 +63,7 @@ export default function Timer(props: TimerProps) { const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Timer'; - }, []); + useWindowTitle('Timer'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/e2e/tests/000-upload-showfile.spec.ts b/e2e/tests/000-upload-showfile.spec.ts index e87815b38..d642f27b8 100644 --- a/e2e/tests/000-upload-showfile.spec.ts +++ b/e2e/tests/000-upload-showfile.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; const fileToUpload = 'e2e/tests/fixtures/test-db.json'; -test('test project file upload', async ({ page }) => { +test('project file upload', async ({ page }) => { await page.goto('http://localhost:4001/editor'); await page.getByRole('button', { name: 'Rundown menu' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click(); diff --git a/e2e/tests/features/206-url-preset.spec.ts b/e2e/tests/features/206-url-preset.spec.ts index 750070d30..1c7451fce 100644 --- a/e2e/tests/features/206-url-preset.spec.ts +++ b/e2e/tests/features/206-url-preset.spec.ts @@ -1,10 +1,10 @@ -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; -test('test URL preset feature, it should redirect to given URL', async ({ page }) => { +test('URL preset feature, it should redirect to given URL', async ({ page }) => { await page.goto('http://localhost:4001/editor'); // open settings - await page.getByTestId('navigation__toggle-settings').click(); + await page.getByRole('button', { name: 'Toggle settings' }).click(); await page.getByRole('button', { name: 'General' }).click(); // create preset @@ -26,4 +26,5 @@ test('test URL preset feature, it should redirect to given URL', async ({ page } // make sure preset works await page.goto('http://localhost:4001/testing'); await page.getByTestId('countdown__select').click(); + await expect(page.getByTestId('countdown__select')).toBeVisible(); }); diff --git a/e2e/tests/features/301-spreadsheet-import.spec.ts b/e2e/tests/features/301-spreadsheet-import.spec.ts index 9dc70fdd7..5f56fc125 100644 --- a/e2e/tests/features/301-spreadsheet-import.spec.ts +++ b/e2e/tests/features/301-spreadsheet-import.spec.ts @@ -7,7 +7,7 @@ test('sheet file upload', async ({ page }) => { await page.getByRole('button', { name: 'Rundown menu' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click(); - await page.getByTestId('navigation__toggle-settings').click(); + await page.getByRole('button', { name: 'Toggle settings' }).click(); await page.getByRole('button', { name: 'Import spreadsheet' }).click(); // workaround to upload file on hidden input