mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 16:49:41 +00:00
feat(automation): ask a recipe for its parameters instead of its whole form
A recipe knows almost everything about the automation it makes. What it cannot know is where your gear is: which machine runs QLab, which Companion button, how long the aux timer should run. Handing the user the full automation form to supply three of those made them read a form of twenty fields to change one. A recipe now declares its parameters, and picking one asks for those and nothing else. Create makes the automation and its trigger through the same endpoints the form uses, so what lands in the list is an ordinary automation with nothing special about it. Each recipe carries a `build` function rather than a literal, which is what lets the answers reach the outputs — a Companion address and a page, row and column become one URL. That also absorbs the two things a user does without thinking: an address pasted with a trailing slash, and a webhook URL that already carries a query string. Every default still points at loopback. For the list to hold many recipes it has to be searchable and grouped, so it is both. Search matches the title, the description, the category and a keywords list, so the Companion recipe answers to "stream deck" and "elgato" and QLab answers to "osc" and "audio". Enter takes the top result. Escape clears the search rather than closing the dialog, which is the behaviour the settings search already has. The picker and the parameter step are two views of one dialog rather than two stacked modals, so Back means back rather than dismissing everything. Rows carry only the lifecycle tag: with many recipes, the description and one tag is what stays readable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfDKsy6PE3Rbyt32Fg4YKf
This commit is contained in:
@@ -33,7 +33,7 @@ import Tag from '../../../../common/components/tag/Tag';
|
||||
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||
import useCustomFields from '../../../../common/hooks-query/useCustomFields';
|
||||
import * as Panel from '../../panel-utils/PanelUtils';
|
||||
import { cycles, isAutomation, makeFieldList, operators, type OutputErrors } from './automationUtils';
|
||||
import { cycles, isAutomation, makeFieldList, makeTriggerTitle, operators, type OutputErrors } from './automationUtils';
|
||||
import HttpOutputForm from './HttpOutputForm';
|
||||
import OntimeActionForm from './OntimeActionForm';
|
||||
import OscOutputForm from './OscOutputForm';
|
||||
@@ -54,12 +54,10 @@ interface AutomationFormProps {
|
||||
automation: Automation | AutomationDTO;
|
||||
/** global triggers, used to resolve which lifecycles this automation is currently bound to */
|
||||
triggers: Trigger[];
|
||||
/** lifecycles a new automation starts with selected, used by recipes */
|
||||
defaultCycles?: TimerLifeCycle[];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function AutomationForm({ automation, triggers, defaultCycles, onClose }: AutomationFormProps) {
|
||||
export default function AutomationForm({ automation, triggers, onClose }: AutomationFormProps) {
|
||||
const isEdit = isAutomation(automation);
|
||||
const { data } = useCustomFields();
|
||||
const { refetch } = useAutomationSettings();
|
||||
@@ -79,10 +77,7 @@ export default function AutomationForm({ automation, triggers, defaultCycles, on
|
||||
() => Array.from(new Set(initialTriggers.map((trigger) => trigger.trigger))),
|
||||
[initialTriggers],
|
||||
);
|
||||
// a new automation can arrive pre-filled from a recipe, an existing one resolves its own triggers
|
||||
const [selectedCycles, setSelectedCycles] = useState<TimerLifeCycle[]>(
|
||||
isEdit ? initialCycles : (defaultCycles ?? []),
|
||||
);
|
||||
const [selectedCycles, setSelectedCycles] = useState<TimerLifeCycle[]>(initialCycles);
|
||||
/** set once a create succeeds, so a retry after a failed trigger sync edits instead of creating a duplicate */
|
||||
const [createdId, setCreatedId] = useState<string | null>(null);
|
||||
|
||||
@@ -233,8 +228,7 @@ export default function AutomationForm({ automation, triggers, defaultCycles, on
|
||||
|
||||
const toAdd = selectedCycles.filter((cycle) => !initialCycles.includes(cycle));
|
||||
for (const cycle of toAdd) {
|
||||
const label = cycles.find(({ value }) => value === cycle)?.label ?? cycle;
|
||||
await addTrigger({ title: `${title} — ${label}`, trigger: cycle, automationId });
|
||||
await addTrigger({ title: makeTriggerTitle(title, cycle), trigger: cycle, automationId });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,8 +279,7 @@ export default function AutomationForm({ automation, triggers, defaultCycles, on
|
||||
return `${fieldLabel} ${operatorLabel} ${value ? `“${value}”` : 'nothing'}`;
|
||||
};
|
||||
|
||||
// a recipe arrives complete, so a new automation is savable without the user changing anything
|
||||
const canSubmit = !isSubmitting && (!isEdit || isDirty || cyclesAreDirty) && isValid;
|
||||
const canSubmit = !isSubmitting && (isDirty || cyclesAreDirty) && isValid;
|
||||
const hasContinuousCycle = selectedCycles.some((cycle) => continuousCycles.includes(cycle));
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user