mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-09 08:19:15 +00:00
Custom add (#889)
This commit is contained in:
@@ -2,6 +2,7 @@ import { Playback } from 'ontime-types';
|
|||||||
|
|
||||||
import { usePlaybackControl } from '../../../common/hooks/useSocket';
|
import { usePlaybackControl } from '../../../common/hooks/useSocket';
|
||||||
|
|
||||||
|
import AddTime from './add-time/AddTime';
|
||||||
import { ExtraTimer } from './extra-timer/ExtraTimer';
|
import { ExtraTimer } from './extra-timer/ExtraTimer';
|
||||||
import PlaybackButtons from './playback-buttons/PlaybackButtons';
|
import PlaybackButtons from './playback-buttons/PlaybackButtons';
|
||||||
import PlaybackTimer from './playback-timer/PlaybackTimer';
|
import PlaybackTimer from './playback-timer/PlaybackTimer';
|
||||||
@@ -13,7 +14,9 @@ export default function PlaybackControl() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.mainContainer}>
|
<div className={style.mainContainer}>
|
||||||
<PlaybackTimer playback={data.playback as Playback} />
|
<PlaybackTimer playback={data.playback as Playback}>
|
||||||
|
<AddTime playback={data.playback} />
|
||||||
|
</PlaybackTimer>
|
||||||
<PlaybackButtons
|
<PlaybackButtons
|
||||||
playback={data.playback}
|
playback={data.playback}
|
||||||
numEvents={data.numEvents}
|
numEvents={data.numEvents}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
.addTime {
|
||||||
|
grid-area: addtime;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $element-inner-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButtons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: $element-inner-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tallButtons {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Tooltip } from '@chakra-ui/react';
|
||||||
|
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||||
|
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||||
|
import { Playback } from 'ontime-types';
|
||||||
|
import { MILLIS_PER_SECOND } from 'ontime-utils';
|
||||||
|
|
||||||
|
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||||
|
import { useLocalStorage } from '../../../../common/hooks/useLocalStorage';
|
||||||
|
import { setPlayback } from '../../../../common/hooks/useSocket';
|
||||||
|
import { forgivingStringToMillis } from '../../../../common/utils/dateConfig';
|
||||||
|
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
||||||
|
import TapButton from '../tap-button/TapButton';
|
||||||
|
|
||||||
|
import style from './AddTime.module.scss';
|
||||||
|
|
||||||
|
interface AddTimeProps {
|
||||||
|
playback: Playback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AddTime(props: AddTimeProps) {
|
||||||
|
const { playback } = props;
|
||||||
|
const [time, setTime] = useLocalStorage('add-time', 300_000); // 5 minutes
|
||||||
|
|
||||||
|
const handleTimeChange = (_field: string, value: string) => {
|
||||||
|
const newTime = forgivingStringToMillis(value);
|
||||||
|
setTime(newTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddTime = (direction: 'add' | 'remove') => {
|
||||||
|
// API expects input in seconds
|
||||||
|
if (direction === 'add') {
|
||||||
|
setPlayback.addTime(time / MILLIS_PER_SECOND);
|
||||||
|
} else {
|
||||||
|
setPlayback.addTime((-1 * time) / MILLIS_PER_SECOND);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canAddTime = playback === Playback.Play || playback === Playback.Pause;
|
||||||
|
const doDisableButtons = !canAddTime || time === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.addTime}>
|
||||||
|
<TimeInput name='addtime' submitHandler={handleTimeChange} time={time} placeholder='Add time' />
|
||||||
|
<div className={style.addButtons}>
|
||||||
|
<Tooltip label='Remove time' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||||
|
<TapButton onClick={() => handleAddTime('remove')} disabled={doDisableButtons} className={style.tallButtons}>
|
||||||
|
<IoRemove />
|
||||||
|
</TapButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label='Add time' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||||
|
<TapButton onClick={() => handleAddTime('add')} disabled={doDisableButtons} className={style.tallButtons}>
|
||||||
|
<IoAdd />
|
||||||
|
</TapButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+30
-55
@@ -1,16 +1,17 @@
|
|||||||
.timeContainer {
|
.timeContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'ind clk clk btn'
|
'indicators timer addtime'
|
||||||
'... sta fin btn';
|
'status status addtime';
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
grid-template-columns: 1.25rem 1fr 1fr 5rem;
|
grid-template-columns: 1.25rem 1fr 6.5rem;
|
||||||
gap: $element-inner-spacing;
|
gap: $element-inner-spacing;
|
||||||
justify-items: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------> INDICATORS
|
||||||
|
|
||||||
.indicators {
|
.indicators {
|
||||||
grid-area: ind;
|
grid-area: indicators;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -18,72 +19,46 @@
|
|||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indRoll,
|
.indicatorRoll,
|
||||||
.indDelay,
|
.indicatorDelay,
|
||||||
.indNegative {
|
.indicatorNegative {
|
||||||
background-color: $black-10;
|
background-color: $black-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indRoll,
|
.indicatorRoll,
|
||||||
.indRollActive,
|
.indicatorDelay {
|
||||||
.indDelay,
|
|
||||||
.indDelayActive {
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-radius: 6px;
|
border-radius: 5px;
|
||||||
width: 12px;
|
width: 10px;
|
||||||
height: 12px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indRollActive {
|
.indicatorRoll[data-active='true'] {
|
||||||
background-color: $ontime-roll;
|
background-color: $ontime-roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indNegative,
|
.indicatorDelay[data-active='true'] {
|
||||||
.indNegativeActive {
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 90%;
|
|
||||||
height: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indNegativeActive {
|
|
||||||
background-color: $playback-negative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indDelayActive {
|
|
||||||
background-color: $ontime-delay;
|
background-color: $ontime-delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.indicatorNegative {
|
||||||
grid-area: btn;
|
margin: 0 auto;
|
||||||
display: grid;
|
width: 90%;
|
||||||
grid-template-columns: 1fr 1fr;
|
height: 0.25rem;
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
width: 100%;
|
&[data-active='true'] {
|
||||||
gap: $element-inner-spacing;
|
background-color: $playback-negative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.minus {
|
// ---------> LABELS
|
||||||
grid-area: min;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.start,
|
.status {
|
||||||
.finish,
|
grid-area: status;
|
||||||
.roll {
|
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
display: flex;
|
||||||
|
gap: $section-spacing;
|
||||||
.start {
|
margin-left: 1.5rem;
|
||||||
grid-area: sta;
|
|
||||||
}
|
|
||||||
|
|
||||||
.finish {
|
|
||||||
grid-area: fin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roll {
|
|
||||||
grid-area: 2 / 2 / 2 / 4 ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
import { Tooltip } from '@chakra-ui/react';
|
import { Tooltip } from '@chakra-ui/react';
|
||||||
import { Playback } from 'ontime-types';
|
import { Playback } from 'ontime-types';
|
||||||
import { dayInMs, millisToMinutes, millisToSeconds, millisToString } from 'ontime-utils';
|
import { dayInMs, millisToMinutes, millisToSeconds, millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import { setPlayback, useTimer } from '../../../../common/hooks/useSocket';
|
import { useTimer } from '../../../../common/hooks/useSocket';
|
||||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
|
||||||
import TapButton from '../tap-button/TapButton';
|
|
||||||
import TimerDisplay from '../timer-display/TimerDisplay';
|
import TimerDisplay from '../timer-display/TimerDisplay';
|
||||||
|
|
||||||
import style from './PlaybackTimer.module.scss';
|
import style from './PlaybackTimer.module.scss';
|
||||||
@@ -13,8 +12,30 @@ interface PlaybackTimerProps {
|
|||||||
playback: Playback;
|
playback: Playback;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PlaybackTimer(props: PlaybackTimerProps) {
|
function resolveAddedTimeLabel(addedTime: number) {
|
||||||
const { playback } = props;
|
function resolveClosestUnit(ms: number) {
|
||||||
|
if (ms < 6000) {
|
||||||
|
return `${millisToSeconds(ms)} seconds`;
|
||||||
|
} else if (ms < 12000) {
|
||||||
|
return '1 minute';
|
||||||
|
} else {
|
||||||
|
return `${millisToMinutes(ms)} minutes`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedTime > 0) {
|
||||||
|
return `Added ${resolveClosestUnit(addedTime)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedTime < 0) {
|
||||||
|
return `Removed ${resolveClosestUnit(addedTime)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaybackTimer(props: PropsWithChildren<PlaybackTimerProps>) {
|
||||||
|
const { playback, children } = props;
|
||||||
const timer = useTimer();
|
const timer = useTimer();
|
||||||
|
|
||||||
const started = millisToString(timer.startedAt);
|
const started = millisToString(timer.startedAt);
|
||||||
@@ -22,88 +43,43 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
|
|||||||
const finish = millisToString(expectedFinish);
|
const finish = millisToString(expectedFinish);
|
||||||
|
|
||||||
const isRolling = playback === Playback.Roll;
|
const isRolling = playback === Playback.Roll;
|
||||||
const isStopped = playback === Playback.Stop;
|
|
||||||
const isWaiting = timer.secondaryTimer !== null && timer.secondaryTimer > 0 && timer.current === null;
|
const isWaiting = timer.secondaryTimer !== null && timer.secondaryTimer > 0 && timer.current === null;
|
||||||
const disableButtons = isStopped || isRolling;
|
|
||||||
const isOvertime = timer.current !== null && timer.current < 0;
|
const isOvertime = timer.current !== null && timer.current < 0;
|
||||||
const hasAddedTime = Boolean(timer.addedTime);
|
const hasAddedTime = Boolean(timer.addedTime);
|
||||||
|
|
||||||
const rollLabel = isRolling ? 'Roll mode active' : '';
|
const rollLabel = isRolling ? 'Roll mode active' : '';
|
||||||
|
|
||||||
const resolveAddedTimeLabel = () => {
|
const addedTimeLabel = resolveAddedTimeLabel(timer.addedTime);
|
||||||
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 (
|
return (
|
||||||
<div className={style.timeContainer}>
|
<div className={style.timeContainer}>
|
||||||
<div className={style.indicators}>
|
<div className={style.indicators}>
|
||||||
<Tooltip label={rollLabel}>
|
<Tooltip label={rollLabel}>
|
||||||
<div className={isRolling ? style.indRollActive : style.indRoll} />
|
<div className={style.indicatorRoll} data-active={isRolling} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div className={isOvertime ? style.indNegativeActive : style.indNegative} />
|
<div className={style.indicatorNegative} data-active={isOvertime} />
|
||||||
<Tooltip label={addedTimeLabel}>
|
<Tooltip label={addedTimeLabel}>
|
||||||
<div className={hasAddedTime ? style.indDelayActive : style.indDelay} />
|
<div className={style.indicatorDelay} data-active={hasAddedTime} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<TimerDisplay time={isWaiting ? timer.secondaryTimer : timer.current} />
|
<TimerDisplay time={isWaiting ? timer.secondaryTimer : timer.current} />
|
||||||
{isWaiting ? (
|
<div className={style.status}>
|
||||||
<div className={style.roll}>
|
{isWaiting ? (
|
||||||
<span className={style.rolltag}>Roll: Countdown to start</span>
|
<span className={style.rolltag}>Roll: Countdown to start</span>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<>
|
||||||
<>
|
<span className={style.start}>
|
||||||
<div className={style.start}>
|
<span className={style.tag}>Started at</span>
|
||||||
<span className={style.tag}>Started at</span>
|
<span className={style.time}>{started}</span>
|
||||||
<span className={style.time}>{started}</span>
|
</span>
|
||||||
</div>
|
<span className={style.finish}>
|
||||||
<div className={style.finish}>
|
<span className={style.tag}>Expect end</span>
|
||||||
<span className={style.tag}>Expect end</span>
|
<span className={style.time}>{finish}</span>
|
||||||
<span className={style.time}>{finish}</span>
|
</span>
|
||||||
</div>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
|
||||||
<div className={style.btn}>
|
|
||||||
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
|
||||||
<TapButton onClick={() => setPlayback.addTime(-60)} disabled={disableButtons} aspect='square'>
|
|
||||||
-1
|
|
||||||
</TapButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label='Add 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
|
||||||
<TapButton onClick={() => setPlayback.addTime(60)} disabled={disableButtons} aspect='square'>
|
|
||||||
+1
|
|
||||||
</TapButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label='Remove 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
|
||||||
<TapButton onClick={() => setPlayback.addTime(-5 * 60)} disabled={disableButtons} aspect='square'>
|
|
||||||
-5
|
|
||||||
</TapButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label='Add 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
|
||||||
<TapButton onClick={() => setPlayback.addTime(+5 * 60)} disabled={disableButtons} aspect='square'>
|
|
||||||
+5
|
|
||||||
</TapButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@use '../../../../theme/viewerDefs' as *;
|
@use '../../../../theme/viewerDefs' as *;
|
||||||
|
|
||||||
.timer {
|
.timer {
|
||||||
grid-area: clk;
|
grid-area: timer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 18.75rem;
|
max-width: 18.75rem;
|
||||||
min-width: 5em;
|
min-width: 5em;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@use './EditorMixin' as editor;
|
@use './EditorMixin' as editor;
|
||||||
|
|
||||||
$min-playback-width: 27rem;
|
$min-playback-width: 27rem;
|
||||||
$max-playback-width: 35rem;
|
$max-playback-width: 30rem;
|
||||||
$panel-gap: 0.5rem;
|
$panel-gap: 0.5rem;
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
|
|||||||
Reference in New Issue
Block a user