mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 12:53:32 +00:00
V2 integration (#293)
* feat: integrations and lifecycles * refactor: prevent issues with start order
This commit is contained in:
@@ -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;
|
||||
}
|
||||
+11
-22
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+8
-9
@@ -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',
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user