mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 18:03:47 +00:00
Timer/blinking and blackout buttons (#406)
*feat: timer blink *feat: blackout --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
@@ -32,6 +32,8 @@ export const setMessage = {
|
||||
lowerText: (payload: string) => socketSendJson('set-lower-message-text', payload),
|
||||
lowerVisible: (payload: boolean) => socketSendJson('set-lower-message-visible', payload),
|
||||
onAir: (payload: boolean) => socketSendJson('set-onAir', payload),
|
||||
timerBlink: (payload: boolean) => socketSendJson('set-timer-blink', payload),
|
||||
timerBlackout: (payload: boolean) => socketSendJson('set-timer-blackout', payload),
|
||||
};
|
||||
|
||||
export const usePlaybackControl = () => {
|
||||
|
||||
@@ -22,6 +22,8 @@ export const runtimeStorePlaceholder = {
|
||||
timerMessage: {
|
||||
text: '',
|
||||
visible: false,
|
||||
timerBlink: false,
|
||||
timerBlackout: false,
|
||||
},
|
||||
publicMessage: {
|
||||
text: '',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IoEye } from '@react-icons/all-files/io5/IoEye';
|
||||
import { IoEyeOffOutline } from '@react-icons/all-files/io5/IoEyeOffOutline';
|
||||
|
||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import style from './InputRow.module.scss';
|
||||
@@ -14,17 +15,19 @@ interface InputRowProps {
|
||||
visible?: boolean;
|
||||
actionHandler: (action: string, payload: object) => void;
|
||||
changeHandler: (newValue: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function InputRow(props: InputRowProps) {
|
||||
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
|
||||
const { label, placeholder, text, visible, actionHandler, changeHandler, className } = props;
|
||||
|
||||
const handleInputChange = (newValue: string) => {
|
||||
changeHandler(newValue);
|
||||
};
|
||||
const classes = cx([style.inputRow, className]);
|
||||
|
||||
return (
|
||||
<div className={style.inputRow}>
|
||||
<div className={classes}>
|
||||
<label className={`${style.label} ${visible ? style.active : ''}`}>{label}</label>
|
||||
<div className={style.inputItems}>
|
||||
<Input
|
||||
@@ -39,7 +42,7 @@ export default function InputRow(props: InputRowProps) {
|
||||
tooltip={visible ? 'Make invisible' : 'Make visible'}
|
||||
aria-label={`Toggle ${label}`}
|
||||
openDelay={tooltipDelayMid}
|
||||
icon={visible? <IoEye size='18px' /> : <IoEyeOffOutline size='18px' />}
|
||||
icon={visible ? <IoEye size='18px' /> : <IoEyeOffOutline size='18px' />}
|
||||
variant={visible ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='sm'
|
||||
/>
|
||||
|
||||
@@ -7,12 +7,18 @@
|
||||
}
|
||||
|
||||
.onAirSection {
|
||||
margin-top: $section-spacing;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $element-spacing;
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: $element-spacing;
|
||||
margin-top: -0.5rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: $inner-section-text-size;
|
||||
color: $label-gray;
|
||||
@@ -21,3 +27,7 @@
|
||||
color: $action-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.padTop {
|
||||
margin-top: $section-spacing;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoEye } from '@react-icons/all-files/io5/IoEye';
|
||||
import { IoEyeOffOutline } from '@react-icons/all-files/io5/IoEyeOffOutline';
|
||||
import { IoMicOffOutline } from '@react-icons/all-files/io5/IoMicOffOutline';
|
||||
import { IoMicSharp } from '@react-icons/all-files/io5/IoMicSharp';
|
||||
import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
|
||||
import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline';
|
||||
|
||||
import { setMessage, useMessageControl } from '../../../common/hooks/useSocket';
|
||||
|
||||
@@ -13,14 +17,6 @@ export default function MessageControl() {
|
||||
|
||||
return (
|
||||
<div className={style.messageContainer}>
|
||||
<InputRow
|
||||
label='Timer screen message'
|
||||
placeholder='Shown in stage timer'
|
||||
text={data.timerMessage.text || ''}
|
||||
visible={data.timerMessage.visible || false}
|
||||
changeHandler={(newValue) => setMessage.presenterText(newValue)}
|
||||
actionHandler={() => setMessage.presenterVisible(!data.timerMessage.visible)}
|
||||
/>
|
||||
<InputRow
|
||||
label='Public / Backstage screen message'
|
||||
placeholder='Shown in public and backstage screens'
|
||||
@@ -37,7 +33,33 @@ export default function MessageControl() {
|
||||
changeHandler={(newValue) => setMessage.lowerText(newValue)}
|
||||
actionHandler={() => setMessage.lowerVisible(!data.lowerMessage.visible)}
|
||||
/>
|
||||
<div className={style.onAirSection}>
|
||||
<InputRow
|
||||
label='Timer message'
|
||||
placeholder='Shown in stage timer'
|
||||
text={data.timerMessage.text || ''}
|
||||
visible={data.timerMessage.visible || false}
|
||||
changeHandler={(newValue) => setMessage.presenterText(newValue)}
|
||||
actionHandler={() => setMessage.presenterVisible(!data.timerMessage.visible)}
|
||||
/>
|
||||
<div className={style.buttonSection}>
|
||||
<label className={style.label}>Timer messsage blink</label>
|
||||
<label className={style.label}>Blackout timer screens</label>
|
||||
<Button
|
||||
className={`${data.timerMessage.timerBlink ? style.blink : ''}`}
|
||||
variant={data.timerMessage.timerBlink ? 'ontime-filled' : 'ontime-subtle'}
|
||||
leftIcon={data.timerMessage.timerBlink ? <IoSunny size='24px' /> : <IoSunnyOutline size='24px' />}
|
||||
onClick={() => setMessage.timerBlink(!data.timerMessage.timerBlink)}
|
||||
data-testid='toggle timer blink'
|
||||
/>
|
||||
<Button
|
||||
className={style.blackoutButton}
|
||||
variant={data.timerMessage.timerBlackout ? 'ontime-filled' : 'ontime-subtle'}
|
||||
leftIcon={data.timerMessage.timerBlackout ? <IoEye size='24px' /> : <IoEyeOffOutline size='24px' />}
|
||||
onClick={() => setMessage.timerBlackout(!data.timerMessage.timerBlackout)}
|
||||
data-testid='toggle timer blackout'
|
||||
/>
|
||||
</div>
|
||||
<div className={`${style.onAirSection} ${style.padTop}`}>
|
||||
<label className={style.label}>Toggle On Air state</label>
|
||||
<Button
|
||||
variant={data.onAir ? 'ontime-filled' : 'ontime-subtle'}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
height: 100vh;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
|
||||
background: var(--background-color-override, $viewer-background-color);
|
||||
color: var(--color-override, $viewer-color);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { EventData, Message, Playback, TimerType, ViewSettings } from 'ontime-types';
|
||||
import { EventData, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
@@ -14,7 +14,7 @@ import './MinimalTimer.scss';
|
||||
|
||||
interface MinimalTimerProps {
|
||||
isMirrored: boolean;
|
||||
pres: Message;
|
||||
pres: TimerMessage;
|
||||
time: TimeManagerType;
|
||||
viewSettings: ViewSettings;
|
||||
general: EventData;
|
||||
@@ -136,6 +136,9 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
const showProgress = time.playback !== Playback.Stop;
|
||||
const showWarning = (time.current ?? 1) < viewSettings.warningThreshold;
|
||||
const showDanger = (time.current ?? 1) < viewSettings.dangerThreshold;
|
||||
const showBlinking = pres.timerBlink;
|
||||
const showBlackout = pres.timerBlackout;
|
||||
|
||||
const timerColor = userOptions.textColour
|
||||
? userOptions.textColour
|
||||
: showProgress && showDanger
|
||||
@@ -153,8 +156,10 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
|
||||
const timerFontSize = (89 / (stageTimerCharacters - 1)) * (userOptions.size || 1);
|
||||
|
||||
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''} ${
|
||||
showBlinking ? (showOverlay ? '' : 'blink') : ''
|
||||
}`;
|
||||
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''} ${showBlackout ? 'blackout' : ''}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -169,11 +174,13 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
<NavigationMenu />
|
||||
{!hideMessagesOverlay && (
|
||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||
<div className='message'>{pres.text}</div>
|
||||
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
|
||||
</div>
|
||||
)}
|
||||
{showEndMessage ? (
|
||||
<div className='end-message'>{viewSettings.endMessage}</div>
|
||||
<div className={`end-message ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`}>
|
||||
{viewSettings.endMessage}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={timerClasses}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
height: 100vh;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
|
||||
font-family: var(--font-family-override, $viewer-font-family);
|
||||
background: var(--background-color-override, $viewer-background-color);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { EventData, Message, Playback, TimerType, ViewSettings } from 'ontime-types';
|
||||
import { EventData, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
||||
@@ -39,7 +39,7 @@ const titleVariants = {
|
||||
interface TimerProps {
|
||||
isMirrored: boolean;
|
||||
general: EventData;
|
||||
pres: Message;
|
||||
pres: TimerMessage;
|
||||
title: TitleManager;
|
||||
time: TimeManagerType;
|
||||
viewSettings: ViewSettings;
|
||||
@@ -73,14 +73,16 @@ export default function Timer(props: TimerProps) {
|
||||
const showFinished = finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
const showWarning = (time.current ?? 1) < viewSettings.warningThreshold;
|
||||
const showDanger = (time.current ?? 1) < viewSettings.dangerThreshold;
|
||||
const showBlinking = pres.timerBlink;
|
||||
const showBlackout = pres.timerBlackout;
|
||||
const showClock = time.timerType !== TimerType.Clock;
|
||||
|
||||
const timerColor =
|
||||
showProgress && showDanger
|
||||
? viewSettings.dangerColor
|
||||
: showProgress && showWarning
|
||||
? viewSettings.warningColor
|
||||
: viewSettings.normalColor;
|
||||
const showClock = time.timerType !== TimerType.Clock;
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
|
||||
const stageTimer = getTimerByType(time);
|
||||
let display = formatTimerDisplay(stageTimer);
|
||||
@@ -89,6 +91,7 @@ export default function Timer(props: TimerProps) {
|
||||
display = `-${display}`;
|
||||
}
|
||||
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''} ${showBlackout ? 'blackout' : ''}`;
|
||||
const timerFontSize = 89 / (stageTimerCharacters - 1);
|
||||
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||
|
||||
@@ -96,7 +99,7 @@ export default function Timer(props: TimerProps) {
|
||||
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
||||
<NavigationMenu />
|
||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||
<div className='message'>{pres.text}</div>
|
||||
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
|
||||
</div>
|
||||
|
||||
<div className={`clock-container ${showClock ? '' : 'clock-container--hidden'}`}>
|
||||
@@ -104,7 +107,7 @@ export default function Timer(props: TimerProps) {
|
||||
<div className='clock'>{clock}</div>
|
||||
</div>
|
||||
|
||||
<div className='timer-container'>
|
||||
<div className={`timer-container ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`}>
|
||||
{showEndMessage ? (
|
||||
<div className='end-message'>{viewSettings.endMessage}</div>
|
||||
) : (
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
$transition-time-action: 0.1s;
|
||||
$transition-time-feedback: 0.3s;
|
||||
|
||||
$blinking-time: 1.2s;
|
||||
|
||||
$component-border-radius-md: 3px;
|
||||
$component-border-radius-sm: 2px;
|
||||
|
||||
@@ -51,3 +53,13 @@ $secondary-text-gray: $gray-400;
|
||||
$section-white: $ui-white;
|
||||
$inner-section-text-size: 14px;
|
||||
$text-body-size: 15px;
|
||||
|
||||
.blink {
|
||||
animation: blink $blinking-time linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 20%;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
.mirror {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.blackout {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
// General
|
||||
$viewer-transition-time: 0.5s;
|
||||
|
||||
|
||||
// Text
|
||||
$viewer-font-family: "Open Sans", "Segoe UI", sans-serif; // --font-family-override
|
||||
$viewer-opacity-disabled: 0.6;
|
||||
@@ -26,3 +27,4 @@ $element-border-radius: 8px;
|
||||
$timer-color: rgba(white, 80%); // --timer-color-override
|
||||
$timer-finished-color: $playback-negative;
|
||||
$timer-bold-font-family: "Arial Black", sans-serif; // --card-background-color-override
|
||||
|
||||
|
||||
@@ -32,6 +32,20 @@ export function dispatchFromAdapter(type: string, payload: unknown, source?: 'os
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set-timer-blink': {
|
||||
if (typeof payload !== 'undefined') {
|
||||
messageService.setTimerBlink(Boolean(payload));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set-timer-blackout': {
|
||||
if (typeof payload !== 'undefined') {
|
||||
messageService.setTimerBlackout(Boolean(payload));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set-timer-message-text': {
|
||||
if (typeof payload !== 'string') {
|
||||
throw new Error(`Unable to parse payload: ${payload}`);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Message } from 'ontime-types';
|
||||
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
import { TimerMessage } from 'ontime-types/src/definitions/runtime/MessageControl.type.js';
|
||||
|
||||
let instance;
|
||||
|
||||
class MessageService {
|
||||
timerMessage: Message;
|
||||
timerMessage: TimerMessage;
|
||||
publicMessage: Message;
|
||||
lowerMessage: Message;
|
||||
onAir: boolean;
|
||||
@@ -21,6 +22,8 @@ class MessageService {
|
||||
this.timerMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
timerBlink: false,
|
||||
timerBlackout: false,
|
||||
};
|
||||
|
||||
this.publicMessage = {
|
||||
@@ -103,6 +106,34 @@ class MessageService {
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of timer blink, toggles if parameters are offered
|
||||
*/
|
||||
|
||||
setTimerBlink(status?: boolean) {
|
||||
if (typeof status === 'undefined') {
|
||||
this.timerMessage.timerBlink = !this.timerMessage.timerBlink;
|
||||
} else {
|
||||
this.timerMessage.timerBlink = status;
|
||||
}
|
||||
eventStore.set('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of timer blackout, toggles if parameters are offered
|
||||
*/
|
||||
|
||||
setTimerBlackout(status?: boolean) {
|
||||
if (typeof status === 'undefined') {
|
||||
this.timerMessage.timerBlackout = !this.timerMessage.timerBlackout;
|
||||
} else {
|
||||
this.timerMessage.timerBlackout = status;
|
||||
}
|
||||
eventStore.set('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns feature data
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 32400000,
|
||||
"timeEnd": 36000000,
|
||||
@@ -38,7 +38,7 @@
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 36000000,
|
||||
"timeEnd": 39600000,
|
||||
@@ -70,7 +70,7 @@
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 39600000,
|
||||
"timeEnd": 720000,
|
||||
|
||||
@@ -7,11 +7,11 @@ test.describe('pages routes are available', () => {
|
||||
await page.goto('http://localhost:4001/editor');
|
||||
|
||||
await expect(page).toHaveTitle(/ontime/);
|
||||
await page.getByTestId('event-editor').click();
|
||||
await page.getByTestId('panel-rundown').click();
|
||||
await page.getByTestId('panel-timer-control').click();
|
||||
await page.getByTestId('panel-messages-control').click();
|
||||
await page.getByTestId('panel-info').click();
|
||||
await expect(page.getByTestId('event-editor')).toBeVisible()
|
||||
await expect(page.getByTestId('panel-rundown')).toBeVisible()
|
||||
await expect(page.getByTestId('panel-timer-control')).toBeVisible()
|
||||
await expect(page.getByTestId('panel-messages-control')).toBeVisible()
|
||||
await expect(page.getByTestId('panel-info')).toBeVisible()
|
||||
});
|
||||
test('cuesheet', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/cuesheet');
|
||||
@@ -24,19 +24,19 @@ test.describe('pages routes are available', () => {
|
||||
test.describe('detached views', () => {
|
||||
test('rundown', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/rundown');
|
||||
await page.getByTestId('panel-rundown').click();
|
||||
await expect(page.getByTestId('panel-rundown')).toBeVisible()
|
||||
});
|
||||
test('timer control', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/timercontrol');
|
||||
await page.getByTestId('panel-timer-control').click();
|
||||
await expect(page.getByTestId('panel-timer-control')).toBeVisible()
|
||||
});
|
||||
test('message control', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/messagecontrol');
|
||||
await page.getByTestId('panel-messages-control').click();
|
||||
await expect(page.getByTestId('panel-messages-control')).toBeVisible()
|
||||
});
|
||||
test('info', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/info');
|
||||
await page.getByTestId('panel-info').click();
|
||||
await expect(page.getByTestId('panel-info')).toBeVisible()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,34 +6,36 @@ test('test', async ({ context }) => {
|
||||
|
||||
await editorPage.goto('http://localhost:4001/messagecontrol');
|
||||
|
||||
// stage timer message
|
||||
await editorPage.getByPlaceholder('Shown in stage timer').click();
|
||||
await expect(editorPage.getByPlaceholder('Shown in stage timer')).toBeEnabled();
|
||||
|
||||
await editorPage.getByPlaceholder('Shown in stage timer').fill('testing stage');
|
||||
await editorPage.getByRole('button', { name: /toggle timer screen message/i }).click({ timeout: 5000 });
|
||||
|
||||
await featurePage.goto('http://localhost:4001/timer');
|
||||
await featurePage.getByText('testing stage').click();
|
||||
|
||||
// public screen message
|
||||
await editorPage.getByPlaceholder('Shown in public and backstage screens').click();
|
||||
await editorPage.getByPlaceholder('Shown in public and backstage screens').fill('testing public');
|
||||
await editorPage.getByRole('button', { name: 'Toggle Public / Backstage screen message' }).click();
|
||||
|
||||
await featurePage.goto('http://localhost:4001/public');
|
||||
await featurePage.getByText('testing public').click({ timeout: 10000 });
|
||||
await featurePage.waitForLoadState('load', { timeout: 5000 });
|
||||
await featurePage.getByText('testing public').click({ timeout: 5000 });
|
||||
|
||||
// lower third message
|
||||
await editorPage.getByPlaceholder('Shown in lower third').click();
|
||||
await editorPage.getByPlaceholder('Shown in lower third').fill('testing lower');
|
||||
await editorPage.getByRole('button', { name: /toggle lower third message/i }).click();
|
||||
|
||||
await featurePage.goto('http://localhost:4001/lower');
|
||||
// increase the timeout to accommodate animation
|
||||
await featurePage.getByText('testing lower').click({ timeout: 10000 });
|
||||
await featurePage.waitForLoadState('load', { timeout: 5000 });
|
||||
await featurePage.getByText('testing lower').click({ timeout: 5000 });
|
||||
|
||||
// stage timer message
|
||||
await editorPage.getByPlaceholder('Shown in stage timer').click();
|
||||
await editorPage.getByPlaceholder('Shown in stage timer').fill('testing stage');
|
||||
await editorPage.getByRole('button', { name: /toggle timer message/i }).click({ timeout: 5000 });
|
||||
|
||||
await featurePage.goto('http://localhost:4001/timer');
|
||||
await featurePage.waitForLoadState('load', { timeout: 5000 });
|
||||
await featurePage.getByText('testing stage').click();
|
||||
|
||||
// on air state
|
||||
await editorPage.getByTestId('toggle on air').click();
|
||||
|
||||
await featurePage.goto('http://localhost:4001/studio');
|
||||
await featurePage.getByText('ON AIR').click();
|
||||
const onAirActive = await featurePage.locator('data-testid=on-air-enabled');
|
||||
|
||||
Vendored
+3
-3
@@ -5,7 +5,7 @@
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 32400000,
|
||||
"timeEnd": 36000000,
|
||||
@@ -38,7 +38,7 @@
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 36000000,
|
||||
"timeEnd": 39600000,
|
||||
@@ -70,7 +70,7 @@
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "",
|
||||
"endAction": "continue",
|
||||
"endAction": "none",
|
||||
"timerType": "count-down",
|
||||
"timeStart": 39600000,
|
||||
"timeEnd": 720000,
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
"cleanup": "rm -rf node_modules && rm -rf **/node_modules && rm -rf **/**/node_modules"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.32.3",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
|
||||
@@ -2,3 +2,8 @@ export type Message = {
|
||||
text: string;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export type TimerMessage = Message & {
|
||||
timerBlink: boolean;
|
||||
timerBlackout: boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Playback } from './Playback.type.js';
|
||||
import { Message } from './MessageControl.type.js';
|
||||
import { Message, TimerMessage } from './MessageControl.type.js';
|
||||
import { TimerState } from './TimerState.type.js';
|
||||
import { TitleBlock } from './TitleBlock.type.js';
|
||||
import { Loaded } from './Playlist.type.js';
|
||||
@@ -10,7 +10,7 @@ export type RuntimeStore = {
|
||||
playback: Playback;
|
||||
|
||||
// messages service
|
||||
timerMessage: Message;
|
||||
timerMessage: TimerMessage;
|
||||
publicMessage: Message;
|
||||
lowerMessage: Message;
|
||||
onAir: boolean;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Alias } from './definitions/core/Alias.type.js';
|
||||
import { DatabaseModel } from './definitions/DataModel.type.js';
|
||||
import { EndAction } from './definitions/EndAction.type.js';
|
||||
import { EventData } from './definitions/core/EventData.type.js';
|
||||
import { Message } from './definitions/runtime/MessageControl.type.js';
|
||||
import { Message, TimerMessage } from './definitions/runtime/MessageControl.type.js';
|
||||
import {
|
||||
OntimeBaseEvent,
|
||||
OntimeBlock,
|
||||
@@ -61,6 +61,7 @@ export { Playback };
|
||||
export { TimerLifeCycle };
|
||||
|
||||
export type { Message };
|
||||
export type { TimerMessage };
|
||||
export type { Loaded };
|
||||
export type { RuntimeStore };
|
||||
export type { TimerState };
|
||||
|
||||
@@ -23,11 +23,15 @@ const config: PlaywrightTestConfig = {
|
||||
},
|
||||
use: {
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
actionTimeout: 5000,
|
||||
baseURL: 'http://localhost:4001',
|
||||
ignoreHTTPSErrors: true,
|
||||
trace: 'on-first-retry',
|
||||
launchOptions: {
|
||||
slowMo: process.env.CI ? undefined : 250
|
||||
}
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
|
||||
Generated
+12
-8
@@ -4,7 +4,7 @@ importers:
|
||||
|
||||
.:
|
||||
specifiers:
|
||||
'@playwright/test': ^1.32.3
|
||||
'@playwright/test': ^1.34.3
|
||||
'@types/node': ^18.11.18
|
||||
'@typescript-eslint/eslint-plugin': ^5.48.0
|
||||
'@typescript-eslint/parser': ^5.48.0
|
||||
@@ -16,7 +16,7 @@ importers:
|
||||
turbo: ^1.8.8
|
||||
typescript: ^4.8.3
|
||||
devDependencies:
|
||||
'@playwright/test': 1.32.3
|
||||
'@playwright/test': 1.34.3
|
||||
'@types/node': 18.11.18
|
||||
'@typescript-eslint/eslint-plugin': 5.48.0_k73wpmdolxikpyqun3p36akaaq
|
||||
'@typescript-eslint/parser': 5.48.0_iukboom6ndih5an6iafl45j2fe
|
||||
@@ -2167,13 +2167,13 @@ packages:
|
||||
fastq: 1.15.0
|
||||
dev: true
|
||||
|
||||
/@playwright/test/1.32.3:
|
||||
resolution: {integrity: sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==}
|
||||
/@playwright/test/1.34.3:
|
||||
resolution: {integrity: sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 18.15.11
|
||||
playwright-core: 1.32.3
|
||||
'@types/node': 18.16.16
|
||||
playwright-core: 1.34.3
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
@@ -2804,6 +2804,10 @@ packages:
|
||||
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
|
||||
dev: true
|
||||
|
||||
/@types/node/18.16.16:
|
||||
resolution: {integrity: sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g==}
|
||||
dev: true
|
||||
|
||||
/@types/normalize-package-data/2.4.1:
|
||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||
dev: true
|
||||
@@ -6786,8 +6790,8 @@ packages:
|
||||
pathe: 1.1.0
|
||||
dev: true
|
||||
|
||||
/playwright-core/1.32.3:
|
||||
resolution: {integrity: sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==}
|
||||
/playwright-core/1.34.3:
|
||||
resolution: {integrity: sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
Reference in New Issue
Block a user