Navigation3 (#860)

This commit is contained in:
Carlos Valente
2024-03-31 21:18:24 +02:00
committed by GitHub
parent 6bb3fb2931
commit 9a03e4850a
24 changed files with 290 additions and 210 deletions
+1 -1
View File
@@ -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'));
@@ -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 (
<div className={`${style.buttonContainer} ${!showButton ? style.hidden : ''}`}>
<button
onClick={toggleMenu}
aria-label='toggle menu'
className={style.navButton}
data-testid='navigation__toggle-menu'
>
<IoApps />
</button>
<button
className={style.button}
onClick={toggleSettings}
aria-label='toggle settings'
data-testid='navigation__toggle-settings'
>
<IoSettingsOutline />
</button>
</div>
);
}
@@ -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<NavigationMenuProps>) {
const { children, editCallback } = props;
const { children, isOpen, onClose } = props;
const [showButton, setShowButton] = useState(false);
const { isOpen, onOpen, onClose } = useDisclosure();
const menuRef = useRef<HTMLDivElement | null>(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(
<div id='navigation-menu-portal' ref={menuRef}>
<div className={`${style.buttonContainer} ${!showButton && !isOpen ? style.hidden : ''}`}>
<button
onClick={toggleMenu}
aria-label='toggle menu'
className={style.navButton}
data-testid='navigation__toggle-menu'
>
<IoApps />
</button>
<button
className={style.button}
onClick={editCallback}
aria-label='toggle settings'
data-testid='navigation__toggle-settings'
>
<IoSettingsOutline />
</button>
<Drawer placement='left' onClose={onClose} isOpen={isOpen} variant='ontime' data-testid='navigation__menu'>
<DrawerOverlay />
<DrawerContent>
<DrawerHeader>
<DrawerCloseButton size='lg' />
Ontime
</DrawerHeader>
<DrawerBody padding={0}>{children}</DrawerBody>
</DrawerContent>
</Drawer>
</div>
<Drawer placement='left' onClose={onClose} isOpen={isOpen} variant='ontime' data-testid='navigation__menu'>
<DrawerOverlay />
<DrawerContent>
<DrawerHeader>
<DrawerCloseButton size='lg' />
Ontime
</DrawerHeader>
<DrawerBody padding={0}>{children}</DrawerBody>
</DrawerContent>
</Drawer>
</div>,
document.body,
);
@@ -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 (
<NavigationMenu editCallback={handleSettings}>
<NavigationMenu isOpen={isMenuOpen} onClose={onMenuClose}>
<RenameClientModal isOpen={isOpen} onClose={onClose} />
<div className={style.buttonsContainer}>
<div
@@ -12,6 +12,7 @@ import { useViewOptionsStore } from '../../stores/viewOptions';
import { isKeyEnter } from '../../utils/keyEvent';
import RenameClientModal from './rename-client-modal/RenameClientModal';
import FloatingNavigation from './FloatingNavigation';
import NavigationMenu from './NavigationMenu';
import style from './NavigationMenu.module.scss';
@@ -21,66 +22,72 @@ function ViewNavigationMenu() {
const { fullscreen, toggle } = useFullscreen();
const { toggleMirror } = useViewOptionsStore();
const [searchParams, setSearchParams] = useSearchParams();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen: isRenameOpen, onOpen: onRenameOpen, onClose: onRenameClose } = useDisclosure();
const { isOpen: isMenuOpen, onOpen: onMenuOpen, onClose: onMenuClose } = useDisclosure();
const showEditFormDrawer = useCallback(() => {
searchParams.set('edit', 'true');
setSearchParams(searchParams);
}, [searchParams, setSearchParams]);
const toggleMenu = () => (isMenuOpen ? onMenuClose() : onMenuOpen());
return (
<NavigationMenu editCallback={showEditFormDrawer}>
<RenameClientModal isOpen={isOpen} onClose={onClose} />
<div className={style.buttonsContainer}>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={toggle}
onKeyDown={(event) => {
isKeyEnter(event) && toggle();
}}
>
Toggle Fullscreen
{fullscreen ? <IoContract /> : <IoExpand />}
<>
<FloatingNavigation toggleMenu={toggleMenu} toggleSettings={showEditFormDrawer} />
<NavigationMenu isOpen={isMenuOpen} onClose={onMenuClose}>
<RenameClientModal isOpen={isRenameOpen} onClose={onRenameClose} />
<div className={style.buttonsContainer}>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={toggle}
onKeyDown={(event) => {
isKeyEnter(event) && toggle();
}}
>
Toggle Fullscreen
{fullscreen ? <IoContract /> : <IoExpand />}
</div>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={() => toggleMirror()}
onKeyDown={(event) => {
isKeyEnter(event) && toggleMirror();
}}
>
Flip Screen
<IoSwapVertical />
</div>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={onRenameOpen}
onKeyDown={(event) => {
isKeyEnter(event) && onRenameOpen();
}}
>
Rename Client
</div>
</div>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={() => toggleMirror()}
onKeyDown={(event) => {
isKeyEnter(event) && toggleMirror();
}}
>
Flip Screen
<IoSwapVertical />
</div>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={onOpen}
onKeyDown={(event) => {
isKeyEnter(event) && onOpen();
}}
>
Rename Client
</div>
</div>
<hr className={style.separator} />
{navigatorConstants.map((route) => (
<Link
key={route.url}
to={route.url}
className={`${style.link} ${route.url === location.pathname ? style.current : undefined}`}
tabIndex={0}
>
{route.label}
<IoArrowUp className={style.linkIcon} />
</Link>
))}
</NavigationMenu>
<hr className={style.separator} />
{navigatorConstants.map((route) => (
<Link
key={route.url}
to={route.url}
className={`${style.link} ${route.url === location.pathname ? style.current : undefined}`}
tabIndex={0}
>
{route.label}
<IoArrowUp className={style.linkIcon} />
</Link>
))}
</NavigationMenu>
</>
);
}
@@ -0,0 +1,11 @@
import { useEffect } from 'react';
/**
* Sets tab title
* @param title
*/
export function useWindowTitle(title: string) {
useEffect(() => {
document.title = `ontime - ${title}`;
}, []);
}
@@ -1,7 +1,7 @@
.tableWrapper {
width: 100%;
height: 100vh;
padding: 1rem;
padding: 1rem 0.5rem;
display: grid;
grid-template-rows: 3rem auto 1fr;
@@ -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 (
<div className={styles.tableWrapper} data-testid='cuesheet'>
<Overview />
<ProductionNavigationMenu isMenuOpen={isMenuOpen} onMenuClose={onClose} />
<Overview>
<IconButton
aria-label='Toggle settings'
variant='ontime-subtle-white'
size='lg'
icon={<IoApps />}
onClick={onOpen}
/>
<IconButton
aria-label='Toggle navigation'
variant='ontime-subtle-white'
size='lg'
icon={<IoSettingsOutline />}
onClick={() => toggleSettings()}
/>
</Overview>
<CuesheetProgress />
<ProductionNavigationMenu handleSettings={() => toggleSettings()} />
<Cuesheet
data={flatRundown}
columns={columns}
+30 -13
View File
@@ -1,7 +1,11 @@
import { lazy, useCallback, useEffect } 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 ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
import useElectronEvent from '../../common/hooks/useElectronEvent';
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import AppSettings from '../app-settings/AppSettings';
import useAppSettingsNavigation from '../app-settings/useAppSettingsNavigation';
import Overview from '../overview/Overview';
@@ -13,16 +17,17 @@ const TimerControl = lazy(() => 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 (
<div className={styles.mainContainer} data-testid='event-editor'>
<ProductionNavigationMenu handleSettings={handleSettings} />
{isOpen ? (
<ProductionNavigationMenu isMenuOpen={isMenuOpen} onMenuClose={onClose} />
<Overview>
<IconButton
aria-label='Toggle navigation'
variant='ontime-subtle-white'
size='lg'
icon={<IoApps />}
onClick={onOpen}
/>
<IconButton
aria-label='Toggle settings'
variant='ontime-subtle-white'
size='lg'
icon={<IoSettingsOutline />}
onClick={toggleSettings}
/>
</Overview>
{isSettingsOpen ? (
<AppSettings />
) : (
<div id='panels' className={styles.panelContainer}>
@@ -74,7 +92,6 @@ export default function Editor() {
<Rundown />
</div>
)}
<Overview />
</div>
);
}
+3 -12
View File
@@ -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<NodeJS.Timeout | null>(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 (
<div className={style.operatorContainer}>
<ProductionNavigationMenu handleSettings={showEditFormDrawer} />
<ViewParamsEditor paramFields={operatorOptions} />
{editEvent && <EditModal event={editEvent} onClose={() => setEditEvent(null)} />}
@@ -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 (
<>
<FloatingNavigation toggleMenu={toggleMenu} toggleSettings={showEditFormDrawer} />
<ProductionNavigationMenu isMenuOpen={isOpen} onMenuClose={onClose} />
<Operator />
</>
);
}
@@ -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 {
+19 -11
View File
@@ -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 (
<div className={style.overview}>
<ErrorBoundary>
<TitlesOverview />
<div className={style.column}>
<TimeRow label='Planned start' value={formatedTime(plannedStart)} className={style.start} />
<TimeRow label='Actual start' value={formatedTime(actualStart)} className={style.start} />
</div>
<RuntimeOverview />
<div className={style.column}>
<TimeRow label='Planned end' value={plannedEndText} className={style.end} daySpan={maybePlannedDaySpan} />
<TimeRow label='Expected end' value={expectedEndText} className={style.end} daySpan={maybeExpectedDaySpan} />
<div className={style.nav}>{children}</div>
<div className={style.info}>
<TitlesOverview />
<div>
<TimeRow label='Planned start' value={formatedTime(plannedStart)} className={style.start} />
<TimeRow label='Actual start' value={formatedTime(actualStart)} className={style.start} />
</div>
<RuntimeOverview />
<div>
<TimeRow label='Planned end' value={plannedEndText} className={style.end} daySpan={maybePlannedDaySpan} />
<TimeRow
label='Expected end'
value={expectedEndText}
className={style.end}
daySpan={maybeExpectedDaySpan}
/>
</div>
</div>
</ErrorBoundary>
</div>
@@ -43,7 +51,7 @@ function TitlesOverview() {
const { data } = useProjectData();
return (
<div className={style.titles}>
<div>
<div className={style.title}>{data.title}</div>
<div className={style.description}>{data.description}</div>
</div>
@@ -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(() => {
@@ -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) {
@@ -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>(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
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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 = '';
@@ -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) {
+1 -1
View File
@@ -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();
+4 -3
View File
@@ -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();
});
@@ -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