V2 integration (#293)

* feat: integrations and lifecycles

* refactor: prevent issues with start order
This commit is contained in:
Carlos Valente
2023-02-23 20:22:38 +01:00
committed by GitHub
parent d8a84ac88d
commit 70cc071425
61 changed files with 1510 additions and 687 deletions
@@ -0,0 +1,77 @@
@use '../../theme/v2Styles' as *;
@use '../../theme/ontimeColours' as *;
.headerNotes {
font-size: $text-body-size;
width: 100%;
padding: 0 16px;
color: $modal-note-color;
margin-bottom: $section-spacing;
}
.divider {
margin: 8px 0;
border: 0;
border-top: 1px solid $gray-100;
}
.sectionContainer {
display: flex;
flex-direction: column;
height: 100%
}
.splitSection {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 3px;
padding: 8px;
&:hover {
background-color: $blue-50;
}
}
.sectionTitle {
font-size: 14px;
display: block;
&.main {
font-weight: 600;
}
}
@mixin subsection {
font-size: 14px;
display: block;
}
.sectionSubtitle {
@include subsection;
color: $modal-note-color;
}
.error {
@include subsection;
color: $error-red;
}
.buttonSection {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid $gray-100;
display: flex;
gap: 16px;
}
.buttonSection {
button:first-of-type {
margin-right: auto;
}
}
.spacer {
flex-grow: 1;
}
@@ -10,17 +10,19 @@ import {
TabPanels,
Tabs,
} from '@chakra-ui/react';
import PropTypes from 'prop-types';
import AliasesModal from './AliasesModal';
import AppSettingsModal from './AppSettingsModal';
import EventSettingsModal from './EventSettingsModal';
import IntegrationSettingsModal from './IntegrationSettingsModal';
import OscSettingsModal from './OscSettingsModal';
import TableOptionsModal from './TableOptionsModal';
import ViewsSettingsModal from './ViewsSettingsModal';
export default function ModalManager(props) {
interface ModalManagerProps {
isOpen: boolean;
onClose: () => void;
}
export default function ModalManager(props: ModalManagerProps) {
const { isOpen, onClose } = props;
return (
<Modal
@@ -38,13 +40,11 @@ export default function ModalManager(props) {
<Tabs size='sm' isLazy>
<TabList>
<Tab style={{ fontSize: '0.9em' }}>App Settings</Tab>
<Tab style={{ fontSize: '0.9em' }}>Viewers</Tab>
<Tab style={{ fontSize: '0.9em' }}>Event Data</Tab>
<Tab style={{ fontSize: '0.9em' }}>URL Aliases</Tab>
<Tab style={{ fontSize: '0.9em' }}>Cuesheet</Tab>
<Tab style={{ fontSize: '0.9em' }}>OSC</Tab>
{/*<Tab style={{ fontSize: '0.9em' }}>Integration</Tab>*/}
<Tab>App Settings</Tab>
<Tab>Viewers</Tab>
<Tab>Event Data</Tab>
<Tab>URL Aliases</Tab>
<Tab>Cuesheet</Tab>
</TabList>
<TabPanels>
<TabPanel>
@@ -62,20 +62,9 @@ export default function ModalManager(props) {
<TabPanel>
<TableOptionsModal />
</TabPanel>
<TabPanel>
<OscSettingsModal />
</TabPanel>
{/*<TabPanel>*/}
{/* <IntegrationSettingsModal />*/}
{/*</TabPanel>*/}
</TabPanels>
</Tabs>
</ModalContent>
</Modal>
);
}
ModalManager.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
};
@@ -0,0 +1,23 @@
import { Button, ModalFooter } from '@chakra-ui/react';
import styles from './Modal.module.scss';
export default function ModalSubmitFooter() {
return (
<ModalFooter className={styles.buttonSection}>
<Button variant='ghosted' paddingLeft={0} color='#6c6c6c'>
Revert to saved
</Button>
<Button colorScheme='gray'>Cancel</Button>
<Button
variant='ontime-filled'
type='submit'
// disabled={isSubmitting}
isLoading={false}
padding='0 2.5em'
>
Save
</Button>
</ModalFooter>
);
}
@@ -0,0 +1,41 @@
import { PropsWithChildren } from 'react';
import {
Button,
Modal,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react';
import styles from './Modal.module.scss';
interface ModalWrapperProps {
isOpen: boolean;
onClose: () => void;
title: string;
}
export default function ModalWrapper(props: PropsWithChildren<ModalWrapperProps>) {
const { isOpen, onClose, title, children } = props;
return (
<Modal
onClose={onClose}
isOpen={isOpen}
closeOnOverlayClick={false}
motionPreset='slideInBottom'
size='xl'
scrollBehavior='inside'
preserveScrollBarGap
variant='ontime'
>
<ModalOverlay />
<ModalContent>
<ModalHeader>{title}</ModalHeader>
<ModalCloseButton />
{children}
</ModalContent>
</Modal>
);
}
@@ -1,4 +1,5 @@
@use '../../theme/main' as *;
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
//////////////////////////////////// main
@@ -7,7 +8,7 @@
.notes {
font-weight: 400;
color: $light-bg;
color: $action-blue;
display: grid;
place-items: center;
height: 4em;
@@ -16,7 +17,7 @@
.modalFields {
max-height: 45vh;
overflow-y: auto;
scrollbar-color: rgba($light-bg, 0.35) rgba($light-bg, 0.15);
scrollbar-color: rgba($action-blue, 0.35) rgba($action-blue, 0.15);
padding-right: 6px;
label {
@@ -42,7 +43,7 @@
grid-template-columns: 20% 1fr 4em;
.placeholder {
background: $light-text;
background: black;
width: 100%;
height: 24px;
}
@@ -51,19 +52,19 @@
/* Track */
::-webkit-scrollbar-track {
background: rgba($light-bg, 0.15);
background: rgba($gray-50, 0.15);
border-radius: 3px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgba($light-bg, 0.35);
background: rgba($gray-100, 0.35);
border-radius: 3px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgba($light-bg, 0.45);
background: rgba($gray-200, 0.45);
}
.modalInline {
@@ -99,9 +100,8 @@
padding-top: 2em;
display: flex;
justify-content: flex-end;
gap: 1em;
gap: 16px;
}
}
.modalBody > * {
@@ -115,7 +115,7 @@ ul.featureList {
display: flex;
align-items: center;
svg {
color: $ontime-accent-text;
color: black;
margin-right: 4px;
}
}
@@ -124,7 +124,7 @@ ul.featureList {
p {
&.notes {
text-align: center;
border-color: $light-bg-transparent;
border-color: black;
border-width: 0 2px;
font-size: 0.9em;
margin-bottom: 1em;
@@ -139,7 +139,7 @@ span {
}
.blockNotes {
background-color: $bg-gray;
background-color: $gray-1100;
margin: 1em 0;
padding: 0.5em;
font-size: 0.8em;
@@ -147,7 +147,7 @@ span {
table {
background-color: #fff;
border-left: 4px solid lighten($ontime-pink, 5%);
border-left: 4px solid lighten($ontime-color, 5%);
width: 100%;
margin: 0.5em 0;
border-radius: 2px;
@@ -181,12 +181,12 @@ span {
}
.labelNote {
color: $light-bg;
color: $action-blue;
padding-right: 1em;
}
.labelNoteInline {
color: $light-bg;
color: $action-blue;
}
.inlineFlex {
@@ -0,0 +1,40 @@
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
import ModalWrapper from '../ModalWrapper';
import OscIntegrationSettings from './OscIntegrationSettings';
import OscSettingsModal from './OscSettingsModal';
import styles from '../Modal.module.scss';
interface IntegrationModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function IntegrationModal(props: IntegrationModalProps) {
const { isOpen, onClose } = props;
return (
<ModalWrapper title='Integration Settings' isOpen={isOpen} onClose={onClose}>
<div className={styles.headerNotes}>
Manage settings related to protocol integrations. <br />
Changes take effect on app restart.
</div>
<Tabs variant='ontime' size='sm' isLazy>
<TabList>
<Tab>OSC</Tab>
<Tab>Old OSC</Tab>
</TabList>
<TabPanels>
<TabPanel>
<OscIntegrationSettings />
</TabPanel>
<TabPanel>
<OscSettingsModal />
</TabPanel>
</TabPanels>
</Tabs>
</ModalWrapper>
);
}
@@ -0,0 +1,181 @@
import { useContext } from 'react';
import { useForm } from 'react-hook-form';
import { Button, FormControl, Input, ModalBody, ModalFooter, Switch } from '@chakra-ui/react';
import { postOSC } from '../../../common/api/ontimeApi';
import { LoggingContext } from '../../../common/context/LoggingContext';
import useOscSettings from '../../../common/hooks-query/useOscSettings';
import { PlaceholderSettings } from '../../../common/models/OscSettings.type';
import { isIPAddress, isOnlyNumbers } from '../../../common/utils/regex';
import styles from '../Modal.module.scss';
export default function OscIntegrationSettings() {
const { data } = useOscSettings();
const { emitError } = useContext(LoggingContext);
const {
handleSubmit,
register,
reset,
setError,
formState: { errors, isSubmitting, isDirty, isValid },
} = useForm<PlaceholderSettings>({
defaultValues: data,
values: data,
});
const disableSubmit = isSubmitting || !isDirty || !isValid;
const onSubmit = async (values: PlaceholderSettings) => {
const numericPortIn = Number(values.portIn);
const numericPortOut = Number(values.portOut);
if (numericPortIn === numericPortOut) {
setError('portIn', { message: 'OSC IN and OUT Ports cant be the same' });
return;
}
const parsedValues = {
...values,
portIn: numericPortIn,
portOut: numericPortOut,
};
try {
await postOSC(parsedValues);
} catch (error) {
emitError(`Error setting OSC: ${error}`);
}
};
const resetForm = () => reset(data);
return (
<>
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='test'>
<ModalBody>
<div className={styles.splitSection}>
<div>
<span className={`${styles.sectionTitle} ${styles.main}`}>OSC Input</span>
<span className={styles.sectionSubtitle}>Control Ontime with OSC</span>
</div>
<Switch {...register('enabledIn')} variant='ontime-on-light' />
</div>
<FormControl isInvalid={!!errors.portIn} className={styles.splitSection}>
<label htmlFor='portIn'>
<span className={styles.sectionTitle}>Listen on Port</span>
{errors.portIn ? (
<span className={styles.error}>{errors.portIn.message}</span>
) : (
<span className={styles.sectionSubtitle}>Default 8888</span>
)}
</label>
<Input
id='portIn'
placeholder='8888'
width='75px'
size='sm'
textAlign='right'
maxLength={5}
{...register('portIn', {
required: { value: true, message: 'Required field' },
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
pattern: {
value: isOnlyNumbers,
message: 'Value should be numeric',
},
})}
/>
</FormControl>
<hr className={styles.divider} />
<div className={styles.splitSection}>
<div>
<span className={styles.sectionTitle} style={{ fontWeight: 600 }}>
OSC Output
</span>
<span className={styles.sectionSubtitle}>Ontime data feedback</span>
</div>
<Switch {...register('enabledOut')} variant='ontime-on-light' />
</div>
<FormControl isInvalid={!!errors.targetIP} className={styles.splitSection}>
<label htmlFor='targetIP'>
<span className={styles.sectionTitle}>OSC target IP</span>
{errors.targetIP ? (
<span className={styles.error}>{errors.targetIP.message}</span>
) : (
<span className={styles.sectionSubtitle}>Default 127.0.0.1</span>
)}
</label>
<Input
id='targetIP'
placeholder='127.0.0.1'
width='140px'
size='sm'
textAlign='right'
{...register('targetIP', {
required: { value: true, message: 'Required field' },
pattern: {
value: isIPAddress,
message: 'Invalid IP address',
},
})}
/>
</FormControl>
<FormControl className={styles.splitSection}>
<label htmlFor='portOut'>
<span className={styles.sectionTitle}>OSC target Port</span>
{errors.portOut ? (
<span className={styles.error}>{errors.portOut.message}</span>
) : (
<span className={styles.sectionSubtitle}>Default 9999</span>
)}
</label>
<Input
id='portOut'
placeholder='9999'
width='75px'
size='sm'
textAlign='right'
maxLength={5}
{...register('portOut', {
required: { value: true, message: 'Required field' },
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
pattern: {
value: isOnlyNumbers,
message: 'Value should be numeric',
},
})}
/>
</FormControl>
</ModalBody>
{/*<ModalSubmitFooter />*/}
</form>
<ModalFooter className={styles.buttonSection}>
<Button variant='ontime-ghost-on-light' size='sm' onClick={resetForm}>
Revert to saved
</Button>
<Button variant='ontime-subtle-on-light' size='sm'>
Cancel
</Button>
<Button
variant='ontime-filled'
type='submit'
form='test'
disabled={disableSubmit}
isLoading={isSubmitting}
padding='0 2em'
size='sm'
>
Save
</Button>
</ModalFooter>
</>
);
}
@@ -2,16 +2,15 @@ import { useCallback, useContext, useEffect, useState } from 'react';
import { FormControl, FormLabel, Input, ModalBody } from '@chakra-ui/react';
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
import { postOSC } from '../../common/api/ontimeApi';
import EnableBtn from '../../common/components/buttons/EnableBtn';
import { LoggingContext } from '../../common/context/LoggingContext';
import useOscSettings from '../../common/hooks-query/useOscSettings';
import { oscPlaceholderSettings } from '../../common/models/OscSettings.type';
import { postOSC } from '../../../common/api/ontimeApi';
import EnableBtn from '../../../common/components/buttons/EnableBtn';
import { LoggingContext } from '../../../common/context/LoggingContext';
import useOscSettings from '../../../common/hooks-query/useOscSettings';
import { oscPlaceholderSettings } from '../../../common/models/OscSettings.type';
import { inputProps, portInputProps } from '../modalHelper';
import SubmitContainer from '../SubmitContainer';
import { inputProps, portInputProps } from './modalHelper';
import SubmitContainer from './SubmitContainer';
import style from './Modals.module.scss';
import style from '../Modals.module.scss';
// currently defined endpoints
// temporary
@@ -9,4 +9,4 @@ export const portInputProps = {
type: 'number',
min: '1024',
max: '65535',
};
};