From a358ec0f176b315266246a5cfa2e60ff4852c590 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sat, 2 Aug 2025 06:49:51 +0200 Subject: [PATCH] refactor: small style tweaks refactor: consistent font size on inputs refactor: style tweaks for block spacing refactor: improve skipped styles fix: improve responsiveness in extracted rundown refactor: style tweaks to overview refactor: simplify group target duration --- .../input/time-input/NullableTimeInput.tsx | 4 +- .../components/select/Select.module.scss | 3 +- apps/client/src/common/utils/offset.ts | 4 +- .../overview/composite/TimeElements.tsx | 2 +- .../overview/composite/TimeLayout.module.scss | 11 ++-- .../overview/composite/TimeLayout.tsx | 4 +- apps/client/src/features/rundown/Rundown.tsx | 31 +++++----- .../src/features/rundown/RundownExport.tsx | 2 +- .../rundown/entry-editor/BlockEditor.tsx | 38 ++++++------ .../entry-editor/EntryEditor.module.scss | 21 ++++--- .../composite/TargetDurationInput.tsx | 45 ++++++++++++++ .../rundown-block/RundownBlock.module.scss | 4 +- ...odule.scss => RundownBlockEnd.module.scss} | 2 +- .../{BlockEnd.tsx => RundownBlockEnd.tsx} | 4 +- .../rundown-header/RundownHeaderMobile.tsx | 8 +-- .../time-input-flow/TimeInputFlow.module.scss | 58 ++----------------- .../rundown/time-input-flow/TimeInputFlow.tsx | 22 +++---- .../TimeInputGroup.module.scss | 25 ++++++++ .../time-input-flow/TimeInputGroup.tsx | 13 +++++ .../EventRow.module.scss | 5 ++ 20 files changed, 175 insertions(+), 131 deletions(-) create mode 100644 apps/client/src/features/rundown/entry-editor/composite/TargetDurationInput.tsx rename apps/client/src/features/rundown/rundown-block/{BlockEnd.module.scss => RundownBlockEnd.module.scss} (88%) rename apps/client/src/features/rundown/rundown-block/{BlockEnd.tsx => RundownBlockEnd.tsx} (86%) create mode 100644 apps/client/src/features/rundown/time-input-flow/TimeInputGroup.module.scss create mode 100644 apps/client/src/features/rundown/time-input-flow/TimeInputGroup.tsx 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 eb3c53c3b..3b87384f9 100644 --- a/apps/client/src/common/components/input/time-input/NullableTimeInput.tsx +++ b/apps/client/src/common/components/input/time-input/NullableTimeInput.tsx @@ -9,7 +9,7 @@ import style from './TimeInput.module.scss'; interface NullableTimeInputProps { id?: T; name: T; - submitHandler: (field: T, value: string) => void; + submitHandler: (field: T, value: number) => void; time?: number | null; emptyDisplay: string; placeholder?: string; @@ -71,7 +71,7 @@ export default function NullableTimeInput({ return false; } - submitHandler(name, newValue); + submitHandler(name, valueInMillis); return true; }, [name, submitHandler, time], diff --git a/apps/client/src/common/components/select/Select.module.scss b/apps/client/src/common/components/select/Select.module.scss index 06ae80952..70aa3af22 100644 --- a/apps/client/src/common/components/select/Select.module.scss +++ b/apps/client/src/common/components/select/Select.module.scss @@ -14,7 +14,6 @@ border: 1px solid transparent; padding-inline: 0.5rem; - font-size: calc(1rem - 2px); white-space: nowrap; &:hover:not([data-disabled]) { @@ -57,7 +56,7 @@ } .popup { - font-size: calc(1rem - 2px); + font-size: 1rem; background-color: $gray-1200; box-sizing: border-box; padding: 2px; diff --git a/apps/client/src/common/utils/offset.ts b/apps/client/src/common/utils/offset.ts index 3555bd0a7..532fae07e 100644 --- a/apps/client/src/common/utils/offset.ts +++ b/apps/client/src/common/utils/offset.ts @@ -19,7 +19,7 @@ export function getOffsetText(offset: MaybeNumber): string { } export function getOffsetState(offset: MaybeNumber): 'over' | 'under' | 'muted' | null { - if (offset === null) return null; - if (offset === 0) return 'muted'; + if (offset === null) return 'muted'; + if (offset === 0) return null; return offset < 0 ? 'over' : 'under'; } diff --git a/apps/client/src/features/overview/composite/TimeElements.tsx b/apps/client/src/features/overview/composite/TimeElements.tsx index 5a918302d..88a68c5e2 100644 --- a/apps/client/src/features/overview/composite/TimeElements.tsx +++ b/apps/client/src/features/overview/composite/TimeElements.tsx @@ -202,7 +202,7 @@ export function OffsetOverview() { const isPlaying = isPlaybackActive(playback); const correctedOffset = offset * -1; - const offsetState = getOffsetState(offset); + const offsetState = getOffsetState(isPlaying ? offset : null); const offsetText = getOffsetText(isPlaying ? correctedOffset : null); return ; diff --git a/apps/client/src/features/overview/composite/TimeLayout.module.scss b/apps/client/src/features/overview/composite/TimeLayout.module.scss index ddbb970ed..1c0a49895 100644 --- a/apps/client/src/features/overview/composite/TimeLayout.module.scss +++ b/apps/client/src/features/overview/composite/TimeLayout.module.scss @@ -9,14 +9,11 @@ letter-spacing: 0.5px; min-width: 5em; font-variant-numeric: tabular-nums; + color: $ui-white; &::after { content: '\200b'; } - - &.muted { - color: $muted-gray; - } } .column { @@ -32,7 +29,7 @@ gap: 0.25rem; } - &[data-state="over"] { + &[data-state='over'] { .label .over { color: $playback-over; } @@ -41,7 +38,7 @@ } } - &[data-state="under"] { + &[data-state='under'] { .label .under { color: $playback-under; } @@ -50,7 +47,7 @@ } } - &[data-state="muted"] { + &[data-state='muted'] { .clock { color: $muted-gray; } diff --git a/apps/client/src/features/overview/composite/TimeLayout.tsx b/apps/client/src/features/overview/composite/TimeLayout.tsx index 9bc8e6b76..5e501c7e8 100644 --- a/apps/client/src/features/overview/composite/TimeLayout.tsx +++ b/apps/client/src/features/overview/composite/TimeLayout.tsx @@ -13,9 +13,9 @@ interface TimeLayoutProps { export function TimeColumn({ label, value, muted, className, testId }: TimeLayoutProps) { return ( -
+
{label} - + {value}
diff --git a/apps/client/src/features/rundown/Rundown.tsx b/apps/client/src/features/rundown/Rundown.tsx index 9f8fd5b82..3f368e08d 100644 --- a/apps/client/src/features/rundown/Rundown.tsx +++ b/apps/client/src/features/rundown/Rundown.tsx @@ -42,8 +42,8 @@ import { AppMode, sessionKeys } from '../../ontimeConfig'; import QuickAddButtons from './entry-editor/quick-add-buttons/QuickAddButtons'; import QuickAddInline from './entry-editor/quick-add-cursor/QuickAddInline'; -import BlockEnd from './rundown-block/BlockEnd'; import RundownBlock from './rundown-block/RundownBlock'; +import RundownBlockEnd from './rundown-block/RundownBlockEnd'; import { canDrop, makeRundownMetadata, makeSortableList } from './rundown.utils'; import RundownEmpty from './RundownEmpty'; import { useEventSelection } from './useEventSelection'; @@ -444,20 +444,23 @@ export default function Rundown({ data }: RundownProps) { if (isBlockCollapsed) { return null; - } else { - const parentColour = (entries[parentId] as OntimeBlock | undefined)?.colour; - // if the previous element is selected, it will have its own QuickAddInline - // we use thisId instead of previousEntryId because the block end does not process - // and it does not cause the reassignment of the iteration id to the previous entry - return ( - - {isEditMode && rundownMetadata.groupEntries === 0 && ( - - )} - - - ); } + + // if the previous element is selected, it will have its own QuickAddInline + // we use thisId instead of previousEntryId because the block end does not process + // and it does not cause the reassignment of the iteration id to the previous entry + return ( + + {isEditMode && rundownMetadata.groupEntries === 0 && ( + + )} + + + ); } // we iterate through a stateful copy of order to make the dnd operations smoother diff --git a/apps/client/src/features/rundown/RundownExport.tsx b/apps/client/src/features/rundown/RundownExport.tsx index b3d9926da..b46c6d38a 100644 --- a/apps/client/src/features/rundown/RundownExport.tsx +++ b/apps/client/src/features/rundown/RundownExport.tsx @@ -37,7 +37,7 @@ function RundownExport() { > -
+
diff --git a/apps/client/src/features/rundown/entry-editor/BlockEditor.tsx b/apps/client/src/features/rundown/entry-editor/BlockEditor.tsx index 376e856ec..372b648fc 100644 --- a/apps/client/src/features/rundown/entry-editor/BlockEditor.tsx +++ b/apps/client/src/features/rundown/entry-editor/BlockEditor.tsx @@ -1,25 +1,25 @@ import { useCallback } from 'react'; -import { OntimeBlock } from 'ontime-types'; -import { millisToString, parseUserTime } from 'ontime-utils'; +import { MaybeNumber, OntimeBlock } from 'ontime-types'; +import { millisToString } from 'ontime-utils'; import * as Editor from '../../../common/components/editor-utils/EditorUtils'; import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect'; -import NullableTimeInput from '../../../common/components/input/time-input/NullableTimeInput'; import AppLink from '../../../common/components/link/app-link/AppLink'; import { useEntryActions } from '../../../common/hooks/useEntryAction'; import useCustomFields from '../../../common/hooks-query/useCustomFields'; import { getOffsetState } from '../../../common/utils/offset'; -import { enDash, timerPlaceholder } from '../../../common/utils/styleUtils'; +import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils'; import TextLikeInput from '../../../views/cuesheet/cuesheet-table/cuesheet-table-elements/TextLikeInput'; import EntryEditorCustomFields from './composite/EventEditorCustomFields'; import EventTextArea from './composite/EventTextArea'; import EntryEditorTextInput from './composite/EventTextInput'; +import TargetDurationInput from './composite/TargetDurationInput'; import style from './EntryEditor.module.scss'; // title + colour + custom field labels -export type BlockEditorUpdateTextFields = 'targetDuration' | 'title' | 'colour' | string; +export type BlockEditorUpdateTextFields = 'title' | 'colour' | string; export type BlockEditorUpdateMaybeNumberFields = 'targetDuration'; interface BlockEditorProps { @@ -31,7 +31,7 @@ export default function BlockEditor({ block }: BlockEditorProps) { const { updateEntry } = useEntryActions(); const handleSubmit = useCallback( - (field: BlockEditorUpdateTextFields | BlockEditorUpdateMaybeNumberFields, value: string | boolean) => { + (field: BlockEditorUpdateTextFields | BlockEditorUpdateMaybeNumberFields, value: string | MaybeNumber) => { // Handle custom fields if (typeof field === 'string' && field.startsWith('custom-')) { const fieldLabel = field.split('custom-')[1]; @@ -40,11 +40,7 @@ export default function BlockEditor({ block }: BlockEditorProps) { } if (field === 'targetDuration') { - if (value === '') { - return updateEntry({ id: block.id, targetDuration: null }); - } - - return updateEntry({ id: block.id, targetDuration: parseUserTime(value as string) }); + return updateEntry({ id: block.id, targetDuration: value as MaybeNumber }); } // all other strings are text fields @@ -85,22 +81,22 @@ export default function BlockEditor({ block }: BlockEditorProps) {
-
- Target duration - -
Plan offset - + {planOffset !== null && planOffset > 0 ? '+' : ''} {millisToString(planOffset, { fallback: enDash })}
+
diff --git a/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss b/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss index 5bf009808..1bc8f43ac 100644 --- a/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss +++ b/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss @@ -94,16 +94,21 @@ /* approximating the style of a disabled input */ .textLikeInput { - background-color: rgba($gray-1200, 0.4); - font-weight: 400; - color: $gray-200; - border: 1px solid transparent; - - justify-content: center; - + background: transparent; + justify-content: start; width: 7.5em; + border: 1px solid transparent; + border-bottom: 1px solid $gray-1100; &:hover { - background-color: rgba($gray-1200, 0.4); + background: transparent; } } + +.inactive { + color: $muted-gray; +} + +.active { + color: $active-indicator; +} diff --git a/apps/client/src/features/rundown/entry-editor/composite/TargetDurationInput.tsx b/apps/client/src/features/rundown/entry-editor/composite/TargetDurationInput.tsx new file mode 100644 index 000000000..888f8921b --- /dev/null +++ b/apps/client/src/features/rundown/entry-editor/composite/TargetDurationInput.tsx @@ -0,0 +1,45 @@ +import { IoLockClosed, IoLockOpenOutline } from 'react-icons/io5'; +import { MaybeNumber } from 'ontime-types'; + +import IconButton from '../../../../common/components/buttons/IconButton'; +import * as Editor from '../../../../common/components/editor-utils/EditorUtils'; +import NullableTimeInput from '../../../../common/components/input/time-input/NullableTimeInput'; +import Tooltip from '../../../../common/components/tooltip/Tooltip'; +import { cx, enDash } from '../../../../common/utils/styleUtils'; +import TimeInputGroup from '../../time-input-flow/TimeInputGroup'; + +import style from '../EntryEditor.module.scss'; + +interface TargetDurationInputProps { + duration: MaybeNumber; + targetDuration: MaybeNumber; + submitHandler: (field: 'targetDuration', value: MaybeNumber) => void; +} + +export default function TargetDurationInput({ duration, targetDuration, submitHandler }: TargetDurationInputProps) { + const isBlocked = targetDuration !== null; + + return ( +
+ Target duration + + + submitHandler('targetDuration', isBlocked ? null : duration)} + data-testid='lock__duration' + render={} + > + {isBlocked ? : } + + +
+ ); +} 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 51cad5024..6c791427e 100644 --- a/apps/client/src/features/rundown/rundown-block/RundownBlock.module.scss +++ b/apps/client/src/features/rundown/rundown-block/RundownBlock.module.scss @@ -3,7 +3,7 @@ .block { @include block-styling; - margin-block: 1rem 0.25rem; + margin-block: 0.5rem; display: grid; grid-template-columns: 2rem 1fr; grid-template-areas: 'binder header'; @@ -14,7 +14,7 @@ } &.expanded { - margin-block: 1rem 0; + margin-block: 0.5rem 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%); } diff --git a/apps/client/src/features/rundown/rundown-block/BlockEnd.module.scss b/apps/client/src/features/rundown/rundown-block/RundownBlockEnd.module.scss similarity index 88% rename from apps/client/src/features/rundown/rundown-block/BlockEnd.module.scss rename to apps/client/src/features/rundown/rundown-block/RundownBlockEnd.module.scss index fcef9bf25..b2b024222 100644 --- a/apps/client/src/features/rundown/rundown-block/BlockEnd.module.scss +++ b/apps/client/src/features/rundown/rundown-block/RundownBlockEnd.module.scss @@ -6,5 +6,5 @@ background-color: var(--user-bg, $gray-1050); border-radius: 0 0 $block-border-radius $block-border-radius; - margin-bottom: 0.25rem; + margin-bottom: 0.5rem; } diff --git a/apps/client/src/features/rundown/rundown-block/BlockEnd.tsx b/apps/client/src/features/rundown/rundown-block/RundownBlockEnd.tsx similarity index 86% rename from apps/client/src/features/rundown/rundown-block/BlockEnd.tsx rename to apps/client/src/features/rundown/rundown-block/RundownBlockEnd.tsx index c3a2ed788..09c08feea 100644 --- a/apps/client/src/features/rundown/rundown-block/BlockEnd.tsx +++ b/apps/client/src/features/rundown/rundown-block/RundownBlockEnd.tsx @@ -1,14 +1,14 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import style from './BlockEnd.module.scss'; +import style from './RundownBlockEnd.module.scss'; interface BlockEndProps { id: string; colour?: string; } -export default function BlockEnd({ id, colour }: BlockEndProps) { +export default function RundownBlockEnd({ id, colour }: BlockEndProps) { const { attributes: dragAttributes, listeners: dragListeners, diff --git a/apps/client/src/features/rundown/rundown-header/RundownHeaderMobile.tsx b/apps/client/src/features/rundown/rundown-header/RundownHeaderMobile.tsx index ea7912516..a4cc977cc 100644 --- a/apps/client/src/features/rundown/rundown-header/RundownHeaderMobile.tsx +++ b/apps/client/src/features/rundown/rundown-header/RundownHeaderMobile.tsx @@ -37,10 +37,10 @@ function RundownHeader() { return ( - } value={AppMode.Run} className={style.button}> + } value={AppMode.Run} className={style.radioButton}> Run - } value={AppMode.Edit} className={style.button}> + } value={AppMode.Edit} className={style.radioButton}> Edit @@ -48,10 +48,10 @@ function RundownHeader() { - } value={OffsetMode.Absolute} className={style.button}> + } value={OffsetMode.Absolute} className={style.radioButton}> Absolute - } value={OffsetMode.Relative} className={style.button}> + } value={OffsetMode.Relative} className={style.radioButton}> Relative 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 c27e8ce55..49713813c 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,31 +1,3 @@ -.timeAction { - background: $gray-1050; - color: $gray-500; - cursor: pointer; - 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 { - color: var(--status-color-active-override, $active-indicator); - } -} - .fourtyfive { transform: rotate(-45deg); } @@ -36,28 +8,10 @@ font-size: 1.5em; } -.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; - } +.inactive { + color: $muted-gray; +} + +.active { + color: $active-indicator; } 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 55accc706..a628a369d 100644 --- a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx +++ b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx @@ -3,11 +3,13 @@ import { IoAlertCircleOutline, IoLink, IoLockClosed, IoLockOpenOutline, IoUnlink import { TimeField, TimeStrategy } from 'ontime-types'; import { dayInMs } from 'ontime-utils'; +import IconButton from '../../../common/components/buttons/IconButton'; import * as Editor from '../../../common/components/editor-utils/EditorUtils'; 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 TimeInputGroup from './TimeInputGroup'; import style from './TimeInputFlow.module.scss'; @@ -67,7 +69,7 @@ function TimeInputFlow({ <>
{showLabels && Start time} -
+ handleLink(!linkStart)} + render={} > {linkStart ? : } -
+
{showLabels && End time} -
+ } onClick={() => handleChangeStrategy(TimeStrategy.LockEnd)} data-testid='lock__end' > {isLockedEnd ? : } -
+
{showLabels && Duration} -
+ } onClick={() => handleChangeStrategy(TimeStrategy.LockDuration)} data-testid='lock__duration' > {isLockedDuration ? : } -
+
{warnings.length > 0 && ( diff --git a/apps/client/src/features/rundown/time-input-flow/TimeInputGroup.module.scss b/apps/client/src/features/rundown/time-input-flow/TimeInputGroup.module.scss new file mode 100644 index 000000000..316e10b01 --- /dev/null +++ b/apps/client/src/features/rundown/time-input-flow/TimeInputGroup.module.scss @@ -0,0 +1,25 @@ +.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/TimeInputGroup.tsx b/apps/client/src/features/rundown/time-input-flow/TimeInputGroup.tsx new file mode 100644 index 000000000..772ab6f21 --- /dev/null +++ b/apps/client/src/features/rundown/time-input-flow/TimeInputGroup.tsx @@ -0,0 +1,13 @@ +import { PropsWithChildren } from 'react'; + +import { cx } from '../../../common/utils/styleUtils'; + +import style from './TimeInputGroup.module.scss'; + +interface TimeInputGroupProps { + hasDelay?: boolean; +} + +export default function TimeInputGroup({ hasDelay, children }: PropsWithChildren) { + return
{children}
; +} 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 8b565f3ae..60a13271b 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 @@ -18,6 +18,11 @@ &.skip { position: relative; + .indexColumn { + text-decoration: line-through; + opacity: $opacity-disabled; + } + &::after { content: ''; position: absolute;