mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
Merge branch 'chore/cleanup' of https://github.com/cpvalente/ontime into chore/cleanup
This commit is contained in:
+3
-3
@@ -3,9 +3,9 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^1.4.1",
|
"@chakra-ui/react": "^1.6.10",
|
||||||
"@emotion/react": "^11",
|
"@emotion/react": "^11.5.0",
|
||||||
"@emotion/styled": "^11",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import { ontimeURL } from './apiConstants';
|
|||||||
|
|
||||||
export const ontimePlaceholderInfo = {
|
export const ontimePlaceholderInfo = {
|
||||||
networkInterfaces: [],
|
networkInterfaces: [],
|
||||||
|
version: '',
|
||||||
|
serverPort: 4001,
|
||||||
|
oscInPort: '',
|
||||||
|
oscOutPort: '',
|
||||||
|
oscOutIP: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInfo = async () => {
|
export const getInfo = async () => {
|
||||||
@@ -10,6 +15,11 @@ export const getInfo = async () => {
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const postInfo = async (data) => {
|
||||||
|
const res = await axios.post(ontimeURL + '/info', data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const downloadEvents = async () => {
|
export const downloadEvents = async () => {
|
||||||
await axios({
|
await axios({
|
||||||
url: ontimeURL + '/db',
|
url: ontimeURL + '/db',
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSocket } from 'app/context/socketContext';
|
import { useSocket } from 'app/context/socketContext';
|
||||||
import style from './Info.module.css';
|
import style from './Info.module.css';
|
||||||
import InfoTitle from './InfoTitle';
|
import InfoTitle from './InfoTitle';
|
||||||
import InfoLogger from './InfoLogger';
|
import InfoLogger from './InfoLogger';
|
||||||
import InfoNif from './InfoNif';
|
import InfoNif from './InfoNif';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export default function Info() {
|
export default function Info() {
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ export default function InfoNif() {
|
|||||||
placeholderData: ontimePlaceholderInfo,
|
placeholderData: ontimePlaceholderInfo,
|
||||||
});
|
});
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const baseURL = 'http://__IP__:4001';
|
||||||
const baseURL = `http://__IP__:${4001}`;
|
|
||||||
|
|
||||||
const handleLink = (url) => {
|
const handleLink = (url) => {
|
||||||
if (window.process?.type === 'renderer') {
|
if (window.process?.type === 'renderer') {
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { IconButton } from '@chakra-ui/button';
|
||||||
|
import { FiPlus, FiMinus } from 'react-icons/fi';
|
||||||
|
import { ModalBody } from '@chakra-ui/modal';
|
||||||
|
import { Input } from '@chakra-ui/react';
|
||||||
|
import { fetchEvent } from 'app/api/eventApi';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
|
import { EVENT_TABLE } from 'app/api/apiConstants';
|
||||||
|
import style from './Modals.module.css';
|
||||||
|
|
||||||
|
export default function AliasesModal() {
|
||||||
|
const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const submitHandler = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
// NOTHING HERE YET
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hardcoded links for now
|
||||||
|
// it will need dynamic PORT assignment
|
||||||
|
const speakerLink = 'http://localhost:4001/speaker';
|
||||||
|
const smLink = 'http://localhost:4001/sm';
|
||||||
|
const publicLink = 'http://localhost:4001/public';
|
||||||
|
const pipLink = 'http://localhost:4001/pip';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={submitHandler}>
|
||||||
|
<ModalBody className={style.modalBody}>
|
||||||
|
<p className={style.notes}>
|
||||||
|
Configure easy to use URL Aliases
|
||||||
|
<br />
|
||||||
|
!!! Feature is not yet implemented !!!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<span> Default URLs </span>
|
||||||
|
|
||||||
|
<div className={style.highNotes}>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Presenter Screen <br />
|
||||||
|
<a
|
||||||
|
href={speakerLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{speakerLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Backstage / Stage Manager Screen <br />
|
||||||
|
<a
|
||||||
|
href={smLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{smLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Public / Foyer Screen <br />
|
||||||
|
<a
|
||||||
|
href={publicLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{publicLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Picture in Picture Screen <br />
|
||||||
|
<a
|
||||||
|
href={pipLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{pipLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span> Manage custom aliases</span>
|
||||||
|
<div className={style.modalInline}>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='URL'
|
||||||
|
placeholder='A long URL'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'A long URL'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='Alias'
|
||||||
|
placeholder='A nice alias'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'A nice alias'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='sm'
|
||||||
|
icon={<FiMinus />}
|
||||||
|
colorScheme='red'
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style.separator} />
|
||||||
|
<div className={style.modalInline}>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='URL'
|
||||||
|
placeholder='URL'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'URL'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='Alias'
|
||||||
|
placeholder='Alias'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'Alias'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='sm'
|
||||||
|
icon={<FiPlus />}
|
||||||
|
colorScheme='blue'
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import { ModalBody } from '@chakra-ui/modal';
|
||||||
|
import { FormLabel, FormControl, Input, Button } from '@chakra-ui/react';
|
||||||
|
import { getInfo, ontimePlaceholderInfo, postInfo } from 'app/api/ontimeApi';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
|
import { APP_TABLE } from 'app/api/apiConstants';
|
||||||
|
import { showErrorToast } from 'common/helpers/toastManager';
|
||||||
|
import style from './Modals.module.css';
|
||||||
|
|
||||||
|
export default function AppSettingsModal() {
|
||||||
|
const { data, status } = useFetch(APP_TABLE, getInfo);
|
||||||
|
const [formData, setFormData] = useState(ontimePlaceholderInfo);
|
||||||
|
const [changed, setChanged] = useState(false);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
oscInPort: data.oscInPort,
|
||||||
|
oscOutPort: data.oscOutPort,
|
||||||
|
oscOutIP: data.oscOutIP,
|
||||||
|
});
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const submitHandler = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const f = formData;
|
||||||
|
let e = { status: false, message: '' };
|
||||||
|
|
||||||
|
// Validate fields
|
||||||
|
if (f.oscInPort < 1024 || f.oscInPort > 65535) {
|
||||||
|
// Port in incorrect range
|
||||||
|
e.status = true;
|
||||||
|
e.message += 'OSC IN Port in incorrect range (1024 - 65535)';
|
||||||
|
} else if (f.oscOutPort < 1024 || f.oscOutPort > 65535) {
|
||||||
|
// Port in incorrect range
|
||||||
|
e.status = true;
|
||||||
|
e.message += 'OSC OUT Port in incorrect range (1024 - 65535)';
|
||||||
|
} else if (f.oscInPort === f.oscOutPort) {
|
||||||
|
// Cant use the same port
|
||||||
|
e.status = true;
|
||||||
|
e.message += 'OSC IN and OUT Ports cant be the same';
|
||||||
|
}
|
||||||
|
|
||||||
|
// set fields with error
|
||||||
|
if (e.status) {
|
||||||
|
showErrorToast('Invalid Input', e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post here
|
||||||
|
postInfo(formData);
|
||||||
|
|
||||||
|
setChanged(false);
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={submitHandler}>
|
||||||
|
<ModalBody className={style.modalBody}>
|
||||||
|
{status === 'success' && (
|
||||||
|
<>
|
||||||
|
<p className={style.notes}>
|
||||||
|
Options related to the application
|
||||||
|
<br />
|
||||||
|
!!! Changes take effect after app restart !!!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<FormControl id='serverPort'>
|
||||||
|
<FormLabel htmlFor='serverPort'>
|
||||||
|
Viewer Port
|
||||||
|
<span className={style.notes}>Port to access viewers</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='title'
|
||||||
|
placeholder='4001'
|
||||||
|
autoComplete='off'
|
||||||
|
value={4001}
|
||||||
|
readOnly
|
||||||
|
style={{ width: '6em', textAlign: 'center' }}
|
||||||
|
/>
|
||||||
|
<span className={style.notes}>(Read Only Value)</span>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id='oscInPort'>
|
||||||
|
<FormLabel htmlFor='oscInPort'>
|
||||||
|
OSC In Port
|
||||||
|
<span className={style.notes}>
|
||||||
|
<br />
|
||||||
|
App Control - Default 8888
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='oscInPort'
|
||||||
|
placeholder='8888'
|
||||||
|
autoComplete='off'
|
||||||
|
type='number'
|
||||||
|
value={formData.oscInPort}
|
||||||
|
min='1024'
|
||||||
|
max='65535'
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
oscInPort: parseInt(event.target.value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
style={{ width: '6em', textAlign: 'center' }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className={style.modalInline}>
|
||||||
|
<FormControl id='oscOutIP' width='auto'>
|
||||||
|
<FormLabel htmlFor='oscOutIP'>
|
||||||
|
OSC Out Target IP
|
||||||
|
<span className={style.notes}>
|
||||||
|
<br />
|
||||||
|
App Feedback - Default 127.0.0.1
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='oscOutIP'
|
||||||
|
placeholder='127.0.0.1'
|
||||||
|
autoComplete='off'
|
||||||
|
value={formData.oscOutIP}
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
oscOutIP: event.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
style={{ width: '12em', textAlign: 'right' }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id='oscOutPort' width='auto'>
|
||||||
|
<FormLabel htmlFor='oscOutPort'>
|
||||||
|
OSC Out Port
|
||||||
|
<span className={style.notes}>
|
||||||
|
<br />
|
||||||
|
Default 9999
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='oscOutPort'
|
||||||
|
placeholder='9999'
|
||||||
|
autoComplete='off'
|
||||||
|
type='number'
|
||||||
|
value={formData.oscOutPort}
|
||||||
|
min='1024'
|
||||||
|
max='65535'
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
oscOutPort: parseInt(event.target.value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
style={{ width: '6em', textAlign: 'left' }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
colorScheme='blue'
|
||||||
|
type='submit'
|
||||||
|
isLoading={submitting}
|
||||||
|
disabled={!changed}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</ModalBody>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
+67
-39
@@ -10,15 +10,18 @@ import { fetchEvent, postEvent } from 'app/api/eventApi';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import { EVENT_TABLE } from 'app/api/apiConstants';
|
import { EVENT_TABLE } from 'app/api/apiConstants';
|
||||||
|
import style from './Modals.module.css';
|
||||||
|
|
||||||
export default function SettingsModal() {
|
export default function SettingsModal() {
|
||||||
const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent);
|
const { data, status } = useFetch(EVENT_TABLE, fetchEvent);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
title: '',
|
title: '',
|
||||||
url: '',
|
url: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
endMessage: '',
|
||||||
});
|
});
|
||||||
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -29,6 +32,7 @@ export default function SettingsModal() {
|
|||||||
url: data.url,
|
url: data.url,
|
||||||
publicInfo: data.publicInfo,
|
publicInfo: data.publicInfo,
|
||||||
backstageInfo: data.backstageInfo,
|
backstageInfo: data.backstageInfo,
|
||||||
|
endMessage: data.endMessage,
|
||||||
});
|
});
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
@@ -36,41 +40,47 @@ export default function SettingsModal() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
await postEvent(formData).then(setSubmitting(false));
|
await postEvent(formData);
|
||||||
|
|
||||||
|
setChanged(false);
|
||||||
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={submitHandler}>
|
<form onSubmit={submitHandler}>
|
||||||
<ModalBody>
|
<ModalBody className={style.modalBody}>
|
||||||
{status === 'success' && (
|
{status === 'success' && (
|
||||||
<>
|
<>
|
||||||
|
<p className={style.notes}>
|
||||||
|
Options related to the running event
|
||||||
|
<br />
|
||||||
|
Affect rendered views
|
||||||
|
</p>
|
||||||
|
|
||||||
<FormControl id='title'>
|
<FormControl id='title'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='title'>Event Title</FormLabel>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='title'
|
|
||||||
>
|
|
||||||
Event Title
|
|
||||||
</FormLabel>
|
|
||||||
<Input
|
<Input
|
||||||
size='sm'
|
size='sm'
|
||||||
|
maxLength={35}
|
||||||
name='title'
|
name='title'
|
||||||
placeholder='Event Title'
|
placeholder='Event Title'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
setFormData({ ...formData, title: event.target.value })
|
setChanged(true);
|
||||||
}
|
setFormData({ ...formData, title: event.target.value });
|
||||||
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='url'>
|
<FormControl id='url'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='url'>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='url'
|
|
||||||
>
|
|
||||||
Event URL
|
Event URL
|
||||||
|
<span className={style.notes}>
|
||||||
|
(shown as a QR code in some views)
|
||||||
|
</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
size='sm'
|
size='sm'
|
||||||
@@ -78,55 +88,73 @@ export default function SettingsModal() {
|
|||||||
placeholder='www.onsite.no'
|
placeholder='www.onsite.no'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.url}
|
value={formData.url}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
setFormData({ ...formData, url: event.target.value })
|
setChanged(true);
|
||||||
}
|
setFormData({ ...formData, url: event.target.value });
|
||||||
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='pubInfo'>
|
<FormControl id='pubInfo'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='pubInfo'>Public Info</FormLabel>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='pubInfo'
|
|
||||||
>
|
|
||||||
Public Info
|
|
||||||
</FormLabel>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
size='sm'
|
size='sm'
|
||||||
name='pubInfo'
|
name='pubInfo'
|
||||||
placeholder='Information to be shown on public screens'
|
placeholder='Information to be shown on public screens'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.publicInfo}
|
value={formData.publicInfo}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
publicInfo: event.target.value,
|
publicInfo: event.target.value,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='backstageInfo'>
|
<FormControl id='backstageInfo'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='backstageInfo'>Backstage Info</FormLabel>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='backstageInfo'
|
|
||||||
>
|
|
||||||
Backstage Info
|
|
||||||
</FormLabel>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
size='sm'
|
size='sm'
|
||||||
name='backstageInfo'
|
name='backstageInfo'
|
||||||
placeholder='Information to be shown on backstage screens'
|
placeholder='Information to be shown on backstage screens'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.backstageInfo}
|
value={formData.backstageInfo}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
backstageInfo: event.target.value,
|
backstageInfo: event.target.value,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl id='endMessage'>
|
||||||
|
<FormLabel htmlFor='endMessage'>
|
||||||
|
End Message
|
||||||
|
<span className={style.notes}>
|
||||||
|
Shown on presenter view when time is finished
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
maxLength={30}
|
||||||
|
name='endMessage'
|
||||||
|
placeholder='Empty message shows elapsed time'
|
||||||
|
autoComplete='off'
|
||||||
|
value={formData.endMessage}
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
endMessage: event.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -134,9 +162,9 @@ export default function SettingsModal() {
|
|||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
mr={3}
|
|
||||||
isLoading={submitting}
|
|
||||||
type='submit'
|
type='submit'
|
||||||
|
isLoading={submitting}
|
||||||
|
disabled={!changed}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
} from '@chakra-ui/modal';
|
} from '@chakra-ui/modal';
|
||||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/tabs';
|
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/tabs';
|
||||||
import SettingsModal from './SettingsModal';
|
import EventSettingsModal from './EventSettingsModal';
|
||||||
|
import AppSettingsModal from './AppSettingsModal';
|
||||||
|
import AliasesModal from './AliasesModal';
|
||||||
|
|
||||||
export default function ModalManager(props) {
|
export default function ModalManager(props) {
|
||||||
const { isOpen, onClose } = props;
|
const { isOpen, onClose } = props;
|
||||||
@@ -31,19 +33,13 @@ export default function ModalManager(props) {
|
|||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<SettingsModal onClose={onClose} />
|
<EventSettingsModal />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<p>Application settings will be here!</p>
|
<AppSettingsModal />
|
||||||
<p>Set port</p>
|
|
||||||
<p>Set end message</p>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<p>You will be able to define URL aliases here!</p>
|
<AliasesModal />
|
||||||
<p>List existing aliases</p>
|
|
||||||
<p>List existing aliases</p>
|
|
||||||
<p>List existing aliases</p>
|
|
||||||
<p>Add new alias input + button</p>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -54,5 +50,5 @@ export default function ModalManager(props) {
|
|||||||
|
|
||||||
ModalManager.propTypes = {
|
ModalManager.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
onClose: PropTypes.bool.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.modalBody {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalBody > * {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalBody > button {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
font-weight: 400;
|
||||||
|
color: #2b6cb0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.notes {
|
||||||
|
text-align: center;
|
||||||
|
border-color: #2b6cb055;
|
||||||
|
border-width: 0 2px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.notes {
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding-left: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalInline {
|
||||||
|
display: flex;
|
||||||
|
gap: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highNotes {
|
||||||
|
background-color: #2b6cb022;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexNote {
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a::after {
|
||||||
|
content: ' \2197';
|
||||||
|
color: #ff7597;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #ff7597;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border: 1px solid #2b6cb055;
|
||||||
|
width: 50%;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@ const withSocket = (Component) => {
|
|||||||
url: '',
|
url: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
endMessage: '',
|
||||||
});
|
});
|
||||||
const [playback, setPlayback] = useState(null);
|
const [playback, setPlayback] = useState(null);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import TitleCard from 'common/components/views/TitleCard';
|
|||||||
import style from './PresenterView.module.css';
|
import style from './PresenterView.module.css';
|
||||||
|
|
||||||
export default function PresenterView(props) {
|
export default function PresenterView(props) {
|
||||||
const { pres, title, time } = props;
|
const { general, pres, title, time } = props;
|
||||||
|
|
||||||
// Set window title
|
// Set window title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -18,6 +18,14 @@ export default function PresenterView(props) {
|
|||||||
const isPlaying = time.playstate !== 'pause';
|
const isPlaying = time.playstate !== 'pause';
|
||||||
const normalisedTime = Math.max(time.running, 0);
|
const normalisedTime = Math.max(time.running, 0);
|
||||||
|
|
||||||
|
// show timer if end message is empty
|
||||||
|
const endMessage =
|
||||||
|
general.endMessage == null || general.endMessage === '' ? (
|
||||||
|
<Countdown time={time.running} hideZeroHours negative />
|
||||||
|
) : (
|
||||||
|
general.endMessage
|
||||||
|
);
|
||||||
|
|
||||||
// motion
|
// motion
|
||||||
const titleVariants = {
|
const titleVariants = {
|
||||||
hidden: {
|
hidden: {
|
||||||
@@ -57,7 +65,7 @@ export default function PresenterView(props) {
|
|||||||
|
|
||||||
<div className={style.timerContainer}>
|
<div className={style.timerContainer}>
|
||||||
{time.finished ? (
|
{time.finished ? (
|
||||||
<div className={style.finished}>TIME UP</div>
|
<div className={style.finished}>{endMessage}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
||||||
<Countdown time={normalisedTime} hideZeroHours />
|
<Countdown time={normalisedTime} hideZeroHours />
|
||||||
|
|||||||
@@ -66,7 +66,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.finished {
|
.finished {
|
||||||
font-size: 18vw;
|
text-align: center;
|
||||||
|
font-size: 12vw;
|
||||||
line-height: 18vw;
|
line-height: 18vw;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #ff6969;
|
color: #ff6969;
|
||||||
|
|||||||
+459
-421
File diff suppressed because it is too large
Load Diff
+6
-2
@@ -112,12 +112,16 @@
|
|||||||
"url": "www.carlosvalente.com",
|
"url": "www.carlosvalente.com",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
||||||
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"osc_enabled": false,
|
"serverPort": 4001,
|
||||||
"osc_port": 8888,
|
"oscInPort": 8888,
|
||||||
|
"oscOutPort": 9999,
|
||||||
|
"oscOutIP": "127.0.0.1",
|
||||||
|
"oscEnabled": true,
|
||||||
"lock": false
|
"lock": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-5
@@ -26,12 +26,14 @@ const nodePath =
|
|||||||
const { startServer, startOSCServer, startOSCClient } = await import(
|
const { startServer, startOSCServer, startOSCClient } = await import(
|
||||||
nodePath
|
nodePath
|
||||||
);
|
);
|
||||||
// Start express server
|
|
||||||
loaded = startServer();
|
|
||||||
// Start OSC Server (API)
|
|
||||||
startOSCServer();
|
|
||||||
// Start OSC Client (Feedback)
|
// Start OSC Client (Feedback)
|
||||||
startOSCClient();
|
await startOSCClient();
|
||||||
|
|
||||||
|
// Start express server
|
||||||
|
loaded = await startServer();
|
||||||
|
|
||||||
|
// Start OSC Server (API)
|
||||||
|
await startOSCServer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
loaded = error;
|
loaded = error;
|
||||||
|
|||||||
+53
-6
@@ -4,6 +4,7 @@ import { sessionId, user } from './utils/analytics.js';
|
|||||||
user.screenview('Node service', 'ontime').send();
|
user.screenview('Node service', 'ontime').send();
|
||||||
user.event('NODE', 'started', 'starting node service').send();
|
user.event('NODE', 'started', 'starting node service').send();
|
||||||
|
|
||||||
|
// import config
|
||||||
import { config } from './config/config.js';
|
import { config } from './config/config.js';
|
||||||
|
|
||||||
// init database
|
// init database
|
||||||
@@ -95,20 +96,66 @@ app.use((err, req, res, next) => {
|
|||||||
res.status(500).send(err.stack);
|
res.status(500).send(err.stack);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*************** START SERVICES ***************/
|
||||||
|
/* Override config
|
||||||
|
* ----------------
|
||||||
|
*
|
||||||
|
* Configuration of services comes from app general config
|
||||||
|
* It can be overriden here by the settings in the db
|
||||||
|
* It can also be overriden on call
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const s = data.settings;
|
||||||
|
|
||||||
|
const oscIP = s.oscOutIP || config.osc.ipOut;
|
||||||
|
const oscOutPort = s.oscOutPort || config.osc.portOut;
|
||||||
|
const oscInPort = s.oscInPort || config.osc.port;
|
||||||
|
|
||||||
|
const serverPort = s.serverPort || config.server.port;
|
||||||
|
|
||||||
|
// Start OSC server
|
||||||
|
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
||||||
|
|
||||||
|
export const startOSCServer = async (overrideConfig = null) => {
|
||||||
|
// Setup default port
|
||||||
|
const oscSettings = {
|
||||||
|
port: overrideConfig?.port || oscInPort,
|
||||||
|
ipOut: oscIP,
|
||||||
|
portOut: oscOutPort,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start OSC Server
|
||||||
|
initiateOSC(oscSettings);
|
||||||
|
};
|
||||||
|
|
||||||
// Start OSC Client
|
// Start OSC Client
|
||||||
// TODO: Move this to function
|
let oscClient = null;
|
||||||
const oscClient = new Client('127.0.0.1', 9999);
|
|
||||||
|
export const startOSCClient = async (overrideConfig = null) => {
|
||||||
|
// Setup default port
|
||||||
|
const port = overrideConfig?.port || oscOutPort;
|
||||||
|
console.log('initialise OSC Client on port: ', port);
|
||||||
|
|
||||||
|
oscClient = new Client(oscIP, oscOutPort);
|
||||||
|
};
|
||||||
|
|
||||||
// create HTTP server
|
// create HTTP server
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
export const startServer = (overrideConfig = null) => {
|
export const startServer = async (overrideConfig = null) => {
|
||||||
// Setup default port
|
// Setup default port
|
||||||
const serverPort = overrideConfig?.port || config.server.port;
|
// const port = overrideConfig?.port || serverPort;
|
||||||
|
|
||||||
|
/* Note: Port is hardcoded
|
||||||
|
* need to find a good solution for sharing
|
||||||
|
* the dynamic info with the frontend
|
||||||
|
*/
|
||||||
|
const port = 4001;
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
const returnMessage = `HTTP Server is listening on port ${serverPort}`;
|
const returnMessage = `HTTP Server is listening on port ${port}`;
|
||||||
server.listen(serverPort, '0.0.0.0', () => console.log(returnMessage));
|
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
|
||||||
|
|
||||||
// init timer
|
// init timer
|
||||||
global.timer = new EventTimer(server, oscClient, config);
|
global.timer = new EventTimer(server, oscClient, config);
|
||||||
|
|||||||
@@ -158,19 +158,52 @@ const getNetworkInterfaces = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/info'
|
// Create controller for POST request to '/ontime/info'
|
||||||
// Returns -
|
// Returns -
|
||||||
// TODO: Add version
|
|
||||||
export const getInfo = async (req, res) => {
|
export const getInfo = async (req, res) => {
|
||||||
|
const version = data.settings.version;
|
||||||
|
const serverPort = data.settings.serverPort;
|
||||||
|
const oscInPort = data.settings.oscInPort;
|
||||||
|
const oscOutPort = data.settings.oscOutPort;
|
||||||
|
const oscOutIP = data.settings.oscOutIP;
|
||||||
|
|
||||||
|
// get nif and inject localhost
|
||||||
const ni = getNetworkInterfaces();
|
const ni = getNetworkInterfaces();
|
||||||
|
ni.unshift({ name: 'localhost', address: '127.0.0.1' });
|
||||||
|
|
||||||
|
// send object with network information
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
networkInterfaces: ni,
|
networkInterfaces: ni,
|
||||||
|
version,
|
||||||
|
serverPort,
|
||||||
|
oscInPort,
|
||||||
|
oscOutPort,
|
||||||
|
oscOutIP,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create controller for POST request to '/ontime/info'
|
||||||
|
// Returns ACK message
|
||||||
|
export const postInfo = async (req, res) => {
|
||||||
|
if (!req.body) {
|
||||||
|
res.status(400).send('No object found in request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: validate data
|
||||||
|
try {
|
||||||
|
data.settings = { ...data.settings, ...req.body };
|
||||||
|
await db.write();
|
||||||
|
res.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send(error);
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/db'
|
// Create controller for POST request to '/ontime/db'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const dbUpload = async (req, res) => {
|
export const dbUpload = async (req, res) => {
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ export const dbModel = {
|
|||||||
url: '',
|
url: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
endMessage: '',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
version: 1,
|
version: 1,
|
||||||
osc_enabled: false,
|
serverPort: 4001,
|
||||||
osc_port: 8888,
|
oscInPort: 8888,
|
||||||
|
oscOutPort: 9999,
|
||||||
|
oscOutIP: '127.0.0.1',
|
||||||
|
oscEnabled: false,
|
||||||
lock: false,
|
lock: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
dbDownload,
|
dbDownload,
|
||||||
dbUpload,
|
dbUpload,
|
||||||
getInfo,
|
getInfo,
|
||||||
|
postInfo,
|
||||||
dbPathToUpload,
|
dbPathToUpload,
|
||||||
} from '../controllers/ontimeController.js';
|
} from '../controllers/ontimeController.js';
|
||||||
|
|
||||||
@@ -18,5 +19,8 @@ router.post('/db', uploadJson, dbUpload);
|
|||||||
// create route between controller and '/ontime/info' endpoint
|
// create route between controller and '/ontime/info' endpoint
|
||||||
router.get('/info', uploadJson, getInfo);
|
router.get('/info', uploadJson, getInfo);
|
||||||
|
|
||||||
|
// create route between controller and '/ontime/info' endpoint
|
||||||
|
router.post('/info', postInfo);
|
||||||
|
|
||||||
// create route between controller and '/ontime/dbpath' endpoint
|
// create route between controller and '/ontime/dbpath' endpoint
|
||||||
router.post('/dbpath', dbPathToUpload);
|
router.post('/dbpath', dbPathToUpload);
|
||||||
|
|||||||
Reference in New Issue
Block a user