import type { Automation } from 'ontime-types'; import { useState } from 'react'; import { maybeAxiosError } from '../../../../../common/api/utils'; import Button from '../../../../../common/components/buttons/Button'; import Info from '../../../../../common/components/info/Info'; import ExternalLink from '../../../../../common/components/link/external-link/ExternalLink'; import Modal from '../../../../../common/components/modal/Modal'; import Tag from '../../../../../common/components/tag/Tag'; import { getLifecycleLabel } from '../../../../../common/constants/timerLifecycle'; import { summariseOutputs } from '../../../../../common/utils/automationOutputs'; import { isOntimeCloud } from '../../../../../externals'; import * as Panel from '../../../panel-utils/PanelUtils'; import { automationRecipes, recipeCategoryLabels, recipeCategoryOrder, type AutomationRecipe, } from './automationRecipes'; import { installRecipe } from './recipeUtils'; import style from './RecipeLibraryModal.module.scss'; interface RecipeLibraryModalProps { onClose: () => void; /** called with the installed automation so the caller can open it for editing */ onInstalled: (automation: AutomationRecipe, created: Automation) => void; } export default function RecipeLibraryModal({ onClose, onInstalled }: RecipeLibraryModalProps) { const [installing, setInstalling] = useState(null); const [error, setError] = useState(null); // OSC is not available in the cloud service, offering those recipes there would be a lie const available = isOntimeCloud ? automationRecipes.filter((recipe) => !recipe.automation.outputs.some((output) => output.type === 'osc')) : automationRecipes; const handleInstall = async (recipe: AutomationRecipe) => { setError(null); setInstalling(recipe.id); try { const created = await installRecipe(recipe); onInstalled(recipe, created); } catch (error) { setError(maybeAxiosError(error)); } finally { setInstalling(null); } }; return ( Recipes are a starting point, not a black box. Each one is added as a normal automation that you can edit, test or delete. Recipes that reach external software are set to this machine, so point them at the right device before you rely on them. {recipeCategoryOrder.map((category) => { const recipes = available.filter((recipe) => recipe.category === category); if (recipes.length === 0) { return null; } return (
{recipeCategoryLabels[category]}
{recipes.map((recipe) => (
{recipe.title}
{recipe.description}
{recipe.triggers.map((cycle) => ( {getLifecycleLabel(cycle)} ))} {summariseOutputs(recipe.automation.outputs).map(({ type, label, count }) => ( {count > 1 ? `${label} ×${count}` : label} ))} {recipe.needsSetup && Needs a target}
{recipe.docsUrl && Docs}
))}
); })} } footerElements={ <> {error && {error}} } /> ); }