Skip to content

Commit

Permalink
wip: automation UI
Browse files Browse the repository at this point in the history
  • Loading branch information
cpvalente committed Nov 24, 2024
1 parent de2b60f commit e6ee477
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 2 deletions.
2 changes: 2 additions & 0 deletions apps/client/src/features/app-settings/AppSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -31,6 +32,7 @@ export default function AppSettings() {
{panel === 'feature_settings' && <FeatureSettingsPanel location={location} />}
{panel === 'sources' && <SourcesPanel />}
{panel === 'integrations' && <IntegrationsPanel location={location} />}
{panel === 'automations' && <AutomationsPanel location={location} />}
{panel === 'client_control' && <ClientControlPanel />}
{panel === 'about' && <AboutPanel />}
{panel === 'network' && <NetworkLogPanel location={location} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ export function Table({ className, children }: { className?: string; children: R
);
}

export function ListGroup({ children }: { children: ReactNode }) {
return <ul className={style.listGroup}>{children}</ul>;
export function ListGroup({ className, children }: { className?: string; children: ReactNode }) {
const classes = cx([style.listGroup, className]);
return <ul className={classes}>{children}</ul>;
}

export function ListItem({ children }: { children: ReactNode }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<AutomationCardProps>) {
const { append, children } = props;
return (
<div className={style.card}>
<Panel.Card>{children}</Panel.Card>
<div className={style.append}>{append}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 (
<AutomationCardExpanded title={title} trigger={trigger}>
<Button onClick={() => setExpanded(false)}>Collapse</Button>
</AutomationCardExpanded>
);
}

return (
<li className={style.cardCollapsed}>
<div>
<Panel.Description>Automation title</Panel.Description>
<div>{title}</div>
</div>
<div>
<Panel.Description>Trigger</Panel.Description>
<div>{trigger}</div>
</div>
<Button onClick={() => setExpanded(true)}>Expand</Button>
</li>
);
}

function AutomationCardExpanded(props: PropsWithChildren<AutomationCardProps>) {
const { title, trigger, children } = props;

Check failure on line 43 in apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'title' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 43 in apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'trigger' is assigned a value but never used. Allowed unused vars must match /^_/u
return (
<li className={style.cardExpanded}>
<AutomationCard append='Automation title'>
<Input />
</AutomationCard>

<AutomationCard append='Trigger on'>
<Select />
</AutomationCard>

<AutomationCard append='Trigger if all / any match'>
<Select />
<Select />
<Select />
</AutomationCard>

<AutomationCard append='Send'>
<Select />
<div>
<Input />
<Input />
</div>
<Button />
<Button>+</Button>
</AutomationCard>
{children}
</li>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.list {
display: flex;
flex-direction: column;
gap: 3rem;
}
Original file line number Diff line number Diff line change
@@ -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 (
<Panel.Card>
<Panel.SubHeader>
Manage automations
<div>
<Button variant='ontime-ghosted' size='sm' onClick={() => undefined} isDisabled={false}>
Revert to saved
</Button>
<Button variant='ontime-filled' size='sm' type='submit' form='osc-form' isDisabled={false} isLoading={false}>
Save
</Button>
</div>
</Panel.SubHeader>

<Panel.Divider />

<Panel.Section
as='form'
id='automations-form'
onSubmit={() => undefined}
onKeyDown={() => console.log('prevent escapee')}
>
<Panel.Loader isLoading={false} />
<Panel.ListGroup className={style.list}>
{data.map((automation) => {
return <AutomationItem key={automation.id} title={automation.title} trigger={automation.trigger} />;
})}
</Panel.ListGroup>
</Panel.Section>
</Panel.Card>
);
}
Original file line number Diff line number Diff line change
@@ -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,

Check failure on line 23 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'handleSubmit' is assigned a value but never used. Allowed unused vars must match /^_/u
reset,

Check failure on line 24 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'reset' is assigned a value but never used. Allowed unused vars must match /^_/u
register,
setError,

Check failure on line 26 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'setError' is assigned a value but never used. Allowed unused vars must match /^_/u
formState: { errors, isSubmitting, isDirty, isValid },

Check failure on line 27 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'isSubmitting' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 27 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'isDirty' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 27 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

'isValid' is assigned a value but never used. Allowed unused vars must match /^_/u
} = useForm<AutomationSettingsOptions>({
mode: 'onChange',
defaultValues: automationSettingsPlaceholder,
values: automationSettingsPlaceholder,
resetOptions: {
keepDirtyValues: true,
},
});

return (
<Panel.Card>
<Panel.SubHeader>
Automation settings
<div className={style.flex}>
<Button variant='ontime-ghosted' size='sm' onClick={() => undefined} isDisabled={false}>
Revert to saved
</Button>
<Button variant='ontime-filled' size='sm' type='submit' form='osc-form' isDisabled={false} isLoading={false}>
Save
</Button>
</div>
</Panel.SubHeader>

<Panel.Divider />

<Panel.Section
as='form'
id='automation-form'
onSubmit={() => undefined}
onKeyDown={() => console.log('prevent escapee')}
>
<Panel.Loader isLoading={false} />
<Panel.Title>OSC Input</Panel.Title>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='OSC input' description='Allow control of Ontime through OSC' />
<Controller
control={control}
name='enabledIn'
render={({ field: { onChange, value, ref } }) => (
<Switch variant='ontime' size='lg' isChecked={value} onChange={onChange} ref={ref} />
)}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field title='Listen on port' description='Port for incoming OSC. Default: 8888' error={'asdas'} />

Check failure on line 74 in apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx

View workflow job for this annotation

GitHub Actions / unit-test

Curly braces are unnecessary here
<Input
id='portIn'
placeholder='8888'
width='5rem'
maxLength={5}
size='sm'
textAlign='right'
variant='ontime-filled'
type='number'
autoComplete='off'
{...register('portIn', {
required: { value: true, message: 'Required field' },
max: { value: 65535, message: 'Port must be within range 1024 - 65535' },
min: { value: 1024, message: 'Port must be within range 1024 - 65535' },
pattern: {
value: isOnlyNumbers,
message: 'Value should be numeric',
},
})}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.Title>Automation</Panel.Title>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Enable automations' description='Allow Ontime to send messages on lifecycle triggers' />
<Controller
control={control}
name='enabledIn'
render={({ field: { onChange, value, ref } }) => (
<Switch variant='ontime' size='lg' isChecked={value} onChange={onChange} ref={ref} />
)}
/>
</Panel.ListItem>
</Panel.ListGroup>
</Panel.Section>
</Panel.Card>
);
}
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>('osc', location);
const httpRef = useScrollIntoView<HTMLDivElement>('http', location);

return (
<>
<Panel.Header>Automation</Panel.Header>
<Panel.Section>
<Alert status='info' variant='ontime-on-dark-info'>
<AlertIcon />
<AlertDescription>
Integrations allow Ontime to receive commands or send its data to other systems in your workflow. <br />
<br />
Currently supported protocols are OSC (Open Sound Control), HTTP and Websockets. <br />
WebSockets are used for Ontime and cannot be configured independently. <br />
<ExternalLink href={integrationDocsUrl}>See the docs</ExternalLink>
</AlertDescription>
</Alert>
</Panel.Section>
<Panel.Section>
<div ref={oscRef}>
<AutomationSettings />
</div>
<div ref={httpRef}>
<AutomationManagement />
</div>
</Panel.Section>
</>
);
}
Loading

0 comments on commit e6ee477

Please sign in to comment.