diff --git a/apps/client/src/features/app-settings/AppSettings.tsx b/apps/client/src/features/app-settings/AppSettings.tsx index 28c0098f13..beefc05f7a 100644 --- a/apps/client/src/features/app-settings/AppSettings.tsx +++ b/apps/client/src/features/app-settings/AppSettings.tsx @@ -3,6 +3,7 @@ import { ErrorBoundary } from '@sentry/react'; import { useKeyDown } from '../../common/hooks/useKeyDown'; import AboutPanel from './panel/about-panel/AboutPanel'; +import AutomationsPanel from './panel/automations-panel/AutomationsPanel'; import ClientControlPanel from './panel/client-control-panel/ClientControlPanel'; import FeatureSettingsPanel from './panel/feature-settings-panel/FeatureSettingsPanel'; import GeneralPanel from './panel/general-panel/GeneralPanel'; @@ -31,6 +32,7 @@ export default function AppSettings() { {panel === 'feature_settings' && } {panel === 'sources' && } {panel === 'integrations' && } + {panel === 'automations' && } {panel === 'client_control' && } {panel === 'about' && } {panel === 'network' && } diff --git a/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx b/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx index 4020d73d61..2f3cdab0ee 100644 --- a/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx +++ b/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx @@ -52,8 +52,9 @@ export function Table({ className, children }: { className?: string; children: R ); } -export function ListGroup({ children }: { children: ReactNode }) { - return
    {children}
; +export function ListGroup({ className, children }: { className?: string; children: ReactNode }) { + const classes = cx([style.listGroup, className]); + return
    {children}
; } export function ListItem({ children }: { children: ReactNode }) { diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss new file mode 100644 index 0000000000..a6b401da3c --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss @@ -0,0 +1,12 @@ +.card { + position: relative; +} + +.append { + position: absolute; + top: -1rem; + background-color: $gray-1300; + left: 2rem; + padding: 0.25rem 1rem; + border-radius: 2px; +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx new file mode 100644 index 0000000000..4121decf3d --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx @@ -0,0 +1,18 @@ +import { PropsWithChildren, ReactNode } from 'react'; + +import * as Panel from '../../panel-utils/PanelUtils'; + +import style from './AutomationCard.module.scss'; + +interface AutomationCardProps { + append: ReactNode; +} +export default function AutomationCard(props: PropsWithChildren) { + const { append, children } = props; + return ( +
+ {children} +
{append}
+
+ ); +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss new file mode 100644 index 0000000000..bfe2e48dd1 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss @@ -0,0 +1,17 @@ +.cardCollapsed { + background-color: $gray-1200; + display: flex; + justify-content: space-between; +} + +.cardExpanded { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.threeCols { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx new file mode 100644 index 0000000000..9956591d6f --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx @@ -0,0 +1,72 @@ +import { PropsWithChildren, useState } from 'react'; +import { Button, Input, Select } from '@chakra-ui/react'; + +import * as Panel from '../../panel-utils/PanelUtils'; + +import AutomationCard from './AutomationCard'; + +import style from './AutomationCard.module.scss'; + +interface AutomationCardProps { + title: string; + trigger: string; +} + +export default function AutomationItem(props: AutomationCardProps) { + const { title, trigger } = props; + const [expanded, setExpanded] = useState(false); + + if (expanded) { + return ( + + + + ); + } + + return ( +
  • +
    + Automation title +
    {title}
    +
    +
    + Trigger +
    {trigger}
    +
    + +
  • + ); +} + +function AutomationCardExpanded(props: PropsWithChildren) { + const { title, trigger, children } = props; + return ( +
  • + + + + + + + + + + + + + + + + {children} +
  • + ); +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss new file mode 100644 index 0000000000..32368827af --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss @@ -0,0 +1,5 @@ +.list { + display: flex; + flex-direction: column; + gap: 3rem; +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx new file mode 100644 index 0000000000..dba0bd38e2 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx @@ -0,0 +1,69 @@ +import { Button } from '@chakra-ui/react'; +import { TimerLifeCycle } from 'ontime-types'; + +import * as Panel from '../../panel-utils/PanelUtils'; + +import AutomationItem from './AutomationItem'; + +import style from "./AutomationCard.module.scss"; + +const data = [ + { + id: '1', + title: 'Automation 1', + trigger: TimerLifeCycle.onClock, + filterRule: 'all', + filter: [], + output: [], + }, + { + id: '2', + title: 'Automation 1', + trigger: TimerLifeCycle.onClock, + filterRule: 'all', + filter: [], + output: [], + }, + { + id: '3', + title: 'Automation 1', + trigger: TimerLifeCycle.onClock, + filterRule: 'all', + filter: [], + output: [], + }, +]; + +export default function AutomationManagement() { + return ( + + + Manage automations +
    + + +
    +
    + + + + undefined} + onKeyDown={() => console.log('prevent escapee')} + > + + + {data.map((automation) => { + return ; + })} + + +
    + ); +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx new file mode 100644 index 0000000000..e4aace642b --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx @@ -0,0 +1,114 @@ +import { Controller, useForm } from 'react-hook-form'; +import { Button, Input, Switch } from '@chakra-ui/react'; + +import { isOnlyNumbers } from '../../../../common/utils/regex'; +import * as Panel from '../../panel-utils/PanelUtils'; + +interface AutomationSettingsOptions { + enabledIn: boolean; + portIn: number; +} + +const automationSettingsPlaceholder = { + enabledIn: false, + portIn: 8888, +}; + +const style = { + flex: 'flex', +}; +export default function AutomationSettings() { + const { + control, + handleSubmit, + reset, + register, + setError, + formState: { errors, isSubmitting, isDirty, isValid }, + } = useForm({ + mode: 'onChange', + defaultValues: automationSettingsPlaceholder, + values: automationSettingsPlaceholder, + resetOptions: { + keepDirtyValues: true, + }, + }); + + return ( + + + Automation settings +
    + + +
    +
    + + + + undefined} + onKeyDown={() => console.log('prevent escapee')} + > + + OSC Input + {errors?.root && {errors.root.message}} + + + + ( + + )} + /> + + + + + + + Automation + {errors?.root && {errors.root.message}} + + + + ( + + )} + /> + + + +
    + ); +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx new file mode 100644 index 0000000000..f038628fb8 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx @@ -0,0 +1,42 @@ +import { Alert, AlertDescription, AlertIcon } from '@chakra-ui/react'; + +import ExternalLink from '../../../../common/components/external-link/ExternalLink'; +import useScrollIntoView from '../../../../common/hooks/useScrollIntoView'; +import type { PanelBaseProps } from '../../panel-list/PanelList'; +import * as Panel from '../../panel-utils/PanelUtils'; + +import AutomationManagement from './AutomationManagement'; +import AutomationSettings from './AutomationSettings'; + +const integrationDocsUrl = 'https://docs.getontime.no/api/integrations/'; + +export default function IntegrationsPanel({ location }: PanelBaseProps) { + const oscRef = useScrollIntoView('osc', location); + const httpRef = useScrollIntoView('http', location); + + return ( + <> + Automation + + + + + Integrations allow Ontime to receive commands or send its data to other systems in your workflow.
    +
    + Currently supported protocols are OSC (Open Sound Control), HTTP and Websockets.
    + WebSockets are used for Ontime and cannot be configured independently.
    + See the docs +
    +
    +
    + +
    + +
    +
    + +
    +
    + + ); +} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts new file mode 100644 index 0000000000..1f0456cfa8 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts @@ -0,0 +1,19 @@ +import { TimerLifeCycle } from 'ontime-types'; + +type CycleLabel = { + id: number; + label: string; + value: keyof typeof TimerLifeCycle; +}; + +export const cycles: CycleLabel[] = [ + { id: 1, label: 'On Load', value: 'onLoad' }, + { id: 2, label: 'On Start', value: 'onStart' }, + { id: 3, label: 'On Pause', value: 'onPause' }, + { id: 4, label: 'On Stop', value: 'onStop' }, + { id: 5, label: 'Every second', value: 'onClock' }, + { id: 5, label: 'On Timer Update', value: 'onUpdate' }, + { id: 6, label: 'On Finish', value: 'onFinish' }, + { id: 7, label: 'On Warning', value: 'onWarning' }, + { id: 8, label: 'On Danger', value: 'onDanger' }, +]; diff --git a/apps/client/src/features/app-settings/useAppSettingsMenu.tsx b/apps/client/src/features/app-settings/useAppSettingsMenu.tsx index 7f87b4d2db..a277ace683 100644 --- a/apps/client/src/features/app-settings/useAppSettingsMenu.tsx +++ b/apps/client/src/features/app-settings/useAppSettingsMenu.tsx @@ -54,6 +54,14 @@ const staticOptions = [ { id: 'integrations__http', label: 'HTTP settings' }, ], }, + { + id: 'automations', + label: 'Automations', + secondary: [ + { id: 'automations__settings', label: 'Automation settings' }, + { id: 'automations__manage', label: 'Manage automations' }, + ], + }, { id: 'network', label: 'Network',