mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 05:13:32 +00:00
* fix: prevent add filter from submitting form * refactor: event clarifies whether automations exist but are disabled * refactor: improve readability of automation form * refactor: add affordance for field warnings * refactor: improve visibility of automation off state * refactor: apply warning styles to other feature toggles * Adding playback actions to automations (#2024) * adding intial automation actions * cleaning up * fixing formatting * ran oxfmt * switching action names to playback- to match the dropdown strings * fixing flicker that was caused by scroll arrows gettting unmounted --------- Co-authored-by: Cameron Slipp <cdslipp@gmail.com>
This commit is contained in:
@@ -140,11 +140,21 @@ $inner-padding: 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.fieldHeading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.fieldDescription {
|
||||
font-size: calc(1rem - 2px);
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
.warningText {
|
||||
color: $orange-500;
|
||||
}
|
||||
|
||||
.fieldError {
|
||||
font-size: calc(1rem - 2px);
|
||||
color: $red-500;
|
||||
|
||||
@@ -85,18 +85,34 @@ export function ListItem({ children }: { children: ReactNode }) {
|
||||
return <li className={style.listItem}>{children}</li>;
|
||||
}
|
||||
|
||||
export function Field({ title, description, error }: { title: string; description: string; error?: string }) {
|
||||
export function Field({
|
||||
title,
|
||||
description,
|
||||
error,
|
||||
descriptionTone = 'default',
|
||||
}: {
|
||||
title: ReactNode;
|
||||
description: ReactNode;
|
||||
error?: string;
|
||||
descriptionTone?: 'default' | 'warning';
|
||||
}) {
|
||||
return (
|
||||
<div className={style.fieldTitle}>
|
||||
{title}
|
||||
<div className={style.fieldHeading}>{title}</div>
|
||||
{error && <Error>{error}</Error>}
|
||||
{!error && description && <Description>{description}</Description>}
|
||||
{!error && description && <Description tone={descriptionTone}>{description}</Description>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Description({ children }: { children: ReactNode }) {
|
||||
return <div className={style.fieldDescription}>{children}</div>;
|
||||
export function Description({
|
||||
children,
|
||||
tone = 'default',
|
||||
}: {
|
||||
children: ReactNode;
|
||||
tone?: 'default' | 'warning';
|
||||
}) {
|
||||
return <div className={cx([style.fieldDescription, tone === 'warning' && style.warningText])}>{children}</div>;
|
||||
}
|
||||
|
||||
export function Highlight({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -282,7 +282,7 @@ export default function AutomationForm({ automation, onClose }: AutomationFormPr
|
||||
);
|
||||
})}
|
||||
<div>
|
||||
<Button type='submit' onClick={handleAddNewFilter}>
|
||||
<Button onClick={handleAddNewFilter}>
|
||||
Add filter <IoAdd />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,8 @@ export default function AutomationPanel({ location }: PanelBaseProps) {
|
||||
const automationsRef = useScrollIntoView<HTMLDivElement>('automations', location);
|
||||
|
||||
const isLoading = status === 'pending';
|
||||
const automationState = isLoading ? undefined : data.enabledAutomations;
|
||||
const oscInputState = isLoading ? undefined : data.enabledOscIn;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -24,13 +26,15 @@ export default function AutomationPanel({ location }: PanelBaseProps) {
|
||||
enabledAutomations={data.enabledAutomations}
|
||||
enabledOscIn={data.enabledOscIn}
|
||||
oscPortIn={data.oscPortIn}
|
||||
automationState={automationState}
|
||||
oscInputState={oscInputState}
|
||||
/>
|
||||
</div>
|
||||
<div ref={automationsRef}>
|
||||
<AutomationsList automations={data.automations} />
|
||||
<AutomationsList automations={data.automations} enabledAutomations={automationState} />
|
||||
</div>
|
||||
<div ref={triggersRef}>
|
||||
<TriggersList triggers={data.triggers} automations={data.automations} />
|
||||
<TriggersList triggers={data.triggers} automations={data.automations} enabledAutomations={automationState} />
|
||||
</div>
|
||||
</Panel.Section>
|
||||
</>
|
||||
|
||||
+33
-7
@@ -7,6 +7,7 @@ import Info from '../../../../common/components/info/Info';
|
||||
import Input from '../../../../common/components/input/input/Input';
|
||||
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
|
||||
import Switch from '../../../../common/components/switch/Switch';
|
||||
import Tag from '../../../../common/components/tag/Tag';
|
||||
import { preventEscape } from '../../../../common/utils/keyEvent';
|
||||
import { isOnlyNumbers } from '../../../../common/utils/regex';
|
||||
import { isOntimeCloud } from '../../../../externals';
|
||||
@@ -18,12 +19,16 @@ interface AutomationSettingsProps {
|
||||
enabledAutomations: boolean;
|
||||
enabledOscIn: boolean;
|
||||
oscPortIn: number;
|
||||
automationState?: boolean;
|
||||
oscInputState?: boolean;
|
||||
}
|
||||
|
||||
export default function AutomationSettingsForm({
|
||||
enabledAutomations,
|
||||
enabledOscIn,
|
||||
oscPortIn,
|
||||
automationState,
|
||||
oscInputState,
|
||||
}: AutomationSettingsProps) {
|
||||
const {
|
||||
handleSubmit,
|
||||
@@ -56,6 +61,8 @@ export default function AutomationSettingsForm({
|
||||
};
|
||||
|
||||
const canSubmit = !isSubmitting && isDirty && isValid;
|
||||
const automationsEnabled = watch('enabledAutomations');
|
||||
const oscInputEnabled = watch('enabledOscIn');
|
||||
|
||||
return (
|
||||
<Panel.Card>
|
||||
@@ -102,33 +109,52 @@ export default function AutomationSettingsForm({
|
||||
<Panel.ListGroup>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='Enable automations'
|
||||
description='Allow Ontime to send messages on lifecycle triggers'
|
||||
title={
|
||||
<>
|
||||
<span>Enable automations</span>
|
||||
{automationState === false && <Tag variant='warning'>OFF</Tag>}
|
||||
</>
|
||||
}
|
||||
description={
|
||||
automationState === false
|
||||
? 'Automations are OFF. Triggers stay configured, but Ontime will not send messages.'
|
||||
: 'Allow Ontime to send messages on lifecycle triggers'
|
||||
}
|
||||
descriptionTone={automationState === false ? 'warning' : 'default'}
|
||||
error={errors.enabledAutomations?.message}
|
||||
/>
|
||||
<Switch
|
||||
size='large'
|
||||
checked={watch('enabledAutomations')}
|
||||
checked={automationsEnabled}
|
||||
onCheckedChange={(value: boolean) =>
|
||||
setValue('enabledAutomations', value, { shouldDirty: true, shouldValidate: true })
|
||||
}
|
||||
/>
|
||||
</Panel.ListItem>
|
||||
</Panel.ListGroup>
|
||||
|
||||
<Panel.Title>OSC Input</Panel.Title>
|
||||
|
||||
<Panel.ListGroup>
|
||||
{isOntimeCloud && <Info>For security reasons OSC integrations are not available in the cloud service.</Info>}
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='OSC input'
|
||||
description='Allow control of Ontime through OSC'
|
||||
title={
|
||||
<>
|
||||
<span>OSC input</span>
|
||||
{oscInputState === false && <Tag variant='warning'>OFF</Tag>}
|
||||
</>
|
||||
}
|
||||
description={
|
||||
oscInputState === false
|
||||
? 'OSC input is OFF. Ontime will not listen for incoming OSC control messages.'
|
||||
: 'Allow control of Ontime through OSC'
|
||||
}
|
||||
descriptionTone={oscInputState === false ? 'warning' : 'default'}
|
||||
error={errors.enabledOscIn?.message}
|
||||
/>
|
||||
<Switch
|
||||
size='large'
|
||||
checked={watch('enabledOscIn')}
|
||||
checked={oscInputEnabled}
|
||||
onCheckedChange={(value: boolean) =>
|
||||
setValue('enabledOscIn', value, { shouldDirty: true, shouldValidate: true })
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IoAdd, IoPencil, IoTrash } from 'react-icons/io5';
|
||||
import { deleteAutomation } from '../../../../common/api/automation';
|
||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import Info from '../../../../common/components/info/Info';
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import Tag from '../../../../common/components/tag/Tag';
|
||||
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||
@@ -20,10 +21,11 @@ const automationPlaceholder: AutomationDTO = {
|
||||
|
||||
interface AutomationsListProps {
|
||||
automations: NormalisedAutomation;
|
||||
enabledAutomations?: boolean;
|
||||
}
|
||||
|
||||
export default function AutomationsList(props: AutomationsListProps) {
|
||||
const { automations } = props;
|
||||
const { automations, enabledAutomations } = props;
|
||||
const { refetch } = useAutomationSettings();
|
||||
const [automationFormData, setAutomationFormData] = useState<AutomationDTO | null>(null);
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
@@ -56,6 +58,13 @@ export default function AutomationsList(props: AutomationsListProps) {
|
||||
|
||||
<Panel.Divider />
|
||||
|
||||
{enabledAutomations === false && (
|
||||
<Info>
|
||||
Automations are disabled. You can still manage automation definitions here, but they will not run until
|
||||
enabled.
|
||||
</Info>
|
||||
)}
|
||||
|
||||
{automationFormData !== null && (
|
||||
<AutomationForm automation={automationFormData} onClose={() => setAutomationFormData(null)} />
|
||||
)}
|
||||
|
||||
@@ -66,6 +66,11 @@ export default function OntimeActionForm({
|
||||
{ value: 'aux2-set', label: 'Aux 2: set' },
|
||||
{ value: 'aux3-set', label: 'Aux 3: set' },
|
||||
|
||||
{ value: 'playback-start', label: 'Playback: start' },
|
||||
{ value: 'playback-stop', label: 'Playback: stop' },
|
||||
{ value: 'playback-pause', label: 'Playback: pause' },
|
||||
{ value: 'playback-roll', label: 'Playback: roll' },
|
||||
|
||||
{ value: 'message-set', label: 'Primary Message: set' },
|
||||
{ value: 'message-secondary', label: 'Secondary Message: source' },
|
||||
]}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IoAdd } from 'react-icons/io5';
|
||||
import { deleteTrigger } from '../../../../common/api/automation';
|
||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import Info from '../../../../common/components/info/Info';
|
||||
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||
import * as Panel from '../../panel-utils/PanelUtils';
|
||||
import { checkDuplicates } from './automationUtils';
|
||||
@@ -14,10 +15,11 @@ import TriggersListItem from './TriggersListItem';
|
||||
interface TriggersListProps {
|
||||
triggers: Trigger[];
|
||||
automations: NormalisedAutomation;
|
||||
enabledAutomations?: boolean;
|
||||
}
|
||||
|
||||
export default function TriggersList(props: TriggersListProps) {
|
||||
const { triggers, automations } = props;
|
||||
const { triggers, automations, enabledAutomations } = props;
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const { refetch } = useAutomationSettings();
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
@@ -52,6 +54,9 @@ export default function TriggersList(props: TriggersListProps) {
|
||||
</Panel.SubHeader>
|
||||
<Panel.Divider />
|
||||
<Panel.Section>
|
||||
{enabledAutomations === false && (
|
||||
<Info>Automations are disabled. You can still manage triggers here, but they will not run until enabled.</Info>
|
||||
)}
|
||||
{duplicates && (
|
||||
<Panel.Error>
|
||||
You have created multiple links between the same trigger and automation which can performance issues.
|
||||
|
||||
@@ -9,6 +9,7 @@ import Info from '../../../../common/components/info/Info';
|
||||
import { SwatchPickerRHF } from '../../../../common/components/input/colour-input/SwatchPicker';
|
||||
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
|
||||
import Switch from '../../../../common/components/switch/Switch';
|
||||
import Tag from '../../../../common/components/tag/Tag';
|
||||
import useViewSettings from '../../../../common/hooks-query/useViewSettings';
|
||||
import { preventEscape } from '../../../../common/utils/keyEvent';
|
||||
import * as Panel from '../../panel-utils/PanelUtils';
|
||||
@@ -55,6 +56,8 @@ export default function ViewSettings() {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const overrideStylesEnabled = watch('overrideStyles');
|
||||
|
||||
if (!control) {
|
||||
return null;
|
||||
}
|
||||
@@ -91,12 +94,22 @@ export default function ViewSettings() {
|
||||
<CodeEditorModal isOpen={isCodeEditorOpen} onClose={codeEditorHandler.close} />
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='Override CSS styles'
|
||||
description='Enables overriding view styles with custom stylesheet'
|
||||
title={
|
||||
<>
|
||||
<span>Override CSS styles</span>
|
||||
{overrideStylesEnabled && <Tag variant='warning'>ON</Tag>}
|
||||
</>
|
||||
}
|
||||
description={
|
||||
overrideStylesEnabled
|
||||
? 'CSS override is ON. Ontime views will use the custom override stylesheet.'
|
||||
: 'Enables overriding view styles with custom stylesheet'
|
||||
}
|
||||
descriptionTone={overrideStylesEnabled ? 'warning' : 'default'}
|
||||
/>
|
||||
<Switch
|
||||
size='large'
|
||||
checked={watch('overrideStyles')}
|
||||
checked={overrideStylesEnabled}
|
||||
onCheckedChange={(value: boolean) => setValue('overrideStyles', value, { shouldDirty: true })}
|
||||
/>
|
||||
<Button onClick={codeEditorHandler.open} disabled={isSubmitting}>
|
||||
|
||||
Reference in New Issue
Block a user