mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-11 17:19:34 +00:00
Feat/62 logger (#79)
* feat/62-logger request log data in app * feat/62-logger refact osc integration * feat/62-logger feedback on osc * feat/62-logger refact broadcast on triggers * feat/62-logger log triggers * feat/62-logger add link to studio * feat/62-logger replace toasts with logger context * feat/62-logger style improvements * feat/62-logger refactor code duplications * feat/62-logger cleanup and version bump
This commit is contained in:
+3
-1
@@ -5,6 +5,8 @@
|
|||||||
],
|
],
|
||||||
"plugins": ["react", "testing-library", "jest"],
|
"plugins": ["react", "testing-library", "jest"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"jest/no-mocks-import": "warn"
|
"jest/no-mocks-import": "warn",
|
||||||
|
"no-useless-concat": "warn",
|
||||||
|
"prefer-template": "warn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$ontime-accent: #4bffabcc;
|
||||||
|
$ontime-pink: #ff7597;
|
||||||
|
$ontime-roll: #2b6cb0;
|
||||||
|
|
||||||
|
$notes-color: #d69e2e;
|
||||||
|
|
||||||
|
$header-gray: #ccc;
|
||||||
|
$label-gray: #aaa;
|
||||||
|
|
||||||
|
@mixin container-bg {
|
||||||
|
background-color: rgba(0, 0, 0, 0.13);
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
@@ -75,28 +75,26 @@ export const ontimeVars = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const getInfo = async () => {
|
export const getInfo = async () => {
|
||||||
const res = await axios.get(ontimeURL + '/info');
|
const res = await axios.get(`${ontimeURL}/info`);
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postInfo = async (data) => {
|
export const postInfo = async (data) => {
|
||||||
const res = await axios.post(ontimeURL + '/info', data);
|
return await axios.post(`${ontimeURL}/info`, data);
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getOSC = async () => {
|
export const getOSC = async () => {
|
||||||
const res = await axios.get(ontimeURL + '/osc');
|
const res = await axios.get(`${ontimeURL}/osc`);
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postOSC = async (data) => {
|
export const postOSC = async (data) => {
|
||||||
const res = await axios.post(ontimeURL + '/osc', data);
|
return await axios.post(`${ontimeURL}/osc`, data);
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadEvents = async () => {
|
export const downloadEvents = async () => {
|
||||||
await axios({
|
await axios({
|
||||||
url: ontimeURL + '/db',
|
url: `${ontimeURL}/db`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
responseType: 'blob', // important
|
responseType: 'blob', // important
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
@@ -123,15 +121,13 @@ export const uploadEvents = async (file) => {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('userFile', file); // appending file
|
formData.append('userFile', file); // appending file
|
||||||
await axios
|
await axios
|
||||||
.post(ontimeURL + '/db', formData, {
|
.post(`${ontimeURL}/db`, formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
.then((res) => console.log(res.data))
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadEventsWithPath = async (filepath) => {
|
export const uploadEventsWithPath = async (filepath) => {
|
||||||
await axios.post(ontimeURL + '/dbpath', { path: filepath });
|
await axios.post(`${ontimeURL}/dbpath`, { path: filepath });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { useSocket } from './socketContext';
|
||||||
|
import { createContext, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { generateId } from 'ontime-server/utils/generate_id';
|
||||||
|
import { nowInMillis, stringFromMillis } from 'ontime-server/utils/time';
|
||||||
|
|
||||||
|
export const LoggingContext = createContext({
|
||||||
|
logData: [],
|
||||||
|
emitInfo: () => undefined,
|
||||||
|
emitWarning: () => undefined,
|
||||||
|
emitError: () => undefined,
|
||||||
|
clearLog: () => undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LoggingProvider = (props) => {
|
||||||
|
const MAX_MESSAGES = 100;
|
||||||
|
const socket = useSocket();
|
||||||
|
const [logData, setLogData] = useState([]);
|
||||||
|
const origin = 'USER';
|
||||||
|
|
||||||
|
// handle incoming messages
|
||||||
|
useEffect(() => {
|
||||||
|
if (socket == null) return;
|
||||||
|
|
||||||
|
// Ask for log data
|
||||||
|
socket.emit('get-logger');
|
||||||
|
|
||||||
|
socket.on('logger', (data) => {
|
||||||
|
setLogData((l) => [data, ...l]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear listener
|
||||||
|
return () => {
|
||||||
|
socket.off('logger');
|
||||||
|
};
|
||||||
|
}, [socket]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function sends message over socket
|
||||||
|
* @param text
|
||||||
|
* @param level
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const _send = useCallback((text, level) => {
|
||||||
|
if (socket != null) {
|
||||||
|
const m = {
|
||||||
|
id: generateId(),
|
||||||
|
origin,
|
||||||
|
time: stringFromMillis(nowInMillis()),
|
||||||
|
level,
|
||||||
|
text
|
||||||
|
}
|
||||||
|
setLogData((l) => [m, ...l]);
|
||||||
|
socket.emit('logger', m);
|
||||||
|
}
|
||||||
|
if (logData.length > MAX_MESSAGES) {
|
||||||
|
setLogData((l) => l.pop());
|
||||||
|
}
|
||||||
|
},[logData, socket]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with level INFO
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
const emitInfo = useCallback((text) => {
|
||||||
|
_send(text, 'INFO');
|
||||||
|
}, [_send]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with level WARN
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
const emitWarning = useCallback((text) => {
|
||||||
|
_send(text, 'WARN');
|
||||||
|
}, [_send]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with level ERROR
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
const emitError = useCallback((text) => {
|
||||||
|
_send(text, 'ERROR');
|
||||||
|
}, [_send]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears running log
|
||||||
|
*/
|
||||||
|
const clearLog = useCallback(() => {
|
||||||
|
setLogData([])
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoggingContext.Provider value = {{ emitInfo, logData, emitWarning, emitError, clearLog }}>
|
||||||
|
{props.children}
|
||||||
|
</LoggingContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import style from "../../../features/info/Info.module.scss";
|
||||||
|
import {Icon} from "@chakra-ui/react";
|
||||||
|
import {FiChevronUp} from "react-icons/fi";
|
||||||
|
|
||||||
|
export default function CollapseBar(props) {
|
||||||
|
const {title = 'Collapse bar', isCollapsed = false, onClick}= props;
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className={style.header}>
|
||||||
|
{title}
|
||||||
|
<Icon
|
||||||
|
className={isCollapsed ? style.moreCollapsed : style.moreExpanded}
|
||||||
|
as={FiChevronUp}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CollapseBar.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
isCollapsed: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.header,
|
||||||
|
.header__roll {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #ccc;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__roll {
|
||||||
|
color: #2b6cb0;
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
|
static contextType = LoggingContext;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { error: null, errorInfo: null };
|
this.state = { error: null, errorInfo: null };
|
||||||
@@ -17,15 +20,11 @@ class ErrorBoundary extends React.Component {
|
|||||||
errorInfo: info,
|
errorInfo: info,
|
||||||
});
|
});
|
||||||
// TODO: Log the error to an error reporting service
|
// TODO: Log the error to an error reporting service
|
||||||
this.logErrorToServices(error.toString(), info.componentStack);
|
this.context.emitError(error.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// A fake logging service.
|
|
||||||
logErrorToServices = console.log;
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.errorMessage) {
|
if (this.state.errorMessage) {
|
||||||
// You can render any custom fallback UI
|
|
||||||
return <p>:/</p>;
|
return <p>:/</p>;
|
||||||
}
|
}
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import EditableTimer from 'common/input/EditableTimer';
|
import EditableTimer from 'common/input/EditableTimer';
|
||||||
import { showWarningToast } from 'common/helpers/toastManager';
|
import { useContext } from 'react';
|
||||||
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function EventTimes(props) {
|
export default function EventTimes(props) {
|
||||||
const { actionHandler, delay, timeStart, timeEnd } = props;
|
const { actionHandler, delay, timeStart, timeEnd } = props;
|
||||||
|
const { emitWarning } = useContext(LoggingContext);
|
||||||
|
|
||||||
const handleValidate = (entry, v) => {
|
const handleValidate = (entry, v) => {
|
||||||
// we dont inforce validation here
|
// we dont enforce validation here
|
||||||
|
|
||||||
if (v == null || timeStart == null || timeEnd == null) return true;
|
if (v == null || timeStart == null || timeEnd == null) return true;
|
||||||
if (timeStart === 0) return true;
|
if (timeStart === 0) return true;
|
||||||
|
|
||||||
let validate = { value: true, catch: '' };
|
let validate = { value: true, catch: '' };
|
||||||
if (entry === 'timeStart' && v > timeEnd)
|
if (entry === 'timeStart' && v > timeEnd) {
|
||||||
validate.catch = 'Start time later than end time';
|
validate.catch = 'Start time later than end time';
|
||||||
else if (entry === 'timeEnd' && v < timeStart)
|
} else if (entry === 'timeEnd' && v < timeStart) {
|
||||||
validate.catch = 'End time earlier than start time';
|
validate.catch = 'End time earlier than start time';
|
||||||
|
}
|
||||||
|
|
||||||
if (validate.catch !== '')
|
if (validate.catch !== '')
|
||||||
showWarningToast('Time Input Warning', validate.catch);
|
emitWarning(`Time Input Warning: ${validate.catch}`);
|
||||||
return validate.value;
|
return validate.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import EditableTimer from 'common/input/EditableTimer';
|
import EditableTimer from 'common/input/EditableTimer';
|
||||||
import { showWarningToast } from 'common/helpers/toastManager';
|
import { stringFromMillis } from 'ontime-server/utils/time';
|
||||||
import { stringFromMillis } from 'common/utils/dateConfig';
|
import { useContext } from 'react';
|
||||||
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
|
||||||
const label = {
|
const label = {
|
||||||
fontSize: '0.75em',
|
fontSize: '0.75em',
|
||||||
@@ -83,6 +84,8 @@ const Times = (props) => {
|
|||||||
|
|
||||||
export default function EventTimesVertical(props) {
|
export default function EventTimesVertical(props) {
|
||||||
const { delay, timeStart, timeEnd, duration } = props;
|
const { delay, timeStart, timeEnd, duration } = props;
|
||||||
|
const { emitWarning } = useContext(LoggingContext);
|
||||||
|
|
||||||
const handleValidate = (entry, v) => {
|
const handleValidate = (entry, v) => {
|
||||||
// we dont enforce validation here
|
// we dont enforce validation here
|
||||||
|
|
||||||
@@ -90,17 +93,20 @@ export default function EventTimesVertical(props) {
|
|||||||
if (timeStart === 0) return true;
|
if (timeStart === 0) return true;
|
||||||
|
|
||||||
let validate = { value: true, catch: '' };
|
let validate = { value: true, catch: '' };
|
||||||
if (entry === 'timeStart' && v > timeEnd)
|
if (entry === 'timeStart' && v > timeEnd) {
|
||||||
validate.catch = 'Start time later than end time';
|
validate.catch = 'Start time later than end time';
|
||||||
else if (entry === 'timeEnd' && v < timeStart)
|
} else if (entry === 'timeEnd' && v < timeStart) {
|
||||||
validate.catch = 'End time earlier than start time';
|
validate.catch = 'End time earlier than start time';
|
||||||
|
}
|
||||||
|
|
||||||
if (validate.catch !== '')
|
if (validate.catch !== '') {
|
||||||
showWarningToast('Time Input Warning', validate.catch);
|
emitWarning(`Time Input Warning: ${validate.catch}`);
|
||||||
|
}
|
||||||
return validate.value;
|
return validate.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (delay != null) & (delay > 0) ? (
|
return (
|
||||||
|
(delay != null) && (delay > 0) ? (
|
||||||
<TimesDelayed
|
<TimesDelayed
|
||||||
handleValidate={handleValidate}
|
handleValidate={handleValidate}
|
||||||
actionHandler={props.actionHandler}
|
actionHandler={props.actionHandler}
|
||||||
@@ -117,5 +123,6 @@ export default function EventTimesVertical(props) {
|
|||||||
timeEnd={timeEnd}
|
timeEnd={timeEnd}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { stringFromMillis } from 'common/utils/dateConfig';
|
import { stringFromMillis } from 'ontime-server/utils/time';
|
||||||
import style from './Paginator.module.css';
|
import style from './Paginator.module.css';
|
||||||
export default function TodayItem(props) {
|
export default function TodayItem(props) {
|
||||||
const { selected, timeStart, timeEnd, title, backstageEvent } = props;
|
const { selected, timeStart, timeEnd, title, backstageEvent } = props;
|
||||||
@@ -19,7 +19,7 @@ export default function TodayItem(props) {
|
|||||||
}`}
|
}`}
|
||||||
>{`${start} · ${end}`}</div>
|
>{`${start} · ${end}`}</div>
|
||||||
<div className={style.entryTitle}>{title}</div>
|
<div className={style.entryTitle}>{title}</div>
|
||||||
{backstageEvent && <div className={style.backstageInd}></div>}
|
{backstageEvent && <div className={style.backstageInd}/>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { createStandaloneToast } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
const toast = createStandaloneToast();
|
|
||||||
// const customToast = createStandaloneToast({ theme: yourCustomTheme })
|
|
||||||
|
|
||||||
// error toast
|
|
||||||
export const showErrorToast = (title, description) => {
|
|
||||||
toast({
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
position: 'top-left',
|
|
||||||
variant: 'subtle',
|
|
||||||
status: 'error',
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// warning toast
|
|
||||||
export const showWarningToast = (title, description) => {
|
|
||||||
toast({
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
position: 'top-left',
|
|
||||||
variant: 'subtle',
|
|
||||||
status: 'warning',
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
isTimeString,
|
isTimeString,
|
||||||
stringFromMillis,
|
|
||||||
timeStringToMillis,
|
timeStringToMillis,
|
||||||
} from '../utils/dateConfig';
|
} from '../utils/dateConfig';
|
||||||
import { showErrorToast } from '../helpers/toastManager';
|
import { stringFromMillis } from 'ontime-server/utils/time';
|
||||||
import style from './EditableTimer.module.css';
|
import style from './EditableTimer.module.css';
|
||||||
|
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function EditableTimer(props) {
|
export default function EditableTimer(props) {
|
||||||
const { name, actionHandler, time, delay, validate } = props;
|
const { name, actionHandler, time, delay, validate } = props;
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
// prepare time fields
|
// prepare time fields
|
||||||
@@ -18,9 +19,9 @@ export default function EditableTimer(props) {
|
|||||||
try {
|
try {
|
||||||
setValue(stringFromMillis(time + delay));
|
setValue(stringFromMillis(time + delay));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error parsing date', error.text);
|
emitError(`Unable to parse date: ${error.text}`);
|
||||||
}
|
}
|
||||||
}, [time, delay]);
|
}, [time, delay, emitError]);
|
||||||
|
|
||||||
const validateValue = (value) => {
|
const validateValue = (value) => {
|
||||||
const success = handleSubmit(value);
|
const success = handleSubmit(value);
|
||||||
|
|||||||
@@ -4,37 +4,7 @@ export const timeFormatSeconds = 'HH:mm:ss';
|
|||||||
const mts = 1000; // millis to seconds
|
const mts = 1000; // millis to seconds
|
||||||
const mtm = 1000 * 60; // millis to minutes
|
const mtm = 1000 * 60; // millis to minutes
|
||||||
const mth = 1000 * 60 * 60; // millis to hours
|
const mth = 1000 * 60 * 60; // millis to hours
|
||||||
const mtd = 1000 * 60 * 60 * 24; // millis to days
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Converts milliseconds to string representing time
|
|
||||||
* @param {number} ms - time in milliseconds
|
|
||||||
* @param {boolean} showSeconds - wether to show the seconds
|
|
||||||
* @param {string} delim - character between HH MM SS
|
|
||||||
* @param {string} ifNull - what to return if value is null
|
|
||||||
* @returns {string} String representing time 00:12:02
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This is shared and tested in backend in time.js
|
|
||||||
export const stringFromMillis = (
|
|
||||||
ms,
|
|
||||||
showSeconds = true,
|
|
||||||
delim = ':',
|
|
||||||
ifNull = '...'
|
|
||||||
) => {
|
|
||||||
if (ms === null || isNaN(ms)) return ifNull;
|
|
||||||
const isNegative = ms < 0 ? '-' : '';
|
|
||||||
const showWith0 = (value) => (value < 10 ? `0${value}` : value);
|
|
||||||
const hours = showWith0(Math.floor(((ms / mth) % 60) % 24));
|
|
||||||
const minutes = showWith0(Math.floor((ms / mtm) % 60));
|
|
||||||
const seconds = showWith0(Math.floor((ms / mts) % 60));
|
|
||||||
|
|
||||||
return showSeconds
|
|
||||||
? `${isNegative}${
|
|
||||||
parseInt(hours) ? `${hours}${delim}` : `00${delim}`
|
|
||||||
}${minutes}${delim}${seconds}`
|
|
||||||
: `${isNegative}${parseInt(hours) ? `${hours}` : '00'}${delim}${minutes}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* another go at simpler string formatting (counters)
|
* another go at simpler string formatting (counters)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { stringFromMillis } from 'ontime-server/utils/time';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description From a list of events, returns only events of type event with calculated delays
|
* @description From a list of events, returns only events of type event with calculated delays
|
||||||
* @param {Object[]} events - given events
|
* @param {Object[]} events - given events
|
||||||
* @returns {Object[]} Filtered events with calculated delays
|
* @returns {Object[]} Filtered events with calculated delays
|
||||||
*/
|
*/
|
||||||
import {stringFromMillis} from "./dateConfig";
|
|
||||||
|
|
||||||
export const getEventsWithDelay = (events) => {
|
export const getEventsWithDelay = (events) => {
|
||||||
|
|
||||||
if (events == null) return [];
|
if (events == null) return [];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import style from './PlaybackControl.module.scss';
|
import style from './PlaybackControl.module.scss';
|
||||||
import Countdown from 'common/components/countdown/Countdown';
|
import Countdown from 'common/components/countdown/Countdown';
|
||||||
import {stringFromMillis} from 'common/utils/dateConfig';
|
import { stringFromMillis } from 'ontime-server/utils/time';
|
||||||
import {Tooltip} from '@chakra-ui/react';
|
import {Tooltip} from '@chakra-ui/react';
|
||||||
import {Button} from '@chakra-ui/button';
|
import {Button} from '@chakra-ui/button';
|
||||||
import {memo} from 'react';
|
import {memo} from 'react';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { lazy, useEffect } from 'react';
|
import { lazy, useEffect } from 'react';
|
||||||
import { Box } from '@chakra-ui/layout';
|
import { Box } from '@chakra-ui/layout';
|
||||||
import { useDisclosure } from '@chakra-ui/hooks';
|
import { useDisclosure } from '@chakra-ui/hooks';
|
||||||
import styles from './Editor.module.css';
|
import styles from './Editor.module.scss';
|
||||||
import MenuBar from 'features/menu/MenuBar';
|
import MenuBar from 'features/menu/MenuBar';
|
||||||
import ModalManager from 'features/modals/ModalManager';
|
import ModalManager from 'features/modals/ModalManager';
|
||||||
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
||||||
|
import { LoggingProvider } from '../../app/context/LoggingContext';
|
||||||
|
|
||||||
const EventListWrapper = lazy(() =>
|
const EventListWrapper = lazy(() =>
|
||||||
import('features/editors/list/EventListWrapper')
|
import('features/editors/list/EventListWrapper')
|
||||||
@@ -22,7 +23,7 @@ export default function Editor() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<LoggingProvider>
|
||||||
<ModalManager isOpen={isOpen} onClose={onClose} />
|
<ModalManager isOpen={isOpen} onClose={onClose} />
|
||||||
|
|
||||||
<div className={styles.mainContainer}>
|
<div className={styles.mainContainer}>
|
||||||
@@ -68,6 +69,6 @@ export default function Editor() {
|
|||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</LoggingProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
grid-template-columns: 40px 48em auto auto;
|
grid-template-columns: 40px 48em 31em auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'sett even play info'
|
'sett even play info'
|
||||||
'sett even mess info';
|
'sett even mess info';
|
||||||
@@ -110,12 +110,23 @@ h1 {
|
|||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
grid-area: even;
|
grid-area: even;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: calc(100% - 3em);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
grid-area: info;
|
grid-area: info;
|
||||||
min-width: 17em;
|
min-width: 17em;
|
||||||
max-width: 32em;
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100% - 3em);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import style from './List.module.css';
|
import style from './List.module.scss';
|
||||||
import { createRef, useCallback, useEffect, useMemo, useState } from 'react';
|
import { createRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSocket } from 'app/context/socketContext';
|
import { useSocket } from 'app/context/socketContext';
|
||||||
import Empty from 'common/state/Empty';
|
import Empty from 'common/state/Empty';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import DelayBlock from './DelayBlock';
|
import DelayBlock from './DelayBlock';
|
||||||
import BlockBlock from './BlockBlock';
|
import BlockBlock from './BlockBlock';
|
||||||
import EventBlock from './EventBlock';
|
import EventBlock from './EventBlock';
|
||||||
import { showErrorToast } from 'common/helpers/toastManager';
|
import { memo, useContext } from 'react';
|
||||||
import { memo } from 'react';
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
|
||||||
const areEqual = (prevProps, nextProps) => {
|
const areEqual = (prevProps, nextProps) => {
|
||||||
return (
|
return (
|
||||||
@@ -26,6 +26,7 @@ const EventListItem = (props) => {
|
|||||||
delay,
|
delay,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
|
|
||||||
// Create / delete new events
|
// Create / delete new events
|
||||||
const actionHandler = (action, payload) => {
|
const actionHandler = (action, payload) => {
|
||||||
@@ -59,7 +60,7 @@ const EventListItem = (props) => {
|
|||||||
// request update in parent
|
// request update in parent
|
||||||
eventsHandler('patch', newData);
|
eventsHandler('patch', newData);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast('Field Error: ' + field);
|
emitError(`Unknown field: ${field}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMutation, useQueryClient } from 'react-query';
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
fetchAllEvents,
|
fetchAllEvents,
|
||||||
requestPatch,
|
requestPatch,
|
||||||
@@ -12,16 +12,17 @@ import {
|
|||||||
} from 'app/api/eventsApi.js';
|
} from 'app/api/eventsApi.js';
|
||||||
import EventList from './EventList';
|
import EventList from './EventList';
|
||||||
import EventListMenu from 'features/menu/EventListMenu.jsx';
|
import EventListMenu from 'features/menu/EventListMenu.jsx';
|
||||||
import { showErrorToast } from 'common/helpers/toastManager';
|
|
||||||
import { useFetch } from 'app/hooks/useFetch.js';
|
import { useFetch } from 'app/hooks/useFetch.js';
|
||||||
import Empty from 'common/state/Empty';
|
import Empty from 'common/state/Empty';
|
||||||
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
||||||
import { BatchOperation } from 'app/context/collapseAtom';
|
import { BatchOperation } from 'app/context/collapseAtom';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function EventListWrapper() {
|
export default function EventListWrapper() {
|
||||||
const [, setCollapsed] = useAtom(BatchOperation);
|
const [, setCollapsed] = useAtom(BatchOperation);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const { data, status, isError, refetch } = useFetch(
|
const { data, status, isError, refetch } = useFetch(
|
||||||
EVENTS_TABLE,
|
EVENTS_TABLE,
|
||||||
fetchAllEvents
|
fetchAllEvents
|
||||||
@@ -230,9 +231,9 @@ export default function EventListWrapper() {
|
|||||||
// Show toasts on errors
|
// Show toasts on errors
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isError) {
|
if (isError) {
|
||||||
showErrorToast('Error fetching data');
|
emitError('Error fetching data');
|
||||||
}
|
}
|
||||||
}, [isError]);
|
}, [emitError, isError]);
|
||||||
|
|
||||||
// Events API
|
// Events API
|
||||||
const eventsHandler = useCallback(
|
const eventsHandler = useCallback(
|
||||||
@@ -242,35 +243,35 @@ export default function EventListWrapper() {
|
|||||||
try {
|
try {
|
||||||
await addEvent.mutateAsync(payload);
|
await addEvent.mutateAsync(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error creating event', error.message);
|
emitError(`Error fetching data: ${error.message}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
try {
|
try {
|
||||||
await updateEvent.mutateAsync(payload);
|
await updateEvent.mutateAsync(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error updating event', error.message);
|
emitError(`Error updating event: ${error.message}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'patch':
|
case 'patch':
|
||||||
try {
|
try {
|
||||||
await patchEvent.mutateAsync(payload);
|
await patchEvent.mutateAsync(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error updating event', error.message);
|
emitError(`Error updating event: ${error.message}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
try {
|
try {
|
||||||
await deleteEvent.mutateAsync(payload);
|
await deleteEvent.mutateAsync(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error deleting event', error.message);
|
emitError(`Error deleting event: ${error.message}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'reorder':
|
case 'reorder':
|
||||||
try {
|
try {
|
||||||
await reorderEvent.mutateAsync(payload);
|
await reorderEvent.mutateAsync(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error reordering event', error.message);
|
emitError(`Error re-ordering event: ${error.message}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'applyDelay':
|
case 'applyDelay':
|
||||||
@@ -293,13 +294,13 @@ export default function EventListWrapper() {
|
|||||||
// delete block after, if any
|
// delete block after, if any
|
||||||
if (blockAfter) await deleteEvent.mutateAsync(blockAfter);
|
if (blockAfter) await deleteEvent.mutateAsync(blockAfter);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error applying delay', error.message);
|
emitError(`Error applying delay: ${error.message}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await applyDelay.mutateAsync(payload.id);
|
await applyDelay.mutateAsync(payload.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error applying delay', error.message);
|
emitError(`Error applying delay: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -317,11 +318,11 @@ export default function EventListWrapper() {
|
|||||||
try {
|
try {
|
||||||
await deleteAllEvents.mutateAsync();
|
await deleteAllEvents.mutateAsync();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error deleting events', error.message);
|
emitError(`Error deleting events: ${error.message}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
showErrorToast('Unrecognised request', action);
|
emitError(`Unhandled request: ${action}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
height: 73vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
@@ -15,11 +15,10 @@ export default function Info() {
|
|||||||
titleNext: '',
|
titleNext: '',
|
||||||
subtitleNext: '',
|
subtitleNext: '',
|
||||||
presenterNext: '',
|
presenterNext: '',
|
||||||
noteNext: '',
|
noteNext: ''
|
||||||
});
|
});
|
||||||
const [selected, setSelected] = useState('No events');
|
const [selected, setSelected] = useState('No events');
|
||||||
const [playback, setPlayback] = useState(null);
|
const [playback, setPlayback] = useState(null);
|
||||||
const logData = [];
|
|
||||||
|
|
||||||
// handle incoming messages
|
// handle incoming messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -61,20 +60,19 @@ export default function Info() {
|
|||||||
};
|
};
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
// TODO: Put this in use effect
|
|
||||||
// prepare data
|
// prepare data
|
||||||
const titlesNow = {
|
const titlesNow = {
|
||||||
title: titles.titleNow,
|
title: titles.titleNow,
|
||||||
subtitle: titles.subtitleNow,
|
subtitle: titles.subtitleNow,
|
||||||
presenter: titles.presenterNow,
|
presenter: titles.presenterNow,
|
||||||
note: titles.noteNow,
|
note: titles.noteNow
|
||||||
};
|
};
|
||||||
|
|
||||||
const titlesNext = {
|
const titlesNext = {
|
||||||
title: titles.titleNext,
|
title: titles.titleNext,
|
||||||
subtitle: titles.subtitleNext,
|
subtitle: titles.subtitleNext,
|
||||||
presenter: titles.presenterNext,
|
presenter: titles.presenterNext,
|
||||||
note: titles.noteNext,
|
note: titles.noteNext
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -83,10 +81,10 @@ export default function Info() {
|
|||||||
<span>{`Running on port 4001`}</span>
|
<span>{`Running on port 4001`}</span>
|
||||||
<span>{selected}</span>
|
<span>{selected}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* <InfoLogger logData={logData} /> */}
|
|
||||||
<InfoNif />
|
<InfoNif />
|
||||||
<InfoTitle title={'Now'} data={titlesNow} roll={playback === 'roll'} />
|
<InfoTitle title={'Now'} data={titlesNow} roll={playback === 'roll'} />
|
||||||
<InfoTitle title={'Next'} data={titlesNext} roll={playback === 'roll'} />
|
<InfoTitle title={'Next'} data={titlesNext} roll={playback === 'roll'} />
|
||||||
|
<InfoLogger />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
.container {
|
@use '../../main' as *;
|
||||||
|
|
||||||
|
@mixin container {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -8,9 +10,13 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include container;
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: #ff7597;
|
color: $ontime-pink;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
@@ -20,17 +26,17 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: #ccc;
|
color: $header-gray;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
color: #ccc;
|
color: $header-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerRoll {
|
.headerRoll {
|
||||||
color: #2b6cb0;
|
color: $ontime-roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsedTitle {
|
.collapsedTitle {
|
||||||
@@ -57,7 +63,7 @@
|
|||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: #aaa;
|
color: $label-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label::after {
|
.label::after {
|
||||||
@@ -70,41 +76,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notes {
|
.notes {
|
||||||
color: #d69e2e;
|
color: $notes-color;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.if {
|
.if {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: #4bffabcc;
|
color: $ontime-accent;
|
||||||
background-color: rgba(0, 0, 0, 0.13);
|
@include container-bg;
|
||||||
border-radius: 2px;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
margin: 0 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log {
|
ul > li {
|
||||||
overflow-y: scroll;
|
|
||||||
height: 30vh;
|
|
||||||
|
|
||||||
ul > li {
|
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client {
|
|
||||||
color: lightblue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.moreExpanded,
|
.moreExpanded,
|
||||||
|
|||||||
@@ -1,34 +1,117 @@
|
|||||||
import { Icon } from '@chakra-ui/react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { useState } from 'react';
|
import style from './InfoLogger.module.scss';
|
||||||
import { FiChevronUp } from 'react-icons/fi';
|
import CollapseBar from "../../common/components/collapseBar/CollapseBar";
|
||||||
import style from './Info.module.scss';
|
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function InfoLogger(props) {
|
export default function InfoLogger() {
|
||||||
|
const { logData, clearLog } = useContext(LoggingContext);
|
||||||
|
const [data, setData] = useState([]);
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
// Todo: save in local storage
|
||||||
|
const [showClient, setShowClient] = useState(true);
|
||||||
|
const [showServer, setShowServer] = useState(true);
|
||||||
|
const [showRx, setShowRx] = useState(true);
|
||||||
|
const [showTx, setShowTx] = useState(true);
|
||||||
|
const [showPlayback, setShowPlayback] = useState(true);
|
||||||
|
const [showUser, setShowUser] = useState(true);
|
||||||
|
|
||||||
const { logData } = props;
|
useEffect(() => {
|
||||||
|
const matchers = [];
|
||||||
|
if (showUser) {
|
||||||
|
matchers.push('USER');
|
||||||
|
}
|
||||||
|
if (showClient) {
|
||||||
|
matchers.push('CLIENT');
|
||||||
|
}
|
||||||
|
if (showServer) {
|
||||||
|
matchers.push('SERVER');
|
||||||
|
}
|
||||||
|
if (showRx) {
|
||||||
|
matchers.push('RX');
|
||||||
|
}
|
||||||
|
if (showTx) {
|
||||||
|
matchers.push('TX');
|
||||||
|
}
|
||||||
|
if (showPlayback) {
|
||||||
|
matchers.push('PLAYBACK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = logData.filter((d) => (
|
||||||
|
matchers.some((m) => d.origin === m)
|
||||||
|
))
|
||||||
|
|
||||||
|
setData(d);
|
||||||
|
},[logData, showUser, showClient, showServer, showPlayback, showRx, showTx])
|
||||||
|
|
||||||
|
const disableOthers = (toEnable) => {
|
||||||
|
toEnable === 'USER' ? setShowUser(true) : setShowUser(false);
|
||||||
|
toEnable === 'CLIENT' ? setShowClient(true) : setShowClient(false);
|
||||||
|
toEnable === 'SERVER' ? setShowServer(true) : setShowServer(false);
|
||||||
|
toEnable === 'RX' ? setShowRx(true) : setShowRx(false);
|
||||||
|
toEnable === 'TX' ? setShowTx(true) : setShowTx(false);
|
||||||
|
toEnable === 'PLAYBACK' ? setShowPlayback(true) : setShowPlayback(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container}>
|
<div className={collapsed ? style.container : style.container__expanded}>
|
||||||
<div className={style.header}>
|
<CollapseBar title={'Log'} isCollapsed={collapsed} onClick={() => setCollapsed((c) => !c)}/>
|
||||||
Log
|
|
||||||
<Icon
|
|
||||||
className={collapsed ? style.moreCollapsed : style.moreExpanded}
|
|
||||||
as={FiChevronUp}
|
|
||||||
onClick={() => setCollapsed((c) => !c)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
|
<>
|
||||||
|
<div className={style.toggleBar}>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowUser((s) => !s)}
|
||||||
|
onAuxClick={() => disableOthers('USER')}
|
||||||
|
className={(showUser) ? style.active : null}>
|
||||||
|
USER
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowClient((s) => !s)}
|
||||||
|
onAuxClick={() => disableOthers('CLIENT')}
|
||||||
|
className={(showClient) ? style.active : null}>
|
||||||
|
CLIENT
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowServer((s) => !s)}
|
||||||
|
onAuxClick={() => disableOthers('SERVER')}
|
||||||
|
className={(showServer) ? style.active : null}>
|
||||||
|
SERVER
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowPlayback((s) => !s)}
|
||||||
|
onAuxClick={() => disableOthers('PLAYBACK')}
|
||||||
|
className={(showPlayback) ? style.active : null}>
|
||||||
|
Playback
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowRx((s) => !s)}
|
||||||
|
onAuxClick={() => disableOthers('RX')}
|
||||||
|
className={(showRx) ? style.active : null}>
|
||||||
|
RX
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowTx((s) => !s)}
|
||||||
|
onAuxClick={() => disableOthers('TX')}
|
||||||
|
className={(showTx) ? style.active : null}>
|
||||||
|
TX
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={clearLog}
|
||||||
|
className={style.clear}>
|
||||||
|
Clear
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ul className={style.log}>
|
<ul className={style.log}>
|
||||||
<li className={style.info}>10:35:23 [PLAYBACK] Next</li>
|
{data.map((d) => (
|
||||||
<li className={style.client}>
|
<li key={d.id} className={d.level === 'INFO' ? style.info : d.level === 'WARN' ? style.warn : d.level === 'ERROR' ? style.error : ''}>
|
||||||
10:32:10 [CLIENT] New socket client (total: 3)
|
<div
|
||||||
|
className={style.time}
|
||||||
|
>{d.time}</div>
|
||||||
|
<div className={style.origin}>{d.origin}</div>
|
||||||
|
<div className={style.msg}>{d.text}</div>
|
||||||
</li>
|
</li>
|
||||||
<li className={style.info}>10:28:23 [PLAYBACK] Next</li>
|
))}
|
||||||
<li className={style.info}>10:25:23 [PLAYBACK] Play</li>
|
|
||||||
<li className={style.info}>10:23:13 [SERVER] Server Reconnected</li>
|
|
||||||
<li className={style.error}>10:23:10 [SERVER] Server Disconnected</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
@use 'Info.module' as *;
|
||||||
|
@use '../../main' as *;
|
||||||
|
|
||||||
|
.container,
|
||||||
|
.container__expanded{
|
||||||
|
@include container;
|
||||||
|
max-height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container__expanded {
|
||||||
|
min-height: 50%;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
font-size: 0.8em;
|
||||||
|
user-select:text;
|
||||||
|
@include container-bg;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
|
||||||
|
.time {
|
||||||
|
width: 13%;
|
||||||
|
}
|
||||||
|
.origin {
|
||||||
|
width: 18%;
|
||||||
|
}
|
||||||
|
.msg {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.info {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
li.warn {
|
||||||
|
color: #dd6b20;
|
||||||
|
}
|
||||||
|
li.error {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry:hover {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client {
|
||||||
|
color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleBar {
|
||||||
|
display: flex;
|
||||||
|
font-size: 0.7em;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 1em;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
div {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: #0002;
|
||||||
|
border: 1px solid #fff1;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.active {
|
||||||
|
background: $ontime-accent;
|
||||||
|
color: darken($ontime-accent, 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
border: 1px solid rgba($ontime-pink, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { Icon } from '@chakra-ui/react';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FiChevronUp } from 'react-icons/fi';
|
|
||||||
import { APP_TABLE } from 'app/api/apiConstants';
|
import { APP_TABLE } from 'app/api/apiConstants';
|
||||||
import { getInfo, ontimePlaceholderInfo } from 'app/api/ontimeApi';
|
import { getInfo, ontimePlaceholderInfo } from 'app/api/ontimeApi';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import style from './Info.module.scss';
|
import style from './Info.module.scss';
|
||||||
|
import CollapseBar from '../../common/components/collapseBar/CollapseBar';
|
||||||
|
|
||||||
export default function InfoNif() {
|
export default function InfoNif() {
|
||||||
const { data, status } = useFetch(APP_TABLE, getInfo, {
|
const { data, status } = useFetch(APP_TABLE, getInfo, {
|
||||||
@@ -23,15 +22,7 @@ export default function InfoNif() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
<div className={style.header}>
|
<CollapseBar title={'Network Info'} isCollapsed={collapsed} onClick={() => setCollapsed((c) => !c)}/>
|
||||||
Network Info
|
|
||||||
<Icon
|
|
||||||
className={collapsed ? style.moreCollapsed : style.moreExpanded}
|
|
||||||
as={FiChevronUp}
|
|
||||||
onClick={() => setCollapsed((c) => !c)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div>
|
<div>
|
||||||
{status === 'success' && (
|
{status === 'success' && (
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import QuitIconBtn from './buttons/QuitIconBtn';
|
|||||||
import style from './MenuBar.module.css';
|
import style from './MenuBar.module.css';
|
||||||
import HelpIconBtn from './buttons/HelpIconBtn';
|
import HelpIconBtn from './buttons/HelpIconBtn';
|
||||||
import UploadIconBtn from './buttons/UploadIconBtn';
|
import UploadIconBtn from './buttons/UploadIconBtn';
|
||||||
import { useRef } from 'react';
|
import { useContext, useRef } from 'react';
|
||||||
|
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function MenuBar(props) {
|
export default function MenuBar(props) {
|
||||||
const { onOpen } = props;
|
const { onOpen } = props;
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const hiddenFileInput = useRef(null);
|
const hiddenFileInput = useRef(null);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const uploaddb = useMutation(uploadEvents, {
|
const uploaddb = useMutation(uploadEvents, {
|
||||||
@@ -34,28 +36,24 @@ export default function MenuBar(props) {
|
|||||||
const handleUpload = (event) => {
|
const handleUpload = (event) => {
|
||||||
const fileUploaded = event.target.files[0];
|
const fileUploaded = event.target.files[0];
|
||||||
if (fileUploaded == null) return;
|
if (fileUploaded == null) return;
|
||||||
console.log(fileUploaded);
|
|
||||||
|
|
||||||
// Limit file size to 1MB
|
// Limit file size to 1MB
|
||||||
if (fileUploaded.size > 1000000) {
|
if (fileUploaded.size > 1000000) {
|
||||||
console.log('Error: File size limit (1MB) exceeded');
|
emitError('Error: File size limit (1MB) exceeded')
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file extension
|
// Check file extension
|
||||||
if (fileUploaded.name.endsWith('.xlsx')) {
|
if (! fileUploaded.name.endsWith('.xlsx')
|
||||||
console.log('excel file');
|
|| !fileUploaded.name.endsWith('.json')) {
|
||||||
} else if (fileUploaded.name.endsWith('.json')) {
|
emitError('Error: File type unknown')
|
||||||
console.log('json file');
|
|
||||||
} else {
|
|
||||||
console.log('Error: File type unknown');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uploaddb.mutate(fileUploaded);
|
uploaddb.mutate(fileUploaded);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
emitError(`Failed uploading file: ${error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset input value
|
// reset input value
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default function AliasesModal() {
|
|||||||
const smLink = 'http://localhost:4001/sm';
|
const smLink = 'http://localhost:4001/sm';
|
||||||
const publicLink = 'http://localhost:4001/public';
|
const publicLink = 'http://localhost:4001/public';
|
||||||
const pipLink = 'http://localhost:4001/pip';
|
const pipLink = 'http://localhost:4001/pip';
|
||||||
|
const studioLink = 'http://localhost:4001/studio';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -81,6 +82,17 @@ export default function AliasesModal() {
|
|||||||
{pipLink}
|
{pipLink}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Studio Clock<br />
|
||||||
|
<a
|
||||||
|
href={studioLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{studioLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>Manage custom aliases</span>
|
<span>Manage custom aliases</span>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { ModalBody } from '@chakra-ui/modal';
|
import { ModalBody } from '@chakra-ui/modal';
|
||||||
import { FormLabel, FormControl, Input, Button } from '@chakra-ui/react';
|
import { FormLabel, FormControl, Input, Button } from '@chakra-ui/react';
|
||||||
import { getOSC, oscPlaceholderSettings, postOSC } from 'app/api/ontimeApi';
|
import { getOSC, oscPlaceholderSettings, postOSC } from 'app/api/ontimeApi';
|
||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import { OSC_SETTINGS } from 'app/api/apiConstants';
|
import { OSC_SETTINGS } from 'app/api/apiConstants';
|
||||||
import { showErrorToast } from 'common/helpers/toastManager';
|
|
||||||
import style from './Modals.module.scss';
|
import style from './Modals.module.scss';
|
||||||
|
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function AppSettingsModal() {
|
export default function AppSettingsModal() {
|
||||||
const { data, status } = useFetch(OSC_SETTINGS, getOSC);
|
const { data, status } = useFetch(OSC_SETTINGS, getOSC);
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const [formData, setFormData] = useState(oscPlaceholderSettings);
|
const [formData, setFormData] = useState(oscPlaceholderSettings);
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -41,7 +42,7 @@ export default function AppSettingsModal() {
|
|||||||
|
|
||||||
// set fields with error
|
// set fields with error
|
||||||
if (e.status) {
|
if (e.status) {
|
||||||
showErrorToast('Invalid Input', e.message);
|
emitError(`Invalid Input: ${e.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ import {
|
|||||||
ontimeVars,
|
ontimeVars,
|
||||||
postInfo,
|
postInfo,
|
||||||
} from 'app/api/ontimeApi';
|
} from 'app/api/ontimeApi';
|
||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import { APP_TABLE } from 'app/api/apiConstants';
|
import { APP_TABLE } from 'app/api/apiConstants';
|
||||||
import { showErrorToast } from 'common/helpers/toastManager';
|
|
||||||
import style from './Modals.module.scss';
|
import style from './Modals.module.scss';
|
||||||
|
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||||
|
|
||||||
export default function IntegrationSettingsModal() {
|
export default function IntegrationSettingsModal() {
|
||||||
const { data, status } = useFetch(APP_TABLE, getInfo);
|
const { data, status } = useFetch(APP_TABLE, getInfo);
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const [formData, setFormData] = useState(httpPlaceholder);
|
const [formData, setFormData] = useState(httpPlaceholder);
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -51,7 +52,7 @@ export default function IntegrationSettingsModal() {
|
|||||||
|
|
||||||
// set fields with error
|
// set fields with error
|
||||||
if (e.status) {
|
if (e.status) {
|
||||||
showErrorToast('Invalid Input', e.message);
|
emitError(`Invalid Input: ${e.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { fetchAllEvents } from 'app/api/eventsApi';
|
import { fetchAllEvents } from 'app/api/eventsApi';
|
||||||
import { fetchEvent } from 'app/api/eventApi';
|
import { fetchEvent } from 'app/api/eventApi';
|
||||||
import { useSocket } from 'app/context/socketContext';
|
import { useSocket } from 'app/context/socketContext';
|
||||||
import { stringFromMillis } from 'common/utils/dateConfig';
|
import { stringFromMillis } from 'ontime-server/utils/time';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import { EVENTS_TABLE, EVENT_TABLE } from 'app/api/apiConstants';
|
import { EVENTS_TABLE, EVENT_TABLE } from 'app/api/apiConstants';
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -119,7 +119,7 @@ app.whenReady().then(() => {
|
|||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
// register global shortcuts
|
// register global shortcuts
|
||||||
// (available regardless of wheter app is in focus)
|
// (available regardless of whether app is in focus)
|
||||||
// bring focus to window
|
// bring focus to window
|
||||||
globalShortcut.register('Alt+1', () => {
|
globalShortcut.register('Alt+1', () => {
|
||||||
win.show();
|
win.show();
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "0.4.7",
|
"version": "0.4.8",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
+1
-1
@@ -153,7 +153,7 @@ export const startServer = async (overrideConfig = null) => {
|
|||||||
const port = 4001;
|
const port = 4001;
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
const returnMessage = `HTTP Server is listening on port ${port}`;
|
const returnMessage = `Ontime is listening on port ${port}`;
|
||||||
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
|
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
|
||||||
|
|
||||||
// OSC Config
|
// OSC Config
|
||||||
|
|||||||
+305
-243
@@ -1,110 +1,85 @@
|
|||||||
import {Timer} from './Timer.js';
|
import { Timer } from './Timer.js';
|
||||||
import {Server} from 'socket.io';
|
import { Server } from 'socket.io';
|
||||||
import {DAY_TO_MS, getSelectionByRoll, replacePlaceholder, updateRoll} from './classUtils.js';
|
import {
|
||||||
import {OSCIntegration} from './integrations/Osc.js';
|
DAY_TO_MS,
|
||||||
import {HTTPIntegration} from "./integrations/Http.js";
|
getSelectionByRoll,
|
||||||
import {cleanURL} from "../utils/url.js";
|
replacePlaceholder,
|
||||||
|
updateRoll,
|
||||||
|
} from './classUtils.js';
|
||||||
|
import { OSCIntegration } from './integrations/Osc.js';
|
||||||
|
import { HTTPIntegration } from './integrations/Http.js';
|
||||||
|
import { cleanURL } from '../utils/url.js';
|
||||||
|
import getRandomName from '../utils/getRandomName.js';
|
||||||
|
import { stringFromMillis } from '../utils/time.js';
|
||||||
|
import { generateId } from '../utils/generate_id.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EventTimer adds functions specific to APP
|
* Class EventTimer adds functions specific to APP
|
||||||
* namely:
|
* @extends Timer
|
||||||
* - Presenter message, text and status
|
|
||||||
* - Public message, text and status
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class EventTimer extends Timer {
|
export class EventTimer extends Timer {
|
||||||
|
/**
|
||||||
|
* Instantiates an event timer object
|
||||||
|
* @param {object} httpServer
|
||||||
|
* @param {object} timerConfig
|
||||||
|
* @param {object} [oscConfig]
|
||||||
|
* @param {object} [httpConfig]
|
||||||
|
*/
|
||||||
|
constructor(httpServer, timerConfig, oscConfig, httpConfig) {
|
||||||
|
// call super constructor
|
||||||
|
super();
|
||||||
|
|
||||||
// Keep track of Timer lifecycle
|
this.cycleState = {
|
||||||
// idle: before it is initialised
|
/* idle: before it is initialised */
|
||||||
// load: when a new event is loaded
|
|
||||||
// update: every update call cycle (1 x second)
|
|
||||||
// stop: when the timer is stopped
|
|
||||||
// finish: when a timer finishes
|
|
||||||
cycleState = {
|
|
||||||
idle: 'idle',
|
idle: 'idle',
|
||||||
|
/* onLoad: when a new event is loaded */
|
||||||
onLoad: 'onLoad',
|
onLoad: 'onLoad',
|
||||||
|
/* armed: when a new event is loaded but hasn't started */
|
||||||
armed: 'armed',
|
armed: 'armed',
|
||||||
onStart: 'onStart',
|
onStart: 'onStart',
|
||||||
|
/* update: every update call cycle (1 x second) */
|
||||||
onUpdate: 'onUpdate',
|
onUpdate: 'onUpdate',
|
||||||
onPause: 'onPause',
|
onPause: 'onPause',
|
||||||
onStop: 'onStop',
|
onStop: 'onStop',
|
||||||
onFinish: 'onFinish',
|
onFinish: 'onFinish',
|
||||||
};
|
};
|
||||||
ontimeCycle = 'idle';
|
this.ontimeCycle = 'idle';
|
||||||
prevCycle = null;
|
this.prevCycle = null;
|
||||||
lastUpdate = null;
|
|
||||||
|
|
||||||
// Socket IO Object
|
|
||||||
io = null;
|
|
||||||
|
|
||||||
// OSC Object
|
// OSC Object
|
||||||
osc = null;
|
this.osc = null;
|
||||||
|
|
||||||
// HTTP Client Object
|
// HTTP Client Object
|
||||||
http = null;
|
this.http = null;
|
||||||
|
|
||||||
_numClients = 0;
|
this._numClients = 0;
|
||||||
_interval = null;
|
this._interval = null;
|
||||||
|
|
||||||
presenter = {
|
this.presenter = {
|
||||||
text: '',
|
text: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
};
|
};
|
||||||
public = {
|
this.public = {
|
||||||
text: '',
|
text: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
};
|
};
|
||||||
lower = {
|
this.lower = {
|
||||||
text: '',
|
text: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
titlesPublic = {
|
// call general title reset
|
||||||
titleNow: null,
|
this._resetSelection();
|
||||||
subtitleNow: null,
|
|
||||||
presenterNow: null,
|
|
||||||
titleNext: null,
|
|
||||||
subtitleNext: null,
|
|
||||||
presenterNext: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
titles = {
|
|
||||||
titleNow: null,
|
|
||||||
subtitleNow: null,
|
|
||||||
presenterNow: null,
|
|
||||||
noteNow: null,
|
|
||||||
titleNext: null,
|
|
||||||
subtitleNext: null,
|
|
||||||
presenterNext: null,
|
|
||||||
noteNext: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
selectedEventIndex = null;
|
|
||||||
selectedEventId = null;
|
|
||||||
nextEventId = null;
|
|
||||||
selectedPublicEventId = null;
|
|
||||||
nextPublicEventId = null;
|
|
||||||
numEvents = null;
|
|
||||||
_eventlist = null;
|
|
||||||
onAir = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates an event timer object
|
|
||||||
* @param httpServer
|
|
||||||
* @param timerConfig
|
|
||||||
* @param [oscConfig]
|
|
||||||
* @param [httpConfig]
|
|
||||||
*/
|
|
||||||
constructor(httpServer, timerConfig, oscConfig, httpConfig) {
|
|
||||||
|
|
||||||
// call super constructor
|
|
||||||
super();
|
|
||||||
|
|
||||||
// initialise class variables
|
|
||||||
this.numEvents = 0;
|
this.numEvents = 0;
|
||||||
|
this._eventlist = null;
|
||||||
|
this.onAir = false;
|
||||||
|
|
||||||
// initialise socketIO server
|
// initialise socketIO server
|
||||||
|
this.messageStack = [];
|
||||||
|
this.MAX_MESSAGES = 100;
|
||||||
|
this._clientNames = {};
|
||||||
this.io = new Server(httpServer, {
|
this.io = new Server(httpServer, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: '*',
|
origin: '*',
|
||||||
@@ -114,22 +89,6 @@ export class EventTimer extends Timer {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo: extract
|
|
||||||
// initialise osc object
|
|
||||||
if (oscConfig != null) {
|
|
||||||
console.log('initialise OSC Client on port: ', oscConfig?.port);
|
|
||||||
this.osc = new OSCIntegration();
|
|
||||||
this.osc.init(oscConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo: extract
|
|
||||||
// initialise http object
|
|
||||||
if (httpConfig != null) {
|
|
||||||
this.http = new HTTPIntegration();
|
|
||||||
this.http.init(httpConfig);
|
|
||||||
this.httpMessages = httpConfig.messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set recurrent emits
|
// set recurrent emits
|
||||||
this._interval = setInterval(
|
this._interval = setInterval(
|
||||||
() => this.runCycle(),
|
() => this.runCycle(),
|
||||||
@@ -138,28 +97,65 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
// listen to new connections
|
// listen to new connections
|
||||||
this._listenToConnections();
|
this._listenToConnections();
|
||||||
|
|
||||||
|
if (oscConfig != null) {
|
||||||
|
this._initOscClient(oscConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpConfig != null) {
|
||||||
|
this._initHTTPClient(httpConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Shutdown process
|
* @description Shutdown process
|
||||||
*/
|
*/
|
||||||
shutdown() {
|
shutdown() {
|
||||||
console.log('Shutting down integrations')
|
this.info('SERVER', 'Shutting down ontime');
|
||||||
console.log('... Closing socket server');
|
this.info('TX', '... Closing socket server');
|
||||||
this.io.close();
|
this.io.close();
|
||||||
console.log('... Closing osc server');
|
this.info('TX', '... Closing OSC Client');
|
||||||
this.osc.shutdown();
|
this.osc.shutdown();
|
||||||
|
this.info('TX', '... Closing HTTP Client');
|
||||||
|
this.http.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// send current timer
|
/**
|
||||||
|
* Initialises OSC Integration object
|
||||||
|
* @param {object} oscConfig
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initOscClient(oscConfig) {
|
||||||
|
this.osc = new OSCIntegration();
|
||||||
|
const r = this.osc.init(oscConfig);
|
||||||
|
r.success ? this.info('TX', r.message) : this.error('TX', r.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises HTTP Integration object
|
||||||
|
* @param {object} httpConfig
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initHTTPClient(httpConfig) {
|
||||||
|
this.info('TX', `Initialise HTTP Client on port`);
|
||||||
|
this.http = new HTTPIntegration();
|
||||||
|
this.http.init(httpConfig);
|
||||||
|
this.httpMessages = httpConfig.messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends time object over websockets
|
||||||
|
*/
|
||||||
broadcastTimer() {
|
broadcastTimer() {
|
||||||
// through websockets
|
// through websockets
|
||||||
this.io.emit('timer', this.getTimes());
|
this.io.emit('timer', this.getTimeObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcast state
|
/**
|
||||||
broadcastState(update = true) {
|
* Broadcasts complete object state
|
||||||
this.io.emit('timer', this.getTimes(update));
|
*/
|
||||||
|
broadcastState() {
|
||||||
|
this.broadcastTimer();
|
||||||
this.io.emit('playstate', this.state);
|
this.io.emit('playstate', this.state);
|
||||||
this.io.emit('selected', {
|
this.io.emit('selected', {
|
||||||
id: this.selectedEventId,
|
id: this.selectedEventId,
|
||||||
@@ -168,6 +164,7 @@ export class EventTimer extends Timer {
|
|||||||
});
|
});
|
||||||
this.io.emit('selected-id', this.selectedEventId);
|
this.io.emit('selected-id', this.selectedEventId);
|
||||||
this.io.emit('next-id', this.nextEventId);
|
this.io.emit('next-id', this.nextEventId);
|
||||||
|
this.io.emit('numevents', this.numEvents);
|
||||||
this.io.emit('publicselected-id', this.selectedPublicEventId);
|
this.io.emit('publicselected-id', this.selectedPublicEventId);
|
||||||
this.io.emit('publicnext-id', this.nextPublicEventId);
|
this.io.emit('publicnext-id', this.nextPublicEventId);
|
||||||
this.io.emit('titles', this.titles);
|
this.io.emit('titles', this.titles);
|
||||||
@@ -175,7 +172,11 @@ export class EventTimer extends Timer {
|
|||||||
this.io.emit('onAir', this.onAir);
|
this.io.emit('onAir', this.onAir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcast message
|
/**
|
||||||
|
* Broadcast given message
|
||||||
|
* @param {string} address - socket io address
|
||||||
|
* @param {any} payload - message body
|
||||||
|
*/
|
||||||
broadcastThis(address, payload) {
|
broadcastThis(address, payload) {
|
||||||
this.io.emit(address, payload);
|
this.io.emit(address, payload);
|
||||||
}
|
}
|
||||||
@@ -192,69 +193,73 @@ export class EventTimer extends Timer {
|
|||||||
case 'start':
|
case 'start':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Play Mode Start');
|
||||||
this.start();
|
this.start();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'pause':
|
case 'pause':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Play Mode Pause');
|
||||||
this.pause();
|
this.pause();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'stop':
|
case 'stop':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Play Mode Stop');
|
||||||
this.stop();
|
this.stop();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'roll':
|
case 'roll':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Play Mode Roll');
|
||||||
this.roll();
|
this.roll();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'previous':
|
case 'previous':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Play Mode Previous');
|
||||||
this.previous();
|
this.previous();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'next':
|
case 'next':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Play Mode Next');
|
||||||
this.next();
|
this.next();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'unload':
|
case 'unload':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Events unloaded');
|
||||||
this.unload();
|
this.unload();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'reload':
|
case 'reload':
|
||||||
if (this.numEvents === 0 || this.numEvents == null) return false;
|
if (this.numEvents === 0 || this.numEvents == null) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Reloaded event');
|
||||||
this.reload();
|
this.reload();
|
||||||
this.runCycle();
|
|
||||||
break;
|
break;
|
||||||
case 'onAir':
|
case 'onAir':
|
||||||
// Call action
|
// Call action
|
||||||
|
this.info('PLAYBACK', 'Going On Air');
|
||||||
this.setonAir(true);
|
this.setonAir(true);
|
||||||
break;
|
break;
|
||||||
case 'offAir':
|
case 'offAir':
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
|
this.info('PLAYBACK', 'Going Off Air');
|
||||||
this.setonAir(false);
|
this.setonAir(false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Error, disable flag
|
// Error, disable flag
|
||||||
console.log('ERROR: Unhandled action triggered')
|
this.error('RX', `Unhandled action triggered ${action}`);
|
||||||
reply = false;
|
reply = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
|
this.runCycle();
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description State machine checks what actions need to
|
* @description State machine checks what actions need to
|
||||||
* happen at every app cycle
|
* happen at every app cycle
|
||||||
@@ -264,18 +269,19 @@ export class EventTimer extends Timer {
|
|||||||
let httpMessage = null;
|
let httpMessage = null;
|
||||||
|
|
||||||
switch (this.ontimeCycle) {
|
switch (this.ontimeCycle) {
|
||||||
case "idle":
|
case 'idle':
|
||||||
break;
|
break;
|
||||||
case "armed":
|
case 'armed':
|
||||||
// if we come from roll, see if we can start
|
// if we come from roll, see if we can start
|
||||||
if (this.state === 'roll') {
|
if (this.state === 'roll') {
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "onLoad":
|
case 'onLoad':
|
||||||
// broadcast change
|
// broadcast change
|
||||||
this.broadcastState();
|
this.broadcastState();
|
||||||
|
|
||||||
|
// Todo: wrap in reusable function
|
||||||
// check integrations - http
|
// check integrations - http
|
||||||
if (h?.onLoad?.enabled) {
|
if (h?.onLoad?.enabled) {
|
||||||
if (h?.onLoad?.url != null || h?.onLoad?.url !== '') {
|
if (h?.onLoad?.url != null || h?.onLoad?.url !== '') {
|
||||||
@@ -286,13 +292,13 @@ export class EventTimer extends Timer {
|
|||||||
// update lifecycle: armed
|
// update lifecycle: armed
|
||||||
this.ontimeCycle = this.cycleState.armed;
|
this.ontimeCycle = this.cycleState.armed;
|
||||||
break;
|
break;
|
||||||
case "onStart":
|
case 'onStart':
|
||||||
// broadcast current state
|
// broadcast current state
|
||||||
this.broadcastState();
|
this.broadcastState();
|
||||||
// send OSC if there is something running
|
// send OSC if there is something running
|
||||||
// _finish at is only set when an event is loaded
|
// _finish at is only set when an event is loaded
|
||||||
if (this._finishAt > 0) {
|
if (this._finishAt > 0) {
|
||||||
this.osc.send(this.osc.implemented.play);
|
this.sendOsc(this.osc.implemented.play);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check integrations - http
|
// check integrations - http
|
||||||
@@ -305,7 +311,7 @@ export class EventTimer extends Timer {
|
|||||||
// update lifecycle: onUpdate
|
// update lifecycle: onUpdate
|
||||||
this.ontimeCycle = this.cycleState.onUpdate;
|
this.ontimeCycle = this.cycleState.onUpdate;
|
||||||
break;
|
break;
|
||||||
case "onUpdate":
|
case 'onUpdate':
|
||||||
// call update
|
// call update
|
||||||
this.update();
|
this.update();
|
||||||
// broadcast current state
|
// broadcast current state
|
||||||
@@ -313,9 +319,15 @@ export class EventTimer extends Timer {
|
|||||||
// through OSC, only if running
|
// through OSC, only if running
|
||||||
if (this.state === 'start' || this.state === 'roll') {
|
if (this.state === 'start' || this.state === 'roll') {
|
||||||
if (this.current != null && this.secondaryTimer == null) {
|
if (this.current != null && this.secondaryTimer == null) {
|
||||||
this.osc.send(this.osc.implemented.time, this.timeTag);
|
this.sendOsc(this.osc.implemented.time, this.timeTag);
|
||||||
this.osc.send(this.osc.implemented.overtime, this.current > 0 ? 0 : 1);
|
this.sendOsc(
|
||||||
this.osc.send(this.osc.implemented.title, this.titles?.titleNow || '');
|
this.osc.implemented.overtime,
|
||||||
|
this.current > 0 ? 0 : 1
|
||||||
|
);
|
||||||
|
this.sendOsc(
|
||||||
|
this.osc.implemented.title,
|
||||||
|
this.titles?.titleNow || ''
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,11 +339,11 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "onPause":
|
case 'onPause':
|
||||||
// broadcast current state
|
// broadcast current state
|
||||||
this.broadcastState();
|
this.broadcastState();
|
||||||
// send OSC
|
// send OSC
|
||||||
this.osc.send(this.osc.implemented.pause);
|
this.sendOsc(this.osc.implemented.pause);
|
||||||
|
|
||||||
// check integrations - http
|
// check integrations - http
|
||||||
if (h?.onLoad?.enabled) {
|
if (h?.onLoad?.enabled) {
|
||||||
@@ -344,13 +356,13 @@ export class EventTimer extends Timer {
|
|||||||
this.ontimeCycle = this.cycleState.armed;
|
this.ontimeCycle = this.cycleState.armed;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "onStop":
|
case 'onStop':
|
||||||
// broadcast change
|
// broadcast change
|
||||||
this.broadcastState();
|
this.broadcastState();
|
||||||
|
|
||||||
// send OSC if something was actually stopped
|
// send OSC if something was actually stopped
|
||||||
if (this.prevCycle === this.cycleState.onUpdate) {
|
if (this.prevCycle === this.cycleState.onUpdate) {
|
||||||
this.osc.send(this.osc.implemented.stop);
|
this.sendOsc(this.osc.implemented.stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check integrations - http
|
// check integrations - http
|
||||||
@@ -363,12 +375,11 @@ export class EventTimer extends Timer {
|
|||||||
// update lifecycle: idle
|
// update lifecycle: idle
|
||||||
this.ontimeCycle = this.cycleState.idle;
|
this.ontimeCycle = this.cycleState.idle;
|
||||||
break;
|
break;
|
||||||
case "onFinish":
|
case 'onFinish':
|
||||||
console.log('onFinish')
|
|
||||||
// broadcast change
|
// broadcast change
|
||||||
this.broadcastState(false);
|
this.broadcastState();
|
||||||
// finished an event
|
// finished an event
|
||||||
this.osc.send(this.osc.implemented.finished);
|
this.sendOsc(this.osc.implemented.finished);
|
||||||
|
|
||||||
// check integrations - http
|
// check integrations - http
|
||||||
if (h?.onLoad?.enabled) {
|
if (h?.onLoad?.enabled) {
|
||||||
@@ -381,20 +392,20 @@ export class EventTimer extends Timer {
|
|||||||
this.ontimeCycle = this.cycleState.onUpdate;
|
this.ontimeCycle = this.cycleState.onUpdate;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log(`ERROR: Unhandled cycle: ${this.ontimeCycle}`)
|
this.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send http message if any
|
// send http message if any
|
||||||
if (httpMessage != null) {
|
if (httpMessage != null) {
|
||||||
const v = {
|
const v = {
|
||||||
'$timer': this.timeTag,
|
$timer: this.timeTag,
|
||||||
'$title': this.titles.titleNow,
|
$title: this.titles.titleNow,
|
||||||
'$presenter': this.titles.presenterNow,
|
$presenter: this.titles.presenterNow,
|
||||||
'$subtitle': this.titles.subtitleNow,
|
$subtitle: this.titles.subtitleNow,
|
||||||
'$next-title': this.titles.titleNext,
|
'$next-title': this.titles.titleNext,
|
||||||
'$next-presenter': this.titles.presenterNext,
|
'$next-presenter': this.titles.presenterNext,
|
||||||
'$next-subtitle': this.titles.subtitleNext,
|
'$next-subtitle': this.titles.subtitleNext,
|
||||||
}
|
};
|
||||||
const m = cleanURL(replacePlaceholder(httpMessage, v));
|
const m = cleanURL(replacePlaceholder(httpMessage, v));
|
||||||
this.http.send(m);
|
this.http.send(m);
|
||||||
}
|
}
|
||||||
@@ -407,7 +418,6 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
|
||||||
// if there is nothing selected, update clock
|
// if there is nothing selected, update clock
|
||||||
const now = this._getCurrentTime();
|
const now = this._getCurrentTime();
|
||||||
|
|
||||||
@@ -450,13 +460,17 @@ export class EventTimer extends Timer {
|
|||||||
selectedEventId: this.selectedEventId,
|
selectedEventId: this.selectedEventId,
|
||||||
current: this.current,
|
current: this.current,
|
||||||
// safeguard on midnight rollover
|
// safeguard on midnight rollover
|
||||||
_finishAt: this._finishAt >= this._startedAt ? this._finishAt : this._finishAt + DAY_TO_MS,
|
_finishAt:
|
||||||
|
this._finishAt >= this._startedAt
|
||||||
|
? this._finishAt
|
||||||
|
: this._finishAt + DAY_TO_MS,
|
||||||
clock: this.clock,
|
clock: this.clock,
|
||||||
secondaryTimer: this.secondaryTimer,
|
secondaryTimer: this.secondaryTimer,
|
||||||
_secondaryTarget: this._secondaryTarget,
|
_secondaryTarget: this._secondaryTarget,
|
||||||
}
|
};
|
||||||
|
|
||||||
const {updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished} = updateRoll(u);
|
const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } =
|
||||||
|
updateRoll(u);
|
||||||
|
|
||||||
this.current = updatedTimer;
|
this.current = updatedTimer;
|
||||||
this.secondaryTimer = updatedSecondaryTimer;
|
this.secondaryTimer = updatedSecondaryTimer;
|
||||||
@@ -473,7 +487,13 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setterManager(action, payload) {
|
/**
|
||||||
|
* Set titles and broadcast change
|
||||||
|
* @param {string} action
|
||||||
|
* @param {any} payload
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_setTitles(action, payload) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
/*******************************************/
|
/*******************************************/
|
||||||
// Presenter message
|
// Presenter message
|
||||||
@@ -513,6 +533,10 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle socket io connections
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_listenToConnections() {
|
_listenToConnections() {
|
||||||
this.io.on('connection', (socket) => {
|
this.io.on('connection', (socket) => {
|
||||||
/*******************************/
|
/*******************************/
|
||||||
@@ -521,12 +545,14 @@ export class EventTimer extends Timer {
|
|||||||
/*******************************/
|
/*******************************/
|
||||||
// keep track of connections
|
// keep track of connections
|
||||||
this._numClients++;
|
this._numClients++;
|
||||||
console.log(
|
this._clientNames[socket.id] = getRandomName();
|
||||||
`EventTimer: ${this._numClients} Clients with new connection: ${socket.id}`
|
const m = `${this._numClients} Clients with new connection: ${
|
||||||
);
|
this._clientNames[socket.id]
|
||||||
|
}`;
|
||||||
|
this.info('CLIENT', m);
|
||||||
|
|
||||||
// send state
|
// send state
|
||||||
socket.emit('timer', this.getTimes());
|
socket.emit('timer', this.getTimeObject());
|
||||||
socket.emit('playstate', this.state);
|
socket.emit('playstate', this.state);
|
||||||
socket.emit('selected-id', this.selectedEventId);
|
socket.emit('selected-id', this.selectedEventId);
|
||||||
socket.emit('next-id', this.nextEventId);
|
socket.emit('next-id', this.nextEventId);
|
||||||
@@ -539,9 +565,11 @@ export class EventTimer extends Timer {
|
|||||||
/********************************/
|
/********************************/
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
this._numClients--;
|
this._numClients--;
|
||||||
console.log(
|
const m = `${this._numClients} Clients with disconnection: ${
|
||||||
`EventTimer: Client disconnected, total now: ${this._numClients}`
|
this._clientNames[socket.id]
|
||||||
);
|
}`;
|
||||||
|
delete this._clientNames[socket.id];
|
||||||
|
this.info('CLIENT', m);
|
||||||
});
|
});
|
||||||
|
|
||||||
/***************************************/
|
/***************************************/
|
||||||
@@ -552,7 +580,7 @@ export class EventTimer extends Timer {
|
|||||||
/*******************************************/
|
/*******************************************/
|
||||||
// general playback state
|
// general playback state
|
||||||
socket.on('get-state', () => {
|
socket.on('get-state', () => {
|
||||||
socket.emit('timer', this.getTimes());
|
socket.emit('timer', this.getTimeObject());
|
||||||
socket.emit('playstate', this.state);
|
socket.emit('playstate', this.state);
|
||||||
socket.emit('selected-id', this.selectedEventId);
|
socket.emit('selected-id', this.selectedEventId);
|
||||||
socket.emit('next-id', this.nextEventId);
|
socket.emit('next-id', this.nextEventId);
|
||||||
@@ -567,7 +595,7 @@ export class EventTimer extends Timer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('get-timer', () => {
|
socket.on('get-timer', () => {
|
||||||
socket.emit('timer', this.getTimes());
|
socket.emit('timer', this.getTimeObject());
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('increment-timer', (data) => {
|
socket.on('increment-timer', (data) => {
|
||||||
@@ -651,11 +679,11 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
// Presenter message
|
// Presenter message
|
||||||
socket.on('set-presenter-text', (data) => {
|
socket.on('set-presenter-text', (data) => {
|
||||||
this._setterManager('set-presenter-text', data);
|
this._setTitles('set-presenter-text', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('set-presenter-visible', (data) => {
|
socket.on('set-presenter-visible', (data) => {
|
||||||
this._setterManager('set-presenter-visible', data);
|
this._setTitles('set-presenter-visible', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('get-presenter', () => {
|
socket.on('get-presenter', () => {
|
||||||
@@ -664,11 +692,11 @@ export class EventTimer extends Timer {
|
|||||||
/*******************************************/
|
/*******************************************/
|
||||||
// Public message
|
// Public message
|
||||||
socket.on('set-public-text', (data) => {
|
socket.on('set-public-text', (data) => {
|
||||||
this._setterManager('set-public-text', data);
|
this._setTitles('set-public-text', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('set-public-visible', (data) => {
|
socket.on('set-public-visible', (data) => {
|
||||||
this._setterManager('set-public-visible', data);
|
this._setTitles('set-public-visible', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('get-public', () => {
|
socket.on('get-public', () => {
|
||||||
@@ -678,11 +706,11 @@ export class EventTimer extends Timer {
|
|||||||
/*******************************************/
|
/*******************************************/
|
||||||
// Lower third message
|
// Lower third message
|
||||||
socket.on('set-lower-text', (data) => {
|
socket.on('set-lower-text', (data) => {
|
||||||
this._setterManager('set-lower-text', data);
|
this._setTitles('set-lower-text', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('set-lower-visible', (data) => {
|
socket.on('set-lower-visible', (data) => {
|
||||||
this._setterManager('set-lower-visible', data);
|
this._setTitles('set-lower-visible', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('get-lower', () => {
|
socket.on('get-lower', () => {
|
||||||
@@ -691,6 +719,9 @@ export class EventTimer extends Timer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes running event list from object
|
||||||
|
*/
|
||||||
clearEventList() {
|
clearEventList() {
|
||||||
// unload events
|
// unload events
|
||||||
this.unload();
|
this.unload();
|
||||||
@@ -706,6 +737,10 @@ export class EventTimer extends Timer {
|
|||||||
this.broadcastThis('numevents', this.numEvents);
|
this.broadcastThis('numevents', this.numEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event list to object
|
||||||
|
* @param {array} eventlist
|
||||||
|
*/
|
||||||
setupWithEventList(eventlist) {
|
setupWithEventList(eventlist) {
|
||||||
if (!Array.isArray(eventlist) || eventlist.length < 1) return;
|
if (!Array.isArray(eventlist) || eventlist.length < 1) return;
|
||||||
|
|
||||||
@@ -717,19 +752,23 @@ export class EventTimer extends Timer {
|
|||||||
this._eventlist = events;
|
this._eventlist = events;
|
||||||
this.numEvents = numEvents;
|
this.numEvents = numEvents;
|
||||||
|
|
||||||
// list may be empty
|
// list may contain no events
|
||||||
if (numEvents < 1) return;
|
if (numEvents < 1) return;
|
||||||
|
|
||||||
// load first event
|
// load first event
|
||||||
this.loadEvent(0);
|
this.loadEvent(0);
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
this.broadcastThis('numevents', this.numEvents);
|
this.broadcastState();
|
||||||
|
|
||||||
// run cycle
|
// run cycle
|
||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates event list in object
|
||||||
|
* @param {array} eventlist
|
||||||
|
*/
|
||||||
updateEventList(eventlist) {
|
updateEventList(eventlist) {
|
||||||
// filter only events
|
// filter only events
|
||||||
const events = eventlist.filter((e) => e.type === 'event');
|
const events = eventlist.filter((e) => e.type === 'event');
|
||||||
@@ -771,12 +810,17 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
this.broadcastThis('numevents', this.numEvents);
|
this.broadcastState();
|
||||||
|
|
||||||
// run cycle
|
// run cycle
|
||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a single id in the object list
|
||||||
|
* @param {string} id
|
||||||
|
* @param {object} entry - new event object
|
||||||
|
*/
|
||||||
updateSingleEvent(id, entry) {
|
updateSingleEvent(id, entry) {
|
||||||
// find object in events
|
// find object in events
|
||||||
const eventIndex = this._eventlist.findIndex((e) => e.id === id);
|
const eventIndex = this._eventlist.findIndex((e) => e.id === id);
|
||||||
@@ -810,16 +854,20 @@ export class EventTimer extends Timer {
|
|||||||
this._loadTitlesNow();
|
this._loadTitlesNow();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
this.error('SERVER', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
this.broadcastThis('numevents', this.numEvents);
|
this.broadcastState();
|
||||||
|
|
||||||
// run cycle
|
// run cycle
|
||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deleted an event from the list by its id
|
||||||
|
* @param {string} eventId
|
||||||
|
*/
|
||||||
deleteId(eventId) {
|
deleteId(eventId) {
|
||||||
// find object in events
|
// find object in events
|
||||||
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
|
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
|
||||||
@@ -848,7 +896,7 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
this.broadcastThis('numevents', this.numEvents);
|
this.broadcastState();
|
||||||
|
|
||||||
// run cycle
|
// run cycle
|
||||||
this.runCycle();
|
this.runCycle();
|
||||||
@@ -856,34 +904,35 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description loads an event with a given Id
|
* @description loads an event with a given Id
|
||||||
* @param eventId - ID of event in eventlist
|
* @param {string} eventId - ID of event in eventlist
|
||||||
*/
|
*/
|
||||||
loadEventById(eventId) {
|
loadEventById(eventId) {
|
||||||
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
|
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
|
||||||
|
|
||||||
if (eventIndex === -1) return;
|
if (eventIndex === -1) return;
|
||||||
this.pause();
|
this.pause();
|
||||||
this.loadEvent(eventIndex, 'load', true);
|
this.loadEvent(eventIndex, 'load');
|
||||||
// run cycle
|
// run cycle
|
||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description loads an event with a given index
|
* @description loads an event with a given index
|
||||||
* @param eventIndex - Index of event in eventlist
|
* @param {number} eventIndex - Index of event in eventlist
|
||||||
*/
|
*/
|
||||||
loadEventByIndex(eventIndex) {
|
loadEventByIndex(eventIndex) {
|
||||||
if (eventIndex === -1 || eventIndex > this.numEvents) return;
|
if (eventIndex === -1 || eventIndex > this.numEvents) return;
|
||||||
this.pause();
|
this.pause();
|
||||||
this.loadEvent(eventIndex, 'load', true);
|
this.loadEvent(eventIndex, 'load');
|
||||||
// run cycle
|
// run cycle
|
||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads a given event
|
/**
|
||||||
// load timers
|
* Loads a given event by index
|
||||||
// load selectedEventIndex
|
* @param {object} eventIndex
|
||||||
// load titles
|
* @param {string} [type='load'] - 'load' or 'reload', whether we are keeping running time
|
||||||
|
*/
|
||||||
loadEvent(eventIndex, type = 'load') {
|
loadEvent(eventIndex, type = 'load') {
|
||||||
const e = this._eventlist[eventIndex];
|
const e = this._eventlist[eventIndex];
|
||||||
if (e == null) return;
|
if (e == null) return;
|
||||||
@@ -894,7 +943,6 @@ export class EventTimer extends Timer {
|
|||||||
if (end < start) end += DAY_TO_MS;
|
if (end < start) end += DAY_TO_MS;
|
||||||
|
|
||||||
// time stuff changes on whether we keep the running clock
|
// time stuff changes on whether we keep the running clock
|
||||||
|
|
||||||
if (type === 'load') {
|
if (type === 'load') {
|
||||||
this._resetTimers();
|
this._resetTimers();
|
||||||
|
|
||||||
@@ -1092,75 +1140,6 @@ export class EventTimer extends Timer {
|
|||||||
this.nextPublicEventId = null;
|
this.nextPublicEventId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
print() {
|
|
||||||
return `
|
|
||||||
Timer
|
|
||||||
=========
|
|
||||||
|
|
||||||
Playback
|
|
||||||
------------------------------
|
|
||||||
state = ${this.state}
|
|
||||||
current = ${this.current}
|
|
||||||
duration = ${this.duration}
|
|
||||||
secondaryTimer = ${this.secondaryTimer}
|
|
||||||
|
|
||||||
Events
|
|
||||||
------------------------------
|
|
||||||
numEvents = ${this.numEvents}
|
|
||||||
selectedEventIndex = ${this.selectedEventIndex}
|
|
||||||
selectedEventId = ${this.selectedEventId}
|
|
||||||
nextEventId = ${this.nextEventId}
|
|
||||||
selectedPublicEventId = ${this.selectedPublicEventId}
|
|
||||||
nextPublicEventId = ${this.nextPublicEventId}
|
|
||||||
|
|
||||||
Private Titles
|
|
||||||
------------------------------
|
|
||||||
NowID = ${this.selectedEventId}
|
|
||||||
NextID = ${this.nextEventId}
|
|
||||||
Title Now = ${this.titles.titleNow}
|
|
||||||
Subtitle Now = ${this.titles.subtitleNow}
|
|
||||||
Presenter Now = ${this.titles.presenterNow}
|
|
||||||
Note Now = ${this.titles.noteNow}
|
|
||||||
Title Next = ${this.titles.titleNext}
|
|
||||||
Subtitle Next = ${this.titles.subtitleNext}
|
|
||||||
Presenter Next = ${this.titles.presenterNext}
|
|
||||||
Note Next = ${this.titles.noteNext}
|
|
||||||
|
|
||||||
Public Titles
|
|
||||||
------------------------------
|
|
||||||
NowID = ${this.selectedPublicEventId}
|
|
||||||
NextID = ${this.nextPublicEventId}
|
|
||||||
Title Now = ${this.titlesPublic.titleNow}
|
|
||||||
Subtitle Now = ${this.titlesPublic.subtitleNow}
|
|
||||||
Presenter Now = ${this.titlesPublic.presenterNow}
|
|
||||||
Title Next = ${this.titlesPublic.titleNext}
|
|
||||||
Subtitle Next = ${this.titlesPublic.subtitleNext}
|
|
||||||
Presenter Next = ${this.titlesPublic.presenterNext}
|
|
||||||
|
|
||||||
Messages
|
|
||||||
------------------------------
|
|
||||||
presenter text = ${this.presenter.text}
|
|
||||||
presenter vis = ${this.presenter.visible}
|
|
||||||
public text = ${this.public.text}
|
|
||||||
public vis = ${this.public.visible}
|
|
||||||
lower text = ${this.lower.text}
|
|
||||||
lower vis = ${this.lower.visible}
|
|
||||||
|
|
||||||
Private
|
|
||||||
------------------------------
|
|
||||||
finishAt = ${this._finishAt}
|
|
||||||
finished = ${this._finishedAt}
|
|
||||||
startedAt = ${this._startedAt}
|
|
||||||
pausedAt = ${this._pausedAt}
|
|
||||||
pausedInterval = ${this._pausedInterval}
|
|
||||||
pausedTotal = ${this._pausedTotal}
|
|
||||||
|
|
||||||
Socket
|
|
||||||
------------------------------
|
|
||||||
numClients = ${this._numClients}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Set onAir property of timer
|
* @description Set onAir property of timer
|
||||||
* @param {boolean} onAir - whether flag is active
|
* @param {boolean} onAir - whether flag is active
|
||||||
@@ -1241,7 +1220,7 @@ export class EventTimer extends Timer {
|
|||||||
// nothing to play, unload
|
// nothing to play, unload
|
||||||
if (nowIndex === null && nextIndex === null) {
|
if (nowIndex === null && nextIndex === null) {
|
||||||
this.unload();
|
this.unload();
|
||||||
console.log('Roll: no events found');
|
this.warning('SERVER', 'Roll: no events found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,8 +1246,9 @@ export class EventTimer extends Timer {
|
|||||||
// Set running timers
|
// Set running timers
|
||||||
if (nowIndex === null) {
|
if (nowIndex === null) {
|
||||||
// only warn the first time
|
// only warn the first time
|
||||||
if (this.secondaryTimer === null)
|
if (this.secondaryTimer === null) {
|
||||||
console.log('Roll: waiting for event start');
|
this.info('SERVER', 'Roll: waiting for event start');
|
||||||
|
}
|
||||||
|
|
||||||
// reset running timer
|
// reset running timer
|
||||||
// ??? should this not have been reset?
|
// ??? should this not have been reset?
|
||||||
@@ -1319,9 +1299,6 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
// load into event
|
// load into event
|
||||||
this.rollLoad();
|
this.rollLoad();
|
||||||
|
|
||||||
// broadcast change
|
|
||||||
this.broadcastState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previous() {
|
previous() {
|
||||||
@@ -1338,7 +1315,7 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send OSC
|
// send OSC
|
||||||
this.osc.send(this.osc.implemented.previous);
|
this.sendOsc(this.osc.implemented.previous);
|
||||||
|
|
||||||
// change playstate
|
// change playstate
|
||||||
this.pause();
|
this.pause();
|
||||||
@@ -1364,7 +1341,7 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send OSC
|
// send OSC
|
||||||
this.osc.send(this.osc.implemented.next);
|
this.sendOsc(this.osc.implemented.next);
|
||||||
|
|
||||||
// change playstate
|
// change playstate
|
||||||
this.pause();
|
this.pause();
|
||||||
@@ -1399,9 +1376,94 @@ export class EventTimer extends Timer {
|
|||||||
this.pause();
|
this.pause();
|
||||||
|
|
||||||
// send OSC
|
// send OSC
|
||||||
this.osc.send(this.osc.implemented.reload);
|
this.sendOsc(this.osc.implemented.reload);
|
||||||
|
|
||||||
// reload data
|
// reload data
|
||||||
this.loadEvent(this.selectedEventIndex);
|
this.loadEvent(this.selectedEventIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****************************************************************************/
|
||||||
|
/**
|
||||||
|
* Logger logic
|
||||||
|
* -------------
|
||||||
|
*
|
||||||
|
* This should be separate of event timer, left here for convenience
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method, sends message and pushes into stack
|
||||||
|
* @param {string} level
|
||||||
|
* @param {string} origin
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
_push(level, origin, text) {
|
||||||
|
const m = {
|
||||||
|
id: generateId(),
|
||||||
|
level,
|
||||||
|
origin,
|
||||||
|
text,
|
||||||
|
time: stringFromMillis(this._getCurrentTime()),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.messageStack.unshift(m);
|
||||||
|
this.io.emit('logger', m);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'prod') {
|
||||||
|
console.log(`[${m.level}] \t ${m.origin} \t ${m.text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.messageStack.length > this.MAX_MESSAGES) {
|
||||||
|
this.messageStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with level LOG
|
||||||
|
* @param {string} origin
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
info(origin, text) {
|
||||||
|
this._push('INFO', origin, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with level WARN
|
||||||
|
* @param {string} origin
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
warning(origin, text) {
|
||||||
|
this._push('WARN', origin, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with level ERROR
|
||||||
|
* @param {string} origin
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
error(origin, text) {
|
||||||
|
this._push('ERROR', origin, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************/
|
||||||
|
/**
|
||||||
|
* Integrations
|
||||||
|
* -------------
|
||||||
|
*
|
||||||
|
* Code related to integrations
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls OSC send message and resolves reply to logger
|
||||||
|
* @param {string} message
|
||||||
|
* @param {any} [payload]
|
||||||
|
*/
|
||||||
|
async sendOsc(message, payload = undefined) {
|
||||||
|
// Todo: add disabled osc check
|
||||||
|
const reply = await this.osc.send(message, payload);
|
||||||
|
if (!reply.success) {
|
||||||
|
this.error('TX', reply.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-30
@@ -7,22 +7,11 @@
|
|||||||
import { stringFromMillis } from '../utils/time.js';
|
import { stringFromMillis } from '../utils/time.js';
|
||||||
|
|
||||||
export class Timer {
|
export class Timer {
|
||||||
clock = null;
|
constructor() {
|
||||||
duration = null;
|
this.clock = null;
|
||||||
current = null;
|
this._resetTimers(true);
|
||||||
timeTag = null;
|
this.state = 'stop';
|
||||||
secondaryTimer = null;
|
}
|
||||||
_secondaryTarget = null;
|
|
||||||
_finishAt = null;
|
|
||||||
_finishedAt = null;
|
|
||||||
_finishedFlag = false;
|
|
||||||
_startedAt = null;
|
|
||||||
_pausedAt = null;
|
|
||||||
_pausedInterval = null;
|
|
||||||
_pausedTotal = null;
|
|
||||||
state = 'stop';
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
// call setup separately
|
// call setup separately
|
||||||
setupWithSeconds(seconds, autoStart = false) {
|
setupWithSeconds(seconds, autoStart = false) {
|
||||||
@@ -74,11 +63,11 @@ export class Timer {
|
|||||||
if (this._startedAt != null) {
|
if (this._startedAt != null) {
|
||||||
// update current timer
|
// update current timer
|
||||||
this.current =
|
this.current =
|
||||||
this._startedAt
|
this._startedAt +
|
||||||
+ this.duration
|
this.duration +
|
||||||
+ this._pausedTotal
|
this._pausedTotal +
|
||||||
+ this._pausedInterval
|
this._pausedInterval -
|
||||||
- now;
|
now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable flag
|
// enable flag
|
||||||
@@ -92,13 +81,14 @@ export class Timer {
|
|||||||
if (checkFinish) {
|
if (checkFinish) {
|
||||||
// is event finished?
|
// is event finished?
|
||||||
const isTimeOver = this.current <= 0;
|
const isTimeOver = this.current <= 0;
|
||||||
const isUpdating = (this.state !== 'pause');
|
const isUpdating = this.state !== 'pause';
|
||||||
|
|
||||||
if (isTimeOver && isUpdating && this._finishedAt == null) {
|
if (isTimeOver && isUpdating && this._finishedAt == null) {
|
||||||
if (this._finishedAt === null) this._finishedAt = now;
|
if (this._finishedAt === null) this._finishedAt = now;
|
||||||
this._finishedFlag = true;
|
this._finishedFlag = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.timeTag = stringFromMillis(this.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
@@ -136,6 +126,7 @@ export class Timer {
|
|||||||
_resetTimers(total = false) {
|
_resetTimers(total = false) {
|
||||||
if (total) this.duration = null;
|
if (total) this.duration = null;
|
||||||
this.current = this.duration;
|
this.current = this.duration;
|
||||||
|
this.timeTag = null;
|
||||||
this.running = null;
|
this.running = null;
|
||||||
this.secondaryTimer = null;
|
this.secondaryTimer = null;
|
||||||
this._secondaryTarget = null;
|
this._secondaryTarget = null;
|
||||||
@@ -153,14 +144,11 @@ export class Timer {
|
|||||||
return this.duration - this.current;
|
return this.duration - this.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get time object
|
/**
|
||||||
getTimes(update = true) {
|
* Builds time object
|
||||||
// update timer
|
* @returns {{running: number, secondary: number, expectedFinish: number, durationSeconds: number, startedAt: null, clock: null}}
|
||||||
if (update) this.update();
|
*/
|
||||||
|
getTimeObject() {
|
||||||
// update timetag
|
|
||||||
this.timeTag = stringFromMillis(this.current);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clock: this.clock,
|
clock: this.clock,
|
||||||
running: Timer.toSeconds(this.current),
|
running: Timer.toSeconds(this.current),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility variable: 24 hour in milliseconds .
|
* Utility variable: 24 hour in milliseconds .
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -11,7 +10,8 @@ export const DAY_TO_MS = 86400000;
|
|||||||
* @param {number} end - When does the event end
|
* @param {number} end - When does the event end
|
||||||
* @returns {number} normalised time
|
* @returns {number} normalised time
|
||||||
*/
|
*/
|
||||||
export const normaliseEndTime = (start, end) => (end < start ? end + DAY_TO_MS : end);
|
export const normaliseEndTime = (start, end) =>
|
||||||
|
end < start ? end + DAY_TO_MS : end;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Sorts an array of objects by given property
|
* @description Sorts an array of objects by given property
|
||||||
@@ -36,7 +36,6 @@ export const sortArrayByProperty = (arr, property) => {
|
|||||||
export const replacePlaceholder = (str, values) => {
|
export const replacePlaceholder = (str, values) => {
|
||||||
for (let [k, v] of Object.entries(values)) {
|
for (let [k, v] of Object.entries(values)) {
|
||||||
str = str.replace(k, v);
|
str = str.replace(k, v);
|
||||||
console.log(k, v);
|
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
@@ -72,7 +71,10 @@ export const getSelectionByRoll = (arr, now) => {
|
|||||||
|
|
||||||
// exit early if we are past the events
|
// exit early if we are past the events
|
||||||
const lastEvent = orderedEvents[orderedEvents.length - 1];
|
const lastEvent = orderedEvents[orderedEvents.length - 1];
|
||||||
const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd);
|
const lastNormalEnd = normaliseEndTime(
|
||||||
|
lastEvent.timeStart,
|
||||||
|
lastEvent.timeEnd
|
||||||
|
);
|
||||||
if (now > lastNormalEnd) {
|
if (now > lastNormalEnd) {
|
||||||
return {
|
return {
|
||||||
nowIndex,
|
nowIndex,
|
||||||
@@ -128,7 +130,7 @@ export const getSelectionByRoll = (arr, now) => {
|
|||||||
// check how far the start is from now
|
// check how far the start is from now
|
||||||
const wait = e.timeStart - now;
|
const wait = e.timeStart - now;
|
||||||
|
|
||||||
if (nextIndex === null || wait < timeToNext) {
|
if (nextIndex == null || wait < timeToNext) {
|
||||||
timeToNext = wait;
|
timeToNext = wait;
|
||||||
nextIndex = arr.findIndex((a) => a.id === e.id);
|
nextIndex = arr.findIndex((a) => a.id === e.id);
|
||||||
}
|
}
|
||||||
@@ -161,8 +163,14 @@ export const getSelectionByRoll = (arr, now) => {
|
|||||||
* @returns {object} object with selection variables
|
* @returns {object} object with selection variables
|
||||||
*/
|
*/
|
||||||
export const updateRoll = (currentTimers) => {
|
export const updateRoll = (currentTimers) => {
|
||||||
|
const {
|
||||||
const {selectedEventId,current,_finishAt,clock,secondaryTimer,_secondaryTarget} = currentTimers;
|
selectedEventId,
|
||||||
|
current,
|
||||||
|
_finishAt,
|
||||||
|
clock,
|
||||||
|
secondaryTimer,
|
||||||
|
_secondaryTarget,
|
||||||
|
} = currentTimers;
|
||||||
|
|
||||||
// timers
|
// timers
|
||||||
let updatedTimer = current;
|
let updatedTimer = current;
|
||||||
@@ -181,8 +189,6 @@ export const updateRoll = (currentTimers) => {
|
|||||||
if (updatedTimer < 0) {
|
if (updatedTimer < 0) {
|
||||||
isFinished = true;
|
isFinished = true;
|
||||||
}
|
}
|
||||||
console.log(updatedTimer, isFinished, _finishAt)
|
|
||||||
|
|
||||||
} else if (secondaryTimer >= 0) {
|
} else if (secondaryTimer >= 0) {
|
||||||
// if secondaryTimer is running we are in waiting to roll
|
// if secondaryTimer is running we are in waiting to roll
|
||||||
|
|
||||||
@@ -202,6 +208,5 @@ export const updateRoll = (currentTimers) => {
|
|||||||
doRollLoad = true;
|
doRollLoad = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished};
|
return { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished };
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
/** Class contains logic towards outgoing OSC communications. */
|
/** Class contains logic towards outgoing OSC communications. */
|
||||||
import {Client, Message} from 'node-osc';
|
import { Client, Message } from 'node-osc';
|
||||||
|
|
||||||
export class OSCIntegration {
|
export class OSCIntegration {
|
||||||
|
|
||||||
ADDRESS = '/ontime';
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// OSC Client
|
// OSC Client
|
||||||
|
this.ADDRESS = '/ontime';
|
||||||
this.oscClient = null;
|
this.oscClient = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,8 +24,8 @@ export class OSCIntegration {
|
|||||||
time: 'time',
|
time: 'time',
|
||||||
overtime: 'overtime',
|
overtime: 'overtime',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
presenter:'presenter',
|
presenter: 'presenter',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,13 +35,19 @@ export class OSCIntegration {
|
|||||||
* @param {number} oscConfig.port - OSC Destination Port
|
* @param {number} oscConfig.port - OSC Destination Port
|
||||||
*/
|
*/
|
||||||
init(oscConfig) {
|
init(oscConfig) {
|
||||||
const {ip, port} = oscConfig;
|
const { ip, port } = oscConfig;
|
||||||
try {
|
try {
|
||||||
this.oscClient = new Client(ip, port);
|
this.oscClient = new Client(ip, port);
|
||||||
console.log(`Initialised OSC Client at ${ip}:${port}`);
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Initialised OSC Client at ${ip}:${port}`,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.oscClient = null;
|
this.oscClient = null;
|
||||||
console.log(`Failed initialising OSC Client: ${error}`);
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Failed initialising OSC Client: ${error}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,14 +57,21 @@ export class OSCIntegration {
|
|||||||
* @param {string} [payload] - optional payload required in some message types
|
* @param {string} [payload] - optional payload required in some message types
|
||||||
*/
|
*/
|
||||||
async send(messageType, payload) {
|
async send(messageType, payload) {
|
||||||
|
const reply = {
|
||||||
|
success: true,
|
||||||
|
message: 'OSC Message sent',
|
||||||
|
};
|
||||||
|
|
||||||
if (this.oscClient == null) {
|
if (this.oscClient == null) {
|
||||||
console.log('OSC ERROR: Client not initialised');
|
reply.success = false;
|
||||||
return;
|
reply.message = 'Client not initialised';
|
||||||
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageType == null) {
|
if (messageType == null) {
|
||||||
console.log('OSC ERROR: Message undefined');
|
reply.success = false;
|
||||||
return;
|
reply.message = 'Message undefined';
|
||||||
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only specify special cases
|
// only specify special cases
|
||||||
@@ -68,35 +79,58 @@ export class OSCIntegration {
|
|||||||
case 'overtime':
|
case 'overtime':
|
||||||
// Whether timer is negative
|
// Whether timer is negative
|
||||||
this.oscClient.send(`${this.ADDRESS}/overtime`, payload, (err) => {
|
this.oscClient.send(`${this.ADDRESS}/overtime`, payload, (err) => {
|
||||||
if (err) console.error(err);
|
if (err) {
|
||||||
|
reply.success = false;
|
||||||
|
reply.message = err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'title':
|
case 'title':
|
||||||
if (payload != null && payload !== "") {
|
if (payload != null && payload !== '') {
|
||||||
// Send Title of current event
|
// Send Title of current event
|
||||||
this.oscClient.send(`${this.ADDRESS}/title`, payload, (err) => {
|
this.oscClient.send(`${this.ADDRESS}/title`, payload, (err) => {
|
||||||
if (err) console.error(err);
|
if (err) {
|
||||||
|
reply.success = false;
|
||||||
|
reply.message = err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
reply.success = false;
|
||||||
|
reply.message = 'Missing message data';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'presenter':
|
case 'presenter':
|
||||||
if (payload != null && payload !== "") {
|
if (payload != null && payload !== '') {
|
||||||
// Send presenter data on current event
|
// Send presenter data on current event
|
||||||
this.oscClient.send(`${this.ADDRESS}/presenter`, payload, (err) => {
|
this.oscClient.send(`${this.ADDRESS}/presenter`, payload, (err) => {
|
||||||
if (err) console.error(err);
|
if (err) {
|
||||||
|
reply.success = false;
|
||||||
|
reply.message = err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
reply.success = false;
|
||||||
|
reply.message = 'Missing message data';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// catch all for messages, allows to add new messages
|
// catch all for messages, allows to add new messages
|
||||||
// but should be used with the integrations definition
|
// but should be used with the integrations definition
|
||||||
const message = new Message(`${this.ADDRESS}/${messageType}`)
|
// eslint-disable-next-line no-case-declarations
|
||||||
if (payload != null) message.append(payload)
|
const message = new Message(`${this.ADDRESS}/${messageType}`);
|
||||||
|
if (payload != null) message.append(payload);
|
||||||
this.oscClient.send(message, (err) => {
|
this.oscClient.send(message, (err) => {
|
||||||
if (err) console.error(err);
|
if (err) {
|
||||||
|
reply.success = false;
|
||||||
|
reply.message = err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown() {
|
shutdown() {
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ export const shutdownOSCServer = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initiateOSC = (config) => {
|
export const initiateOSC = (config) => {
|
||||||
oscServer = new Server(config.port, '0.0.0.0', () => {
|
oscServer = new Server(config.port, '0.0.0.0');
|
||||||
console.log(`OSC Server is listening on port ${config.port}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// error
|
// error
|
||||||
oscServer.on('error', console.error);
|
oscServer.on('error', console.error);
|
||||||
@@ -19,7 +17,6 @@ export const initiateOSC = (config) => {
|
|||||||
// ontime: fixed message for app
|
// ontime: fixed message for app
|
||||||
// path: command to be called
|
// path: command to be called
|
||||||
// args: extra data, only used on some of the API entries (delay, goto)
|
// args: extra data, only used on some of the API entries (delay, goto)
|
||||||
console.log('OSC received', msg);
|
|
||||||
|
|
||||||
// split message
|
// split message
|
||||||
const [, address, path] = msg[0].split('/');
|
const [, address, path] = msg[0].split('/');
|
||||||
@@ -46,40 +43,35 @@ export const initiateOSC = (config) => {
|
|||||||
break;
|
break;
|
||||||
case 'start':
|
case 'start':
|
||||||
case 'play':
|
case 'play':
|
||||||
console.log('calling play');
|
|
||||||
global.timer.trigger('start');
|
global.timer.trigger('start');
|
||||||
break;
|
break;
|
||||||
case 'pause':
|
case 'pause':
|
||||||
console.log('calling pause');
|
|
||||||
global.timer.trigger('pause');
|
global.timer.trigger('pause');
|
||||||
break;
|
break;
|
||||||
case 'prev':
|
case 'prev':
|
||||||
console.log('calling prev');
|
|
||||||
global.timer.trigger('previous');
|
global.timer.trigger('previous');
|
||||||
break;
|
break;
|
||||||
case 'next':
|
case 'next':
|
||||||
console.log('calling next');
|
|
||||||
global.timer.trigger('next');
|
global.timer.trigger('next');
|
||||||
break;
|
break;
|
||||||
case 'unload':
|
case 'unload':
|
||||||
case 'stop':
|
case 'stop':
|
||||||
console.log('calling unload');
|
|
||||||
global.timer.trigger('unload');
|
global.timer.trigger('unload');
|
||||||
break;
|
break;
|
||||||
case 'reload':
|
case 'reload':
|
||||||
console.log('calling reload');
|
|
||||||
global.timer.trigger('reload');
|
global.timer.trigger('reload');
|
||||||
break;
|
break;
|
||||||
case 'roll':
|
case 'roll':
|
||||||
console.log('calling roll');
|
|
||||||
global.timer.trigger('roll');
|
global.timer.trigger('roll');
|
||||||
break;
|
break;
|
||||||
case 'delay':
|
case 'delay':
|
||||||
console.log('calling delay with', args);
|
|
||||||
try {
|
try {
|
||||||
const t = parseInt(args);
|
const t = parseInt(args);
|
||||||
if (isNaN(t)) {
|
if (isNaN(t)) {
|
||||||
console.error(`OSC IN: delay time not recognised ${args}`);
|
global.timer.error(
|
||||||
|
'RX',
|
||||||
|
`OSC IN: delay time not recognised ${args}`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
global.timer.increment(t * 1000 * 60);
|
global.timer.increment(t * 1000 * 60);
|
||||||
@@ -88,36 +80,37 @@ export const initiateOSC = (config) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'goto':
|
case 'goto':
|
||||||
console.log('calling goto with', args);
|
|
||||||
try {
|
try {
|
||||||
const eventIndex = parseInt(args);
|
const eventIndex = parseInt(args);
|
||||||
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null) {
|
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null) {
|
||||||
console.error(
|
global.timer.error(
|
||||||
|
'RX',
|
||||||
`OSC IN: event index not recognised or out of range ${eventIndex}`
|
`OSC IN: event index not recognised or out of range ${eventIndex}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
global.timer.loadEventByIndex(eventIndex - 1);
|
global.timer.loadEventByIndex(eventIndex - 1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error calling goto: ', error);
|
global.timer.error('RX', `OSC IN: error calling goto ${error}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'gotoid':
|
case 'gotoid':
|
||||||
console.log('calling gotoid with', args);
|
console.log('calling gotoid with', args);
|
||||||
if (args == null) {
|
if (args == null) {
|
||||||
console.error(
|
global.timer.error(
|
||||||
`OSC IN: event id not recognised or out of range ${args}`
|
'RX',
|
||||||
|
`OSC IN: event id not recognised or out of range ${args}}`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
global.timer.loadEventById(args.toString().toLowerCase());
|
global.timer.loadEventById(args.toString().toLowerCase());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error calling goto: ', error);
|
global.timer.error('RX', `OSC IN: error calling goto ${error}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log(`Error: unhandled message ${path}`);
|
global.timer.warning('RX', `OSC IN: unhandled message ${path}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,5 @@ export const postEvent = async (req, res) => {
|
|||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ export const postInfo = async (req, res) => {
|
|||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,69 +7,59 @@ export const pbGet = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/onAir'
|
// Create controller for GET request to '/playback/onAir'
|
||||||
// Turns onAir flag to true
|
// Turns onAir flag to true
|
||||||
export const onAir = async (req, res) => {
|
export const onAir = async (req, res) => {
|
||||||
console.log('Setting onAir to true');
|
|
||||||
global.timer.trigger('onAir') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('onAir') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/onAir'
|
// Create controller for GET request to '/playback/onAir'
|
||||||
// Turns onAir flag to true
|
// Turns onAir flag to true
|
||||||
export const offAir = async (req, res) => {
|
export const offAir = async (req, res) => {
|
||||||
console.log('Setting onAir to false');
|
|
||||||
global.timer.trigger('offAir') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('offAir') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/start'
|
// Create controller for GET request to '/playback/start'
|
||||||
// Starts timer object
|
// Starts timer object
|
||||||
export const pbStart = async (req, res) => {
|
export const pbStart = async (req, res) => {
|
||||||
console.log('Calling start');
|
|
||||||
global.timer.trigger('start') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('start') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/pause'
|
// Create controller for GET request to '/playback/pause'
|
||||||
// Pauses timer object
|
// Pauses timer object
|
||||||
export const pbPause = async (req, res) => {
|
export const pbPause = async (req, res) => {
|
||||||
console.log('Calling pause');
|
|
||||||
global.timer.trigger('pause') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('pause') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/stop'
|
// Create controller for GET request to '/playback/stop'
|
||||||
// Stops timer object
|
// Stops timer object
|
||||||
export const pbStop = async (req, res) => {
|
export const pbStop = async (req, res) => {
|
||||||
console.log('Calling stop');
|
|
||||||
global.timer.trigger('stop') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('stop') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/roll'
|
// Create controller for GET request to '/playback/roll'
|
||||||
// Sets timer object to roll mode
|
// Sets timer object to roll mode
|
||||||
export const pbRoll = async (req, res) => {
|
export const pbRoll = async (req, res) => {
|
||||||
console.log('Calling roll');
|
|
||||||
global.timer.trigger('roll') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('roll') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/previous'
|
// Create controller for GET request to '/playback/previous'
|
||||||
// Sets timer object to roll mode
|
// Sets timer object to roll mode
|
||||||
export const pbPrevious = async (req, res) => {
|
export const pbPrevious = async (req, res) => {
|
||||||
console.log('Calling previous');
|
|
||||||
global.timer.trigger('previous') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('previous') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/next'
|
// Create controller for GET request to '/playback/next'
|
||||||
// Sets timer object to roll mode
|
// Sets timer object to roll mode
|
||||||
export const pbNext = async (req, res) => {
|
export const pbNext = async (req, res) => {
|
||||||
console.log('Calling next');
|
|
||||||
global.timer.trigger('next') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('next') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/unload'
|
// Create controller for GET request to '/playback/unload'
|
||||||
// Unloads any events
|
// Unloads any events
|
||||||
export const pbUnload = async (req, res) => {
|
export const pbUnload = async (req, res) => {
|
||||||
console.log('Calling unload');
|
|
||||||
global.timer.trigger('unload') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('unload') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/playback/reload'
|
// Create controller for GET request to '/playback/reload'
|
||||||
// Reloads current event
|
// Reloads current event
|
||||||
export const pbReload = async (req, res) => {
|
export const pbReload = async (req, res) => {
|
||||||
console.log('Calling reload');
|
|
||||||
global.timer.trigger('reload') ? res.sendStatus(200) : res.sendStatus(400);
|
global.timer.trigger('reload') ? res.sendStatus(200) : res.sendStatus(400);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "~1.19.0",
|
"body-parser": "~1.19.0",
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import getRandomName from '../getRandomName.js';
|
||||||
|
|
||||||
|
test('generates 500 unique names', () => {
|
||||||
|
let names = [];
|
||||||
|
for (let i = 0; i < 500; i++) {
|
||||||
|
names.push(getRandomName());
|
||||||
|
}
|
||||||
|
|
||||||
|
const unique = [...new Set(names)];
|
||||||
|
expect(names.length).toBe(unique.length);
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,26 @@ const mts = 1000; // millis to seconds
|
|||||||
const mtm = 1000 * 60; // millis to minutes
|
const mtm = 1000 * 60; // millis to minutes
|
||||||
const mth = 1000 * 60 * 60; // millis to hours
|
const mth = 1000 * 60 * 60; // millis to hours
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current time in milliseconds
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export const nowInMillis = () => {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// extract milliseconds since midnight
|
||||||
|
let elapsed = now.getHours() * 3600000;
|
||||||
|
elapsed += now.getMinutes() * 60000;
|
||||||
|
elapsed += now.getSeconds() * 1000;
|
||||||
|
elapsed += now.getMilliseconds();
|
||||||
|
|
||||||
|
return elapsed;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Converts milliseconds to string representing time
|
* @description Converts milliseconds to string representing time
|
||||||
* @param {number} ms - time in milliseconds
|
* @param {number} ms - time in milliseconds
|
||||||
* @param {boolean} showSeconds - wether to show the seconds
|
* @param {boolean} showSeconds - weather to show the seconds
|
||||||
* @param {string} delim - character between HH MM SS
|
* @param {string} delim - character between HH MM SS
|
||||||
* @param {string} ifNull - what to return if value is null
|
* @param {string} ifNull - what to return if value is null
|
||||||
* @returns {string} String representing time 00:12:02
|
* @returns {string} String representing time 00:12:02
|
||||||
@@ -17,7 +33,7 @@ export const stringFromMillis = (
|
|||||||
delim = ':',
|
delim = ':',
|
||||||
ifNull = '...'
|
ifNull = '...'
|
||||||
) => {
|
) => {
|
||||||
if (ms === null || isNaN(ms)) return ifNull;
|
if (ms == null || isNaN(ms)) return ifNull;
|
||||||
const isNegative = ms < 0 ? '-' : '';
|
const isNegative = ms < 0 ? '-' : '';
|
||||||
const millis = Math.abs(ms);
|
const millis = Math.abs(ms);
|
||||||
|
|
||||||
@@ -36,7 +52,7 @@ export const stringFromMillis = (
|
|||||||
/**
|
/**
|
||||||
* @description Converts an excel date to milliseconds
|
* @description Converts an excel date to milliseconds
|
||||||
* @argument {string} excelDate - excel string date
|
* @argument {string} excelDate - excel string date
|
||||||
* @returns {number} - time in millisenconds
|
* @returns {number} - time in milliseconds
|
||||||
*/
|
*/
|
||||||
export const excelDateStringToMillis = (excelDate) => {
|
export const excelDateStringToMillis = (excelDate) => {
|
||||||
const date = new Date(excelDate);
|
const date = new Date(excelDate);
|
||||||
@@ -47,5 +63,5 @@ export const excelDateStringToMillis = (excelDate) => {
|
|||||||
|
|
||||||
return h * mth + m * mtm + s * mts;
|
return h * mth + m * mtm + s * mts;
|
||||||
}
|
}
|
||||||
return null;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user