mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 15:39:11 +00:00
Event trigger (#1557)
* crude UI * add server functionalety * better event trigger list * fix default value * remove log * add trigger to recalculate whitelist * use DTO type * fix rebase * one callback for delete and submit * refactor * add invalid description * clean up commits * cleanup * refactor: find event triggers in `triggerAutomations` * refactor: switch to flat array for event triggers * prevent deleting a automation that is in use * refactor * refactor: extract EventEditorCustom * move trigger options to separate file * move trigger edit down a file level * cleanup * spelling * refactor: ui review proposal * change default value * fix delete filter --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
committed by
GitHub
parent
6817263123
commit
a94b10cb0f
@@ -3,8 +3,9 @@
|
|||||||
font-size: $text-body-size;
|
font-size: $text-body-size;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
|
color: $blue-400;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $blue-400;
|
color: $blue-500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { CSSProperties, useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { CustomFieldLabel, OntimeEvent } from 'ontime-types';
|
import { CustomFieldLabel, OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import AppLink from '../../../common/components/link/app-link/AppLink';
|
import AppLink from '../../../common/components/link/app-link/AppLink';
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||||
import { getAccessibleColour } from '../../../common/utils/styleUtils';
|
|
||||||
import * as Editor from '../../editors/editor-utils/EditorUtils';
|
import * as Editor from '../../editors/editor-utils/EditorUtils';
|
||||||
|
|
||||||
import EventEditorImage from './composite/EventEditorImage';
|
import EventCustom from './composite/EventEditorCustom';
|
||||||
import EventEditorTimes from './composite/EventEditorTimes';
|
import EventEditorTimes from './composite/EventEditorTimes';
|
||||||
import EventEditorTitles from './composite/EventEditorTitles';
|
import EventEditorTitles from './composite/EventEditorTitles';
|
||||||
import EventTextArea from './composite/EventTextArea';
|
import EventEditorTriggers from './composite/EventEditorTriggers';
|
||||||
import EventTextInput from './composite/EventTextInput';
|
|
||||||
import EventEditorEmpty from './EventEditorEmpty';
|
import EventEditorEmpty from './EventEditorEmpty';
|
||||||
|
|
||||||
import style from './EventEditor.module.scss';
|
import style from './EventEditor.module.scss';
|
||||||
@@ -77,52 +75,16 @@ export default function EventEditor(props: EventEditorProps) {
|
|||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
<Editor.Title>
|
<Editor.Title>
|
||||||
Custom Fields
|
Custom Fields
|
||||||
{isEditor && <AppLink search='settings=feature_settings__custom'>Manage</AppLink>}
|
{isEditor && <AppLink search='settings=feature_settings__custom'>Manage Custom Fields</AppLink>}
|
||||||
</Editor.Title>
|
</Editor.Title>
|
||||||
|
<EventCustom fields={customFields} handleSubmit={handleSubmit} event={event} />
|
||||||
{Object.keys(customFields).map((fieldKey) => {
|
</div>
|
||||||
const key = `${event.id}-${fieldKey}`;
|
<div className={style.column}>
|
||||||
const fieldName = `custom-${fieldKey}`;
|
<Editor.Title>
|
||||||
const initialValue = event.custom[fieldKey] ?? '';
|
Automations
|
||||||
const { backgroundColor, color } = getAccessibleColour(customFields[fieldKey].colour);
|
{isEditor && <AppLink search='settings=automation__automations'>Manage Automations</AppLink>}
|
||||||
const labelText = customFields[fieldKey].label;
|
</Editor.Title>
|
||||||
|
<EventEditorTriggers triggers={event.triggers} eventId={event.id} />
|
||||||
if (customFields[fieldKey].type === 'string') {
|
|
||||||
return (
|
|
||||||
<EventTextArea
|
|
||||||
key={key}
|
|
||||||
field={fieldName}
|
|
||||||
label={labelText}
|
|
||||||
initialValue={initialValue}
|
|
||||||
submitHandler={handleSubmit}
|
|
||||||
className={style.decorated}
|
|
||||||
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customFields[fieldKey].type === 'image') {
|
|
||||||
return (
|
|
||||||
<div key={key} className={style.customImage}>
|
|
||||||
<EventTextInput
|
|
||||||
key={key}
|
|
||||||
field={fieldName}
|
|
||||||
label={labelText}
|
|
||||||
initialValue={initialValue}
|
|
||||||
placeholder='Paste image URL'
|
|
||||||
submitHandler={handleSubmit}
|
|
||||||
className={style.decorated}
|
|
||||||
maxLength={255}
|
|
||||||
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
|
|
||||||
/>
|
|
||||||
<EventEditorImage src={initialValue} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we should have exhausted all types by now
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { CSSProperties, Fragment } from 'react';
|
||||||
|
import { CustomFields, OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
|
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||||
|
import { EditorUpdateFields } from '../EventEditor';
|
||||||
|
|
||||||
|
import EventEditorImage from './EventEditorImage';
|
||||||
|
import EventTextArea from './EventTextArea';
|
||||||
|
import EventTextInput from './EventTextInput';
|
||||||
|
|
||||||
|
import style from '../EventEditor.module.scss';
|
||||||
|
|
||||||
|
interface EventEditorCustomProps {
|
||||||
|
fields: CustomFields;
|
||||||
|
event: OntimeEvent;
|
||||||
|
handleSubmit: (field: EditorUpdateFields, value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EventEditorCustom(props: EventEditorCustomProps) {
|
||||||
|
const { fields: customFields, handleSubmit, event } = props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{Object.keys(customFields).map((fieldKey) => {
|
||||||
|
const key = `${event.id}-${fieldKey}`;
|
||||||
|
const fieldName = `custom-${fieldKey}`;
|
||||||
|
const initialValue = event.custom[fieldKey] ?? '';
|
||||||
|
const { backgroundColor, color } = getAccessibleColour(customFields[fieldKey].colour);
|
||||||
|
const labelText = customFields[fieldKey].label;
|
||||||
|
|
||||||
|
if (customFields[fieldKey].type === 'string') {
|
||||||
|
return (
|
||||||
|
<EventTextArea
|
||||||
|
key={key}
|
||||||
|
field={fieldName}
|
||||||
|
label={labelText}
|
||||||
|
initialValue={initialValue}
|
||||||
|
submitHandler={handleSubmit}
|
||||||
|
className={style.decorated}
|
||||||
|
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customFields[fieldKey].type === 'image') {
|
||||||
|
return (
|
||||||
|
<div key={key} className={style.customImage}>
|
||||||
|
<EventTextInput
|
||||||
|
key={key}
|
||||||
|
field={fieldName}
|
||||||
|
label={labelText}
|
||||||
|
initialValue={initialValue}
|
||||||
|
placeholder='Paste image URL'
|
||||||
|
submitHandler={handleSubmit}
|
||||||
|
className={style.decorated}
|
||||||
|
maxLength={255}
|
||||||
|
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
|
||||||
|
/>
|
||||||
|
<EventEditorImage src={initialValue} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should have exhausted all types by now
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
.triggerForm {
|
||||||
|
padding-block: 0.5rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr auto auto;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: $white-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorLabel {
|
||||||
|
color: $red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: $green-500;
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import { Fragment, useCallback, useState } from 'react';
|
||||||
|
import { IoAlertCircle, IoCheckmarkCircle, IoTrash } from 'react-icons/io5';
|
||||||
|
import { Button, IconButton, Select, Tooltip } from '@chakra-ui/react';
|
||||||
|
import { TimerLifeCycle, timerLifecycleValues, Trigger } from 'ontime-types';
|
||||||
|
import { generateId } from 'ontime-utils';
|
||||||
|
|
||||||
|
import Tag from '../../../../common/components/tag/Tag';
|
||||||
|
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||||
|
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||||
|
|
||||||
|
import { eventTriggerOptions } from './eventTrigger.constants';
|
||||||
|
|
||||||
|
import style from './EventEditorTriggers.module.scss';
|
||||||
|
|
||||||
|
interface EventEditorTriggersProps {
|
||||||
|
eventId: string;
|
||||||
|
triggers?: Trigger[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EventEditorTriggers(props: EventEditorTriggersProps) {
|
||||||
|
const { triggers, eventId } = props;
|
||||||
|
const showTriggers = triggers !== undefined && triggers.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showTriggers && <ExistingEventTriggers triggers={triggers} eventId={eventId} />}
|
||||||
|
<EventTriggerForm triggers={triggers} eventId={eventId} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventTriggerFormProps {
|
||||||
|
eventId: string;
|
||||||
|
triggers?: Trigger[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function EventTriggerForm(props: EventTriggerFormProps) {
|
||||||
|
const { eventId, triggers } = props;
|
||||||
|
const { data: automationSettings } = useAutomationSettings();
|
||||||
|
const { updateEvent } = useEventAction();
|
||||||
|
const [automationId, setAutomationId] = useState<string | undefined>(undefined);
|
||||||
|
const [cycleValue, setCycleValue] = useState(TimerLifeCycle.onStart);
|
||||||
|
|
||||||
|
const handleSubmit = (triggerLifeCycle: TimerLifeCycle, automationId: string) => {
|
||||||
|
const newTriggers = triggers ?? new Array<Trigger>();
|
||||||
|
const id = generateId();
|
||||||
|
newTriggers.push({ id, title: '', trigger: triggerLifeCycle, automationId });
|
||||||
|
updateEvent({ id: eventId, triggers: newTriggers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getValidationError = (cycle: TimerLifeCycle, automationId?: string): string | undefined => {
|
||||||
|
if (automationId === undefined) {
|
||||||
|
return 'Select an automation';
|
||||||
|
}
|
||||||
|
if (!Object.keys(automationSettings.automations).includes(automationId)) {
|
||||||
|
return 'This automation does not exist';
|
||||||
|
}
|
||||||
|
if (triggers === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return Object.values(triggers).some((t) => t.automationId === automationId && t.trigger === cycle)
|
||||||
|
? 'Automation can only be used once'
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationError = getValidationError(cycleValue, automationId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.triggerForm}>
|
||||||
|
<Select
|
||||||
|
size='sm'
|
||||||
|
variant='ontime'
|
||||||
|
value={cycleValue}
|
||||||
|
onChange={(e) => setCycleValue(e.target.value as TimerLifeCycle)}
|
||||||
|
defaultValue={TimerLifeCycle.onStart}
|
||||||
|
>
|
||||||
|
<option disabled>Lifecycle Trigger</option>
|
||||||
|
{eventTriggerOptions.map((cycle) => (
|
||||||
|
<option key={cycle} value={cycle}>
|
||||||
|
{cycle}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
size='sm'
|
||||||
|
variant='ontime'
|
||||||
|
value={automationId}
|
||||||
|
defaultValue='«invalid»'
|
||||||
|
onChange={(e) => setAutomationId(e.target.value)}
|
||||||
|
>
|
||||||
|
<option disabled value='«invalid»'>
|
||||||
|
Automation
|
||||||
|
</option>
|
||||||
|
{Object.values(automationSettings.automations).map(({ id, title }) => (
|
||||||
|
<option key={id} value={id}>
|
||||||
|
{title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
variant='ontime-subtle'
|
||||||
|
size='sm'
|
||||||
|
isDisabled={validationError !== undefined}
|
||||||
|
onClick={() => automationId && handleSubmit(cycleValue, automationId)}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
{validationError !== undefined ? (
|
||||||
|
<Tooltip label={validationError} shouldWrapChildren>
|
||||||
|
<IoAlertCircle className={style.errorLabel} />
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<IoCheckmarkCircle className={style.success} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExistingEventTriggersProps {
|
||||||
|
eventId: string;
|
||||||
|
triggers: Trigger[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExistingEventTriggers(props: ExistingEventTriggersProps) {
|
||||||
|
const { eventId, triggers } = props;
|
||||||
|
const { updateEvent } = useEventAction();
|
||||||
|
const { data: automationSettings } = useAutomationSettings();
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(triggerId: string) => {
|
||||||
|
const newTriggers = triggers.filter((trigger) => trigger.id !== triggerId);
|
||||||
|
updateEvent({ id: eventId, triggers: newTriggers });
|
||||||
|
},
|
||||||
|
[eventId, triggers, updateEvent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredTriggers: Record<string, Trigger[]> = {};
|
||||||
|
|
||||||
|
// sort triggers out into groups by the Lifecycle they are on
|
||||||
|
timerLifecycleValues.forEach((triggerType) => {
|
||||||
|
const thisTriggerType = triggers.filter((trigger) => trigger.trigger === triggerType);
|
||||||
|
if (thisTriggerType.length) {
|
||||||
|
Object.assign(filteredTriggers, { [triggerType]: thisTriggerType });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{Object.entries(filteredTriggers).map(([triggerLifeCycle, triggerGroup]) => (
|
||||||
|
<Fragment key={triggerLifeCycle}>
|
||||||
|
{triggerGroup.map((trigger) => {
|
||||||
|
const { id, automationId } = trigger;
|
||||||
|
const automationTitle = automationSettings.automations[automationId]?.title ?? '<MISSING AUTOMATION>';
|
||||||
|
return (
|
||||||
|
<div key={id} className={style.trigger}>
|
||||||
|
<Tag>{triggerLifeCycle}</Tag>
|
||||||
|
<Tag>{automationTitle}</Tag>
|
||||||
|
<IconButton
|
||||||
|
size='sm'
|
||||||
|
variant='ontime-ghosted'
|
||||||
|
color='#FA5656' // $red-500
|
||||||
|
icon={<IoTrash />}
|
||||||
|
aria-label='Delete entry'
|
||||||
|
onClick={() => handleDelete(id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
|
export const eventTriggerOptions: TimerLifeCycle[] = [
|
||||||
|
TimerLifeCycle.onLoad,
|
||||||
|
TimerLifeCycle.onStart,
|
||||||
|
TimerLifeCycle.onPause,
|
||||||
|
TimerLifeCycle.onFinish,
|
||||||
|
TimerLifeCycle.onWarning,
|
||||||
|
TimerLifeCycle.onDanger,
|
||||||
|
];
|
||||||
@@ -9,12 +9,13 @@ import type {
|
|||||||
import { deleteAtIndex, generateId } from 'ontime-utils';
|
import { deleteAtIndex, generateId } from 'ontime-utils';
|
||||||
|
|
||||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||||
|
import { getTimedEvents } from '../../services/rundown-service/rundownUtils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a copy of the stored automation settings
|
* Gets a copy of the stored automation settings
|
||||||
*/
|
*/
|
||||||
export function getAutomationSettings(): AutomationSettings {
|
export function getAutomationSettings(): AutomationSettings {
|
||||||
return structuredClone(getDataProvider().getAutomation());
|
return getDataProvider().getAutomation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,14 +139,25 @@ export async function deleteAutomation(id: string): Promise<void> {
|
|||||||
if (!Object.hasOwn(automations, id)) {
|
if (!Object.hasOwn(automations, id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// prevent deleting a automation that is in use
|
|
||||||
const triggers = getAutomationTriggers();
|
// prevent deleting a automation that is in use in triggers
|
||||||
for (let i = 0; i < triggers.length; i++) {
|
const triggers = getAutomationTriggers().filter((trigger) => trigger.automationId === id);
|
||||||
const trigger = triggers[i];
|
if (triggers.length) {
|
||||||
if (trigger.automationId === id) {
|
throw new Error(
|
||||||
throw new Error(`Unable to delete automation used in trigger ${trigger.title}`);
|
`Unable to delete automation used in trigger ${triggers[0].title}${triggers.length > 1 ? ` and ${triggers.length - 1} more` : ''}`,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prevent deleting a automation that is in use in events
|
||||||
|
const events = getTimedEvents().filter(
|
||||||
|
(event) => event.triggers && event.triggers.some((trigger) => trigger.automationId === id),
|
||||||
|
);
|
||||||
|
if (events.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to delete automation used in event: ${events[0].id}${events.length > 1 ? ` and ${events.length - 1} more` : ''}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
delete automations[id];
|
delete automations[id];
|
||||||
await saveChanges({ automations });
|
await saveChanges({ automations });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import {
|
|||||||
isOntimeAction,
|
isOntimeAction,
|
||||||
isOSCOutput,
|
isOSCOutput,
|
||||||
LogOrigin,
|
LogOrigin,
|
||||||
|
TimerLifeCycle,
|
||||||
type AutomationFilter,
|
type AutomationFilter,
|
||||||
type AutomationOutput,
|
type AutomationOutput,
|
||||||
type FilterRule,
|
type FilterRule,
|
||||||
type TimerLifeCycle,
|
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import { getPropertyFromPath } from 'ontime-utils';
|
import { getPropertyFromPath } from 'ontime-utils';
|
||||||
|
|
||||||
@@ -23,14 +23,21 @@ import { toOntimeAction } from './clients/ontime.client.js';
|
|||||||
/**
|
/**
|
||||||
* Exposes a method for triggering actions based on a TimerLifeCycle event
|
* Exposes a method for triggering actions based on a TimerLifeCycle event
|
||||||
*/
|
*/
|
||||||
export function triggerAutomations(event: TimerLifeCycle, state: RuntimeState) {
|
export function triggerAutomations(cycle: TimerLifeCycle, state: RuntimeState) {
|
||||||
if (!getAutomationsEnabled()) {
|
if (!getAutomationsEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggers = getAutomationTriggers();
|
let triggers = getAutomationTriggers();
|
||||||
const triggerAutomations = triggers.filter((trigger) => trigger.trigger === event);
|
|
||||||
if (triggerAutomations.length === 0) {
|
// get triggers from event
|
||||||
|
if (state.eventNow?.triggers) {
|
||||||
|
triggers = triggers.concat(state.eventNow.triggers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: there are no onStop triggers in event
|
||||||
|
const filteredTrigger = triggers.filter((trigger) => trigger.trigger === cycle);
|
||||||
|
if (filteredTrigger.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +46,7 @@ export function triggerAutomations(event: TimerLifeCycle, state: RuntimeState) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerAutomations.forEach((trigger) => {
|
filteredTrigger.forEach((trigger) => {
|
||||||
const automation = automations[trigger.automationId];
|
const automation = automations[trigger.automationId];
|
||||||
if (!automation || automation.outputs.length === 0) {
|
if (!automation || automation.outputs.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export enum regenerateWhitelist {
|
|||||||
'timeWarning',
|
'timeWarning',
|
||||||
'timeDanger',
|
'timeDanger',
|
||||||
'custom',
|
'custom',
|
||||||
|
'triggers',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -396,6 +396,7 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
|||||||
timeWarning: patchEvent.timeWarning ?? originalEvent.timeWarning,
|
timeWarning: patchEvent.timeWarning ?? originalEvent.timeWarning,
|
||||||
timeDanger: patchEvent.timeDanger ?? originalEvent.timeDanger,
|
timeDanger: patchEvent.timeDanger ?? originalEvent.timeDanger,
|
||||||
custom: { ...originalEvent.custom, ...patchEvent.custom },
|
custom: { ...originalEvent.custom, ...patchEvent.custom },
|
||||||
|
triggers: patchEvent.triggers ?? originalEvent.triggers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { EndAction, EventCustomFields, MaybeString, TimerType, TimeStrategy } from '../../index.js';
|
import type { EndAction, EventCustomFields, MaybeString, TimerType, TimeStrategy, Trigger } from '../../index.js';
|
||||||
|
|
||||||
export enum SupportedEvent {
|
export enum SupportedEvent {
|
||||||
Event = 'event',
|
Event = 'event',
|
||||||
@@ -44,6 +44,7 @@ export type OntimeEvent = OntimeBaseEvent & {
|
|||||||
timeWarning: number;
|
timeWarning: number;
|
||||||
timeDanger: number;
|
timeDanger: number;
|
||||||
custom: EventCustomFields;
|
custom: EventCustomFields;
|
||||||
|
triggers?: Trigger[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PlayableEvent = OntimeEvent & { skip: false };
|
export type PlayableEvent = OntimeEvent & { skip: false };
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ export {
|
|||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
isOntimeCycle,
|
|
||||||
isKeyOfType,
|
isKeyOfType,
|
||||||
isOSCOutput,
|
isOSCOutput,
|
||||||
isHTTPOutput,
|
isHTTPOutput,
|
||||||
isOntimeAction,
|
isOntimeAction,
|
||||||
|
isTimerLifeCycle,
|
||||||
} from './utils/guards.js';
|
} from './utils/guards.js';
|
||||||
export type { MaybeNumber, MaybeString } from './utils/utils.type.js';
|
export type { MaybeNumber, MaybeString } from './utils/utils.type.js';
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import type { AutomationOutput, HTTPOutput, OntimeAction, OSCOutput } from '../d
|
|||||||
import type { OntimeBlock, OntimeDelay, OntimeEvent, PlayableEvent } from '../definitions/core/OntimeEvent.type.js';
|
import type { OntimeBlock, OntimeDelay, OntimeEvent, PlayableEvent } from '../definitions/core/OntimeEvent.type.js';
|
||||||
import { SupportedEvent } from '../definitions/core/OntimeEvent.type.js';
|
import { SupportedEvent } from '../definitions/core/OntimeEvent.type.js';
|
||||||
import type { OntimeRundownEntry } from '../definitions/core/Rundown.type.js';
|
import type { OntimeRundownEntry } from '../definitions/core/Rundown.type.js';
|
||||||
import type { TimerLifeCycleKey } from '../definitions/core/TimerLifecycle.type.js';
|
import { type TimerLifeCycle, timerLifecycleValues } from '../definitions/core/TimerLifecycle.type.js';
|
||||||
import { TimerLifeCycle } from '../definitions/core/TimerLifecycle.type.js';
|
|
||||||
|
|
||||||
type MaybeEvent = OntimeRundownEntry | Partial<OntimeRundownEntry> | null | undefined;
|
type MaybeEvent = OntimeRundownEntry | Partial<OntimeRundownEntry> | null | undefined;
|
||||||
|
|
||||||
@@ -29,11 +28,6 @@ export function isKeyOfType<T extends object>(key: PropertyKey, obj: T): key is
|
|||||||
return key in obj;
|
return key in obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOntimeCycle(maybeCycle: unknown): maybeCycle is TimerLifeCycleKey {
|
|
||||||
if (typeof maybeCycle !== 'string') return false;
|
|
||||||
return Object.values(TimerLifeCycle).includes(maybeCycle as TimerLifeCycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isOSCOutput(output: AutomationOutput): output is OSCOutput {
|
export function isOSCOutput(output: AutomationOutput): output is OSCOutput {
|
||||||
return output.type === 'osc';
|
return output.type === 'osc';
|
||||||
}
|
}
|
||||||
@@ -45,3 +39,8 @@ export function isHTTPOutput(output: AutomationOutput): output is HTTPOutput {
|
|||||||
export function isOntimeAction(output: AutomationOutput): output is OntimeAction {
|
export function isOntimeAction(output: AutomationOutput): output is OntimeAction {
|
||||||
return output.type === 'ontime';
|
return output.type === 'ontime';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTimerLifeCycle(maybeCycle: unknown): maybeCycle is TimerLifeCycle {
|
||||||
|
if (typeof maybeCycle !== 'string') return false;
|
||||||
|
return timerLifecycleValues.includes(maybeCycle);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user