mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 11:23:50 +00:00
Feat/studio clock (#60)
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import navlogo from 'assets/images/logos/LOGO-72.png';
|
||||
import style from './NavLogo.module.css';
|
||||
import style from './NavLogo.module.scss';
|
||||
|
||||
export default function NavLogo() {
|
||||
export default function NavLogo(props) {
|
||||
const {isHidden} = props;
|
||||
const [showNav, setShowNav] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
@@ -30,9 +32,10 @@ export default function NavLogo() {
|
||||
};
|
||||
}, [handleKeyPress]);
|
||||
|
||||
const baseOpacity = (isHidden) ? 0 : 0.5
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0.5 }}
|
||||
initial={{ opacity: baseOpacity }}
|
||||
whileHover={{ opacity: 1 }}
|
||||
className={style.navContainer}
|
||||
>
|
||||
@@ -51,12 +54,12 @@ export default function NavLogo() {
|
||||
className={showNav ? style.nav : style.navHidden}
|
||||
>
|
||||
<Link
|
||||
to='/speaker'
|
||||
to='/presenter'
|
||||
className={style.navItem}
|
||||
tabIndex={1}
|
||||
onKeyDownCapture={() => <Redirect push to='/speaker' />}
|
||||
onKeyDownCapture={() => <Redirect push to='/presenter' />}
|
||||
>
|
||||
Speaker
|
||||
Presenter
|
||||
</Link>
|
||||
<Link
|
||||
to='/sm'
|
||||
@@ -90,9 +93,21 @@ export default function NavLogo() {
|
||||
>
|
||||
PIP
|
||||
</Link>
|
||||
<Link
|
||||
to='/studio'
|
||||
className={style.navItem}
|
||||
tabIndex={5}
|
||||
onKeyDownCapture={() => <Redirect push to='/studio' />}
|
||||
>
|
||||
Studio Clock
|
||||
</Link>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
NavLogo.propTypes = {
|
||||
isHidden: PropTypes.bool,
|
||||
}
|
||||
|
||||
+4
@@ -1,6 +1,9 @@
|
||||
$nav-color: #fff;
|
||||
|
||||
.navContainer {
|
||||
position: absolute;
|
||||
right: 2vw;
|
||||
top: 1vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
@@ -23,4 +26,5 @@
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 0.5vh 1vw;
|
||||
border-radius: 4px;
|
||||
color: $nav-color;
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
import {formatEventList, getEventsWithDelay, trimEventlist} from "../eventsManager";
|
||||
|
||||
test('getEventsWithDelay function', () => {
|
||||
|
||||
const testData = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"timeStart": 28800000,
|
||||
"timeEnd": 30600000,
|
||||
"type": "event",
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"duration": 60000,
|
||||
"type": "delay",
|
||||
"id": "24240"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"timeStart": 34920000,
|
||||
"timeEnd": 35520000,
|
||||
"type": "event",
|
||||
"id": "8ee5"
|
||||
},
|
||||
{
|
||||
"title": "Use simpler times to create a timer",
|
||||
"timeStart": 120000,
|
||||
"timeEnd": 720000,
|
||||
"type": "event",
|
||||
"id": "8222"
|
||||
},
|
||||
{
|
||||
"duration": 900000,
|
||||
"type": "delay",
|
||||
"revision": 0,
|
||||
"id": "a386"
|
||||
},
|
||||
{
|
||||
"title": "Add delay blocks to affect all events",
|
||||
"timeStart": 37320000,
|
||||
"timeEnd": 38520000,
|
||||
"type": "event",
|
||||
"id": "6dce"
|
||||
},
|
||||
{
|
||||
"title": "Add and remove events with [+] and [-]",
|
||||
"timeStart": 38520000,
|
||||
"timeEnd": 45120000,
|
||||
"type": "event",
|
||||
"id": "2651"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "e6a1"
|
||||
},
|
||||
{
|
||||
"title": "And control whether they are public",
|
||||
"timeStart": 46800000,
|
||||
"timeEnd": 57600000,
|
||||
"type": "event",
|
||||
"id": "1358"
|
||||
}
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"timeStart": 28800000,
|
||||
"timeEnd": 30600000,
|
||||
"type": "event",
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"timeStart": 34920000+60000,
|
||||
"timeEnd": 35520000+60000,
|
||||
"type": "event",
|
||||
"id": "8ee5"
|
||||
},
|
||||
{
|
||||
"title": "Use simpler times to create a timer",
|
||||
"timeStart": 120000+60000,
|
||||
"timeEnd": 720000+60000,
|
||||
"type": "event",
|
||||
"id": "8222"
|
||||
},
|
||||
{
|
||||
"title": "Add delay blocks to affect all events",
|
||||
"timeStart": 37320000+60000+900000,
|
||||
"timeEnd": 38520000+60000+900000,
|
||||
"type": "event",
|
||||
"id": "6dce"
|
||||
},
|
||||
{
|
||||
"title": "Add and remove events with [+] and [-]",
|
||||
"timeStart": 38520000+60000+900000,
|
||||
"timeEnd": 45120000+60000+900000,
|
||||
"type": "event",
|
||||
"id": "2651"
|
||||
},
|
||||
{
|
||||
"title": "And control whether they are public",
|
||||
"timeStart": 46800000,
|
||||
"timeEnd": 57600000,
|
||||
"type": "event",
|
||||
"id": "1358"
|
||||
}
|
||||
]
|
||||
|
||||
expect(getEventsWithDelay(testData)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
describe('getEventsWithDelay edge cases', () => {
|
||||
|
||||
test('given an empty array', () => {
|
||||
const emptyArray = {
|
||||
test: [],
|
||||
expect: [],
|
||||
}
|
||||
|
||||
expect(getEventsWithDelay(emptyArray.test)).toStrictEqual(emptyArray.expect);
|
||||
});
|
||||
|
||||
test('given an undefined object', () => {
|
||||
const withUndefined = {
|
||||
test: undefined,
|
||||
expect: [],
|
||||
}
|
||||
|
||||
expect(getEventsWithDelay(withUndefined.test)).toStrictEqual(withUndefined.expect);
|
||||
});
|
||||
|
||||
test('given a corrupted event object', () => {
|
||||
const testData = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"timeEnd": 30600000,
|
||||
"type": "event",
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"duration": 60000,
|
||||
"type": "delay",
|
||||
"id": "24240"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"timeStart": 34920000,
|
||||
"timeEnd": 35520000,
|
||||
"type": "event",
|
||||
"id": "8ee5"
|
||||
}
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"timeEnd": 30600000,
|
||||
"type": "event",
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"timeStart": 34920000+60000,
|
||||
"timeEnd": 35520000+60000,
|
||||
"type": "event",
|
||||
"id": "8ee5"
|
||||
}
|
||||
];
|
||||
|
||||
expect(getEventsWithDelay(testData)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('given a corrupted delay object', () => {
|
||||
const testData = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"timeStart": 28800000,
|
||||
"timeEnd": 30600000,
|
||||
"type": "event",
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"type": "delay",
|
||||
"id": "24240"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"timeStart": 34920000,
|
||||
"timeEnd": 35520000,
|
||||
"type": "event",
|
||||
"id": "8ee5"
|
||||
}
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"timeStart": 28800000,
|
||||
"timeEnd": 30600000,
|
||||
"type": "event",
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"timeStart": 34920000,
|
||||
"timeEnd": 35520000,
|
||||
"type": "event",
|
||||
"id": "8ee5"
|
||||
}
|
||||
];
|
||||
|
||||
expect(getEventsWithDelay(testData)).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test trimEventlist function', () => {
|
||||
|
||||
const limit = 8;
|
||||
const testData = [
|
||||
{id: '1'},
|
||||
{id: '2'},
|
||||
{id: '3'},
|
||||
{id: '4'},
|
||||
{id: '5'},
|
||||
{id: '6'},
|
||||
{id: '7'},
|
||||
{id: '8'},
|
||||
{id: '9'},
|
||||
{id: '10'},
|
||||
{id: '11'},
|
||||
{id: '12'},
|
||||
];
|
||||
|
||||
|
||||
test('when we use the first item', () => {
|
||||
const selectedId = '1';
|
||||
const expected = [
|
||||
{id: '1'},
|
||||
{id: '2'},
|
||||
{id: '3'},
|
||||
{id: '4'},
|
||||
{id: '5'},
|
||||
{id: '6'},
|
||||
{id: '7'},
|
||||
{id: '8'},
|
||||
];
|
||||
|
||||
const l = trimEventlist(testData, selectedId, limit);
|
||||
expect(l.length).toBe(limit);
|
||||
expect(l).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('when we use the third item', () => {
|
||||
const selectedId = '3';
|
||||
const expected = [
|
||||
{id: '1'},
|
||||
{id: '2'},
|
||||
{id: '3'},
|
||||
{id: '4'},
|
||||
{id: '5'},
|
||||
{id: '6'},
|
||||
{id: '7'},
|
||||
{id: '8'}
|
||||
];
|
||||
|
||||
const l = trimEventlist(testData, selectedId, limit);
|
||||
expect(l.length).toBe(limit);
|
||||
expect(l).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('when we use the fourth item', () => {
|
||||
const selectedId = '4';
|
||||
const expected = [
|
||||
{id: '2'},
|
||||
{id: '3'},
|
||||
{id: '4'},
|
||||
{id: '5'},
|
||||
{id: '6'},
|
||||
{id: '7'},
|
||||
{id: '8'},
|
||||
{id: '9'}
|
||||
];
|
||||
|
||||
const l = trimEventlist(testData, selectedId, limit);
|
||||
expect(l.length).toBe(limit);
|
||||
expect(l).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('if selected is not found', () => {
|
||||
const selectedId = '15';
|
||||
const expected = [
|
||||
{id: '1'},
|
||||
{id: '2'},
|
||||
{id: '3'},
|
||||
{id: '4'},
|
||||
{id: '5'},
|
||||
{id: '6'},
|
||||
{id: '7'},
|
||||
{id: '8'},
|
||||
];
|
||||
|
||||
const l = trimEventlist(testData, selectedId, limit);
|
||||
expect(l.length).toBe(limit);
|
||||
expect(l).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test formatEvents function', () => {
|
||||
const testEvent = [
|
||||
{
|
||||
"title": "Welcome to Ontime",
|
||||
"subtitle": "Subtitles are useful",
|
||||
"presenter": "cpvalente",
|
||||
"note": "Maybe a running note for the operator?",
|
||||
"timeStart": 28800000,
|
||||
"timeEnd": 30600000,
|
||||
"isPublic": false,
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "5946"
|
||||
},
|
||||
{
|
||||
"title": "Unless recalled by the OSC address",
|
||||
"subtitle": "",
|
||||
"presenter": "",
|
||||
"note": "In green, below",
|
||||
"timeStart": 34800000,
|
||||
"timeEnd": 35400000,
|
||||
"isPublic": false,
|
||||
"type": "event",
|
||||
"revision": 0,
|
||||
"id": "8ee5"
|
||||
}
|
||||
];
|
||||
|
||||
test ('it parses correctly', () => {
|
||||
const selectedId = 'otherEvent';
|
||||
const nextId = 'notHere';
|
||||
const expected = [
|
||||
{
|
||||
id: '5946',
|
||||
time: '08:00 - 08:30',
|
||||
title: 'Welcome to Ontime',
|
||||
isNow: false,
|
||||
isNext: false,
|
||||
},
|
||||
{
|
||||
id: '8ee5',
|
||||
time: '09:40 - 09:50',
|
||||
title: 'Unless recalled by the OSC address',
|
||||
isNow: false,
|
||||
isNext: false,
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const parsed = formatEventList(testEvent, selectedId, nextId, true);
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test ('it handles selected correctly', () => {
|
||||
const selectedId = '5946';
|
||||
const nextId = '8ee5';
|
||||
const expected = [
|
||||
{
|
||||
id: '5946',
|
||||
time: '08:00 - 08:30',
|
||||
title: 'Welcome to Ontime',
|
||||
isNow: true,
|
||||
isNext: false,
|
||||
},
|
||||
{
|
||||
id: '8ee5',
|
||||
time: '09:40 - 09:50',
|
||||
title: 'Unless recalled by the OSC address',
|
||||
isNow: false,
|
||||
isNext: true,
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const parsed = formatEventList(testEvent, selectedId, nextId,true);
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test ('it handles next correctly', () => {
|
||||
const selectedId = '8ee5';
|
||||
const nextId = 'notHere';
|
||||
|
||||
const expected = [
|
||||
{
|
||||
id: '5946',
|
||||
time: '08:00 - 08:30',
|
||||
title: 'Welcome to Ontime',
|
||||
isNow: false,
|
||||
isNext: false,
|
||||
},
|
||||
{
|
||||
id: '8ee5',
|
||||
time: '09:40 - 09:50',
|
||||
title: 'Unless recalled by the OSC address',
|
||||
isNow: true,
|
||||
isNext: false,
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const parsed = formatEventList(testEvent, selectedId, nextId, true);
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
})
|
||||
@@ -40,11 +40,11 @@ export const stringFromMillis = (
|
||||
* another go at simpler string formatting (counters)
|
||||
* @description Converts seconds to string representing time
|
||||
* @param {number} seconds - time in seconds
|
||||
* @param {boolean} hideZero - wether to show hours in case its 00
|
||||
* @param {boolean} [hideZero] - whether to show hours in case its 00
|
||||
* @returns {string} String representing absolute time 00:12:02
|
||||
*/
|
||||
|
||||
export function formatDisplay(seconds, hideZero) {
|
||||
export function formatDisplay(seconds, hideZero=false) {
|
||||
// add an extra 0 if necessary
|
||||
const format = (val) => `0${Math.floor(val)}`.slice(-2);
|
||||
|
||||
@@ -59,7 +59,6 @@ export function formatDisplay(seconds, hideZero) {
|
||||
/**
|
||||
* @description Converts milliseconds to seconds
|
||||
* @param {number} millis - time in seconds
|
||||
* @param {boolean} hideZero - wether to show hours in case its 00
|
||||
* @returns {number} Amount in seconds
|
||||
*/
|
||||
|
||||
@@ -71,7 +70,6 @@ export const millisToSeconds = (millis) => {
|
||||
/**
|
||||
* @description Converts milliseconds to seconds
|
||||
* @param {number} millis - time in seconds
|
||||
* @param {boolean} hideZero - wether to show hours in case its 00
|
||||
* @returns {number} Amount in seconds
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @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 [];
|
||||
|
||||
const unfilteredEvents = [...events];
|
||||
|
||||
// Add running delay
|
||||
let delay = 0;
|
||||
for (const e of unfilteredEvents) {
|
||||
if (e.type === 'block') delay = 0;
|
||||
else if (e.type === 'delay') delay = delay + e.duration;
|
||||
else if (e.type === 'event' && delay > 0) {
|
||||
e.timeStart += delay;
|
||||
e.timeEnd += delay;
|
||||
}
|
||||
}
|
||||
|
||||
// filter just events
|
||||
return unfilteredEvents.filter((e) => e.type === 'event');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Returns trimmed event list array
|
||||
* @param {Object[]} events - given events
|
||||
* @param {string} selectedId - id of currently selected event
|
||||
* @param {number} limit - max number of events to return
|
||||
* @returns {Object[]} Event list with maximum <limit> objects
|
||||
*/
|
||||
export const trimEventlist = (events, selectedId, limit) => {
|
||||
if (events == null) return [];
|
||||
|
||||
const BEFORE = 2;
|
||||
const trimmedEvents = [...events];
|
||||
|
||||
// limit events length if necessary
|
||||
if (limit != null) {
|
||||
while (trimmedEvents.length > limit) {
|
||||
const idx = trimmedEvents.findIndex((e) => e.id === selectedId);
|
||||
if (idx <= BEFORE) { trimmedEvents.pop(); }
|
||||
else { trimmedEvents.shift(); }
|
||||
}
|
||||
}
|
||||
return trimmedEvents;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Returns list of events formatted to be displayed
|
||||
* @param {Object[]} events - given events
|
||||
* @param {string} selectedId - id of currently selected event
|
||||
* @param {string} nextId - id of next event
|
||||
* @param {boolean} [showEnd] - whether to show the end time
|
||||
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
|
||||
*/
|
||||
export const formatEventList = (events, selectedId, nextId, showEnd = false) => {
|
||||
if (events == null) return [];
|
||||
|
||||
const givenEvents = [...events];
|
||||
|
||||
// format list
|
||||
let formattedEvents = [];
|
||||
for (const g of givenEvents) {
|
||||
const start = stringFromMillis(g.timeStart, false);
|
||||
const end = stringFromMillis(g.timeEnd, false);
|
||||
|
||||
formattedEvents.push({
|
||||
id: g.id,
|
||||
time: showEnd ? `${start} - ${end}` : start,
|
||||
title: g.title,
|
||||
isNow: g.id === selectedId,
|
||||
isNext: g.id === nextId,
|
||||
});
|
||||
}
|
||||
|
||||
return formattedEvents;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user