From cd34149921c1ac5c46209d27fdaa393bd294a941 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 24 Nov 2024 14:33:54 +0100 Subject: [PATCH] wip: automation UI --- .../src/features/app-settings/AppSettings.tsx | 2 + .../app-settings/panel-utils/PanelUtils.tsx | 7 +- .../AutomationCard.module.scss | 20 +++ .../automations-panel/AutomationCard.tsx | 22 +++ .../AutomationItem.module.scss | 34 +++++ .../automations-panel/AutomationItem.tsx | 133 ++++++++++++++++++ .../AutomationManagement.module.scss | 5 + .../AutomationManagement.tsx | 69 +++++++++ .../automations-panel/AutomationSettings.tsx | 114 +++++++++++++++ .../automations-panel/AutomationsPanel.tsx | 42 ++++++ .../automations-panel/automationUtils.ts | 29 ++++ .../app-settings/useAppSettingsMenu.tsx | 8 ++ 12 files changed, 481 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx create mode 100644 apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts 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..69be8a6e55 100644 --- a/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx +++ b/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx @@ -44,16 +44,15 @@ export function Card({ children, className, ...props }: { children: ReactNode } } export function Table({ className, children }: { className?: string; children: ReactNode }) { - const classes = cx([style.table, className]); return (
- {children}
+ {children}
); } -export function ListGroup({ children }: { children: ReactNode }) { - return
    {children}
; +export function ListGroup({ className, children }: { className?: string; children: ReactNode }) { + 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..566552bbf6 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss @@ -0,0 +1,20 @@ +.cardCollapsed { + display: flex; + gap: 1rem; +} + +.append { + position: absolute; + display: flex; + align-items: center; + gap: 1rem; + top: -0.75rem; + background-color: $gray-900; + left: 0; + left: 50%; + height: 2rem; // match button height + transform: translateX(-50%); + padding-inline: 1rem; + border-radius: 99px; + font-size: calc(1rem - 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..441feddd0e --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx @@ -0,0 +1,22 @@ +import { PropsWithChildren, ReactNode } from 'react'; + +import { cx } from '../../../../common/utils/styleUtils'; +import * as Panel from '../../panel-utils/PanelUtils'; + +import style from './AutomationCard.module.scss'; + +interface AutomationCardProps { + append: ReactNode; + className?: string; +} + +export default function AutomationCard(props: PropsWithChildren) { + const { append, className, 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..b45c3c4f7e --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss @@ -0,0 +1,34 @@ +.cardItems { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.fullWidth { + flex: 1; + align-items: center; +} + +.cardCollapsed { + background-color: $gray-1350; + 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; +} + +.filterRow{ + display: grid; + grid-template-columns: 1fr 1fr 1fr auto; + 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..9cd168671a --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx @@ -0,0 +1,133 @@ +import { PropsWithChildren, useState } from 'react'; +import { Button, ButtonGroup, IconButton, Input, Select } from '@chakra-ui/react'; +import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; +import { IoChevronDown } from '@react-icons/all-files/io5/IoChevronDown'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; + +import AutomationCard from './AutomationCard'; +import { cycles } from './automationUtils'; + +import style from './AutomationItem.module.scss'; + +interface AutomationCardProps { + title: string; + trigger: string; +} + +export default function AutomationItem(props: AutomationCardProps) { + const { title, trigger } = props; + const [expanded, setExpanded] = useState(false); + + return ( +
  • + + + } + aria-label='Edit entry' + onClick={() => setExpanded((prev) => !prev)} + /> + } + color='#FA5656' + aria-label='Delete entry' + onClick={() => setExpanded((prev) => !prev)} + /> + + {expanded && } +
  • + ); +} + +function AutomationCardExpanded(props: PropsWithChildren) { + const { title, trigger } = props; + const [triggerRole, setTriggerRole] = useState('all'); + + return ( + <> + + + + + + If + + + + + match + + } + > +
    + + + + } + aria-label='Delete entry' + onClick={() => undefined} + /> +
    + } + aria-label='Add entry' + onClick={() => undefined} + /> +
    + + + + + + } + aria-label='Add entry' + onClick={() => undefined} + /> + + + ); +} 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..2f9e33d1b7 --- /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..7d528e1aae --- /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 './AutomationManagement.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..95a0b6f62b --- /dev/null +++ b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts @@ -0,0 +1,29 @@ +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' }, +]; + +export const field = [ + { id: 1, label: 'Cue', value: 'cue' }, + { id: 2, label: 'Title', value: 'title' }, + { id: 3, label: 'Note', value: 'note' }, + { id: 4, label: 'Custom', value: 'cue' }, + { id: 5, label: 'Cue', value: 'cue' }, + { id: 6, label: 'Cue', value: 'cue' }, + { id: 7, label: 'Cue', value: 'cue' }, +]; 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',