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