Merge branch 'chore/cleanup' of https://github.com/cpvalente/ontime into chore/cleanup

This commit is contained in:
cv
2021-10-28 12:09:19 +02:00
19 changed files with 1068 additions and 497 deletions
+3 -3
View File
@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@chakra-ui/react": "^1.4.1",
"@emotion/react": "^11",
"@emotion/styled": "^11",
"@chakra-ui/react": "^1.6.10",
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
+10
View File
@@ -3,6 +3,11 @@ import { ontimeURL } from './apiConstants';
export const ontimePlaceholderInfo = {
networkInterfaces: [],
version: '',
serverPort: 4001,
oscInPort: '',
oscOutPort: '',
oscOutIP: '',
};
export const getInfo = async () => {
@@ -10,6 +15,11 @@ export const getInfo = async () => {
return res.data;
};
export const postInfo = async (data) => {
const res = await axios.post(ontimeURL + '/info', data);
return res;
};
export const downloadEvents = async () => {
await axios({
url: ontimeURL + '/db',
+1 -2
View File
@@ -1,10 +1,9 @@
import { useEffect } from 'react';
import { useState, useEffect } from 'react';
import { useSocket } from 'app/context/socketContext';
import style from './Info.module.css';
import InfoTitle from './InfoTitle';
import InfoLogger from './InfoLogger';
import InfoNif from './InfoNif';
import { useState } from 'react';
export default function Info() {
const socket = useSocket();
+1 -2
View File
@@ -11,8 +11,7 @@ export default function InfoNif() {
placeholderData: ontimePlaceholderInfo,
});
const [collapsed, setCollapsed] = useState(false);
const baseURL = `http://__IP__:${4001}`;
const baseURL = 'http://__IP__:4001';
const handleLink = (url) => {
if (window.process?.type === 'renderer') {
+152
View File
@@ -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>
</>
);
}
@@ -10,15 +10,18 @@ import { fetchEvent, postEvent } from 'app/api/eventApi';
import { useEffect, 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 SettingsModal() {
const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent);
const { data, status } = useFetch(EVENT_TABLE, fetchEvent);
const [formData, setFormData] = useState({
title: '',
url: '',
publicInfo: '',
backstageInfo: '',
endMessage: '',
});
const [changed, setChanged] = useState(false);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
@@ -29,6 +32,7 @@ export default function SettingsModal() {
url: data.url,
publicInfo: data.publicInfo,
backstageInfo: data.backstageInfo,
endMessage: data.endMessage,
});
}, [data]);
@@ -36,41 +40,47 @@ export default function SettingsModal() {
event.preventDefault();
setSubmitting(true);
await postEvent(formData).then(setSubmitting(false));
await postEvent(formData);
setChanged(false);
setSubmitting(false);
};
return (
<>
<form onSubmit={submitHandler}>
<ModalBody>
<ModalBody className={style.modalBody}>
{status === 'success' && (
<>
<p className={style.notes}>
Options related to the running event
<br />
Affect rendered views
</p>
<FormControl id='title'>
<FormLabel
style={{ fontWeight: 400, paddingTop: '1em' }}
htmlFor='title'
>
Event Title
</FormLabel>
<FormLabel htmlFor='title'>Event Title</FormLabel>
<Input
size='sm'
maxLength={35}
name='title'
placeholder='Event Title'
autoComplete='off'
value={formData.title}
onChange={(event) =>
setFormData({ ...formData, title: event.target.value })
}
onChange={(event) => {
setChanged(true);
setFormData({ ...formData, title: event.target.value });
}}
isDisabled={submitting}
/>
</FormControl>
<FormControl id='url'>
<FormLabel
style={{ fontWeight: 400, paddingTop: '1em' }}
htmlFor='url'
>
<FormLabel htmlFor='url'>
Event URL
<span className={style.notes}>
(shown as a QR code in some views)
</span>
</FormLabel>
<Input
size='sm'
@@ -78,55 +88,73 @@ export default function SettingsModal() {
placeholder='www.onsite.no'
autoComplete='off'
value={formData.url}
onChange={(event) =>
setFormData({ ...formData, url: event.target.value })
}
onChange={(event) => {
setChanged(true);
setFormData({ ...formData, url: event.target.value });
}}
isDisabled={submitting}
/>
</FormControl>
<FormControl id='pubInfo'>
<FormLabel
style={{ fontWeight: 400, paddingTop: '1em' }}
htmlFor='pubInfo'
>
Public Info
</FormLabel>
<FormLabel htmlFor='pubInfo'>Public Info</FormLabel>
<Textarea
size='sm'
name='pubInfo'
placeholder='Information to be shown on public screens'
autoComplete='off'
value={formData.publicInfo}
onChange={(event) =>
onChange={(event) => {
setChanged(true);
setFormData({
...formData,
publicInfo: event.target.value,
})
}
});
}}
isDisabled={submitting}
/>
</FormControl>
<FormControl id='backstageInfo'>
<FormLabel
style={{ fontWeight: 400, paddingTop: '1em' }}
htmlFor='backstageInfo'
>
Backstage Info
</FormLabel>
<FormLabel htmlFor='backstageInfo'>Backstage Info</FormLabel>
<Textarea
size='sm'
name='backstageInfo'
placeholder='Information to be shown on backstage screens'
autoComplete='off'
value={formData.backstageInfo}
onChange={(event) =>
onChange={(event) => {
setChanged(true);
setFormData({
...formData,
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}
/>
</FormControl>
@@ -134,9 +162,9 @@ export default function SettingsModal() {
)}
<Button
colorScheme='blue'
mr={3}
isLoading={submitting}
type='submit'
isLoading={submitting}
disabled={!changed}
>
Save
</Button>
+7 -11
View File
@@ -7,7 +7,9 @@ import {
ModalOverlay,
} from '@chakra-ui/modal';
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) {
const { isOpen, onClose } = props;
@@ -31,19 +33,13 @@ export default function ModalManager(props) {
</TabList>
<TabPanels>
<TabPanel>
<SettingsModal onClose={onClose} />
<EventSettingsModal />
</TabPanel>
<TabPanel>
<p>Application settings will be here!</p>
<p>Set port</p>
<p>Set end message</p>
<AppSettingsModal />
</TabPanel>
<TabPanel>
<p>You will be able to define URL aliases here!</p>
<p>List existing aliases</p>
<p>List existing aliases</p>
<p>List existing aliases</p>
<p>Add new alias input + button</p>
<AliasesModal />
</TabPanel>
</TabPanels>
</Tabs>
@@ -54,5 +50,5 @@ export default function ModalManager(props) {
ModalManager.propTypes = {
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: '',
publicInfo: '',
backstageInfo: '',
endMessage: '',
});
const [playback, setPlayback] = useState(null);
@@ -7,7 +7,7 @@ import TitleCard from 'common/components/views/TitleCard';
import style from './PresenterView.module.css';
export default function PresenterView(props) {
const { pres, title, time } = props;
const { general, pres, title, time } = props;
// Set window title
useEffect(() => {
@@ -18,6 +18,14 @@ export default function PresenterView(props) {
const isPlaying = time.playstate !== 'pause';
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
const titleVariants = {
hidden: {
@@ -57,7 +65,7 @@ export default function PresenterView(props) {
<div className={style.timerContainer}>
{time.finished ? (
<div className={style.finished}>TIME UP</div>
<div className={style.finished}>{endMessage}</div>
) : (
<div className={isPlaying ? style.countdown : style.countdownPaused}>
<Countdown time={normalisedTime} hideZeroHours />
@@ -66,7 +66,8 @@
}
.finished {
font-size: 18vw;
text-align: center;
font-size: 12vw;
line-height: 18vw;
font-weight: 600;
color: #ff6969;
+459 -421
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -112,12 +112,16 @@
"url": "www.carlosvalente.com",
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
"endMessage": ""
},
"settings": {
"app": "ontime",
"version": 1,
"osc_enabled": false,
"osc_port": 8888,
"serverPort": 4001,
"oscInPort": 8888,
"oscOutPort": 9999,
"oscOutIP": "127.0.0.1",
"oscEnabled": true,
"lock": false
}
}
+7 -5
View File
@@ -26,12 +26,14 @@ const nodePath =
const { startServer, startOSCServer, startOSCClient } = await import(
nodePath
);
// Start express server
loaded = startServer();
// Start OSC Server (API)
startOSCServer();
// Start OSC Client (Feedback)
startOSCClient();
await startOSCClient();
// Start express server
loaded = await startServer();
// Start OSC Server (API)
await startOSCServer();
} catch (error) {
console.log(error);
loaded = error;
+53 -6
View File
@@ -4,6 +4,7 @@ import { sessionId, user } from './utils/analytics.js';
user.screenview('Node service', 'ontime').send();
user.event('NODE', 'started', 'starting node service').send();
// import config
import { config } from './config/config.js';
// init database
@@ -95,20 +96,66 @@ app.use((err, req, res, next) => {
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
// TODO: Move this to function
const oscClient = new Client('127.0.0.1', 9999);
let oscClient = null;
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
const server = http.createServer(app);
export const startServer = (overrideConfig = null) => {
export const startServer = async (overrideConfig = null) => {
// 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
const returnMessage = `HTTP Server is listening on port ${serverPort}`;
server.listen(serverPort, '0.0.0.0', () => console.log(returnMessage));
const returnMessage = `HTTP Server is listening on port ${port}`;
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
// init timer
global.timer = new EventTimer(server, oscClient, config);
+34 -1
View File
@@ -158,19 +158,52 @@ const getNetworkInterfaces = () => {
}
}
}
return results;
};
// Create controller for POST request to '/ontime/info'
// Returns -
// TODO: Add version
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();
ni.unshift({ name: 'localhost', address: '127.0.0.1' });
// send object with network information
res.status(200).send({
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'
// Returns -
export const dbUpload = async (req, res) => {
+6 -2
View File
@@ -5,12 +5,16 @@ export const dbModel = {
url: '',
publicInfo: '',
backstageInfo: '',
endMessage: '',
},
settings: {
app: 'ontime',
version: 1,
osc_enabled: false,
osc_port: 8888,
serverPort: 4001,
oscInPort: 8888,
oscOutPort: 9999,
oscOutIP: '127.0.0.1',
oscEnabled: false,
lock: false,
},
};
+4
View File
@@ -6,6 +6,7 @@ import {
dbDownload,
dbUpload,
getInfo,
postInfo,
dbPathToUpload,
} from '../controllers/ontimeController.js';
@@ -18,5 +19,8 @@ router.post('/db', uploadJson, dbUpload);
// create route between controller and '/ontime/info' endpoint
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
router.post('/dbpath', dbPathToUpload);