mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 21:03:29 +00:00
fix; osc subscriptions (#392)
* style: fix typo * refactor: convert to typescript * refactor: convert to typescript * refactor: create patch for osc subscriptions * fix: issue with subscription invalidation
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { OSCSettings, OscSubscription } from 'ontime-types';
|
||||
import type { OscSubscription } from 'ontime-types';
|
||||
import { TimerLifeCycle } from 'ontime-types';
|
||||
import { generateId } from 'ontime-utils';
|
||||
|
||||
import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-query/useOscSettings';
|
||||
import { oscPlaceholderSettings, PlaceholderSettings } from '../../../common/models/OscSettings';
|
||||
import useOscSettings, { usePostOscSubscriptions } from '../../../common/hooks-query/useOscSettings';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
@@ -44,87 +42,102 @@ const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string
|
||||
|
||||
export default function OscIntegration() {
|
||||
const { data } = useOscSettings();
|
||||
const { mutateAsync } = useOscSettingsMutation();
|
||||
const { mutateAsync } = usePostOscSubscriptions();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { isSubmitting, isDirty, isValid },
|
||||
} = useForm<PlaceholderSettings>({
|
||||
defaultValues: data,
|
||||
values: data,
|
||||
} = useForm<OscSubscription>({
|
||||
defaultValues: data.subscriptions,
|
||||
values: data.subscriptions,
|
||||
});
|
||||
|
||||
const [subscriptionState, setSubscription] = useState<OscSubscription>(
|
||||
data?.subscriptions || oscPlaceholderSettings.subscriptions,
|
||||
);
|
||||
const [hasManualChange, setHasManualChange] = useState(false);
|
||||
|
||||
const [showSection, setShowSection] = useState<OntimeCycle>(TimerLifeCycle.onLoad);
|
||||
|
||||
const resetForm = () => {
|
||||
const originalData = data || oscPlaceholderSettings;
|
||||
setSubscription(originalData.subscriptions);
|
||||
// @ts-expect-error -- we know the data here is safe
|
||||
reset(originalData);
|
||||
reset(data.subscriptions);
|
||||
};
|
||||
|
||||
const deleteSubscriptionEntry = (cycle: OntimeCycle, id: string) => {
|
||||
setSubscription((prev) => {
|
||||
const newData = { ...prev };
|
||||
newData[cycle] = [...prev[cycle].filter((el) => el.id !== id)];
|
||||
return newData;
|
||||
});
|
||||
setHasManualChange(true);
|
||||
};
|
||||
|
||||
const addNewSubscriptionEntry = async (cycle: OntimeCycle) => {
|
||||
setSubscription((prev) => {
|
||||
const newData = structuredClone(prev);
|
||||
newData[cycle] = [...prev[cycle], { id: generateId(), message: '', enabled: false }];
|
||||
return newData;
|
||||
});
|
||||
setHasManualChange(true);
|
||||
};
|
||||
|
||||
const onSubmit = async (values: OSCSettings | PlaceholderSettings) => {
|
||||
const onSubmit = async (values: OscSubscription) => {
|
||||
try {
|
||||
// @ts-expect-error -- we know of the type mismatch, not pertinent here
|
||||
await mutateAsync(values);
|
||||
setHasManualChange(false);
|
||||
const subscriptions = {
|
||||
onLoad: values.onLoad ?? [],
|
||||
onStart: values.onStart ?? [],
|
||||
onPause: values.onPause ?? [],
|
||||
onStop: values.onStop ?? [],
|
||||
onUpdate: values.onUpdate ?? [],
|
||||
onFinish: values.onFinish ?? [],
|
||||
};
|
||||
|
||||
await mutateAsync(subscriptions);
|
||||
} catch (error) {
|
||||
emitError(`Error setting OSC: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const subscriptionKeys = Object.keys(subscriptionState);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSubscriptions'>
|
||||
{subscriptionKeys.map((cycle, idx) => {
|
||||
return (
|
||||
<div key={`${cycle}-${idx}`}>
|
||||
<OscSubscriptionRow
|
||||
key={cycle}
|
||||
cycle={cycle as TimerLifeCycle}
|
||||
title={sectionText[cycle as TimerLifeCycle].title}
|
||||
subtitle={sectionText[cycle as TimerLifeCycle].subtitle}
|
||||
visible={showSection === cycle}
|
||||
setShowSection={setShowSection}
|
||||
subscriptionOptions={subscriptionState[cycle as TimerLifeCycle]}
|
||||
handleDelete={deleteSubscriptionEntry}
|
||||
handleAddNew={addNewSubscriptionEntry}
|
||||
register={register}
|
||||
/>
|
||||
{idx < subscriptionKeys.length - 1 && <hr className={styles.divider} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='osc-subscriptions'>
|
||||
<OscSubscriptionRow
|
||||
cycle={TimerLifeCycle.onLoad}
|
||||
title={sectionText.onLoad.title}
|
||||
subtitle={sectionText.onLoad.subtitle}
|
||||
visible={showSection === TimerLifeCycle.onLoad}
|
||||
setShowSection={setShowSection}
|
||||
register={register}
|
||||
control={control}
|
||||
/>
|
||||
<OscSubscriptionRow
|
||||
cycle={TimerLifeCycle.onStart}
|
||||
title={sectionText.onStart.title}
|
||||
subtitle={sectionText.onStart.subtitle}
|
||||
visible={showSection === TimerLifeCycle.onStart}
|
||||
setShowSection={setShowSection}
|
||||
register={register}
|
||||
control={control}
|
||||
/>
|
||||
<OscSubscriptionRow
|
||||
cycle={TimerLifeCycle.onPause}
|
||||
title={sectionText.onPause.title}
|
||||
subtitle={sectionText.onPause.subtitle}
|
||||
visible={showSection === TimerLifeCycle.onPause}
|
||||
setShowSection={setShowSection}
|
||||
register={register}
|
||||
control={control}
|
||||
/>
|
||||
<OscSubscriptionRow
|
||||
cycle={TimerLifeCycle.onStop}
|
||||
title={sectionText.onStop.title}
|
||||
subtitle={sectionText.onStop.subtitle}
|
||||
visible={showSection === TimerLifeCycle.onStop}
|
||||
setShowSection={setShowSection}
|
||||
register={register}
|
||||
control={control}
|
||||
/>
|
||||
<OscSubscriptionRow
|
||||
cycle={TimerLifeCycle.onUpdate}
|
||||
title={sectionText.onUpdate.title}
|
||||
subtitle={sectionText.onUpdate.subtitle}
|
||||
visible={showSection === TimerLifeCycle.onUpdate}
|
||||
setShowSection={setShowSection}
|
||||
register={register}
|
||||
control={control}
|
||||
/>
|
||||
<OscSubscriptionRow
|
||||
cycle={TimerLifeCycle.onFinish}
|
||||
title={sectionText.onFinish.title}
|
||||
subtitle={sectionText.onFinish.subtitle}
|
||||
visible={showSection === TimerLifeCycle.onFinish}
|
||||
setShowSection={setShowSection}
|
||||
register={register}
|
||||
control={control}
|
||||
/>
|
||||
<OntimeModalFooter
|
||||
formId='oscSubscriptions'
|
||||
formId='osc-subscriptions'
|
||||
handleRevert={resetForm}
|
||||
isDirty={isDirty || hasManualChange}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
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 { OscSubscriptionOptions, TimerLifeCycle } from 'ontime-types';
|
||||
import { 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';
|
||||
@@ -13,37 +15,50 @@ interface OscSubscriptionRowProps {
|
||||
subtitle: string;
|
||||
visible: boolean;
|
||||
setShowSection: (cycle: TimerLifeCycle) => void;
|
||||
subscriptionOptions: OscSubscriptionOptions[];
|
||||
handleDelete: (cycle: TimerLifeCycle, id: string) => void;
|
||||
handleAddNew: (cycle: TimerLifeCycle) => void;
|
||||
register: UseFormRegister<any>;
|
||||
register: UseFormRegister<OscSubscription>;
|
||||
control: Control<OscSubscription>;
|
||||
}
|
||||
|
||||
export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
|
||||
const { cycle, title, subtitle, visible, setShowSection, subscriptionOptions, handleDelete, handleAddNew, register } =
|
||||
props;
|
||||
const { cycle, title, subtitle, visible, setShowSection, register, control } = props;
|
||||
const { emitError } = useEmitLog();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: cycle,
|
||||
control,
|
||||
});
|
||||
|
||||
const hasTooManyOptions = subscriptionOptions.length >= 3;
|
||||
const hasTooManyOptions = fields.length >= 3;
|
||||
const headerStyle = `${styles.splitSection} ${visible ? '' : styles.showPointer}`;
|
||||
const registerPrefix = `subscriptions.${cycle}`;
|
||||
|
||||
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}`}>{title}</span>
|
||||
<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 && (
|
||||
<>
|
||||
{subscriptionOptions.map((option, idx) => (
|
||||
<div key={option.id} className={styles.entryRow}>
|
||||
<input type='hidden' {...register(`${registerPrefix}[${idx}].id`)} value={option.id} />
|
||||
{fields.map((subscription, index) => (
|
||||
<div key={subscription.id} className={styles.entryRow}>
|
||||
<IconButton
|
||||
icon={<IoRemove />}
|
||||
onClick={() => handleDelete(cycle, option.id)}
|
||||
onClick={() => remove(index)}
|
||||
aria-label='delete'
|
||||
size='xs'
|
||||
colorScheme='red'
|
||||
@@ -52,13 +67,14 @@ export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
|
||||
placeholder='OSC Message'
|
||||
size='xs'
|
||||
variant='ontime-filled-on-light'
|
||||
{...register(`${registerPrefix}[${idx}].message`)}
|
||||
autoComplete='off'
|
||||
{...register(`${cycle}.${index}.message`)}
|
||||
/>
|
||||
<Switch variant='ontime-on-light' {...register(`${registerPrefix}[${idx}].enabled`)} />
|
||||
<Switch variant='ontime-on-light' {...register(`${cycle}.${index}.enabled`)} />
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => handleAddNew(cycle)}
|
||||
onClick={handleAddNew}
|
||||
className={styles.shiftRight}
|
||||
isDisabled={hasTooManyOptions}
|
||||
size='xs'
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function AliasesForm() {
|
||||
|
||||
const addNew = () => {
|
||||
if (fields.length > 20) {
|
||||
emitError('Maximum amount of aliases reacted (20)');
|
||||
emitError('Maximum amount of aliases reached (20)');
|
||||
return;
|
||||
}
|
||||
append({
|
||||
|
||||
Reference in New Issue
Block a user