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:
Fabian Posenau
2023-06-04 21:25:56 +02:00
committed by GitHub
parent 07737cc8b3
commit 5cb8020ab9
24 changed files with 196 additions and 66 deletions
@@ -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 = () => {
+2
View File
@@ -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>
) : (
+12
View File
@@ -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%;
}
}
+4
View File
@@ -1,3 +1,7 @@
.mirror {
transform: rotate(180deg);
}
.blackout {
opacity: 0;
}
+2
View File
@@ -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
*/
+3 -3
View File
@@ -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,
+9 -9
View File
@@ -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()
});
});
});
+15 -13
View File
@@ -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');
+3 -3
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 };
+4
View File
@@ -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: [
{
+12 -8
View File
@@ -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