many hotfixes (#465)

* style: disable menu bar in run mode

* fix: external devices in dev mode

* fix: add missing elements to translations

* ux: improve translation strings

* refactor: improve typing

* style: blink on event change

* refactor: param options is object

---------

Co-authored-by: Hannes Rüger <hannesrueger@gmx.de>
This commit is contained in:
Carlos Valente
2023-07-23 15:10:53 +02:00
committed by GitHub
parent bb1eb2838f
commit 31fc222876
15 changed files with 72 additions and 25 deletions
+6 -3
View File
@@ -12,9 +12,12 @@ export const APP_SETTINGS = ['appSettings'];
export const VIEW_SETTINGS = ['viewSettings']; export const VIEW_SETTINGS = ['viewSettings'];
export const RUNTIME = ['runtimeStore']; export const RUNTIME = ['runtimeStore'];
export const serverPort = import.meta.env.DEV ? STATIC_PORT : window.location.port; const location = window.location;
export const serverURL = import.meta.env.DEV ? `http://localhost:${serverPort}` : window.location.origin; const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
export const websocketUrl = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.hostname}:${serverPort}/ws`;
export const serverPort = import.meta.env.DEV ? STATIC_PORT : location.port;
export const serverURL = import.meta.env.DEV ? `http://${location.hostname}:${serverPort}` : location.origin;
export const websocketUrl = `${socketProtocol}://${location.hostname}:${serverPort}/ws`;
export const eventURL = `${serverURL}/eventdata`; export const eventURL = `${serverURL}/eventdata`;
export const rundownURL = `${serverURL}/events`; export const rundownURL = `${serverURL}/events`;
@@ -15,12 +15,12 @@ export default function ParamInput({ paramField }: EditFormInputProps) {
if (type === 'option') { if (type === 'option') {
const optionFromParams = searchParams.get(id); const optionFromParams = searchParams.get(id);
const defaultOptionValue = paramField.values.find((value) => value === optionFromParams); const defaultOptionValue = optionFromParams || undefined;
return ( return (
<Select placeholder='Select an option' variant='ontime' name={id} defaultValue={defaultOptionValue}> <Select placeholder='Select an option' variant='ontime' name={id} defaultValue={defaultOptionValue}>
{paramField.values.map((value) => ( {Object.entries(paramField.values).map(([key, value]) => (
<option key={value} value={value}> <option key={key} value={key}>
{value} {value}
</option> </option>
))} ))}
@@ -5,7 +5,7 @@ export const TIME_FORMAT_OPTION: ParamField = {
title: '12 / 24 hour timer', title: '12 / 24 hour timer',
description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences', description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences',
type: 'option', type: 'option',
values: ['12', '24'], values: { '12': '12 hour AM/PM', '24': '24 hour' },
}; };
export const CLOCK_OPTIONS: ParamField[] = [ export const CLOCK_OPTIONS: ParamField[] = [
@@ -45,7 +45,7 @@ export const CLOCK_OPTIONS: ParamField[] = [
title: 'Align Horizontal', title: 'Align Horizontal',
description: 'Moves the horizontally in page to start = left | center | end = right', description: 'Moves the horizontally in page to start = left | center | end = right',
type: 'option', type: 'option',
values: ['start', 'center', 'end'], values: { start: 'Start', center: 'Center', end: 'End' },
}, },
{ {
id: 'offsetx', id: 'offsetx',
@@ -58,7 +58,7 @@ export const CLOCK_OPTIONS: ParamField[] = [
title: 'Align Vertical', title: 'Align Vertical',
description: 'Moves the vertically in page to start = left | center | end = right', description: 'Moves the vertically in page to start = left | center | end = right',
type: 'option', type: 'option',
values: ['start', 'center', 'end'], values: { start: 'Start', center: 'Center', end: 'End' },
}, },
{ {
id: 'offsety', id: 'offsety',
@@ -106,7 +106,7 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
title: 'Align Horizontal', title: 'Align Horizontal',
description: 'Moves the horizontally in page to start = left | center | end = right', description: 'Moves the horizontally in page to start = left | center | end = right',
type: 'option', type: 'option',
values: ['start', 'center', 'end'], values: { start: 'Start', center: 'Center', end: 'End' },
}, },
{ {
id: 'offsetx', id: 'offsetx',
@@ -119,7 +119,7 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
title: 'Align Vertical', title: 'Align Vertical',
description: 'Moves the vertically in page to start = left | center | end = right', description: 'Moves the vertically in page to start = left | center | end = right',
type: 'option', type: 'option',
values: ['start', 'center', 'end'], values: { start: 'Start', center: 'Center', end: 'End' },
}, },
{ {
id: 'offsety', id: 'offsety',
@@ -4,7 +4,7 @@ type BaseField = {
description: string; description: string;
}; };
type OptionsField = { type: 'option'; values: string[] }; type OptionsField = { type: 'option'; values: Record<string, string> };
type StringField = { type: 'string' }; type StringField = { type: 'string' };
type BooleanField = { type: 'boolean' }; type BooleanField = { type: 'boolean' };
type NumberField = { type: 'number' }; type NumberField = { type: 'number' };
@@ -107,6 +107,7 @@ const MenuBar = (props: MenuBarProps) => {
<div className={style.gap} /> <div className={style.gap} />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
isDisabled={appMode === AppMode.Run}
icon={<IoColorWand />} icon={<IoColorWand />}
className={isQuickStartOpen ? style.open : ''} className={isQuickStartOpen ? style.open : ''}
clickHandler={onQuickStartOpen} clickHandler={onQuickStartOpen}
@@ -115,6 +116,7 @@ const MenuBar = (props: MenuBarProps) => {
/> />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
isDisabled={appMode === AppMode.Run}
icon={<IoPushOutline />} icon={<IoPushOutline />}
className={isUploadOpen ? style.open : ''} className={isUploadOpen ? style.open : ''}
clickHandler={onUploadOpen} clickHandler={onUploadOpen}
@@ -125,6 +127,7 @@ const MenuBar = (props: MenuBarProps) => {
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
icon={<IoSaveOutline />} icon={<IoSaveOutline />}
isDisabled={appMode === AppMode.Run}
clickHandler={downloadRundown} clickHandler={downloadRundown}
tooltip='Export project file' tooltip='Export project file'
aria-label='Export project file' aria-label='Export project file'
@@ -154,6 +157,7 @@ const MenuBar = (props: MenuBarProps) => {
<div className={style.gap} /> <div className={style.gap} />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
isDisabled={appMode === AppMode.Run}
icon={isIntegrationOpen ? <IoExtensionPuzzle /> : <IoExtensionPuzzleOutline />} icon={isIntegrationOpen ? <IoExtensionPuzzle /> : <IoExtensionPuzzleOutline />}
className={isIntegrationOpen ? style.open : ''} className={isIntegrationOpen ? style.open : ''}
clickHandler={onIntegrationOpen} clickHandler={onIntegrationOpen}
@@ -163,6 +167,7 @@ const MenuBar = (props: MenuBarProps) => {
/> />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
isDisabled={appMode === AppMode.Run}
icon={<IoSettingsOutline />} icon={<IoSettingsOutline />}
className={isSettingsOpen ? style.open : ''} className={isSettingsOpen ? style.open : ''}
clickHandler={onSettingsOpen} clickHandler={onSettingsOpen}
@@ -1,4 +1,5 @@
@use '../../../theme/viewerDefs' as *; @use '../../../theme/viewerDefs' as *;
@use '../../../theme/v2Styles' as *;
.backstage { .backstage {
margin: 0; margin: 0;
@@ -86,6 +87,10 @@
background-color: var(--card-background-color-override, $viewer-card-bg-color); background-color: var(--card-background-color-override, $viewer-card-bg-color);
padding: 16px 24px; padding: 16px 24px;
border-radius: 8px; border-radius: 8px;
&.blink {
animation: blink 0.5s ease-in-out 3;
}
} }
.timer-group { .timer-group {
@@ -119,7 +124,7 @@
grid-area: schedule; grid-area: schedule;
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
margin-left: clamp(16px, 5vw, 64px);; margin-left: clamp(16px, 5vw, 64px);
} }
.schedule-nav-container { .schedule-nav-container {
@@ -143,9 +148,20 @@
} }
.qr { .qr {
margin-left: clamp(16px, 5vw, 64px);; margin-left: clamp(16px, 5vw, 64px);
padding: 4px; padding: 4px;
background-color: white; background-color: white;
} }
} }
} }
/* =================== AMIMATION ===================*/
@keyframes blink {
0% {
background-color: var(--card-background-color-blink-override, $playback-start);
}
20% {
background-color: var(--card-background-color-override, $viewer-card-bg-color);
}
}
@@ -1,4 +1,4 @@
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { EventData, Message, OntimeEvent, ViewSettings } from 'ontime-types'; import { EventData, Message, OntimeEvent, ViewSettings } from 'ontime-types';
@@ -43,12 +43,24 @@ export default function Backstage(props: BackstageProps) {
const { isMirrored, publ, title, time, backstageEvents, selectedId, general, viewSettings } = props; const { isMirrored, publ, title, time, backstageEvents, selectedId, general, viewSettings } = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation(); const { getLocalizedString } = useTranslation();
const [blinkClass, setBlinkClass] = useState(false);
// Set window title // Set window title
useEffect(() => { useEffect(() => {
document.title = 'ontime - Backstage Screen'; document.title = 'ontime - Backstage Screen';
}, []); }, []);
// blink on change
useEffect(() => {
setBlinkClass(false);
const timer = setTimeout(() => {
setBlinkClass(true);
}, 10);
return () => clearTimeout(timer);
}, [selectedId]);
// defer rendering until we load stylesheets // defer rendering until we load stylesheets
if (!shouldRender) { if (!shouldRender) {
return null; return null;
@@ -57,7 +69,9 @@ export default function Backstage(props: BackstageProps) {
const clock = formatTime(time.clock, formatOptions); const clock = formatTime(time.clock, formatOptions);
const startedAt = formatTime(time.startedAt, formatOptions); const startedAt = formatTime(time.startedAt, formatOptions);
const isNegative = (time.current ?? 0) < 0; const isNegative = (time.current ?? 0) < 0;
const expectedFinish = isNegative ? 'In overtime' : formatTime(time.expectedFinish, formatOptions); const expectedFinish = isNegative
? getLocalizedString('countdown.overtime')
: formatTime(time.expectedFinish, formatOptions);
const qrSize = Math.max(window.innerWidth / 15, 128); const qrSize = Math.max(window.innerWidth / 15, 128);
const filteredEvents = getEventsWithDelay(backstageEvents); const filteredEvents = getEventsWithDelay(backstageEvents);
@@ -99,7 +113,7 @@ export default function Backstage(props: BackstageProps) {
<AnimatePresence> <AnimatePresence>
{title.showNow && ( {title.showNow && (
<motion.div <motion.div
className='event now' className={`event now ${blinkClass ? 'blink' : ''}`}
key='now' key='now'
variants={titleVariants} variants={titleVariants}
initial='hidden' initial='hidden'
+2 -1
View File
@@ -1,4 +1,4 @@
import { TranslationObject } from '../Translation.types'; import { TranslationObject } from './en';
export const langDe: TranslationObject = { export const langDe: TranslationObject = {
'common.end_time': 'Endzeit', 'common.end_time': 'Endzeit',
@@ -15,4 +15,5 @@ export const langDe: TranslationObject = {
'countdown.select_event': 'Wählen Sie eine Veranstaltung aus, um sie zu verfolgen', 'countdown.select_event': 'Wählen Sie eine Veranstaltung aus, um sie zu verfolgen',
'countdown.to_start': 'Zeit bis zum Start', 'countdown.to_start': 'Zeit bis zum Start',
'countdown.waiting': 'Warten auf den Veranstaltungsbeginn', 'countdown.waiting': 'Warten auf den Veranstaltungsbeginn',
'countdown.overtime': 'überfällig',
}; };
@@ -13,4 +13,7 @@ export const langEn = {
'countdown.select_event': 'Select an event to follow', 'countdown.select_event': 'Select an event to follow',
'countdown.to_start': 'Time to start', 'countdown.to_start': 'Time to start',
'countdown.waiting': 'Waiting for event start', 'countdown.waiting': 'Waiting for event start',
'countdown.overtime': 'in overtime',
}; };
export type TranslationObject = Record<keyof typeof langEn, string>;
+3 -2
View File
@@ -1,4 +1,4 @@
import { TranslationObject } from '../Translation.types'; import { TranslationObject } from './en';
export const langEs: TranslationObject = { export const langEs: TranslationObject = {
'common.end_time': 'Hora de finalización', 'common.end_time': 'Hora de finalización',
@@ -9,10 +9,11 @@ export const langEs: TranslationObject = {
'common.start_time': 'Hora de inicio', 'common.start_time': 'Hora de inicio',
'common.stage_timer': 'Temporizador de presentador', 'common.stage_timer': 'Temporizador de presentador',
'common.started_at': 'Iniciado en', 'common.started_at': 'Iniciado en',
'common.time_now': 'Hora actual', 'common.time_now': 'Ahora',
'countdown.ended': 'Evento finalizado a las', 'countdown.ended': 'Evento finalizado a las',
'countdown.running': 'Evento en curso', 'countdown.running': 'Evento en curso',
'countdown.select_event': 'Seleccionar un evento para seguir', 'countdown.select_event': 'Seleccionar un evento para seguir',
'countdown.to_start': 'Tiempo para comenzar', 'countdown.to_start': 'Tiempo para comenzar',
'countdown.waiting': 'Esperando el inicio del evento', 'countdown.waiting': 'Esperando el inicio del evento',
'countdown.overtime': 'en tiempo extra',
}; };
+3 -2
View File
@@ -1,4 +1,4 @@
import { TranslationObject } from '../Translation.types'; import { TranslationObject } from './en';
export const langNo: TranslationObject = { export const langNo: TranslationObject = {
'common.end_time': 'Sluttid', 'common.end_time': 'Sluttid',
@@ -9,10 +9,11 @@ export const langNo: TranslationObject = {
'common.start_time': 'Starttid', 'common.start_time': 'Starttid',
'common.stage_timer': 'Scenetimer', 'common.stage_timer': 'Scenetimer',
'common.started_at': 'Startet', 'common.started_at': 'Startet',
'common.time_now': 'Tid nå', 'common.time_now': 'Klokken nå',
'countdown.ended': 'Hendelse avsluttet', 'countdown.ended': 'Hendelse avsluttet',
'countdown.running': 'Hendelse pågår', 'countdown.running': 'Hendelse pågår',
'countdown.select_event': 'Velg en hendelse å følge', 'countdown.select_event': 'Velg en hendelse å følge',
'countdown.to_start': 'Tid til start', 'countdown.to_start': 'Tid til start',
'countdown.waiting': 'Venter på start', 'countdown.waiting': 'Venter på start',
'countdown.overtime': 'i overtiden',
}; };
+2 -1
View File
@@ -1,4 +1,4 @@
import { TranslationObject } from '../Translation.types'; import { TranslationObject } from './en';
export const langPt: TranslationObject = { export const langPt: TranslationObject = {
'common.end_time': 'Hora de término', 'common.end_time': 'Hora de término',
@@ -15,4 +15,5 @@ export const langPt: TranslationObject = {
'countdown.select_event': 'Selecione um evento para acompanhar', 'countdown.select_event': 'Selecione um evento para acompanhar',
'countdown.to_start': 'Tempo para iniciar', 'countdown.to_start': 'Tempo para iniciar',
'countdown.waiting': 'Aguardando o início do evento', 'countdown.waiting': 'Aguardando o início do evento',
'countdown.overtime': 'em tempo extra',
}; };
+3 -2
View File
@@ -1,4 +1,4 @@
import { TranslationObject } from '../Translation.types'; import { TranslationObject } from './en';
export const langSv: TranslationObject = { export const langSv: TranslationObject = {
'common.end_time': 'Sluttid', 'common.end_time': 'Sluttid',
@@ -9,10 +9,11 @@ export const langSv: TranslationObject = {
'common.start_time': 'Starttid', 'common.start_time': 'Starttid',
'common.stage_timer': 'Timer för scenen', 'common.stage_timer': 'Timer för scenen',
'common.started_at': 'Började vid', 'common.started_at': 'Började vid',
'common.time_now': 'Tid nu', 'common.time_now': 'Klockan nu',
'countdown.ended': 'Evenemanget avslutades vid', 'countdown.ended': 'Evenemanget avslutades vid',
'countdown.running': 'Evenemang pågår', 'countdown.running': 'Evenemang pågår',
'countdown.select_event': 'Välj ett evenemang att följa', 'countdown.select_event': 'Välj ett evenemang att följa',
'countdown.to_start': 'Tid till start', 'countdown.to_start': 'Tid till start',
'countdown.waiting': '"Väntar på att evenemanget ska starta', 'countdown.waiting': '"Väntar på att evenemanget ska starta',
'countdown.overtime': 'i övertid',
}; };
+1
View File
@@ -6,6 +6,7 @@
--label-color-override: #6c6c6c; --label-color-override: #6c6c6c;
--timer-color-override: #202020; --timer-color-override: #202020;
--card-background-color-override: #fff; --card-background-color-override: #fff;
--card-background-color-blink-override: #339e4e;
--font-family-override: "Open Sans"; --font-family-override: "Open Sans";
--font-family-bold-override: "Arial Black"; --font-family-bold-override: "Arial Black";
--timer-progress-bg-override: #fff; --timer-progress-bg-override: #fff;