diff --git a/client/.eslintrc b/client/.eslintrc
index 93abcf088..5a459fde9 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -5,6 +5,8 @@
],
"plugins": ["react", "testing-library", "jest"],
"rules": {
- "jest/no-mocks-import": "warn"
+ "jest/no-mocks-import": "warn",
+ "no-useless-concat": "warn",
+ "prefer-template": "warn"
}
}
diff --git a/client/src/_main.scss b/client/src/_main.scss
new file mode 100644
index 000000000..83da3237e
--- /dev/null
+++ b/client/src/_main.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/client/src/app/api/ontimeApi.js b/client/src/app/api/ontimeApi.js
index 1d736dfb8..91fdce1f5 100644
--- a/client/src/app/api/ontimeApi.js
+++ b/client/src/app/api/ontimeApi.js
@@ -75,28 +75,26 @@ export const ontimeVars = [
];
export const getInfo = async () => {
- const res = await axios.get(ontimeURL + '/info');
+ const res = await axios.get(`${ontimeURL}/info`);
return res.data;
};
export const postInfo = async (data) => {
- const res = await axios.post(ontimeURL + '/info', data);
- return res;
+ return await axios.post(`${ontimeURL}/info`, data);
};
export const getOSC = async () => {
- const res = await axios.get(ontimeURL + '/osc');
+ const res = await axios.get(`${ontimeURL}/osc`);
return res.data;
};
export const postOSC = async (data) => {
- const res = await axios.post(ontimeURL + '/osc', data);
- return res;
+ return await axios.post(`${ontimeURL}/osc`, data);
};
export const downloadEvents = async () => {
await axios({
- url: ontimeURL + '/db',
+ url: `${ontimeURL}/db`,
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
@@ -123,15 +121,13 @@ export const uploadEvents = async (file) => {
const formData = new FormData();
formData.append('userFile', file); // appending file
await axios
- .post(ontimeURL + '/db', formData, {
+ .post(`${ontimeURL}/db`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
- })
- .then((res) => console.log(res.data))
- .catch((err) => console.error(err));
+ });
};
export const uploadEventsWithPath = async (filepath) => {
- await axios.post(ontimeURL + '/dbpath', { path: filepath });
+ await axios.post(`${ontimeURL}/dbpath`, { path: filepath });
};
diff --git a/client/src/app/context/LoggingContext.js b/client/src/app/context/LoggingContext.js
new file mode 100644
index 000000000..6f85ee39c
--- /dev/null
+++ b/client/src/app/context/LoggingContext.js
@@ -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 (
+
+ {props.children}
+
+ )
+}
\ No newline at end of file
diff --git a/client/src/common/components/collapseBar/CollapseBar.jsx b/client/src/common/components/collapseBar/CollapseBar.jsx
new file mode 100644
index 000000000..ed0126d2c
--- /dev/null
+++ b/client/src/common/components/collapseBar/CollapseBar.jsx
@@ -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(
+
+ {title}
+
+
+ )
+}
+CollapseBar.propTypes = {
+ title: PropTypes.string,
+ isCollapsed: PropTypes.bool,
+ onClick: PropTypes.func,
+}
\ No newline at end of file
diff --git a/client/src/common/components/collapseBar/CollapseBar.module.scss b/client/src/common/components/collapseBar/CollapseBar.module.scss
new file mode 100644
index 000000000..43fbb7a87
--- /dev/null
+++ b/client/src/common/components/collapseBar/CollapseBar.module.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/client/src/common/components/errorBoundary/ErrorBoundary.jsx b/client/src/common/components/errorBoundary/ErrorBoundary.jsx
index c97221ee1..d8662eac9 100644
--- a/client/src/common/components/errorBoundary/ErrorBoundary.jsx
+++ b/client/src/common/components/errorBoundary/ErrorBoundary.jsx
@@ -1,6 +1,9 @@
import React from 'react';
+import { LoggingContext } from '../../../app/context/LoggingContext';
class ErrorBoundary extends React.Component {
+ static contextType = LoggingContext;
+
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
@@ -17,15 +20,11 @@ class ErrorBoundary extends React.Component {
errorInfo: info,
});
// 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() {
if (this.state.errorMessage) {
- // You can render any custom fallback UI
return :/
;
}
return this.props.children;
diff --git a/client/src/common/components/eventTimes/EventTimes.jsx b/client/src/common/components/eventTimes/EventTimes.jsx
index 0b7b66fd1..cfc0d9300 100644
--- a/client/src/common/components/eventTimes/EventTimes.jsx
+++ b/client/src/common/components/eventTimes/EventTimes.jsx
@@ -1,23 +1,26 @@
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) {
const { actionHandler, delay, timeStart, timeEnd } = props;
+ const { emitWarning } = useContext(LoggingContext);
const handleValidate = (entry, v) => {
- // we dont inforce validation here
+ // we dont enforce validation here
if (v == null || timeStart == null || timeEnd == null) return true;
if (timeStart === 0) return true;
let validate = { value: true, catch: '' };
- if (entry === 'timeStart' && v > timeEnd)
+ if (entry === 'timeStart' && v > timeEnd) {
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';
+ }
if (validate.catch !== '')
- showWarningToast('Time Input Warning', validate.catch);
+ emitWarning(`Time Input Warning: ${validate.catch}`);
return validate.value;
};
diff --git a/client/src/common/components/eventTimes/EventTimesVertical.jsx b/client/src/common/components/eventTimes/EventTimesVertical.jsx
index 3e0ea8357..7a796c294 100644
--- a/client/src/common/components/eventTimes/EventTimesVertical.jsx
+++ b/client/src/common/components/eventTimes/EventTimesVertical.jsx
@@ -1,6 +1,7 @@
import EditableTimer from 'common/input/EditableTimer';
-import { showWarningToast } from 'common/helpers/toastManager';
-import { stringFromMillis } from 'common/utils/dateConfig';
+import { stringFromMillis } from 'ontime-server/utils/time';
+import { useContext } from 'react';
+import { LoggingContext } from '../../../app/context/LoggingContext';
const label = {
fontSize: '0.75em',
@@ -83,6 +84,8 @@ const Times = (props) => {
export default function EventTimesVertical(props) {
const { delay, timeStart, timeEnd, duration } = props;
+ const { emitWarning } = useContext(LoggingContext);
+
const handleValidate = (entry, v) => {
// we dont enforce validation here
@@ -90,32 +93,36 @@ export default function EventTimesVertical(props) {
if (timeStart === 0) return true;
let validate = { value: true, catch: '' };
- if (entry === 'timeStart' && v > timeEnd)
+ if (entry === 'timeStart' && v > timeEnd) {
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';
+ }
- if (validate.catch !== '')
- showWarningToast('Time Input Warning', validate.catch);
+ if (validate.catch !== '') {
+ emitWarning(`Time Input Warning: ${validate.catch}`);
+ }
return validate.value;
};
- return (delay != null) & (delay > 0) ? (
-
- ) : (
-
- );
+ return (
+ (delay != null) && (delay > 0) ? (
+
+ ) : (
+
+ )
+ )
}
diff --git a/client/src/common/components/views/TodayItem.jsx b/client/src/common/components/views/TodayItem.jsx
index 73da1d3f1..5a7ca28df 100644
--- a/client/src/common/components/views/TodayItem.jsx
+++ b/client/src/common/components/views/TodayItem.jsx
@@ -1,4 +1,4 @@
-import { stringFromMillis } from 'common/utils/dateConfig';
+import { stringFromMillis } from 'ontime-server/utils/time';
import style from './Paginator.module.css';
export default function TodayItem(props) {
const { selected, timeStart, timeEnd, title, backstageEvent } = props;
@@ -19,7 +19,7 @@ export default function TodayItem(props) {
}`}
>{`${start} ยท ${end}`}
{title}
- {backstageEvent && }
+ {backstageEvent && }
);
}
diff --git a/client/src/common/helpers/toastManager.jsx b/client/src/common/helpers/toastManager.jsx
deleted file mode 100644
index 1f686a8ac..000000000
--- a/client/src/common/helpers/toastManager.jsx
+++ /dev/null
@@ -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,
- });
-};
diff --git a/client/src/common/input/EditableTimer.jsx b/client/src/common/input/EditableTimer.jsx
index d3962c59c..c7f6c5c14 100644
--- a/client/src/common/input/EditableTimer.jsx
+++ b/client/src/common/input/EditableTimer.jsx
@@ -1,15 +1,16 @@
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
-import { useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import {
isTimeString,
- stringFromMillis,
timeStringToMillis,
} from '../utils/dateConfig';
-import { showErrorToast } from '../helpers/toastManager';
+import { stringFromMillis } from 'ontime-server/utils/time';
import style from './EditableTimer.module.css';
+import { LoggingContext } from '../../app/context/LoggingContext';
export default function EditableTimer(props) {
const { name, actionHandler, time, delay, validate } = props;
+ const { emitError } = useContext(LoggingContext);
const [value, setValue] = useState('');
// prepare time fields
@@ -18,9 +19,9 @@ export default function EditableTimer(props) {
try {
setValue(stringFromMillis(time + delay));
} catch (error) {
- showErrorToast('Error parsing date', error.text);
+ emitError(`Unable to parse date: ${error.text}`);
}
- }, [time, delay]);
+ }, [time, delay, emitError]);
const validateValue = (value) => {
const success = handleSubmit(value);
diff --git a/client/src/common/utils/dateConfig.js b/client/src/common/utils/dateConfig.js
index 3bb3af543..171729cd8 100644
--- a/client/src/common/utils/dateConfig.js
+++ b/client/src/common/utils/dateConfig.js
@@ -4,37 +4,7 @@ export const timeFormatSeconds = 'HH:mm:ss';
const mts = 1000; // millis to seconds
const mtm = 1000 * 60; // millis to minutes
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)
@@ -79,7 +49,7 @@ export const millisToMinutes = (millis) => {
};
/**
- * @description Converts timestring to milliseconds
+ * @description Converts timestring to milliseconds
* @param {string} string - time string "23:00:12"
* @returns {number} Amount in milliseconds
*/
diff --git a/client/src/common/utils/eventsManager.js b/client/src/common/utils/eventsManager.js
index b00db9df4..5e861e9ff 100644
--- a/client/src/common/utils/eventsManager.js
+++ b/client/src/common/utils/eventsManager.js
@@ -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
* @param {Object[]} events - given events
* @returns {Object[]} Filtered events with calculated delays
*/
-import {stringFromMillis} from "./dateConfig";
-
export const getEventsWithDelay = (events) => {
if (events == null) return [];
diff --git a/client/src/features/control/PlaybackTimer.jsx b/client/src/features/control/PlaybackTimer.jsx
index 5aad78e3d..635646ecb 100644
--- a/client/src/features/control/PlaybackTimer.jsx
+++ b/client/src/features/control/PlaybackTimer.jsx
@@ -1,6 +1,6 @@
import style from './PlaybackControl.module.scss';
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 {Button} from '@chakra-ui/button';
import {memo} from 'react';
diff --git a/client/src/features/editors/Editor.jsx b/client/src/features/editors/Editor.jsx
index 27047811a..8ce7224f4 100644
--- a/client/src/features/editors/Editor.jsx
+++ b/client/src/features/editors/Editor.jsx
@@ -1,10 +1,11 @@
import { lazy, useEffect } from 'react';
import { Box } from '@chakra-ui/layout';
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 ModalManager from 'features/modals/ModalManager';
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
+import { LoggingProvider } from '../../app/context/LoggingContext';
const EventListWrapper = lazy(() =>
import('features/editors/list/EventListWrapper')
@@ -22,7 +23,7 @@ export default function Editor() {
}, []);
return (
- <>
+
@@ -68,6 +69,6 @@ export default function Editor() {
- >
+
);
}
diff --git a/client/src/features/editors/Editor.module.css b/client/src/features/editors/Editor.module.scss
similarity index 91%
rename from client/src/features/editors/Editor.module.css
rename to client/src/features/editors/Editor.module.scss
index ec991e8ed..51c39b1ed 100644
--- a/client/src/features/editors/Editor.module.css
+++ b/client/src/features/editors/Editor.module.scss
@@ -8,7 +8,7 @@
display: grid;
grid-template-rows: auto 1fr;
- grid-template-columns: 40px 48em auto auto;
+ grid-template-columns: 40px 48em 31em auto;
grid-template-areas:
'sett even play info'
'sett even mess info';
@@ -110,12 +110,23 @@ h1 {
.editor {
grid-area: even;
+
+ .content {
+ height: calc(100% - 3em);
+ overflow: hidden;
+ }
}
.info {
grid-area: info;
min-width: 17em;
- max-width: 32em;
+
+ .content {
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 3em);
+ overflow: hidden;
+ }
}
.messages {
diff --git a/client/src/features/editors/list/EventList.jsx b/client/src/features/editors/list/EventList.jsx
index 9e901f729..8c535ddb8 100644
--- a/client/src/features/editors/list/EventList.jsx
+++ b/client/src/features/editors/list/EventList.jsx
@@ -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 { useSocket } from 'app/context/socketContext';
import Empty from 'common/state/Empty';
diff --git a/client/src/features/editors/list/EventListItem.jsx b/client/src/features/editors/list/EventListItem.jsx
index df8ad7d44..f4c5d5a98 100644
--- a/client/src/features/editors/list/EventListItem.jsx
+++ b/client/src/features/editors/list/EventListItem.jsx
@@ -1,8 +1,8 @@
import DelayBlock from './DelayBlock';
import BlockBlock from './BlockBlock';
import EventBlock from './EventBlock';
-import { showErrorToast } from 'common/helpers/toastManager';
-import { memo } from 'react';
+import { memo, useContext } from 'react';
+import { LoggingContext } from '../../../app/context/LoggingContext';
const areEqual = (prevProps, nextProps) => {
return (
@@ -26,6 +26,7 @@ const EventListItem = (props) => {
delay,
...rest
} = props;
+ const { emitError } = useContext(LoggingContext);
// Create / delete new events
const actionHandler = (action, payload) => {
@@ -59,7 +60,7 @@ const EventListItem = (props) => {
// request update in parent
eventsHandler('patch', newData);
} else {
- showErrorToast('Field Error: ' + field);
+ emitError(`Unknown field: ${field}`);
}
break;
default:
diff --git a/client/src/features/editors/list/EventListWrapper.jsx b/client/src/features/editors/list/EventListWrapper.jsx
index 86befdc66..562e563e7 100644
--- a/client/src/features/editors/list/EventListWrapper.jsx
+++ b/client/src/features/editors/list/EventListWrapper.jsx
@@ -1,5 +1,5 @@
import { useMutation, useQueryClient } from 'react-query';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useContext, useEffect, useState } from 'react';
import {
fetchAllEvents,
requestPatch,
@@ -12,16 +12,17 @@ import {
} from 'app/api/eventsApi.js';
import EventList from './EventList';
import EventListMenu from 'features/menu/EventListMenu.jsx';
-import { showErrorToast } from 'common/helpers/toastManager';
import { useFetch } from 'app/hooks/useFetch.js';
import Empty from 'common/state/Empty';
import { EVENTS_TABLE } from 'app/api/apiConstants';
import { BatchOperation } from 'app/context/collapseAtom';
import { useAtom } from 'jotai';
+import { LoggingContext } from '../../../app/context/LoggingContext';
export default function EventListWrapper() {
const [, setCollapsed] = useAtom(BatchOperation);
const queryClient = useQueryClient();
+ const { emitError } = useContext(LoggingContext);
const { data, status, isError, refetch } = useFetch(
EVENTS_TABLE,
fetchAllEvents
@@ -230,9 +231,9 @@ export default function EventListWrapper() {
// Show toasts on errors
useEffect(() => {
if (isError) {
- showErrorToast('Error fetching data');
+ emitError('Error fetching data');
}
- }, [isError]);
+ }, [emitError, isError]);
// Events API
const eventsHandler = useCallback(
@@ -242,35 +243,35 @@ export default function EventListWrapper() {
try {
await addEvent.mutateAsync(payload);
} catch (error) {
- showErrorToast('Error creating event', error.message);
+ emitError(`Error fetching data: ${error.message}`);
}
break;
case 'update':
try {
await updateEvent.mutateAsync(payload);
} catch (error) {
- showErrorToast('Error updating event', error.message);
+ emitError(`Error updating event: ${error.message}`);
}
break;
case 'patch':
try {
await patchEvent.mutateAsync(payload);
} catch (error) {
- showErrorToast('Error updating event', error.message);
+ emitError(`Error updating event: ${error.message}`);
}
break;
case 'delete':
try {
await deleteEvent.mutateAsync(payload);
} catch (error) {
- showErrorToast('Error deleting event', error.message);
+ emitError(`Error deleting event: ${error.message}`);
}
break;
case 'reorder':
try {
await reorderEvent.mutateAsync(payload);
} catch (error) {
- showErrorToast('Error reordering event', error.message);
+ emitError(`Error re-ordering event: ${error.message}`);
}
break;
case 'applyDelay':
@@ -293,13 +294,13 @@ export default function EventListWrapper() {
// delete block after, if any
if (blockAfter) await deleteEvent.mutateAsync(blockAfter);
} catch (error) {
- showErrorToast('Error applying delay', error.message);
+ emitError(`Error applying delay: ${error.message}`);
}
} else {
try {
await applyDelay.mutateAsync(payload.id);
} catch (error) {
- showErrorToast('Error applying delay', error.message);
+ emitError(`Error applying delay: ${error.message}`);
}
}
break;
@@ -317,11 +318,11 @@ export default function EventListWrapper() {
try {
await deleteAllEvents.mutateAsync();
} catch (error) {
- showErrorToast('Error deleting events', error.message);
+ emitError(`Error deleting events: ${error.message}`);
}
break;
default:
- showErrorToast('Unrecognised request', action);
+ emitError(`Unhandled request: ${action}`);
break;
}
},
diff --git a/client/src/features/editors/list/List.module.css b/client/src/features/editors/list/List.module.scss
similarity index 96%
rename from client/src/features/editors/list/List.module.css
rename to client/src/features/editors/list/List.module.scss
index 6670524f3..417dea6b8 100644
--- a/client/src/features/editors/list/List.module.css
+++ b/client/src/features/editors/list/List.module.scss
@@ -7,7 +7,7 @@
border-radius: 4px;
padding: 8px;
overflow-y: scroll;
- height: 73vh;
+ height: 100%;
}
.list {
diff --git a/client/src/features/info/Info.jsx b/client/src/features/info/Info.jsx
index a519b3e97..87cac9397 100644
--- a/client/src/features/info/Info.jsx
+++ b/client/src/features/info/Info.jsx
@@ -15,11 +15,10 @@ export default function Info() {
titleNext: '',
subtitleNext: '',
presenterNext: '',
- noteNext: '',
+ noteNext: ''
});
const [selected, setSelected] = useState('No events');
const [playback, setPlayback] = useState(null);
- const logData = [];
// handle incoming messages
useEffect(() => {
@@ -61,20 +60,19 @@ export default function Info() {
};
}, [socket]);
- // TODO: Put this in use effect
// prepare data
const titlesNow = {
title: titles.titleNow,
subtitle: titles.subtitleNow,
presenter: titles.presenterNow,
- note: titles.noteNow,
+ note: titles.noteNow
};
const titlesNext = {
title: titles.titleNext,
subtitle: titles.subtitleNext,
presenter: titles.presenterNext,
- note: titles.noteNext,
+ note: titles.noteNext
};
return (
@@ -83,10 +81,10 @@ export default function Info() {
{`Running on port 4001`}
{selected}
- {/* */}
+
>
);
}
diff --git a/client/src/features/info/Info.module.scss b/client/src/features/info/Info.module.scss
index 318ead39e..6d5d2e3da 100644
--- a/client/src/features/info/Info.module.scss
+++ b/client/src/features/info/Info.module.scss
@@ -1,4 +1,6 @@
-.container {
+@use '../../main' as *;
+
+@mixin container {
margin-top: 1em;
display: flex;
flex-direction: column;
@@ -8,9 +10,13 @@
padding: 8px;
}
+.container {
+ @include container;
+}
+
.main {
font-size: 0.9em;
- color: #ff7597;
+ color: $ontime-pink;
display: flex;
justify-content: space-between;
}
@@ -20,17 +26,17 @@
padding: 0;
margin: 0;
font-size: 0.9em;
- color: #ccc;
+ color: $header-gray;
display: flex;
justify-content: space-between;
}
.header {
- color: #ccc;
+ color: $header-gray;
}
.headerRoll {
- color: #2b6cb0;
+ color: $ontime-roll;
}
.collapsedTitle {
@@ -57,7 +63,7 @@
.label {
font-size: 0.9em;
- color: #aaa;
+ color: $label-gray;
}
.label::after {
@@ -70,43 +76,22 @@
}
.notes {
- color: #d69e2e;
+ color: $notes-color;
overflow: hidden;
text-overflow: ellipsis;
}
.if {
font-size: 0.8em;
- color: #4bffabcc;
- background-color: rgba(0, 0, 0, 0.13);
- border-radius: 2px;
- padding: 0 0.5em;
- margin: 0 0.5em;
+ color: $ontime-accent;
+ @include container-bg;
}
-.log {
- overflow-y: scroll;
- height: 30vh;
-
- ul > li {
- font-size: 0.9em;
- color: #fff;
- }
-
-}
-
-.info {
+ul > li {
+ font-size: 0.9em;
color: #fff;
}
-.error {
- color: red;
-}
-
-.client {
- color: lightblue;
-}
-
.moreExpanded,
.moreCollapsed {
cursor: pointer;
diff --git a/client/src/features/info/InfoLogger.jsx b/client/src/features/info/InfoLogger.jsx
index 6e58dc3a2..36cd8e265 100644
--- a/client/src/features/info/InfoLogger.jsx
+++ b/client/src/features/info/InfoLogger.jsx
@@ -1,34 +1,117 @@
-import { Icon } from '@chakra-ui/react';
-import { useState } from 'react';
-import { FiChevronUp } from 'react-icons/fi';
-import style from './Info.module.scss';
+import { useContext, useEffect, useState } from 'react';
+import style from './InfoLogger.module.scss';
+import CollapseBar from "../../common/components/collapseBar/CollapseBar";
+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);
+ // 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 (
-
-
- Log
- setCollapsed((c) => !c)}
- />
-
+
+
setCollapsed((c) => !c)}/>
{!collapsed && (
-
- - 10:35:23 [PLAYBACK] Next
- -
- 10:32:10 [CLIENT] New socket client (total: 3)
-
- - 10:28:23 [PLAYBACK] Next
- - 10:25:23 [PLAYBACK] Play
- - 10:23:13 [SERVER] Server Reconnected
- - 10:23:10 [SERVER] Server Disconnected
-
+ <>
+
+
setShowUser((s) => !s)}
+ onAuxClick={() => disableOthers('USER')}
+ className={(showUser) ? style.active : null}>
+ USER
+
+
setShowClient((s) => !s)}
+ onAuxClick={() => disableOthers('CLIENT')}
+ className={(showClient) ? style.active : null}>
+ CLIENT
+
+
setShowServer((s) => !s)}
+ onAuxClick={() => disableOthers('SERVER')}
+ className={(showServer) ? style.active : null}>
+ SERVER
+
+
setShowPlayback((s) => !s)}
+ onAuxClick={() => disableOthers('PLAYBACK')}
+ className={(showPlayback) ? style.active : null}>
+ Playback
+
+
setShowRx((s) => !s)}
+ onAuxClick={() => disableOthers('RX')}
+ className={(showRx) ? style.active : null}>
+ RX
+
+
setShowTx((s) => !s)}
+ onAuxClick={() => disableOthers('TX')}
+ className={(showTx) ? style.active : null}>
+ TX
+
+
+ Clear
+
+
+
+ {data.map((d) => (
+ -
+
{d.time}
+ {d.origin}
+ {d.text}
+
+ ))}
+
+ >
)}
);
diff --git a/client/src/features/info/InfoLogger.module.scss b/client/src/features/info/InfoLogger.module.scss
new file mode 100644
index 000000000..51087c484
--- /dev/null
+++ b/client/src/features/info/InfoLogger.module.scss
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/client/src/features/info/InfoNif.jsx b/client/src/features/info/InfoNif.jsx
index bf563de0d..647c35176 100644
--- a/client/src/features/info/InfoNif.jsx
+++ b/client/src/features/info/InfoNif.jsx
@@ -1,10 +1,9 @@
-import { Icon } from '@chakra-ui/react';
import { useState } from 'react';
-import { FiChevronUp } from 'react-icons/fi';
import { APP_TABLE } from 'app/api/apiConstants';
import { getInfo, ontimePlaceholderInfo } from 'app/api/ontimeApi';
import { useFetch } from 'app/hooks/useFetch';
import style from './Info.module.scss';
+import CollapseBar from '../../common/components/collapseBar/CollapseBar';
export default function InfoNif() {
const { data, status } = useFetch(APP_TABLE, getInfo, {
@@ -22,16 +21,8 @@ export default function InfoNif() {
};
return (
-
-
- Network Info
- setCollapsed((c) => !c)}
- />
-
-
+
+
setCollapsed((c) => !c)}/>
{!collapsed && (
{status === 'success' && (
diff --git a/client/src/features/menu/MenuBar.jsx b/client/src/features/menu/MenuBar.jsx
index 4bfe0f7fd..b902bebff 100644
--- a/client/src/features/menu/MenuBar.jsx
+++ b/client/src/features/menu/MenuBar.jsx
@@ -9,10 +9,12 @@ import QuitIconBtn from './buttons/QuitIconBtn';
import style from './MenuBar.module.css';
import HelpIconBtn from './buttons/HelpIconBtn';
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) {
const { onOpen } = props;
+ const { emitError } = useContext(LoggingContext);
const hiddenFileInput = useRef(null);
const queryClient = useQueryClient();
const uploaddb = useMutation(uploadEvents, {
@@ -34,28 +36,24 @@ export default function MenuBar(props) {
const handleUpload = (event) => {
const fileUploaded = event.target.files[0];
if (fileUploaded == null) return;
- console.log(fileUploaded);
// Limit file size to 1MB
if (fileUploaded.size > 1000000) {
- console.log('Error: File size limit (1MB) exceeded');
+ emitError('Error: File size limit (1MB) exceeded')
return;
}
// Check file extension
- if (fileUploaded.name.endsWith('.xlsx')) {
- console.log('excel file');
- } else if (fileUploaded.name.endsWith('.json')) {
- console.log('json file');
- } else {
- console.log('Error: File type unknown');
+ if (! fileUploaded.name.endsWith('.xlsx')
+ || !fileUploaded.name.endsWith('.json')) {
+ emitError('Error: File type unknown')
return;
}
try {
uploaddb.mutate(fileUploaded);
} catch (error) {
- console.log(error);
+ emitError(`Failed uploading file: ${error}`)
}
// reset input value
diff --git a/client/src/features/modals/AliasesModal.jsx b/client/src/features/modals/AliasesModal.jsx
index 08a9cc296..8a9663ee1 100644
--- a/client/src/features/modals/AliasesModal.jsx
+++ b/client/src/features/modals/AliasesModal.jsx
@@ -23,6 +23,7 @@ export default function AliasesModal() {
const smLink = 'http://localhost:4001/sm';
const publicLink = 'http://localhost:4001/public';
const pipLink = 'http://localhost:4001/pip';
+ const studioLink = 'http://localhost:4001/studio';
return (
<>
@@ -81,6 +82,17 @@ export default function AliasesModal() {
{pipLink}
+
+ Studio Clock
+
+ {studioLink}
+
+
Manage custom aliases
diff --git a/client/src/features/modals/AppSettingsModal.jsx b/client/src/features/modals/AppSettingsModal.jsx
index da66984b2..03ed5f975 100644
--- a/client/src/features/modals/AppSettingsModal.jsx
+++ b/client/src/features/modals/AppSettingsModal.jsx
@@ -1,14 +1,15 @@
import { ModalBody } from '@chakra-ui/modal';
import { FormLabel, FormControl, Input, Button } from '@chakra-ui/react';
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 { OSC_SETTINGS } from 'app/api/apiConstants';
-import { showErrorToast } from 'common/helpers/toastManager';
import style from './Modals.module.scss';
+import { LoggingContext } from '../../app/context/LoggingContext';
export default function AppSettingsModal() {
const { data, status } = useFetch(OSC_SETTINGS, getOSC);
+ const { emitError } = useContext(LoggingContext);
const [formData, setFormData] = useState(oscPlaceholderSettings);
const [changed, setChanged] = useState(false);
const [submitting, setSubmitting] = useState(false);
@@ -41,7 +42,7 @@ export default function AppSettingsModal() {
// set fields with error
if (e.status) {
- showErrorToast('Invalid Input', e.message);
+ emitError(`Invalid Input: ${e.message}`);
return;
}
diff --git a/client/src/features/modals/IntegrationSettingsModal.jsx b/client/src/features/modals/IntegrationSettingsModal.jsx
index fd735c3bd..8f32c7a37 100644
--- a/client/src/features/modals/IntegrationSettingsModal.jsx
+++ b/client/src/features/modals/IntegrationSettingsModal.jsx
@@ -12,14 +12,15 @@ import {
ontimeVars,
postInfo,
} from 'app/api/ontimeApi';
-import { useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import { useFetch } from 'app/hooks/useFetch';
import { APP_TABLE } from 'app/api/apiConstants';
-import { showErrorToast } from 'common/helpers/toastManager';
import style from './Modals.module.scss';
+import { LoggingContext } from '../../app/context/LoggingContext';
export default function IntegrationSettingsModal() {
const { data, status } = useFetch(APP_TABLE, getInfo);
+ const { emitError } = useContext(LoggingContext);
const [formData, setFormData] = useState(httpPlaceholder);
const [changed, setChanged] = useState(false);
const [submitting, setSubmitting] = useState(false);
@@ -51,7 +52,7 @@ export default function IntegrationSettingsModal() {
// set fields with error
if (e.status) {
- showErrorToast('Invalid Input', e.message);
+ emitError(`Invalid Input: ${e.message}`);
return;
}
diff --git a/client/src/features/viewers/ViewWrapper.jsx b/client/src/features/viewers/ViewWrapper.jsx
index 7f58dea19..95f54736b 100644
--- a/client/src/features/viewers/ViewWrapper.jsx
+++ b/client/src/features/viewers/ViewWrapper.jsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { fetchAllEvents } from 'app/api/eventsApi';
import { fetchEvent } from 'app/api/eventApi';
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 { EVENTS_TABLE, EVENT_TABLE } from 'app/api/apiConstants';
diff --git a/server/main.js b/server/main.js
index 5b92123a9..42cb37b97 100644
--- a/server/main.js
+++ b/server/main.js
@@ -119,7 +119,7 @@ app.whenReady().then(() => {
createWindow();
// register global shortcuts
- // (available regardless of wheter app is in focus)
+ // (available regardless of whether app is in focus)
// bring focus to window
globalShortcut.register('Alt+1', () => {
win.show();
diff --git a/server/package.json b/server/package.json
index 7db4c45d7..b845aef15 100644
--- a/server/package.json
+++ b/server/package.json
@@ -1,6 +1,6 @@
{
"name": "ontime",
- "version": "0.4.7",
+ "version": "0.4.8",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
diff --git a/server/src/app.js b/server/src/app.js
index c3028230e..fdb72b17a 100644
--- a/server/src/app.js
+++ b/server/src/app.js
@@ -153,7 +153,7 @@ export const startServer = async (overrideConfig = null) => {
const port = 4001;
// 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));
// OSC Config
diff --git a/server/src/classes/EventTimer.js b/server/src/classes/EventTimer.js
index 03a79b57c..59c981ba5 100644
--- a/server/src/classes/EventTimer.js
+++ b/server/src/classes/EventTimer.js
@@ -1,110 +1,85 @@
-import {Timer} from './Timer.js';
-import {Server} from 'socket.io';
-import {DAY_TO_MS, getSelectionByRoll, replacePlaceholder, updateRoll} from './classUtils.js';
-import {OSCIntegration} from './integrations/Osc.js';
-import {HTTPIntegration} from "./integrations/Http.js";
-import {cleanURL} from "../utils/url.js";
+import { Timer } from './Timer.js';
+import { Server } from 'socket.io';
+import {
+ DAY_TO_MS,
+ getSelectionByRoll,
+ 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
- * namely:
- * - Presenter message, text and status
- * - Public message, text and status
- *
+ * Class EventTimer adds functions specific to APP
+ * @extends Timer
*/
export class EventTimer extends Timer {
-
- // Keep track of Timer lifecycle
- // 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',
- onLoad: 'onLoad',
- armed: 'armed',
- onStart: 'onStart',
- onUpdate: 'onUpdate',
- onPause: 'onPause',
- onStop: 'onStop',
- onFinish: 'onFinish',
- };
- ontimeCycle = 'idle';
- prevCycle = null;
- lastUpdate = null;
-
- // Socket IO Object
- io = null;
-
- // OSC Object
- osc = null;
-
- // HTTP Client Object
- http = null;
-
- _numClients = 0;
- _interval = null;
-
- presenter = {
- text: '',
- visible: false,
- };
- public = {
- text: '',
- visible: false,
- };
- lower = {
- text: '',
- visible: false,
- };
-
- titlesPublic = {
- titleNow: null,
- 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]
+ * @param {object} httpServer
+ * @param {object} timerConfig
+ * @param {object} [oscConfig]
+ * @param {object} [httpConfig]
*/
constructor(httpServer, timerConfig, oscConfig, httpConfig) {
-
// call super constructor
super();
- // initialise class variables
+ this.cycleState = {
+ /* idle: before it is initialised */
+ idle: 'idle',
+ /* onLoad: when a new event is loaded */
+ onLoad: 'onLoad',
+ /* armed: when a new event is loaded but hasn't started */
+ armed: 'armed',
+ onStart: 'onStart',
+ /* update: every update call cycle (1 x second) */
+ onUpdate: 'onUpdate',
+ onPause: 'onPause',
+ onStop: 'onStop',
+ onFinish: 'onFinish',
+ };
+ this.ontimeCycle = 'idle';
+ this.prevCycle = null;
+
+ // OSC Object
+ this.osc = null;
+
+ // HTTP Client Object
+ this.http = null;
+
+ this._numClients = 0;
+ this._interval = null;
+
+ this.presenter = {
+ text: '',
+ visible: false,
+ };
+ this.public = {
+ text: '',
+ visible: false,
+ };
+ this.lower = {
+ text: '',
+ visible: false,
+ };
+
+ // call general title reset
+ this._resetSelection();
+
this.numEvents = 0;
+ this._eventlist = null;
+ this.onAir = false;
// initialise socketIO server
+ this.messageStack = [];
+ this.MAX_MESSAGES = 100;
+ this._clientNames = {};
this.io = new Server(httpServer, {
cors: {
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
this._interval = setInterval(
() => this.runCycle(),
@@ -138,28 +97,65 @@ export class EventTimer extends Timer {
// listen to new connections
this._listenToConnections();
+
+ if (oscConfig != null) {
+ this._initOscClient(oscConfig);
+ }
+
+ if (httpConfig != null) {
+ this._initHTTPClient(httpConfig);
+ }
}
/**
* @description Shutdown process
*/
shutdown() {
- console.log('Shutting down integrations')
- console.log('... Closing socket server');
+ this.info('SERVER', 'Shutting down ontime');
+ this.info('TX', '... Closing socket server');
this.io.close();
- console.log('... Closing osc server');
+ this.info('TX', '... Closing OSC Client');
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() {
// through websockets
- this.io.emit('timer', this.getTimes());
+ this.io.emit('timer', this.getTimeObject());
}
- // broadcast state
- broadcastState(update = true) {
- this.io.emit('timer', this.getTimes(update));
+ /**
+ * Broadcasts complete object state
+ */
+ broadcastState() {
+ this.broadcastTimer();
this.io.emit('playstate', this.state);
this.io.emit('selected', {
id: this.selectedEventId,
@@ -168,6 +164,7 @@ export class EventTimer extends Timer {
});
this.io.emit('selected-id', this.selectedEventId);
this.io.emit('next-id', this.nextEventId);
+ this.io.emit('numevents', this.numEvents);
this.io.emit('publicselected-id', this.selectedPublicEventId);
this.io.emit('publicnext-id', this.nextPublicEventId);
this.io.emit('titles', this.titles);
@@ -175,7 +172,11 @@ export class EventTimer extends Timer {
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) {
this.io.emit(address, payload);
}
@@ -192,69 +193,73 @@ export class EventTimer extends Timer {
case 'start':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Play Mode Start');
this.start();
- this.runCycle();
break;
case 'pause':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Play Mode Pause');
this.pause();
- this.runCycle();
break;
case 'stop':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Play Mode Stop');
this.stop();
- this.runCycle();
break;
case 'roll':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Play Mode Roll');
this.roll();
- this.runCycle();
break;
case 'previous':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Play Mode Previous');
this.previous();
- this.runCycle();
break;
case 'next':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Play Mode Next');
this.next();
- this.runCycle();
break;
case 'unload':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Events unloaded');
this.unload();
- this.runCycle();
break;
case 'reload':
if (this.numEvents === 0 || this.numEvents == null) return false;
// Call action and force update
+ this.info('PLAYBACK', 'Reloaded event');
this.reload();
- this.runCycle();
break;
case 'onAir':
// Call action
+ this.info('PLAYBACK', 'Going On Air');
this.setonAir(true);
break;
case 'offAir':
// Call action and force update
+ this.info('PLAYBACK', 'Going Off Air');
this.setonAir(false);
break;
default:
// Error, disable flag
- console.log('ERROR: Unhandled action triggered')
+ this.error('RX', `Unhandled action triggered ${action}`);
reply = false;
break;
}
+
+ // update state
+ this.runCycle();
return reply;
}
-
/**
* @description State machine checks what actions need to
* happen at every app cycle
@@ -264,18 +269,19 @@ export class EventTimer extends Timer {
let httpMessage = null;
switch (this.ontimeCycle) {
- case "idle":
+ case 'idle':
break;
- case "armed":
+ case 'armed':
// if we come from roll, see if we can start
if (this.state === 'roll') {
this.update();
}
break;
- case "onLoad":
+ case 'onLoad':
// broadcast change
this.broadcastState();
+ // Todo: wrap in reusable function
// check integrations - http
if (h?.onLoad?.enabled) {
if (h?.onLoad?.url != null || h?.onLoad?.url !== '') {
@@ -286,13 +292,13 @@ export class EventTimer extends Timer {
// update lifecycle: armed
this.ontimeCycle = this.cycleState.armed;
break;
- case "onStart":
+ case 'onStart':
// broadcast current state
this.broadcastState();
// send OSC if there is something running
// _finish at is only set when an event is loaded
if (this._finishAt > 0) {
- this.osc.send(this.osc.implemented.play);
+ this.sendOsc(this.osc.implemented.play);
}
// check integrations - http
@@ -305,7 +311,7 @@ export class EventTimer extends Timer {
// update lifecycle: onUpdate
this.ontimeCycle = this.cycleState.onUpdate;
break;
- case "onUpdate":
+ case 'onUpdate':
// call update
this.update();
// broadcast current state
@@ -313,9 +319,15 @@ export class EventTimer extends Timer {
// through OSC, only if running
if (this.state === 'start' || this.state === 'roll') {
if (this.current != null && this.secondaryTimer == null) {
- this.osc.send(this.osc.implemented.time, this.timeTag);
- this.osc.send(this.osc.implemented.overtime, this.current > 0 ? 0 : 1);
- this.osc.send(this.osc.implemented.title, this.titles?.titleNow || '');
+ this.sendOsc(this.osc.implemented.time, this.timeTag);
+ this.sendOsc(
+ 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;
- case "onPause":
+ case 'onPause':
// broadcast current state
this.broadcastState();
// send OSC
- this.osc.send(this.osc.implemented.pause);
+ this.sendOsc(this.osc.implemented.pause);
// check integrations - http
if (h?.onLoad?.enabled) {
@@ -344,13 +356,13 @@ export class EventTimer extends Timer {
this.ontimeCycle = this.cycleState.armed;
break;
- case "onStop":
+ case 'onStop':
// broadcast change
this.broadcastState();
// send OSC if something was actually stopped
if (this.prevCycle === this.cycleState.onUpdate) {
- this.osc.send(this.osc.implemented.stop);
+ this.sendOsc(this.osc.implemented.stop);
}
// check integrations - http
@@ -363,12 +375,11 @@ export class EventTimer extends Timer {
// update lifecycle: idle
this.ontimeCycle = this.cycleState.idle;
break;
- case "onFinish":
- console.log('onFinish')
+ case 'onFinish':
// broadcast change
- this.broadcastState(false);
+ this.broadcastState();
// finished an event
- this.osc.send(this.osc.implemented.finished);
+ this.sendOsc(this.osc.implemented.finished);
// check integrations - http
if (h?.onLoad?.enabled) {
@@ -381,20 +392,20 @@ export class EventTimer extends Timer {
this.ontimeCycle = this.cycleState.onUpdate;
break;
default:
- console.log(`ERROR: Unhandled cycle: ${this.ontimeCycle}`)
+ this.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
}
// send http message if any
if (httpMessage != null) {
const v = {
- '$timer': this.timeTag,
- '$title': this.titles.titleNow,
- '$presenter': this.titles.presenterNow,
- '$subtitle': this.titles.subtitleNow,
+ $timer: this.timeTag,
+ $title: this.titles.titleNow,
+ $presenter: this.titles.presenterNow,
+ $subtitle: this.titles.subtitleNow,
'$next-title': this.titles.titleNext,
'$next-presenter': this.titles.presenterNext,
'$next-subtitle': this.titles.subtitleNext,
- }
+ };
const m = cleanURL(replacePlaceholder(httpMessage, v));
this.http.send(m);
}
@@ -407,7 +418,6 @@ export class EventTimer extends Timer {
}
update() {
-
// if there is nothing selected, update clock
const now = this._getCurrentTime();
@@ -444,19 +454,23 @@ export class EventTimer extends Timer {
this.runCycle();
}
- // only implement roll here, rest implemented in super
+ // only implement roll here, rest implemented in super
if (this.state === 'roll') {
const u = {
selectedEventId: this.selectedEventId,
current: this.current,
// 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,
secondaryTimer: this.secondaryTimer,
_secondaryTarget: this._secondaryTarget,
- }
+ };
- const {updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished} = updateRoll(u);
+ const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } =
+ updateRoll(u);
this.current = updatedTimer;
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) {
/*******************************************/
// Presenter message
@@ -513,6 +533,10 @@ export class EventTimer extends Timer {
}
}
+ /**
+ * Handle socket io connections
+ * @private
+ */
_listenToConnections() {
this.io.on('connection', (socket) => {
/*******************************/
@@ -521,12 +545,14 @@ export class EventTimer extends Timer {
/*******************************/
// keep track of connections
this._numClients++;
- console.log(
- `EventTimer: ${this._numClients} Clients with new connection: ${socket.id}`
- );
+ this._clientNames[socket.id] = getRandomName();
+ const m = `${this._numClients} Clients with new connection: ${
+ this._clientNames[socket.id]
+ }`;
+ this.info('CLIENT', m);
// send state
- socket.emit('timer', this.getTimes());
+ socket.emit('timer', this.getTimeObject());
socket.emit('playstate', this.state);
socket.emit('selected-id', this.selectedEventId);
socket.emit('next-id', this.nextEventId);
@@ -539,9 +565,11 @@ export class EventTimer extends Timer {
/********************************/
socket.on('disconnect', () => {
this._numClients--;
- console.log(
- `EventTimer: Client disconnected, total now: ${this._numClients}`
- );
+ const m = `${this._numClients} Clients with disconnection: ${
+ 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
socket.on('get-state', () => {
- socket.emit('timer', this.getTimes());
+ socket.emit('timer', this.getTimeObject());
socket.emit('playstate', this.state);
socket.emit('selected-id', this.selectedEventId);
socket.emit('next-id', this.nextEventId);
@@ -567,7 +595,7 @@ export class EventTimer extends Timer {
});
socket.on('get-timer', () => {
- socket.emit('timer', this.getTimes());
+ socket.emit('timer', this.getTimeObject());
});
socket.on('increment-timer', (data) => {
@@ -651,11 +679,11 @@ export class EventTimer extends Timer {
// Presenter message
socket.on('set-presenter-text', (data) => {
- this._setterManager('set-presenter-text', data);
+ this._setTitles('set-presenter-text', data);
});
socket.on('set-presenter-visible', (data) => {
- this._setterManager('set-presenter-visible', data);
+ this._setTitles('set-presenter-visible', data);
});
socket.on('get-presenter', () => {
@@ -664,11 +692,11 @@ export class EventTimer extends Timer {
/*******************************************/
// Public message
socket.on('set-public-text', (data) => {
- this._setterManager('set-public-text', data);
+ this._setTitles('set-public-text', data);
});
socket.on('set-public-visible', (data) => {
- this._setterManager('set-public-visible', data);
+ this._setTitles('set-public-visible', data);
});
socket.on('get-public', () => {
@@ -678,11 +706,11 @@ export class EventTimer extends Timer {
/*******************************************/
// Lower third message
socket.on('set-lower-text', (data) => {
- this._setterManager('set-lower-text', data);
+ this._setTitles('set-lower-text', data);
});
socket.on('set-lower-visible', (data) => {
- this._setterManager('set-lower-visible', data);
+ this._setTitles('set-lower-visible', data);
});
socket.on('get-lower', () => {
@@ -691,6 +719,9 @@ export class EventTimer extends Timer {
});
}
+ /**
+ * Deletes running event list from object
+ */
clearEventList() {
// unload events
this.unload();
@@ -706,6 +737,10 @@ export class EventTimer extends Timer {
this.broadcastThis('numevents', this.numEvents);
}
+ /**
+ * Adds an event list to object
+ * @param {array} eventlist
+ */
setupWithEventList(eventlist) {
if (!Array.isArray(eventlist) || eventlist.length < 1) return;
@@ -717,19 +752,23 @@ export class EventTimer extends Timer {
this._eventlist = events;
this.numEvents = numEvents;
- // list may be empty
+ // list may contain no events
if (numEvents < 1) return;
// load first event
this.loadEvent(0);
// update clients
- this.broadcastThis('numevents', this.numEvents);
+ this.broadcastState();
// run cycle
this.runCycle();
}
+ /**
+ * Updates event list in object
+ * @param {array} eventlist
+ */
updateEventList(eventlist) {
// filter only events
const events = eventlist.filter((e) => e.type === 'event');
@@ -771,12 +810,17 @@ export class EventTimer extends Timer {
}
// update clients
- this.broadcastThis('numevents', this.numEvents);
+ this.broadcastState();
// run cycle
this.runCycle();
}
+ /**
+ * Updates a single id in the object list
+ * @param {string} id
+ * @param {object} entry - new event object
+ */
updateSingleEvent(id, entry) {
// find object in events
const eventIndex = this._eventlist.findIndex((e) => e.id === id);
@@ -810,16 +854,20 @@ export class EventTimer extends Timer {
this._loadTitlesNow();
}
} catch (error) {
- console.log(error);
+ this.error('SERVER', error);
}
// update clients
- this.broadcastThis('numevents', this.numEvents);
+ this.broadcastState();
// run cycle
this.runCycle();
}
+ /**
+ * Deleted an event from the list by its id
+ * @param {string} eventId
+ */
deleteId(eventId) {
// find object in events
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
@@ -848,7 +896,7 @@ export class EventTimer extends Timer {
}
// update clients
- this.broadcastThis('numevents', this.numEvents);
+ this.broadcastState();
// run cycle
this.runCycle();
@@ -856,34 +904,35 @@ export class EventTimer extends Timer {
/**
* @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) {
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
if (eventIndex === -1) return;
this.pause();
- this.loadEvent(eventIndex, 'load', true);
+ this.loadEvent(eventIndex, 'load');
// run cycle
this.runCycle();
}
/**
* @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) {
if (eventIndex === -1 || eventIndex > this.numEvents) return;
this.pause();
- this.loadEvent(eventIndex, 'load', true);
+ this.loadEvent(eventIndex, 'load');
// run cycle
this.runCycle();
}
- // Loads a given event
- // load timers
- // load selectedEventIndex
- // load titles
+ /**
+ * Loads a given event by index
+ * @param {object} eventIndex
+ * @param {string} [type='load'] - 'load' or 'reload', whether we are keeping running time
+ */
loadEvent(eventIndex, type = 'load') {
const e = this._eventlist[eventIndex];
if (e == null) return;
@@ -894,7 +943,6 @@ export class EventTimer extends Timer {
if (end < start) end += DAY_TO_MS;
// time stuff changes on whether we keep the running clock
-
if (type === 'load') {
this._resetTimers();
@@ -1092,75 +1140,6 @@ export class EventTimer extends Timer {
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
* @param {boolean} onAir - whether flag is active
@@ -1241,7 +1220,7 @@ export class EventTimer extends Timer {
// nothing to play, unload
if (nowIndex === null && nextIndex === null) {
this.unload();
- console.log('Roll: no events found');
+ this.warning('SERVER', 'Roll: no events found');
return;
}
@@ -1267,8 +1246,9 @@ export class EventTimer extends Timer {
// Set running timers
if (nowIndex === null) {
// only warn the first time
- if (this.secondaryTimer === null)
- console.log('Roll: waiting for event start');
+ if (this.secondaryTimer === null) {
+ this.info('SERVER', 'Roll: waiting for event start');
+ }
// reset running timer
// ??? should this not have been reset?
@@ -1319,9 +1299,6 @@ export class EventTimer extends Timer {
// load into event
this.rollLoad();
-
- // broadcast change
- this.broadcastState();
}
previous() {
@@ -1338,7 +1315,7 @@ export class EventTimer extends Timer {
}
// send OSC
- this.osc.send(this.osc.implemented.previous);
+ this.sendOsc(this.osc.implemented.previous);
// change playstate
this.pause();
@@ -1364,7 +1341,7 @@ export class EventTimer extends Timer {
}
// send OSC
- this.osc.send(this.osc.implemented.next);
+ this.sendOsc(this.osc.implemented.next);
// change playstate
this.pause();
@@ -1399,9 +1376,94 @@ export class EventTimer extends Timer {
this.pause();
// send OSC
- this.osc.send(this.osc.implemented.reload);
+ this.sendOsc(this.osc.implemented.reload);
// reload data
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);
+ }
+ }
}
diff --git a/server/src/classes/Timer.js b/server/src/classes/Timer.js
index 485e35b99..820610d67 100644
--- a/server/src/classes/Timer.js
+++ b/server/src/classes/Timer.js
@@ -7,22 +7,11 @@
import { stringFromMillis } from '../utils/time.js';
export class Timer {
- clock = null;
- duration = null;
- current = null;
- timeTag = null;
- secondaryTimer = null;
- _secondaryTarget = null;
- _finishAt = null;
- _finishedAt = null;
- _finishedFlag = false;
- _startedAt = null;
- _pausedAt = null;
- _pausedInterval = null;
- _pausedTotal = null;
- state = 'stop';
-
- constructor() {}
+ constructor() {
+ this.clock = null;
+ this._resetTimers(true);
+ this.state = 'stop';
+ }
// call setup separately
setupWithSeconds(seconds, autoStart = false) {
@@ -74,11 +63,11 @@ export class Timer {
if (this._startedAt != null) {
// update current timer
this.current =
- this._startedAt
- + this.duration
- + this._pausedTotal
- + this._pausedInterval
- - now;
+ this._startedAt +
+ this.duration +
+ this._pausedTotal +
+ this._pausedInterval -
+ now;
}
// enable flag
@@ -92,13 +81,14 @@ export class Timer {
if (checkFinish) {
// is event finished?
const isTimeOver = this.current <= 0;
- const isUpdating = (this.state !== 'pause');
+ const isUpdating = this.state !== 'pause';
if (isTimeOver && isUpdating && this._finishedAt == null) {
if (this._finishedAt === null) this._finishedAt = now;
this._finishedFlag = true;
}
}
+ this.timeTag = stringFromMillis(this.current);
}
// helpers
@@ -136,6 +126,7 @@ export class Timer {
_resetTimers(total = false) {
if (total) this.duration = null;
this.current = this.duration;
+ this.timeTag = null;
this.running = null;
this.secondaryTimer = null;
this._secondaryTarget = null;
@@ -153,14 +144,11 @@ export class Timer {
return this.duration - this.current;
}
- // get time object
- getTimes(update = true) {
- // update timer
- if (update) this.update();
-
- // update timetag
- this.timeTag = stringFromMillis(this.current);
-
+ /**
+ * Builds time object
+ * @returns {{running: number, secondary: number, expectedFinish: number, durationSeconds: number, startedAt: null, clock: null}}
+ */
+ getTimeObject() {
return {
clock: this.clock,
running: Timer.toSeconds(this.current),
diff --git a/server/src/classes/classUtils.js b/server/src/classes/classUtils.js
index 8465fa03a..13b5857ee 100644
--- a/server/src/classes/classUtils.js
+++ b/server/src/classes/classUtils.js
@@ -1,4 +1,3 @@
-
/**
* Utility variable: 24 hour in milliseconds .
* @type {number}
@@ -11,7 +10,8 @@ export const DAY_TO_MS = 86400000;
* @param {number} end - When does the event end
* @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
@@ -36,7 +36,6 @@ export const sortArrayByProperty = (arr, property) => {
export const replacePlaceholder = (str, values) => {
for (let [k, v] of Object.entries(values)) {
str = str.replace(k, v);
- console.log(k, v);
}
return str;
};
@@ -72,7 +71,10 @@ export const getSelectionByRoll = (arr, now) => {
// exit early if we are past the events
const lastEvent = orderedEvents[orderedEvents.length - 1];
- const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd);
+ const lastNormalEnd = normaliseEndTime(
+ lastEvent.timeStart,
+ lastEvent.timeEnd
+ );
if (now > lastNormalEnd) {
return {
nowIndex,
@@ -128,7 +130,7 @@ export const getSelectionByRoll = (arr, now) => {
// check how far the start is from now
const wait = e.timeStart - now;
- if (nextIndex === null || wait < timeToNext) {
+ if (nextIndex == null || wait < timeToNext) {
timeToNext = wait;
nextIndex = arr.findIndex((a) => a.id === e.id);
}
@@ -161,8 +163,14 @@ export const getSelectionByRoll = (arr, now) => {
* @returns {object} object with selection variables
*/
export const updateRoll = (currentTimers) => {
-
- const {selectedEventId,current,_finishAt,clock,secondaryTimer,_secondaryTarget} = currentTimers;
+ const {
+ selectedEventId,
+ current,
+ _finishAt,
+ clock,
+ secondaryTimer,
+ _secondaryTarget,
+ } = currentTimers;
// timers
let updatedTimer = current;
@@ -181,8 +189,6 @@ export const updateRoll = (currentTimers) => {
if (updatedTimer < 0) {
isFinished = true;
}
- console.log(updatedTimer, isFinished, _finishAt)
-
} else if (secondaryTimer >= 0) {
// if secondaryTimer is running we are in waiting to roll
@@ -202,6 +208,5 @@ export const updateRoll = (currentTimers) => {
doRollLoad = true;
}
- return {updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished};
-}
-
+ return { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished };
+};
diff --git a/server/src/classes/integrations/Osc.js b/server/src/classes/integrations/Osc.js
index 8c78250ff..0752700f2 100644
--- a/server/src/classes/integrations/Osc.js
+++ b/server/src/classes/integrations/Osc.js
@@ -1,12 +1,10 @@
/** Class contains logic towards outgoing OSC communications. */
-import {Client, Message} from 'node-osc';
+import { Client, Message } from 'node-osc';
export class OSCIntegration {
-
- ADDRESS = '/ontime';
-
constructor() {
// OSC Client
+ this.ADDRESS = '/ontime';
this.oscClient = null;
}
@@ -26,8 +24,8 @@ export class OSCIntegration {
time: 'time',
overtime: 'overtime',
title: 'title',
- presenter:'presenter',
- }
+ presenter: 'presenter',
+ };
}
/**
@@ -37,13 +35,19 @@ export class OSCIntegration {
* @param {number} oscConfig.port - OSC Destination Port
*/
init(oscConfig) {
- const {ip, port} = oscConfig;
+ const { ip, port } = oscConfig;
try {
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) {
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
*/
async send(messageType, payload) {
+ const reply = {
+ success: true,
+ message: 'OSC Message sent',
+ };
+
if (this.oscClient == null) {
- console.log('OSC ERROR: Client not initialised');
- return;
+ reply.success = false;
+ reply.message = 'Client not initialised';
+ return reply;
}
if (messageType == null) {
- console.log('OSC ERROR: Message undefined');
- return;
+ reply.success = false;
+ reply.message = 'Message undefined';
+ return reply;
}
// only specify special cases
@@ -68,35 +79,58 @@ export class OSCIntegration {
case 'overtime':
// Whether timer is negative
this.oscClient.send(`${this.ADDRESS}/overtime`, payload, (err) => {
- if (err) console.error(err);
+ if (err) {
+ reply.success = false;
+ reply.message = err;
+ }
});
break;
+
case 'title':
- if (payload != null && payload !== "") {
+ if (payload != null && payload !== '') {
// Send Title of current event
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;
+
case 'presenter':
- if (payload != null && payload !== "") {
+ if (payload != null && payload !== '') {
// Send presenter data on current event
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;
+
default:
// catch all for messages, allows to add new messages
// but should be used with the integrations definition
- const message = new Message(`${this.ADDRESS}/${messageType}`)
- if (payload != null) message.append(payload)
+ // eslint-disable-next-line no-case-declarations
+ const message = new Message(`${this.ADDRESS}/${messageType}`);
+ if (payload != null) message.append(payload);
this.oscClient.send(message, (err) => {
- if (err) console.error(err);
+ if (err) {
+ reply.success = false;
+ reply.message = err;
+ }
});
break;
}
+ return reply;
}
shutdown() {
@@ -104,4 +138,4 @@ export class OSCIntegration {
this.oscClient.close();
this.oscClient = null;
}
-}
\ No newline at end of file
+}
diff --git a/server/src/controllers/OscController.js b/server/src/controllers/OscController.js
index 398d11937..a0a0fb586 100644
--- a/server/src/controllers/OscController.js
+++ b/server/src/controllers/OscController.js
@@ -7,9 +7,7 @@ export const shutdownOSCServer = () => {
};
export const initiateOSC = (config) => {
- oscServer = new Server(config.port, '0.0.0.0', () => {
- console.log(`OSC Server is listening on port ${config.port}`);
- });
+ oscServer = new Server(config.port, '0.0.0.0');
// error
oscServer.on('error', console.error);
@@ -19,7 +17,6 @@ export const initiateOSC = (config) => {
// ontime: fixed message for app
// path: command to be called
// args: extra data, only used on some of the API entries (delay, goto)
- console.log('OSC received', msg);
// split message
const [, address, path] = msg[0].split('/');
@@ -46,40 +43,35 @@ export const initiateOSC = (config) => {
break;
case 'start':
case 'play':
- console.log('calling play');
global.timer.trigger('start');
break;
case 'pause':
- console.log('calling pause');
global.timer.trigger('pause');
break;
case 'prev':
- console.log('calling prev');
global.timer.trigger('previous');
break;
case 'next':
- console.log('calling next');
global.timer.trigger('next');
break;
case 'unload':
case 'stop':
- console.log('calling unload');
global.timer.trigger('unload');
break;
case 'reload':
- console.log('calling reload');
global.timer.trigger('reload');
break;
case 'roll':
- console.log('calling roll');
global.timer.trigger('roll');
break;
case 'delay':
- console.log('calling delay with', args);
try {
const t = parseInt(args);
if (isNaN(t)) {
- console.error(`OSC IN: delay time not recognised ${args}`);
+ global.timer.error(
+ 'RX',
+ `OSC IN: delay time not recognised ${args}`
+ );
return;
}
global.timer.increment(t * 1000 * 60);
@@ -88,36 +80,37 @@ export const initiateOSC = (config) => {
}
break;
case 'goto':
- console.log('calling goto with', args);
try {
const eventIndex = parseInt(args);
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null) {
- console.error(
+ global.timer.error(
+ 'RX',
`OSC IN: event index not recognised or out of range ${eventIndex}`
);
}
global.timer.loadEventByIndex(eventIndex - 1);
} catch (error) {
- console.log('error calling goto: ', error);
+ global.timer.error('RX', `OSC IN: error calling goto ${error}`);
}
break;
case 'gotoid':
console.log('calling gotoid with', args);
if (args == null) {
- console.error(
- `OSC IN: event id not recognised or out of range ${args}`
+ global.timer.error(
+ 'RX',
+ `OSC IN: event id not recognised or out of range ${args}}`
);
return;
}
try {
global.timer.loadEventById(args.toString().toLowerCase());
} catch (error) {
- console.log('error calling goto: ', error);
+ global.timer.error('RX', `OSC IN: error calling goto ${error}`);
}
break;
default:
- console.log(`Error: unhandled message ${path}`);
+ global.timer.warning('RX', `OSC IN: unhandled message ${path}`);
break;
}
});
diff --git a/server/src/controllers/eventController.js b/server/src/controllers/eventController.js
index 938e412b7..b40eb5f79 100644
--- a/server/src/controllers/eventController.js
+++ b/server/src/controllers/eventController.js
@@ -21,6 +21,5 @@ export const postEvent = async (req, res) => {
res.sendStatus(200);
} catch (error) {
res.status(400).send(error);
- console.log(error);
}
};
diff --git a/server/src/controllers/ontimeController.js b/server/src/controllers/ontimeController.js
index 3e9555bba..00b7dfeb9 100644
--- a/server/src/controllers/ontimeController.js
+++ b/server/src/controllers/ontimeController.js
@@ -130,7 +130,6 @@ export const postInfo = async (req, res) => {
res.sendStatus(200);
} catch (error) {
res.status(400).send(error);
- console.log(error);
}
};
diff --git a/server/src/controllers/playbackController.js b/server/src/controllers/playbackController.js
index b97ba1b26..54176421d 100644
--- a/server/src/controllers/playbackController.js
+++ b/server/src/controllers/playbackController.js
@@ -7,69 +7,59 @@ export const pbGet = async (req, res) => {
// Create controller for GET request to '/playback/onAir'
// Turns onAir flag to true
export const onAir = async (req, res) => {
- console.log('Setting onAir to true');
global.timer.trigger('onAir') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/onAir'
// Turns onAir flag to true
export const offAir = async (req, res) => {
- console.log('Setting onAir to false');
global.timer.trigger('offAir') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/start'
// Starts timer object
export const pbStart = async (req, res) => {
- console.log('Calling start');
global.timer.trigger('start') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/pause'
// Pauses timer object
export const pbPause = async (req, res) => {
- console.log('Calling pause');
global.timer.trigger('pause') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/stop'
// Stops timer object
export const pbStop = async (req, res) => {
- console.log('Calling stop');
global.timer.trigger('stop') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/roll'
// Sets timer object to roll mode
export const pbRoll = async (req, res) => {
- console.log('Calling roll');
global.timer.trigger('roll') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/previous'
// Sets timer object to roll mode
export const pbPrevious = async (req, res) => {
- console.log('Calling previous');
global.timer.trigger('previous') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/next'
// Sets timer object to roll mode
export const pbNext = async (req, res) => {
- console.log('Calling next');
global.timer.trigger('next') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/unload'
// Unloads any events
export const pbUnload = async (req, res) => {
- console.log('Calling unload');
global.timer.trigger('unload') ? res.sendStatus(200) : res.sendStatus(400);
};
// Create controller for GET request to '/playback/reload'
// Reloads current event
export const pbReload = async (req, res) => {
- console.log('Calling reload');
global.timer.trigger('reload') ? res.sendStatus(200) : res.sendStatus(400);
};
diff --git a/server/src/package.json b/server/src/package.json
index f04c7aded..4277cb3c4 100644
--- a/server/src/package.json
+++ b/server/src/package.json
@@ -1,4 +1,5 @@
{
+ "name": "ontime-server",
"type": "module",
"dependencies": {
"body-parser": "~1.19.0",
diff --git a/server/src/utils/__tests__/getRandomName.test.js b/server/src/utils/__tests__/getRandomName.test.js
new file mode 100644
index 000000000..308faa090
--- /dev/null
+++ b/server/src/utils/__tests__/getRandomName.test.js
@@ -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);
+});
\ No newline at end of file
diff --git a/server/src/utils/getRandomName.js b/server/src/utils/getRandomName.js
new file mode 100644
index 000000000..423d21f5c
--- /dev/null
+++ b/server/src/utils/getRandomName.js
@@ -0,0 +1,2860 @@
+const adjective = [
+ 'abandoned',
+ 'able',
+ 'absolute',
+ 'adorable',
+ 'adventurous',
+ 'academic',
+ 'acceptable',
+ 'acclaimed',
+ 'accomplished',
+ 'accurate',
+ 'aching',
+ 'acidic',
+ 'acrobatic',
+ 'active',
+ 'actual',
+ 'adept',
+ 'admirable',
+ 'admired',
+ 'adolescent',
+ 'adorable',
+ 'adored',
+ 'advanced',
+ 'afraid',
+ 'affectionate',
+ 'aged',
+ 'aggravating',
+ 'aggressive',
+ 'agile',
+ 'agitated',
+ 'agonizing',
+ 'agreeable',
+ 'ajar',
+ 'alarmed',
+ 'alarming',
+ 'alert',
+ 'alienated',
+ 'alive',
+ 'all',
+ 'altruistic',
+ 'amazing',
+ 'ambitious',
+ 'ample',
+ 'amused',
+ 'amusing',
+ 'anchored',
+ 'ancient',
+ 'angelic',
+ 'angry',
+ 'anguished',
+ 'animated',
+ 'annual',
+ 'another',
+ 'antique',
+ 'anxious',
+ 'any',
+ 'apprehensive',
+ 'appropriate',
+ 'apt',
+ 'arctic',
+ 'arid',
+ 'aromatic',
+ 'artistic',
+ 'ashamed',
+ 'assured',
+ 'astonishing',
+ 'athletic',
+ 'attached',
+ 'attentive',
+ 'attractive',
+ 'austere',
+ 'authentic',
+ 'authorized',
+ 'automatic',
+ 'avaricious',
+ 'average',
+ 'aware',
+ 'awesome',
+ 'awful',
+ 'awkward',
+ 'babyish',
+ 'bad',
+ 'back',
+ 'baggy',
+ 'bare',
+ 'barren',
+ 'basic',
+ 'beautiful',
+ 'belated',
+ 'beloved',
+ 'beneficial',
+ 'better',
+ 'best',
+ 'bewitched',
+ 'big',
+ 'big-hearted',
+ 'biodegradable',
+ 'bite-sized',
+ 'bitter',
+ 'black',
+ 'black-and-white',
+ 'bland',
+ 'blank',
+ 'blaring',
+ 'bleak',
+ 'blind',
+ 'blissful',
+ 'blond',
+ 'blue',
+ 'blushing',
+ 'bogus',
+ 'boiling',
+ 'bold',
+ 'bony',
+ 'boring',
+ 'bossy',
+ 'both',
+ 'bouncy',
+ 'bountiful',
+ 'bowed',
+ 'brave',
+ 'breakable',
+ 'brief',
+ 'bright',
+ 'brilliant',
+ 'brisk',
+ 'broken',
+ 'bronze',
+ 'brown',
+ 'bruised',
+ 'bubbly',
+ 'bulky',
+ 'bumpy',
+ 'buoyant',
+ 'burdensome',
+ 'burly',
+ 'bustling',
+ 'busy',
+ 'buttery',
+ 'buzzing',
+ 'calculating',
+ 'calm',
+ 'candid',
+ 'canine',
+ 'capital',
+ 'carefree',
+ 'careful',
+ 'careless',
+ 'caring',
+ 'cautious',
+ 'cavernous',
+ 'celebrated',
+ 'charming',
+ 'cheap',
+ 'cheerful',
+ 'cheery',
+ 'chief',
+ 'chilly',
+ 'chubby',
+ 'circular',
+ 'classic',
+ 'clean',
+ 'clear',
+ 'clear-cut',
+ 'clever',
+ 'close',
+ 'closed',
+ 'cloudy',
+ 'clueless',
+ 'clumsy',
+ 'cluttered',
+ 'coarse',
+ 'cold',
+ 'colorful',
+ 'colorless',
+ 'colossal',
+ 'comfortable',
+ 'common',
+ 'compassionate',
+ 'competent',
+ 'complete',
+ 'complex',
+ 'complicated',
+ 'composed',
+ 'concerned',
+ 'concrete',
+ 'confused',
+ 'conscious',
+ 'considerate',
+ 'constant',
+ 'content',
+ 'conventional',
+ 'cooked',
+ 'cool',
+ 'cooperative',
+ 'coordinated',
+ 'corny',
+ 'corrupt',
+ 'costly',
+ 'courageous',
+ 'courteous',
+ 'crafty',
+ 'crazy',
+ 'creamy',
+ 'creative',
+ 'creepy',
+ 'criminal',
+ 'crisp',
+ 'critical',
+ 'crooked',
+ 'crowded',
+ 'cruel',
+ 'crushing',
+ 'cuddly',
+ 'cultivated',
+ 'cultured',
+ 'cumbersome',
+ 'curly',
+ 'curvy',
+ 'cute',
+ 'cylindrical',
+ 'damaged',
+ 'damp',
+ 'dangerous',
+ 'dapper',
+ 'daring',
+ 'darling',
+ 'dark',
+ 'dazzling',
+ 'dead',
+ 'deadly',
+ 'deafening',
+ 'dear',
+ 'dearest',
+ 'decent',
+ 'decimal',
+ 'decisive',
+ 'deep',
+ 'defenseless',
+ 'defensive',
+ 'defiant',
+ 'deficient',
+ 'definite',
+ 'definitive',
+ 'delayed',
+ 'delectable',
+ 'delicious',
+ 'delightful',
+ 'delirious',
+ 'demanding',
+ 'dense',
+ 'dental',
+ 'dependable',
+ 'dependent',
+ 'descriptive',
+ 'deserted',
+ 'detailed',
+ 'determined',
+ 'devoted',
+ 'different',
+ 'difficult',
+ 'digital',
+ 'diligent',
+ 'dim',
+ 'dimpled',
+ 'dimwitted',
+ 'direct',
+ 'disastrous',
+ 'discrete',
+ 'disfigured',
+ 'disgusting',
+ 'disloyal',
+ 'dismal',
+ 'distant',
+ 'downright',
+ 'dreary',
+ 'dirty',
+ 'disguised',
+ 'dishonest',
+ 'dismal',
+ 'distant',
+ 'distinct',
+ 'distorted',
+ 'dizzy',
+ 'dopey',
+ 'doting',
+ 'double',
+ 'downright',
+ 'drab',
+ 'drafty',
+ 'dramatic',
+ 'dreary',
+ 'droopy',
+ 'dry',
+ 'dual',
+ 'dull',
+ 'dutiful',
+ 'each',
+ 'eager',
+ 'earnest',
+ 'early',
+ 'easy',
+ 'easy-going',
+ 'ecstatic',
+ 'edible',
+ 'educated',
+ 'elaborate',
+ 'elastic',
+ 'elated',
+ 'elderly',
+ 'electric',
+ 'elegant',
+ 'elementary',
+ 'elliptical',
+ 'embarrassed',
+ 'embellished',
+ 'eminent',
+ 'emotional',
+ 'empty',
+ 'enchanted',
+ 'enchanting',
+ 'energetic',
+ 'enlightened',
+ 'enormous',
+ 'enraged',
+ 'entire',
+ 'envious',
+ 'equal',
+ 'equatorial',
+ 'essential',
+ 'esteemed',
+ 'ethical',
+ 'euphoric',
+ 'even',
+ 'evergreen',
+ 'everlasting',
+ 'every',
+ 'evil',
+ 'exalted',
+ 'excellent',
+ 'exemplary',
+ 'exhausted',
+ 'excitable',
+ 'excited',
+ 'exciting',
+ 'exotic',
+ 'expensive',
+ 'experienced',
+ 'expert',
+ 'extraneous',
+ 'extroverted',
+ 'extra-large',
+ 'extra-small',
+ 'fabulous',
+ 'failing',
+ 'faint',
+ 'fair',
+ 'faithful',
+ 'fake',
+ 'false',
+ 'familiar',
+ 'famous',
+ 'fancy',
+ 'fantastic',
+ 'far',
+ 'faraway',
+ 'far-flung',
+ 'far-off',
+ 'fast',
+ 'fat',
+ 'fatal',
+ 'fatherly',
+ 'favorable',
+ 'favorite',
+ 'fearful',
+ 'fearless',
+ 'feisty',
+ 'feline',
+ 'female',
+ 'feminine',
+ 'few',
+ 'fickle',
+ 'filthy',
+ 'fine',
+ 'finished',
+ 'firm',
+ 'first',
+ 'firsthand',
+ 'fitting',
+ 'fixed',
+ 'flaky',
+ 'flamboyant',
+ 'flashy',
+ 'flat',
+ 'flawed',
+ 'flawless',
+ 'flickering',
+ 'flimsy',
+ 'flippant',
+ 'flowery',
+ 'fluffy',
+ 'fluid',
+ 'flustered',
+ 'focused',
+ 'fond',
+ 'foolhardy',
+ 'foolish',
+ 'forceful',
+ 'forked',
+ 'formal',
+ 'forsaken',
+ 'forthright',
+ 'fortunate',
+ 'fragrant',
+ 'frail',
+ 'frank',
+ 'frayed',
+ 'free',
+ 'French',
+ 'fresh',
+ 'frequent',
+ 'friendly',
+ 'frightened',
+ 'frightening',
+ 'frigid',
+ 'frilly',
+ 'frizzy',
+ 'frivolous',
+ 'front',
+ 'frosty',
+ 'frozen',
+ 'frugal',
+ 'fruitful',
+ 'full',
+ 'fumbling',
+ 'functional',
+ 'funny',
+ 'fussy',
+ 'fuzzy',
+ 'gargantuan',
+ 'gaseous',
+ 'general',
+ 'generous',
+ 'gentle',
+ 'genuine',
+ 'giant',
+ 'giddy',
+ 'gigantic',
+ 'gifted',
+ 'giving',
+ 'glamorous',
+ 'glaring',
+ 'glass',
+ 'gleaming',
+ 'gleeful',
+ 'glistening',
+ 'glittering',
+ 'gloomy',
+ 'glorious',
+ 'glossy',
+ 'glum',
+ 'golden',
+ 'good',
+ 'good-natured',
+ 'gorgeous',
+ 'graceful',
+ 'gracious',
+ 'grand',
+ 'grandiose',
+ 'granular',
+ 'grateful',
+ 'grave',
+ 'gray',
+ 'great',
+ 'greedy',
+ 'green',
+ 'gregarious',
+ 'grim',
+ 'grimy',
+ 'gripping',
+ 'grizzled',
+ 'gross',
+ 'grotesque',
+ 'grouchy',
+ 'grounded',
+ 'growing',
+ 'growling',
+ 'grown',
+ 'grubby',
+ 'gruesome',
+ 'grumpy',
+ 'guilty',
+ 'gullible',
+ 'gummy',
+ 'hairy',
+ 'half',
+ 'handmade',
+ 'handsome',
+ 'handy',
+ 'happy',
+ 'happy-go-lucky',
+ 'hard',
+ 'hard-to-find',
+ 'harmful',
+ 'harmless',
+ 'harmonious',
+ 'harsh',
+ 'hasty',
+ 'hateful',
+ 'haunting',
+ 'healthy',
+ 'heartfelt',
+ 'hearty',
+ 'heavenly',
+ 'heavy',
+ 'hefty',
+ 'helpful',
+ 'helpless',
+ 'hidden',
+ 'hideous',
+ 'high',
+ 'high-level',
+ 'hilarious',
+ 'hoarse',
+ 'hollow',
+ 'homely',
+ 'honest',
+ 'honorable',
+ 'honored',
+ 'hopeful',
+ 'horrible',
+ 'hospitable',
+ 'hot',
+ 'huge',
+ 'humble',
+ 'humiliating',
+ 'humming',
+ 'humongous',
+ 'hungry',
+ 'hurtful',
+ 'husky',
+ 'icky',
+ 'icy',
+ 'ideal',
+ 'idealistic',
+ 'identical',
+ 'idle',
+ 'idiotic',
+ 'idolized',
+ 'ignorant',
+ 'ill',
+ 'illegal',
+ 'ill-fated',
+ 'ill-informed',
+ 'illiterate',
+ 'illustrious',
+ 'imaginary',
+ 'imaginative',
+ 'immaculate',
+ 'immaterial',
+ 'immediate',
+ 'immense',
+ 'impassioned',
+ 'impeccable',
+ 'impartial',
+ 'imperfect',
+ 'imperturbable',
+ 'impish',
+ 'impolite',
+ 'important',
+ 'impossible',
+ 'impractical',
+ 'impressionable',
+ 'impressive',
+ 'improbable',
+ 'impure',
+ 'inborn',
+ 'incomparable',
+ 'incompatible',
+ 'incomplete',
+ 'inconsequential',
+ 'incredible',
+ 'indelible',
+ 'inexperienced',
+ 'indolent',
+ 'infamous',
+ 'infantile',
+ 'infatuated',
+ 'inferior',
+ 'infinite',
+ 'informal',
+ 'innocent',
+ 'insecure',
+ 'insidious',
+ 'insignificant',
+ 'insistent',
+ 'instructive',
+ 'insubstantial',
+ 'intelligent',
+ 'intent',
+ 'intentional',
+ 'interesting',
+ 'internal',
+ 'international',
+ 'intrepid',
+ 'ironclad',
+ 'irresponsible',
+ 'irritating',
+ 'itchy',
+ 'jaded',
+ 'jagged',
+ 'jam-packed',
+ 'jaunty',
+ 'jealous',
+ 'jittery',
+ 'joint',
+ 'jolly',
+ 'jovial',
+ 'joyful',
+ 'joyous',
+ 'jubilant',
+ 'judicious',
+ 'juicy',
+ 'jumbo',
+ 'junior',
+ 'jumpy',
+ 'juvenile',
+ 'kaleidoscopic',
+ 'keen',
+ 'key',
+ 'kind',
+ 'kindhearted',
+ 'kindly',
+ 'klutzy',
+ 'knobby',
+ 'knotty',
+ 'knowledgeable',
+ 'knowing',
+ 'known',
+ 'kooky',
+ 'kosher',
+ 'lame',
+ 'lanky',
+ 'large',
+ 'last',
+ 'lasting',
+ 'late',
+ 'lavish',
+ 'lawful',
+ 'lazy',
+ 'leading',
+ 'lean',
+ 'leafy',
+ 'left',
+ 'legal',
+ 'legitimate',
+ 'light',
+ 'lighthearted',
+ 'likable',
+ 'likely',
+ 'limited',
+ 'limp',
+ 'limping',
+ 'linear',
+ 'lined',
+ 'liquid',
+ 'little',
+ 'live',
+ 'lively',
+ 'livid',
+ 'loathsome',
+ 'lone',
+ 'lonely',
+ 'long',
+ 'long-term',
+ 'loose',
+ 'lopsided',
+ 'lost',
+ 'loud',
+ 'lovable',
+ 'lovely',
+ 'loving',
+ 'low',
+ 'loyal',
+ 'lucky',
+ 'lumbering',
+ 'luminous',
+ 'lumpy',
+ 'lustrous',
+ 'luxurious',
+ 'mad',
+ 'made-up',
+ 'magnificent',
+ 'majestic',
+ 'major',
+ 'male',
+ 'mammoth',
+ 'married',
+ 'marvelous',
+ 'masculine',
+ 'massive',
+ 'mature',
+ 'meager',
+ 'mealy',
+ 'mean',
+ 'measly',
+ 'meaty',
+ 'medical',
+ 'mediocre',
+ 'medium',
+ 'meek',
+ 'mellow',
+ 'melodic',
+ 'memorable',
+ 'menacing',
+ 'merry',
+ 'messy',
+ 'metallic',
+ 'mild',
+ 'milky',
+ 'mindless',
+ 'miniature',
+ 'minor',
+ 'minty',
+ 'miserable',
+ 'miserly',
+ 'misguided',
+ 'misty',
+ 'mixed',
+ 'modern',
+ 'modest',
+ 'moist',
+ 'monstrous',
+ 'monthly',
+ 'monumental',
+ 'moral',
+ 'mortified',
+ 'motherly',
+ 'motionless',
+ 'mountainous',
+ 'muddy',
+ 'muffled',
+ 'multicolored',
+ 'mundane',
+ 'murky',
+ 'mushy',
+ 'musty',
+ 'muted',
+ 'mysterious',
+ 'naive',
+ 'narrow',
+ 'nasty',
+ 'natural',
+ 'naughty',
+ 'nautical',
+ 'near',
+ 'neat',
+ 'necessary',
+ 'needy',
+ 'negative',
+ 'neglected',
+ 'negligible',
+ 'neighboring',
+ 'nervous',
+ 'new',
+ 'next',
+ 'nice',
+ 'nifty',
+ 'nimble',
+ 'nippy',
+ 'nocturnal',
+ 'noisy',
+ 'nonstop',
+ 'normal',
+ 'notable',
+ 'noted',
+ 'noteworthy',
+ 'novel',
+ 'noxious',
+ 'numb',
+ 'nutritious',
+ 'nutty',
+ 'obedient',
+ 'obese',
+ 'oblong',
+ 'oily',
+ 'oblong',
+ 'obvious',
+ 'occasional',
+ 'odd',
+ 'oddball',
+ 'offbeat',
+ 'offensive',
+ 'official',
+ 'old',
+ 'old-fashioned',
+ 'only',
+ 'open',
+ 'optimal',
+ 'optimistic',
+ 'opulent',
+ 'orange',
+ 'orderly',
+ 'organic',
+ 'ornate',
+ 'ornery',
+ 'ordinary',
+ 'original',
+ 'other',
+ 'our',
+ 'outlying',
+ 'outgoing',
+ 'outlandish',
+ 'outrageous',
+ 'outstanding',
+ 'oval',
+ 'overcooked',
+ 'overdue',
+ 'overjoyed',
+ 'overlooked',
+ 'palatable',
+ 'pale',
+ 'paltry',
+ 'parallel',
+ 'parched',
+ 'partial',
+ 'passionate',
+ 'past',
+ 'pastel',
+ 'peaceful',
+ 'peppery',
+ 'perfect',
+ 'perfumed',
+ 'periodic',
+ 'perky',
+ 'personal',
+ 'pertinent',
+ 'pesky',
+ 'pessimistic',
+ 'petty',
+ 'phony',
+ 'physical',
+ 'piercing',
+ 'pink',
+ 'pitiful',
+ 'plain',
+ 'plaintive',
+ 'plastic',
+ 'playful',
+ 'pleasant',
+ 'pleased',
+ 'pleasing',
+ 'plump',
+ 'plush',
+ 'polished',
+ 'polite',
+ 'political',
+ 'pointed',
+ 'pointless',
+ 'poised',
+ 'poor',
+ 'popular',
+ 'portly',
+ 'posh',
+ 'positive',
+ 'possible',
+ 'potable',
+ 'powerful',
+ 'powerless',
+ 'practical',
+ 'precious',
+ 'present',
+ 'prestigious',
+ 'pretty',
+ 'precious',
+ 'previous',
+ 'pricey',
+ 'prickly',
+ 'primary',
+ 'prime',
+ 'pristine',
+ 'private',
+ 'prize',
+ 'probable',
+ 'productive',
+ 'profitable',
+ 'profuse',
+ 'proper',
+ 'proud',
+ 'prudent',
+ 'punctual',
+ 'pungent',
+ 'puny',
+ 'pure',
+ 'purple',
+ 'pushy',
+ 'putrid',
+ 'puzzled',
+ 'puzzling',
+ 'quaint',
+ 'qualified',
+ 'quarrelsome',
+ 'quarterly',
+ 'queasy',
+ 'querulous',
+ 'questionable',
+ 'quick',
+ 'quick-witted',
+ 'quiet',
+ 'quintessential',
+ 'quirky',
+ 'quixotic',
+ 'quizzical',
+ 'radiant',
+ 'ragged',
+ 'rapid',
+ 'rare',
+ 'rash',
+ 'raw',
+ 'recent',
+ 'reckless',
+ 'rectangular',
+ 'ready',
+ 'real',
+ 'realistic',
+ 'reasonable',
+ 'red',
+ 'reflecting',
+ 'regal',
+ 'regular',
+ 'reliable',
+ 'relieved',
+ 'remarkable',
+ 'remorseful',
+ 'remote',
+ 'repentant',
+ 'required',
+ 'respectful',
+ 'responsible',
+ 'repulsive',
+ 'revolving',
+ 'rewarding',
+ 'rich',
+ 'rigid',
+ 'right',
+ 'ringed',
+ 'ripe',
+ 'roasted',
+ 'robust',
+ 'rosy',
+ 'rotating',
+ 'rotten',
+ 'rough',
+ 'round',
+ 'rowdy',
+ 'royal',
+ 'rubbery',
+ 'rundown',
+ 'ruddy',
+ 'rude',
+ 'runny',
+ 'rural',
+ 'rusty',
+ 'sad',
+ 'safe',
+ 'salty',
+ 'same',
+ 'sandy',
+ 'sane',
+ 'sarcastic',
+ 'sardonic',
+ 'satisfied',
+ 'scaly',
+ 'scarce',
+ 'scared',
+ 'scary',
+ 'scented',
+ 'scholarly',
+ 'scientific',
+ 'scornful',
+ 'scratchy',
+ 'scrawny',
+ 'second',
+ 'secondary',
+ 'second-hand',
+ 'secret',
+ 'self-assured',
+ 'self-reliant',
+ 'selfish',
+ 'sentimental',
+ 'separate',
+ 'serene',
+ 'serious',
+ 'serpentine',
+ 'several',
+ 'severe',
+ 'shabby',
+ 'shadowy',
+ 'shady',
+ 'shallow',
+ 'shameful',
+ 'shameless',
+ 'sharp',
+ 'shimmering',
+ 'shiny',
+ 'shocked',
+ 'shocking',
+ 'shoddy',
+ 'short',
+ 'short-term',
+ 'showy',
+ 'shrill',
+ 'shy',
+ 'sick',
+ 'silent',
+ 'silky',
+ 'silly',
+ 'silver',
+ 'similar',
+ 'simple',
+ 'simplistic',
+ 'sinful',
+ 'single',
+ 'sizzling',
+ 'skeletal',
+ 'skinny',
+ 'sleepy',
+ 'slight',
+ 'slim',
+ 'slimy',
+ 'slippery',
+ 'slow',
+ 'slushy',
+ 'small',
+ 'smart',
+ 'smoggy',
+ 'smooth',
+ 'smug',
+ 'snappy',
+ 'snarling',
+ 'sneaky',
+ 'sniveling',
+ 'snoopy',
+ 'sociable',
+ 'soft',
+ 'soggy',
+ 'solid',
+ 'somber',
+ 'some',
+ 'spherical',
+ 'sophisticated',
+ 'sore',
+ 'sorrowful',
+ 'soulful',
+ 'soupy',
+ 'sour',
+ 'Spanish',
+ 'sparkling',
+ 'sparse',
+ 'specific',
+ 'spectacular',
+ 'speedy',
+ 'spicy',
+ 'spiffy',
+ 'spirited',
+ 'spiteful',
+ 'splendid',
+ 'spotless',
+ 'spotted',
+ 'spry',
+ 'square',
+ 'squeaky',
+ 'squiggly',
+ 'stable',
+ 'staid',
+ 'stained',
+ 'stale',
+ 'standard',
+ 'starchy',
+ 'stark',
+ 'starry',
+ 'steep',
+ 'sticky',
+ 'stiff',
+ 'stimulating',
+ 'stingy',
+ 'stormy',
+ 'straight',
+ 'strange',
+ 'steel',
+ 'strict',
+ 'strident',
+ 'striking',
+ 'striped',
+ 'strong',
+ 'studious',
+ 'stunning',
+ 'stupendous',
+ 'stupid',
+ 'sturdy',
+ 'stylish',
+ 'subdued',
+ 'submissive',
+ 'substantial',
+ 'subtle',
+ 'suburban',
+ 'sudden',
+ 'sugary',
+ 'sunny',
+ 'super',
+ 'superb',
+ 'superficial',
+ 'superior',
+ 'supportive',
+ 'sure-footed',
+ 'surprised',
+ 'suspicious',
+ 'svelte',
+ 'sweaty',
+ 'sweet',
+ 'sweltering',
+ 'swift',
+ 'sympathetic',
+ 'tall',
+ 'talkative',
+ 'tame',
+ 'tan',
+ 'tangible',
+ 'tart',
+ 'tasty',
+ 'tattered',
+ 'taut',
+ 'tedious',
+ 'teeming',
+ 'tempting',
+ 'tender',
+ 'tense',
+ 'tepid',
+ 'terrible',
+ 'terrific',
+ 'testy',
+ 'thankful',
+ 'that',
+ 'these',
+ 'thick',
+ 'thin',
+ 'third',
+ 'thirsty',
+ 'this',
+ 'thorough',
+ 'thorny',
+ 'those',
+ 'thoughtful',
+ 'threadbare',
+ 'thrifty',
+ 'thunderous',
+ 'tidy',
+ 'tight',
+ 'timely',
+ 'tinted',
+ 'tiny',
+ 'tired',
+ 'torn',
+ 'total',
+ 'tough',
+ 'traumatic',
+ 'treasured',
+ 'tremendous',
+ 'tragic',
+ 'trained',
+ 'tremendous',
+ 'triangular',
+ 'tricky',
+ 'trifling',
+ 'trim',
+ 'trivial',
+ 'troubled',
+ 'true',
+ 'trusting',
+ 'trustworthy',
+ 'trusty',
+ 'truthful',
+ 'tubby',
+ 'turbulent',
+ 'twin',
+ 'ugly',
+ 'ultimate',
+ 'unacceptable',
+ 'unaware',
+ 'uncomfortable',
+ 'uncommon',
+ 'unconscious',
+ 'understated',
+ 'unequaled',
+ 'uneven',
+ 'unfinished',
+ 'unfit',
+ 'unfolded',
+ 'unfortunate',
+ 'unhappy',
+ 'unhealthy',
+ 'uniform',
+ 'unimportant',
+ 'unique',
+ 'united',
+ 'unkempt',
+ 'unknown',
+ 'unlawful',
+ 'unlined',
+ 'unlucky',
+ 'unnatural',
+ 'unpleasant',
+ 'unrealistic',
+ 'unripe',
+ 'unruly',
+ 'unselfish',
+ 'unsightly',
+ 'unsteady',
+ 'unsung',
+ 'untidy',
+ 'untimely',
+ 'untried',
+ 'untrue',
+ 'unused',
+ 'unusual',
+ 'unwelcome',
+ 'unwieldy',
+ 'unwilling',
+ 'unwitting',
+ 'unwritten',
+ 'upbeat',
+ 'upright',
+ 'upset',
+ 'urban',
+ 'usable',
+ 'used',
+ 'useful',
+ 'useless',
+ 'utilized',
+ 'utter',
+ 'vacant',
+ 'vague',
+ 'vain',
+ 'valid',
+ 'valuable',
+ 'vapid',
+ 'variable',
+ 'vast',
+ 'velvety',
+ 'venerated',
+ 'vengeful',
+ 'verifiable',
+ 'vibrant',
+ 'vicious',
+ 'victorious',
+ 'vigilant',
+ 'vigorous',
+ 'villainous',
+ 'violet',
+ 'violent',
+ 'virtual',
+ 'virtuous',
+ 'visible',
+ 'vital',
+ 'vivacious',
+ 'vivid',
+ 'voluminous',
+ 'wan',
+ 'warlike',
+ 'warm',
+ 'warmhearted',
+ 'warped',
+ 'wary',
+ 'wasteful',
+ 'watchful',
+ 'waterlogged',
+ 'watery',
+ 'wavy',
+ 'wealthy',
+ 'weak',
+ 'weary',
+ 'webbed',
+ 'wee',
+ 'weekly',
+ 'weepy',
+ 'weighty',
+ 'weird',
+ 'welcome',
+ 'well-documented',
+ 'well-groomed',
+ 'well-informed',
+ 'well-lit',
+ 'well-made',
+ 'well-off',
+ 'well-to-do',
+ 'well-worn',
+ 'wet',
+ 'which',
+ 'whimsical',
+ 'whirlwind',
+ 'whispered',
+ 'white',
+ 'whole',
+ 'whopping',
+ 'wicked',
+ 'wide',
+ 'wide-eyed',
+ 'wiggly',
+ 'wild',
+ 'willing',
+ 'wilted',
+ 'winding',
+ 'windy',
+ 'winged',
+ 'wiry',
+ 'wise',
+ 'witty',
+ 'wobbly',
+ 'woeful',
+ 'wonderful',
+ 'wooden',
+ 'woozy',
+ 'wordy',
+ 'worldly',
+ 'worn',
+ 'worried',
+ 'worrisome',
+ 'worse',
+ 'worst',
+ 'worthless',
+ 'worthwhile',
+ 'worthy',
+ 'wrathful',
+ 'wretched',
+ 'writhing',
+ 'wrong',
+ 'wry',
+ 'yawning',
+ 'yearly',
+ 'yellow',
+ 'yellowish',
+ 'young',
+ 'youthful',
+ 'yummy',
+ 'zany',
+ 'zealous',
+ 'zesty',
+ 'zigzag',
+ 'rocky',
+];
+
+const object = [
+ 'people',
+ 'history',
+ 'way',
+ 'art',
+ 'world',
+ 'information',
+ 'map',
+ 'family',
+ 'government',
+ 'health',
+ 'system',
+ 'computer',
+ 'meat',
+ 'year',
+ 'thanks',
+ 'music',
+ 'person',
+ 'reading',
+ 'method',
+ 'data',
+ 'food',
+ 'understanding',
+ 'theory',
+ 'law',
+ 'bird',
+ 'literature',
+ 'problem',
+ 'software',
+ 'control',
+ 'knowledge',
+ 'power',
+ 'ability',
+ 'economics',
+ 'love',
+ 'internet',
+ 'television',
+ 'science',
+ 'library',
+ 'nature',
+ 'fact',
+ 'product',
+ 'idea',
+ 'temperature',
+ 'investment',
+ 'area',
+ 'society',
+ 'activity',
+ 'story',
+ 'industry',
+ 'media',
+ 'thing',
+ 'oven',
+ 'community',
+ 'definition',
+ 'safety',
+ 'quality',
+ 'development',
+ 'language',
+ 'management',
+ 'player',
+ 'variety',
+ 'video',
+ 'week',
+ 'security',
+ 'country',
+ 'exam',
+ 'movie',
+ 'organization',
+ 'equipment',
+ 'physics',
+ 'analysis',
+ 'policy',
+ 'series',
+ 'thought',
+ 'basis',
+ 'boyfriend',
+ 'direction',
+ 'strategy',
+ 'technology',
+ 'army',
+ 'camera',
+ 'freedom',
+ 'paper',
+ 'environment',
+ 'child',
+ 'instance',
+ 'month',
+ 'truth',
+ 'marketing',
+ 'university',
+ 'writing',
+ 'article',
+ 'department',
+ 'difference',
+ 'goal',
+ 'news',
+ 'audience',
+ 'fishing',
+ 'growth',
+ 'income',
+ 'marriage',
+ 'user',
+ 'combination',
+ 'failure',
+ 'meaning',
+ 'medicine',
+ 'philosophy',
+ 'teacher',
+ 'communication',
+ 'night',
+ 'chemistry',
+ 'disease',
+ 'disk',
+ 'energy',
+ 'nation',
+ 'road',
+ 'role',
+ 'soup',
+ 'advertising',
+ 'location',
+ 'success',
+ 'addition',
+ 'apartment',
+ 'education',
+ 'math',
+ 'moment',
+ 'painting',
+ 'politics',
+ 'attention',
+ 'decision',
+ 'event',
+ 'property',
+ 'shopping',
+ 'student',
+ 'wood',
+ 'competition',
+ 'distribution',
+ 'entertainment',
+ 'office',
+ 'population',
+ 'president',
+ 'unit',
+ 'category',
+ 'cigarette',
+ 'context',
+ 'introduction',
+ 'opportunity',
+ 'performance',
+ 'driver',
+ 'flight',
+ 'length',
+ 'magazine',
+ 'newspaper',
+ 'relationship',
+ 'teaching',
+ 'cell',
+ 'dealer',
+ 'debate',
+ 'finding',
+ 'lake',
+ 'member',
+ 'message',
+ 'phone',
+ 'scene',
+ 'appearance',
+ 'association',
+ 'concept',
+ 'customer',
+ 'death',
+ 'discussion',
+ 'housing',
+ 'inflation',
+ 'insurance',
+ 'mood',
+ 'woman',
+ 'advice',
+ 'blood',
+ 'effort',
+ 'expression',
+ 'importance',
+ 'opinion',
+ 'payment',
+ 'reality',
+ 'responsibility',
+ 'situation',
+ 'skill',
+ 'statement',
+ 'wealth',
+ 'application',
+ 'city',
+ 'county',
+ 'depth',
+ 'estate',
+ 'foundation',
+ 'grandmother',
+ 'heart',
+ 'perspective',
+ 'photo',
+ 'recipe',
+ 'studio',
+ 'topic',
+ 'collection',
+ 'depression',
+ 'imagination',
+ 'passion',
+ 'percentage',
+ 'resource',
+ 'setting',
+ 'ad',
+ 'agency',
+ 'college',
+ 'connection',
+ 'criticism',
+ 'debt',
+ 'description',
+ 'memory',
+ 'patience',
+ 'secretary',
+ 'solution',
+ 'administration',
+ 'aspect',
+ 'attitude',
+ 'director',
+ 'personality',
+ 'psychology',
+ 'recommendation',
+ 'response',
+ 'selection',
+ 'storage',
+ 'version',
+ 'alcohol',
+ 'argument',
+ 'complaint',
+ 'contract',
+ 'emphasis',
+ 'highway',
+ 'loss',
+ 'membership',
+ 'possession',
+ 'preparation',
+ 'steak',
+ 'union',
+ 'agreement',
+ 'cancer',
+ 'currency',
+ 'employment',
+ 'engineering',
+ 'entry',
+ 'interaction',
+ 'limit',
+ 'mixture',
+ 'preference',
+ 'region',
+ 'republic',
+ 'seat',
+ 'tradition',
+ 'virus',
+ 'actor',
+ 'classroom',
+ 'delivery',
+ 'device',
+ 'difficulty',
+ 'drama',
+ 'election',
+ 'engine',
+ 'football',
+ 'guidance',
+ 'hotel',
+ 'match',
+ 'owner',
+ 'priority',
+ 'protection',
+ 'suggestion',
+ 'tension',
+ 'variation',
+ 'anxiety',
+ 'atmosphere',
+ 'awareness',
+ 'bread',
+ 'climate',
+ 'comparison',
+ 'confusion',
+ 'construction',
+ 'elevator',
+ 'emotion',
+ 'employee',
+ 'employer',
+ 'guest',
+ 'height',
+ 'leadership',
+ 'mall',
+ 'manager',
+ 'operation',
+ 'recording',
+ 'respect',
+ 'sample',
+ 'transportation',
+ 'boring',
+ 'charity',
+ 'cousin',
+ 'disaster',
+ 'editor',
+ 'efficiency',
+ 'excitement',
+ 'extent',
+ 'feedback',
+ 'guitar',
+ 'homework',
+ 'leader',
+ 'mom',
+ 'outcome',
+ 'permission',
+ 'presentation',
+ 'promotion',
+ 'reflection',
+ 'refrigerator',
+ 'resolution',
+ 'revenue',
+ 'session',
+ 'singer',
+ 'tennis',
+ 'basket',
+ 'bonus',
+ 'cabinet',
+ 'childhood',
+ 'church',
+ 'clothes',
+ 'coffee',
+ 'dinner',
+ 'drawing',
+ 'hair',
+ 'hearing',
+ 'initiative',
+ 'judgment',
+ 'lab',
+ 'measurement',
+ 'mode',
+ 'mud',
+ 'orange',
+ 'poetry',
+ 'police',
+ 'possibility',
+ 'procedure',
+ 'queen',
+ 'ratio',
+ 'relation',
+ 'restaurant',
+ 'satisfaction',
+ 'sector',
+ 'signature',
+ 'significance',
+ 'song',
+ 'tooth',
+ 'town',
+ 'vehicle',
+ 'volume',
+ 'wife',
+ 'accident',
+ 'airport',
+ 'appointment',
+ 'arrival',
+ 'assumption',
+ 'baseball',
+ 'chapter',
+ 'committee',
+ 'conversation',
+ 'database',
+ 'enthusiasm',
+ 'error',
+ 'explanation',
+ 'farmer',
+ 'gate',
+ 'girl',
+ 'hall',
+ 'historian',
+ 'hospital',
+ 'injury',
+ 'instruction',
+ 'maintenance',
+ 'manufacturer',
+ 'meal',
+ 'perception',
+ 'pie',
+ 'poem',
+ 'presence',
+ 'proposal',
+ 'reception',
+ 'replacement',
+ 'revolution',
+ 'river',
+ 'son',
+ 'speech',
+ 'tea',
+ 'village',
+ 'warning',
+ 'winner',
+ 'worker',
+ 'writer',
+ 'assistance',
+ 'breath',
+ 'buyer',
+ 'chest',
+ 'chocolate',
+ 'conclusion',
+ 'contribution',
+ 'cookie',
+ 'courage',
+ 'desk',
+ 'drawer',
+ 'establishment',
+ 'examination',
+ 'garbage',
+ 'grocery',
+ 'honey',
+ 'impression',
+ 'improvement',
+ 'independence',
+ 'insect',
+ 'inspection',
+ 'inspector',
+ 'king',
+ 'ladder',
+ 'menu',
+ 'penalty',
+ 'piano',
+ 'potato',
+ 'profession',
+ 'professor',
+ 'quantity',
+ 'reaction',
+ 'requirement',
+ 'salad',
+ 'sister',
+ 'supermarket',
+ 'tongue',
+ 'weakness',
+ 'wedding',
+ 'affair',
+ 'ambition',
+ 'analyst',
+ 'apple',
+ 'assignment',
+ 'assistant',
+ 'bathroom',
+ 'bedroom',
+ 'beer',
+ 'birthday',
+ 'celebration',
+ 'championship',
+ 'cheek',
+ 'client',
+ 'consequence',
+ 'departure',
+ 'diamond',
+ 'dirt',
+ 'ear',
+ 'fortune',
+ 'friendship',
+ 'funeral',
+ 'gene',
+ 'girlfriend',
+ 'hat',
+ 'indication',
+ 'intention',
+ 'lady',
+ 'midnight',
+ 'negotiation',
+ 'obligation',
+ 'passenger',
+ 'pizza',
+ 'platform',
+ 'poet',
+ 'pollution',
+ 'recognition',
+ 'reputation',
+ 'shirt',
+ 'speaker',
+ 'stranger',
+ 'surgery',
+ 'sympathy',
+ 'tale',
+ 'throat',
+ 'trainer',
+ 'uncle',
+ 'youth',
+ 'time',
+ 'work',
+ 'film',
+ 'water',
+ 'money',
+ 'example',
+ 'while',
+ 'business',
+ 'study',
+ 'game',
+ 'life',
+ 'form',
+ 'air',
+ 'day',
+ 'place',
+ 'number',
+ 'part',
+ 'field',
+ 'fish',
+ 'back',
+ 'process',
+ 'heat',
+ 'hand',
+ 'experience',
+ 'job',
+ 'book',
+ 'end',
+ 'point',
+ 'type',
+ 'home',
+ 'economy',
+ 'value',
+ 'body',
+ 'market',
+ 'guide',
+ 'interest',
+ 'state',
+ 'radio',
+ 'course',
+ 'company',
+ 'price',
+ 'size',
+ 'card',
+ 'list',
+ 'mind',
+ 'trade',
+ 'line',
+ 'care',
+ 'group',
+ 'risk',
+ 'word',
+ 'fat',
+ 'force',
+ 'key',
+ 'light',
+ 'training',
+ 'name',
+ 'school',
+ 'top',
+ 'amount',
+ 'level',
+ 'order',
+ 'practice',
+ 'research',
+ 'sense',
+ 'service',
+ 'piece',
+ 'web',
+ 'boss',
+ 'sport',
+ 'fun',
+ 'house',
+ 'page',
+ 'term',
+ 'test',
+ 'answer',
+ 'sound',
+ 'focus',
+ 'matter',
+ 'kind',
+ 'soil',
+ 'board',
+ 'oil',
+ 'picture',
+ 'access',
+ 'garden',
+ 'range',
+ 'rate',
+ 'reason',
+ 'future',
+ 'site',
+ 'demand',
+ 'exercise',
+ 'image',
+ 'case',
+ 'cause',
+ 'coast',
+ 'action',
+ 'age',
+ 'bad',
+ 'boat',
+ 'record',
+ 'result',
+ 'section',
+ 'building',
+ 'mouse',
+ 'cash',
+ 'class',
+ 'period',
+ 'plan',
+ 'store',
+ 'tax',
+ 'side',
+ 'subject',
+ 'space',
+ 'rule',
+ 'stock',
+ 'weather',
+ 'chance',
+ 'figure',
+ 'man',
+ 'model',
+ 'source',
+ 'beginning',
+ 'earth',
+ 'program',
+ 'chicken',
+ 'design',
+ 'feature',
+ 'head',
+ 'material',
+ 'purpose',
+ 'question',
+ 'rock',
+ 'salt',
+ 'act',
+ 'birth',
+ 'car',
+ 'dog',
+ 'object',
+ 'scale',
+ 'sun',
+ 'note',
+ 'profit',
+ 'rent',
+ 'speed',
+ 'style',
+ 'war',
+ 'bank',
+ 'craft',
+ 'half',
+ 'inside',
+ 'outside',
+ 'standard',
+ 'bus',
+ 'exchange',
+ 'eye',
+ 'fire',
+ 'position',
+ 'pressure',
+ 'stress',
+ 'advantage',
+ 'benefit',
+ 'box',
+ 'frame',
+ 'issue',
+ 'step',
+ 'cycle',
+ 'face',
+ 'item',
+ 'metal',
+ 'paint',
+ 'review',
+ 'room',
+ 'screen',
+ 'structure',
+ 'view',
+ 'account',
+ 'ball',
+ 'discipline',
+ 'medium',
+ 'share',
+ 'balance',
+ 'bit',
+ 'black',
+ 'bottom',
+ 'choice',
+ 'gift',
+ 'impact',
+ 'machine',
+ 'shape',
+ 'tool',
+ 'wind',
+ 'address',
+ 'average',
+ 'career',
+ 'culture',
+ 'morning',
+ 'pot',
+ 'sign',
+ 'table',
+ 'task',
+ 'condition',
+ 'contact',
+ 'credit',
+ 'egg',
+ 'hope',
+ 'ice',
+ 'network',
+ 'north',
+ 'square',
+ 'attempt',
+ 'date',
+ 'effect',
+ 'link',
+ 'post',
+ 'star',
+ 'voice',
+ 'capital',
+ 'challenge',
+ 'friend',
+ 'self',
+ 'shot',
+ 'brush',
+ 'couple',
+ 'exit',
+ 'front',
+ 'function',
+ 'lack',
+ 'living',
+ 'plant',
+ 'plastic',
+ 'spot',
+ 'summer',
+ 'taste',
+ 'theme',
+ 'track',
+ 'wing',
+ 'brain',
+ 'button',
+ 'click',
+ 'desire',
+ 'foot',
+ 'gas',
+ 'influence',
+ 'notice',
+ 'rain',
+ 'wall',
+ 'base',
+ 'damage',
+ 'distance',
+ 'feeling',
+ 'pair',
+ 'savings',
+ 'staff',
+ 'sugar',
+ 'target',
+ 'text',
+ 'animal',
+ 'author',
+ 'budget',
+ 'discount',
+ 'file',
+ 'ground',
+ 'lesson',
+ 'minute',
+ 'officer',
+ 'phase',
+ 'reference',
+ 'register',
+ 'sky',
+ 'stage',
+ 'stick',
+ 'title',
+ 'trouble',
+ 'bowl',
+ 'bridge',
+ 'campaign',
+ 'character',
+ 'club',
+ 'edge',
+ 'evidence',
+ 'fan',
+ 'letter',
+ 'lock',
+ 'maximum',
+ 'novel',
+ 'option',
+ 'pack',
+ 'park',
+ 'quarter',
+ 'skin',
+ 'sort',
+ 'weight',
+ 'baby',
+ 'background',
+ 'carry',
+ 'dish',
+ 'factor',
+ 'fruit',
+ 'glass',
+ 'joint',
+ 'master',
+ 'muscle',
+ 'red',
+ 'strength',
+ 'traffic',
+ 'trip',
+ 'vegetable',
+ 'appeal',
+ 'chart',
+ 'gear',
+ 'ideal',
+ 'kitchen',
+ 'land',
+ 'log',
+ 'mother',
+ 'net',
+ 'party',
+ 'principle',
+ 'relative',
+ 'sale',
+ 'season',
+ 'signal',
+ 'spirit',
+ 'street',
+ 'tree',
+ 'wave',
+ 'belt',
+ 'bench',
+ 'commission',
+ 'copy',
+ 'drop',
+ 'minimum',
+ 'path',
+ 'progress',
+ 'project',
+ 'sea',
+ 'south',
+ 'status',
+ 'stuff',
+ 'ticket',
+ 'tour',
+ 'angle',
+ 'blue',
+ 'breakfast',
+ 'confidence',
+ 'daughter',
+ 'degree',
+ 'doctor',
+ 'dot',
+ 'dream',
+ 'duty',
+ 'essay',
+ 'father',
+ 'fee',
+ 'finance',
+ 'hour',
+ 'juice',
+ 'luck',
+ 'milk',
+ 'mouth',
+ 'peace',
+ 'pipe',
+ 'stable',
+ 'storm',
+ 'substance',
+ 'team',
+ 'trick',
+ 'afternoon',
+ 'bat',
+ 'beach',
+ 'blank',
+ 'catch',
+ 'chain',
+ 'consideration',
+ 'cream',
+ 'crew',
+ 'detail',
+ 'gold',
+ 'interview',
+ 'kid',
+ 'mark',
+ 'mission',
+ 'pain',
+ 'pleasure',
+ 'score',
+ 'screw',
+ 'sex',
+ 'shop',
+ 'shower',
+ 'suit',
+ 'tone',
+ 'window',
+ 'agent',
+ 'band',
+ 'bath',
+ 'block',
+ 'bone',
+ 'calendar',
+ 'candidate',
+ 'cap',
+ 'coat',
+ 'contest',
+ 'corner',
+ 'court',
+ 'cup',
+ 'district',
+ 'door',
+ 'east',
+ 'finger',
+ 'garage',
+ 'guarantee',
+ 'hole',
+ 'hook',
+ 'implement',
+ 'layer',
+ 'lecture',
+ 'lie',
+ 'manner',
+ 'meeting',
+ 'nose',
+ 'parking',
+ 'partner',
+ 'profile',
+ 'rice',
+ 'routine',
+ 'schedule',
+ 'swimming',
+ 'telephone',
+ 'tip',
+ 'winter',
+ 'airline',
+ 'bag',
+ 'battle',
+ 'bed',
+ 'bill',
+ 'bother',
+ 'cake',
+ 'code',
+ 'curve',
+ 'designer',
+ 'dimension',
+ 'dress',
+ 'ease',
+ 'emergency',
+ 'evening',
+ 'extension',
+ 'farm',
+ 'fight',
+ 'gap',
+ 'grade',
+ 'holiday',
+ 'horror',
+ 'horse',
+ 'host',
+ 'husband',
+ 'loan',
+ 'mistake',
+ 'mountain',
+ 'nail',
+ 'noise',
+ 'occasion',
+ 'package',
+ 'patient',
+ 'pause',
+ 'phrase',
+ 'proof',
+ 'race',
+ 'relief',
+ 'sand',
+ 'sentence',
+ 'shoulder',
+ 'smoke',
+ 'stomach',
+ 'string',
+ 'tourist',
+ 'towel',
+ 'vacation',
+ 'west',
+ 'wheel',
+ 'wine',
+ 'arm',
+ 'aside',
+ 'associate',
+ 'bet',
+ 'blow',
+ 'border',
+ 'branch',
+ 'breast',
+ 'brother',
+ 'buddy',
+ 'bunch',
+ 'chip',
+ 'coach',
+ 'cross',
+ 'document',
+ 'draft',
+ 'dust',
+ 'expert',
+ 'floor',
+ 'god',
+ 'golf',
+ 'habit',
+ 'iron',
+ 'judge',
+ 'knife',
+ 'landscape',
+ 'league',
+ 'mail',
+ 'mess',
+ 'native',
+ 'opening',
+ 'parent',
+ 'pattern',
+ 'pin',
+ 'pool',
+ 'pound',
+ 'request',
+ 'salary',
+ 'shame',
+ 'shelter',
+ 'shoe',
+ 'silver',
+ 'tackle',
+ 'tank',
+ 'trust',
+ 'assist',
+ 'bake',
+ 'bar',
+ 'bell',
+ 'bike',
+ 'blame',
+ 'boy',
+ 'brick',
+ 'chair',
+ 'closet',
+ 'clue',
+ 'collar',
+ 'comment',
+ 'conference',
+ 'devil',
+ 'diet',
+ 'fear',
+ 'fuel',
+ 'glove',
+ 'jacket',
+ 'lunch',
+ 'monitor',
+ 'mortgage',
+ 'nurse',
+ 'pace',
+ 'panic',
+ 'peak',
+ 'plane',
+ 'reward',
+ 'row',
+ 'sandwich',
+ 'shock',
+ 'spite',
+ 'spray',
+ 'surprise',
+ 'till',
+ 'transition',
+ 'weekend',
+ 'welcome',
+ 'yard',
+ 'alarm',
+ 'bend',
+ 'bicycle',
+ 'bite',
+ 'blind',
+ 'bottle',
+ 'cable',
+ 'candle',
+ 'clerk',
+ 'cloud',
+ 'concert',
+ 'counter',
+ 'flower',
+ 'grandfather',
+ 'harm',
+ 'knee',
+ 'lawyer',
+ 'leather',
+ 'load',
+ 'mirror',
+ 'neck',
+ 'pension',
+ 'plate',
+ 'purple',
+ 'ruin',
+ 'ship',
+ 'skirt',
+ 'slice',
+ 'snow',
+ 'specialist',
+ 'stroke',
+ 'switch',
+ 'trash',
+ 'tune',
+ 'zone',
+ 'anger',
+ 'award',
+ 'bid',
+ 'bitter',
+ 'boot',
+ 'bug',
+ 'camp',
+ 'candy',
+ 'carpet',
+ 'cat',
+ 'champion',
+ 'channel',
+ 'clock',
+ 'comfort',
+ 'cow',
+ 'crack',
+ 'engineer',
+ 'entrance',
+ 'fault',
+ 'grass',
+ 'guy',
+ 'hell',
+ 'highlight',
+ 'incident',
+ 'island',
+ 'joke',
+ 'jury',
+ 'leg',
+ 'lip',
+ 'mate',
+ 'motor',
+ 'nerve',
+ 'passage',
+ 'pen',
+ 'pride',
+ 'priest',
+ 'prize',
+ 'promise',
+ 'resident',
+ 'resort',
+ 'ring',
+ 'roof',
+ 'rope',
+ 'sail',
+ 'scheme',
+ 'script',
+ 'sock',
+ 'station',
+ 'toe',
+ 'tower',
+ 'truck',
+ 'witness',
+ 'can',
+ 'will',
+ 'other',
+ 'use',
+ 'make',
+ 'good',
+ 'look',
+ 'help',
+ 'go',
+ 'great',
+ 'being',
+ 'still',
+ 'public',
+ 'read',
+ 'keep',
+ 'start',
+ 'give',
+ 'human',
+ 'local',
+ 'general',
+ 'specific',
+ 'long',
+ 'play',
+ 'feel',
+ 'high',
+ 'put',
+ 'common',
+ 'set',
+ 'change',
+ 'simple',
+ 'past',
+ 'big',
+ 'possible',
+ 'particular',
+ 'major',
+ 'personal',
+ 'current',
+ 'national',
+ 'cut',
+ 'natural',
+ 'physical',
+ 'show',
+ 'try',
+ 'check',
+ 'second',
+ 'call',
+ 'move',
+ 'pay',
+ 'let',
+ 'increase',
+ 'single',
+ 'individual',
+ 'turn',
+ 'ask',
+ 'buy',
+ 'guard',
+ 'hold',
+ 'main',
+ 'offer',
+ 'potential',
+ 'professional',
+ 'international',
+ 'travel',
+ 'cook',
+ 'alternative',
+ 'special',
+ 'working',
+ 'whole',
+ 'dance',
+ 'excuse',
+ 'cold',
+ 'commercial',
+ 'low',
+ 'purchase',
+ 'deal',
+ 'primary',
+ 'worth',
+ 'fall',
+ 'necessary',
+ 'positive',
+ 'produce',
+ 'search',
+ 'present',
+ 'spend',
+ 'talk',
+ 'creative',
+ 'tell',
+ 'cost',
+ 'drive',
+ 'green',
+ 'support',
+ 'glad',
+ 'remove',
+ 'return',
+ 'run',
+ 'complex',
+ 'due',
+ 'effective',
+ 'middle',
+ 'regular',
+ 'reserve',
+ 'independent',
+ 'leave',
+ 'original',
+ 'reach',
+ 'rest',
+ 'serve',
+ 'watch',
+ 'beautiful',
+ 'charge',
+ 'active',
+ 'break',
+ 'negative',
+ 'safe',
+ 'stay',
+ 'visit',
+ 'visual',
+ 'affect',
+ 'cover',
+ 'report',
+ 'rise',
+ 'walk',
+ 'white',
+ 'junior',
+ 'pick',
+ 'unique',
+ 'classic',
+ 'final',
+ 'lift',
+ 'mix',
+ 'private',
+ 'stop',
+ 'teach',
+ 'western',
+ 'concern',
+ 'familiar',
+ 'fly',
+ 'official',
+ 'broad',
+ 'comfortable',
+ 'gain',
+ 'rich',
+ 'save',
+ 'stand',
+ 'young',
+ 'heavy',
+ 'lead',
+ 'listen',
+ 'valuable',
+ 'worry',
+ 'handle',
+ 'leading',
+ 'meet',
+ 'release',
+ 'sell',
+ 'finish',
+ 'normal',
+ 'press',
+ 'ride',
+ 'secret',
+ 'spread',
+ 'spring',
+ 'tough',
+ 'wait',
+ 'brown',
+ 'deep',
+ 'display',
+ 'flow',
+ 'hit',
+ 'objective',
+ 'shoot',
+ 'touch',
+ 'cancel',
+ 'chemical',
+ 'cry',
+ 'dump',
+ 'extreme',
+ 'push',
+ 'conflict',
+ 'eat',
+ 'fill',
+ 'formal',
+ 'jump',
+ 'kick',
+ 'opposite',
+ 'pass',
+ 'pitch',
+ 'remote',
+ 'total',
+ 'treat',
+ 'vast',
+ 'abuse',
+ 'beat',
+ 'burn',
+ 'deposit',
+ 'print',
+ 'raise',
+ 'sleep',
+ 'somewhere',
+ 'advance',
+ 'consist',
+ 'dark',
+ 'double',
+ 'draw',
+ 'equal',
+ 'fix',
+ 'hire',
+ 'internal',
+ 'join',
+ 'kill',
+ 'sensitive',
+ 'tap',
+ 'win',
+ 'attack',
+ 'claim',
+ 'constant',
+ 'drag',
+ 'drink',
+ 'guess',
+ 'minor',
+ 'pull',
+ 'raw',
+ 'soft',
+ 'solid',
+ 'wear',
+ 'weird',
+ 'wonder',
+ 'annual',
+ 'count',
+ 'dead',
+ 'doubt',
+ 'feed',
+ 'forever',
+ 'impress',
+ 'repeat',
+ 'round',
+ 'sing',
+ 'slide',
+ 'strip',
+ 'wish',
+ 'combine',
+ 'command',
+ 'dig',
+ 'divide',
+ 'equivalent',
+ 'hang',
+ 'hunt',
+ 'initial',
+ 'march',
+ 'mention',
+ 'spiritual',
+ 'survey',
+ 'tie',
+ 'adult',
+ 'brief',
+ 'crazy',
+ 'escape',
+ 'gather',
+ 'hate',
+ 'prior',
+ 'repair',
+ 'rough',
+ 'sad',
+ 'scratch',
+ 'sick',
+ 'strike',
+ 'employ',
+ 'external',
+ 'hurt',
+ 'illegal',
+ 'laugh',
+ 'lay',
+ 'mobile',
+ 'nasty',
+ 'ordinary',
+ 'respond',
+ 'royal',
+ 'senior',
+ 'split',
+ 'strain',
+ 'struggle',
+ 'swim',
+ 'train',
+ 'upper',
+ 'wash',
+ 'yellow',
+ 'convert',
+ 'crash',
+ 'dependent',
+ 'fold',
+ 'funny',
+ 'grab',
+ 'hide',
+ 'miss',
+ 'permit',
+ 'quote',
+ 'recover',
+ 'resolve',
+ 'roll',
+ 'sink',
+ 'slip',
+ 'spare',
+ 'suspect',
+ 'sweet',
+ 'swing',
+ 'twist',
+ 'upstairs',
+ 'usual',
+ 'abroad',
+ 'brave',
+ 'calm',
+ 'concentrate',
+ 'estimate',
+ 'grand',
+ 'male',
+ 'mine',
+ 'prompt',
+ 'quiet',
+ 'refuse',
+ 'regret',
+ 'reveal',
+ 'rush',
+ 'shake',
+ 'shift',
+ 'shine',
+ 'steal',
+ 'suck',
+ 'surround',
+ 'bear',
+ 'brilliant',
+ 'dare',
+ 'dear',
+ 'delay',
+ 'drunk',
+ 'female',
+ 'hurry',
+ 'inevitable',
+ 'invite',
+ 'kiss',
+ 'neat',
+ 'pop',
+ 'punch',
+ 'quit',
+ 'reply',
+ 'representative',
+ 'resist',
+ 'rip',
+ 'rub',
+ 'silly',
+ 'smile',
+ 'spell',
+ 'stretch',
+ 'stupid',
+ 'tear',
+ 'temporary',
+ 'tomorrow',
+ 'wake',
+ 'wrap',
+ 'yesterday',
+ 'Thomas',
+ 'Tom',
+ 'Lieuwe',
+];
+
+export default function getRandomName() {
+ return `${adjective[Math.floor(Math.random() * adjective.length)]} ${
+ object[Math.floor(Math.random() * object.length)]
+ }`;
+};
\ No newline at end of file
diff --git a/server/src/utils/time.js b/server/src/utils/time.js
index 771943b11..1a121dfee 100644
--- a/server/src/utils/time.js
+++ b/server/src/utils/time.js
@@ -2,10 +2,26 @@ const mts = 1000; // millis to seconds
const mtm = 1000 * 60; // millis to minutes
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
* @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} ifNull - what to return if value is null
* @returns {string} String representing time 00:12:02
@@ -17,7 +33,7 @@ export const stringFromMillis = (
delim = ':',
ifNull = '...'
) => {
- if (ms === null || isNaN(ms)) return ifNull;
+ if (ms == null || isNaN(ms)) return ifNull;
const isNegative = ms < 0 ? '-' : '';
const millis = Math.abs(ms);
@@ -36,7 +52,7 @@ export const stringFromMillis = (
/**
* @description Converts an excel date to milliseconds
* @argument {string} excelDate - excel string date
- * @returns {number} - time in millisenconds
+ * @returns {number} - time in milliseconds
*/
export const excelDateStringToMillis = (excelDate) => {
const date = new Date(excelDate);
@@ -47,5 +63,5 @@ export const excelDateStringToMillis = (excelDate) => {
return h * mth + m * mtm + s * mts;
}
- return null;
+ return 0;
};