mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 12:23:51 +00:00
V2 beta1 (#326)
* style: labels on added time * style: remove mentions of PiP * refactor: unify usage of ms for timers * refactor: create events with 0 duration * style: several small tweaks * refactor: keep block when applying delays * feat: blocks have titles * style: improvements in time entry warnings * style: override progress bar styles * style: prevent overflow * feat: show character count in editor * refactor: provide initial payload * refactor: lower test boundary * refactor: get colour from swatches * style: rename title block * chore: version bump
This commit is contained in:
@@ -4,7 +4,7 @@ import { millisToString } from 'ontime-utils';
|
||||
|
||||
import TimerDisplay from '../../../common/components/timer-display/TimerDisplay';
|
||||
import { setPlayback, useTimer } from '../../../common/hooks/useSocket';
|
||||
import { millisToMinutes } from '../../../common/utils/dateConfig';
|
||||
import { millisToMinutes, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import TapButton from './TapButton';
|
||||
@@ -30,7 +30,30 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
|
||||
const hasAddedTime = Boolean(timer.addedTime);
|
||||
|
||||
const rollLabel = isRolling ? 'Roll mode active' : '';
|
||||
const addedTimeLabel = hasAddedTime ? `Added ${millisToMinutes(timer.addedTime)} minutes` : '';
|
||||
|
||||
const resolveAddedTimeLabel = () => {
|
||||
function resolveClosestUnit(ms: number) {
|
||||
if (ms < 6000) {
|
||||
return `${millisToSeconds(ms)} seconds`;
|
||||
} else if (ms < 12000) {
|
||||
return `1 minute`;
|
||||
} else {
|
||||
return `${millisToMinutes(ms)} minutes`;
|
||||
}
|
||||
}
|
||||
|
||||
if (timer.addedTime > 0) {
|
||||
return `Added ${resolveClosestUnit(timer.addedTime)}`;
|
||||
}
|
||||
|
||||
if (timer.addedTime < 0) {
|
||||
return `Removed ${resolveClosestUnit(timer.addedTime)}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const addedTimeLabel = resolveAddedTimeLabel();
|
||||
|
||||
return (
|
||||
<div className={style.timeContainer}>
|
||||
|
||||
@@ -64,10 +64,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.inputLabel {
|
||||
@mixin input-label() {
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
color: $label-gray;
|
||||
}
|
||||
|
||||
.countedInput {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@include input-label;
|
||||
}
|
||||
|
||||
.inputLabel {
|
||||
display: block;
|
||||
@include input-label;
|
||||
|
||||
.delayLabel {
|
||||
color: $ontime-delay-text;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, Select, Switch } from '@chakra-ui/react';
|
||||
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
||||
import { Select, Switch } from '@chakra-ui/react';
|
||||
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import CopyTag from '../../common/components/copy-tag/CopyTag';
|
||||
import ColourInput from '../../common/components/input/colour-input/ColourInput';
|
||||
import TextInput from '../../common/components/input/text-input/TextInput';
|
||||
import SwatchSelect from '../../common/components/input/colour-input/SwatchSelect';
|
||||
import TimeInput from '../../common/components/input/time-input/TimeInput';
|
||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||
import useRundown from '../../common/hooks-query/useRundown';
|
||||
@@ -16,6 +14,9 @@ import { millisToMinutes } from '../../common/utils/dateConfig';
|
||||
import getDelayTo from '../../common/utils/getDelayTo';
|
||||
import { calculateDuration, TimeEntryField, validateEntry } from '../../common/utils/timesManager';
|
||||
|
||||
import CountedTextArea from './composite/CountedTextArea';
|
||||
import CountedTextInput from './composite/CountedTextInput';
|
||||
|
||||
import style from './EventEditor.module.scss';
|
||||
|
||||
export type EventEditorSubmitActions = keyof OntimeEvent | 'durationOverride';
|
||||
@@ -24,10 +25,11 @@ export type EventEditorSubmitActions = keyof OntimeEvent | 'durationOverride';
|
||||
export default function EventEditor() {
|
||||
const { openId } = useEventEditorStore();
|
||||
const { data } = useRundown();
|
||||
const { emitWarning, emitError } = useEmitLog();
|
||||
const { emitError } = useEmitLog();
|
||||
const { updateEvent } = useEventAction();
|
||||
const [event, setEvent] = useState<OntimeEvent | null>(null);
|
||||
const [delay, setDelay] = useState(0);
|
||||
const [warning, setWarnings] = useState({ start: '', end: '', duration: '' });
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || !openId) {
|
||||
@@ -85,16 +87,14 @@ export default function EventEditor() {
|
||||
|
||||
const timerValidationHandler = useCallback(
|
||||
(entry: TimeEntryField, val: number) => {
|
||||
if (!event) {
|
||||
return;
|
||||
if (!event?.timeStart) {
|
||||
return true;
|
||||
}
|
||||
const valid = validateEntry(entry, val, event.timeStart, event.timeEnd);
|
||||
if (!valid.value) {
|
||||
emitWarning(`Time Input Warning: ${valid.catch}`);
|
||||
}
|
||||
setWarnings((prev) => ({ ...prev, ...valid.warnings }));
|
||||
return valid.value;
|
||||
},
|
||||
[event, emitWarning],
|
||||
[event?.timeStart, event?.timeEnd],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@@ -145,6 +145,7 @@ export default function EventEditor() {
|
||||
time={event.timeStart}
|
||||
delay={delay}
|
||||
placeholder='Start'
|
||||
warning={warning.start}
|
||||
/>
|
||||
<label className={style.inputLabel}>
|
||||
End time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
|
||||
@@ -157,6 +158,7 @@ export default function EventEditor() {
|
||||
time={event.timeEnd}
|
||||
delay={delay}
|
||||
placeholder='End'
|
||||
warning={warning.end}
|
||||
/>
|
||||
<label className={style.inputLabel}>Duration</label>
|
||||
<TimeInput
|
||||
@@ -165,6 +167,7 @@ export default function EventEditor() {
|
||||
validationHandler={timerValidationHandler}
|
||||
time={event.duration}
|
||||
placeholder='Duration'
|
||||
warning={warning.duration}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.timeSettings}>
|
||||
@@ -202,40 +205,28 @@ export default function EventEditor() {
|
||||
</div>
|
||||
<div className={style.titles}>
|
||||
<div className={style.left}>
|
||||
<div className={style.column}>
|
||||
<label className={style.inputLabel}>Title</label>
|
||||
<TextInput field='title' initialText={event.title} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
<div className={style.column}>
|
||||
<label className={style.inputLabel}>Presenter</label>
|
||||
<TextInput field='presenter' initialText={event.presenter} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
<div className={style.column}>
|
||||
<label className={style.inputLabel}>Subtitle</label>
|
||||
<TextInput field='subtitle' initialText={event.subtitle} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
<CountedTextInput field='title' label='Title' initialValue={event.title} submitHandler={handleSubmit} />
|
||||
<CountedTextInput
|
||||
field='presenter'
|
||||
label='Presenter'
|
||||
initialValue={event.presenter}
|
||||
submitHandler={handleSubmit}
|
||||
/>
|
||||
<CountedTextInput
|
||||
field='subtitle'
|
||||
label='Subtitle'
|
||||
initialValue={event.subtitle}
|
||||
submitHandler={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.right}>
|
||||
<div className={style.column}>
|
||||
<label className={style.inputLabel}>Colour</label>
|
||||
<div className={style.inline}>
|
||||
<ColourInput name='colour' value={event?.colour} handleChange={handleSubmit} />
|
||||
<Button leftIcon={<IoBan />} onClick={() => handleSubmit('colour', '')} variant='ontime-subtle' size='sm'>
|
||||
Clear colour
|
||||
</Button>
|
||||
<SwatchSelect name='colour' value={event.colour} handleChange={handleSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${style.column} ${style.fullHeight}`}>
|
||||
<label className={style.inputLabel}>Note</label>
|
||||
<TextInput
|
||||
field='note'
|
||||
initialText={event.note}
|
||||
submitHandler={handleSubmit}
|
||||
isTextArea
|
||||
isFullHeight
|
||||
resize='none'
|
||||
/>
|
||||
</div>
|
||||
<CountedTextArea field='note' label='Note' initialValue={event.note} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Textarea } from '@chakra-ui/react';
|
||||
|
||||
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
||||
import { EventEditorSubmitActions } from '../EventEditor';
|
||||
|
||||
import style from '../EventEditor.module.scss';
|
||||
|
||||
interface CountedTextAreaProps {
|
||||
field: EventEditorSubmitActions;
|
||||
label: string;
|
||||
initialValue: string;
|
||||
submitHandler: (field: EventEditorSubmitActions, value: string) => void;
|
||||
}
|
||||
|
||||
export default function CountedTextArea(props: CountedTextAreaProps) {
|
||||
const { field, label, initialValue, submitHandler } = props;
|
||||
|
||||
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
|
||||
|
||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback);
|
||||
|
||||
return (
|
||||
<div className={`${style.column} ${style.fullHeight}`}>
|
||||
<div className={style.countedInput}>
|
||||
<label className={style.inputLabel}>{label}</label>
|
||||
<span className={style.charCount}>{`${value.length} characters`}</span>
|
||||
</div>
|
||||
<Textarea
|
||||
size='sm'
|
||||
resize='none'
|
||||
variant='ontime-filled'
|
||||
style={{ height: '100%' }}
|
||||
data-testid='input-textarea'
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
|
||||
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
||||
import { EventEditorSubmitActions } from '../EventEditor';
|
||||
|
||||
import style from '../EventEditor.module.scss';
|
||||
|
||||
interface CountedTextInputProps {
|
||||
field: EventEditorSubmitActions;
|
||||
label: string;
|
||||
initialValue: string;
|
||||
submitHandler: (field: EventEditorSubmitActions, value: string) => void;
|
||||
}
|
||||
|
||||
export default function CountedTextInput(props: CountedTextInputProps) {
|
||||
const { field, label, initialValue, submitHandler } = props;
|
||||
|
||||
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
|
||||
|
||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback, {
|
||||
submitOnEnter: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={style.column}>
|
||||
<div className={style.countedInput}>
|
||||
<label className={style.inputLabel}>{label}</label>
|
||||
<span className={style.charCount}>{`${value.length} characters`}</span>
|
||||
</div>
|
||||
<Input
|
||||
size='sm'
|
||||
variant='ontime-filled'
|
||||
data-testid='input-textfield'
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -42,14 +42,14 @@ export default function ViewsSettingsModal() {
|
||||
try {
|
||||
await postView(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error view settings: ${error}`)
|
||||
} finally{
|
||||
emitError(`Error view settings: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
setSubmitting(false);
|
||||
},
|
||||
[emitError, formData, refetch]
|
||||
[emitError, formData, refetch],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,7 @@ export default function ViewsSettingsModal() {
|
||||
setFormData(temp);
|
||||
setChanged(true);
|
||||
},
|
||||
[formData]
|
||||
[formData],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -89,18 +89,30 @@ export default function ViewsSettingsModal() {
|
||||
<IoInformationCircleOutline color='#2b6cb0' fontSize='2em' />
|
||||
CSS Style Overrides
|
||||
</span>
|
||||
This feature allows user defined CSS to override the application stylesheets as a way to
|
||||
customise viewers appearance.
|
||||
This feature allows user defined CSS to override the application stylesheets as a way to customise viewers
|
||||
appearance.
|
||||
<br />
|
||||
Currently the feature affects the following views
|
||||
<br />
|
||||
Currently the feature affects the following views<br />
|
||||
<ul className={style.featureList}>
|
||||
<li><IoCheckmarkSharp /> Stage timer</li>
|
||||
<li><IoCheckmarkSharp /> Clock</li>
|
||||
<li><IoCheckmarkSharp /> Minimal timer</li>
|
||||
<li><IoCheckmarkSharp /> Backstage screen</li>
|
||||
<li><IoCheckmarkSharp /> Public screen</li>
|
||||
<li><IoCheckmarkSharp /> Picture in Picture</li>
|
||||
<li><IoCheckmarkSharp /> Countdown</li>
|
||||
<li>
|
||||
<IoCheckmarkSharp /> Stage timer
|
||||
</li>
|
||||
<li>
|
||||
<IoCheckmarkSharp /> Clock
|
||||
</li>
|
||||
<li>
|
||||
<IoCheckmarkSharp /> Minimal timer
|
||||
</li>
|
||||
<li>
|
||||
<IoCheckmarkSharp /> Backstage screen
|
||||
</li>
|
||||
<li>
|
||||
<IoCheckmarkSharp /> Public screen
|
||||
</li>
|
||||
<li>
|
||||
<IoCheckmarkSharp /> Countdown
|
||||
</li>
|
||||
</ul>
|
||||
Read more about it in the documentation{' '}
|
||||
<a
|
||||
@@ -123,19 +135,12 @@ export default function ViewsSettingsModal() {
|
||||
</FormLabel>
|
||||
<EnableBtn
|
||||
active={formData.overrideStyles}
|
||||
text={
|
||||
formData.overrideStyles ? 'Style Override Enabled' : 'Style Override Disabled'
|
||||
}
|
||||
text={formData.overrideStyles ? 'Style Override Enabled' : 'Style Override Disabled'}
|
||||
actionHandler={() => handleChange('overrideStyles', !formData.overrideStyles)}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<SubmitContainer
|
||||
revert={revert}
|
||||
submitting={submitting}
|
||||
changed={changed}
|
||||
status={status}
|
||||
/>
|
||||
<SubmitContainer revert={revert} submitting={submitting} changed={changed} status={status} />
|
||||
</div>
|
||||
</form>
|
||||
</ModalBody>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
box-sizing: content-box;
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
height: $secondary-block-height;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||
import { OntimeBlock, OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||
import BlockActionMenu from '../event-block/composite/BlockActionMenu';
|
||||
import { EventItemActions } from '../RundownEntry';
|
||||
|
||||
@@ -57,6 +58,7 @@ export default function BlockBlock(props: BlockBlockProps) {
|
||||
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
||||
<IoReorderTwo />
|
||||
</span>
|
||||
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Block title' />
|
||||
<BlockActionMenu className={style.actionOverlay} showAdd showDelay enableDelete actionHandler={actionHandler} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/react';
|
||||
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
import style from './TitleEditor.module.scss';
|
||||
|
||||
interface TitleEditorProps {
|
||||
title: string;
|
||||
eventId: string;
|
||||
placeholder: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function EditableBlockTitle(props: TitleEditorProps) {
|
||||
const { title, eventId, placeholder, className } = props;
|
||||
const [blockTitle, setBlockTitle] = useState<string>(title || '');
|
||||
const { updateEvent } = useEventAction();
|
||||
|
||||
useEffect(() => {
|
||||
setBlockTitle(title);
|
||||
}, [title]);
|
||||
|
||||
const handleTitle = useCallback(
|
||||
(text: string) => {
|
||||
if (text === title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanVal = text.trim();
|
||||
setBlockTitle(cleanVal);
|
||||
|
||||
updateEvent({ id: eventId, title: cleanVal });
|
||||
},
|
||||
[title, updateEvent, eventId],
|
||||
);
|
||||
|
||||
const classes = cx([className, style.eventTitle, !blockTitle ? style.noTitle : null]);
|
||||
return (
|
||||
<Editable
|
||||
variant='ontime'
|
||||
value={blockTitle}
|
||||
className={classes}
|
||||
placeholder={placeholder}
|
||||
onChange={(value) => setBlockTitle(value)}
|
||||
onSubmit={(value) => handleTitle(value)}
|
||||
>
|
||||
<EditablePreview className={style.preview} />
|
||||
<EditableInput />
|
||||
</Editable>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
@use '../../../theme/v2Styles' as *;
|
||||
|
||||
.titleEditor {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.noTitle {
|
||||
.preview {
|
||||
opacity: $opacity-disabled;
|
||||
}
|
||||
}
|
||||
@@ -87,18 +87,6 @@
|
||||
|
||||
.eventTitle {
|
||||
grid-area: title;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.noTitle {
|
||||
.preview {
|
||||
opacity: $opacity-disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eventActions {
|
||||
@@ -158,6 +146,7 @@
|
||||
gap: 8px;
|
||||
|
||||
.tag {
|
||||
padding-top: 1px;
|
||||
font-size: 0.55em;
|
||||
color: $active-indicator;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoCaretDownCircle } from '@react-icons/all-files/io5/IoCaretDownCircle';
|
||||
import { IoCaretUpCircle } from '@react-icons/all-files/io5/IoCaretUpCircle';
|
||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||
@@ -17,10 +17,10 @@ import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
||||
import { EndAction, Playback, TimerType } from 'ontime-types';
|
||||
|
||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import { setEventPlayback } from '../../../common/hooks/useSocket';
|
||||
import { useEventEditorStore } from '../../../common/stores/eventEditor';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||
import { EventItemActions } from '../RundownEntry';
|
||||
|
||||
import BlockActionMenu from './composite/BlockActionMenu';
|
||||
@@ -78,38 +78,14 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
actionHandler,
|
||||
} = props;
|
||||
|
||||
const { updateEvent } = useEventAction();
|
||||
|
||||
const [blockTitle, setBlockTitle] = useState<string>(title || '');
|
||||
const [renderInner, setRenderInner] = useState(false);
|
||||
const setOpenEvent = useEventEditorStore((state) => state.setOpenEvent);
|
||||
const removeOpenEvent = useEventEditorStore((state) => state.removeOpenEvent);
|
||||
|
||||
// Todo: could I re-render the item without causing a state change here?
|
||||
// ?? use refs instead?
|
||||
|
||||
useEffect(() => {
|
||||
setRenderInner(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setBlockTitle(title);
|
||||
}, [title]);
|
||||
|
||||
const handleTitle = useCallback(
|
||||
(text: string) => {
|
||||
if (text === title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanVal = text.trim();
|
||||
setBlockTitle(cleanVal);
|
||||
|
||||
updateEvent({ id: eventId, title: cleanVal });
|
||||
},
|
||||
[title, updateEvent, eventId],
|
||||
);
|
||||
|
||||
const toggleOpenEvent = useCallback(() => {
|
||||
if (isOpen) {
|
||||
removeOpenEvent();
|
||||
@@ -173,17 +149,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
actionHandler={actionHandler}
|
||||
previousEnd={previousEnd}
|
||||
/>
|
||||
<Editable
|
||||
variant='ontime'
|
||||
value={blockTitle}
|
||||
className={`${style.eventTitle} ${!title ? style.noTitle : ''}`}
|
||||
placeholder='Event title'
|
||||
onChange={(value) => setBlockTitle(value)}
|
||||
onSubmit={(value) => handleTitle(value)}
|
||||
>
|
||||
<EditablePreview className={style.preview} />
|
||||
<EditableInput />
|
||||
</Editable>
|
||||
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
||||
<div className={style.statusElements}>
|
||||
<span className={style.eventNote}>{note}</span>
|
||||
<div className={selected ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
|
||||
|
||||
+22
-23
@@ -1,18 +1,25 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||
import { millisToMinutes } from '../../../../common/utils/dateConfig';
|
||||
import { validateEntry } from '../../../../common/utils/timesManager';
|
||||
import { TimeEntryField, validateEntry } from '../../../../common/utils/timesManager';
|
||||
import { EventItemActions } from '../../RundownEntry';
|
||||
|
||||
import style from '../EventBlock.module.scss';
|
||||
|
||||
export default function EventBlockTimers(props) {
|
||||
interface EventBlockTimerProps {
|
||||
timeStart: number;
|
||||
timeEnd: number;
|
||||
duration: number;
|
||||
delay: number;
|
||||
actionHandler: (action: EventItemActions, payload?: any) => void;
|
||||
previousEnd: number;
|
||||
}
|
||||
|
||||
export default function EventBlockTimers(props: EventBlockTimerProps) {
|
||||
const { timeStart, timeEnd, duration, delay, actionHandler, previousEnd } = props;
|
||||
const { emitWarning } = useEmitLog();
|
||||
const [warning, setWarnings] = useState({ start: '', end: '', duration: '' });
|
||||
|
||||
const delayTime = `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))}`;
|
||||
const newTime = millisToString(timeStart + delay);
|
||||
@@ -24,21 +31,19 @@ export default function EventBlockTimers(props) {
|
||||
* @return {boolean}
|
||||
*/
|
||||
const handleValidation = useCallback(
|
||||
(field, value) => {
|
||||
(field: TimeEntryField, value: number) => {
|
||||
const valid = validateEntry(field, value, timeStart, timeEnd);
|
||||
if (valid.catch) {
|
||||
emitWarning(`Time Input Warning: ${valid.catch}`);
|
||||
}
|
||||
setWarnings((prev) => ({ ...prev, ...valid.warnings }));
|
||||
return valid.value;
|
||||
},
|
||||
[emitWarning, timeEnd, timeStart]
|
||||
[timeEnd, timeStart],
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(field, value) => {
|
||||
(field: TimeEntryField, value: number) => {
|
||||
actionHandler('update', { field, value });
|
||||
},
|
||||
[actionHandler]
|
||||
[actionHandler],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -51,6 +56,7 @@ export default function EventBlockTimers(props) {
|
||||
delay={delay}
|
||||
placeholder='Start'
|
||||
previousEnd={previousEnd}
|
||||
warning={warning.start}
|
||||
/>
|
||||
<TimeInput
|
||||
name='timeEnd'
|
||||
@@ -60,6 +66,7 @@ export default function EventBlockTimers(props) {
|
||||
delay={delay}
|
||||
placeholder='End'
|
||||
previousEnd={previousEnd}
|
||||
warning={warning.end}
|
||||
/>
|
||||
<TimeInput
|
||||
name='durationOverride'
|
||||
@@ -68,6 +75,7 @@ export default function EventBlockTimers(props) {
|
||||
time={duration}
|
||||
placeholder='Duration'
|
||||
previousEnd={previousEnd}
|
||||
warning={warning.duration}
|
||||
/>
|
||||
{delay !== 0 && delay !== null && (
|
||||
<div className={style.delayNote}>
|
||||
@@ -79,12 +87,3 @@ export default function EventBlockTimers(props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EventBlockTimers.propTypes = {
|
||||
timeStart: PropTypes.number,
|
||||
timeEnd: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
delay: PropTypes.number,
|
||||
actionHandler: PropTypes.func,
|
||||
previousEnd: PropTypes.number,
|
||||
};
|
||||
@@ -15,6 +15,7 @@
|
||||
gap: 10%;
|
||||
|
||||
.quickBtn {
|
||||
font-weight: 400;
|
||||
width: auto;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TableSettingsContext } from '../../common/context/TableSettingsContext'
|
||||
import useFullscreen from '../../common/hooks/useFullscreen';
|
||||
import { useTimer } from '../../common/hooks/useSocket';
|
||||
import useEventData from '../../common/hooks-query/useEventData';
|
||||
import { formatDisplay, millisToSeconds } from '../../common/utils/dateConfig';
|
||||
import { formatDisplay } from '../../common/utils/dateConfig';
|
||||
import { formatTime } from '../../common/utils/time';
|
||||
import { tooltipDelayFast } from '../../ontimeConfig';
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function TableHeader({ handleCSVExport, featureData }) {
|
||||
|
||||
// prepare presentation variables
|
||||
const isOvertime = timer.current < 0;
|
||||
const timerNow = `${isOvertime ? '-' : ''}${formatDisplay(millisToSeconds(timer.current))}`;
|
||||
const timerNow = `${isOvertime ? '-' : ''}${formatDisplay(timer.current)}`;
|
||||
const timeNow = formatTime(timer.clock, {
|
||||
showSeconds: true,
|
||||
format: 'hh:mm:ss a',
|
||||
|
||||
@@ -12,7 +12,7 @@ import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
||||
import TitleCard from '../../../common/components/title-card/TitleCard';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
@@ -66,7 +66,7 @@ export default function Backstage(props: BackstageProps) {
|
||||
if (time.current === null) {
|
||||
stageTimer = '- - : - -';
|
||||
} else {
|
||||
stageTimer = formatDisplay(Math.abs(millisToSeconds(time.current)), true);
|
||||
stageTimer = formatDisplay(Math.abs(time.current), true);
|
||||
if (isNegative) {
|
||||
stageTimer = `-${stageTimer}`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TimerType } from 'ontime-types';
|
||||
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
|
||||
const formatOptions = {
|
||||
@@ -36,7 +36,7 @@ export function formatTimerDisplay(timer?: string | number | null): string {
|
||||
} else if (timer === null || typeof timer === 'undefined' || isNaN(timer)) {
|
||||
display = '-- : -- : --';
|
||||
} else {
|
||||
display = formatDisplay(millisToSeconds(timer), true);
|
||||
display = formatDisplay(timer, true);
|
||||
}
|
||||
|
||||
return display;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||
import getDelayTo from '../../../common/utils/getDelayTo';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
@@ -102,8 +102,8 @@ export default function Countdown(props: CountdownProps) {
|
||||
runningMessage === TimerMessage.ended
|
||||
? formatTime(runningTimer, formatOptionsFinished)
|
||||
: formatDisplay(
|
||||
isSelected ? millisToSeconds(runningTimer) : millisToSeconds(runningTimer + delay),
|
||||
isSelected || time.waiting,
|
||||
isSelected ? runningTimer : runningTimer + delay,
|
||||
isSelected || runningMessage === TimerMessage.waiting,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,7 +21,7 @@ export const sanitiseTitle = (title: string | null) => (title ? title : '{no tit
|
||||
export const fetchTimerData = (
|
||||
time: TimeManagerType,
|
||||
follow: OntimeEvent,
|
||||
selectedId: string,
|
||||
selectedId: string | null,
|
||||
): { message: TimerMessage; timer: number } => {
|
||||
let message;
|
||||
let timer;
|
||||
|
||||
@@ -83,7 +83,7 @@ export default function StudioClock(props) {
|
||||
{title.titleNext}
|
||||
</div>
|
||||
<div className={isNegative ? 'next-countdown' : 'next-countdown next-countdown--overtime'}>
|
||||
{selectedId != null && formatDisplay(time.current)}
|
||||
{selectedId !== null && formatDisplay(time.current)}
|
||||
</div>
|
||||
<div className='clock-indicators'>
|
||||
{activeIndicators.map((i) => (
|
||||
|
||||
@@ -65,7 +65,7 @@ export default function Timer(props: TimerProps) {
|
||||
const isNegative =
|
||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||
|
||||
const showEndMessage = time.current < 0 && general.endMessage;
|
||||
const showEndMessage = (time.current ?? 1) < 0 && general.endMessage;
|
||||
const showProgress = time.playback !== Playback.Stop;
|
||||
const showFinished = time.finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
const showClock = time.timerType !== TimerType.Clock;
|
||||
@@ -110,8 +110,8 @@ export default function Timer(props: TimerProps) {
|
||||
|
||||
<ProgressBar
|
||||
className={isPlaying ? 'progress-container' : 'progress-container progress-container--paused'}
|
||||
now={time.current}
|
||||
complete={time.duration}
|
||||
now={time.current || 0}
|
||||
complete={time.duration || 0}
|
||||
hidden={!showProgress}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user