mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 04:43:35 +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:
@@ -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>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user