URL Params Form (#420)

* feat create form to edit URL params in views
This commit is contained in:
asharonbaltazar
2023-06-05 13:54:03 -04:00
committed by GitHub
parent 5cb8020ab9
commit bfb483508d
17 changed files with 549 additions and 99 deletions
@@ -0,0 +1,45 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
.drawerContent {
background-color: $gray-1200;
}
.drawerHeader {
@extend .drawerContent;
color: $section-white;
}
.drawerFooter {
@extend .drawerContent;
display: flex;
gap: $element-spacing;
button[type='submit'] {
padding: 0 2em;
}
}
.label {
font-size: $inner-section-text-size;
color: $label-gray;
}
.columnSection {
display: flex;
padding: $element-spacing;
flex-direction: column;
gap: $element-inner-spacing;
}
.title {
font-size: $inner-section-text-size;
display: block;
width: 100%;
}
.description {
font-size: $inner-section-text-size;
display: block;
color: $modal-note-color;
}
@@ -0,0 +1,95 @@
import { FormEvent, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import {
Button,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
useDisclosure,
} from '@chakra-ui/react';
import EditFormInput from './EditFormInput';
import { ParamField } from './types';
import style from './EditFormDrawer.module.scss';
interface EditFormDrawerProps {
paramFields: ParamField[];
}
export default function EditFormDrawer({ paramFields }: EditFormDrawerProps) {
const [searchParams, setSearchParams] = useSearchParams();
const { isOpen, onClose, onOpen } = useDisclosure();
useEffect(() => {
const isEditing = searchParams.get('edit');
if (isEditing === 'true') {
return onOpen();
}
}, [searchParams, onOpen]);
const onEditDrawerClose = () => {
onClose();
searchParams.delete('edit');
setSearchParams(searchParams);
};
const onParamsFormSubmit = (formEvent: FormEvent<HTMLFormElement>) => {
formEvent.preventDefault();
const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget));
const newSearchParams = Object.entries(newParamsObject).reduce((newSearchParams, [id, value]) => {
if (typeof value === 'string' && value.length) {
newSearchParams.set(id, value);
return newSearchParams;
}
return newSearchParams;
}, new URLSearchParams());
onEditDrawerClose();
setSearchParams(newSearchParams);
};
return (
<Drawer isOpen={isOpen} placement='right' onClose={onEditDrawerClose} size='lg'>
<DrawerOverlay />
<DrawerContent>
<DrawerHeader className={style.drawerHeader}>
<DrawerCloseButton _hover={{ bg: '#ebedf0', color: '#333' }} size='lg' />
Customise
</DrawerHeader>
<DrawerBody className={style.drawerContent}>
<form id='edit-params-form' onSubmit={onParamsFormSubmit}>
{paramFields.map((field) => (
<div key={field.title} className={style.columnSection}>
<label className={style.label}>
<span className={style.title}>{field.title}</span>
<span className={style.description}>{field.description}</span>
<EditFormInput key={field.title} paramField={field} />
</label>
</div>
))}
</form>
</DrawerBody>
<DrawerFooter className={style.drawerFooter}>
<Button variant='ontime-subtle' onClick={onEditDrawerClose}>
Cancel
</Button>
<Button variant='ontime-filled' form='edit-params-form' type='submit'>
Save
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
@@ -0,0 +1,47 @@
import { useSearchParams } from 'react-router-dom';
import { Input, Select, Switch } from '@chakra-ui/react';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { ParamField } from './types';
interface EditFormInputProps {
paramField: ParamField;
}
export default function EditFormInput({ paramField }: EditFormInputProps) {
const [searchParams] = useSearchParams();
const { id, type } = paramField;
if (type === 'option') {
const optionFromParams = searchParams.get(id);
const defaultOptionValue = paramField.values.find((value) => value === optionFromParams);
return (
<Select placeholder='Select an option' variant='ontime' name={id} defaultValue={defaultOptionValue}>
{paramField.values.map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</Select>
);
}
if (type === 'boolean') {
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) ?? false;
// checked value should be 'true', so it can be captured by the form event
return <Switch variant='ontime' name={id} defaultChecked={defaultCheckedValue} value='true' />;
}
if (type === 'number') {
const defaultNumberValue = searchParams.get(id) ?? '';
return <Input type='number' variant='ontime-filled' name={id} defaultValue={defaultNumberValue} />;
}
const defaultStringValue = searchParams.get(id) ?? '';
return <Input variant='ontime-filled' name={id} defaultValue={defaultStringValue} />;
}
@@ -0,0 +1,219 @@
import { ParamField } from './types';
export const TIME_FORMAT_OPTION: ParamField = {
id: 'format',
title: '12 / 24 hour timer',
description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences',
type: 'option',
values: ['12', '24'],
};
export const CLOCK_OPTIONS: ParamField[] = [
{
id: 'key',
title: 'Key',
description: 'Background colour in hexadecimal',
type: 'string',
},
{
id: 'text',
title: 'Text Colour',
description: 'Text colour in hexadecimal',
type: 'string',
},
{
id: 'textbg',
title: 'Text Background',
description: 'Colour of text background in hexadecimal',
type: 'string',
},
{
id: 'font',
title: 'Font',
description: 'Font family, will use the fonts available in the system',
type: 'string',
},
{
id: 'size',
title: 'Text Size',
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
type: 'number',
},
{
id: 'alignx',
title: 'Align Horizontal',
description: 'Moves the horizontally in page to start = left | center | end = right',
type: 'option',
values: ['start', 'center', 'end'],
},
{
id: 'offsetx',
title: 'Offset Horizontal',
description: 'Offsets the timer horizontal position by a given amount in pixels',
type: 'number',
},
{
id: 'aligny',
title: 'Align Vertical',
description: 'Moves the vertically in page to start = left | center | end = right',
type: 'option',
values: ['start', 'center', 'end'],
},
{
id: 'offsety',
title: 'Offset Vertical',
description: 'Offsets the timer vertical position by a given amount in pixels',
type: 'number',
},
{
id: 'hidenav',
title: 'Hide Nav',
description: 'Whether to hide the nav logo in the right corner',
type: 'boolean',
},
TIME_FORMAT_OPTION,
];
export const TIMER_OPTIONS: ParamField[] = [
{
id: 'progress',
title: 'Progress Bar',
description: 'Whether bar counts up or down',
type: 'option',
values: ['up', 'down'],
},
TIME_FORMAT_OPTION,
];
export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
{
id: 'key',
title: 'Key',
description: 'Background colour in hexadecimal',
type: 'string',
},
{
id: 'text',
title: 'Text Colour',
description: 'Text colour in hexadecimal',
type: 'string',
},
{
id: 'textbg',
title: 'Text Background',
description: 'Colour of text background in hexadecimal',
type: 'string',
},
{
id: 'font',
title: 'Font',
description: 'Font family, will use the fonts available in the system',
type: 'string',
},
{
id: 'size',
title: 'Text Size',
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
type: 'number',
},
{
id: 'alignx',
title: 'Align Horizontal',
description: 'Moves the horizontally in page to start = left | center | end = right',
type: 'option',
values: ['start', 'center', 'end'],
},
{
id: 'offsetx',
title: 'Offset Horizontal',
description: 'Offsets the timer horizontal position by a given amount in pixels',
type: 'number',
},
{
id: 'aligny',
title: 'Align Vertical',
description: 'Moves the vertically in page to start = left | center | end = right',
type: 'option',
values: ['start', 'center', 'end'],
},
{
id: 'offsety',
title: 'Offset Vertical',
description: 'Offsets the timer vertical position by a given amount in pixels',
type: 'number',
},
{
id: 'hidenav',
title: 'Hide Nav',
description: 'Whether to hide the nav logo in the right corner',
type: 'boolean',
},
{
id: 'hideovertime',
title: 'Hide Overtime',
description: 'Whether to supress overtime styles (red borders and red text)',
type: 'boolean',
},
{
id: 'hidemessages',
title: 'Hide Message Overlay',
description: 'Whether to hide the overlay from showing timer screen messages',
type: 'boolean',
},
];
export const LOWER_THIRDS_OPTIONS: ParamField[] = [
{
id: 'preset',
title: 'Preset',
description: 'Selects a style preset',
type: 'number',
},
{
id: 'size',
title: 'Size',
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
type: 'number',
},
{
id: 'transition',
title: 'Transition',
description: 'Transition in time in seconds (default 5)',
type: 'number',
},
{
id: 'text',
title: 'Text',
description: 'Text colour in hexadecimal',
type: 'string',
},
{
id: 'bg',
title: 'BG',
description: 'Text background colour in hexadecimal',
type: 'string',
},
{
id: 'key',
title: 'Key',
description: 'Screen background colour in hexadecimal',
type: 'string',
},
{
id: 'fadeout',
title: 'Fadeout',
description: 'Time (in seconds) the lower third displays before fading out',
type: 'number',
},
TIME_FORMAT_OPTION,
];
export const STUDIO_CLOCK_OPTIONS: ParamField[] = [
{
id: 'seconds',
title: 'Seconds',
description: 'Shows seconds in clock',
type: 'boolean',
},
TIME_FORMAT_OPTION,
];
@@ -0,0 +1,12 @@
type BaseField = {
id: string;
title: string;
description: string;
};
type OptionsField = { type: 'option'; values: string[] };
type StringField = { type: 'string' };
type BooleanField = { type: 'boolean' };
type NumberField = { type: 'number' };
export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField);
@@ -1,6 +1,6 @@
@use "../../../theme/v2Styles" as *;
@use "../../../theme/mixins" as *;
@use "../../../theme/ontimeColours" as *;
@use '../../../theme/v2Styles' as *;
@use '../../../theme/mixins' as *;
@use '../../../theme/ontimeColours' as *;
$menu-bg: $gray-1200;
$menu-hover-bg: $gray-1350;
@@ -14,14 +14,26 @@ $button-size: 48px;
transform: rotate(180deg);
}
.navButton {
z-index: 2;
position: absolute;
left: 0.5em;
top: 0.5em;
.buttonContainer {
display: flex;
flex-direction: column;
row-gap: 1rem;
padding: 0.5em;
transition-property: opacity;
transition-duration: 0.3s;
opacity: 1;
position: fixed;
left: 0;
top: 0;
&.hidden {
opacity: 0;
}
}
.button {
font-size: 24px;
color: $icon-color;
background-color: $button-bg;
@@ -30,10 +42,11 @@ $button-size: 48px;
display: grid;
place-content: center;
border-radius: 3px;
}
&.hidden {
opacity: 0;
}
.navButton {
@extend .button;
z-index: 2;
}
.menuContainer {
@@ -1,10 +1,11 @@
import { KeyboardEvent, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Link, useLocation } from 'react-router-dom';
import { Link, useLocation, useSearchParams } from 'react-router-dom';
import { IoApps } from '@react-icons/all-files/io5/IoApps';
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
import { IoContract } from '@react-icons/all-files/io5/IoContract';
import { IoExpand } from '@react-icons/all-files/io5/IoExpand';
import { IoPencilSharp } from '@react-icons/all-files/io5/IoPencilSharp';
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
import { navigatorConstants } from '../../../viewerConfig';
@@ -21,12 +22,13 @@ export default function NavigationMenu() {
const { isFullScreen, toggleFullScreen } = useFullscreen();
const { mirror, toggleMirror } = useViewOptionsStore();
const [showButton, setShowButton] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef<HTMLDivElement | null>(null);
useClickOutside(menuRef, () => setShowMenu(false));
const toggleMenu = () => setShowMenu((prev) => !prev);
useKeyDown(toggleMenu, ' ');
useKeyDown(toggleMenu, ' ', { isDisabled: searchParams.get('edit') === 'true' });
useEffect(() => {
let fadeOut: NodeJS.Timeout | null = null;
@@ -49,63 +51,68 @@ export default function NavigationMenu() {
const isKeyEnter = (event: KeyboardEvent<HTMLDivElement>) => event.key === 'Enter';
const handleFullscreen = () => toggleFullScreen();
const handleMirror = () => toggleMirror();
const showEditFormDrawer = () => {
searchParams.append('edit', 'true');
setSearchParams(searchParams);
};
return createPortal(
<div id='navigation-menu-portal' ref={menuRef} className={mirror ? style.mirror : ''}>
<button
onClick={toggleMenu}
aria-label='toggle menu'
className={`${style.navButton} ${!showButton && !showMenu ? style.hidden : ''}`}
>
<IoApps />
</button>
{showMenu && (
<div className={style.menuContainer} data-testid='navigation-menu'>
<div className={style.buttonsContainer}>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={handleFullscreen}
onKeyDown={(event) => {
isKeyEnter(event) && handleFullscreen();
}}
>
Toggle Fullscreen
{isFullScreen ? <IoContract /> : <IoExpand />}
<div className={`${style.buttonContainer} ${!showButton && !showMenu ? style.hidden : ''}`}>
<button onClick={toggleMenu} aria-label='toggle menu' className={style.navButton}>
<IoApps />
</button>
<button className={style.button} onClick={showEditFormDrawer}>
<IoPencilSharp />
</button>
{showMenu && (
<div className={style.menuContainer} data-testid='navigation-menu'>
<div className={style.buttonsContainer}>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={handleFullscreen}
onKeyDown={(event) => {
isKeyEnter(event) && handleFullscreen();
}}
>
Toggle Fullscreen
{isFullScreen ? <IoContract /> : <IoExpand />}
</div>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={handleMirror}
onKeyDown={(event) => {
isKeyEnter(event) && handleMirror();
}}
>
Flip Screen
<IoSwapVertical />
</div>
{/*<div className={style.link} tabIndex={0}>*/}
{/* Rename Client*/}
{/*</div>*/}
</div>
<div
className={style.link}
tabIndex={0}
role='button'
onClick={handleMirror}
onKeyDown={(event) => {
isKeyEnter(event) && handleMirror();
}}
>
Flip Screen
<IoSwapVertical />
</div>
{/*<div className={style.link} tabIndex={0}>*/}
{/* Rename Client*/}
{/*</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>
))}
</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>
))}
</div>
)}
)}
</div>
</div>,
document.body,
);
}
+17 -10
View File
@@ -1,18 +1,25 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
export const useKeyDown = (callback: () => void, targetKey: string) => {
const onKeyDown = (event: KeyboardEvent) => {
const targetKeyPressed = event.key === targetKey && !event.repeat;
if (targetKeyPressed) {
event.preventDefault();
callback();
}
};
type UseKeyDown = (callback: () => void, targetKey: string, options?: { isDisabled?: boolean }) => void;
export const useKeyDown: UseKeyDown = (callback, targetKey, options = {}) => {
const { isDisabled = false } = options;
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
const targetKeyPressed = event.key === targetKey && !event.repeat;
if (targetKeyPressed && isDisabled === false) {
event.preventDefault();
callback();
}
},
[callback, isDisabled, targetKey],
);
useEffect(() => {
document.addEventListener('keydown', onKeyDown);
return () => {
document.removeEventListener('keydown', onKeyDown);
};
}, []);
}, [onKeyDown]);
};
@@ -4,6 +4,8 @@ import { AnimatePresence, motion } from 'framer-motion';
import { EventData, Message, OntimeEvent, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { TIME_FORMAT_OPTION } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import ProgressBar from '../../../common/components/progress-bar/ProgressBar';
import Schedule from '../../../common/components/schedule/Schedule';
@@ -77,7 +79,7 @@ export default function Backstage(props: BackstageProps) {
return (
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
<NavigationMenu />
<EditFormDrawer paramFields={[TIME_FORMAT_OPTION]} />
<div className='event-header'>
{general.title}
<div className='clock-container'>
@@ -3,6 +3,8 @@ import { useSearchParams } from 'react-router-dom';
import { ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { CLOCK_OPTIONS } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
@@ -132,6 +134,7 @@ export default function Clock(props: ClockProps) {
data-testid='clock-view'
>
<NavigationMenu />
<EditFormDrawer paramFields={CLOCK_OPTIONS} />
<div
className='clock'
style={{
@@ -3,6 +3,8 @@ import { useSearchParams } from 'react-router-dom';
import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { TIME_FORMAT_OPTION } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
@@ -110,6 +112,7 @@ export default function Countdown(props: CountdownProps) {
return (
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
<NavigationMenu />
<EditFormDrawer paramFields={[TIME_FORMAT_OPTION]} />
{follow === null ? (
<CountdownSelect events={backstageEvents} />
) : (
@@ -21,10 +21,7 @@ export default function LowerClean(props) {
if (!options.fadeOut) return;
// Calculate time
const fadeOutTime =
(parseInt(options.fadeOut, 10) +
(options.transitionIn || defaults.transitionIn)) *
1000;
const fadeOutTime = (parseInt(options.fadeOut, 10) + (options.transitionIn || defaults.transitionIn)) * 1000;
if (isNaN(fadeOutTime)) return;
const timeout = setTimeout(() => {
@@ -86,8 +83,7 @@ export default function LowerClean(props) {
fontSize: `${sizeMultiplier}vh`,
}}
>
<NavigationMenu />
<NavigationMenu isEditBtnHidden />
<AnimatePresence>
{showLower && (
@@ -1,6 +1,8 @@
import { useEffect, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { LOWER_THIRDS_OPTIONS } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import './LowerLines.scss';
@@ -20,10 +22,7 @@ export default function LowerLines(props) {
if (!options.fadeOut) return;
// Calculate time
const fadeOutTime =
(parseInt(options.fadeOut, 10) +
(options.transitionIn || defaults.transitionIn)) *
1000;
const fadeOutTime = (parseInt(options.fadeOut, 10) + (options.transitionIn || defaults.transitionIn)) * 1000;
if (isNaN(fadeOutTime)) return;
const timeout = setTimeout(() => {
@@ -129,8 +128,8 @@ export default function LowerLines(props) {
fontSize: `${sizeMultiplier}vh`,
}}
>
<NavigationMenu />
<EditFormDrawer paramFields={LOWER_THIRDS_OPTIONS} />
<AnimatePresence>
{showLower && (
@@ -142,24 +141,15 @@ export default function LowerLines(props) {
animate='visible'
exit='exit'
>
<motion.div
className='title-container'
variants={titleContainerVariants}
>
<motion.div className='title-container' variants={titleContainerVariants}>
<motion.div className='title' variants={titleVariants}>
{title.titleNow}
</motion.div>
<div className='title-decor' />
</motion.div>
<motion.div
className='subtitle-container'
variants={subtitleContainerVariants}
>
<motion.div className='subtitle-container' variants={subtitleContainerVariants}>
<div className='sub-decor' />
<motion.div
className='subtitle'
variants={subtitleVariants}
>
<motion.div className='subtitle' variants={subtitleVariants}>
{title.presenterNow}
</motion.div>
</motion.div>
@@ -3,6 +3,8 @@ import { useSearchParams } from 'react-router-dom';
import { EventData, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
@@ -172,6 +174,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
data-testid='minimal-timer'
>
<NavigationMenu />
<EditFormDrawer paramFields={MINIMAL_TIMER_OPTIONS} />
{!hideMessagesOverlay && (
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
@@ -4,6 +4,8 @@ import { AnimatePresence, motion } from 'framer-motion';
import { EventData, Message, OntimeEvent, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { TIME_FORMAT_OPTION } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import Schedule from '../../../common/components/schedule/Schedule';
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
@@ -55,7 +57,7 @@ export default function Public(props: BackstageProps) {
return (
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
<NavigationMenu />
<EditFormDrawer paramFields={[TIME_FORMAT_OPTION]} />
<div className='event-header'>
{general.title}
<div className='clock-container'>
@@ -4,6 +4,8 @@ import { millisToString } from 'ontime-utils';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { STUDIO_CLOCK_OPTIONS } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import useFitText from '../../../common/hooks/useFitText';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
@@ -73,6 +75,7 @@ export default function StudioClock(props) {
return (
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
<NavigationMenu />
<EditFormDrawer paramFields={STUDIO_CLOCK_OPTIONS} />
<div className='clock-container'>
<div className={`studio-timer ${showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
<div
@@ -3,6 +3,8 @@ import { AnimatePresence, motion } from 'framer-motion';
import { EventData, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { TIMER_OPTIONS } from '../../../common/components/edit-form-drawer/constants';
import EditFormDrawer from '../../../common/components/edit-form-drawer/EditFormDrawer';
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import TitleCard from '../../../common/components/title-card/TitleCard';
@@ -98,6 +100,7 @@ export default function Timer(props: TimerProps) {
return (
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
<NavigationMenu />
<EditFormDrawer paramFields={TIMER_OPTIONS} />
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
</div>