refactor: subscription types and form register

This commit is contained in:
Carlos Valente
2023-11-30 22:28:55 +01:00
parent 52a61777ed
commit f720c56345
13 changed files with 205 additions and 113 deletions
@@ -1,4 +1,4 @@
import { isIPAddress, isOnlyNumbers } from '../regex';
import { isIPAddress, isOnlyNumbers, startsWithHttp } from '../regex';
describe('simple tests for regex', () => {
test('isOnlyNumbers', () => {
@@ -24,4 +24,16 @@ describe('simple tests for regex', () => {
expect(isIPAddress.test(t)).toBe(false);
});
});
test('startsWithHttp', () => {
const right = ['http://test'];
const wrong = ['https://test', 'testing', '123.0.1'];
right.forEach((t) => {
expect(startsWithHttp.test(t)).toBe(true);
});
wrong.forEach((t) => {
expect(startsWithHttp.test(t)).toBe(false);
});
});
});
+1
View File
@@ -1,2 +1,3 @@
export const isOnlyNumbers = /^\d+$/;
export const isIPAddress = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
export const startsWithHttp = /^http:\/\//;
@@ -2,9 +2,9 @@ import { ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/r
import ModalWrapper from '../ModalWrapper';
import HttpIntegration from './HttpIntegration';
import OscIntegration from './OscIntegration';
import OscSettings from './OscSettings';
import HttpIntegration from './http/HttpIntegration';
import OscIntegration from './osc/OscIntegration';
import OscSettings from './osc/OscSettings';
import styles from '../Modal.module.scss';
@@ -4,44 +4,15 @@ import { Switch } from '@chakra-ui/react';
import type { HTTPSettings } from 'ontime-types';
import { TimerLifeCycle } from 'ontime-types';
import { useHttpSettings, usePostHttpSettings } from '../../../common/hooks-query/useHttpSettings';
import { useEmitLog } from '../../../common/stores/logger';
import ModalLoader from '../modal-loader/ModalLoader';
import OntimeModalFooter from '../OntimeModalFooter';
import { useHttpSettings, usePostHttpSettings } from '../../../../common/hooks-query/useHttpSettings';
import { useEmitLog } from '../../../../common/stores/logger';
import ModalLoader from '../../modal-loader/ModalLoader';
import OntimeModalFooter from '../../OntimeModalFooter';
import { OntimeCycle, sectionText } from '../integration.utils';
import SubscriptionRow from './SubscriptionRow';
import HttpSubscriptionRow from './HttpSubscriptionRow';
import styles from '../Modal.module.scss';
type OntimeCycle = keyof typeof TimerLifeCycle;
const placeholder = 'http://x.x.x.x:xxxx/api/path';
const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string } } = {
onLoad: {
title: 'On Load',
subtitle: 'Triggers when a timer is loaded',
},
onStart: {
title: 'On Start',
subtitle: 'Triggers when a timer starts',
},
onPause: {
title: 'On Pause',
subtitle: 'Triggers when a running timer is paused',
},
onStop: {
title: 'On Stop',
subtitle: 'Triggers when a running timer is stopped',
},
onUpdate: {
title: 'On Every Second',
subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)',
},
onFinish: {
title: 'On Finish',
subtitle: 'Triggers when a running reaches 0',
},
};
import styles from '../../Modal.module.scss';
export default function HttpIntegration() {
const { data, isFetching } = useHttpSettings();
@@ -54,6 +25,7 @@ export default function HttpIntegration() {
reset,
formState: { isSubmitting, isDirty, isValid },
} = useForm<HTTPSettings>({
mode: 'onBlur',
defaultValues: data,
values: data,
resetOptions: {
@@ -87,7 +59,7 @@ export default function HttpIntegration() {
},
};
console.log('debug will submit', newSettings)
console.log('debug will submit', newSettings);
await mutateAsync(newSettings);
} catch (error) {
@@ -98,6 +70,8 @@ export default function HttpIntegration() {
if (isFetching) {
return <ModalLoader />;
}
const placeholder = 'http://x.x.x.x:xxxx/api/path';
return (
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='http-subscriptions'>
<div className={styles.splitSection}>
@@ -107,7 +81,7 @@ export default function HttpIntegration() {
</div>
<Switch {...register('enabledOut')} variant='ontime-on-light' />
</div>
<SubscriptionRow
<HttpSubscriptionRow
cycle={TimerLifeCycle.onLoad}
title={sectionText.onLoad.title}
subtitle={sectionText.onLoad.subtitle}
@@ -117,7 +91,7 @@ export default function HttpIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<HttpSubscriptionRow
cycle={TimerLifeCycle.onStart}
title={sectionText.onStart.title}
subtitle={sectionText.onStart.subtitle}
@@ -127,7 +101,7 @@ export default function HttpIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<HttpSubscriptionRow
cycle={TimerLifeCycle.onPause}
title={sectionText.onPause.title}
subtitle={sectionText.onPause.subtitle}
@@ -137,7 +111,7 @@ export default function HttpIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<HttpSubscriptionRow
cycle={TimerLifeCycle.onStop}
title={sectionText.onStop.title}
subtitle={sectionText.onStop.subtitle}
@@ -147,7 +121,7 @@ export default function HttpIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<HttpSubscriptionRow
cycle={TimerLifeCycle.onUpdate}
title={sectionText.onUpdate.title}
subtitle={sectionText.onUpdate.subtitle}
@@ -157,7 +131,7 @@ export default function HttpIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<HttpSubscriptionRow
cycle={TimerLifeCycle.onFinish}
title={sectionText.onFinish.title}
subtitle={sectionText.onFinish.subtitle}
@@ -2,12 +2,13 @@ import { Control, useFieldArray, UseFormRegister } from 'react-hook-form';
import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
import { Subscription, TimerLifeCycle } from 'ontime-types';
import { HTTPSettings, TimerLifeCycle } from 'ontime-types';
import { useEmitLog } from '../../../common/stores/logger';
import { useEmitLog } from '../../../../common/stores/logger';
import { startsWithHttp } from '../../../../common/utils/regex';
import collapseStyles from '../../../common/components/collapse-bar/CollapseBar.module.scss';
import styles from '../Modal.module.scss';
import collapseStyles from '../../../../common/components/collapse-bar/CollapseBar.module.scss';
import styles from '../../Modal.module.scss';
interface SubscriptionRowProps {
cycle: TimerLifeCycle;
@@ -15,8 +16,8 @@ interface SubscriptionRowProps {
subtitle: string;
visible: boolean;
setShowSection: (cycle: TimerLifeCycle) => void;
register: UseFormRegister<Subscription>;
control: Control<Subscription>;
register: UseFormRegister<HTTPSettings>;
control: Control<HTTPSettings>;
placeholder: string;
}
@@ -24,7 +25,7 @@ export default function SubscriptionRow(props: SubscriptionRowProps) {
const { cycle, title, subtitle, visible, setShowSection, register, control, placeholder } = props;
const { emitError } = useEmitLog();
const { fields, append, remove } = useFieldArray({
name: cycle,
name: `subscriptions.${cycle}`,
control,
});
@@ -69,7 +70,9 @@ export default function SubscriptionRow(props: SubscriptionRowProps) {
size='xs'
variant='ontime-filled-on-light'
autoComplete='off'
{...register(`subscriptions.${cycle}.${index}.message`)}
{...register(`subscriptions.${cycle}.${index}.message`, {
pattern: { value: startsWithHttp, message: 'Request address must start with http://' },
})}
/>
<Switch variant='ontime-on-light' {...register(`subscriptions.${cycle}.${index}.enabled`)} />
</div>
@@ -0,0 +1,30 @@
import { TimerLifeCycle } from 'ontime-types';
export type OntimeCycle = keyof typeof TimerLifeCycle;
export const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string } } = {
onLoad: {
title: 'On Load',
subtitle: 'Triggers when a timer is loaded',
},
onStart: {
title: 'On Start',
subtitle: 'Triggers when a timer starts',
},
onPause: {
title: 'On Pause',
subtitle: 'Triggers when a running timer is paused',
},
onStop: {
title: 'On Stop',
subtitle: 'Triggers when a running timer is stopped',
},
onUpdate: {
title: 'On Every Second',
subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)',
},
onFinish: {
title: 'On Finish',
subtitle: 'Triggers when a running reaches 0',
},
};
@@ -1,45 +1,17 @@
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { Subscription } from 'ontime-types';
import type { OscSubscription } from 'ontime-types';
import { TimerLifeCycle } from 'ontime-types';
import useOscSettings, { usePostOscSubscriptions } from '../../../common/hooks-query/useOscSettings';
import { useEmitLog } from '../../../common/stores/logger';
import ModalLoader from '../modal-loader/ModalLoader';
import OntimeModalFooter from '../OntimeModalFooter';
import useOscSettings, { usePostOscSubscriptions } from '../../../../common/hooks-query/useOscSettings';
import { useEmitLog } from '../../../../common/stores/logger';
import ModalLoader from '../../modal-loader/ModalLoader';
import OntimeModalFooter from '../../OntimeModalFooter';
import { type OntimeCycle, sectionText } from '../integration.utils';
import SubscriptionRow from './SubscriptionRow';
import OscSubscriptionRow from './OscSubscriptionRow';
import styles from '../Modal.module.scss';
type OntimeCycle = keyof typeof TimerLifeCycle;
const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string } } = {
onLoad: {
title: 'On Load',
subtitle: 'Triggers when a timer is loaded',
},
onStart: {
title: 'On Start',
subtitle: 'Triggers when a timer starts',
},
onPause: {
title: 'On Pause',
subtitle: 'Triggers when a running timer is paused',
},
onStop: {
title: 'On Stop',
subtitle: 'Triggers when a running timer is stopped',
},
onUpdate: {
title: 'On Every Second',
subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)',
},
onFinish: {
title: 'On Finish',
subtitle: 'Triggers when a running reaches 0',
},
};
import styles from '../../Modal.module.scss';
export default function OscIntegration() {
const { data, isFetching } = useOscSettings();
@@ -51,7 +23,7 @@ export default function OscIntegration() {
register,
reset,
formState: { isSubmitting, isDirty, isValid },
} = useForm<Subscription>({
} = useForm<OscSubscription>({
defaultValues: data.subscriptions,
values: data.subscriptions,
resetOptions: {
@@ -71,7 +43,7 @@ export default function OscIntegration() {
reset(data.subscriptions);
};
const onSubmit = async (values: Subscription) => {
const onSubmit = async (values: OscSubscription) => {
try {
const subscriptions = {
onLoad: values.onLoad ?? [],
@@ -91,10 +63,11 @@ export default function OscIntegration() {
if (isFetching) {
return <ModalLoader />;
}
const placeholder = 'OSC message';
return (
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='osc-subscriptions'>
<SubscriptionRow
<OscSubscriptionRow
cycle={TimerLifeCycle.onLoad}
title={sectionText.onLoad.title}
subtitle={sectionText.onLoad.subtitle}
@@ -104,7 +77,7 @@ export default function OscIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<OscSubscriptionRow
cycle={TimerLifeCycle.onStart}
title={sectionText.onStart.title}
subtitle={sectionText.onStart.subtitle}
@@ -114,7 +87,7 @@ export default function OscIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<OscSubscriptionRow
cycle={TimerLifeCycle.onPause}
title={sectionText.onPause.title}
subtitle={sectionText.onPause.subtitle}
@@ -124,7 +97,7 @@ export default function OscIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<OscSubscriptionRow
cycle={TimerLifeCycle.onStop}
title={sectionText.onStop.title}
subtitle={sectionText.onStop.subtitle}
@@ -134,7 +107,7 @@ export default function OscIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<OscSubscriptionRow
cycle={TimerLifeCycle.onUpdate}
title={sectionText.onUpdate.title}
subtitle={sectionText.onUpdate.subtitle}
@@ -144,7 +117,7 @@ export default function OscIntegration() {
control={control}
placeholder={placeholder}
/>
<SubscriptionRow
<OscSubscriptionRow
cycle={TimerLifeCycle.onFinish}
title={sectionText.onFinish.title}
subtitle={sectionText.onFinish.subtitle}
@@ -4,14 +4,14 @@ import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { FormControl, Input, Switch } from '@chakra-ui/react';
import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-query/useOscSettings';
import { PlaceholderSettings } from '../../../common/models/OscSettings';
import { useEmitLog } from '../../../common/stores/logger';
import { isIPAddress, isOnlyNumbers } from '../../../common/utils/regex';
import ModalLoader from '../modal-loader/ModalLoader';
import OntimeModalFooter from '../OntimeModalFooter';
import useOscSettings, { useOscSettingsMutation } from '../../../../common/hooks-query/useOscSettings';
import { PlaceholderSettings } from '../../../../common/models/OscSettings';
import { useEmitLog } from '../../../../common/stores/logger';
import { isIPAddress, isOnlyNumbers } from '../../../../common/utils/regex';
import ModalLoader from '../../modal-loader/ModalLoader';
import OntimeModalFooter from '../../OntimeModalFooter';
import styles from '../Modal.module.scss';
import styles from '../../Modal.module.scss';
export default function OscSettings() {
const { data, isFetching } = useOscSettings();
@@ -0,0 +1,92 @@
import { Control, useFieldArray, UseFormRegister } from 'react-hook-form';
import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
import type { OscSubscription, TimerLifeCycle } from 'ontime-types';
import { useEmitLog } from '../../../../common/stores/logger';
import collapseStyles from '../../../../common/components/collapse-bar/CollapseBar.module.scss';
import styles from '../../Modal.module.scss';
interface OscSubscriptionRowProps {
cycle: TimerLifeCycle;
title: string;
subtitle: string;
visible: boolean;
setShowSection: (cycle: TimerLifeCycle) => void;
register: UseFormRegister<OscSubscription>;
control: Control<OscSubscription>;
placeholder: string;
}
export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
const { cycle, title, subtitle, visible, setShowSection, register, control, placeholder } = props;
const { emitError } = useEmitLog();
const { fields, append, remove } = useFieldArray({
name: cycle,
control,
});
const hasTooManyOptions = fields.length >= 3;
const headerStyle = `${styles.splitSection} ${visible ? '' : styles.showPointer}`;
const sectionTitle = `${title} ${fields.length ? fields.length : '-'} / 3`;
const handleAddNew = () => {
if (hasTooManyOptions) {
emitError('Maximum amount of onLoad subscriptions reached (3)');
return;
}
append({
message: '',
enabled: false,
});
};
return (
<>
<div className={headerStyle} onClick={() => setShowSection(cycle)}>
<div>
<span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span>
{visible && <span className={styles.sectionSubtitle}>{subtitle}</span>}
</div>
<FiChevronUp className={visible ? collapseStyles.moreCollapsed : collapseStyles.moreExpanded} />
</div>
{visible && (
<>
{fields.map((subscription, index) => (
<div key={subscription.id} className={styles.entryRow}>
<IconButton
icon={<IoRemove />}
onClick={() => remove(index)}
aria-label='delete'
size='xs'
colorScheme='red'
/>
<Input
placeholder={placeholder}
size='xs'
variant='ontime-filled-on-light'
autoComplete='off'
{...register(`${cycle}.${index}.message`)}
/>
<Switch variant='ontime-on-light' {...register(`${cycle}.${index}.enabled`)} />
</div>
))}
<Button
onClick={handleAddNew}
className={styles.shiftRight}
isDisabled={hasTooManyOptions}
size='xs'
colorScheme='blue'
variant='outline'
padding='0 2em'
>
Add new
</Button>
</>
)}
</>
);
}
@@ -1,5 +1,9 @@
import { Subscription } from './Subscription.type.js';
type HttpSubscriptionOptions = { message: string; enabled: boolean };
export type HttpSubscription = Subscription<HttpSubscriptionOptions>;
export interface HTTPSettings {
enabledOut: boolean;
subscriptions: Subscription;
subscriptions: HttpSubscription;
}
@@ -1,10 +1,13 @@
import { Subscription } from './Subscription.type.js';
type OscSubscriptionOptions = { message: string; enabled: boolean };
export type OscSubscription = Subscription<OscSubscriptionOptions>;
export interface OSCSettings {
portIn: number;
portOut: number;
targetIP: string;
enabledIn: boolean;
enabledOut: boolean;
subscriptions: Subscription;
subscriptions: OscSubscription;
}
@@ -1,4 +1,3 @@
import { TimerLifeCycleKey } from './TimerLifecycle.type.js';
export type SubscriptionOptions = { message: string; enabled: boolean };
export type Subscription = { [key in TimerLifeCycleKey]: SubscriptionOptions[] };
export type Subscription<T> = { [key in TimerLifeCycleKey]: T[] };
+4 -3
View File
@@ -29,11 +29,12 @@ export type { Alias } from './definitions/core/Alias.type.js';
export type { UserFields } from './definitions/core/UserFields.type.js';
// ---> Integration, Subscription
export type { Subscription, SubscriptionOptions } from './definitions/core/Subscription.type.js';
export type { Subscription } from './definitions/core/Subscription.type.js';
// ---> OSC
export type { OSCSettings } from './definitions/core/OscSettings.type.js';
export type { OSCSettings, OscSubscription } from './definitions/core/OscSettings.type.js';
// ---> HTTP
export type { HTTPSettings } from './definitions/core/HttpSettings.type.js';
export type { HTTPSettings, HttpSubscription } from './definitions/core/HttpSettings.type.js';
// SERVER RESPONSES
export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js';