From 17a7d035bfdbb6dfab449f6b374e96f259dd367f Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 6 Jul 2025 21:33:46 +0200 Subject: [PATCH] refactor: style cleanups and migrations fix: close modal with button refactor: small type improvements refactor: migrate time inputs refactor: migrate tooltips refactor: prevent component resizing --- apps/client/src/App.tsx | 27 ++-- .../common/components/buttons/IconButton.tsx | 37 ++--- .../components/buttons/TooltipActionBtn.tsx | 17 --- .../delay-indicator/DelayIndicator.tsx | 7 +- .../input/time-input/NullableTimeInput.tsx | 16 ++- .../input/time-input/TimeInput.module.scss | 4 + .../components/input/time-input/TimeInput.tsx | 17 ++- .../TimeInputWithButton.module.scss | 10 -- .../input/time-input/TimeInputWithButton.tsx | 37 ----- .../external-link/ExternalLink.module.scss | 5 + .../common/components/modal/Modal.module.scss | 1 + .../components/tooltip/Tooltip.module.scss | 24 ++++ .../src/common/components/tooltip/Tooltip.tsx | 24 ++++ .../panel-content/PanelContent.module.scss | 6 +- .../app-settings/panel-list/PanelList.tsx | 5 +- .../panel/feature-panel/FeaturePanel.tsx | 2 +- .../panel/feature-panel/GenerateLinkForm.tsx | 14 +- .../panel/feature-panel/UrlPresetsForm.tsx | 20 ++- .../import-map/ImportMapForm.tsx | 14 +- .../features/control/message/TimerPreview.tsx | 48 +++++-- .../playback/PlaybackControl.module.scss | 3 +- .../control/playback/add-time/AddTime.tsx | 18 +-- .../playback/aux-timer/AuxTimer.module.scss | 21 +++ .../control/playback/aux-timer/AuxTimer.tsx | 23 +++- .../playback-buttons/PlaybackButtons.tsx | 34 ++--- .../playback/playback-timer/PlaybackTimer.tsx | 10 +- .../overview/composite/TimeLayout.tsx | 11 +- .../src/features/rundown/_blockMixins.scss | 1 + .../composite/EventEditorTimes.tsx | 13 +- .../composite/EventEditorTriggers.tsx | 4 +- .../rundown-block/RundownBlock.module.scss | 12 +- .../rundown/rundown-block/RundownBlock.tsx | 3 +- .../rundown-delay/RundownDelay.module.scss | 13 +- .../rundown/rundown-delay/RundownDelay.tsx | 25 ++-- .../rundown-event/RundownEvent.module.scss | 6 +- .../rundown-event/RundownEventInner.tsx | 27 ++-- .../RundownIndicators.module.scss | 2 +- .../composite/RundownEventChip.module.scss | 4 +- .../composite/RundownEventChip.tsx | 31 ++--- .../composite/RundownEventPlayback.tsx | 124 ++++++++--------- .../time-input-flow/TimeInputFlow.module.scss | 69 +++++++--- .../rundown/time-input-flow/TimeInputFlow.tsx | 130 +++++++++--------- .../cuesheet/cuesheet-table/CuesheetTable.tsx | 2 +- .../EventRow.module.scss | 1 - .../cuesheet-table-elements/EventRow.tsx | 3 +- .../views/editor/welcome/Welcome.module.scss | 3 +- .../src/views/editor/welcome/Welcome.tsx | 19 +-- apps/server/src/adapters/WebsocketAdapter.ts | 15 +- .../services/sheet-service/SheetService.ts | 2 +- e2e/tests/features/208-auxtimer.spec.ts | 6 +- .../features/209-rundown-shortcuts.spec.ts | 14 +- e2e/tests/features/211-report-data.spec.ts | 2 +- e2e/tests/features/212-time-until.spec.ts | 2 +- .../features/301-spreadsheet-import.spec.ts | 2 + 54 files changed, 521 insertions(+), 469 deletions(-) delete mode 100644 apps/client/src/common/components/buttons/TooltipActionBtn.tsx delete mode 100644 apps/client/src/common/components/input/time-input/TimeInputWithButton.module.scss delete mode 100644 apps/client/src/common/components/input/time-input/TimeInputWithButton.tsx create mode 100644 apps/client/src/common/components/tooltip/Tooltip.module.scss create mode 100644 apps/client/src/common/components/tooltip/Tooltip.tsx diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index c07768b94..81e140fe5 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,4 +1,5 @@ import { BrowserRouter } from 'react-router-dom'; +import { Tooltip } from '@base-ui-components/react/tooltip'; import { ChakraProvider } from '@chakra-ui/react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; @@ -20,20 +21,22 @@ function App() { - -
+ + +
+ + + + + + + +
- - - - +
- -
- -
- - + + diff --git a/apps/client/src/common/components/buttons/IconButton.tsx b/apps/client/src/common/components/buttons/IconButton.tsx index 0fe1e26b5..1160ea78f 100644 --- a/apps/client/src/common/components/buttons/IconButton.tsx +++ b/apps/client/src/common/components/buttons/IconButton.tsx @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes } from 'react'; +import { ButtonHTMLAttributes, forwardRef } from 'react'; import { cx } from '../../utils/styleUtils'; @@ -17,20 +17,21 @@ interface IconButtonProps extends ButtonHTMLAttributes { size?: 'small' | 'medium' | 'large' | 'xlarge'; } -export default function IconButton({ - className, - children, - variant = 'subtle', - size = 'medium', - ...buttonProps -}: IconButtonProps) { - return ( - - ); -} +const IconButton = forwardRef( + ({ className, children, variant = 'subtle', size = 'medium', ...buttonProps }, ref) => { + return ( + + ); + }, +); + +IconButton.displayName = 'IconButton'; + +export default IconButton; diff --git a/apps/client/src/common/components/buttons/TooltipActionBtn.tsx b/apps/client/src/common/components/buttons/TooltipActionBtn.tsx deleted file mode 100644 index 81f4de703..000000000 --- a/apps/client/src/common/components/buttons/TooltipActionBtn.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { MouseEvent } from 'react'; -import { IconButton, IconButtonProps, Tooltip } from '@chakra-ui/react'; - -interface TooltipActionBtnProps extends IconButtonProps { - clickHandler: (event: MouseEvent) => void | Promise; - tooltip: string; - openDelay?: number; -} - -export default function TooltipActionBtn(props: TooltipActionBtnProps) { - const { clickHandler, icon, size = 'xs', tooltip, openDelay = 0, className, ...rest } = props; - return ( - - - - ); -} diff --git a/apps/client/src/common/components/delay-indicator/DelayIndicator.tsx b/apps/client/src/common/components/delay-indicator/DelayIndicator.tsx index 6eb07432c..16c1de1e1 100644 --- a/apps/client/src/common/components/delay-indicator/DelayIndicator.tsx +++ b/apps/client/src/common/components/delay-indicator/DelayIndicator.tsx @@ -1,8 +1,7 @@ import { IoChevronDown, IoChevronUp } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; -import { tooltipDelayFast } from '../../../ontimeConfig'; import { millisToDelayString } from '../../utils/dateConfig'; +import Tooltip from '../tooltip/Tooltip'; import style from './DelayIndicator.module.scss'; @@ -23,8 +22,8 @@ export default function DelayIndicator(props: DelayIndicatorProps) { : millisToDelayString(delayValue); return ( - - {delayValue < 0 ? : } + } className={style.delaySymbol}> + {delayValue < 0 ? : } ); } diff --git a/apps/client/src/common/components/input/time-input/NullableTimeInput.tsx b/apps/client/src/common/components/input/time-input/NullableTimeInput.tsx index 8c65385fe..eb3c53c3b 100644 --- a/apps/client/src/common/components/input/time-input/NullableTimeInput.tsx +++ b/apps/client/src/common/components/input/time-input/NullableTimeInput.tsx @@ -18,8 +18,20 @@ interface NullableTimeInputProps { className?: string; } -export default function NullableTimeInput(props: NullableTimeInputProps) { - const { id, name, submitHandler, time, emptyDisplay, placeholder, disabled, align = 'center', className } = props; +/** + * Similar to TimeInput, but allows clearing the time value + */ +export default function NullableTimeInput({ + id, + name, + submitHandler, + time, + emptyDisplay, + placeholder, + disabled, + align = 'center', + className, +}: NullableTimeInputProps) { const inputRef = useRef(null); const [value, setValue] = useState(''); const ignoreChange = useRef(false); diff --git a/apps/client/src/common/components/input/time-input/TimeInput.module.scss b/apps/client/src/common/components/input/time-input/TimeInput.module.scss index f9bea38c1..656de5515 100644 --- a/apps/client/src/common/components/input/time-input/TimeInput.module.scss +++ b/apps/client/src/common/components/input/time-input/TimeInput.module.scss @@ -3,4 +3,8 @@ max-width: 7.5em; letter-spacing: 0.5px; font-variant-numeric: tabular-nums; + + &.delayed { + border: 1px solid $ontime-delay-text; + } } diff --git a/apps/client/src/common/components/input/time-input/TimeInput.tsx b/apps/client/src/common/components/input/time-input/TimeInput.tsx index 64fd5f73a..902200a64 100644 --- a/apps/client/src/common/components/input/time-input/TimeInput.tsx +++ b/apps/client/src/common/components/input/time-input/TimeInput.tsx @@ -14,11 +14,21 @@ interface TimeInputProps { placeholder?: string; disabled?: boolean; align?: 'left' | 'center'; + delayed?: boolean; className?: string; } -export default function TimeInput(props: TimeInputProps) { - const { id, name, submitHandler, time, placeholder, disabled, align = 'center', className } = props; +export default function TimeInput({ + id, + name, + submitHandler, + time, + placeholder, + disabled, + align = 'center', + delayed, + className, +}: TimeInputProps) { const inputRef = useRef(null); const [value, setValue] = useState(''); const ignoreChange = useRef(false); @@ -122,7 +132,7 @@ export default function TimeInput(props: TimeInputProps) { disabled={disabled} ref={inputRef} data-testid={`time-input-${name}`} - className={cx([style.timeInput, className])} + className={cx([style.timeInput, delayed && style.delayed, className])} placeholder={placeholder} onFocus={handleFocus} onChange={(event) => setValue(event.target.value)} @@ -130,7 +140,6 @@ export default function TimeInput(props: TimeInputProps) { onKeyDown={onKeyDownHandler} value={value} maxLength={8} - autoComplete='off' style={{ textAlign: align, }} diff --git a/apps/client/src/common/components/input/time-input/TimeInputWithButton.module.scss b/apps/client/src/common/components/input/time-input/TimeInputWithButton.module.scss deleted file mode 100644 index 75f8ca1b7..000000000 --- a/apps/client/src/common/components/input/time-input/TimeInputWithButton.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -$input-delayed-border-color: $ontime-delay-text; - -.timeInput { - border: 1px solid transparent; - color: $label-gray; - - &.delayed { - border: 1px solid $input-delayed-border-color; - } -} diff --git a/apps/client/src/common/components/input/time-input/TimeInputWithButton.tsx b/apps/client/src/common/components/input/time-input/TimeInputWithButton.tsx deleted file mode 100644 index e491a6ee3..000000000 --- a/apps/client/src/common/components/input/time-input/TimeInputWithButton.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { PropsWithChildren } from 'react'; -import { InputGroup } from '@chakra-ui/react'; - -import { cx } from '../../../utils/styleUtils'; - -import TimeInput from './TimeInput'; - -import style from './TimeInputWithButton.module.scss'; - -interface TimeInputWithButtonProps { - name: T; - submitHandler: (field: T, value: string) => void; - time?: number; - hasDelay?: boolean; - disabled?: boolean; - placeholder: string; -} - -export default function TimeInputWithButton(props: PropsWithChildren>) { - const { name, submitHandler, time, hasDelay, placeholder, disabled, children } = props; - - const inputClasses = cx([style.timeInput, hasDelay ? style.delayed : null]); - - return ( - - - name={name} - submitHandler={submitHandler} - time={time} - placeholder={placeholder} - align='left' - disabled={disabled} - /> - {children} - - ); -} diff --git a/apps/client/src/common/components/link/external-link/ExternalLink.module.scss b/apps/client/src/common/components/link/external-link/ExternalLink.module.scss index 3aafa8c42..9fb22c59a 100644 --- a/apps/client/src/common/components/link/external-link/ExternalLink.module.scss +++ b/apps/client/src/common/components/link/external-link/ExternalLink.module.scss @@ -15,4 +15,9 @@ &:hover { color: $ontime-color; } + + &:focus { + outline: none; + box-shadow: 0 1px 0 0 currentColor; + } } diff --git a/apps/client/src/common/components/modal/Modal.module.scss b/apps/client/src/common/components/modal/Modal.module.scss index 11256f68a..3138d139e 100644 --- a/apps/client/src/common/components/modal/Modal.module.scss +++ b/apps/client/src/common/components/modal/Modal.module.scss @@ -14,6 +14,7 @@ border-radius: 3px; box-shadow: $box-shadow-l1; border: 1px solid $gray-1100; + outline: none; } .backdrop { diff --git a/apps/client/src/common/components/tooltip/Tooltip.module.scss b/apps/client/src/common/components/tooltip/Tooltip.module.scss new file mode 100644 index 000000000..aaec6aae6 --- /dev/null +++ b/apps/client/src/common/components/tooltip/Tooltip.module.scss @@ -0,0 +1,24 @@ +.tooltip { + font-size: calc(1rem - 3px); + background-color: $ui-white; + color: $ui-black; + padding: 0.125rem 0.5rem; + border-radius: 2px; + line-height: 1.25em; + max-width: 200px; + + transform-origin: var(--transform-origin); + transition: + transform 150ms, + opacity 150ms; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: scale(0.9); + } + + &[data-instant] { + transition-duration: 0ms; + } +} diff --git a/apps/client/src/common/components/tooltip/Tooltip.tsx b/apps/client/src/common/components/tooltip/Tooltip.tsx new file mode 100644 index 000000000..304b4c35d --- /dev/null +++ b/apps/client/src/common/components/tooltip/Tooltip.tsx @@ -0,0 +1,24 @@ +import { PropsWithChildren } from 'react'; +import { Tooltip as BaseTooltip } from '@base-ui-components/react/tooltip'; + +import style from './Tooltip.module.scss'; + +interface TooltipProps extends BaseTooltip.Trigger.Props { + text: string; +} + +export default function Tooltip({ text, children, ...triggerProps }: PropsWithChildren) { + return ( + + {children} + + + + + {text} + + + + + ); +} diff --git a/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss b/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss index 3d5a6283c..0e8d6afd8 100644 --- a/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss +++ b/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss @@ -1,7 +1,7 @@ .corner { - position: absolute; - top: 1rem; - right: 2rem; + position: fixed; + top: 6rem; + right: 4rem; z-index: $zindex-floating; } diff --git a/apps/client/src/features/app-settings/panel-list/PanelList.tsx b/apps/client/src/features/app-settings/panel-list/PanelList.tsx index 7406946fa..5ad62f94e 100644 --- a/apps/client/src/features/app-settings/panel-list/PanelList.tsx +++ b/apps/client/src/features/app-settings/panel-list/PanelList.tsx @@ -1,9 +1,8 @@ import { Fragment } from 'react'; -import { Tooltip } from '@chakra-ui/react'; +import Tooltip from '../../../common/components/tooltip/Tooltip'; import { isKeyEnter } from '../../../common/utils/keyEvent'; import { cx } from '../../../common/utils/styleUtils'; -import { tooltipDelayFast } from '../../../ontimeConfig'; import { SettingsOption, SettingsOptionId, useAppSettingsMenu } from '../useAppSettingsMenu'; import useAppSettingsNavigation from '../useAppSettingsNavigation'; @@ -26,7 +25,7 @@ export default function PanelList({ selectedPanel, location }: PanelListProps) { const isSelected = selectedPanel === panel.id; if (panel.highlight) { return ( - + }> ); diff --git a/apps/client/src/features/app-settings/panel/feature-panel/FeaturePanel.tsx b/apps/client/src/features/app-settings/panel/feature-panel/FeaturePanel.tsx index 41c30c2fb..e235f6f63 100644 --- a/apps/client/src/features/app-settings/panel/feature-panel/FeaturePanel.tsx +++ b/apps/client/src/features/app-settings/panel/feature-panel/FeaturePanel.tsx @@ -23,13 +23,13 @@ export default function FeaturePanel({ location }: PanelBaseProps) { Share Ontime Link - {!isOntimeCloud && ( <> Ontime is streaming on the following network interfaces )} + diff --git a/apps/client/src/features/app-settings/panel/feature-panel/GenerateLinkForm.tsx b/apps/client/src/features/app-settings/panel/feature-panel/GenerateLinkForm.tsx index a309c62fe..596ad9039 100644 --- a/apps/client/src/features/app-settings/panel/feature-panel/GenerateLinkForm.tsx +++ b/apps/client/src/features/app-settings/panel/feature-panel/GenerateLinkForm.tsx @@ -73,18 +73,12 @@ export default function GenerateLinkForm({ hostOptions, pathOptions, isLockedToV }; return ( - preventEscape(event)}> +
preventEscape(event)}> {errors.root && {errors.root.message}} {!isLockedToView ? ( - - - You can generate a link to share with your team or to use in automation (such as companion). - - + You can generate a link to share with your team or to use in automation (such as companion). ) : ( - - You can generate a link to share with your team - + You can generate a link to share with your team )} @@ -136,6 +130,6 @@ export default function GenerateLinkForm({ hostOptions, pathOptions, isLockedToV
- + ); } diff --git a/apps/client/src/features/app-settings/panel/feature-panel/UrlPresetsForm.tsx b/apps/client/src/features/app-settings/panel/feature-panel/UrlPresetsForm.tsx index 8411a9360..aa4da6e10 100644 --- a/apps/client/src/features/app-settings/panel/feature-panel/UrlPresetsForm.tsx +++ b/apps/client/src/features/app-settings/panel/feature-panel/UrlPresetsForm.tsx @@ -8,10 +8,10 @@ import { postUrlPresets } from '../../../../common/api/urlPresets'; import { maybeAxiosError } from '../../../../common/api/utils'; import Button from '../../../../common/components/buttons/Button'; import IconButton from '../../../../common/components/buttons/IconButton'; -import TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn'; import Info from '../../../../common/components/info/Info'; import Input from '../../../../common/components/input/input/Input'; import ExternalLink from '../../../../common/components/link/external-link/ExternalLink'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; import useUrlPresets from '../../../../common/hooks-query/useUrlPresets'; import { preventEscape } from '../../../../common/utils/keyEvent'; import { handleLinks } from '../../../../common/utils/linkUtils'; @@ -194,17 +194,15 @@ export default function UrlPresetsForm() { {maybeUrlError} - handleLinks(preset.alias, event)} - tooltip='Test preset' - aria-label='Test preset' - variant='ontime-ghosted' - color='#e2e2e2' // $gray-200 - icon={} + } data-testid={`field__test_${index}`} - /> + onClick={(event) => handleLinks(preset.alias, event)} + disabled={!canTest} + > + + remove(index)} variant='ghosted-destructive' diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx index 8a642bba7..d9ed1ae3f 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from 'react'; import { useFieldArray, useForm } from 'react-hook-form'; import { IoAdd, IoTrash } from 'react-icons/io5'; -import { Select, Tooltip } from '@chakra-ui/react'; +import { Select } from '@chakra-ui/react'; import { ImportMap, isAlphanumericWithSpace } from 'ontime-utils'; import Button from '../../../../../../common/components/buttons/Button'; import IconButton from '../../../../../../common/components/buttons/IconButton'; import Input from '../../../../../../common/components/input/input/Input'; +import Tooltip from '../../../../../../common/components/tooltip/Tooltip'; import * as Panel from '../../../../panel-utils/PanelUtils'; import useGoogleSheet from '../useGoogleSheet'; import { useSheetStore } from '../useSheetStore'; @@ -97,10 +98,13 @@ export default function ImportMapForm(props: ImportMapFormProps) { Import options {!isSpreadsheet && ( - - + } + onClick={handleRevoke} + disabled={isLoading} + > + Revoke )}
- - + } + className={style.statusIcon} + data-active={timerType === TimerType.CountDown} + > + - - + } + className={style.statusIcon} + data-active={timerType === TimerType.CountUp} + > + - - + } + className={style.statusIcon} + data-active={timerType === TimerType.Clock} + > + - - + } + className={style.statusIcon} + data-active={timerType === TimerType.None} + > + - - + } + className={style.statusIcon} + data-active={countToEnd} + > +
diff --git a/apps/client/src/features/control/playback/PlaybackControl.module.scss b/apps/client/src/features/control/playback/PlaybackControl.module.scss index 246d85fe8..c3dfc7bdf 100644 --- a/apps/client/src/features/control/playback/PlaybackControl.module.scss +++ b/apps/client/src/features/control/playback/PlaybackControl.module.scss @@ -4,6 +4,7 @@ } .auxTimers { - display: flex; + display: grid; + grid-template-columns: 1fr 1fr 1fr; gap: 0.5rem; } diff --git a/apps/client/src/features/control/playback/add-time/AddTime.tsx b/apps/client/src/features/control/playback/add-time/AddTime.tsx index 19b7b717d..b53566611 100644 --- a/apps/client/src/features/control/playback/add-time/AddTime.tsx +++ b/apps/client/src/features/control/playback/add-time/AddTime.tsx @@ -1,12 +1,10 @@ import { IoAdd, IoRemove } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; import { useLocalStorage } from '@mantine/hooks'; import { Playback } from 'ontime-types'; import { MILLIS_PER_HOUR, parseUserTime } from 'ontime-utils'; import TimeInput from '../../../../common/components/input/time-input/TimeInput'; import { setPlayback } from '../../../../common/hooks/useSocket'; -import { tooltipDelayMid } from '../../../../ontimeConfig'; import TapButton from '../tap-button/TapButton'; import style from './AddTime.module.scss'; @@ -41,16 +39,12 @@ export default function AddTime(props: AddTimeProps) {
- - handleAddTime('remove')} disabled={doDisableButtons} className={style.tallButtons}> - - - - - handleAddTime('add')} disabled={doDisableButtons} className={style.tallButtons}> - - - + handleAddTime('remove')} disabled={doDisableButtons} className={style.tallButtons}> + + + handleAddTime('add')} disabled={doDisableButtons} className={style.tallButtons}> + +
); diff --git a/apps/client/src/features/control/playback/aux-timer/AuxTimer.module.scss b/apps/client/src/features/control/playback/aux-timer/AuxTimer.module.scss index 889a46078..58297dd12 100644 --- a/apps/client/src/features/control/playback/aux-timer/AuxTimer.module.scss +++ b/apps/client/src/features/control/playback/aux-timer/AuxTimer.module.scss @@ -22,3 +22,24 @@ gap: 0.25rem; height: 1.5rem; } + +.fakeInput { + box-sizing: border-box; + width: 100%; + max-width: 7.5em; + height: 2rem; + display: grid; + place-content: center; + + font-size: 1rem; + font-weight: 400; + color: $gray-200; + border-radius: $component-border-radius-md; + border: 1px solid transparent; + letter-spacing: 0.5px; + font-variant-numeric: tabular-nums; + + padding-inline: 0.5em; + outline: none; + +} \ No newline at end of file diff --git a/apps/client/src/features/control/playback/aux-timer/AuxTimer.tsx b/apps/client/src/features/control/playback/aux-timer/AuxTimer.tsx index f483ac04a..fdb3ab61f 100644 --- a/apps/client/src/features/control/playback/aux-timer/AuxTimer.tsx +++ b/apps/client/src/features/control/playback/aux-timer/AuxTimer.tsx @@ -1,6 +1,6 @@ import { IoArrowDown, IoArrowUp, IoPause, IoPlay, IoStop } from 'react-icons/io5'; import { Playback, SimpleDirection, SimplePlayback } from 'ontime-types'; -import { parseUserTime } from 'ontime-utils'; +import { millisToString, parseUserTime } from 'ontime-utils'; import TimeInput from '../../../../common/components/input/time-input/TimeInput'; import { setAuxTimer, useAuxTimerControl, useAuxTimerTime } from '../../../../common/hooks/useSocket'; @@ -22,7 +22,7 @@ export function AuxTimer({ index }: AuxTimerProps) { setDirection(index, newDirection); }; - const canStop = playback !== SimplePlayback.Stop; + const isActive = playback !== SimplePlayback.Stop; const playbackAction = playback === SimplePlayback.Start ? 'pause' : 'play'; return ( @@ -30,15 +30,15 @@ export function AuxTimer({ index }: AuxTimerProps) { Aux Timer {index}
- - + + {direction === SimpleDirection.CountDown && } {direction === SimpleDirection.CountUp && }
- stop(index)} theme={Playback.Stop} disabled={!canStop}> + stop(index)} theme={Playback.Stop} disabled={!isActive}>
@@ -47,11 +47,12 @@ export function AuxTimer({ index }: AuxTimerProps) { ); } -interface AuxTimerInput { +interface AuxTimerInputProps { index: number; + isActive: boolean; } -function AuxTimerInput({ index }: AuxTimerProps) { +function AuxTimerInput({ index, isActive }: AuxTimerInputProps) { const newTimeInMs = useAuxTimerTime(index); const { setDuration } = setAuxTimer; @@ -60,6 +61,14 @@ function AuxTimerInput({ index }: AuxTimerProps) { setDuration(index, newTimeInMs); }; + if (isActive) { + return ( +
+ {millisToString(newTimeInMs)} +
+ ); + } + return ( ); diff --git a/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx b/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx index 47fd1790f..23efcdc65 100644 --- a/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx +++ b/apps/client/src/features/control/playback/playback-buttons/PlaybackButtons.tsx @@ -1,11 +1,9 @@ import { useMemo } from 'react'; import { IoPause, IoPlay, IoPlaySkipBack, IoPlaySkipForward, IoReload, IoStop, IoTime } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; import { Playback, TimerPhase } from 'ontime-types'; import { validatePlayback } from 'ontime-utils'; import { setPlayback } from '../../../../common/hooks/useSocket'; -import { tooltipDelayMid } from '../../../../ontimeConfig'; import TapButton from '../tap-button/TapButton'; import style from './PlaybackButtons.module.scss'; @@ -66,31 +64,23 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
- - - - - - - - - - + + + + + +
- - - - - - - - - - + + + + + +
); diff --git a/apps/client/src/features/control/playback/playback-timer/PlaybackTimer.tsx b/apps/client/src/features/control/playback/playback-timer/PlaybackTimer.tsx index eb1a541a6..0b49e5bf1 100644 --- a/apps/client/src/features/control/playback/playback-timer/PlaybackTimer.tsx +++ b/apps/client/src/features/control/playback/playback-timer/PlaybackTimer.tsx @@ -1,9 +1,9 @@ import { PropsWithChildren } from 'react'; -import { Tooltip } from '@chakra-ui/react'; import { MaybeNumber, Playback, TimerPhase } from 'ontime-types'; import { dayInMs, millisToString } from 'ontime-utils'; import AppLink from '../../../../common/components/link/app-link/AppLink'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; import { useTimer } from '../../../../common/hooks/useSocket'; import useReport from '../../../../common/hooks-query/useReport'; import { formatDuration } from '../../../../common/utils/time'; @@ -43,13 +43,9 @@ export default function PlaybackTimer(props: PropsWithChildren
- -
- + } className={style.indicatorRoll} data-active={isRolling} />
- -
- + } className={style.indicatorDelay} data-active={hasAddedTime} />
diff --git a/apps/client/src/features/overview/composite/TimeLayout.tsx b/apps/client/src/features/overview/composite/TimeLayout.tsx index a1a19cd9a..337a81221 100644 --- a/apps/client/src/features/overview/composite/TimeLayout.tsx +++ b/apps/client/src/features/overview/composite/TimeLayout.tsx @@ -1,5 +1,4 @@ -import { Tooltip } from '@chakra-ui/react'; - +import Tooltip from '../../../common/components/tooltip/Tooltip'; import { cx } from '../../../common/utils/styleUtils'; import style from './TimeLayout.module.scss'; @@ -29,8 +28,12 @@ export function TimeRow({ label, value, daySpan, muted, className }: TimeLayoutP
{label} {daySpan ? ( - - {value} + } + className={cx([style.clock, style.daySpan, className])} + > + {value} ) : ( {value} diff --git a/apps/client/src/features/rundown/_blockMixins.scss b/apps/client/src/features/rundown/_blockMixins.scss index f981f3b1a..b2227793d 100644 --- a/apps/client/src/features/rundown/_blockMixins.scss +++ b/apps/client/src/features/rundown/_blockMixins.scss @@ -22,6 +22,7 @@ $block-cursor-color: $orange-400; border-radius: $block-border-radius; position: relative; color: $block-text-color; + overflow: hidden; min-width: $block-width; } diff --git a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx index 4db9a8d0d..1c3b31a14 100644 --- a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx +++ b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx @@ -1,6 +1,5 @@ import { memo } from 'react'; import { IoInformationCircle } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; import { EndAction, TimerType, TimeStrategy } from 'ontime-types'; import { millisToString, parseUserTime } from 'ontime-utils'; @@ -8,6 +7,7 @@ import * as Editor from '../../../../common/components/editor-utils/EditorUtils' import TimeInput from '../../../../common/components/input/time-input/TimeInput'; import Select from '../../../../common/components/select/Select'; import Switch from '../../../../common/components/switch/Switch'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; import { useEntryActions } from '../../../../common/hooks/useEntryAction'; import { millisToDelayString } from '../../../../common/utils/dateConfig'; import TimeInputFlow from '../../time-input-flow/TimeInputFlow'; @@ -126,11 +126,12 @@ function EventEditorTimes({
- - - Display Options - - + } + > + Display Options +
diff --git a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx index a52539a9b..abafc2786 100644 --- a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx +++ b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx @@ -1,6 +1,5 @@ import { Fragment, useCallback, useMemo, useState } from 'react'; import { IoAlertCircle, IoCheckmarkCircle, IoTrash } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; import { TimerLifeCycle, timerLifecycleValues, Trigger } from 'ontime-types'; import { generateId } from 'ontime-utils'; @@ -8,6 +7,7 @@ import Button from '../../../../common/components/buttons/Button'; import IconButton from '../../../../common/components/buttons/IconButton'; import Select from '../../../../common/components/select/Select'; import Tag from '../../../../common/components/tag/Tag'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; import { useEntryActions } from '../../../../common/hooks/useEntryAction'; import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings'; @@ -107,7 +107,7 @@ function EventTriggerForm({ eventId, triggers }: EventTriggerFormProps) { Add {validationError !== undefined ? ( - + }> ) : ( diff --git a/apps/client/src/features/rundown/rundown-block/RundownBlock.module.scss b/apps/client/src/features/rundown/rundown-block/RundownBlock.module.scss index c43aff0dc..51cad5024 100644 --- a/apps/client/src/features/rundown/rundown-block/RundownBlock.module.scss +++ b/apps/client/src/features/rundown/rundown-block/RundownBlock.module.scss @@ -2,21 +2,21 @@ .block { @include block-styling; - overflow: hidden; + margin-block: 1rem 0.25rem; display: grid; grid-template-columns: 2rem 1fr; grid-template-areas: 'binder header'; align-items: center; - // TODO(style fix): groups have an extra bottom margin which interrupt colour - margin-block: 0.25rem; &.hasCursor { outline: 1px solid $block-cursor-color; } &.expanded { + margin-block: 1rem 0; border-radius: $block-border-radius $block-border-radius 0 0; + border-bottom: 0.25rem solid color-mix(in srgb, transparent 90%, var(--user-bg, transparent) 10%); } .binder { @@ -29,8 +29,12 @@ place-content: center; position: relative; cursor: pointer; - } + &:focus { + outline: 1px solid $blue-500; + outline-offset: -1px; + } + } .header { grid-area: header; diff --git a/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx b/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx index 8fff87166..c2220ea35 100644 --- a/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx +++ b/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx @@ -102,8 +102,9 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }: onClick={handleFocusClick} onContextMenu={onContextMenu} style={{ - ...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}), + // ...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}), ...dragStyle, + '--user-bg': data.colour || '#929292', }} data-testid='rundown-block' > diff --git a/apps/client/src/features/rundown/rundown-delay/RundownDelay.module.scss b/apps/client/src/features/rundown/rundown-delay/RundownDelay.module.scss index bebb4d1da..d38aacdd0 100644 --- a/apps/client/src/features/rundown/rundown-delay/RundownDelay.module.scss +++ b/apps/client/src/features/rundown/rundown-delay/RundownDelay.module.scss @@ -4,12 +4,11 @@ @include block-styling; margin-block: 0.25rem; + padding-right: 0.25rem; background-color: $block-bg2; - padding-right: 0.5rem; display: grid; - grid-template-columns: 2rem 1fr auto; - grid-template-areas: 'drag inpt btns'; + grid-template-columns: 2rem 1fr auto auto; align-items: center; height: $secondary-block-height; gap: 0.5rem; @@ -21,12 +20,4 @@ .drag { @include drag-style; - grid-area: drag; } - -.actionButtons { - grid-area: btns; - display: flex; - align-items: center; - gap: 0.5rem; -} \ No newline at end of file diff --git a/apps/client/src/features/rundown/rundown-delay/RundownDelay.tsx b/apps/client/src/features/rundown/rundown-delay/RundownDelay.tsx index 1812e1b62..b3ae36237 100644 --- a/apps/client/src/features/rundown/rundown-delay/RundownDelay.tsx +++ b/apps/client/src/features/rundown/rundown-delay/RundownDelay.tsx @@ -55,23 +55,24 @@ export default function RundownDelay({ data, hasCursor }: RundownDelayProps) { deleteEntry([data.id]); }; - const blockClasses = cx([style.delay, hasCursor ? style.hasCursor : null]); - return ( -
+
-
- - -
+ +
); } diff --git a/apps/client/src/features/rundown/rundown-event/RundownEvent.module.scss b/apps/client/src/features/rundown/rundown-event/RundownEvent.module.scss index 225c3478c..dac3c67d8 100644 --- a/apps/client/src/features/rundown/rundown-event/RundownEvent.module.scss +++ b/apps/client/src/features/rundown/rundown-event/RundownEvent.module.scss @@ -6,6 +6,7 @@ $skip-opacity: 0.2; @include block-styling; background-color: $block-bg; margin-block: 0.25rem; + overflow: initial; display: grid; grid-template-areas: @@ -15,7 +16,7 @@ $skip-opacity: 0.2; 'binder pb-actions estatus estatus' 'binder ... ... ...'; - grid-template-columns: $block-binder-width 3rem 1fr auto; + grid-template-columns: $block-binder-width 3rem 1fr 3rem; grid-template-rows: 0.125rem 2rem 2rem auto 0.125rem; align-items: center; padding-right: $block-clearance; @@ -92,11 +93,10 @@ $skip-opacity: 0.2; position: relative; cursor: pointer; - border-radius: $block-border-radius 0 0 $block-border-radius; - background-color: $gray-1050; // to override inline color: $section-white; font-size: 1rem; + border-radius: 3px 0 0 3px; .drag { @include drag-style; diff --git a/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx b/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx index e038230cd..67c2868b1 100644 --- a/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx +++ b/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx @@ -10,11 +10,10 @@ import { IoPlaySkipForward, IoTime, } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; import { EndAction, Playback, TimerType, TimeStrategy } from 'ontime-types'; +import Tooltip from '../../../common/components/tooltip/Tooltip'; import { cx } from '../../../common/utils/styleUtils'; -import { tooltipDelayMid } from '../../../ontimeConfig'; import EditableBlockTitle from '../common/EditableBlockTitle'; import TimeInputFlow from '../time-input-flow/TimeInputFlow'; @@ -137,25 +136,17 @@ function RundownEventInner({ {loaded && }
- - - - + }> + - - - - + }> + - - - - + }> + - - - - + }> +
diff --git a/apps/client/src/features/rundown/rundown-event/RundownIndicators.module.scss b/apps/client/src/features/rundown/rundown-event/RundownIndicators.module.scss index 094825564..5ceff61d6 100644 --- a/apps/client/src/features/rundown/rundown-event/RundownIndicators.module.scss +++ b/apps/client/src/features/rundown/rundown-event/RundownIndicators.module.scss @@ -5,7 +5,7 @@ $gap-left: calc(2rem + 0.25rem + 3rem); font-size: calc(1rem - 5px); position: absolute; top: -1em; - z-index: $zindex-floating;; + z-index: $zindex-floating; margin-left: $gap-left; display: flex; diff --git a/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.module.scss b/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.module.scss index d59e42736..1d636bd1a 100644 --- a/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.module.scss +++ b/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.module.scss @@ -1,11 +1,9 @@ .chip { - background-color: $gray-1100; white-space: nowrap; font-size: calc(1rem - 3px); color: $label-gray; - padding: 0.125rem 0.5rem; - border-radius: 2px; + justify-self: end; &.over { color: $ontime-delay-text; diff --git a/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.tsx b/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.tsx index 772ac593f..415370f96 100644 --- a/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.tsx +++ b/apps/client/src/features/rundown/rundown-event/composite/RundownEventChip.tsx @@ -1,13 +1,12 @@ import { useMemo } from 'react'; import { IoCheckmarkCircle } from 'react-icons/io5'; -import { Tooltip } from '@chakra-ui/react'; import { isPlaybackActive, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; import { usePlayback } from '../../../../common/hooks/useSocket'; import useReport from '../../../../common/hooks-query/useReport'; import { cx } from '../../../../common/utils/styleUtils'; import { formatDuration, formatTime, useTimeUntilStart } from '../../../../common/utils/time'; -import { tooltipDelayFast } from '../../../../ontimeConfig'; import style from './RundownEventChip.module.scss'; @@ -39,7 +38,7 @@ export default function RundownEventChip({ const { playback } = usePlayback(); if (isLoaded) { - return null; //TODO: the is a small flash of 'DUE' on the loaded event as clock data arrives before isLoaded propagates + return null; } const playbackActive = isPlaybackActive(playback); @@ -51,16 +50,14 @@ export default function RundownEventChip({ if (playbackActive) { // we extracted the component to avoid unnecessary calculations and re-renders return ( - -
- -
+ } className={className}> + ); } @@ -113,7 +110,7 @@ function EventReport(props: EventReportProps) { const absDifference = Math.abs(difference); if (absDifference < MILLIS_PER_SECOND) { - return ['ontime', 'ontime', 'Event finished ontime']; + return ['ontime', 'under', 'Event finished on time']; } const isOver = difference > 0; @@ -131,10 +128,8 @@ function EventReport(props: EventReportProps) { } return ( - -
- {value === 'ontime' ? : value} -
+ } className={cx([style.chip, style[overUnderStyle], className])}> + {value === 'ontime' ? : value} ); } diff --git a/apps/client/src/features/rundown/rundown-event/composite/RundownEventPlayback.tsx b/apps/client/src/features/rundown/rundown-event/composite/RundownEventPlayback.tsx index 3d55841c2..21514b0dc 100644 --- a/apps/client/src/features/rundown/rundown-event/composite/RundownEventPlayback.tsx +++ b/apps/client/src/features/rundown/rundown-event/composite/RundownEventPlayback.tsx @@ -1,28 +1,13 @@ import { memo, MouseEvent } from 'react'; import { IoPause, IoPlay, IoReload, IoRemoveCircle, IoRemoveCircleOutline } from 'react-icons/io5'; -import TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn'; +import IconButton from '../../../../common/components/buttons/IconButton'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; import { useEntryActions } from '../../../../common/hooks/useEntryAction'; import { setEventPlayback } from '../../../../common/hooks/useSocket'; -import { tooltipDelayMid } from '../../../../ontimeConfig'; import style from '../RundownEvent.module.scss'; -const blockBtnStyle = { - size: 'sm', -}; - -type StyleVariant = { - 'aria-label': string; - tooltip: string; - backgroundColor: string; - _hover: { backgroundColor?: string }; -}; - -const tooltipProps = { - openDelay: tooltipDelayMid, -}; - interface RundownEventPlaybackProps { eventId: string; skip: boolean; @@ -67,67 +52,66 @@ function RundownEventPlayback({ setEventPlayback.loadEvent(eventId); }; - const buttonVariant: Partial = {}; - - if (isPaused) { - // continue - buttonVariant['aria-label'] = 'Continue event'; - buttonVariant.tooltip = 'Continue event'; - buttonVariant.backgroundColor = '#339E4E'; - buttonVariant._hover = { backgroundColor: '#339E4Eee' }; - } else if (isPlaying) { - // pause - buttonVariant['aria-label'] = 'Pause event'; - buttonVariant.tooltip = 'Pause event'; - buttonVariant.backgroundColor = '#c05621'; - buttonVariant._hover = { backgroundColor: '#c05621ee' }; - } else { - // start - buttonVariant['aria-label'] = 'Start event'; - buttonVariant.tooltip = 'Start event'; - if (!disablePlayback) { - buttonVariant._hover = { backgroundColor: '#339E4E' }; + const playButtonStyles: { tooltip: string; backgroundColor: string | undefined } = (() => { + if (isPaused) { + return { + tooltip: 'Continue event', + backgroundColor: '#339E4E', + }; } - } + + if (isPlaying) { + return { + tooltip: 'Pause event', + backgroundColor: '#c05621', + }; + } + return { + tooltip: 'Start event', + backgroundColor: undefined, + }; + })(); return (
- } + onClick={toggleSkip} + tabIndex={-1} + disabled={loaded} + style={{ + background: skip ? '#9A0000' : undefined, + }} aria-label='Skip event' - tooltip='Skip event' - icon={skip ? : } - backgroundColor={skip ? '#B20000' : undefined} - _hover={{ backgroundColor: '#FF7878' }} - {...tooltipProps} - {...blockBtnStyle} - clickHandler={toggleSkip} + > + {skip ? : } + + + } + onClick={load} tabIndex={-1} - isDisabled={loaded} - /> - } - isDisabled={disablePlayback} - {...tooltipProps} - {...blockBtnStyle} - clickHandler={load} + > + + + + } + onClick={actionHandler} tabIndex={-1} - /> - : } - isDisabled={disablePlayback} - {...tooltipProps} - {...blockBtnStyle} - {...buttonVariant} - clickHandler={actionHandler} - tabIndex={-1} - /> + disabled={disablePlayback} + style={{ + backgroundColor: playButtonStyles.backgroundColor, + }} + aria-label={isPlaying ? 'Pause event' : 'Start event'} + > + {!isPlaying ? : } +
); } diff --git a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.module.scss b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.module.scss index 4d19e180a..c27e8ce55 100644 --- a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.module.scss +++ b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.module.scss @@ -1,30 +1,63 @@ -.timeLabel { - position: absolute; - z-index: $zindex-floating; - font-size: 0.75rem; - font-weight: 600; - top: 2px; - right: 4px; - color: inherit; -} - .timeAction { - opacity: 0.4; + background: $gray-1050; + color: $gray-500; cursor: pointer; - padding-right: 0.5em; + height: 2rem; + width: 2rem; + display: grid; + place-content: center; + aspect-ratio: 1; + + &:hover:not(:disabled):not(:active) { + background: $gray-1000; + } + + &:active:not(:disabled) { + background: $gray-1300; + } + + &:focus { + outline: 1px solid $blue-500; + outline-offset: -1px; + } &.active { - opacity: 1; color: var(--status-color-active-override, $active-indicator); } - .fourtyfive { - transform: rotate(-45deg); - } +} + +.fourtyfive { + transform: rotate(-45deg); } .timerNote { + width: 1.25em; color: $blue-500; - margin-right: 0.5rem; font-size: 1.5em; - display: grid; +} + +.inputGroup { + border: 1px solid transparent; + width: fit-content; + display: flex; + align-items: center; + border-radius: $component-border-radius-md; + + &.delayed { + border: 1px solid $ontime-delay-text; + } + + input { + max-width: 6.5em; + border-radius: $component-border-radius-md 0 0 $component-border-radius-md; + border: none; + padding-right: 0; + } + + button { + width: 1.5rem; + height: 2rem; + border-radius: 0 $component-border-radius-md $component-border-radius-md 0; + border: none; + } } diff --git a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx index f26796133..55accc706 100644 --- a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx +++ b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx @@ -1,14 +1,13 @@ import { memo } from 'react'; import { IoAlertCircleOutline, IoLink, IoLockClosed, IoLockOpenOutline, IoUnlink } from 'react-icons/io5'; -import { InputRightElement, Tooltip } from '@chakra-ui/react'; import { TimeField, TimeStrategy } from 'ontime-types'; import { dayInMs } from 'ontime-utils'; import * as Editor from '../../../common/components/editor-utils/EditorUtils'; -import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton'; +import TimeInput from '../../../common/components/input/time-input/TimeInput'; +import Tooltip from '../../../common/components/tooltip/Tooltip'; import { useEntryActions } from '../../../common/hooks/useEntryAction'; import { cx } from '../../../common/utils/styleUtils'; -import { tooltipDelayFast, tooltipDelayMid } from '../../../ontimeConfig'; import style from './TimeInputFlow.module.scss'; @@ -25,8 +24,17 @@ interface EventBlockTimerProps { } export default memo(TimeInputFlow); -function TimeInputFlow(props: EventBlockTimerProps) { - const { eventId, countToEnd, timeStart, timeEnd, duration, timeStrategy, linkStart, delay, showLabels } = props; +function TimeInputFlow({ + eventId, + countToEnd, + timeStart, + timeEnd, + duration, + timeStrategy, + linkStart, + delay, + showLabels, +}: EventBlockTimerProps) { const { updateEntry, updateTimer } = useEntryActions(); // In sync with EventEditorTimes @@ -52,86 +60,80 @@ function TimeInputFlow(props: EventBlockTimerProps) { } const hasDelay = delay !== 0; - const isLockedEnd = timeStrategy === TimeStrategy.LockEnd; const isLockedDuration = timeStrategy === TimeStrategy.LockDuration; - const activeStart = cx([style.timeAction, linkStart && style.active]); - const activeEnd = cx([style.timeAction, isLockedEnd && style.active]); - const activeDuration = cx([style.timeAction, isLockedDuration && style.active]); - return ( <>
{showLabels && Start time} - - name='timeStart' - submitHandler={handleSubmit} - time={timeStart} - hasDelay={hasDelay} - placeholder='Start' - disabled={linkStart} - > - - handleLink(!linkStart)}> - S - {linkStart ? : } - +
+ + handleLink(!linkStart)} + > + {linkStart ? : } - +
{showLabels && End time} - - name='timeEnd' - submitHandler={handleSubmit} - time={timeEnd} - hasDelay={hasDelay} - disabled={isLockedDuration} - placeholder='End' - > - - handleChangeStrategy(TimeStrategy.LockEnd)} - data-testid='lock__end' - > - E - {isLockedEnd ? : } - +
+ + handleChangeStrategy(TimeStrategy.LockEnd)} + data-testid='lock__end' + > + {isLockedEnd ? : } - +
{showLabels && Duration} - - name='duration' - submitHandler={handleSubmit} - time={duration} - disabled={isLockedEnd} - placeholder='Duration' - > - - handleChangeStrategy(TimeStrategy.LockDuration)} - data-testid='lock__duration' - > - D - {isLockedDuration ? : } - +
+ + handleChangeStrategy(TimeStrategy.LockDuration)} + data-testid='lock__duration' + > + {isLockedDuration ? : } - +
{warnings.length > 0 && ( -
- - - -
+ }> + + )} ); diff --git a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx index 53d8b619c..d1ceea187 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx +++ b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx @@ -6,7 +6,7 @@ import { OntimeEntry, TimeField } from 'ontime-types'; import { useEntryActions } from '../../../common/hooks/useEntryAction'; import { useFollowSelected } from '../../../common/hooks/useFollowComponent'; -import { AppMode,sessionKeys } from '../../../ontimeConfig'; +import { AppMode, sessionKeys } from '../../../ontimeConfig'; import { usePersistedCuesheetOptions } from '../cuesheet.options'; import CuesheetBody from './cuesheet-table-elements/CuesheetBody'; diff --git a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.module.scss b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.module.scss index deefcf803..815458b0d 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.module.scss +++ b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.module.scss @@ -1,7 +1,6 @@ @import "../CuesheetTable.module.scss"; .eventRow { - vertical-align: top; background: color-mix(in srgb, transparent 92%, var(--user-bg, $gray-500) 8%); border-left: 4px solid var(--user-bg, $gray-500); diff --git a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.tsx b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.tsx index 796bcce9d..0c366ac9c 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.tsx +++ b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EventRow.tsx @@ -49,7 +49,8 @@ export default function EventRow({ const [cuesheetMode] = useSessionStorage({ key: sessionKeys.cuesheetMode, defaultValue: AppMode.Edit, - }); const ownRef = useRef(null); + }); + const ownRef = useRef(null); const isVisible = useVisibleRowsStore((state) => state.visibleRows.has(rowId)); diff --git a/apps/client/src/views/editor/welcome/Welcome.module.scss b/apps/client/src/views/editor/welcome/Welcome.module.scss index efdee592e..a71781fd0 100644 --- a/apps/client/src/views/editor/welcome/Welcome.module.scss +++ b/apps/client/src/views/editor/welcome/Welcome.module.scss @@ -13,7 +13,8 @@ .about { @extend .column; - font-size: calc(1rem - 2px); + font-size: calc(1rem - 3px); + padding-inline: 0.25rem; } .inline { diff --git a/apps/client/src/views/editor/welcome/Welcome.tsx b/apps/client/src/views/editor/welcome/Welcome.tsx index 55474b032..26e79ed09 100644 --- a/apps/client/src/views/editor/welcome/Welcome.tsx +++ b/apps/client/src/views/editor/welcome/Welcome.tsx @@ -24,17 +24,12 @@ interface WelcomeProps { export default function Welcome({ onClose }: WelcomeProps) { const navigate = useNavigate(); - /** handle cleanup actions before request closing the modal */ - const handleClose = () => { - onClose(); - }; - /** handle loading a selected project */ const handleLoadProject = async (filename: string) => { try { await loadProject(filename); await invalidateAllCaches(); - handleClose(); + onClose(); } catch (_error) { /** no error handling for now */ } @@ -45,7 +40,7 @@ export default function Welcome({ onClose }: WelcomeProps) { try { await loadDemo(); await invalidateAllCaches(); - handleClose(); + onClose(); } catch (_error) { /** no error handling for now */ } @@ -54,13 +49,13 @@ export default function Welcome({ onClose }: WelcomeProps) { /** handle redirect to create modal */ const handleCallCreate = () => { navigate('/editor?settings=project__create'); - handleClose(); + onClose(); }; return ( onClose()} showBackdrop bodyElements={
@@ -74,7 +69,7 @@ export default function Welcome({ onClose }: WelcomeProps) {
Welcome to Ontime - + onClose()}>
@@ -87,7 +82,7 @@ export default function Welcome({ onClose }: WelcomeProps) { Last Used - + onClose()} />
@@ -97,7 +92,7 @@ export default function Welcome({ onClose }: WelcomeProps) {
- + onClose()} /> diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index 3b26ff103..6954ac017 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -35,13 +35,14 @@ import { dispatchFromAdapter } from '../api-integration/integration.controller.j import { generateId } from 'ontime-utils'; import { authenticateSocket } from '../middleware/authenticate.js'; +type ClientId = string; let instance: SocketServer | null = null; class SocketServer implements IAdapter { private readonly MAX_PAYLOAD = 1024 * 256; // 256Kb private wss: WebSocketServer | null; - private readonly clients: Map; + private readonly clients: Map; private lastConnection: Date | null = null; private shouldShowWelcome = true; @@ -52,7 +53,7 @@ class SocketServer implements IAdapter { // eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton instance = this; - this.clients = new Map(); + this.clients = new Map(); this.wss = null; } @@ -165,7 +166,7 @@ class SocketServer implements IAdapter { }; } - private getOrCreateClient(clientId: string): Client { + private getOrCreateClient(clientId: ClientId): Client { if (!this.clients.has(clientId)) { this.clients.set(clientId, { type: 'unknown', @@ -183,11 +184,11 @@ class SocketServer implements IAdapter { this.sendAsJson(MessageTag.ClientList, payload); } - public getClientList(): string[] { + public getClientList(): ClientId[] { return Array.from(this.clients.keys()); } - public renameClient(target: string, name: string) { + public renameClient(target: ClientId, name: string) { const previousData = this.clients.get(target); if (!previousData) { throw new Error(`Client "${target}" not found`); @@ -198,7 +199,7 @@ class SocketServer implements IAdapter { this.sendClientList(); } - public redirectClient(target: string, path: string) { + public redirectClient(target: ClientId, path: string) { const previousData = this.clients.get(target); if (!previousData) { throw new Error(`Client "${target}" not found`); @@ -206,7 +207,7 @@ class SocketServer implements IAdapter { this.sendAsJson(MessageTag.ClientRedirect, { target, path }); } - public identifyClient(target: string, identify: boolean) { + public identifyClient(target: ClientId, identify: boolean) { const previousData = this.clients.get(target); if (!previousData) { throw new Error(`Client "${target}" not found`); diff --git a/apps/server/src/services/sheet-service/SheetService.ts b/apps/server/src/services/sheet-service/SheetService.ts index 3d1981452..c552addb4 100644 --- a/apps/server/src/services/sheet-service/SheetService.ts +++ b/apps/server/src/services/sheet-service/SheetService.ts @@ -15,10 +15,10 @@ import { parseRundowns } from '../../api-data/rundown/rundown.parser.js'; import { getCurrentRundown, getProjectCustomFields } from '../../api-data/rundown/rundown.dao.js'; import { parseExcel } from '../../api-data/excel/excel.parser.js'; import { parseCustomFields } from '../../api-data/custom-fields/customFields.parser.js'; +import { consoleSubdued } from '../../utils/console.js'; import { cellRequestFromEvent, type ClientSecret, getA1Notation, isClientSecret } from './sheetUtils.js'; import { catchCommonImportXlsxError } from './googleApi.utils.js'; -import { consoleError, consoleSubdued } from '../../utils/console.js'; const sheetScope = 'https://www.googleapis.com/auth/spreadsheets'; const codesUrl = 'https://oauth2.googleapis.com/device/code'; diff --git a/e2e/tests/features/208-auxtimer.spec.ts b/e2e/tests/features/208-auxtimer.spec.ts index a54c8fd4e..2654a5550 100644 --- a/e2e/tests/features/208-auxtimer.spec.ts +++ b/e2e/tests/features/208-auxtimer.spec.ts @@ -7,13 +7,13 @@ test('Aux timer buttons', async ({ page }) => { await page.getByTestId('time-input-aux1').press('Enter'); await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:56'); await page.getByTestId('aux-timer-start-1').click(); - await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:53', { timeout: 4000 }); + await expect(page.getByTestId('time-label-aux1')).toHaveText('12:34:53', { timeout: 4000 }); await page.getByTestId('aux-timer-pause-1').click(); - await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:53'); + await expect(page.getByTestId('time-label-aux1')).toHaveText('12:34:53'); await page.getByTestId('aux-timer-stop-1').click(); await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:56'); await page.getByTestId('aux-timer-direction-1').click(); await page.getByTestId('aux-timer-start-1').click(); - await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:59', { timeout: 4000 }); + await expect(page.getByTestId('time-label-aux1')).toHaveText('12:34:59', { timeout: 4000 }); await page.getByTestId('aux-timer-stop-1').click(); }); diff --git a/e2e/tests/features/209-rundown-shortcuts.spec.ts b/e2e/tests/features/209-rundown-shortcuts.spec.ts index cc08e2b76..ba996c496 100644 --- a/e2e/tests/features/209-rundown-shortcuts.spec.ts +++ b/e2e/tests/features/209-rundown-shortcuts.spec.ts @@ -19,9 +19,9 @@ test('Copy-paste', async ({ page }) => { await page.getByTestId('block__title').press('Enter'); // copy paste below - await page.locator('div').filter({ hasText: /^4$/ }).click(); - await page.locator('div').filter({ hasText: /^4$/ }).press('Control+c'); - await page.locator('div').filter({ hasText: /^4$/ }).press('Control+v'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).click(); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).press('Control+c'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).press('Control+v'); // assert await expect(page.getByTestId('entry-2')).toBeVisible(); @@ -29,9 +29,9 @@ test('Copy-paste', async ({ page }) => { await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5'); // copy paste above - await page.locator('div').filter({ hasText: /^5$/ }).click(); - await page.locator('div').filter({ hasText: /^5$/ }).press('Control+c'); - await page.locator('div').filter({ hasText: /^5$/ }).press('Control+Shift+v'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '5' }).click(); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '5' }).press('Control+c'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '5' }).press('Control+Shift+v'); // assert await expect(page.getByTestId('entry-2')).toBeVisible(); @@ -63,7 +63,7 @@ test('Move', async ({ page }) => { // copy move up await page.getByTestId('entry-3').getByTestId('rundown-event').getByText('3').click(); await page.getByTestId('entry-3').getByTestId('rundown-event').filter({ hasText: '3' }).press('Alt+Control+ArrowUp'); - await page.getByTestId('entry-2').locator('div').filter({ hasText: /^3$/ }).press('Alt+Control+ArrowUp'); + await page.getByTestId('entry-3').getByTestId('rundown-event').filter({ hasText: '3' }).press('Alt+Control+ArrowUp'); await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toContainText('3'); }); diff --git a/e2e/tests/features/211-report-data.spec.ts b/e2e/tests/features/211-report-data.spec.ts index b46180748..5fbddae64 100644 --- a/e2e/tests/features/211-report-data.spec.ts +++ b/e2e/tests/features/211-report-data.spec.ts @@ -28,7 +28,7 @@ test('show warning when event starts next day midnight', async ({ page }) => { await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Event' }).nth(4).click(); - await page.getByTestId('entry-2').getByText('E').click(); + await page.getByTestId('entry-2').getByTestId('lock__end').click(); await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').click(); await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').fill('0'); await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').press('Enter'); diff --git a/e2e/tests/features/212-time-until.spec.ts b/e2e/tests/features/212-time-until.spec.ts index a40aec894..daf296408 100644 --- a/e2e/tests/features/212-time-until.spec.ts +++ b/e2e/tests/features/212-time-until.spec.ts @@ -7,7 +7,7 @@ test('time until absolute', async ({ page }) => { await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Create Event' }).click(); - await page.getByRole('button', { name: 'Event' }).nth(4).click(); + await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); diff --git a/e2e/tests/features/301-spreadsheet-import.spec.ts b/e2e/tests/features/301-spreadsheet-import.spec.ts index dc7cade75..742b00913 100644 --- a/e2e/tests/features/301-spreadsheet-import.spec.ts +++ b/e2e/tests/features/301-spreadsheet-import.spec.ts @@ -24,6 +24,8 @@ test('sheet file upload', async ({ page }) => { await page.getByRole('button', { name: 'Import preview' }).click(); await page.getByRole('button', { name: 'Apply' }).click(); await page.getByRole('button', { name: 'Return' }).click(); + + await page.getByRole('button', { name: 'Close settings' }).scrollIntoViewIfNeeded(); await page.getByRole('button', { name: 'Close settings' }).click(); // asset test events