Warning and danger per event (#677)

* feat: warning and danger per event

---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>
This commit is contained in:
Fabian Posenau
2023-12-30 20:56:57 +01:00
committed by GitHub
parent 7b9f64a630
commit 3998b8927d
31 changed files with 290 additions and 203 deletions
@@ -4,34 +4,7 @@ $input-font-size: 15px;
$input-delayed-border-color: #E69056;
.timeInput {
width: fit-content !important;
.inputLeft {
max-width: fit-content;
}
.inputLeft,
.inputButton {
aspect-ratio: 1;
}
.inputField {
font-size: $input-font-size;
letter-spacing: 1px;
width: 7.5em;
padding: 0 0 0 2.6em;
}
.warn {
&::after {
content: "*";
color: $warning-orange;
}
}
&.delayed {
.inputField {
border: 1px solid $input-delayed-border-color;
}
}
}
width: 6.5em;
}
@@ -1,15 +1,12 @@
import { FocusEvent, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Button, Input, InputGroup, InputLeftElement, Tooltip } from '@chakra-ui/react';
import { FocusEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
import { Input } from '@chakra-ui/react';
import { millisToString } from 'ontime-utils';
import { tooltipDelayFast } from '../../../../ontimeConfig';
import { useEmitLog } from '../../../stores/logger';
import { forgivingStringToMillis } from '../../../utils/dateConfig';
import { cx } from '../../../utils/styleUtils';
import { TimeEntryField } from '../../../utils/timesManager';
import style from './TimeInput.module.scss';
interface TimeInputProps {
id?: TimeEntryField;
name: TimeEntryField;
@@ -20,20 +17,7 @@ interface TimeInputProps {
validationHandler: (entry: TimeEntryField, val: number) => boolean;
previousEnd?: number;
warning?: string;
}
function ButtonInitial(name: TimeEntryField) {
if (name === 'timeStart') return 'S';
if (name === 'timeEnd') return 'E';
if (name === 'durationOverride') return 'D';
return '';
}
function ButtonTooltip(name: TimeEntryField, warning?: string) {
if (name === 'timeStart') return `Start${warning ? `: ${warning}` : ''}`;
if (name === 'timeEnd') return `End${warning ? `: ${warning}` : ''}`;
if (name === 'durationOverride') return `Duration${warning ? `: ${warning}` : ''}`;
return '';
className?: string;
}
export default function TimeInput(props: TimeInputProps) {
@@ -46,7 +30,7 @@ export default function TimeInput(props: TimeInputProps) {
placeholder,
validationHandler,
previousEnd = 0,
warning,
className = '',
} = props;
const { emitError } = useEmitLog();
const inputRef = useRef<HTMLInputElement | null>(null);
@@ -169,51 +153,25 @@ export default function TimeInput(props: TimeInputProps) {
resetValue();
}, [resetValue, time]);
const isDelayed = delay !== 0;
const inputClasses = cx([style.timeInput, isDelayed ? style.delayed : null]);
const buttonClasses = cx([style.inputButton, isDelayed ? style.delayed : null, warning ? style.warn : null]);
const TooltipLabel = useMemo(() => {
return ButtonTooltip(name, warning);
}, [name, warning]);
const ButtonText = useMemo(() => {
return ButtonInitial(name);
}, [name]);
const timeInputClass = className ? className : style.timeInput;
return (
<InputGroup size='sm' className={inputClasses}>
<InputLeftElement className={style.inputLeft}>
<Tooltip label={TooltipLabel} openDelay={tooltipDelayFast} variant='ontime-ondark'>
<Button
size='sm'
variant='ontime-subtle-white'
className={buttonClasses}
tabIndex={-1}
border={isDelayed ? '1px solid #E69056' : '1px solid transparent'}
borderRight='1px solid transparent'
borderRadius='2px 0 0 2px'
>
{ButtonText}
</Button>
</Tooltip>
</InputLeftElement>
<Input
ref={inputRef}
id={id}
data-testid={`time-input-${name}`}
className={style.inputField}
type='text'
placeholder={placeholder}
variant='ontime-filled'
onFocus={handleFocus}
onChange={(event) => setValue(event.target.value)}
onBlur={onBlurHandler}
onKeyDown={onKeyDownHandler}
value={value}
maxLength={8}
autoComplete='off'
/>
</InputGroup>
<Input
size='sm'
ref={inputRef}
id={id}
data-testid={`time-input-${name}`}
className={timeInputClass}
type='text'
placeholder={placeholder}
variant='ontime-filled'
onFocus={handleFocus}
onChange={(event) => setValue(event.target.value)}
onBlur={onBlurHandler}
onKeyDown={onKeyDownHandler}
value={value}
maxLength={8}
autoComplete='off'
/>
);
}
@@ -0,0 +1,37 @@
@use "../../../../theme/v2Styles" as *;
$input-font-size: 15px;
$input-delayed-border-color: #E69056;
.timeInput {
width: fit-content !important;
.inputLeft {
max-width: fit-content;
}
.inputLeft,
.inputButton {
aspect-ratio: 1;
}
.inputField {
font-size: $input-font-size;
letter-spacing: 1px;
width: 7.5em;
padding: 0 0 0 2.6em;
}
.warn {
&::after {
content: "*";
color: $warning-orange;
}
}
&.delayed {
.inputField {
border: 1px solid $input-delayed-border-color;
}
}
}
@@ -0,0 +1,97 @@
import { useMemo } from 'react';
import { Button, InputGroup, InputLeftElement, Tooltip } from '@chakra-ui/react';
import { tooltipDelayFast } from '../../../../ontimeConfig';
import { cx } from '../../../utils/styleUtils';
import { TimeEntryField } from '../../../utils/timesManager';
import TimeInput from './TimeInput';
import style from './TimeInputWithButton.module.scss';
interface TimeInputProps {
id?: TimeEntryField;
name: TimeEntryField;
submitHandler: (field: TimeEntryField, value: number) => void;
time?: number;
delay?: number;
placeholder: string;
validationHandler: (entry: TimeEntryField, val: number) => boolean;
previousEnd?: number;
warning?: string;
}
function ButtonInitial(name: TimeEntryField) {
if (name === 'timeStart') return 'S';
if (name === 'timeEnd') return 'E';
if (name === 'durationOverride') return 'D';
if (name === 'timeWarning') return 'Wa';
if (name === 'timeDanger') return 'Da';
return '';
}
function ButtonTooltip(name: TimeEntryField, warning?: string) {
if (name === 'timeStart') return `Start${warning ? `: ${warning}` : ''}`;
if (name === 'timeEnd') return `End${warning ? `: ${warning}` : ''}`;
if (name === 'durationOverride') return `Duration${warning ? `: ${warning}` : ''}`;
if (name === 'timeWarning') return `Warning${warning ? `: ${warning}` : ''}`;
if (name === 'timeDanger') return `Danger${warning ? `: ${warning}` : ''}`;
return '';
}
export default function TimeInputWithButton(props: TimeInputProps) {
const {
id,
name,
submitHandler,
time = 0,
delay = 0,
placeholder,
validationHandler,
previousEnd = 0,
warning,
} = props;
const isDelayed = delay !== 0;
const inputClasses = cx([style.timeInput, isDelayed ? style.delayed : null]);
const buttonClasses = cx([style.inputButton, isDelayed ? style.delayed : null, warning ? style.warn : null]);
const TooltipLabel = useMemo(() => {
return ButtonTooltip(name, warning);
}, [name, warning]);
const ButtonText = useMemo(() => {
return ButtonInitial(name);
}, [name]);
return (
<InputGroup size='sm' className={inputClasses}>
<InputLeftElement className={style.inputLeft}>
<Tooltip label={TooltipLabel} openDelay={tooltipDelayFast} variant='ontime-ondark'>
<Button
size='sm'
variant='ontime-subtle-white'
className={buttonClasses}
tabIndex={-1}
border={isDelayed ? '1px solid #E69056' : '1px solid transparent'}
borderRight='1px solid transparent'
borderRadius='2px 0 0 2px'
>
{ButtonText}
</Button>
</Tooltip>
</InputLeftElement>
<TimeInput
id={id}
name={name}
submitHandler={submitHandler}
time={time}
delay={delay}
placeholder={placeholder}
validationHandler={validationHandler}
previousEnd={previousEnd}
className={style.inputField}
/>
</InputGroup>
);
}
@@ -6,9 +6,9 @@ interface MultiPartProgressBar {
now: number | null;
complete: number;
normalColor: string;
warning: number;
warning?: number | null;
warningColor: string;
danger: number;
danger?: number | null;
dangerColor: string;
hidden?: boolean;
className?: string;
@@ -19,8 +19,8 @@ export default function MultiPartProgressBar(props: MultiPartProgressBar) {
const percentComplete = 100 - clamp(100 - (Math.max(now ?? 0, 0) * 100) / complete, 0, 100);
const dangerWidth = clamp((danger / complete) * 100, 0, 100);
const warningWidth = clamp((warning / complete) * 100, 0, 100);
const dangerWidth = danger ? clamp((danger / complete) * 100, 0, 100) : 0;
const warningWidth = warning ? clamp((warning / complete) * 100, 0, 100) : 0;
return (
<div className={`multiprogress-bar ${hidden ? 'multiprogress-bar--hidden' : ''} ${className}`}>
@@ -15,4 +15,7 @@ export type TimeManagerType = {
finished: boolean;
playback: Playback;
timeWarning: number | null;
timeDanger: number | null;
};
@@ -4,8 +4,6 @@ export const viewsSettingsPlaceholder: ViewSettings = {
overrideStyles: false,
normalColor: '#ffffffcc',
warningColor: '#FFAB33',
warningThreshold: 120000,
dangerColor: '#ED3333',
dangerThreshold: 60000,
endMessage: '',
};
+2
View File
@@ -17,6 +17,8 @@ export const runtimeStorePlaceholder: RuntimeStore = {
duration: null,
timerType: null,
endAction: null,
timeWarning: null,
timeDanger: null,
},
playback: Playback.Stop,
timerMessage: {
@@ -31,6 +31,8 @@ describe('cloneEvent()', () => {
user7: 'user7',
user8: 'user8',
user9: 'user9',
timeWarning: 120000,
timeDanger: 60000,
} as OntimeEvent;
const cloned = cloneEvent(original);
@@ -51,5 +53,7 @@ describe('cloneEvent()', () => {
expect(cloned.colour).toBe(original.colour);
expect(cloned.type).toBe(SupportedEvent.Event);
expect(cloned.revision).toBe(0);
expect(cloned.timeWarning).toBe(original.timeWarning);
expect(cloned.timeDanger).toBe(original.timeDanger);
});
});
@@ -28,5 +28,7 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => {
colour: event.colour,
after: after,
revision: 0,
timeWarning: event.timeWarning,
timeDanger: event.timeDanger,
};
};
+1 -1
View File
@@ -1,4 +1,4 @@
export type TimeEntryField = 'timeStart' | 'timeEnd' | 'durationOverride';
export type TimeEntryField = 'timeStart' | 'timeEnd' | 'durationOverride' | 'timeWarning' | 'timeDanger';
/**
* @description Checks which field the value relates to
@@ -2,7 +2,7 @@ import MultiPartProgressBar from '../../../common/components/multi-part-progress
import { useTimer } from '../../../common/hooks/useSocket';
import useViewSettings from '../../../common/hooks-query/useViewSettings';
import styles from "./CuesheetProgress.module.scss"
import styles from './CuesheetProgress.module.scss';
export default function CuesheetProgress() {
const { data } = useViewSettings();
@@ -14,9 +14,9 @@ export default function CuesheetProgress() {
now={timer.current}
complete={totalTime}
normalColor={data!.normalColor}
warning={data!.warningThreshold}
warning={timer.timeWarning}
warningColor={data!.warningColor}
danger={data!.dangerThreshold}
danger={timer.timeDanger}
dangerColor={data!.dangerColor}
className={styles.progressOverride}
/>
@@ -56,6 +56,8 @@ export default function EventEditor() {
isPublic={event.isPublic}
endAction={event.endAction}
timerType={event.timerType}
timeWarning={event.timeWarning}
timeDanger={event.timeDanger}
/>
<EventEditorDataLeft
key={`${event.id}-left`}
@@ -4,6 +4,7 @@ import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
import { calculateDuration, millisToString } from 'ontime-utils';
import TimeInput from '../../../common/components/input/time-input/TimeInput';
import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { millisToDelayString } from '../../../common/utils/dateConfig';
import { cx } from '../../../common/utils/styleUtils';
@@ -20,13 +21,24 @@ interface EventEditorTimesProps {
isPublic: boolean;
endAction: EndAction;
timerType: TimerType;
timeWarning: number;
timeDanger: number;
}
type TimeActions = 'timeStart' | 'timeEnd' | 'durationOverride' | 'timerType' | 'endAction' | 'isPublic';
type TimeActions =
| 'timeStart'
| 'timeEnd'
| 'durationOverride'
| 'timerType'
| 'endAction'
| 'isPublic'
| 'timeWarning'
| 'timeDanger';
// Todo: add previous end to TimeInput fields
const EventEditorTimes = (props: EventEditorTimesProps) => {
const { eventId, timeStart, timeEnd, duration, delay, isPublic, endAction, timerType } = props;
const { eventId, timeStart, timeEnd, duration, delay, isPublic, endAction, timerType, timeWarning, timeDanger } =
props;
const { updateEvent } = useEventAction();
const [warning, setWarnings] = useState({ start: '', end: '', duration: '' });
@@ -61,7 +73,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
break;
}
default: {
if (field === 'timerType' || field === 'endAction') {
if (field === 'timerType' || field === 'endAction' || field === 'timeWarning' || field === 'timeDanger') {
// @ts-expect-error -- not sure how to typecheck here
newEventData[field as keyof OntimeEvent] = value as string;
} else {
@@ -83,7 +95,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
<label className={inputTimeLabels} htmlFor='timeStart'>
{startLabel}
</label>
<TimeInput
<TimeInputWithButton
id='timeStart'
name='timeStart'
submitHandler={handleSubmit}
@@ -96,7 +108,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
<label className={inputTimeLabels} htmlFor='timeEnd'>
{endLabel}
</label>
<TimeInput
<TimeInputWithButton
id='timeEnd'
name='timeEnd'
submitHandler={handleSubmit}
@@ -109,7 +121,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
<label className={style.inputLabel} htmlFor='durationOverride'>
Duration
</label>
<TimeInput
<TimeInputWithButton
id='durationOverride'
name='durationOverride'
submitHandler={handleSubmit}
@@ -118,6 +130,11 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
placeholder='Duration'
warning={warning.duration}
/>
<span className={style.spacer} />
<label className={`${style.inputLabel} ${style.publicToggle}`}>
<Switch isChecked={isPublic} onChange={() => handleSubmit('isPublic', isPublic)} variant='ontime' />
Event is public
</label>
</div>
<div className={style.timeSettings}>
<label className={style.inputLabel}>Timer Type</label>
@@ -146,11 +163,30 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
<option value={EndAction.LoadNext}>Load Next</option>
<option value={EndAction.PlayNext}>Play Next</option>
</Select>
<span className={style.spacer} />
<label className={`${style.inputLabel} ${style.publicToggle}`}>
<Switch isChecked={isPublic} onChange={() => handleSubmit('isPublic', isPublic)} variant='ontime' />
Event is public
<label className={style.inputLabel} htmlFor='timeWarning'>
Warning Time
</label>
<TimeInput
id='timeWarning'
name='timeWarning'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={timeWarning}
placeholder='Duration'
warning={warning.duration}
/>
<label className={style.inputLabel} htmlFor='timeDanger'>
Danger Time
</label>
<TimeInput
id='timeDanger'
name='timeDanger'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={timeDanger}
placeholder='Duration'
warning={warning.duration}
/>
</div>
</div>
);
@@ -1,35 +0,0 @@
import { useController, UseControllerProps } from 'react-hook-form';
import { Input } from '@chakra-ui/react';
import { ViewSettings } from 'ontime-types';
import { millisToMinutes } from '../../../common/utils/dateConfig';
import { inputProps } from '../modalHelper';
export default function InputMillisWithString(props: UseControllerProps<ViewSettings>) {
const { name, control } = props;
const {
field: { onChange, value },
} = useController({
control,
name,
rules: {
pattern: {
value: /^[0-9]+$/,
message: 'Only numbers are valid',
},
},
});
return (
<Input
{...inputProps}
type='number'
variant='ontime-filled-on-light'
width='75px'
size='sm'
maxLength={3}
defaultValue={millisToMinutes(value as number)}
onChange={onChange}
/>
);
}
@@ -8,7 +8,6 @@ import { postViewSettings } from '../../../common/api/ontimeApi';
import { PopoverPickerRHF } from '../../../common/components/input/popover-picker/PopoverPicker';
import useInfo from '../../../common/hooks-query/useInfo';
import useViewSettings from '../../../common/hooks-query/useViewSettings';
import { mtm } from '../../../common/utils/timeConstants';
import ModalLoader from '../modal-loader/ModalLoader';
import { inputProps } from '../modalHelper';
import ModalInput from '../ModalInput';
@@ -16,8 +15,6 @@ import ModalLink from '../ModalLink';
import ModalSplitInput from '../ModalSplitInput';
import OntimeModalFooter from '../OntimeModalFooter';
import InputMillisWithString from './InputMillisWithString';
import style from './SettingsModal.module.scss';
const cssOverrideDocsUrl = 'https://ontime.gitbook.io/v2/features/custom-styling';
@@ -31,7 +28,7 @@ export default function ViewSettingsForm() {
handleSubmit,
register,
reset,
formState: { isSubmitting, isDirty, isValid, dirtyFields },
formState: { isSubmitting, isDirty, isValid },
} = useForm<ViewSettings>({
defaultValues: data,
values: data,
@@ -47,19 +44,8 @@ export default function ViewSettingsForm() {
}, [data, reset]);
const onSubmit = async (formData: ViewSettings) => {
const parsedWarningThreshold = dirtyFields?.warningThreshold
? // @ts-expect-error -- trust me
Number.parseInt(formData.warningThreshold) * mtm
: formData.warningThreshold;
const parsedDangerThreshold = dirtyFields?.dangerThreshold
? // @ts-expect-error -- trust me
Number.parseInt(formData.dangerThreshold) * mtm
: formData.dangerThreshold;
const newData = {
...formData,
warningThreshold: parsedWarningThreshold,
dangerThreshold: parsedDangerThreshold,
};
try {
@@ -107,27 +93,13 @@ export default function ViewSettingsForm() {
<Switch {...register('overrideStyles')} variant='ontime-on-light' />
</ModalSplitInput>
<span className={style.title}>Timer view settings</span>
<ModalSplitInput field='normalColor' title='Timer colour' description='Normal colour of a running timer'>
<ModalSplitInput field='normalColor' title='Timer color' description='Normal color of a running timer'>
<PopoverPickerRHF name='normalColor' control={control} />
</ModalSplitInput>
<ModalSplitInput
field='warningColor'
title='Warning Color'
description='Time (in minutes) when the timer moves to warning mode'
>
<InputMillisWithString name='warningThreshold' control={control} />
</ModalSplitInput>
<ModalSplitInput field='warningColor' title='Warning Color' description='Colour of timer in warning mode'>
<ModalSplitInput field='warningColor' title='Warning color' description='Color of timer in warning mode'>
<PopoverPickerRHF name='warningColor' control={control} />
</ModalSplitInput>
<ModalSplitInput
field='dangerThreshold'
title='Danger colour'
description='Time (in minutes) when the timer moves to danger mode'
>
<InputMillisWithString name='dangerThreshold' control={control} />
</ModalSplitInput>
<ModalSplitInput field='dangerColor' title='Timer colour' description='Colour of timer in danger mode'>
<ModalSplitInput field='dangerColor' title='Timer color' description='Color of timer in danger mode'>
<PopoverPickerRHF name='dangerColor' control={control} />
</ModalSplitInput>
<div style={{ height: '16px' }} />
@@ -20,9 +20,9 @@ export default function StatusBarProgress(props: StatusBarProgressProps) {
now={timer.current}
complete={totalTime}
normalColor={viewSettings.normalColor}
warning={viewSettings.warningThreshold}
warning={timer?.timeWarning}
warningColor={viewSettings.warningColor}
danger={viewSettings.dangerThreshold}
danger={timer?.timeDanger}
dangerColor={viewSettings.dangerColor}
className={styles.progressOverride}
/>
@@ -2,7 +2,7 @@ import { memo, useCallback, useState } from 'react';
import { OntimeEvent } from 'ontime-types';
import { calculateDuration, millisToString } from 'ontime-utils';
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import TimeInputWithButton from '../../../../common/components/input/time-input/TimeInputWithButton';
import { useEventAction } from '../../../../common/hooks/useEventAction';
import { millisToDelayString } from '../../../../common/utils/dateConfig';
import { TimeEntryField, validateEntry } from '../../../../common/utils/timesManager';
@@ -18,7 +18,7 @@ interface EventBlockTimerProps {
previousEnd: number;
}
type TimeActions = 'timeStart' | 'timeEnd' | 'durationOverride';
type TimeActions = 'timeStart' | 'timeEnd' | 'durationOverride' | 'timeWarning' | 'timeDanger';
const EventBlockTimers = (props: EventBlockTimerProps) => {
const { eventId, timeStart, timeEnd, duration, delay, previousEnd } = props;
@@ -70,7 +70,7 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
return (
<div className={style.eventTimers}>
<TimeInput
<TimeInputWithButton
name='timeStart'
submitHandler={handleSubmit}
validationHandler={handleValidation}
@@ -80,7 +80,7 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
previousEnd={previousEnd}
warning={warning.start}
/>
<TimeInput
<TimeInputWithButton
name='timeEnd'
submitHandler={handleSubmit}
validationHandler={handleValidation}
@@ -90,7 +90,7 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
previousEnd={previousEnd}
warning={warning.end}
/>
<TimeInput
<TimeInputWithButton
name='durationOverride'
submitHandler={handleSubmit}
validationHandler={handleValidation}
@@ -135,8 +135,8 @@ export default function MinimalTimer(props: MinimalTimerProps) {
const showFinished = finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
const showProgress = time.playback !== Playback.Stop;
const showWarning = (time.current ?? 1) < viewSettings.warningThreshold;
const showDanger = (time.current ?? 1) < viewSettings.dangerThreshold;
const showWarning = (time.current ?? 1) < (time.timeWarning ?? 0);
const showDanger = (time.current ?? 1) < (time.timeDanger ?? 0);
const showBlinking = pres.timerBlink;
const showBlackout = pres.timerBlackout;
@@ -98,8 +98,8 @@ export default function Timer(props: TimerProps) {
const showEndMessage = (time.current ?? 1) < 0 && viewSettings.endMessage;
const showProgress = time.playback !== Playback.Stop;
const showFinished = finished && (time.timerType !== TimerType.Clock || showEndMessage);
const showWarning = (time.current ?? 1) < viewSettings.warningThreshold;
const showDanger = (time.current ?? 1) < viewSettings.dangerThreshold;
const showWarning = (time.current ?? 1) < (eventNow?.timeWarning ?? 0);
const showDanger = (time.current ?? 1) < (eventNow?.timeDanger ?? 0);
const showBlinking = pres.timerBlink;
const showBlackout = pres.timerBlackout;
const showClock = time.timerType !== TimerType.Clock;
@@ -176,9 +176,9 @@ export default function Timer(props: TimerProps) {
now={time.current}
complete={totalTime}
normalColor={viewSettings.normalColor}
warning={viewSettings.warningThreshold}
warning={eventNow?.timeWarning}
warningColor={viewSettings.warningColor}
danger={viewSettings.dangerThreshold}
danger={eventNow?.timeDanger}
dangerColor={viewSettings.dangerColor}
hidden={!showProgress}
/>
-2
View File
@@ -24,9 +24,7 @@ export const dbModel: DatabaseModel = {
overrideStyles: false,
normalColor: '#ffffffcc',
warningColor: '#FFAB33',
warningThreshold: 120000,
dangerColor: '#ED3333',
dangerThreshold: 60000,
endMessage: '',
},
aliases: [],
@@ -25,6 +25,8 @@ export const event: Omit<OntimeEvent, 'id' | 'delay' | 'cue'> = {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
};
export const delay: Omit<OntimeDelay, 'id'> = {
+6
View File
@@ -79,6 +79,8 @@ export class TimerService {
duration: null,
timerType: null,
endAction: null,
timeWarning: null,
timeDanger: null,
};
this.loadedTimerId = null;
this.loadedTimerStart = null;
@@ -156,6 +158,8 @@ export class TimerService {
this.timer.endAction = timer.endAction;
this.loadedTimerStart = timer.timeStart;
this.loadedTimerEnd = timer.timeEnd;
this.timer.timeWarning = timer.timeWarning;
this.timer.timeDanger = timer.timeDanger;
// this might not be ideal
this.timer.finishedAt = null;
@@ -196,6 +200,8 @@ export class TimerService {
this.timer.endAction = timer.endAction;
this.pausedTime = 0;
this.pausedAt = 0;
this.timer.timeWarning = timer.timeWarning;
this.timer.timeDanger = timer.timeDanger;
this.timer.current = this.timer.duration;
if (this.timer.timerType === TimerType.TimeToEnd) {
@@ -30,6 +30,8 @@ describe('calculateRuntimeDelays', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
cue: '1',
},
@@ -64,6 +66,8 @@ describe('calculateRuntimeDelays', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
cue: '2',
},
@@ -98,6 +102,8 @@ describe('calculateRuntimeDelays', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
cue: '3',
},
@@ -131,6 +137,8 @@ describe('calculateRuntimeDelays', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
cue: '4',
},
@@ -173,6 +181,8 @@ describe('getDelayAt()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
delay: 0,
cue: '1',
@@ -208,6 +218,8 @@ describe('getDelayAt()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
delay: 600000,
cue: '2',
@@ -243,6 +255,8 @@ describe('getDelayAt()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
delay: 1800000,
cue: '3',
@@ -277,6 +291,8 @@ describe('getDelayAt()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
delay: 0,
cue: '4',
@@ -337,6 +353,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
delay: 0,
cue: '1',
@@ -372,6 +390,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
delay: 0,
cue: '2',
@@ -407,6 +427,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
delay: 1800000,
cue: '3',
@@ -441,6 +463,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
user9: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
delay: 0,
cue: '4',
@@ -852,9 +852,7 @@ describe('test views import', () => {
viewSettings: {
normalColor: '#ffffffcc',
warningColor: '#FFAB33',
warningThreshold: 120000,
dangerColor: '#ED3333',
dangerThreshold: 60000,
endMessage: '',
overrideStyles: false,
notAthing: true,
@@ -866,9 +864,7 @@ describe('test views import', () => {
const expectedParsedViewSettings = {
normalColor: '#ffffffcc',
warningColor: '#FFAB33',
warningThreshold: 120000,
dangerColor: '#ED3333',
dangerThreshold: 60000,
endMessage: '',
overrideStyles: false,
};
+10
View File
@@ -88,6 +88,8 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
let timeStartIndex: number | null = null;
let timeEndIndex: number | null = null;
let durationIndex: number | null = null;
let timeWarningIndex: number | null = null;
let timeDangerIndex: number | null = null;
// options: enum properties
let endActionIndex: number | null = null;
@@ -151,6 +153,8 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
[importMap.user7]: (index: number) => (user7Index = index),
[importMap.user8]: (index: number) => (user8Index = index),
[importMap.user9]: (index: number) => (user9Index = index),
[importMap.timeWarning]: (index: number) => (timeWarningIndex = index),
[importMap.timeDanger]: (index: number) => (timeDangerIndex = index),
} as const;
row.forEach((column, j) => {
@@ -219,6 +223,10 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
event.user8 = makeString(column, '');
} else if (j === user9Index) {
event.user9 = makeString(column, '');
} else if (j === timeWarningIndex) {
event.timeWarning = parseExcelDate(column);
} else if (j === timeDangerIndex) {
event.timeDanger = parseExcelDate(column);
} else {
// 2. if there is no flag, lets see if we know the field type
if (typeof column === 'string') {
@@ -333,6 +341,8 @@ export const validateEvent = (eventArgs: Partial<OntimeEvent>, cueFallback: stri
cue: makeString(e.cue, cueFallback),
id,
type: 'event',
timeWarning: e.timeWarning,
timeDanger: e.timeDanger,
};
}
-2
View File
@@ -149,9 +149,7 @@ export const parseViewSettings = (data): ViewSettings => {
overrideStyles: v.overrideStyles ?? dbModel.viewSettings.overrideStyles,
normalColor: v.normalColor ?? dbModel.viewSettings.normalColor,
warningColor: v.warningColor ?? dbModel.viewSettings.warningColor,
warningThreshold: v.warningThreshold ?? dbModel.viewSettings.warningThreshold,
dangerColor: v.dangerColor ?? dbModel.viewSettings.dangerColor,
dangerThreshold: v.dangerThreshold ?? dbModel.viewSettings.dangerThreshold,
endMessage: v.endMessage ?? dbModel.viewSettings.endMessage,
};
@@ -51,4 +51,6 @@ export type OntimeEvent = OntimeBaseEvent & {
user9: string;
revision: number;
delay?: number; // calculated at runtime
timeWarning: number;
timeDanger: number;
};
@@ -3,7 +3,5 @@ export type ViewSettings = {
endMessage: string;
normalColor: string;
warningColor: string;
warningThreshold: number;
dangerColor: string;
dangerThreshold: number;
};
@@ -14,4 +14,6 @@ export type TimerState = {
duration: number | null;
timerType: TimerType | null;
endAction: EndAction | null;
timeWarning: number | null;
timeDanger: number | null;
};
@@ -32,6 +32,8 @@ export const defaultExcelImportMap = {
user7: 'user7',
user8: 'user8',
user9: 'user9',
timeWarning: 'timeWarning',
timeDanger: 'timeDanger',
};
export function isExcelImportMap(obj: unknown): obj is ExcelImportMap {