mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-01 04:19:12 +00:00
Feat/161 (#186)
* feat(time): add optional time format * feat(time): show 12 hour time in stage timer * feat(time): override locally * feat(time): show timer in selected format * feat(time): show 12 hour time in table header * refactor: extract time formatting into utility * refactor: migrate datetime library * refactor(formatTime): centralise time formatting for viewers * feature(12hour): version bump
This commit is contained in:
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "1.3.0",
|
"version": "1.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^2.0.0-next.3",
|
"@chakra-ui/react": "^2.0.0-next.3",
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"framer-motion": "^6.3.3",
|
"framer-motion": "^6.3.3",
|
||||||
|
"jotai": "^1.7.8",
|
||||||
|
"luxon": "^3.0.1",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
|
|||||||
+2
-2
@@ -11,7 +11,7 @@ import AppRouter from './AppRouter';
|
|||||||
|
|
||||||
// Load Open Sans typeface
|
// Load Open Sans typeface
|
||||||
require('typeface-open-sans');
|
require('typeface-open-sans');
|
||||||
const queryClient = new QueryClient();
|
export const ontimeQueryClient = new QueryClient();
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<ChakraProvider resetCSS theme={theme}>
|
<ChakraProvider resetCSS theme={theme}>
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={ontimeQueryClient}>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className='App'>
|
<div className='App'>
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ export const ontimePlaceholderInfo = {
|
|||||||
* @type {{pinCode: null}}
|
* @type {{pinCode: null}}
|
||||||
*/
|
*/
|
||||||
export const ontimePlaceholderSettings = {
|
export const ontimePlaceholderSettings = {
|
||||||
|
app: 'ontime',
|
||||||
|
version: 1,
|
||||||
|
serverPort: 4001,
|
||||||
|
lock: null,
|
||||||
pinCode: null,
|
pinCode: null,
|
||||||
|
timeFormat: '24',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,9 +228,7 @@ export const downloadEvents = async () => {
|
|||||||
filename = headerLine.substring(startFileNameIndex, endFileNameIndex);
|
filename = headerLine.substring(startFileNameIndex, endFileNameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = window.URL.createObjectURL(
|
const url = window.URL.createObjectURL(new Blob([response.data], { type: 'application/json' }));
|
||||||
new Blob([response.data], { type: 'application/json' })
|
|
||||||
);
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.setAttribute('download', filename);
|
link.setAttribute('download', filename);
|
||||||
@@ -252,4 +255,5 @@ export const uploadEvents = async (file) => {
|
|||||||
* @description HTTP request to upload events
|
* @description HTTP request to upload events
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
export const uploadEventsWithPath = async (filepath) => axios.post(`${ontimeURL}/dbpath`, { path: filepath });
|
export const uploadEventsWithPath = async (filepath) =>
|
||||||
|
axios.post(`${ontimeURL}/dbpath`, { path: filepath });
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { atomWithStorage, selectAtom } from 'jotai/utils';
|
||||||
|
|
||||||
|
export const eventSettingsAtom = atomWithStorage('ontime-eventSettings', {
|
||||||
|
showQuickEntry: false,
|
||||||
|
startTimeIsLastEnd: false,
|
||||||
|
defaultPublic: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const showQuickEntryAtom = selectAtom(
|
||||||
|
eventSettingsAtom,
|
||||||
|
(settings) => settings.showQuickEntry
|
||||||
|
);
|
||||||
|
export const startTimeIsLastEndAtom = selectAtom(
|
||||||
|
eventSettingsAtom,
|
||||||
|
(settings) => settings.startTimeIsLastEnd
|
||||||
|
);
|
||||||
|
export const defaultPublicAtom = selectAtom(
|
||||||
|
eventSettingsAtom,
|
||||||
|
(settings) => settings.defaultPublic
|
||||||
|
);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { stringFromMillis } from '../../utils/time';
|
import { formatTime } from '../../utils/time';
|
||||||
|
|
||||||
import style from './Paginator.module.scss';
|
import style from './Paginator.module.scss';
|
||||||
|
|
||||||
@@ -9,8 +9,8 @@ export default function TodayItem(props) {
|
|||||||
const { selected, timeStart, timeEnd, title, backstageEvent, colour } = props;
|
const { selected, timeStart, timeEnd, title, backstageEvent, colour } = props;
|
||||||
|
|
||||||
// Format timers
|
// Format timers
|
||||||
const start = stringFromMillis(timeStart, false) || '';
|
const start = formatTime(timeStart, { format: 'hh:mm' });
|
||||||
const end = stringFromMillis(timeEnd, false) || '';
|
const end = formatTime(timeEnd, { format: 'hh:mm' });
|
||||||
|
|
||||||
// user colours
|
// user colours
|
||||||
const userColour = colour !== '' ? colour : 'transparent';
|
const userColour = colour !== '' ? colour : 'transparent';
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import React, { createContext, useState } from 'react';
|
|
||||||
|
|
||||||
export const LocalEventSettingsContext = createContext({
|
|
||||||
showQuickEntry: false,
|
|
||||||
starTimeIsLastEnd: true,
|
|
||||||
defaultPublic: true,
|
|
||||||
|
|
||||||
setShowQuickEntry: () => undefined,
|
|
||||||
setStarTimeIsLastEnd: () => undefined,
|
|
||||||
setDefaultPublic: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LocalEventSettingsProvider = ({ children }) => {
|
|
||||||
const [showQuickEntry, setShowQuickEntry] = useState(false);
|
|
||||||
const [starTimeIsLastEnd, setStarTimeIsLastEnd] = useState(true);
|
|
||||||
const [defaultPublic, setDefaultPublic] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LocalEventSettingsContext.Provider
|
|
||||||
value={{
|
|
||||||
showQuickEntry,
|
|
||||||
setShowQuickEntry,
|
|
||||||
starTimeIsLastEnd,
|
|
||||||
setStarTimeIsLastEnd,
|
|
||||||
defaultPublic,
|
|
||||||
setDefaultPublic,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</LocalEventSettingsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -373,10 +373,9 @@ describe('test formatEvents function', () => {
|
|||||||
isNext: false,
|
isNext: false,
|
||||||
colour: "",
|
colour: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const parsed = formatEventList(testEvent, selectedId, nextId, true);
|
const parsed = formatEventList(testEvent, selectedId, nextId, { showEnd: true });
|
||||||
expect(parsed).toStrictEqual(expected);
|
expect(parsed).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -403,7 +402,7 @@ describe('test formatEvents function', () => {
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const parsed = formatEventList(testEvent, selectedId, nextId, true);
|
const parsed = formatEventList(testEvent, selectedId, nextId, { showEnd: true });
|
||||||
expect(parsed).toStrictEqual(expected);
|
expect(parsed).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -431,7 +430,7 @@ describe('test formatEvents function', () => {
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const parsed = formatEventList(testEvent, selectedId, nextId, true);
|
const parsed = formatEventList(testEvent, selectedId, nextId, { showEnd: true });
|
||||||
expect(parsed).toStrictEqual(expected);
|
expect(parsed).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { formatTime } from '../time';
|
||||||
|
|
||||||
|
describe('formatTime()', () => {
|
||||||
|
test('parses 24h strings', () => {
|
||||||
|
const ms = 13 * 60 * 60 * 1000;
|
||||||
|
const options = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'irrelevant',
|
||||||
|
};
|
||||||
|
const time = formatTime(ms, options, () => '24');
|
||||||
|
expect(time).toStrictEqual('13:00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parses same string in 12h strings', () => {
|
||||||
|
const ms = 13 * 60 * 60 * 1000;
|
||||||
|
const options = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
};
|
||||||
|
const time = formatTime(ms, options, () => '12');
|
||||||
|
expect(time).toStrictEqual('01:00:00 PM');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles null times', () => {
|
||||||
|
const ms = null;
|
||||||
|
const time = formatTime(ms);
|
||||||
|
expect(time).toStrictEqual('...');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { stringFromMillis } from './time';
|
import { formatTime } from './time';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description From a list of events, returns only events of type event with calculated delays
|
* @description From a list of events, returns only events of type event with calculated delays
|
||||||
@@ -58,27 +58,29 @@ export const trimEventlist = (events, selectedId, limit) => {
|
|||||||
* @param {Object[]} events - given events
|
* @param {Object[]} events - given events
|
||||||
* @param {string} selectedId - id of currently selected event
|
* @param {string} selectedId - id of currently selected event
|
||||||
* @param {string} nextId - id of next event
|
* @param {string} nextId - id of next event
|
||||||
* @param {boolean} [showEnd] - whether to show the end time
|
* @param {object} [options]
|
||||||
|
* @param {boolean} [options.showEnd] - whether to show the end time
|
||||||
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
|
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
|
||||||
*/
|
*/
|
||||||
export const formatEventList = (events, selectedId, nextId, showEnd = false) => {
|
export const formatEventList = (events, selectedId, nextId, options) => {
|
||||||
if (events == null) return [];
|
if (events == null) return [];
|
||||||
|
const { showEnd = false } = options;
|
||||||
|
|
||||||
const givenEvents = [...events];
|
const givenEvents = [...events];
|
||||||
|
|
||||||
// format list
|
// format list
|
||||||
const formattedEvents = [];
|
const formattedEvents = [];
|
||||||
for (const g of givenEvents) {
|
for (const event of givenEvents) {
|
||||||
const start = stringFromMillis(g.timeStart, false);
|
const start = formatTime(event.timeStart)
|
||||||
const end = stringFromMillis(g.timeEnd, false);
|
const end = formatTime(event.timeEnd);
|
||||||
|
|
||||||
formattedEvents.push({
|
formattedEvents.push({
|
||||||
id: g.id,
|
id: event.id,
|
||||||
time: showEnd ? `${start} - ${end}` : start,
|
time: showEnd ? `${start} - ${end}` : start,
|
||||||
title: g.title,
|
title: event.title,
|
||||||
isNow: g.id === selectedId,
|
isNow: event.id === selectedId,
|
||||||
isNext: g.id === nextId,
|
isNext: event.id === nextId,
|
||||||
colour: g.colour,
|
colour: event.colour,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { ontimeQueryClient } from '../../App';
|
||||||
|
import { APP_SETTINGS } from '../api/apiConstants';
|
||||||
|
|
||||||
const mts = 1000; // millis to seconds
|
const mts = 1000; // millis to seconds
|
||||||
const mtm = 1000 * 60; // millis to minutes
|
const mtm = 1000 * 60; // millis to minutes
|
||||||
const mth = 1000 * 60 * 60; // millis to hours
|
const mth = 1000 * 60 * 60; // millis to hours
|
||||||
@@ -26,12 +31,7 @@ export const nowInMillis = () => {
|
|||||||
* @param {string} ifNull - what to return if value is null
|
* @param {string} ifNull - what to return if value is null
|
||||||
* @returns {string} String representing time 00:12:02
|
* @returns {string} String representing time 00:12:02
|
||||||
*/
|
*/
|
||||||
export const stringFromMillis = (
|
export const stringFromMillis = (ms, showSeconds = true, delim = ':', ifNull = '...') => {
|
||||||
ms,
|
|
||||||
showSeconds = true,
|
|
||||||
delim = ':',
|
|
||||||
ifNull = '...'
|
|
||||||
) => {
|
|
||||||
if (ms == null || isNaN(ms)) return ifNull;
|
if (ms == null || isNaN(ms)) return ifNull;
|
||||||
const isNegative = ms < 0 ? '-' : '';
|
const isNegative = ms < 0 ? '-' : '';
|
||||||
const millis = Math.abs(ms);
|
const millis = Math.abs(ms);
|
||||||
@@ -54,18 +54,34 @@ export const stringFromMillis = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Converts an excel date to milliseconds
|
* @description Resolves format from url and store
|
||||||
* @argument {string} excelDate - excel string date
|
* @return {string|undefined}
|
||||||
* @returns {number} - time in milliseconds
|
|
||||||
*/
|
*/
|
||||||
export const excelDateStringToMillis = (excelDate) => {
|
export const resolveTimeFormat = () => {
|
||||||
const date = new Date(excelDate);
|
const params = new URL(document.location).searchParams;
|
||||||
if (date instanceof Date && !isNaN(date)) {
|
const urlOptions = params.get('format');
|
||||||
const h = date.getHours();
|
const settings = ontimeQueryClient.getQueryData(APP_SETTINGS);
|
||||||
const m = date.getMinutes();
|
|
||||||
const s = date.getSeconds();
|
|
||||||
|
|
||||||
return h * mth + m * mtm + s * mts;
|
return urlOptions || settings?.timeFormat;
|
||||||
}
|
};
|
||||||
return 0;
|
|
||||||
|
/**
|
||||||
|
/**
|
||||||
|
* @description utility function to format a date in 12 or 24 hour format
|
||||||
|
* @param {number} milliseconds
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {boolean} [options.showSeconds]
|
||||||
|
* @param {string} [options.format]
|
||||||
|
* @param {function} resolver
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export const formatTime = (milliseconds, options, resolver = resolveTimeFormat) => {
|
||||||
|
if (milliseconds === null) {
|
||||||
|
return '...';
|
||||||
|
}
|
||||||
|
const timeFormat = resolver();
|
||||||
|
const { showSeconds = false, format: formatString = 'hh:mm a' } = options || {};
|
||||||
|
return timeFormat === '12'
|
||||||
|
? DateTime.fromMillis(milliseconds).toUTC().toFormat(formatString)
|
||||||
|
: stringFromMillis(milliseconds, showSeconds);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React, { lazy, useEffect } from 'react';
|
import React, { lazy } from 'react';
|
||||||
import { useDisclosure } from '@chakra-ui/hooks';
|
import { useDisclosure } from '@chakra-ui/hooks';
|
||||||
import { Box } from '@chakra-ui/layout';
|
import { Box } from '@chakra-ui/layout';
|
||||||
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
||||||
import ModalManager from 'features/modals/ModalManager';
|
import ModalManager from 'features/modals/ModalManager';
|
||||||
|
|
||||||
import { LocalEventSettingsProvider } from '../../common/context/LocalEventSettingsContext';
|
|
||||||
import { LoggingProvider } from '../../common/context/LoggingContext';
|
import { LoggingProvider } from '../../common/context/LoggingContext';
|
||||||
import MenuBar from '../menu/MenuBar';
|
import MenuBar from '../menu/MenuBar';
|
||||||
|
|
||||||
@@ -19,28 +18,24 @@ export default function Editor() {
|
|||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
// Set window title
|
// Set window title
|
||||||
useEffect(() => {
|
document.title = 'ontime - Editor';
|
||||||
document.title = 'ontime - Editor';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoggingProvider>
|
<LoggingProvider>
|
||||||
<LocalEventSettingsProvider>
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<ModalManager isOpen={isOpen} onClose={onClose} />
|
||||||
<ModalManager isOpen={isOpen} onClose={onClose} />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<div className={styles.mainContainer}>
|
||||||
<div className={styles.mainContainer}>
|
<Box id='settings' className={styles.settings}>
|
||||||
<Box id='settings' className={styles.settings}>
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<MenuBar onOpen={onOpen} isOpen={isOpen} onClose={onClose} />
|
||||||
<MenuBar onOpen={onOpen} isOpen={isOpen} onClose={onClose} />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
</Box>
|
||||||
</Box>
|
<EventList onOpen={onOpen} isOpen={isOpen} onClose={onClose} />
|
||||||
<EventList onOpen={onOpen} isOpen={isOpen} onClose={onClose} />
|
<MessageControl />
|
||||||
<MessageControl />
|
<TimerControl />
|
||||||
<TimerControl />
|
<Info />
|
||||||
<Info />
|
</div>
|
||||||
</div>
|
|
||||||
</LocalEventSettingsProvider>
|
|
||||||
</LoggingProvider>
|
</LoggingProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Checkbox } from '@chakra-ui/react';
|
import { Checkbox } from '@chakra-ui/react';
|
||||||
import { Tooltip } from '@chakra-ui/tooltip';
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { LocalEventSettingsContext } from '../../../common/context/LocalEventSettingsContext';
|
import {
|
||||||
|
defaultPublicAtom,
|
||||||
|
startTimeIsLastEndAtom,
|
||||||
|
} from '../../../common/atoms/LocalEventSettings';
|
||||||
|
|
||||||
import style from './EntryBlock.module.scss';
|
import style from './EntryBlock.module.scss';
|
||||||
|
|
||||||
@@ -16,13 +20,14 @@ export default function EntryBlock(props) {
|
|||||||
disableAddDelay = true,
|
disableAddDelay = true,
|
||||||
disableAddBlock,
|
disableAddBlock,
|
||||||
} = props;
|
} = props;
|
||||||
const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext);
|
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||||
const [doStartTime, setStartTime] = useState(starTimeIsLastEnd);
|
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||||
|
const [doStartTime, setStartTime] = useState(startTimeIsLastEnd);
|
||||||
const [doPublic, setPublic] = useState(defaultPublic);
|
const [doPublic, setPublic] = useState(defaultPublic);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setStartTime(starTimeIsLastEnd);
|
setStartTime(startTimeIsLastEnd);
|
||||||
}, [starTimeIsLastEnd]);
|
}, [startTimeIsLastEnd]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPublic(defaultPublic);
|
setPublic(defaultPublic);
|
||||||
@@ -36,7 +41,7 @@ export default function EntryBlock(props) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
eventsHandler(
|
eventsHandler(
|
||||||
'add',
|
'add',
|
||||||
{ type: 'event', after: previousId, isPublic: doPublic },
|
{ type: 'event', after: previousId, isPublic: doPublic },
|
||||||
{ startIsLastEnd: doStartTime ? previousId : undefined }
|
{ startIsLastEnd: doStartTime ? previousId : undefined }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -68,7 +73,9 @@ export default function EntryBlock(props) {
|
|||||||
size='sm'
|
size='sm'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
isChecked={doStartTime}
|
isChecked={doStartTime}
|
||||||
onChange={(e) => setStartTime(e.target.checked)}
|
onChange={(e) => {
|
||||||
|
setStartTime(e.target.checked);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Start time is last end
|
Start time is last end
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
@@ -93,4 +100,3 @@ EntryBlock.propTypes = {
|
|||||||
disableAddDelay: PropTypes.bool,
|
disableAddDelay: PropTypes.bool,
|
||||||
disableAddBlock: PropTypes.bool,
|
disableAddBlock: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import React, { createRef, useCallback, useContext, useEffect, useState } from '
|
|||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
import Empty from 'common/components/state/Empty';
|
import Empty from 'common/components/state/Empty';
|
||||||
import { useSocket } from 'common/context/socketContext';
|
import { useSocket } from 'common/context/socketContext';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
|
||||||
|
import { showQuickEntryAtom } from '../../../common/atoms/LocalEventSettings';
|
||||||
import { CursorContext } from '../../../common/context/CursorContext';
|
import { CursorContext } from '../../../common/context/CursorContext';
|
||||||
import { LocalEventSettingsContext } from '../../../common/context/LocalEventSettingsContext';
|
|
||||||
import EntryBlock from '../EntryBlock/EntryBlock';
|
import EntryBlock from '../EntryBlock/EntryBlock';
|
||||||
|
|
||||||
import EventListItem from './EventListItem';
|
import EventListItem from './EventListItem';
|
||||||
@@ -19,21 +20,24 @@ export default function EventList(props) {
|
|||||||
const [selectedId, setSelectedId] = useState(null);
|
const [selectedId, setSelectedId] = useState(null);
|
||||||
const [nextId, setNextId] = useState(null);
|
const [nextId, setNextId] = useState(null);
|
||||||
const cursorRef = createRef();
|
const cursorRef = createRef();
|
||||||
const { showQuickEntry } = useContext(LocalEventSettingsContext);
|
const showQuickEntry = useAtomValue(showQuickEntryAtom);
|
||||||
|
|
||||||
const insertAtCursor = useCallback((type, cursor) => {
|
const insertAtCursor = useCallback(
|
||||||
if (cursor === -1) {
|
(type, cursor) => {
|
||||||
eventsHandler('add', { type: type });
|
if (cursor === -1) {
|
||||||
} else {
|
eventsHandler('add', { type: type });
|
||||||
const previousEvent = events[cursor];
|
} else {
|
||||||
const nextEvent = events[cursor + 1];
|
const previousEvent = events[cursor];
|
||||||
if (type === 'event') {
|
const nextEvent = events[cursor + 1];
|
||||||
eventsHandler('add', { type: type, after: previousEvent.id });
|
if (type === 'event') {
|
||||||
} else if (previousEvent?.type !== type && nextEvent?.type !== type) {
|
eventsHandler('add', { type: type, after: previousEvent.id });
|
||||||
eventsHandler('add', { type: type, after: previousEvent.id });
|
} else if (previousEvent?.type !== type && nextEvent?.type !== type) {
|
||||||
|
eventsHandler('add', { type: type, after: previousEvent.id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},[events, eventsHandler])
|
[events, eventsHandler]
|
||||||
|
);
|
||||||
|
|
||||||
// Handle keyboard shortcuts
|
// Handle keyboard shortcuts
|
||||||
const handleKeyPress = useCallback(
|
const handleKeyPress = useCallback(
|
||||||
@@ -54,19 +58,19 @@ export default function EventList(props) {
|
|||||||
if (e.key === 'e' || e.key === 'E') {
|
if (e.key === 'e' || e.key === 'E') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cursor == null) return;
|
if (cursor == null) return;
|
||||||
insertAtCursor('event', cursor)
|
insertAtCursor('event', cursor);
|
||||||
}
|
}
|
||||||
// D
|
// D
|
||||||
if (e.key === 'd' || e.key === 'D') {
|
if (e.key === 'd' || e.key === 'D') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cursor == null) return;
|
if (cursor == null) return;
|
||||||
insertAtCursor('delay', cursor)
|
insertAtCursor('delay', cursor);
|
||||||
}
|
}
|
||||||
// B
|
// B
|
||||||
if (e.key === 'b' || e.key === 'B') {
|
if (e.key === 'b' || e.key === 'B') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cursor == null) return;
|
if (cursor == null) return;
|
||||||
insertAtCursor('block', cursor)
|
insertAtCursor('block', cursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import React, { memo, useCallback, useContext } from 'react';
|
import React, { memo, useCallback, useContext } from 'react';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { LocalEventSettingsContext } from '../../../common/context/LocalEventSettingsContext';
|
import {
|
||||||
|
defaultPublicAtom,
|
||||||
|
startTimeIsLastEndAtom,
|
||||||
|
} from '../../../common/atoms/LocalEventSettings';
|
||||||
import { LoggingContext } from '../../../common/context/LoggingContext';
|
import { LoggingContext } from '../../../common/context/LoggingContext';
|
||||||
import BlockBlock from '../BlockBlock/BlockBlock';
|
import BlockBlock from '../BlockBlock/BlockBlock';
|
||||||
import DelayBlock from '../DelayBlock/DelayBlock';
|
import DelayBlock from '../DelayBlock/DelayBlock';
|
||||||
@@ -19,19 +23,11 @@ const areEqual = (prevProps, nextProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EventListItem = (props) => {
|
const EventListItem = (props) => {
|
||||||
const {
|
const { type, index, eventIndex, data, selected, next, eventsHandler, delay, previousEnd } =
|
||||||
type,
|
props;
|
||||||
index,
|
|
||||||
eventIndex,
|
|
||||||
data,
|
|
||||||
selected,
|
|
||||||
next,
|
|
||||||
eventsHandler,
|
|
||||||
delay,
|
|
||||||
previousEnd,
|
|
||||||
} = props;
|
|
||||||
const { emitError } = useContext(LoggingContext);
|
const { emitError } = useContext(LoggingContext);
|
||||||
const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext);
|
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||||
|
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description calculates duration from given options
|
* @description calculates duration from given options
|
||||||
@@ -56,7 +52,7 @@ const EventListItem = (props) => {
|
|||||||
after: data.id,
|
after: data.id,
|
||||||
isPublic: defaultPublic,
|
isPublic: defaultPublic,
|
||||||
},
|
},
|
||||||
{ startIsLastEnd: starTimeIsLastEnd ? data.id : undefined }
|
{ startIsLastEnd: startTimeIsLastEnd ? data.id : undefined }
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'delay':
|
case 'delay':
|
||||||
@@ -103,7 +99,7 @@ const EventListItem = (props) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[calculateDuration, data, defaultPublic, emitError, eventsHandler, starTimeIsLastEnd]
|
[calculateDuration, data, defaultPublic, emitError, eventsHandler, startTimeIsLastEnd]
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -147,5 +143,5 @@ EventListItem.propTypes = {
|
|||||||
next: PropTypes.bool,
|
next: PropTypes.bool,
|
||||||
eventsHandler: PropTypes.func,
|
eventsHandler: PropTypes.func,
|
||||||
delay: PropTypes.number,
|
delay: PropTypes.number,
|
||||||
previousEnd: PropTypes.number
|
previousEnd: PropTypes.number,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,22 +1,32 @@
|
|||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { IconButton } from '@chakra-ui/button';
|
import { IconButton } from '@chakra-ui/button';
|
||||||
import { ModalBody } from '@chakra-ui/modal';
|
import { ModalBody } from '@chakra-ui/modal';
|
||||||
import { Checkbox, FormControl, FormLabel, Input, PinInput, PinInputField } from '@chakra-ui/react';
|
import {
|
||||||
|
Checkbox,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
PinInput,
|
||||||
|
PinInputField,
|
||||||
|
Select,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { FiEye } from '@react-icons/all-files/fi/FiEye';
|
import { FiEye } from '@react-icons/all-files/fi/FiEye';
|
||||||
import { FiX } from '@react-icons/all-files/fi/FiX';
|
import { FiX } from '@react-icons/all-files/fi/FiX';
|
||||||
import { APP_SETTINGS } from 'common/api/apiConstants';
|
import { APP_SETTINGS } from 'common/api/apiConstants';
|
||||||
import { getSettings, ontimePlaceholderSettings, postSettings } from 'common/api/ontimeApi';
|
import { getSettings, ontimePlaceholderSettings, postSettings } from 'common/api/ontimeApi';
|
||||||
import { useFetch } from 'common/hooks/useFetch';
|
import { useFetch } from 'common/hooks/useFetch';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
|
import { eventSettingsAtom } from '../../common/atoms/LocalEventSettings';
|
||||||
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
|
||||||
import { LocalEventSettingsContext } from '../../common/context/LocalEventSettingsContext';
|
|
||||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||||
|
|
||||||
import { inputProps } from './modalHelper';
|
import { inputProps } from './modalHelper';
|
||||||
import SubmitContainer from './SubmitContainer';
|
import SubmitContainer from './SubmitContainer';
|
||||||
|
|
||||||
import style from './Modals.module.scss';
|
import style from './Modals.module.scss';
|
||||||
const version = require('../../../package.json').version
|
|
||||||
|
const version = require('../../../package.json').version;
|
||||||
|
|
||||||
export default function AppSettingsModal() {
|
export default function AppSettingsModal() {
|
||||||
const { data, status, refetch } = useFetch(APP_SETTINGS, getSettings);
|
const { data, status, refetch } = useFetch(APP_SETTINGS, getSettings);
|
||||||
@@ -26,18 +36,8 @@ export default function AppSettingsModal() {
|
|||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [hidePin, setHidePin] = useState(true);
|
const [hidePin, setHidePin] = useState(true);
|
||||||
|
|
||||||
const {
|
const [eventSettings, saveEventSettings] = useAtom(eventSettingsAtom);
|
||||||
showQuickEntry,
|
const [formSettings, setFormSettings] = useState(eventSettings);
|
||||||
setShowQuickEntry,
|
|
||||||
starTimeIsLastEnd,
|
|
||||||
setStarTimeIsLastEnd,
|
|
||||||
defaultPublic,
|
|
||||||
setDefaultPublic,
|
|
||||||
} = useContext(LocalEventSettingsContext);
|
|
||||||
|
|
||||||
const [doShowQuickEntry, setDoShowQuickEntry] = useState(showQuickEntry);
|
|
||||||
const [doStarTimeIsLastEnd, setDoStarTimeIsLastEnd] = useState(starTimeIsLastEnd);
|
|
||||||
const [doDefaultPublic, setDoDefaultPublic] = useState(defaultPublic);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set formdata from server state
|
* Set formdata from server state
|
||||||
@@ -47,117 +47,72 @@ export default function AppSettingsModal() {
|
|||||||
if (changed) return;
|
if (changed) return;
|
||||||
setFormData({
|
setFormData({
|
||||||
pinCode: data.pinCode,
|
pinCode: data.pinCode,
|
||||||
|
timeFormat: data.timeFormat,
|
||||||
});
|
});
|
||||||
}, [changed, data]);
|
}, [changed, data]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Set formdata from context
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
if (showQuickEntry == null) return;
|
|
||||||
setDoShowQuickEntry(showQuickEntry);
|
|
||||||
}, [showQuickEntry]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (starTimeIsLastEnd == null) return;
|
|
||||||
setDoStarTimeIsLastEnd(starTimeIsLastEnd);
|
|
||||||
}, [starTimeIsLastEnd]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (defaultPublic == null) return;
|
|
||||||
setDoDefaultPublic(defaultPublic);
|
|
||||||
}, [defaultPublic]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and submit data
|
* Validate and submit data
|
||||||
*/
|
*/
|
||||||
const submitHandler = useCallback(
|
const submitHandler = async (event) => {
|
||||||
async (event) => {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
setSubmitting(true);
|
||||||
setSubmitting(true);
|
|
||||||
|
|
||||||
// set context
|
// set context
|
||||||
setShowQuickEntry(doShowQuickEntry);
|
saveEventSettings(formSettings);
|
||||||
setStarTimeIsLastEnd(doStarTimeIsLastEnd);
|
const validation = { isValid: false };
|
||||||
setDefaultPublic(doDefaultPublic);
|
|
||||||
|
|
||||||
const f = formData;
|
// we might not have changed this
|
||||||
|
if (formData.pinCode !== data.pinCode) {
|
||||||
// we might not have changed this
|
// Validate fields
|
||||||
if (f.pinCode !== data.pinCode) {
|
if (formData.pinCode === '' || formData.pinCode == null) {
|
||||||
const e = { status: false, message: '' };
|
validation.isValid = true;
|
||||||
|
validation.message += 'App pin code removed';
|
||||||
// Validate fields
|
} else {
|
||||||
if (f.pinCode === '' || f.pinCode == null) {
|
validation.isValid = true;
|
||||||
e.status = true;
|
validation.message += 'App pin code added';
|
||||||
e.message += 'App pin code removed';
|
|
||||||
} else {
|
|
||||||
e.status = true;
|
|
||||||
e.message += 'App pin code added';
|
|
||||||
}
|
|
||||||
|
|
||||||
// set fields with error
|
|
||||||
if (!e.status) {
|
|
||||||
emitError(`Invalid Input: ${e.message}`);
|
|
||||||
} else {
|
|
||||||
await postSettings(formData);
|
|
||||||
await refetch();
|
|
||||||
emitWarning(e.message);
|
|
||||||
setChanged(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setSubmitting(false);
|
}
|
||||||
setChanged(false);
|
|
||||||
},
|
if (formData.timeFormat !== data.timeFormat) {
|
||||||
[
|
if (formData.timeFormat === '12' || formData.timeFormat === '24') {
|
||||||
data.pinCode,
|
validation.isValid = true;
|
||||||
doDefaultPublic,
|
}
|
||||||
doShowQuickEntry,
|
}
|
||||||
doStarTimeIsLastEnd,
|
|
||||||
emitError,
|
// set fields with error
|
||||||
emitWarning,
|
if (!validation.isValid) {
|
||||||
formData,
|
emitError(`Invalid Input: ${validation.message}`);
|
||||||
refetch,
|
} else {
|
||||||
setDefaultPublic,
|
await postSettings(formData);
|
||||||
setShowQuickEntry,
|
await refetch();
|
||||||
setStarTimeIsLastEnd,
|
validation?.message && emitWarning(validation.message);
|
||||||
]
|
}
|
||||||
);
|
setSubmitting(false);
|
||||||
|
setChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverts local state equals to server state
|
* Reverts local state equals to server state
|
||||||
*/
|
*/
|
||||||
const revert = useCallback(async () => {
|
const revert = async () => {
|
||||||
setChanged(false);
|
setChanged(false);
|
||||||
await refetch();
|
|
||||||
|
|
||||||
// set from context
|
// set from context
|
||||||
setDoShowQuickEntry(showQuickEntry);
|
setFormSettings(eventSettings);
|
||||||
setDoStarTimeIsLastEnd(starTimeIsLastEnd);
|
await refetch();
|
||||||
setDoDefaultPublic(defaultPublic);
|
};
|
||||||
}, [defaultPublic, refetch, showQuickEntry, starTimeIsLastEnd]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles change of input field in local state
|
* Handles change of input field in local state
|
||||||
* @param {string} field - object parameter to update
|
* @param {string} field - object parameter to update
|
||||||
* @param {string} value - new object parameter value
|
* @param {string} value - new object parameter value
|
||||||
*/
|
*/
|
||||||
const handleChange = useCallback(
|
const handleChange = (field, value) => {
|
||||||
(field, value) => {
|
const temp = { ...formData };
|
||||||
const temp = { ...formData };
|
temp[field] = value;
|
||||||
temp[field] = value;
|
setFormData(temp);
|
||||||
setFormData(temp);
|
|
||||||
setChanged(true);
|
|
||||||
},
|
|
||||||
[formData]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets changed flag to true
|
|
||||||
*/
|
|
||||||
const handleContextChange = useCallback(() => {
|
|
||||||
setChanged(true);
|
setChanged(true);
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const disableModal = status !== 'success';
|
const disableModal = status !== 'success';
|
||||||
|
|
||||||
@@ -201,6 +156,7 @@ export default function AppSettingsModal() {
|
|||||||
<PinInput
|
<PinInput
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
type='alphanumeric'
|
type='alphanumeric'
|
||||||
|
name='pinCode'
|
||||||
defaultValue=''
|
defaultValue=''
|
||||||
value={formData.pinCode}
|
value={formData.pinCode}
|
||||||
mask={hidePin}
|
mask={hidePin}
|
||||||
@@ -235,31 +191,52 @@ export default function AppSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={style.modalColumn}>
|
||||||
|
<FormControl id='timeFormat'>
|
||||||
|
<FormLabel htmlFor='timeFormat'>
|
||||||
|
Time format
|
||||||
|
<span className={style.labelNote}>
|
||||||
|
<br />
|
||||||
|
12 / 24 hour format (viewers only for now)
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Select
|
||||||
|
size='sm'
|
||||||
|
name='timeFormat'
|
||||||
|
value={formData.timeFormat}
|
||||||
|
isDisabled={disableModal}
|
||||||
|
onChange={(event) => handleChange('timeFormat', event.target.value)}
|
||||||
|
>
|
||||||
|
<option value='12'>12 hours eg. 11:00:10 PM</option>
|
||||||
|
<option value='24'>24 hours eg. 23:00:10</option>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
<div className={style.hSeparator}>Create Event Default Settings</div>
|
<div className={style.hSeparator}>Create Event Default Settings</div>
|
||||||
<div className={style.modalColumn}>
|
<div className={style.modalColumn}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={doShowQuickEntry}
|
isChecked={formSettings.showQuickEntry}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setDoShowQuickEntry(e.target.checked);
|
setFormSettings((prev) => ({ ...prev, showQuickEntry: e.target.checked }));
|
||||||
handleContextChange();
|
setChanged(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Show quick entry on hover
|
Show quick entry on hover
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={doStarTimeIsLastEnd}
|
isChecked={formSettings.startTimeIsLastEnd}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setDoStarTimeIsLastEnd(e.target.checked);
|
setFormSettings((prev) => ({ ...prev, startTimeIsLastEnd: e.target.checked }));
|
||||||
handleContextChange();
|
setChanged(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Start time is last end
|
Start time is last end
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={doDefaultPublic}
|
isChecked={formSettings.defaultPublic}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setDoDefaultPublic(e.target.checked);
|
setFormSettings((prev) => ({ ...prev, defaultPublic: e.target.checked }));
|
||||||
handleContextChange();
|
setChanged(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Event default public
|
Event default public
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'name playback running time actions'
|
'name playback running time actions'
|
||||||
'now playback running time actions';
|
'now playback running time actions';
|
||||||
grid-template-columns: 1fr auto 10em 10em auto;
|
grid-template-columns: 1fr auto 10em 12.5em auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.25em 1em;
|
padding: 0.25em 1em;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useSocket } from '../../common/context/socketContext';
|
|||||||
import { TableSettingsContext } from '../../common/context/TableSettingsContext';
|
import { TableSettingsContext } from '../../common/context/TableSettingsContext';
|
||||||
import { useFetch } from '../../common/hooks/useFetch';
|
import { useFetch } from '../../common/hooks/useFetch';
|
||||||
import { formatDisplay } from '../../common/utils/dateConfig';
|
import { formatDisplay } from '../../common/utils/dateConfig';
|
||||||
import { stringFromMillis } from '../../common/utils/time';
|
import { formatTime } from '../../common/utils/time';
|
||||||
|
|
||||||
import PlaybackIcon from './tableElements/PlaybackIcon';
|
import PlaybackIcon from './tableElements/PlaybackIcon';
|
||||||
|
|
||||||
@@ -97,7 +97,10 @@ export default function TableHeader() {
|
|||||||
|
|
||||||
// prepare presentation variables
|
// prepare presentation variables
|
||||||
const timerNow = `${timer.running < 0 ? '-' : ''}${formatDisplay(timer.running)}`;
|
const timerNow = `${timer.running < 0 ? '-' : ''}${formatDisplay(timer.running)}`;
|
||||||
|
const timeNow = formatTime(timer.clock, {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className={style.header}>
|
<div className={style.header}>
|
||||||
<div className={style.headerName}>{data?.title || ''}</div>
|
<div className={style.headerName}>{data?.title || ''}</div>
|
||||||
@@ -115,7 +118,7 @@ export default function TableHeader() {
|
|||||||
<div className={style.headerClock}>
|
<div className={style.headerClock}>
|
||||||
<span className={style.label}>Time Now</span>
|
<span className={style.label}>Time Now</span>
|
||||||
<br />
|
<br />
|
||||||
<span className={style.timer}>{stringFromMillis(timer.clock)}</span>
|
<span className={style.timer}>{timeNow}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.headerActions}>
|
<div className={style.headerActions}>
|
||||||
<Tooltip openDelay={300} label='Follow selected'>
|
<Tooltip openDelay={300} label='Follow selected'>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { EVENT_TABLE, EVENTS_TABLE } from 'common/api/apiConstants';
|
|
||||||
import { fetchEvent } from 'common/api/eventApi';
|
|
||||||
import { fetchAllEvents } from 'common/api/eventsApi';
|
|
||||||
import { useSocket } from 'common/context/socketContext';
|
|
||||||
import { useFetch } from 'common/hooks/useFetch';
|
|
||||||
|
|
||||||
import { stringFromMillis } from '../../common/utils/time';
|
import { EVENT_TABLE, EVENTS_TABLE } from '../../common/api/apiConstants';
|
||||||
|
import { fetchEvent } from '../../common/api/eventApi';
|
||||||
|
import { fetchAllEvents } from '../../common/api/eventsApi';
|
||||||
|
import { useSocket } from '../../common/context/socketContext';
|
||||||
|
import { useFetch } from '../../common/hooks/useFetch';
|
||||||
|
|
||||||
const withSocket = (Component) => {
|
const withSocket = (Component) => {
|
||||||
return (props) => {
|
return (props) => {
|
||||||
@@ -30,9 +29,9 @@ const withSocket = (Component) => {
|
|||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
const [timer, setTimer] = useState({
|
const [timer, setTimer] = useState({
|
||||||
clock: null,
|
clock: 0,
|
||||||
running: null,
|
running: 0,
|
||||||
isNegative: null,
|
isNegative: false,
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
expectedFinish: null,
|
expectedFinish: null,
|
||||||
});
|
});
|
||||||
@@ -67,7 +66,9 @@ const withSocket = (Component) => {
|
|||||||
|
|
||||||
// Ask for update on load
|
// Ask for update on load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socket == null) return;
|
if (!socket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle timer messages
|
// Handle timer messages
|
||||||
socket.on('messages-timer', (data) => {
|
socket.on('messages-timer', (data) => {
|
||||||
@@ -154,12 +155,12 @@ const withSocket = (Component) => {
|
|||||||
|
|
||||||
// Filter events only to pass down
|
// Filter events only to pass down
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (eventsData == null) return;
|
if (!eventsData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// filter just events with title
|
// filter just events with title
|
||||||
if (Array.isArray(eventsData)) {
|
if (Array.isArray(eventsData)) {
|
||||||
const pe = eventsData.filter(
|
const pe = eventsData.filter((d) => d.type === 'event' && d.title !== '' && d.isPublic);
|
||||||
(d) => d.type === 'event' && d.title !== '' && d.isPublic
|
|
||||||
);
|
|
||||||
setPublicEvents(pe);
|
setPublicEvents(pe);
|
||||||
|
|
||||||
// everything goes backstage
|
// everything goes backstage
|
||||||
@@ -169,10 +170,13 @@ const withSocket = (Component) => {
|
|||||||
|
|
||||||
// Set general data
|
// Set general data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (genData == null) return;
|
if (!genData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setGeneral(genData);
|
setGeneral(genData);
|
||||||
}, [genData]);
|
}, [genData]);
|
||||||
|
|
||||||
|
|
||||||
/********************************************/
|
/********************************************/
|
||||||
/*** + titleManager ***/
|
/*** + titleManager ***/
|
||||||
/*** WRAP INFORMATION RELATED TO TITLES ***/
|
/*** WRAP INFORMATION RELATED TO TITLES ***/
|
||||||
@@ -221,9 +225,6 @@ const withSocket = (Component) => {
|
|||||||
const timeManager = {
|
const timeManager = {
|
||||||
...timer,
|
...timer,
|
||||||
finished: playback === 'start' && timer.isNegative && timer.startedAt,
|
finished: playback === 'start' && timer.isNegative && timer.startedAt,
|
||||||
clock: stringFromMillis(timer.clock),
|
|
||||||
clockMs: timer.clock,
|
|
||||||
clockNoSeconds: stringFromMillis(timer.clock, false),
|
|
||||||
playstate: playback,
|
playstate: playback,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,16 @@ import { AnimatePresence, motion } from 'framer-motion';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
||||||
|
import { formatTime } from '../../../common/utils/time';
|
||||||
import { titleVariants } from '../common/animation';
|
import { titleVariants } from '../common/animation';
|
||||||
|
|
||||||
import style from './StageManager.module.scss';
|
import style from './StageManager.module.scss';
|
||||||
|
|
||||||
|
const formatOptions = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
};
|
||||||
|
|
||||||
export default function StageManager(props) {
|
export default function StageManager(props) {
|
||||||
const { publ, title, time, backstageEvents, selectedId, general } = props;
|
const { publ, title, time, backstageEvents, selectedId, general } = props;
|
||||||
const [filteredEvents, setFilteredEvents] = useState(null);
|
const [filteredEvents, setFilteredEvents] = useState(null);
|
||||||
@@ -41,6 +47,8 @@ export default function StageManager(props) {
|
|||||||
if (time.isNegative) stageTimer = `-${stageTimer}`;
|
if (time.isNegative) stageTimer = `-${stageTimer}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container__gray}>
|
<div className={style.container__gray}>
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
@@ -118,7 +126,7 @@ export default function StageManager(props) {
|
|||||||
|
|
||||||
<div className={style.clockContainer}>
|
<div className={style.clockContainer}>
|
||||||
<div className={style.label}>Time Now</div>
|
<div className={style.label}>Time Now</div>
|
||||||
<div className={style.clock}>{time.clock}</div>
|
<div className={style.clock}>{clock}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.countdownContainer}>
|
<div className={style.countdownContainer}>
|
||||||
|
|||||||
@@ -6,19 +6,24 @@ import NavLogo from '../../../common/components/nav/NavLogo';
|
|||||||
import Empty from '../../../common/components/state/Empty';
|
import Empty from '../../../common/components/state/Empty';
|
||||||
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||||
import getDelayTo from '../../../common/utils/getDelayTo';
|
import getDelayTo from '../../../common/utils/getDelayTo';
|
||||||
import { stringFromMillis } from '../../../common/utils/time';
|
import { formatTime } from '../../../common/utils/time';
|
||||||
|
|
||||||
import { fetchTimerData, sanitiseTitle, timerMessages } from './countdown.helpers';
|
import { fetchTimerData, sanitiseTitle, timerMessages } from './countdown.helpers';
|
||||||
|
|
||||||
import style from './Countdown.module.scss';
|
import style from './Countdown.module.scss';
|
||||||
|
|
||||||
|
const formatOptions = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
};
|
||||||
|
|
||||||
export default function Countdown(props) {
|
export default function Countdown(props) {
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const { backstageEvents, time, selectedId } = props;
|
const { backstageEvents, time, selectedId } = props;
|
||||||
const [follow, setFollow] = useState(null);
|
const [follow, setFollow] = useState(null);
|
||||||
const [runningTimer, setRunningTimer] = useState(0);
|
const [runningTimer, setRunningTimer] = useState(0);
|
||||||
const [runningMessage, setRunningMessage] = useState('');
|
const [runningMessage, setRunningMessage] = useState('');
|
||||||
const [delay, setDelay] = useState(0);
|
const [delay, setDelay] = useState(0);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
// Set window title
|
// Set window title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -46,8 +51,8 @@ export default function Countdown(props) {
|
|||||||
if (typeof followThis !== 'undefined') {
|
if (typeof followThis !== 'undefined') {
|
||||||
setFollow(followThis);
|
setFollow(followThis);
|
||||||
const idx = backstageEvents.findIndex((event) => event.id === followThis.id);
|
const idx = backstageEvents.findIndex((event) => event.id === followThis.id);
|
||||||
const delay = getDelayTo(backstageEvents, idx);
|
const delayToEvent = getDelayTo(backstageEvents, idx);
|
||||||
setDelay(delay);
|
setDelay(delayToEvent);
|
||||||
}
|
}
|
||||||
}, [backstageEvents, searchParams]);
|
}, [backstageEvents, searchParams]);
|
||||||
|
|
||||||
@@ -73,6 +78,16 @@ export default function Countdown(props) {
|
|||||||
|
|
||||||
const isSelected = useMemo(() => runningMessage === timerMessages.running, [runningMessage]);
|
const isSelected = useMemo(() => runningMessage === timerMessages.running, [runningMessage]);
|
||||||
|
|
||||||
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
const startTime =
|
||||||
|
follow === null
|
||||||
|
? '...'
|
||||||
|
: formatTime(follow.timeStart + delay, formatOptions);
|
||||||
|
const endTime =
|
||||||
|
follow === null
|
||||||
|
? '...'
|
||||||
|
: formatTime(follow.timeEnd + delay, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
@@ -100,19 +115,17 @@ export default function Countdown(props) {
|
|||||||
<div className={style.timers}>
|
<div className={style.timers}>
|
||||||
<div className={style.timer}>
|
<div className={style.timer}>
|
||||||
<div className={style.label}>Time Now</div>
|
<div className={style.label}>Time Now</div>
|
||||||
<span className={style.value}>{time.clock}</span>
|
<span className={style.value}>{clock}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.timer}>
|
<div className={style.timer}>
|
||||||
<div className={style.label}>Start Time</div>
|
<div className={style.label}>Start Time</div>
|
||||||
<span className={`${style.value} ${delay > 0 ? style.delayed : ''}`}>
|
<span className={`${style.value} ${delay > 0 ? style.delayed : ''}`}>
|
||||||
{stringFromMillis(follow.timeStart + delay)}
|
{startTime}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.timer}>
|
<div className={style.timer}>
|
||||||
<div className={style.label}>End Time</div>
|
<div className={style.label}>End Time</div>
|
||||||
<span className={`${style.value} ${delay > 0 ? style.delayed : ''}`}>
|
<span className={`${style.value} ${delay > 0 ? style.delayed : ''}`}>{endTime}</span>
|
||||||
{stringFromMillis(follow.timeEnd + delay)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.status}>{runningMessage}</div>
|
<div className={style.status}>{runningMessage}</div>
|
||||||
@@ -126,7 +139,7 @@ export default function Countdown(props) {
|
|||||||
isSelected || time.waiting
|
isSelected || time.waiting
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<div className={style.title}>{follow.title || 'Untitled Event'}</div>
|
<div className={style.title}>{follow?.title || 'Untitled Event'}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -137,4 +150,5 @@ Countdown.propTypes = {
|
|||||||
backstageEvents: PropTypes.array,
|
backstageEvents: PropTypes.array,
|
||||||
time: PropTypes.object,
|
time: PropTypes.object,
|
||||||
selectedId: PropTypes.string,
|
selectedId: PropTypes.string,
|
||||||
|
settings: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('fetchTimerData() function', () => {
|
|||||||
const startMockValue = 10000;
|
const startMockValue = 10000;
|
||||||
const timeNow = 1000;
|
const timeNow = 1000;
|
||||||
const follow = { id: 'anotherevent', timeStart: startMockValue };
|
const follow = { id: 'anotherevent', timeStart: startMockValue };
|
||||||
const time = { clockMs: timeNow };
|
const time = { clock: timeNow };
|
||||||
|
|
||||||
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
||||||
expect(message).toBe(timerMessages.toStart);
|
expect(message).toBe(timerMessages.toStart);
|
||||||
@@ -48,7 +48,7 @@ describe('fetchTimerData() function', () => {
|
|||||||
const timeNow = 15000;
|
const timeNow = 15000;
|
||||||
const followId = 'testId';
|
const followId = 'testId';
|
||||||
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
||||||
const time = { clockMs: timeNow, running: endMockValue - startMockValue };
|
const time = { clock: timeNow, running: endMockValue - startMockValue };
|
||||||
|
|
||||||
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
||||||
expect(message).toBe(timerMessages.waiting);
|
expect(message).toBe(timerMessages.waiting);
|
||||||
@@ -61,7 +61,7 @@ describe('fetchTimerData() function', () => {
|
|||||||
const timeNow = 30000;
|
const timeNow = 30000;
|
||||||
const followId = 'testId';
|
const followId = 'testId';
|
||||||
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
||||||
const time = { clockMs: timeNow, running: endMockValue - startMockValue };
|
const time = { clock: timeNow, running: endMockValue - startMockValue };
|
||||||
|
|
||||||
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
||||||
expect(message).toBe(timerMessages.ended);
|
expect(message).toBe(timerMessages.ended);
|
||||||
@@ -74,7 +74,7 @@ describe('fetchTimerData() function', () => {
|
|||||||
const timeNow = 15000;
|
const timeNow = 15000;
|
||||||
const followId = 'testId';
|
const followId = 'testId';
|
||||||
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
||||||
const time = { clockMs: timeNow, running: DAY_TO_MS + endMockValue - startMockValue };
|
const time = { clock: timeNow, running: DAY_TO_MS + endMockValue - startMockValue };
|
||||||
|
|
||||||
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
||||||
expect(message).toBe(timerMessages.waiting);
|
expect(message).toBe(timerMessages.waiting);
|
||||||
@@ -87,7 +87,7 @@ describe('fetchTimerData() function', () => {
|
|||||||
const timeNow = 15000;
|
const timeNow = 15000;
|
||||||
const followId = 'testId';
|
const followId = 'testId';
|
||||||
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
||||||
const time = { clockMs: timeNow, running: DAY_TO_MS + endMockValue - startMockValue };
|
const time = { clock: timeNow, running: DAY_TO_MS + endMockValue - startMockValue };
|
||||||
|
|
||||||
const { message, timer } = fetchTimerData(time, follow, followId);
|
const { message, timer } = fetchTimerData(time, follow, followId);
|
||||||
expect(message).toBe(timerMessages.running);
|
expect(message).toBe(timerMessages.running);
|
||||||
@@ -100,7 +100,7 @@ describe('fetchTimerData() function', () => {
|
|||||||
const timeNow = 2000;
|
const timeNow = 2000;
|
||||||
const followId = 'testId';
|
const followId = 'testId';
|
||||||
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
const follow = { id: followId, timeStart: startMockValue, timeEnd: endMockValue };
|
||||||
const time = { clockMs: timeNow, running: DAY_TO_MS + endMockValue - startMockValue };
|
const time = { clock: timeNow, running: DAY_TO_MS + endMockValue - startMockValue };
|
||||||
|
|
||||||
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
const { message, timer } = fetchTimerData(time, follow, 'notthesameevent');
|
||||||
expect(message).toBe(timerMessages.toStart);
|
expect(message).toBe(timerMessages.toStart);
|
||||||
|
|||||||
@@ -34,22 +34,22 @@ export const fetchTimerData = (time, follow, selectedId) => {
|
|||||||
// check that is not running
|
// check that is not running
|
||||||
message = time.playstate === 'pause' ? timerMessages.waiting : timerMessages.running;
|
message = time.playstate === 'pause' ? timerMessages.waiting : timerMessages.running;
|
||||||
timer = time.running;
|
timer = time.running;
|
||||||
} else if (time.clockMs < follow.timeStart) {
|
} else if (time.clock < follow.timeStart) {
|
||||||
// if it hasnt started, we count to start
|
// if it hasnt started, we count to start
|
||||||
message = timerMessages.toStart;
|
message = timerMessages.toStart;
|
||||||
timer = millisToSeconds(follow.timeStart - time.clockMs);
|
timer = millisToSeconds(follow.timeStart - time.clock);
|
||||||
} else if (follow.timeStart <= time.clockMs && time.clockMs <= follow.timeEnd) {
|
} else if (follow.timeStart <= time.clock && time.clock <= follow.timeEnd) {
|
||||||
// if it has started, we show running timer
|
// if it has started, we show running timer
|
||||||
message = timerMessages.waiting;
|
message = timerMessages.waiting;
|
||||||
timer = time.running;
|
timer = time.running;
|
||||||
} else {
|
} else {
|
||||||
if (follow.timeStart > follow.timeEnd) {
|
if (follow.timeStart > follow.timeEnd) {
|
||||||
// ends day after
|
// ends day after
|
||||||
if (follow.timeStart > time.clockMs ) {
|
if (follow.timeStart > time.clock ) {
|
||||||
// if it hasnt started, we count to start
|
// if it hasnt started, we count to start
|
||||||
message = timerMessages.toStart;
|
message = timerMessages.toStart;
|
||||||
timer = millisToSeconds(follow.timeStart - time.clockMs);
|
timer = millisToSeconds(follow.timeStart - time.clock);
|
||||||
} else if (follow.timeStart <= time.clockMs) {
|
} else if (follow.timeStart <= time.clock) {
|
||||||
// if it has started, we show running timer
|
// if it has started, we show running timer
|
||||||
message = timerMessages.waiting;
|
message = timerMessages.waiting;
|
||||||
timer = time.running;
|
timer = time.running;
|
||||||
|
|||||||
@@ -6,10 +6,16 @@ import TitleSide from 'common/components/views/TitleSide';
|
|||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { formatTime } from '../../../common/utils/time';
|
||||||
import { titleVariants } from '../common/animation';
|
import { titleVariants } from '../common/animation';
|
||||||
|
|
||||||
import style from './Public.module.scss';
|
import style from './Public.module.scss';
|
||||||
|
|
||||||
|
const formatOptions = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
};
|
||||||
|
|
||||||
export default function Public(props) {
|
export default function Public(props) {
|
||||||
const { publ, publicTitle, time, events, publicSelectedId, general } = props;
|
const { publ, publicTitle, time, events, publicSelectedId, general } = props;
|
||||||
const [pageNumber, setPageNumber] = useState(0);
|
const [pageNumber, setPageNumber] = useState(0);
|
||||||
@@ -23,7 +29,7 @@ export default function Public(props) {
|
|||||||
// Format messages
|
// Format messages
|
||||||
const showPubl = publ.text !== '' && publ.visible;
|
const showPubl = publ.text !== '' && publ.visible;
|
||||||
|
|
||||||
// motion
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container__gray}>
|
<div className={style.container__gray}>
|
||||||
@@ -78,12 +84,12 @@ export default function Public(props) {
|
|||||||
<div className={style.label}>Today</div>
|
<div className={style.label}>Today</div>
|
||||||
<div className={style.nav}>
|
<div className={style.nav}>
|
||||||
{pageNumber > 1 &&
|
{pageNumber > 1 &&
|
||||||
[...Array(pageNumber).keys()].map((i) => (
|
[...Array(pageNumber).keys()].map((i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={i === currentPage ? style.navItemSelected : style.navItem}
|
className={i === currentPage ? style.navItemSelected : style.navItem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Paginator
|
<Paginator
|
||||||
@@ -95,18 +101,14 @@ export default function Public(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className={showPubl ? style.publicContainer : style.publicContainerHidden}>
|
||||||
className={
|
|
||||||
showPubl ? style.publicContainer : style.publicContainerHidden
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className={style.label}>Public message</div>
|
<div className={style.label}>Public message</div>
|
||||||
<div className={style.message}>{publ.text}</div>
|
<div className={style.message}>{publ.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.clockContainer}>
|
<div className={style.clockContainer}>
|
||||||
<div className={style.label}>Time Now</div>
|
<div className={style.label}>Time Now</div>
|
||||||
<div className={style.clock}>{time.clock}</div>
|
<div className={style.clock}>{clock}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.infoContainer}>
|
<div className={style.infoContainer}>
|
||||||
@@ -116,11 +118,7 @@ export default function Public(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className={style.qr}>
|
<div className={style.qr}>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.url != null && general.url !== '' && (
|
||||||
<QRCode
|
<QRCode value={general.url} size={window.innerWidth / 12} level='L' />
|
||||||
value={general.url}
|
|
||||||
size={window.innerWidth / 12}
|
|
||||||
level='L'
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,20 +7,27 @@ import { formatDisplay } from 'common/utils/dateConfig';
|
|||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { formatTime } from '../../../common/utils/time';
|
||||||
|
|
||||||
import style from './Pip.module.scss';
|
import style from './Pip.module.scss';
|
||||||
|
|
||||||
|
const formatOptions = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
};
|
||||||
|
|
||||||
export default function Pip(props) {
|
export default function Pip(props) {
|
||||||
const { time, backstageEvents, selectedId, general } = props;
|
const { time, backstageEvents, selectedId, general } = props;
|
||||||
const [size, setSize] = useState('');
|
const [size, setSize] = useState('');
|
||||||
const ref = useRef(null);
|
const pipAreaRef = useRef(null);
|
||||||
const [filteredEvents, setFilteredEvents] = useState(null);
|
const [filteredEvents, setFilteredEvents] = useState(null);
|
||||||
const [pageNumber, setPageNumber] = useState(0);
|
const [pageNumber, setPageNumber] = useState(0);
|
||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
|
|
||||||
// calculcate pip size
|
// calculate pip size
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const h = ref.current.clientHeight;
|
const h = pipAreaRef.current.clientHeight;
|
||||||
const w = ref.current.clientWidth;
|
const w = pipAreaRef.current.clientWidth;
|
||||||
setSize(`${w} x ${h}`);
|
setSize(`${w} x ${h}`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -51,11 +58,12 @@ export default function Pip(props) {
|
|||||||
}, [backstageEvents]);
|
}, [backstageEvents]);
|
||||||
|
|
||||||
// Format messages
|
// Format messages
|
||||||
const showInfo =
|
const showInfo = general.backstageInfo !== '' && general.backstageInfo != null;
|
||||||
general.backstageInfo !== '' && general.backstageInfo != null;
|
|
||||||
let stageTimer = formatDisplay(Math.abs(time.running), true);
|
let stageTimer = formatDisplay(Math.abs(time.running), true);
|
||||||
if (time.isNegative) stageTimer = `-${stageTimer}`;
|
if (time.isNegative) stageTimer = `-${stageTimer}`;
|
||||||
|
|
||||||
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container__gray}>
|
<div className={style.container__gray}>
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
@@ -67,12 +75,12 @@ export default function Pip(props) {
|
|||||||
<div className={style.label}>Today</div>
|
<div className={style.label}>Today</div>
|
||||||
<div className={style.nav}>
|
<div className={style.nav}>
|
||||||
{pageNumber > 1 &&
|
{pageNumber > 1 &&
|
||||||
[...Array(pageNumber).keys()].map((i) => (
|
[...Array(pageNumber).keys()].map((i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={i === currentPage ? style.navItemSelected : style.navItem}
|
className={i === currentPage ? style.navItemSelected : style.navItem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Paginator
|
<Paginator
|
||||||
@@ -86,7 +94,7 @@ export default function Pip(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.pip} ref={ref}>
|
<div className={style.pip} ref={pipAreaRef}>
|
||||||
<Emptyimage className={style.empty} />
|
<Emptyimage className={style.empty} />
|
||||||
<span className={style.piptext}>{size}</span>
|
<span className={style.piptext}>{size}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,11 +108,7 @@ export default function Pip(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className={style.qr}>
|
<div className={style.qr}>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.url != null && general.url !== '' && (
|
||||||
<QRCode
|
<QRCode value={general.url} size={window.innerWidth / 12} level='L' />
|
||||||
value={general.url}
|
|
||||||
size={window.innerWidth / 12}
|
|
||||||
level='L'
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -113,7 +117,7 @@ export default function Pip(props) {
|
|||||||
|
|
||||||
<div className={style.clockContainer}>
|
<div className={style.clockContainer}>
|
||||||
<div className={style.label}>Time Now</div>
|
<div className={style.label}>Time Now</div>
|
||||||
<div className={style.clock}>{time.clock}</div>
|
<div className={style.clock}>{clock}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.countdownContainer}>
|
<div className={style.countdownContainer}>
|
||||||
|
|||||||
@@ -9,13 +9,18 @@ import {
|
|||||||
getEventsWithDelay,
|
getEventsWithDelay,
|
||||||
trimEventlist,
|
trimEventlist,
|
||||||
} from '../../../common/utils/eventsManager';
|
} from '../../../common/utils/eventsManager';
|
||||||
|
import { formatTime, stringFromMillis } from '../../../common/utils/time';
|
||||||
|
|
||||||
import style from './StudioClock.module.scss';
|
import style from './StudioClock.module.scss';
|
||||||
|
|
||||||
|
const formatOptions = {
|
||||||
|
showSeconds: false,
|
||||||
|
format: 'hh:mm',
|
||||||
|
};
|
||||||
|
|
||||||
export default function StudioClock(props) {
|
export default function StudioClock(props) {
|
||||||
const { title, time, backstageEvents, selectedId, nextId, onAir } = props;
|
const { title, time, backstageEvents, selectedId, nextId, onAir } = props;
|
||||||
const { fontSize, ref } = useFitText({ maxFontSize: 500 });
|
const { fontSize: titleFontSize, ref: titleRef } = useFitText({ maxFontSize: 500 });
|
||||||
const [, , secondsNow] = time.clock.split(':');
|
|
||||||
const [schedule, setSchedule] = useState([]);
|
const [schedule, setSchedule] = useState([]);
|
||||||
|
|
||||||
const activeIndicators = [...Array(12).keys()];
|
const activeIndicators = [...Array(12).keys()];
|
||||||
@@ -28,26 +33,32 @@ export default function StudioClock(props) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Prepare event list
|
// Prepare event list
|
||||||
// Todo: useMemo()
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (backstageEvents == null) return;
|
if (backstageEvents == null) return;
|
||||||
|
|
||||||
|
|
||||||
const delayed = getEventsWithDelay(backstageEvents);
|
const delayed = getEventsWithDelay(backstageEvents);
|
||||||
const events = delayed.filter((e) => e.type === 'event');
|
const events = delayed.filter((e) => e.type === 'event');
|
||||||
const trimmed = trimEventlist(events, selectedId, MAX_TITLES);
|
const trimmed = trimEventlist(events, selectedId, MAX_TITLES);
|
||||||
const formatted = formatEventList(trimmed, selectedId, nextId);
|
const formatted = formatEventList(trimmed, selectedId, nextId, {
|
||||||
|
showEnd: false,
|
||||||
|
});
|
||||||
setSchedule(formatted);
|
setSchedule(formatted);
|
||||||
}, [backstageEvents, selectedId, nextId]);
|
}, [backstageEvents, nextId, selectedId] );
|
||||||
|
|
||||||
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
|
const [, , secondsNow] = stringFromMillis(time.clock).split(':');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
<div className={style.clockContainer}>
|
<div className={style.clockContainer}>
|
||||||
<div className={style.time}>{time.clockNoSeconds}</div>
|
<div className={style.time}>{clock}</div>
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={titleRef}
|
||||||
className={style.nextTitle}
|
className={style.nextTitle}
|
||||||
style={{ fontSize, height: '10vh', width: '100%', maxWidth: '82%' }}
|
style={{ fontSize: titleFontSize, height: '10vh', width: '100%', maxWidth: '82%' }}
|
||||||
>
|
>
|
||||||
{title.titleNext}
|
{title.titleNext}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ $cyan-idle: #0aa;
|
|||||||
line-height: 0.8em;
|
line-height: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeAA {
|
||||||
|
color: $red-active;
|
||||||
|
font-size: calc(#{$clock-size} / 4.5);
|
||||||
|
margin-top: calc(50% - calc(#{$clock-size} / 7));
|
||||||
|
line-height: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
.nextTitle:after,
|
.nextTitle:after,
|
||||||
.nextCountdown:after,
|
.nextCountdown:after,
|
||||||
.nextCountdown__overtime:after {
|
.nextCountdown__overtime:after {
|
||||||
|
|||||||
@@ -7,8 +7,15 @@ import TitleCard from 'common/components/views/TitleCard';
|
|||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { formatTime } from '../../../common/utils/time';
|
||||||
|
|
||||||
import style from './Timer.module.scss';
|
import style from './Timer.module.scss';
|
||||||
|
|
||||||
|
const formatOptions = {
|
||||||
|
showSeconds: true,
|
||||||
|
format: 'hh:mm:ss a',
|
||||||
|
};
|
||||||
|
|
||||||
export default function Timer(props) {
|
export default function Timer(props) {
|
||||||
const { general, pres, title, time } = props;
|
const { general, pres, title, time } = props;
|
||||||
const [elapsed, setElapsed] = useState(true);
|
const [elapsed, setElapsed] = useState(true);
|
||||||
@@ -32,22 +39,12 @@ export default function Timer(props) {
|
|||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
const showOverlay = pres.text !== '' && pres.visible;
|
const showOverlay = pres.text !== '' && pres.visible;
|
||||||
const isPlaying = time.playstate !== 'pause';
|
const isPlaying = time.playstate !== 'pause';
|
||||||
const normalisedTime = Math.max(time.running, 0);
|
const normalisedTime = Math.max(time.running, 0);
|
||||||
|
|
||||||
// show timer if end message is empty
|
|
||||||
const endMessage =
|
|
||||||
general.endMessage == null || general.endMessage === '' ? (
|
|
||||||
<TimerDisplay
|
|
||||||
time={time.running}
|
|
||||||
isNegative={time.isNegative}
|
|
||||||
hideZeroHours
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
general.endMessage
|
|
||||||
);
|
|
||||||
|
|
||||||
// motion
|
// motion
|
||||||
const titleVariants = {
|
const titleVariants = {
|
||||||
hidden: {
|
hidden: {
|
||||||
@@ -65,16 +62,8 @@ export default function Timer(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={time.finished ? style.container__grayFinished : style.container__gray}>
|
||||||
className={
|
<div className={showOverlay ? style.messageOverlayActive : style.messageOverlay}>
|
||||||
time.finished ? style.container__grayFinished : style.container__gray
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
showOverlay ? style.messageOverlayActive : style.messageOverlay
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className={style.message}>{pres.text}</div>
|
<div className={style.message}>{pres.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -82,12 +71,18 @@ export default function Timer(props) {
|
|||||||
|
|
||||||
<div className={style.clockContainer}>
|
<div className={style.clockContainer}>
|
||||||
<div className={style.label}>Time Now</div>
|
<div className={style.label}>Time Now</div>
|
||||||
<div className={style.clock}>{time.clock}</div>
|
<div className={style.clock}>{clock}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.timerContainer}>
|
<div className={style.timerContainer}>
|
||||||
{time.finished ? (
|
{time.finished ? (
|
||||||
<div className={style.finished}>{endMessage}</div>
|
<div className={style.finished}>
|
||||||
|
{general.endMessage == null || general.endMessage === '' ? (
|
||||||
|
<TimerDisplay time={time.running} isNegative={time.isNegative} hideZeroHours />
|
||||||
|
) : (
|
||||||
|
general.endMessage
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
||||||
<TimerDisplay time={normalisedTime} hideZeroHours />
|
<TimerDisplay time={normalisedTime} hideZeroHours />
|
||||||
@@ -96,11 +91,7 @@ export default function Timer(props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!time.finished && (
|
{!time.finished && (
|
||||||
<div
|
<div className={isPlaying ? style.progressContainer : style.progressContainerPaused}>
|
||||||
className={
|
|
||||||
isPlaying ? style.progressContainer : style.progressContainerPaused
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MyProgressBar
|
<MyProgressBar
|
||||||
now={normalisedTime}
|
now={normalisedTime}
|
||||||
complete={time.durationSeconds}
|
complete={time.durationSeconds}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
grid-template-columns: 1fr 1fr 5vw 1fr 1fr;
|
grid-template-columns: 1fr 1fr 5vw 1fr 1fr;
|
||||||
grid-template-rows: auto 1fr auto minmax(25vh, auto);
|
grid-template-rows: auto 1fr auto minmax(25vh, auto);
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
' clck .... .... .... ....'
|
' clck clck .... .... ....'
|
||||||
' timr timr timr timr timr'
|
' timr timr timr timr timr'
|
||||||
' prog prog prog prog prog'
|
' prog prog prog prog prog'
|
||||||
' now now .... next next';
|
' now now .... next next';
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
|
|
||||||
.clock {
|
.clock {
|
||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
font-size: 3vw;
|
font-size: 2.25vw;
|
||||||
line-height: 3vw;
|
line-height: 3vw;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
letter-spacing: 0.25vw;
|
letter-spacing: 0.25vw;
|
||||||
|
|||||||
@@ -6623,6 +6623,11 @@ jest@^27.4.3:
|
|||||||
import-local "^3.0.2"
|
import-local "^3.0.2"
|
||||||
jest-cli "^27.5.1"
|
jest-cli "^27.5.1"
|
||||||
|
|
||||||
|
jotai@^1.7.8:
|
||||||
|
version "1.7.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.7.8.tgz#1ac4daa2731e0f8e7fab8abb96aebf94b9dfc1a3"
|
||||||
|
integrity sha512-rXwWz6uLqyZUCRzWIPiYTjI1hjskVxVpDN3lBoOHi66z6uQFkFmKwR8YhcQd+Ex4lODlReuKNIx6WwsF9aKy3g==
|
||||||
|
|
||||||
js-sha3@0.8.0:
|
js-sha3@0.8.0:
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
||||||
@@ -6928,6 +6933,11 @@ lru-cache@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
|
luxon@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.0.1.tgz#6901111d10ad06fd267ad4e4128a84bef8a77299"
|
||||||
|
integrity sha512-hF3kv0e5gwHQZKz4wtm4c+inDtyc7elkanAsBq+fundaCdUBNJB1dHEGUZIM6SfSBUlbVFduPwEtNjFK8wLtcw==
|
||||||
|
|
||||||
lz-string@^1.4.4:
|
lz-string@^1.4.4:
|
||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
|
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "1.5.0",
|
"version": "1.6.0",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -1049,7 +1049,6 @@ export class EventTimer extends Timer {
|
|||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deleted an event from the list by its id
|
* Deleted an event from the list by its id
|
||||||
* @param {string} eventId
|
* @param {string} eventId
|
||||||
|
|||||||
@@ -201,12 +201,14 @@ export const getSettings = async (req, res) => {
|
|||||||
const version = data.settings.version;
|
const version = data.settings.version;
|
||||||
const serverPort = data.settings.serverPort;
|
const serverPort = data.settings.serverPort;
|
||||||
const pinCode = data.settings.pinCode;
|
const pinCode = data.settings.pinCode;
|
||||||
|
const timeFormat = data.settings.timeFormat;
|
||||||
|
|
||||||
// send object with network information
|
// send object with network information
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
version,
|
version,
|
||||||
serverPort,
|
serverPort,
|
||||||
pinCode,
|
pinCode,
|
||||||
|
timeFormat,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,9 +228,18 @@ export const postSettings = async (req, res) => {
|
|||||||
pin = req.body?.pinCode;
|
pin = req.body?.pinCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timeFormat = data.settings.timeFormat;
|
||||||
|
if (typeof req.body?.timeFormat === 'string') {
|
||||||
|
if (req.body?.timeFormat === '12' || req.body?.timeFormat === '24') {
|
||||||
|
timeFormat = req.body.timeFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data.settings = {
|
data.settings = {
|
||||||
...data.settings,
|
...data.settings,
|
||||||
pinCode: pin,
|
pinCode: pin,
|
||||||
|
timeFormat: timeFormat,
|
||||||
};
|
};
|
||||||
await db.write();
|
await db.write();
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const dbModelv1 = {
|
|||||||
serverPort: 4001,
|
serverPort: 4001,
|
||||||
lock: null,
|
lock: null,
|
||||||
pinCode: null,
|
pinCode: null,
|
||||||
|
timeFormat: '24',
|
||||||
},
|
},
|
||||||
aliases: [],
|
aliases: [],
|
||||||
userFields: {
|
userFields: {
|
||||||
|
|||||||
@@ -51,4 +51,4 @@ describe('getPreviousPlayable()', () => {
|
|||||||
expect(id).toBe(null);
|
expect(id).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ describe('test json parser with valid def', () => {
|
|||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
version: 1,
|
version: 1,
|
||||||
|
timeFormat: '24',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -386,6 +387,7 @@ describe('test corrupt data', () => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
serverPort: 4001,
|
serverPort: 4001,
|
||||||
lock: null,
|
lock: null,
|
||||||
|
timeFormat: '24',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -408,6 +410,7 @@ describe('test corrupt data', () => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
serverPort: 4001,
|
serverPort: 4001,
|
||||||
lock: null,
|
lock: null,
|
||||||
|
timeFormat: '24',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -424,6 +427,7 @@ describe('test corrupt data', () => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
serverPort: 4001,
|
serverPort: 4001,
|
||||||
lock: null,
|
lock: null,
|
||||||
|
timeFormat: '24',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export const parseSettings_v1 = (data, enforce) => {
|
|||||||
const settings = {
|
const settings = {
|
||||||
lock: s.lock || null,
|
lock: s.lock || null,
|
||||||
pinCode: s.pinCode || null,
|
pinCode: s.pinCode || null,
|
||||||
|
timeFormat: s.timeFormat || '24',
|
||||||
};
|
};
|
||||||
|
|
||||||
// write to db
|
// write to db
|
||||||
|
|||||||
Reference in New Issue
Block a user