mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-07 00:13:53 +00:00
v2 beta 4 (#361)
* ux: rename end action * chore: update demo db * style: tweaks on extracted rundown * chore: prevent console logs in production code * feat: go mode * style: remove window size limits * style: rename delete action * style: small tweaks on icons * fix: style override on params * chore: update test db * refactor: small code quality improvements * refactor: prevent circular imports
This commit is contained in:
@@ -11,9 +11,16 @@
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["e2e/**/**.spec.ts", "e2e/**/**.test.ts"],
|
||||
"extends": ["plugin:playwright/playwright-test"]
|
||||
"files": [
|
||||
"e2e/**/**.spec.ts",
|
||||
"e2e/**/**.test.ts"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:playwright/playwright-test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"no-console": "warn"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export default function DelayInput(props: DelayInputProps) {
|
||||
let ignoreChange = false;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof duration === undefined) {
|
||||
if (typeof duration === 'undefined') {
|
||||
return;
|
||||
}
|
||||
setValue(millisToString(duration));
|
||||
|
||||
@@ -47,6 +47,7 @@ export const setPlayback = {
|
||||
start: () => socketSendJson('start'),
|
||||
pause: () => socketSendJson('pause'),
|
||||
roll: () => socketSendJson('roll'),
|
||||
startNext: () => socketSendJson('start-next'),
|
||||
previous: () => {
|
||||
socketSendJson('previous');
|
||||
},
|
||||
|
||||
@@ -461,10 +461,10 @@ describe('millisToDelayString()', () => {
|
||||
expect(millisToDelayString(0)).toBeNull();
|
||||
});
|
||||
describe('converts values in seconds', () => {
|
||||
it(`shows a simple string with value in seconds`, () => {
|
||||
it('shows a simple string with value in seconds', () => {
|
||||
expect(millisToDelayString(10000)).toBe('+10 sec');
|
||||
});
|
||||
it(`... and its negative counterpart`, () => {
|
||||
it('... and its negative counterpart', () => {
|
||||
expect(millisToDelayString(-10000)).toBe('-10 sec');
|
||||
});
|
||||
|
||||
@@ -478,16 +478,16 @@ describe('millisToDelayString()', () => {
|
||||
});
|
||||
|
||||
describe('converts values in minutes', () => {
|
||||
it(`shows a simple string with value in minutes`, () => {
|
||||
it('shows a simple string with value in minutes', () => {
|
||||
expect(millisToDelayString(720000)).toBe('+12 min');
|
||||
});
|
||||
it(`... and its negative counterpart`, () => {
|
||||
it('... and its negative counterpart', () => {
|
||||
expect(millisToDelayString(-720000)).toBe('-12 min');
|
||||
});
|
||||
it(`shows a simple string with value in minutes and seconds`, () => {
|
||||
it('shows a simple string with value in minutes and seconds', () => {
|
||||
expect(millisToDelayString(630000)).toBe('+00:10:30');
|
||||
});
|
||||
it(`... and its negative counterpart`, () => {
|
||||
it('... and its negative counterpart', () => {
|
||||
expect(millisToDelayString(-630000)).toBe('-00:10:30');
|
||||
});
|
||||
|
||||
@@ -500,10 +500,10 @@ describe('millisToDelayString()', () => {
|
||||
});
|
||||
|
||||
describe('converts values with full time string', () => {
|
||||
it(`positive added time`, () => {
|
||||
it('positive added time', () => {
|
||||
expect(millisToDelayString(45015000)).toBe('+12:30:15');
|
||||
});
|
||||
it(`negative added time`, () => {
|
||||
it('negative added time', () => {
|
||||
expect(millisToDelayString(-45015000)).toBe('-12:30:15');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -114,13 +114,13 @@ function checkAmPm(value: string) {
|
||||
* @param {string} value
|
||||
*/
|
||||
function checkMatchers(value: string) {
|
||||
const hoursMatch = value.match(/(\d+)h/);
|
||||
const hoursMatch = /(\d+)h/.exec(value);
|
||||
const hoursMatchValue = hoursMatch ? parse(hoursMatch[1]) : 0;
|
||||
|
||||
const minutesMatch = value.match(/(\d+)m/);
|
||||
const minutesMatch = /(\d+)m/.exec(value);
|
||||
const minutesMatchValue = minutesMatch ? parse(minutesMatch[1]) : 0;
|
||||
|
||||
const secondsMatch = value.match(/(\d+)s/);
|
||||
const secondsMatch = /(\d+)s/.exec(value);
|
||||
const secondsMatchValue = secondsMatch ? parse(secondsMatch[1]) : 0;
|
||||
|
||||
if (hoursMatchValue > 0 || minutesMatchValue > 0 || secondsMatchValue > 0) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export function isStringBoolean(text: string | null) {
|
||||
if (text === null) {
|
||||
return false;
|
||||
}
|
||||
return text?.toLowerCase() === 'true' || text === '1';
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import PlaybackDisplay from './PlaybackDisplay';
|
||||
import Transport from './Transport';
|
||||
|
||||
interface PlaybackButtonsProps {
|
||||
playback: Playback;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
const { playback, noEvents } = props;
|
||||
return (
|
||||
<>
|
||||
<PlaybackDisplay playback={playback} noEvents={noEvents} />
|
||||
<Transport playback={playback} noEvents={noEvents} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,130 +1,4 @@
|
||||
@use '../../../theme/v2Styles' as *;
|
||||
@use '../../../theme/ontimeColours' as *;
|
||||
@use '../../../theme/mixins' as *;
|
||||
|
||||
.mainContainer {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
margin: 0 auto;
|
||||
gap: $element-inner-spacing;
|
||||
}
|
||||
|
||||
.timeContainer {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'ind clk clk btn'
|
||||
'... sta fin btn';
|
||||
grid-template-rows: 1fr auto;
|
||||
grid-template-columns: 1.5em 1fr 1fr 5em;
|
||||
gap: $element-inner-spacing;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.timer {
|
||||
grid-area: clk;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.indicators {
|
||||
grid-area: ind;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.indRoll,
|
||||
.indDelay,
|
||||
.indNegative {
|
||||
background-color: $gray-1350;
|
||||
}
|
||||
|
||||
.indRoll,
|
||||
.indRollActive,
|
||||
.indDelay,
|
||||
.indDelayActive {
|
||||
margin: 0 auto;
|
||||
border-radius: 6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.indRollActive {
|
||||
background-color: $ontime-roll;
|
||||
}
|
||||
|
||||
.indNegative,
|
||||
.indNegativeActive {
|
||||
margin: 0 auto;
|
||||
width: 90%;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.indNegativeActive {
|
||||
background-color: $playback-negative;
|
||||
}
|
||||
|
||||
.indDelayActive {
|
||||
background-color: $ontime-delay;
|
||||
}
|
||||
|
||||
.btn {
|
||||
grid-area: btn;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
width: 100%;
|
||||
gap: $element-inner-spacing;
|
||||
}
|
||||
|
||||
.minus {
|
||||
grid-area: min;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.start,
|
||||
.finish,
|
||||
.roll {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.start {
|
||||
grid-area: sta;
|
||||
}
|
||||
|
||||
.finish {
|
||||
grid-area: fin;
|
||||
}
|
||||
|
||||
.roll {
|
||||
grid-area: 2 / 2 / 2 / 4 ;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: $section-white;
|
||||
font-size: $text-body-size;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: $label-gray;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.rolltag {
|
||||
color: $ontime-roll;
|
||||
font-size: $text-body-size;
|
||||
}
|
||||
|
||||
.playbackContainer {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
padding-top: 0.5em;
|
||||
gap: $element-spacing;
|
||||
}
|
||||
|
||||
.invertX {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { Playback } from 'ontime-types';
|
||||
|
||||
import { usePlaybackControl } from '../../../common/hooks/useSocket';
|
||||
|
||||
import PlaybackButtons from './PlaybackButtons';
|
||||
import PlaybackTimer from './PlaybackTimer';
|
||||
import PlaybackButtons from './playback-buttons/PlaybackButtons';
|
||||
import PlaybackTimer from './playback-timer/PlaybackTimer';
|
||||
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { setPlayback } from '../../../common/hooks/useSocket';
|
||||
|
||||
import TapButton from './TapButton';
|
||||
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
interface PlaybackProps {
|
||||
playback: Playback;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function PlaybackDisplay(props: PlaybackProps) {
|
||||
const { playback, noEvents } = props;
|
||||
const isRolling = playback === Playback.Roll;
|
||||
const isPlaying = playback === Playback.Play;
|
||||
const isPaused = playback === Playback.Pause;
|
||||
const isArmed = playback === Playback.Armed;
|
||||
const isStopped = playback === Playback.Stop;
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.start()}
|
||||
disabled={isStopped || isRolling}
|
||||
theme={Playback.Play}
|
||||
active={isPlaying}
|
||||
>
|
||||
<IoPlay />
|
||||
</TapButton>
|
||||
|
||||
<TapButton
|
||||
onClick={() => setPlayback.pause()}
|
||||
disabled={isStopped || isRolling || isArmed}
|
||||
theme={Playback.Pause}
|
||||
active={isPaused}
|
||||
>
|
||||
<IoPause />
|
||||
</TapButton>
|
||||
|
||||
<TapButton
|
||||
onClick={() => setPlayback.roll()}
|
||||
disabled={!isStopped || noEvents}
|
||||
theme={Playback.Roll}
|
||||
active={isRolling}
|
||||
>
|
||||
<IoTimeOutline />
|
||||
</TapButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { ForwardedRef, forwardRef, PropsWithChildren } from 'react';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import style from './TapButton.module.scss';
|
||||
|
||||
interface TapButtonProps {
|
||||
disabled?: boolean;
|
||||
square?: boolean;
|
||||
onClick: () => void;
|
||||
theme?: Playback | 'neutral';
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
const TapButton = forwardRef((props: PropsWithChildren<TapButtonProps>, ref: ForwardedRef<HTMLButtonElement> ) => {
|
||||
const { children, disabled, onClick, theme = 'neutral', square, active } = props;
|
||||
return (
|
||||
<button
|
||||
className={`${style.tapButton} ${style[theme]} ${square ? style.square : ''} ${active ? style.active : ''}`}
|
||||
disabled={disabled}
|
||||
type='button'
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
TapButton.displayName = "TabButton";
|
||||
export default TapButton;
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
|
||||
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
|
||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { setPlayback } from '../../../common/hooks/useSocket';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import TapButton from './TapButton';
|
||||
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
interface TransportProps {
|
||||
playback: Playback;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function Transport(props: TransportProps) {
|
||||
const { playback, noEvents } = props;
|
||||
const isRolling = playback === Playback.Roll;
|
||||
const isStopped = playback === Playback.Stop;
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<Tooltip label='Previous event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.previous()} disabled={isRolling || noEvents}>
|
||||
<IoPlaySkipBack />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Next event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.next()} disabled={isRolling || noEvents}>
|
||||
<IoPlaySkipForward />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Reload event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.reload()} disabled={isStopped || isRolling}>
|
||||
<IoReload className={style.invertX} />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Unload Event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.stop()} disabled={isStopped && !isRolling} theme={Playback.Stop}>
|
||||
<IoStop />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
@use '../../../../theme/v2Styles' as *;
|
||||
|
||||
.buttonContainer {
|
||||
padding-top: $element-spacing;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"go playback"
|
||||
"go transport"
|
||||
"extra extra";
|
||||
grid-template-rows: repeat(3, 32px);
|
||||
gap: $element-spacing;
|
||||
}
|
||||
|
||||
.go {
|
||||
grid-area: go;
|
||||
font-size: 5em;
|
||||
}
|
||||
|
||||
@mixin spaced-flex {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
gap: $element-spacing;
|
||||
}
|
||||
|
||||
.playbackContainer {
|
||||
grid-area: playback;
|
||||
@include spaced-flex;
|
||||
}
|
||||
|
||||
.transportContainer {
|
||||
grid-area: transport;
|
||||
@include spaced-flex;
|
||||
}
|
||||
|
||||
.extra {
|
||||
grid-area: extra;
|
||||
@include spaced-flex;
|
||||
}
|
||||
|
||||
.invertX {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
|
||||
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
|
||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { setPlayback } from '../../../../common/hooks/useSocket';
|
||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
||||
import TapButton from '../tap-button/TapButton';
|
||||
|
||||
import styles from './PlaybackButtons.module.scss';
|
||||
import style from './PlaybackButtons.module.scss';
|
||||
|
||||
interface PlaybackButtonsProps {
|
||||
playback: Playback;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
const { playback, noEvents } = props;
|
||||
|
||||
const isRolling = playback === Playback.Roll;
|
||||
const isPlaying = playback === Playback.Play;
|
||||
const isPaused = playback === Playback.Pause;
|
||||
const isArmed = playback === Playback.Armed;
|
||||
const isStopped = playback === Playback.Stop;
|
||||
|
||||
return (
|
||||
<div className={styles.buttonContainer}>
|
||||
<TapButton disabled={isRolling} onClick={() => setPlayback.startNext()} aspect='fill' className={styles.go}>
|
||||
GO
|
||||
</TapButton>
|
||||
<div className={style.playbackContainer}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.start()}
|
||||
disabled={isStopped || isRolling}
|
||||
theme={Playback.Play}
|
||||
active={isPlaying}
|
||||
>
|
||||
<IoPlay />
|
||||
</TapButton>
|
||||
|
||||
<TapButton
|
||||
onClick={() => setPlayback.pause()}
|
||||
disabled={isStopped || isRolling || isArmed}
|
||||
theme={Playback.Pause}
|
||||
active={isPaused}
|
||||
>
|
||||
<IoPause />
|
||||
</TapButton>
|
||||
</div>
|
||||
<div className={style.transportContainer}>
|
||||
<Tooltip label='Previous event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.previous()} disabled={isRolling || noEvents}>
|
||||
<IoPlaySkipBack />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Next event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.next()} disabled={isRolling || noEvents}>
|
||||
<IoPlaySkipForward />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={styles.extra}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.roll()}
|
||||
disabled={!isStopped || noEvents}
|
||||
theme={Playback.Roll}
|
||||
active={isRolling}
|
||||
>
|
||||
<IoTimeOutline />
|
||||
</TapButton>
|
||||
<Tooltip label='Reload event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.reload()} disabled={isStopped || isRolling}>
|
||||
<IoReload className={style.invertX} />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Unload Event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.stop()} disabled={isStopped && !isRolling} theme={Playback.Stop}>
|
||||
<IoStop />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
@use '../../../../theme/v2Styles' as *;
|
||||
@use '../../../../theme/ontimeColours' as *;
|
||||
|
||||
.timeContainer {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'ind clk clk btn'
|
||||
'... sta fin btn';
|
||||
grid-template-rows: 1fr auto;
|
||||
grid-template-columns: 1.5em 1fr 1fr 5em;
|
||||
gap: $element-inner-spacing;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.timer {
|
||||
grid-area: clk;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.indicators {
|
||||
grid-area: ind;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.indRoll,
|
||||
.indDelay,
|
||||
.indNegative {
|
||||
background-color: $black-10;
|
||||
}
|
||||
|
||||
.indRoll,
|
||||
.indRollActive,
|
||||
.indDelay,
|
||||
.indDelayActive {
|
||||
margin: 0 auto;
|
||||
border-radius: 6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.indRollActive {
|
||||
background-color: $ontime-roll;
|
||||
}
|
||||
|
||||
.indNegative,
|
||||
.indNegativeActive {
|
||||
margin: 0 auto;
|
||||
width: 90%;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.indNegativeActive {
|
||||
background-color: $playback-negative;
|
||||
}
|
||||
|
||||
.indDelayActive {
|
||||
background-color: $ontime-delay;
|
||||
}
|
||||
|
||||
.btn {
|
||||
grid-area: btn;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
width: 100%;
|
||||
gap: $element-inner-spacing;
|
||||
}
|
||||
|
||||
.minus {
|
||||
grid-area: min;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.start,
|
||||
.finish,
|
||||
.roll {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.start {
|
||||
grid-area: sta;
|
||||
}
|
||||
|
||||
.finish {
|
||||
grid-area: fin;
|
||||
}
|
||||
|
||||
.roll {
|
||||
grid-area: 2 / 2 / 2 / 4 ;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: $section-white;
|
||||
font-size: $text-body-size;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: $label-gray;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.rolltag {
|
||||
color: $ontime-roll;
|
||||
font-size: $text-body-size;
|
||||
}
|
||||
+11
-12
@@ -2,14 +2,13 @@ import { Tooltip } from '@chakra-ui/react';
|
||||
import { Playback } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import TimerDisplay from '../../../common/components/timer-display/TimerDisplay';
|
||||
import { setPlayback, useTimer } from '../../../common/hooks/useSocket';
|
||||
import { millisToMinutes, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
import TimerDisplay from '../../../../common/components/timer-display/TimerDisplay';
|
||||
import { setPlayback, useTimer } from '../../../../common/hooks/useSocket';
|
||||
import { millisToMinutes, millisToSeconds } from '../../../../common/utils/dateConfig';
|
||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
||||
import TapButton from '../tap-button/TapButton';
|
||||
|
||||
import TapButton from './TapButton';
|
||||
|
||||
import style from './PlaybackControl.module.scss';
|
||||
import style from './PlaybackTimer.module.scss';
|
||||
|
||||
interface PlaybackTimerProps {
|
||||
playback: Playback;
|
||||
@@ -36,7 +35,7 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
|
||||
if (ms < 6000) {
|
||||
return `${millisToSeconds(ms)} seconds`;
|
||||
} else if (ms < 12000) {
|
||||
return `1 minute`;
|
||||
return '1 minute';
|
||||
} else {
|
||||
return `${millisToMinutes(ms)} minutes`;
|
||||
}
|
||||
@@ -87,22 +86,22 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
|
||||
)}
|
||||
<div className={style.btn}>
|
||||
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
||||
<TapButton onClick={() => setPlayback.delay(-1)} disabled={disableButtons} square>
|
||||
<TapButton onClick={() => setPlayback.delay(-1)} disabled={disableButtons} aspect='square'>
|
||||
-1
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Add 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
||||
<TapButton onClick={() => setPlayback.delay(1)} disabled={disableButtons} square>
|
||||
<TapButton onClick={() => setPlayback.delay(1)} disabled={disableButtons} aspect='square'>
|
||||
+1
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Remove 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
||||
<TapButton onClick={() => setPlayback.delay(-5)} disabled={disableButtons} square>
|
||||
<TapButton onClick={() => setPlayback.delay(-5)} disabled={disableButtons} aspect='square'>
|
||||
-5
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Add 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
|
||||
<TapButton onClick={() => setPlayback.delay(+5)} disabled={disableButtons} square>
|
||||
<TapButton onClick={() => setPlayback.delay(+5)} disabled={disableButtons} aspect='square'>
|
||||
+5
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
+13
-4
@@ -1,15 +1,14 @@
|
||||
@use '../../../theme/v2Styles' as *;
|
||||
@use '../../../theme/ontimeColours' as *;
|
||||
@use '../../../../theme/v2Styles' as *;
|
||||
@use '../../../../theme/ontimeColours' as *;
|
||||
|
||||
$button-bg-gray: $gray-1050;
|
||||
$button-color-white: $gray-50;
|
||||
|
||||
@mixin tap-factory($theme-color) {
|
||||
font-family: $ontime-font-family;
|
||||
font-size: 22px;
|
||||
font-size: 18px;
|
||||
border-radius: $component-border-radius-md;
|
||||
width: 100%;
|
||||
aspect-ratio: 3/1;
|
||||
transition-property: color, background-color;
|
||||
transition-duration: $transition-time-feedback;
|
||||
display: grid;
|
||||
@@ -78,7 +77,17 @@ $button-color-white: $gray-50;
|
||||
@include tap-factory($ontime-stop);
|
||||
}
|
||||
|
||||
.tapButton.normal {
|
||||
aspect-ratio: 3/1;
|
||||
}
|
||||
|
||||
.tapButton.square {
|
||||
aspect-ratio: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tapButton.fill {
|
||||
aspect-ratio: unset;
|
||||
height: 100%;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ForwardedRef, forwardRef, PropsWithChildren } from 'react';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
|
||||
import style from './TapButton.module.scss';
|
||||
|
||||
interface TapButtonProps {
|
||||
disabled?: boolean;
|
||||
aspect?: 'normal' | 'square' | 'fill';
|
||||
square?: boolean;
|
||||
free?: boolean;
|
||||
onClick: () => void;
|
||||
theme?: Playback | 'neutral';
|
||||
active?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TapButton = forwardRef((props: PropsWithChildren<TapButtonProps>, ref: ForwardedRef<HTMLButtonElement>) => {
|
||||
const { children, disabled, onClick, theme = 'neutral', aspect = 'normal', active, className } = props;
|
||||
const classes = cx([style.tapButton, className, style[theme], style[aspect], active ? style.active : null]);
|
||||
|
||||
return (
|
||||
<button className={classes} disabled={disabled} type='button' onClick={onClick} ref={ref}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
TapButton.displayName = 'TabButton';
|
||||
export default TapButton;
|
||||
@@ -37,11 +37,11 @@ $playback-width: 450px;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-columns: $menu-width $rundown-width $playback-width auto;
|
||||
grid-template-areas:
|
||||
'sett even play info'
|
||||
'sett even mess info';
|
||||
'sett rundown play info'
|
||||
'sett rundown mess info';
|
||||
gap: 8px;
|
||||
|
||||
.editor,
|
||||
.rundown,
|
||||
.playback,
|
||||
.messages,
|
||||
.info,
|
||||
@@ -98,7 +98,7 @@ $playback-width: 450px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.editor,
|
||||
.rundown,
|
||||
.info,
|
||||
.settings {
|
||||
visibility: hidden;
|
||||
@@ -117,7 +117,7 @@ $playback-width: 450px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.editor,
|
||||
.rundown,
|
||||
.messages,
|
||||
.info,
|
||||
.settings {
|
||||
@@ -128,7 +128,7 @@ $playback-width: 450px;
|
||||
|
||||
.mainContainer {
|
||||
.settings,
|
||||
.editor,
|
||||
.rundown,
|
||||
.messages,
|
||||
.playback,
|
||||
.info {
|
||||
@@ -171,8 +171,9 @@ $playback-width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
grid-area: even;
|
||||
.rundown {
|
||||
grid-area: rundown;
|
||||
height: 100%;
|
||||
|
||||
.content {
|
||||
height: calc(100% - 24px);
|
||||
@@ -183,6 +184,7 @@ $playback-width: 450px;
|
||||
.info {
|
||||
grid-area: info;
|
||||
min-width: 17em;
|
||||
max-width: 800px;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
|
||||
@@ -131,7 +131,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
||||
onChange={(event) => handleSubmit('endAction', event.target.value)}
|
||||
variant='ontime'
|
||||
>
|
||||
<option value={EndAction.Continue}>Continue</option>
|
||||
<option value={EndAction.None}>None</option>
|
||||
<option value={EndAction.Stop}>Stop</option>
|
||||
<option value={EndAction.LoadNext}>Load Next</option>
|
||||
<option value={EndAction.PlayNext}>Play Next</option>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FiSave } from '@react-icons/all-files/fi/FiSave';
|
||||
import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
|
||||
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
||||
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
||||
import { IoHelpCircleOutline } from '@react-icons/all-files/io5/IoHelpCircleOutline';
|
||||
import { IoHelp } from '@react-icons/all-files/io5/IoHelp';
|
||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||
@@ -30,7 +30,7 @@ interface MenuBarProps {
|
||||
}
|
||||
|
||||
const buttonStyle = {
|
||||
fontSize: '1.5em',
|
||||
fontSize: '1.25em',
|
||||
size: 'lg',
|
||||
colorScheme: 'white',
|
||||
_hover: {
|
||||
@@ -155,7 +155,7 @@ export default function MenuBar(props: MenuBarProps) {
|
||||
<TooltipActionBtn
|
||||
{...buttonStyle}
|
||||
className={isAboutOpen ? style.open : ''}
|
||||
icon={<IoHelpCircleOutline />}
|
||||
icon={<IoHelp />}
|
||||
clickHandler={onAboutOpen}
|
||||
tooltip='About'
|
||||
aria-label='About'
|
||||
|
||||
@@ -30,6 +30,8 @@ export default function Rundown(props: RundownProps) {
|
||||
const startTimeIsLastEnd = eventSettings.startTimeIsLastEnd;
|
||||
const showQuickEntry = eventSettings.showQuickEntry;
|
||||
|
||||
const isExtracted = window.location.pathname.includes('/rundown');
|
||||
|
||||
// cursor
|
||||
const cursor = useAppMode((state) => state.cursor);
|
||||
const appMode = useAppMode((state) => state.mode);
|
||||
@@ -238,6 +240,7 @@ export default function Rundown(props: RundownProps) {
|
||||
previousEventId={previousEventId}
|
||||
playback={isSelected ? featureData.playback : undefined}
|
||||
isRolling={featureData.playback === Playback.Roll}
|
||||
disableEdit={isExtracted}
|
||||
/>
|
||||
{((showQuickEntry && hasCursor) || isLast) && (
|
||||
<QuickAddBlock
|
||||
|
||||
@@ -26,11 +26,23 @@ interface RundownEntryProps {
|
||||
previousEventId?: string;
|
||||
playback?: Playback; // we only care about this if this event is playing
|
||||
isRolling: boolean; // we need to know even if not related to this event
|
||||
disableEdit: boolean; // we disable edit when the window is extracted
|
||||
}
|
||||
|
||||
export default function RundownEntry(props: RundownEntryProps) {
|
||||
const { eventIndex, data, selected, hasCursor, next, delay, previousEnd, previousEventId, playback, isRolling } =
|
||||
props;
|
||||
const {
|
||||
eventIndex,
|
||||
data,
|
||||
selected,
|
||||
hasCursor,
|
||||
next,
|
||||
delay,
|
||||
previousEnd,
|
||||
previousEventId,
|
||||
playback,
|
||||
isRolling,
|
||||
disableEdit,
|
||||
} = props;
|
||||
const { emitError } = useEmitLog();
|
||||
const { addEvent, updateEvent, deleteEvent } = useEventAction();
|
||||
|
||||
@@ -163,6 +175,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
||||
playback={playback}
|
||||
isRolling={isRolling}
|
||||
actionHandler={actionHandler}
|
||||
disableEdit={disableEdit}
|
||||
/>
|
||||
);
|
||||
} else if (data.type === SupportedEvent.Block) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import style from '../editors/Editor.module.scss';
|
||||
|
||||
const RundownExport = () => {
|
||||
return (
|
||||
<Box className={style.editor} data-testid='panel-rundown'>
|
||||
<Box className={style.rundown} data-testid='panel-rundown'>
|
||||
<IoArrowUp className={style.corner} onClick={(event) => handleLinks(event, 'rundown')} />
|
||||
<ErrorBoundary>
|
||||
<RundownWrapper />
|
||||
|
||||
@@ -20,6 +20,7 @@ $block-cursor-color: $blue-400;
|
||||
border-radius: $block-border-radius;
|
||||
margin: 4px 2px;
|
||||
position: relative;
|
||||
color: $block-text-color;
|
||||
}
|
||||
|
||||
@mixin block-spacing() {
|
||||
|
||||
@@ -7,7 +7,7 @@ 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';
|
||||
import type { EventItemActions } from '../RundownEntry';
|
||||
|
||||
import style from './BlockBlock.module.scss';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import DelayInput from '../../../common/components/input/delay-input/DelayInput'
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import BlockActionMenu from '../event-block/composite/BlockActionMenu';
|
||||
import { EventItemActions } from '../RundownEntry';
|
||||
import type { EventItemActions } from '../RundownEntry';
|
||||
|
||||
import style from './DelayBlock.module.scss';
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ $skip-opacity: 0.1;
|
||||
padding-right: $block-clearance;
|
||||
gap: 2px;
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: $transition-time-feedback;
|
||||
|
||||
@mixin declare-overrides(){
|
||||
--status-color-override: #{$gray-200};
|
||||
--status-color-active-override: #{$green-400};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
|
||||
|
||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||
import { EventItemActions } from '../RundownEntry';
|
||||
import type { EventItemActions } from '../RundownEntry';
|
||||
|
||||
import EventBlockInner from './EventBlockInner';
|
||||
|
||||
@@ -41,6 +41,7 @@ interface EventBlockProps {
|
||||
value: unknown;
|
||||
},
|
||||
) => void;
|
||||
disableEdit: boolean;
|
||||
}
|
||||
|
||||
export default function EventBlock(props: EventBlockProps) {
|
||||
@@ -65,6 +66,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
playback,
|
||||
isRolling,
|
||||
actionHandler,
|
||||
disableEdit,
|
||||
} = props;
|
||||
|
||||
const moveCursorTo = useAppMode((state) => state.setCursor);
|
||||
@@ -164,6 +166,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
playback={playback}
|
||||
isRolling={isRolling}
|
||||
actionHandler={actionHandler}
|
||||
disableEdit={disableEdit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,7 @@ interface EventBlockInnerProps {
|
||||
playback?: Playback;
|
||||
isRolling: boolean;
|
||||
actionHandler: (action: EventItemActions, payload?: any) => void;
|
||||
disableEdit: boolean;
|
||||
}
|
||||
|
||||
const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
@@ -73,6 +74,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
playback,
|
||||
isRolling,
|
||||
actionHandler,
|
||||
disableEdit,
|
||||
} = props;
|
||||
|
||||
const [renderInner, setRenderInner] = useState(false);
|
||||
@@ -159,6 +161,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
tabIndex={-1}
|
||||
backgroundColor={isOpen ? '#2B5ABC' : undefined}
|
||||
color={isOpen ? 'white' : '#f6f6f6'}
|
||||
isDisabled={disableEdit}
|
||||
/>
|
||||
<BlockActionMenu showAdd showDelay showBlock showClone enableDelete={!selected} actionHandler={actionHandler} />
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function BlockActionMenu(props: BlockActionMenuProps) {
|
||||
)}
|
||||
<MenuDivider />
|
||||
<MenuItem icon={<IoTrashBinSharp />} onClick={handleDelete} isDisabled={!enableDelete} color='#D20300'>
|
||||
Delete event
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
@@ -36,8 +36,8 @@ export default function Clock(props: ClockProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get config from url: key, text, font, size, hidenav, hideovertime
|
||||
// eg. http://localhost:3000/minimal?key=f00&text=fff
|
||||
// get config from url: key, text, font, size, hidenav
|
||||
// eg. http://localhost:3000/clock?key=f00&text=fff
|
||||
// Check for user options
|
||||
const userOptions: OverridableOptions = {
|
||||
size: 1,
|
||||
@@ -126,7 +126,6 @@ export default function Clock(props: ClockProps) {
|
||||
className={`clock-view ${isMirrored ? 'mirror' : ''}`}
|
||||
style={{
|
||||
backgroundColor: userOptions.keyColour,
|
||||
color: userOptions.textColour,
|
||||
justifyContent: userOptions.justifyContent,
|
||||
alignItems: userOptions.alignItems,
|
||||
}}
|
||||
@@ -136,6 +135,7 @@ export default function Clock(props: ClockProps) {
|
||||
<div
|
||||
className='clock'
|
||||
style={{
|
||||
color: userOptions.textColour,
|
||||
fontSize: `${(89 / (clean.length - 1)) * (userOptions.size || 1)}vw`,
|
||||
fontFamily: userOptions.font,
|
||||
top: userOptions.top,
|
||||
|
||||
@@ -7,6 +7,7 @@ import NavigationMenu from '../../../common/components/navigation-menu/Navigatio
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { OverridableOptions } from '../../../common/models/View.types';
|
||||
import { isStringBoolean } from '../../../common/utils/viewUtils';
|
||||
import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
|
||||
|
||||
import './MinimalTimer.scss';
|
||||
@@ -33,7 +34,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get config from url: key, text, font, size, hidenav, hideovertime
|
||||
// get config from url: key, text, font, size, hideovertime
|
||||
// eg. http://localhost:3000/minimal?key=f00&text=fff
|
||||
// Check for user options
|
||||
const userOptions: OverridableOptions = {
|
||||
@@ -116,19 +117,19 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
}
|
||||
|
||||
const hideOvertime = searchParams.get('hideovertime');
|
||||
userOptions.hideOvertime = Boolean(hideOvertime);
|
||||
userOptions.hideOvertime = isStringBoolean(hideOvertime);
|
||||
|
||||
const hideMessagesOverlay = searchParams.get('hidemessages');
|
||||
userOptions.hideMessagesOverlay = Boolean(hideMessagesOverlay);
|
||||
userOptions.hideMessagesOverlay = isStringBoolean(hideMessagesOverlay);
|
||||
|
||||
const hideEndMessage = searchParams.get('hideendmessage');
|
||||
userOptions.hideEndMessage = Boolean(hideEndMessage);
|
||||
userOptions.hideEndMessage = isStringBoolean(hideEndMessage);
|
||||
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playback !== Playback.Pause;
|
||||
const isNegative =
|
||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||
const showEndMessage = time.current < 0 && general.endMessage && !hideEndMessage;
|
||||
const showEndMessage = (time.current ?? 0) < 0 && general.endMessage && !hideEndMessage;
|
||||
const showFinished =
|
||||
time.finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@ function createWindow() {
|
||||
height: 1000,
|
||||
minWidth: 525,
|
||||
minHeight: 405,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1440,
|
||||
backgroundColor: '#101010', // $gray-1350
|
||||
icon: appIcon,
|
||||
show: false,
|
||||
|
||||
@@ -82,6 +82,11 @@ export function dispatchFromAdapter(type: string, payload: unknown, source?: 'os
|
||||
break;
|
||||
}
|
||||
|
||||
case 'start-next': {
|
||||
PlaybackService.startNext();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'startindex': {
|
||||
const eventIndex = Number(payload);
|
||||
if (isNaN(eventIndex) || eventIndex <= 0) {
|
||||
|
||||
@@ -5,7 +5,7 @@ export const event: Omit<OntimeEvent, 'id'> = {
|
||||
subtitle: '',
|
||||
presenter: '',
|
||||
note: '',
|
||||
endAction: EndAction.Continue,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
timeStart: 0,
|
||||
timeEnd: 0,
|
||||
|
||||
@@ -116,15 +116,15 @@ export class PlaybackService {
|
||||
return true;
|
||||
}
|
||||
} else if (fallbackAction === 'stop') {
|
||||
logger.info('PLAYBACK', `No next event found! Stopping playback`);
|
||||
logger.info('PLAYBACK', 'No next event found! Stopping playback');
|
||||
PlaybackService.stop();
|
||||
return false;
|
||||
} else if (fallbackAction === 'pause') {
|
||||
logger.info('PLAYBACK', `No next event found! Pausing playback`);
|
||||
logger.info('PLAYBACK', 'No next event found! Pausing playback');
|
||||
PlaybackService.pause();
|
||||
return false;
|
||||
} else {
|
||||
logger.info('PLAYBACK', `No next event found! Continuing playback`);
|
||||
logger.info('PLAYBACK', 'No next event found! Continuing playback');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,10 +371,11 @@ export const fileHandler = async (file) => {
|
||||
res.data.userFields = parseUserFields(dataFromExcel);
|
||||
res.message = 'success';
|
||||
} else {
|
||||
console.log('Error: No sheets found named ontime or event schedule');
|
||||
const errorMessage = 'No sheet found named ontime or event schedule';
|
||||
console.log(errorMessage);
|
||||
res = {
|
||||
error: true,
|
||||
message: `No sheets found named ontime or event schedule`,
|
||||
message: errorMessage,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -91,7 +91,7 @@ export const parseEventData = (data, enforce): EventData => {
|
||||
};
|
||||
} else if (enforce) {
|
||||
newEventData = { ...dbModel.eventData };
|
||||
console.log(`Created event object in db`);
|
||||
console.log('Created event object in db');
|
||||
}
|
||||
return newEventData as EventData;
|
||||
};
|
||||
@@ -126,7 +126,7 @@ export const parseSettings = (data, enforce): Settings => {
|
||||
}
|
||||
} else if (enforce) {
|
||||
newSettings = dbModel.settings;
|
||||
console.log(`Created settings object in db`);
|
||||
console.log('Created settings object in db');
|
||||
}
|
||||
return newSettings as Settings;
|
||||
};
|
||||
@@ -153,7 +153,7 @@ export const parseViewSettings = (data, enforce): ViewSettings => {
|
||||
};
|
||||
} else if (enforce) {
|
||||
newViews = dbModel.viewSettings;
|
||||
console.log(`Created viewSettings object in db`);
|
||||
console.log('Created viewSettings object in db');
|
||||
}
|
||||
return newViews as ViewSettings;
|
||||
};
|
||||
@@ -201,7 +201,7 @@ export const parseOsc = (data: { osc?: Partial<OSCSettings> }, enforce: boolean)
|
||||
subscriptions: validatedSubscriptions,
|
||||
};
|
||||
} else if (enforce) {
|
||||
console.log(`Created OSC object in db`);
|
||||
console.log('Created OSC object in db');
|
||||
return { ...dbModel.osc };
|
||||
} else return {};
|
||||
};
|
||||
@@ -232,7 +232,7 @@ export const parseHttp = (data, enforce) => {
|
||||
} else if (enforce) {
|
||||
// @ts-expect-error -- not yet
|
||||
newHttp.http = { ...dbModel.http };
|
||||
console.log(`Created http object in db`);
|
||||
console.log('Created http object in db');
|
||||
}
|
||||
return newHttp;
|
||||
};
|
||||
|
||||
+94
-1
@@ -1,5 +1,98 @@
|
||||
{
|
||||
"rundown": [],
|
||||
"rundown": [
|
||||
{
|
||||
"title": "First test event",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 32400000,
|
||||
"timeEnd": 36000000,
|
||||
"duration": 3600000,
|
||||
"isPublic": true,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "aa42f"
|
||||
},
|
||||
{
|
||||
"duration": 600000,
|
||||
"type": "delay",
|
||||
"revision": 0,
|
||||
"id": "b1d5a"
|
||||
},
|
||||
{
|
||||
"title": "Second test event",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 36000000,
|
||||
"timeEnd": 39600000,
|
||||
"duration": 3600000,
|
||||
"isPublic": true,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "d71bc"
|
||||
},
|
||||
{
|
||||
"title": "Lunch",
|
||||
"type": "block",
|
||||
"id": "91682"
|
||||
},
|
||||
{
|
||||
"title": "Third test event",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 39600000,
|
||||
"timeEnd": 720000,
|
||||
"duration": 0,
|
||||
"isPublic": true,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "da5b4"
|
||||
}
|
||||
],
|
||||
"eventData": {
|
||||
"title": "All about Carlos demo event",
|
||||
"publicUrl": "www.getontime.no",
|
||||
|
||||
+47
-222
@@ -2,118 +2,15 @@
|
||||
"rundown": [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"subtitle": "Subtitles are useful",
|
||||
"presenter": "cpvalente",
|
||||
"note": "Maybe a running note for the operator?",
|
||||
"timeStart": 28800000,
|
||||
"timeEnd": 30600000,
|
||||
"timeType": "start-end",
|
||||
"duration": 1800000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"title": "This is your event list",
|
||||
"subtitle": "",
|
||||
"presenter": "cpvalente",
|
||||
"note": "",
|
||||
"timeStart": 30600000,
|
||||
"timeEnd": 34200000,
|
||||
"timeType": "start-end",
|
||||
"duration": 3600000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "c2e7"
|
||||
},
|
||||
{
|
||||
"title": "Events run in the list order",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"timeStart": 34200000,
|
||||
"timeEnd": 34800000,
|
||||
"timeType": "start-end",
|
||||
"duration": 600000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "cc0f"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "In green, below",
|
||||
"timeStart": 34800000,
|
||||
"timeEnd": 35400000,
|
||||
"timeType": "start-end",
|
||||
"duration": 600000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "8ee5"
|
||||
},
|
||||
{
|
||||
"title": "Use simpler times to create a timer",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"note": "Ontime is an app for managing event rundowns",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 0,
|
||||
"timeEnd": 600000,
|
||||
"timeType": "start-end",
|
||||
"duration": 600000,
|
||||
"isPublic": false,
|
||||
"isPublic": true,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
@@ -127,100 +24,12 @@
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "8222"
|
||||
},
|
||||
{
|
||||
"duration": 900000,
|
||||
"type": "delay",
|
||||
"revision": 0,
|
||||
"id": "a386"
|
||||
},
|
||||
{
|
||||
"title": "Add delay blocks to affect all events",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "* Until a block is found",
|
||||
"timeStart": 35400000,
|
||||
"timeEnd": 36600000,
|
||||
"timeType": "start-end",
|
||||
"duration": 1200000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "6dce"
|
||||
},
|
||||
{
|
||||
"title": "Add and remove events with [+] and [-]",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "Orange and red buttons on the right",
|
||||
"timeStart": 36600000,
|
||||
"timeEnd": 43200000,
|
||||
"timeType": "start-end",
|
||||
"duration": 6600000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "2651"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "e6a1"
|
||||
},
|
||||
{
|
||||
"title": "And control whether they are public",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "Blue button on the right",
|
||||
"timeStart": 46800000,
|
||||
"timeEnd": 57600000,
|
||||
"timeType": "start-end",
|
||||
"duration": 10800000,
|
||||
"isPublic": false,
|
||||
"skip": false,
|
||||
"colour": "",
|
||||
"user0": "",
|
||||
"user1": "",
|
||||
"user2": "",
|
||||
"user3": "",
|
||||
"user4": "",
|
||||
"user5": "",
|
||||
"user6": "",
|
||||
"user7": "",
|
||||
"user8": "",
|
||||
"user9": "",
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "1358"
|
||||
"revision": 1,
|
||||
"id": "5a6e1"
|
||||
}
|
||||
],
|
||||
"eventData": {
|
||||
"title": "All about Carlos demo event",
|
||||
"title": "Ontime demo event",
|
||||
"publicUrl": "www.getontime.no",
|
||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||
"backstageUrl": "www.getontime.no",
|
||||
@@ -229,19 +38,55 @@
|
||||
},
|
||||
"settings": {
|
||||
"app": "ontime",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"serverPort": 4001,
|
||||
"lock": null,
|
||||
"pinCode": "1234"
|
||||
"pinCode": "1234",
|
||||
"timeFormat": "24"
|
||||
},
|
||||
"viewSettings": {
|
||||
"overrideStyles": false
|
||||
},
|
||||
"aliases": [
|
||||
{
|
||||
"id": "0b0b3",
|
||||
"enabled": true,
|
||||
"alias": "test",
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
],
|
||||
"userFields": {
|
||||
"user0": "user0",
|
||||
"user1": "user1",
|
||||
"user2": "user2",
|
||||
"user3": "user3",
|
||||
"user4": "user4",
|
||||
"user5": "user5",
|
||||
"user6": "user6",
|
||||
"user7": "user7",
|
||||
"user8": "user8",
|
||||
"user9": "user9"
|
||||
},
|
||||
"osc": {
|
||||
"port": 8888,
|
||||
"portIn": 8888,
|
||||
"portOut": 9999,
|
||||
"targetIP": "127.0.0.1",
|
||||
"enabled": true
|
||||
"enabledIn": true,
|
||||
"enabledOut": true,
|
||||
"subscriptions": {
|
||||
"onLoad": [],
|
||||
"onStart": [],
|
||||
"onPause": [],
|
||||
"onStop": [],
|
||||
"onUpdate": [
|
||||
{
|
||||
"id": "10eea",
|
||||
"enabled": true,
|
||||
"message": "/ontime/update/{{timer.current}}"
|
||||
}
|
||||
],
|
||||
"onFinish": []
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"http": {
|
||||
@@ -275,25 +120,5 @@
|
||||
},
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"aliases": [
|
||||
{
|
||||
"id": "0b0b3",
|
||||
"enabled": true,
|
||||
"alias": "test",
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
],
|
||||
"userFields": {
|
||||
"user0": "user0",
|
||||
"user1": "user1",
|
||||
"user2": "user2",
|
||||
"user3": "user3",
|
||||
"user4": "user4",
|
||||
"user5": "user5",
|
||||
"user6": "user6",
|
||||
"user7": "user7",
|
||||
"user8": "user8",
|
||||
"user9": "user9"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('minimal view behaviour can be changed through params', () => {
|
||||
test.use({ viewport: { width: 1920, height: 1080 } });
|
||||
test('without overloads', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/minimal');
|
||||
await page.getByTestId('minimal-timer').click();
|
||||
});
|
||||
test('hide nav', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/minimal?hidenav=true');
|
||||
const navBar = await page.locator('data-test-id=nav-logo');
|
||||
await expect(navBar).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
export enum EndAction {
|
||||
Continue = 'continue',
|
||||
None = 'none',
|
||||
Stop = 'stop',
|
||||
LoadNext = 'load-next',
|
||||
PlayNext = 'play-next',
|
||||
|
||||
Reference in New Issue
Block a user