mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 22:24:11 +00:00
Navigation3 (#860)
This commit is contained in:
@@ -7,7 +7,7 @@ import withData from './features/viewers/ViewWrapper';
|
|||||||
|
|
||||||
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
||||||
const Cuesheet = lazy(() => import('./features/cuesheet/ProtectedCuesheet'));
|
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 TimerView = lazy(() => import('./features/viewers/timer/Timer'));
|
||||||
const MinimalTimerView = lazy(() => import('./features/viewers/minimal-timer/MinimalTimer'));
|
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 { createPortal } from 'react-dom';
|
||||||
import {
|
import { Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerHeader, DrawerOverlay } from '@chakra-ui/react';
|
||||||
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 useClickOutside from '../../hooks/useClickOutside';
|
import useClickOutside from '../../hooks/useClickOutside';
|
||||||
import { debounce } from '../../utils/debounce';
|
|
||||||
|
|
||||||
import style from './NavigationMenu.module.scss';
|
|
||||||
|
|
||||||
interface NavigationMenuProps {
|
interface NavigationMenuProps {
|
||||||
editCallback: () => void;
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenu(props: PropsWithChildren<NavigationMenuProps>) {
|
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);
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useClickOutside(menuRef, () => onClose);
|
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(
|
return createPortal(
|
||||||
<div id='navigation-menu-portal' ref={menuRef}>
|
<div id='navigation-menu-portal' ref={menuRef}>
|
||||||
<div className={`${style.buttonContainer} ${!showButton && !isOpen ? style.hidden : ''}`}>
|
<Drawer placement='left' onClose={onClose} isOpen={isOpen} variant='ontime' data-testid='navigation__menu'>
|
||||||
<button
|
<DrawerOverlay />
|
||||||
onClick={toggleMenu}
|
<DrawerContent>
|
||||||
aria-label='toggle menu'
|
<DrawerHeader>
|
||||||
className={style.navButton}
|
<DrawerCloseButton size='lg' />
|
||||||
data-testid='navigation__toggle-menu'
|
Ontime
|
||||||
>
|
</DrawerHeader>
|
||||||
<IoApps />
|
<DrawerBody padding={0}>{children}</DrawerBody>
|
||||||
</button>
|
</DrawerContent>
|
||||||
<button
|
</Drawer>
|
||||||
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>
|
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,16 +15,18 @@ import NavigationMenu from './NavigationMenu';
|
|||||||
import style from './NavigationMenu.module.scss';
|
import style from './NavigationMenu.module.scss';
|
||||||
|
|
||||||
interface ProductionNavigationMenuProps {
|
interface ProductionNavigationMenuProps {
|
||||||
handleSettings: () => void;
|
isMenuOpen: boolean;
|
||||||
|
onMenuClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProductionNavigationMenu({ handleSettings }: ProductionNavigationMenuProps) {
|
function ProductionNavigationMenu(props: ProductionNavigationMenuProps) {
|
||||||
|
const { isMenuOpen, onMenuClose } = props;
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { fullscreen, toggle } = useFullscreen();
|
const { fullscreen, toggle } = useFullscreen();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationMenu editCallback={handleSettings}>
|
<NavigationMenu isOpen={isMenuOpen} onClose={onMenuClose}>
|
||||||
<RenameClientModal isOpen={isOpen} onClose={onClose} />
|
<RenameClientModal isOpen={isOpen} onClose={onClose} />
|
||||||
<div className={style.buttonsContainer}>
|
<div className={style.buttonsContainer}>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useViewOptionsStore } from '../../stores/viewOptions';
|
|||||||
import { isKeyEnter } from '../../utils/keyEvent';
|
import { isKeyEnter } from '../../utils/keyEvent';
|
||||||
|
|
||||||
import RenameClientModal from './rename-client-modal/RenameClientModal';
|
import RenameClientModal from './rename-client-modal/RenameClientModal';
|
||||||
|
import FloatingNavigation from './FloatingNavigation';
|
||||||
import NavigationMenu from './NavigationMenu';
|
import NavigationMenu from './NavigationMenu';
|
||||||
|
|
||||||
import style from './NavigationMenu.module.scss';
|
import style from './NavigationMenu.module.scss';
|
||||||
@@ -21,66 +22,72 @@ function ViewNavigationMenu() {
|
|||||||
const { fullscreen, toggle } = useFullscreen();
|
const { fullscreen, toggle } = useFullscreen();
|
||||||
const { toggleMirror } = useViewOptionsStore();
|
const { toggleMirror } = useViewOptionsStore();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
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(() => {
|
const showEditFormDrawer = useCallback(() => {
|
||||||
searchParams.set('edit', 'true');
|
searchParams.set('edit', 'true');
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
}, [searchParams, setSearchParams]);
|
}, [searchParams, setSearchParams]);
|
||||||
|
|
||||||
|
const toggleMenu = () => (isMenuOpen ? onMenuClose() : onMenuOpen());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationMenu editCallback={showEditFormDrawer}>
|
<>
|
||||||
<RenameClientModal isOpen={isOpen} onClose={onClose} />
|
<FloatingNavigation toggleMenu={toggleMenu} toggleSettings={showEditFormDrawer} />
|
||||||
<div className={style.buttonsContainer}>
|
<NavigationMenu isOpen={isMenuOpen} onClose={onMenuClose}>
|
||||||
<div
|
<RenameClientModal isOpen={isRenameOpen} onClose={onRenameClose} />
|
||||||
className={style.link}
|
<div className={style.buttonsContainer}>
|
||||||
tabIndex={0}
|
<div
|
||||||
role='button'
|
className={style.link}
|
||||||
onClick={toggle}
|
tabIndex={0}
|
||||||
onKeyDown={(event) => {
|
role='button'
|
||||||
isKeyEnter(event) && toggle();
|
onClick={toggle}
|
||||||
}}
|
onKeyDown={(event) => {
|
||||||
>
|
isKeyEnter(event) && toggle();
|
||||||
Toggle Fullscreen
|
}}
|
||||||
{fullscreen ? <IoContract /> : <IoExpand />}
|
>
|
||||||
|
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>
|
||||||
<div
|
<hr className={style.separator} />
|
||||||
className={style.link}
|
{navigatorConstants.map((route) => (
|
||||||
tabIndex={0}
|
<Link
|
||||||
role='button'
|
key={route.url}
|
||||||
onClick={() => toggleMirror()}
|
to={route.url}
|
||||||
onKeyDown={(event) => {
|
className={`${style.link} ${route.url === location.pathname ? style.current : undefined}`}
|
||||||
isKeyEnter(event) && toggleMirror();
|
tabIndex={0}
|
||||||
}}
|
>
|
||||||
>
|
{route.label}
|
||||||
Flip Screen
|
<IoArrowUp className={style.linkIcon} />
|
||||||
<IoSwapVertical />
|
</Link>
|
||||||
</div>
|
))}
|
||||||
<div
|
</NavigationMenu>
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
.tableWrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
padding: 1rem;
|
padding: 1rem 0.5rem;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 3rem auto 1fr;
|
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 { CustomFieldLabel, isOntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
|
import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
|
||||||
import Empty from '../../common/components/state/Empty';
|
import Empty from '../../common/components/state/Empty';
|
||||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
import { useCuesheet } from '../../common/hooks/useSocket';
|
import { useCuesheet } from '../../common/hooks/useSocket';
|
||||||
|
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import { useFlatRundown } from '../../common/hooks-query/useRundown';
|
import { useFlatRundown } from '../../common/hooks-query/useRundown';
|
||||||
import Overview from '../overview/Overview';
|
import Overview from '../overview/Overview';
|
||||||
@@ -20,16 +24,14 @@ export default function CuesheetWrapper() {
|
|||||||
// TODO: can we use the normalised rundown for the table?
|
// TODO: can we use the normalised rundown for the table?
|
||||||
const { data: flatRundown, status: rundownStatus } = useFlatRundown();
|
const { data: flatRundown, status: rundownStatus } = useFlatRundown();
|
||||||
const { data: customFields } = useCustomFields();
|
const { data: customFields } = useCustomFields();
|
||||||
|
const { isOpen: isMenuOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
const { updateCustomField } = useEventAction();
|
const { updateCustomField } = useEventAction();
|
||||||
const featureData = useCuesheet();
|
const featureData = useCuesheet();
|
||||||
const columns = useMemo(() => makeCuesheetColumns(customFields), [customFields]);
|
const columns = useMemo(() => makeCuesheetColumns(customFields), [customFields]);
|
||||||
const toggleSettings = useCuesheetSettings((state) => state.toggleSettings);
|
const toggleSettings = useCuesheetSettings((state) => state.toggleSettings);
|
||||||
|
|
||||||
// Set window title
|
useWindowTitle('Cuesheet');
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'ontime - Cuesheet';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles updating a field
|
* Handles updating a field
|
||||||
@@ -82,9 +84,24 @@ export default function CuesheetWrapper() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.tableWrapper} data-testid='cuesheet'>
|
<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 />
|
<CuesheetProgress />
|
||||||
<ProductionNavigationMenu handleSettings={() => toggleSettings()} />
|
|
||||||
<Cuesheet
|
<Cuesheet
|
||||||
data={flatRundown}
|
data={flatRundown}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { lazy, useCallback, useEffect } from 'react';
|
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 ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
|
||||||
import useElectronEvent from '../../common/hooks/useElectronEvent';
|
import useElectronEvent from '../../common/hooks/useElectronEvent';
|
||||||
|
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||||
import AppSettings from '../app-settings/AppSettings';
|
import AppSettings from '../app-settings/AppSettings';
|
||||||
import useAppSettingsNavigation from '../app-settings/useAppSettingsNavigation';
|
import useAppSettingsNavigation from '../app-settings/useAppSettingsNavigation';
|
||||||
import Overview from '../overview/Overview';
|
import Overview from '../overview/Overview';
|
||||||
@@ -13,16 +17,17 @@ const TimerControl = lazy(() => import('../control/playback/TimerControlExport')
|
|||||||
const MessageControl = lazy(() => import('../control/message/MessageControlExport'));
|
const MessageControl = lazy(() => import('../control/message/MessageControlExport'));
|
||||||
|
|
||||||
export default function Editor() {
|
export default function Editor() {
|
||||||
const { isOpen, setLocation, close } = useAppSettingsNavigation();
|
const { isOpen: isSettingsOpen, setLocation, close } = useAppSettingsNavigation();
|
||||||
const { isElectron } = useElectronEvent();
|
const { isElectron } = useElectronEvent();
|
||||||
|
const { isOpen: isMenuOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
const handleSettings = useCallback(() => {
|
const toggleSettings = useCallback(() => {
|
||||||
if (isOpen) {
|
if (isSettingsOpen) {
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
setLocation('project');
|
setLocation('project');
|
||||||
}
|
}
|
||||||
}, [close, isOpen, setLocation]);
|
}, [close, isSettingsOpen, setLocation]);
|
||||||
|
|
||||||
// Handle keyboard shortcuts
|
// Handle keyboard shortcuts
|
||||||
const handleKeyPress = useCallback(
|
const handleKeyPress = useCallback(
|
||||||
@@ -34,13 +39,13 @@ export default function Editor() {
|
|||||||
if (event.ctrlKey || event.metaKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
// ctrl + , (settings)
|
// ctrl + , (settings)
|
||||||
if (event.key === ',') {
|
if (event.key === ',') {
|
||||||
handleSettings();
|
toggleSettings();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleSettings],
|
[toggleSettings],
|
||||||
);
|
);
|
||||||
|
|
||||||
// register ctrl + , to open settings
|
// register ctrl + , to open settings
|
||||||
@@ -55,15 +60,28 @@ export default function Editor() {
|
|||||||
};
|
};
|
||||||
}, [handleKeyPress, isElectron]);
|
}, [handleKeyPress, isElectron]);
|
||||||
|
|
||||||
// Set window title
|
useWindowTitle('Editor');
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'ontime - Editor';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.mainContainer} data-testid='event-editor'>
|
<div className={styles.mainContainer} data-testid='event-editor'>
|
||||||
<ProductionNavigationMenu handleSettings={handleSettings} />
|
<ProductionNavigationMenu isMenuOpen={isMenuOpen} onMenuClose={onClose} />
|
||||||
{isOpen ? (
|
<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 />
|
<AppSettings />
|
||||||
) : (
|
) : (
|
||||||
<div id='panels' className={styles.panelContainer}>
|
<div id='panels' className={styles.panelContainer}>
|
||||||
@@ -74,7 +92,6 @@ export default function Editor() {
|
|||||||
<Rundown />
|
<Rundown />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Overview />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { useSearchParams } from 'react-router-dom';
|
|||||||
import { CustomField, CustomFields, isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types';
|
import { CustomField, CustomFields, isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types';
|
||||||
import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils';
|
import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils';
|
||||||
|
|
||||||
import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
|
|
||||||
import Empty from '../../common/components/state/Empty';
|
import Empty from '../../common/components/state/Empty';
|
||||||
import { getOperatorOptions } from '../../common/components/view-params-editor/constants';
|
import { getOperatorOptions } from '../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
||||||
import { useOperator } from '../../common/hooks/useSocket';
|
import { useOperator } from '../../common/hooks/useSocket';
|
||||||
|
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
@@ -41,7 +41,7 @@ export default function Operator() {
|
|||||||
const timeoutId = useRef<NodeJS.Timeout | null>(null);
|
const timeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const featureData = useOperator();
|
const featureData = useOperator();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { data: settings } = useSettings();
|
const { data: settings } = useSettings();
|
||||||
|
|
||||||
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
||||||
@@ -57,10 +57,7 @@ export default function Operator() {
|
|||||||
topOffset: selectedOffset,
|
topOffset: selectedOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set window title
|
useWindowTitle('Operator');
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'ontime - Operator';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// reset scroll if nothing is selected
|
// reset scroll if nothing is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -116,11 +113,6 @@ export default function Operator() {
|
|||||||
[searchParams],
|
[searchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const showEditFormDrawer = useCallback(() => {
|
|
||||||
searchParams.set('edit', 'true');
|
|
||||||
setSearchParams(searchParams);
|
|
||||||
}, [searchParams, setSearchParams]);
|
|
||||||
|
|
||||||
const missingData = !data || !customFields || !projectData;
|
const missingData = !data || !customFields || !projectData;
|
||||||
const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending';
|
const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending';
|
||||||
|
|
||||||
@@ -146,7 +138,6 @@ export default function Operator() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.operatorContainer}>
|
<div className={style.operatorContainer}>
|
||||||
<ProductionNavigationMenu handleSettings={showEditFormDrawer} />
|
|
||||||
<ViewParamsEditor paramFields={operatorOptions} />
|
<ViewParamsEditor paramFields={operatorOptions} />
|
||||||
{editEvent && <EditModal event={editEvent} onClose={() => setEditEvent(null)} />}
|
{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 {
|
.overview {
|
||||||
grid-area: 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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: $inner-section-text-size;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import style from './Overview.module.scss';
|
|||||||
|
|
||||||
export default memo(Overview);
|
export default memo(Overview);
|
||||||
|
|
||||||
function Overview() {
|
function Overview({ children }: { children: React.ReactNode }) {
|
||||||
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
|
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
|
||||||
|
|
||||||
const [maybePlannedEnd, maybePlannedDaySpan] = useMemo(() => calculateEndAndDaySpan(plannedEnd), [plannedEnd]);
|
const [maybePlannedEnd, maybePlannedDaySpan] = useMemo(() => calculateEndAndDaySpan(plannedEnd), [plannedEnd]);
|
||||||
@@ -24,15 +24,23 @@ function Overview() {
|
|||||||
return (
|
return (
|
||||||
<div className={style.overview}>
|
<div className={style.overview}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<TitlesOverview />
|
<div className={style.nav}>{children}</div>
|
||||||
<div className={style.column}>
|
<div className={style.info}>
|
||||||
<TimeRow label='Planned start' value={formatedTime(plannedStart)} className={style.start} />
|
<TitlesOverview />
|
||||||
<TimeRow label='Actual start' value={formatedTime(actualStart)} className={style.start} />
|
<div>
|
||||||
</div>
|
<TimeRow label='Planned start' value={formatedTime(plannedStart)} className={style.start} />
|
||||||
<RuntimeOverview />
|
<TimeRow label='Actual start' value={formatedTime(actualStart)} className={style.start} />
|
||||||
<div className={style.column}>
|
</div>
|
||||||
<TimeRow label='Planned end' value={plannedEndText} className={style.end} daySpan={maybePlannedDaySpan} />
|
<RuntimeOverview />
|
||||||
<TimeRow label='Expected end' value={expectedEndText} className={style.end} daySpan={maybeExpectedDaySpan} />
|
<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>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +51,7 @@ function TitlesOverview() {
|
|||||||
const { data } = useProjectData();
|
const { data } = useProjectData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.titles}>
|
<div>
|
||||||
<div className={style.title}>{data.title}</div>
|
<div className={style.title}>{data.title}</div>
|
||||||
<div className={style.description}>{data.description}</div>
|
<div className={style.description}>{data.description}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
|
|||||||
import { getBackstageOptions } from '../../../common/components/view-params-editor/constants';
|
import { getBackstageOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||||
@@ -57,10 +58,7 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
const [blinkClass, setBlinkClass] = useState(false);
|
const [blinkClass, setBlinkClass] = useState(false);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
// Set window title
|
useWindowTitle('Backstage');
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'ontime - Backstage Screen';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// blink on change
|
// blink on change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Settings, ViewSettings } from 'ontime-types';
|
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 { getClockOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { OverridableOptions } from '../../../common/models/View.types';
|
import { OverridableOptions } from '../../../common/models/View.types';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
@@ -25,9 +25,7 @@ export default function Clock(props: ClockProps) {
|
|||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useWindowTitle('Clock');
|
||||||
document.title = 'ontime - Clock';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// defer rendering until we load stylesheets
|
// defer rendering until we load stylesheets
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { overrideStylesURL } from '../../../common/api/constants';
|
|||||||
import { getCountdownOptions } from '../../../common/components/view-params-editor/constants';
|
import { getCountdownOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||||
@@ -37,9 +38,7 @@ export default function Countdown(props: CountdownProps) {
|
|||||||
const [runningMessage, setRunningMessage] = useState<TimerMessage>(TimerMessage.unhandled);
|
const [runningMessage, setRunningMessage] = useState<TimerMessage>(TimerMessage.unhandled);
|
||||||
const [delay, setDelay] = useState(0);
|
const [delay, setDelay] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useWindowTitle('Countdown');
|
||||||
document.title = 'ontime - Countdown';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// eg. http://localhost:4001/countdown?eventId=ei0us
|
// eg. http://localhost:4001/countdown?eventId=ei0us
|
||||||
// Check for user options
|
// Check for user options
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants';
|
|||||||
import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants';
|
import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { getPropertyValue } from '../common/viewUtils';
|
import { getPropertyValue } from '../common/viewUtils';
|
||||||
|
|
||||||
import './LowerThird.scss';
|
import './LowerThird.scss';
|
||||||
@@ -141,10 +142,7 @@ export default function LowerThird(props: LowerProps) {
|
|||||||
const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre');
|
const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre');
|
||||||
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
|
|
||||||
// set window title
|
useWindowTitle('Lower Third');
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'ontime - Lower Third';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const trigger = useMemo(() => {
|
const trigger = useMemo(() => {
|
||||||
if (options.trigger === TriggerType.Event) {
|
if (options.trigger === TriggerType.Event) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||||
import { MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
|
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 { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { OverridableOptions } from '../../../common/models/View.types';
|
import { OverridableOptions } from '../../../common/models/View.types';
|
||||||
import { timerPlaceholder } from '../../../common/utils/styleUtils';
|
import { timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||||
@@ -28,9 +28,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useWindowTitle('Minimal Timer');
|
||||||
document.title = 'ontime - Minimal Timer';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// defer rendering until we load stylesheets
|
// defer rendering until we load stylesheets
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
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 { getPublicOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||||
@@ -54,10 +54,7 @@ export default function Public(props: BackstageProps) {
|
|||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
// set window title
|
useWindowTitle('Public Schedule');
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'ontime - Public Screen';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// defer rendering until we load stylesheets
|
// defer rendering until we load stylesheets
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types';
|
import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types';
|
||||||
import { isOntimeEvent, Playback } 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 ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import useFitText from '../../../common/hooks/useFitText';
|
import useFitText from '../../../common/hooks/useFitText';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||||
@@ -46,9 +46,7 @@ export default function StudioClock(props: StudioClockProps) {
|
|||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useWindowTitle('Studio Clock');
|
||||||
document.title = 'ontime - Studio Clock';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
let clock = formatTime(time.clock);
|
let clock = formatTime(time.clock);
|
||||||
let hasAmPm = '';
|
let hasAmPm = '';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
@@ -19,6 +18,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
|
|||||||
import { getTimerOptions } from '../../../common/components/view-params-editor/constants';
|
import { getTimerOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { timerPlaceholder } from '../../../common/utils/styleUtils';
|
import { timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
@@ -63,9 +63,7 @@ export default function Timer(props: TimerProps) {
|
|||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useWindowTitle('Timer');
|
||||||
document.title = 'ontime - Timer';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// defer rendering until we load stylesheets
|
// defer rendering until we load stylesheets
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
|
|
||||||
const fileToUpload = 'e2e/tests/fixtures/test-db.json';
|
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.goto('http://localhost:4001/editor');
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
||||||
|
|||||||
@@ -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');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
// open settings
|
// open settings
|
||||||
await page.getByTestId('navigation__toggle-settings').click();
|
await page.getByRole('button', { name: 'Toggle settings' }).click();
|
||||||
await page.getByRole('button', { name: 'General' }).click();
|
await page.getByRole('button', { name: 'General' }).click();
|
||||||
|
|
||||||
// create preset
|
// create preset
|
||||||
@@ -26,4 +26,5 @@ test('test URL preset feature, it should redirect to given URL', async ({ page }
|
|||||||
// make sure preset works
|
// make sure preset works
|
||||||
await page.goto('http://localhost:4001/testing');
|
await page.goto('http://localhost:4001/testing');
|
||||||
await page.getByTestId('countdown__select').click();
|
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('button', { name: 'Rundown menu' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).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();
|
await page.getByRole('button', { name: 'Import spreadsheet' }).click();
|
||||||
|
|
||||||
// workaround to upload file on hidden input
|
// workaround to upload file on hidden input
|
||||||
|
|||||||
Reference in New Issue
Block a user