mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 12:23:51 +00:00
V2 monorepo (#285)
* refactor(project structure): UI * refactor(project structure): extract utilities * refactor(project structure): remove unused * refactor(project structure): electron * refactor(project structure): server refactor: migrate to vitest refactor: monorepo config * refactor: extract application menu * refactor: exit process * refactor: extract tray menu * chore: electron build * Added Seconds in studio clock #282 --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`cx() > ignores falsy values 1`] = `""`;
|
||||
|
||||
exports[`cx() > merges styles 1`] = `"_test_98a1e0 _another_98a1e0"`;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { validateAlias } from '../aliases';
|
||||
|
||||
describe('An alias fails if incorrect', () => {
|
||||
const testsToFail = [
|
||||
// no empty
|
||||
'',
|
||||
// no https, http or www
|
||||
'https://www.test.com',
|
||||
'http://www.test.com',
|
||||
'www.test.com',
|
||||
// no hostname
|
||||
'localhost/test',
|
||||
'127.0.0.1/test',
|
||||
'0.0.0.0/test',
|
||||
// no editor
|
||||
'editor',
|
||||
'editor?test',
|
||||
];
|
||||
|
||||
testsToFail.forEach((t) =>
|
||||
it(`${t}`, () => {
|
||||
expect(validateAlias(t).status).toBeFalsy();
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,451 @@
|
||||
import {
|
||||
forgivingStringToMillis,
|
||||
formatDisplay,
|
||||
isTimeString,
|
||||
millisToMinutes,
|
||||
millisToSeconds,
|
||||
timeStringToMillis,
|
||||
} from '../dateConfig';
|
||||
import { stringFromMillis } from '../time';
|
||||
|
||||
describe('test string from formatDisplay function', () => {
|
||||
it('test with null values', () => {
|
||||
const t = { val: null, result: '00:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with not numbers', () => {
|
||||
const t = { val: 'test', result: '00:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with valid millis', () => {
|
||||
const t = { val: 3600, result: '01:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with negative millis', () => {
|
||||
const t = { val: -3600, result: '01:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 0', () => {
|
||||
const t = { val: 0, result: '00:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -0', () => {
|
||||
const t = { val: -0, result: '00:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 86400 (24 hours)', () => {
|
||||
const t = { val: 86400, result: '00:00:00' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 86401 (24 hours and 1 second)', () => {
|
||||
const t = { val: 86401, result: '00:00:01' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -86401 (-24 hours and 1 second)', () => {
|
||||
const t = { val: -86401, result: '00:00:01' };
|
||||
expect(formatDisplay(t.val, false)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test formatDisplay handles partial secs', () => {
|
||||
it('test with 1795829', () => {
|
||||
const t = { val: 1795829, result: '00:29:55' };
|
||||
expect(stringFromMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test string from formatDisplay function with hidezero', () => {
|
||||
it('test with null values', () => {
|
||||
const t = { val: null, result: '00:00' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with valid millis', () => {
|
||||
const t = { val: 3600, result: '01:00:00' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with negative millis', () => {
|
||||
const t = { val: -3600, result: '01:00:00' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 0', () => {
|
||||
const t = { val: 0, result: '00:00' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -0', () => {
|
||||
const t = { val: -0, result: '00:00' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 86400 (24 hours)', () => {
|
||||
const t = { val: 86400, result: '00:00' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 86401 (24 hours and 1 second)', () => {
|
||||
const t = { val: 86401, result: '00:01' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -86401 (-24 hours and 1 second)', () => {
|
||||
const t = { val: -86401, result: '00:01' };
|
||||
expect(formatDisplay(t.val, true)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test millisToSeconds function', () => {
|
||||
it('test with null values', () => {
|
||||
const t = { val: null, result: 0 };
|
||||
expect(millisToSeconds(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with valid millis', () => {
|
||||
const t = { val: 3600000, result: 3600 };
|
||||
expect(millisToSeconds(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with negative millis', () => {
|
||||
const t = { val: -3600000, result: -3600 };
|
||||
expect(millisToSeconds(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 0', () => {
|
||||
const t = { val: 0, result: 0 };
|
||||
expect(millisToSeconds(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -0', () => {
|
||||
const t = { val: -0, result: -0 };
|
||||
expect(millisToSeconds(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 86401000 (24 hours and 1 second)', () => {
|
||||
const t = { val: 86401000, result: 86401 };
|
||||
expect(millisToSeconds(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -86401000 (-24 hours and 1 second)', () => {
|
||||
const t = { val: -86401000, result: -86401 };
|
||||
expect(millisToSeconds(t.val, false)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test millisToMinutes function', () => {
|
||||
it('test with null values', () => {
|
||||
const t = { val: null, result: 0 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with valid millis', () => {
|
||||
const t = { val: 3600000, result: 60 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with negative millis', () => {
|
||||
const t = { val: -3600000, result: -60 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 0', () => {
|
||||
const t = { val: 0, result: 0 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -0', () => {
|
||||
const t = { val: -0, result: -0 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 86401000 (24 hours and 1 second)', () => {
|
||||
const t = { val: 86401000, result: 1440 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -86401000 (-24 hours and 1 second)', () => {
|
||||
const t = { val: -86401000, result: -1440 };
|
||||
expect(millisToMinutes(t.val, false)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test timeStringToMillis function', () => {
|
||||
it('test with null', () => {
|
||||
const t = { val: null, result: 0 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 00:00:00', () => {
|
||||
const t = { val: '00:00:00', result: 0 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -00:00:00', () => {
|
||||
const t = { val: '-00:00:00', result: 0 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 00:00:01', () => {
|
||||
const t = { val: '00:00:01', result: 1000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with -00:00:01', () => {
|
||||
const t = { val: '-00:00:01', result: 1000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 01:00:01', () => {
|
||||
const t = { val: '01:00:01', result: 3601000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 24:00:01', () => {
|
||||
const t = { val: '24:00:01', result: 86401000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 00:00:5', () => {
|
||||
const t = { val: '00:00:5', result: 5000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 00:1:00', () => {
|
||||
const t = { val: '00:1:00', result: 60000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 1:00:00', () => {
|
||||
const t = { val: '1:00:00', result: 3600000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 1', () => {
|
||||
const t = { val: '1', result: 1000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 120', () => {
|
||||
const t = { val: '120', result: 120000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 56', () => {
|
||||
const t = { val: '56', result: 56000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 2:3', () => {
|
||||
const t = { val: '2:3', result: 123000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 02:3', () => {
|
||||
const t = { val: '02:3', result: 123000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
it('test with 2:03', () => {
|
||||
const t = { val: '2:03', result: 123000 };
|
||||
expect(timeStringToMillis(t.val)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test isTimeString() function', () => {
|
||||
it('it validates time strings', () => {
|
||||
const ts = ['2', '2:10', '2:10:22'];
|
||||
for (const s of ts) {
|
||||
expect(isTimeString(s)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('it fails overloaded times', () => {
|
||||
const ts = ['70', '89:10', '26:10:22'];
|
||||
for (const s of ts) {
|
||||
expect(isTimeString(s)).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('test isTimeString() function handle different separators', () => {
|
||||
const ts = ['2:10', '2,10', '2.10'];
|
||||
for (const s of ts) {
|
||||
it(`it handles ${s}`, () => {
|
||||
expect(isTimeString(s)).toBe(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('test forgivingStringToMillis()', () => {
|
||||
describe('function handles time with no separators', () => {
|
||||
const testData = [
|
||||
{ value: '', expect: 0 },
|
||||
{ value: '0', expect: 0 },
|
||||
{ value: '-0', expect: 0 },
|
||||
{ value: '1', expect: 60 * 1000 },
|
||||
{ value: '-1', expect: 60 * 1000 },
|
||||
{ value: '000000', expect: 0 },
|
||||
{ value: '000001', expect: 1000 },
|
||||
{ value: '000100', expect: 1000 * 60 },
|
||||
{ value: '010000', expect: 1000 * 60 * 60 },
|
||||
{ value: '230000', expect: 1000 * 60 * 60 * 23 },
|
||||
{ value: '121212', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
||||
];
|
||||
|
||||
for (const s of testData) {
|
||||
it(`handles ${s.value} to left`, () => {
|
||||
expect(typeof forgivingStringToMillis(s.value)).toBe('number');
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
it(`handles ${s.value} to right`, () => {
|
||||
expect(typeof forgivingStringToMillis(s.value, false)).toBe('number');
|
||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('parses strings correctly', () => {
|
||||
const ts = [
|
||||
{ value: '1.1.1', expect: 60 * 60 * 1000 + 60 * 1000 + 1000 },
|
||||
{ value: '12.1.1', expect: 12 * 60 * 60 * 1000 + 60 * 1000 + 1000 },
|
||||
{ value: '12.55.1', expect: 12 * 60 * 60 * 1000 + 55 * 60 * 1000 + 1000 },
|
||||
{ value: '12.55.40', expect: 12 * 60 * 60 * 1000 + 55 * 60 * 1000 + 40 * 1000 },
|
||||
];
|
||||
|
||||
for (const s of ts) {
|
||||
it(`handles ${s.value} to the left`, () => {
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
it(`handles ${s.value} to the right`, () => {
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('handles overflows', () => {
|
||||
const ts = [
|
||||
// minutes overflow
|
||||
{ value: '120', expect: 1000 * 60 * 120 },
|
||||
{ value: '2.0.0', expect: 1000 * 60 * 120 },
|
||||
{ value: '99', expect: 1000 * 60 * 99 },
|
||||
{ value: '1.39.0', expect: 1000 * 60 * 99 },
|
||||
// seconds overflow
|
||||
{ value: '0.0.120', expect: 120 * 1000 },
|
||||
{ value: '0.2.0', expect: 120 * 1000 },
|
||||
{ value: '0.0.99', expect: 99 * 1000 },
|
||||
{ value: '0.1.39', expect: 99 * 1000 },
|
||||
// hours overflow
|
||||
{ value: '25.0.0', expect: 1000 * 60 * 60 * 25 },
|
||||
// hours overflow
|
||||
{ value: '50.0.0', expect: 1000 * 60 * 60 * 50 },
|
||||
];
|
||||
|
||||
for (const s of ts) {
|
||||
it(`handles ${s.value} to the left`, () => {
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
it(`handles ${s.value} to the right`, () => {
|
||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('test with fillRight (legacy)', () => {
|
||||
describe('function handles separators', () => {
|
||||
const testData = [
|
||||
{ value: '1:2:3:10', expect: 3723000 },
|
||||
{ value: '2,10', expect: 130000 },
|
||||
{ value: '2.10', expect: 130000 },
|
||||
{ value: '2 10', expect: 130000 },
|
||||
];
|
||||
|
||||
for (const s of testData) {
|
||||
it(`handles ${s.value}`, () => {
|
||||
expect(typeof forgivingStringToMillis(s.value, false)).toBe('number');
|
||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('parses strings correctly', () => {
|
||||
const ts = [
|
||||
{ value: '1.2', expect: 60 * 1000 + 2 * 1000 },
|
||||
{ value: '1.70', expect: 60 * 1000 + 70 * 1000 },
|
||||
];
|
||||
|
||||
for (const s of ts) {
|
||||
it(`handles ${s.value}`, () => {
|
||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('handles overflows', () => {
|
||||
const ts = [
|
||||
// minutes overflow
|
||||
{ value: '0.120', expect: 120 * 1000 },
|
||||
{ value: '0.99', expect: 99 * 1000 },
|
||||
];
|
||||
|
||||
for (const s of ts) {
|
||||
it(`handles ${s.value}`, () => {
|
||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('test with fillLeft', () => {
|
||||
describe('function handles separators', () => {
|
||||
const testData = [
|
||||
{ value: '1:2:3:10', expect: 3723000 },
|
||||
{ value: '2,10', expect: 2 * 60 * 60 * 1000 + 60 * 10 * 1000 },
|
||||
{ value: '2.10', expect: 2 * 60 * 60 * 1000 + 60 * 10 * 1000 },
|
||||
{ value: '2 10', expect: 2 * 60 * 60 * 1000 + 60 * 10 * 1000 },
|
||||
];
|
||||
|
||||
for (const s of testData) {
|
||||
it(`handles ${s.value}`, () => {
|
||||
expect(typeof forgivingStringToMillis(s.value)).toBe('number');
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('parses strings correctly', () => {
|
||||
const ts = [
|
||||
{ value: '1.2', expect: 60 * 60 * 1000 + 2 * 60 * 1000 },
|
||||
{ value: '1.70', expect: 60 * 60 * 1000 + 70 * 60 * 1000 },
|
||||
];
|
||||
|
||||
for (const s of ts) {
|
||||
it(`handles ${s.value}`, () => {
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('handles overflows', () => {
|
||||
const ts = [
|
||||
// minutes overflow
|
||||
{ value: '0.120', expect: 120 * 60 * 1000 },
|
||||
{ value: '0.99', expect: 99 * 60 * 1000 },
|
||||
];
|
||||
|
||||
for (const s of ts) {
|
||||
it(`handles ${s.value}`, () => {
|
||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,431 @@
|
||||
import { formatEventList, getEventsWithDelay, trimEventlist } from '../eventsManager';
|
||||
|
||||
test('getEventsWithDelay function', () => {
|
||||
const testData = [
|
||||
{
|
||||
title: 'Welcome to Ontime',
|
||||
timeStart: 28800000,
|
||||
timeEnd: 30600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '5946',
|
||||
},
|
||||
{
|
||||
duration: 60000,
|
||||
type: 'delay',
|
||||
id: '24240',
|
||||
},
|
||||
{
|
||||
title: 'Unless recalled by the OSC address',
|
||||
timeStart: 34920000,
|
||||
timeEnd: 35520000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8ee5',
|
||||
},
|
||||
{
|
||||
title: 'Use simpler times to create a timer',
|
||||
timeStart: 120000,
|
||||
timeEnd: 720000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8222',
|
||||
},
|
||||
{
|
||||
duration: 900000,
|
||||
type: 'delay',
|
||||
revision: 0,
|
||||
id: 'a386',
|
||||
},
|
||||
{
|
||||
title: 'Add delay blocks to affect all events',
|
||||
timeStart: 37320000,
|
||||
timeEnd: 38520000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '6dce',
|
||||
},
|
||||
{
|
||||
title: 'Add and remove events with [+] and [-]',
|
||||
timeStart: 38520000,
|
||||
timeEnd: 45120000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '2651',
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
id: 'e6a1',
|
||||
},
|
||||
{
|
||||
title: 'And control whether they are public',
|
||||
timeStart: 46800000,
|
||||
timeEnd: 57600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '1358',
|
||||
},
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
title: 'Welcome to Ontime',
|
||||
timeStart: 28800000,
|
||||
timeEnd: 30600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '5946',
|
||||
},
|
||||
{
|
||||
title: 'Unless recalled by the OSC address',
|
||||
timeStart: 34920000 + 60000,
|
||||
timeEnd: 35520000 + 60000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8ee5',
|
||||
},
|
||||
{
|
||||
title: 'Use simpler times to create a timer',
|
||||
timeStart: 120000 + 60000,
|
||||
timeEnd: 720000 + 60000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8222',
|
||||
},
|
||||
{
|
||||
title: 'Add delay blocks to affect all events',
|
||||
timeStart: 37320000 + 60000 + 900000,
|
||||
timeEnd: 38520000 + 60000 + 900000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '6dce',
|
||||
},
|
||||
{
|
||||
title: 'Add and remove events with [+] and [-]',
|
||||
timeStart: 38520000 + 60000 + 900000,
|
||||
timeEnd: 45120000 + 60000 + 900000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '2651',
|
||||
},
|
||||
{
|
||||
title: 'And control whether they are public',
|
||||
timeStart: 46800000,
|
||||
timeEnd: 57600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '1358',
|
||||
},
|
||||
];
|
||||
|
||||
expect(getEventsWithDelay(testData)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
describe('getEventsWithDelay edge cases', () => {
|
||||
it('given an empty array', () => {
|
||||
const emptyArray = {
|
||||
test: [],
|
||||
expect: [],
|
||||
};
|
||||
|
||||
expect(getEventsWithDelay(emptyArray.test)).toStrictEqual(emptyArray.expect);
|
||||
});
|
||||
|
||||
it('given an undefined object', () => {
|
||||
const withUndefined = {
|
||||
test: undefined,
|
||||
expect: [],
|
||||
};
|
||||
|
||||
expect(getEventsWithDelay(withUndefined.test)).toStrictEqual(withUndefined.expect);
|
||||
});
|
||||
|
||||
it('given a corrupted event object', () => {
|
||||
const testData = [
|
||||
{
|
||||
title: 'Welcome to Ontime',
|
||||
timeEnd: 30600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '5946',
|
||||
},
|
||||
{
|
||||
duration: 60000,
|
||||
type: 'delay',
|
||||
id: '24240',
|
||||
},
|
||||
{
|
||||
title: 'Unless recalled by the OSC address',
|
||||
timeStart: 34920000,
|
||||
timeEnd: 35520000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8ee5',
|
||||
},
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
title: 'Welcome to Ontime',
|
||||
timeEnd: 30600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '5946',
|
||||
},
|
||||
{
|
||||
title: 'Unless recalled by the OSC address',
|
||||
timeStart: 34920000 + 60000,
|
||||
timeEnd: 35520000 + 60000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8ee5',
|
||||
},
|
||||
];
|
||||
|
||||
expect(getEventsWithDelay(testData)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('given a corrupted delay object', () => {
|
||||
const testData = [
|
||||
{
|
||||
title: 'Welcome to Ontime',
|
||||
timeStart: 28800000,
|
||||
timeEnd: 30600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '5946',
|
||||
},
|
||||
{
|
||||
type: 'delay',
|
||||
id: '24240',
|
||||
},
|
||||
{
|
||||
title: 'Unless recalled by the OSC address',
|
||||
timeStart: 34920000,
|
||||
timeEnd: 35520000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '8ee5',
|
||||
},
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
title: 'Welcome to Ontime',
|
||||
timeStart: 28800000,
|
||||
timeEnd: 30600000,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
id: '5946',
|
||||
},
|
||||
{
|
||||
title: 'Unless recalled by the OSC address',
|
||||
timeStart: 34920000,
|
||||
timeEnd: 35520000,
|
||||
colour: '',
|
||||
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' },
|
||||
];
|
||||
|
||||
it('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);
|
||||
});
|
||||
|
||||
it('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);
|
||||
});
|
||||
|
||||
it('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);
|
||||
});
|
||||
|
||||
it('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,
|
||||
colour: '',
|
||||
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,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
revision: 0,
|
||||
id: '8ee5',
|
||||
},
|
||||
];
|
||||
|
||||
it('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,
|
||||
colour: '',
|
||||
},
|
||||
{
|
||||
id: '8ee5',
|
||||
time: '09:40 - 09:50',
|
||||
title: 'Unless recalled by the OSC address',
|
||||
isNow: false,
|
||||
isNext: false,
|
||||
colour: '',
|
||||
},
|
||||
];
|
||||
|
||||
const parsed = formatEventList(testEvent, selectedId, nextId, { showEnd: true });
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('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,
|
||||
colour: '',
|
||||
},
|
||||
{
|
||||
id: '8ee5',
|
||||
time: '09:40 - 09:50',
|
||||
title: 'Unless recalled by the OSC address',
|
||||
isNow: false,
|
||||
isNext: true,
|
||||
colour: '',
|
||||
},
|
||||
];
|
||||
|
||||
const parsed = formatEventList(testEvent, selectedId, nextId, { showEnd: true });
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('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,
|
||||
colour: '',
|
||||
},
|
||||
{
|
||||
id: '8ee5',
|
||||
time: '09:40 - 09:50',
|
||||
title: 'Unless recalled by the OSC address',
|
||||
isNow: true,
|
||||
isNext: false,
|
||||
colour: '',
|
||||
},
|
||||
];
|
||||
|
||||
const parsed = formatEventList(testEvent, selectedId, nextId, { showEnd: true });
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import getDelayTo from '../getDelayTo';
|
||||
|
||||
describe('getDelayTo function', () => {
|
||||
it('handles list with delays', () => {
|
||||
const delayDuration = 100;
|
||||
const events = [
|
||||
{ type: 'event' },
|
||||
{ type: 'delay', duration: delayDuration },
|
||||
{ type: 'event' },
|
||||
];
|
||||
|
||||
const notDelayed = getDelayTo(events, 0);
|
||||
expect(notDelayed).toBe(0);
|
||||
const delayedEvent = getDelayTo(events, 2);
|
||||
expect(delayedEvent).toBe(delayDuration);
|
||||
});
|
||||
it('handles list without delays', () => {
|
||||
const events = [{ type: 'event' }, { type: 'event' }];
|
||||
const notDelayed = getDelayTo(events, 1);
|
||||
expect(notDelayed).toBe(0);
|
||||
});
|
||||
|
||||
it('handles list with multiple delays', () => {
|
||||
const delayDuration = 100;
|
||||
const events = [
|
||||
{ type: 'event' },
|
||||
{ type: 'delay', duration: delayDuration },
|
||||
{ type: 'event' },
|
||||
{ type: 'delay', duration: delayDuration },
|
||||
{ type: 'event' },
|
||||
];
|
||||
const doubleDelay = getDelayTo(events, 4);
|
||||
expect(doubleDelay).toBe(delayDuration * 2);
|
||||
});
|
||||
it('handles list with blocks', () => {
|
||||
const events = [
|
||||
{ type: 'event' },
|
||||
{ type: 'delay', duration: 100 },
|
||||
{ type: 'event' },
|
||||
{ type: 'block' },
|
||||
{ type: 'event' },
|
||||
];
|
||||
const notDelayed = getDelayTo(events, 4);
|
||||
expect(notDelayed).toBe(0);
|
||||
});
|
||||
it('handles index greater than list', () => {
|
||||
const events = [{ type: 'event' }, { type: 'delay', duration: 100 }, { type: 'event' }];
|
||||
const notDelayed = getDelayTo(events, 3);
|
||||
expect(notDelayed).toBe(0);
|
||||
});
|
||||
it('handles negative index (not found)', () => {
|
||||
const events = [{ type: 'event' }, { type: 'delay', duration: 100 }, { type: 'event' }];
|
||||
const notDelayed = getDelayTo(events, -1);
|
||||
expect(notDelayed).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { clamp } from '../math';
|
||||
|
||||
test('Clamps a set of numbers correctly', () => {
|
||||
const testCases = [
|
||||
{ num: 10, min: 0, max: 20, result: 10 },
|
||||
{ num: 0, min: 0, max: 20, result: 0 },
|
||||
{ num: 20, min: 0, max: 20, result: 20 },
|
||||
{ num: 20, min: 0, max: 20, result: 20 },
|
||||
{ num: -20, min: 0, max: 20, result: 0 },
|
||||
{ num: -0, min: 0, max: 20, result: 0 },
|
||||
{ num: -50, min: -30, max: -20, result: -30 },
|
||||
{ num: -50, min: 0, max: 0, result: 0 },
|
||||
{ num: 50.5, min: 0, max: 100, result: 50.5 },
|
||||
{ num: 50, min: 0, max: 20.32, result: 20.32 },
|
||||
{ num: 10, min: 20.32, max: 40, result: 20.32 }
|
||||
];
|
||||
|
||||
testCases.forEach((t) => expect(clamp(t.num, t.min, t.max)).toBe(t.result));
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
.test {}
|
||||
.another {}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { cx } from '../styleUtils';
|
||||
|
||||
import style from './styleUtils.module.scss';
|
||||
|
||||
describe('cx()', () => {
|
||||
it('merges styles', () => {
|
||||
const merged = cx([style.test, style.another]);
|
||||
expect(merged).toMatchSnapshot();
|
||||
});
|
||||
it('ignores falsy values', () => {
|
||||
const falsyStuff = false;
|
||||
const merged = cx([undefined, false, 0, null, falsyStuff ? style.test : null]);
|
||||
expect(merged).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { formatTime } from '../time';
|
||||
|
||||
describe('formatTime()', () => {
|
||||
it('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');
|
||||
});
|
||||
|
||||
it('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');
|
||||
});
|
||||
|
||||
it('handles null times', () => {
|
||||
const ms = null;
|
||||
const time = formatTime(ms);
|
||||
expect(time).toStrictEqual('...');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { calculateDuration, DAY_TO_MS } from '../timesManager';
|
||||
|
||||
describe('calculateDuration()', () => {
|
||||
describe('Given start and end values', () => {
|
||||
it('calculates duration correctly', () => {
|
||||
const testStart = 1;
|
||||
const testEnd = 2;
|
||||
const val = calculateDuration(testStart, testEnd);
|
||||
expect(val).toBe(testEnd - testStart);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Handles edge cases', () => {
|
||||
it('when start is after end', () => {
|
||||
const testStart = 3;
|
||||
const testEnd = 2;
|
||||
const val = calculateDuration(testStart, testEnd);
|
||||
expect(val).toBe(testEnd + DAY_TO_MS - testStart);
|
||||
});
|
||||
it('when both are equal', () => {
|
||||
const testStart = 1;
|
||||
const testEnd = 1;
|
||||
const val = calculateDuration(testStart, testEnd);
|
||||
expect(val).toBe(testEnd - testStart);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Validates an alias against defined parameters
|
||||
* @param {string} alias
|
||||
* @returns {{message: string, status: boolean}}
|
||||
*/
|
||||
export const validateAlias = (alias: string) => {
|
||||
|
||||
const valid = { status: true, message: 'ok' };
|
||||
|
||||
if (alias === '' || alias == null) {
|
||||
// cannot be empty
|
||||
valid.status = false;
|
||||
valid.message = 'should not be empty';
|
||||
} else if (alias.includes('http') || alias.includes('https') || alias.includes('www')) {
|
||||
// cannot contain http, https or www
|
||||
valid.status = false;
|
||||
valid.message = 'should not include http, https, www';
|
||||
} else if (alias.includes('127.0.0.1') || alias.includes('localhost') || alias.includes('0.0.0.0')) {
|
||||
// aliases cannot contain hostname
|
||||
valid.status = false;
|
||||
valid.message = 'should not include hostname';
|
||||
} else if (alias.includes('editor')) {
|
||||
// no editor
|
||||
valid.status = false;
|
||||
valid.message = 'No aliases to editor page allowed';
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
import { mth, mtm, mts } from './timeConstants';
|
||||
|
||||
export const timeFormat = 'HH:mm';
|
||||
export const timeFormatSeconds = 'HH:mm:ss';
|
||||
|
||||
/**
|
||||
* another go at simpler string formatting (counters)
|
||||
* @description Converts seconds to string representing time
|
||||
* @param {number | null} seconds - time in seconds
|
||||
* @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 = false) {
|
||||
if (typeof seconds !== 'number') {
|
||||
return hideZero ? '00:00' : '00:00:00';
|
||||
}
|
||||
|
||||
// add an extra 0 if necessary
|
||||
const format = (val) => `0${Math.floor(val)}`.slice(-2);
|
||||
|
||||
const s = Math.abs(seconds);
|
||||
const hours = Math.floor((s / 3600) % 24);
|
||||
const minutes = Math.floor((s % 3600) / 60);
|
||||
|
||||
if (hideZero && hours < 1) return [minutes, s % 60].map(format).join(':');
|
||||
return [hours, minutes, s % 60].map(format).join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts milliseconds to seconds
|
||||
* @param {number | null} millis - time in seconds
|
||||
* @returns {number} Amount in seconds
|
||||
*/
|
||||
export const millisToSeconds = (millis) => {
|
||||
if (millis === null) {
|
||||
return 0;
|
||||
}
|
||||
return millis < 0 ? Math.ceil(millis / mts) : Math.floor(millis / mts);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Converts milliseconds to seconds
|
||||
* @param {number} millis - time in seconds
|
||||
* @returns {number} Amount in seconds
|
||||
*/
|
||||
export const millisToMinutes = (millis) => {
|
||||
return millis < 0 ? Math.ceil(millis / mtm) : Math.floor(millis / mtm);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Converts timestring to milliseconds
|
||||
* @param {string} string - time string "23:00:12"
|
||||
* @returns {number} Amount in milliseconds
|
||||
*/
|
||||
export const timeStringToMillis = (string) => {
|
||||
if (typeof string !== 'string') return 0;
|
||||
const time = string.split(':');
|
||||
if (time.length === 1) return Math.abs(time[0] * mts);
|
||||
if (time.length === 2) return Math.abs(time[0]) * mtm + time[1] * mts;
|
||||
if (time.length === 3) return Math.abs(time[0]) * mth + time[1] * mtm + time[2] * mts;
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Validates a time string
|
||||
* @param {string} string - time string "23:00:12"
|
||||
* @returns {boolean} string represents time
|
||||
*/
|
||||
export const isTimeString = (string) => {
|
||||
// ^ # Start of string
|
||||
// (?: # Try to match...
|
||||
// (?: # Try to match...
|
||||
// ([01]?\d|2[0-3]): # HH:
|
||||
// )? # (optionally).
|
||||
// ([0-5]?\d): # MM: (required)
|
||||
// )? # (entire group optional, so either HH:MM:, MM: or nothing)
|
||||
// ([0-5]?\d) # SS (required)
|
||||
// $ # End of string
|
||||
|
||||
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)$/;
|
||||
return regex.test(string);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description safe parse string to int
|
||||
* @param valueAsString
|
||||
* @return {number}
|
||||
*/
|
||||
const parse = (valueAsString) => {
|
||||
const parsed = parseInt(valueAsString, 10);
|
||||
if (isNaN(parsed)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.abs(parsed);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Parses a time string to millis
|
||||
* @param {string} value - time string
|
||||
* @param {boolean} fillLeft - autofill left = hours / right = seconds
|
||||
* @returns {number} - time string in millis
|
||||
*/
|
||||
export const forgivingStringToMillis = (value, fillLeft = true) => {
|
||||
let millis = 0;
|
||||
|
||||
// split string at known separators : , .
|
||||
const separatorRegex = /[\s,:.]+/;
|
||||
const [first, second, third] = value.split(separatorRegex);
|
||||
|
||||
if (first != null && second != null && third != null) {
|
||||
// if string has three sections, treat as [hours] [minutes] [seconds]
|
||||
millis = parse(first) * mth;
|
||||
millis += parse(second) * mtm;
|
||||
millis += parse(third) * mts;
|
||||
} else if (first != null && second == null && third == null) {
|
||||
// if string has one section,
|
||||
// could be a complete string like 121010 - 12:10:10
|
||||
if (first.length === 6) {
|
||||
const hours = first.substring(0, 2);
|
||||
const minutes = first.substring(2, 4);
|
||||
const seconds = first.substring(4);
|
||||
millis = parse(hours) * mth;
|
||||
millis += parse(minutes) * mtm;
|
||||
millis += parse(seconds) * mts;
|
||||
} else {
|
||||
// otherwise lets treat as [minutes]
|
||||
millis = parse(first) * mtm;
|
||||
}
|
||||
}
|
||||
if (first != null && second != null && third == null) {
|
||||
// if string has two sections
|
||||
if (fillLeft) {
|
||||
// treat as [hours] [minutes]
|
||||
millis = parse(first) * mth;
|
||||
millis += parse(second) * mtm;
|
||||
} else {
|
||||
// treat as [minutes] [seconds]
|
||||
millis = parse(first) * mtm;
|
||||
millis += parse(second) * mts;
|
||||
}
|
||||
}
|
||||
return millis;
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { OntimeEvent, OntimeRundownEntry, SupportedEvent } from '../models/EventTypes';
|
||||
|
||||
import { formatTime } from './time';
|
||||
|
||||
/**
|
||||
* @description From a list of events, returns only events of type event with calculated delays
|
||||
* @param {Object[]} events - given events
|
||||
* @returns {Object[]} Filtered events with calculated delays
|
||||
*/
|
||||
|
||||
export const getEventsWithDelay = (events: OntimeRundownEntry[]): OntimeEvent[] => {
|
||||
if (events == null) return [];
|
||||
|
||||
const unfilteredEvents = [...events];
|
||||
|
||||
// Add running delay
|
||||
let delay = 0;
|
||||
for (const event of unfilteredEvents) {
|
||||
if (event.type === SupportedEvent.Block) delay = 0;
|
||||
else if (event.type === SupportedEvent.Delay) delay = delay + event.duration;
|
||||
else if (event.type === SupportedEvent.Event && delay > 0) {
|
||||
event.timeStart += delay;
|
||||
event.timeEnd += delay;
|
||||
}
|
||||
}
|
||||
|
||||
// filter just events
|
||||
return unfilteredEvents.filter((event) => event.type === SupportedEvent.Event) as OntimeEvent[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @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: OntimeRundownEntry[], selectedId: string, limit: number) => {
|
||||
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;
|
||||
};
|
||||
|
||||
type FormatEventListOptionsProp = {
|
||||
showEnd?: boolean;
|
||||
}
|
||||
/**
|
||||
* @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 {object} [options]
|
||||
* @param {boolean} [options.showEnd] - whether to show the end time
|
||||
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
|
||||
*/
|
||||
export const formatEventList = (events: OntimeEvent[], selectedId: string, nextId: string, options: FormatEventListOptionsProp) => {
|
||||
if (events == null) return [];
|
||||
const { showEnd = false } = options;
|
||||
|
||||
const givenEvents = [...events];
|
||||
|
||||
// format list
|
||||
const formattedEvents = [];
|
||||
for (const event of givenEvents) {
|
||||
const start = formatTime(event.timeStart);
|
||||
const end = formatTime(event.timeEnd);
|
||||
|
||||
formattedEvents.push({
|
||||
id: event.id,
|
||||
time: showEnd ? `${start} - ${end}` : start,
|
||||
title: event.title,
|
||||
isNow: event.id === selectedId,
|
||||
isNext: event.id === nextId,
|
||||
colour: event.colour,
|
||||
});
|
||||
}
|
||||
|
||||
return formattedEvents;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Creates a safe duplicate of an event
|
||||
* @param {object} event
|
||||
* @return {object} clean event
|
||||
*/
|
||||
type ClonedEvent = OntimeEvent | { after?: string };
|
||||
export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => {
|
||||
return {
|
||||
type: SupportedEvent.Event,
|
||||
title: event.title,
|
||||
subtitle: event.subtitle,
|
||||
presenter: event.presenter,
|
||||
note: event.note,
|
||||
timeStart: event.timeStart,
|
||||
timeEnd: event.timeEnd,
|
||||
isPublic: event.isPublic,
|
||||
skip: event.skip,
|
||||
colour: event.colour,
|
||||
after: after,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @description calculates delay to a given event
|
||||
* @param {array} events
|
||||
* @param {number} eventIndex
|
||||
* @return {number} - delay value of given event
|
||||
*/
|
||||
export default function getDelayTo(events, eventIndex) {
|
||||
let delay = 0;
|
||||
let index = 0;
|
||||
if (eventIndex >= 0) {
|
||||
for (const event of events) {
|
||||
if (eventIndex === index) {
|
||||
return delay;
|
||||
}
|
||||
|
||||
if (event.type === 'delay') {
|
||||
delay += event.duration;
|
||||
} else if (event.type === 'block') {
|
||||
delay = 0;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Returns hostname
|
||||
* @type {string}
|
||||
*/
|
||||
export const host = window?.location?.host;
|
||||
|
||||
/**
|
||||
* Open an external URLs: specifically for a electron / browser case
|
||||
* If electron: ask main process to call a new browser window
|
||||
* If browser: open in new tab
|
||||
* @param url
|
||||
*/
|
||||
export function openLink(url) {
|
||||
if (window.process?.type === 'renderer') {
|
||||
window.ipcRenderer.send('send-to-link', url);
|
||||
} else {
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles opening external links
|
||||
* @param event
|
||||
* @param location
|
||||
*/
|
||||
export function handleLinks(event, location) {
|
||||
// we handle the link manually
|
||||
event.preventDefault();
|
||||
openLink(`http://${host}/${location}`);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Clamps a value between a min and a max
|
||||
* @param {number} num - Value to clamp
|
||||
* @param {number} min - min value
|
||||
* @param {number} max - max value
|
||||
* @returns {number}
|
||||
*/
|
||||
export const clamp = (num: number, min: number, max: number) =>
|
||||
Math.max(Math.min(num, Math.max(min, max)), Math.min(min, max));
|
||||
@@ -0,0 +1,17 @@
|
||||
import { serverURL } from '../api/apiConstants';
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
const socket = io(serverURL, { transports: ['websocket'] });
|
||||
const subscriptions = new Set();
|
||||
|
||||
export function subscribeOnce<T>(key: string, callback: (data: T) => void, requestString?: string) {
|
||||
if (subscriptions.has(key)) {
|
||||
return;
|
||||
}
|
||||
subscriptions.add(key);
|
||||
|
||||
requestString ? socket.emit(requestString) : socket.emit(`get-${key}`);
|
||||
socket.on(key, callback);
|
||||
}
|
||||
|
||||
export default socket;
|
||||
@@ -0,0 +1,29 @@
|
||||
import Color from 'color';
|
||||
|
||||
type ColourCombination = {
|
||||
backgroundColor: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Selects text colour to maintain accessible contrast
|
||||
* @param bgColour
|
||||
* @return {{backgroundColor, color: string}}
|
||||
*/
|
||||
export const getAccessibleColour = (bgColour: string): ColourCombination => {
|
||||
if (bgColour) {
|
||||
try {
|
||||
const textColor = Color(bgColour).isLight() ? 'black' : '#fffffa';
|
||||
return { backgroundColor: bgColour, color: textColor };
|
||||
} catch (error) {
|
||||
console.log(`Unable to parse colour: ${bgColour}`);
|
||||
}
|
||||
}
|
||||
return { backgroundColor: '#000', color: "#fffffa" };
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Creates a list of classnames from array of css module conditions
|
||||
* @param classNames - css modules objects
|
||||
*/
|
||||
export const cx = (classNames: any[]) => classNames.filter(Boolean).join(" ");
|
||||
@@ -0,0 +1,86 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { APP_SETTINGS } from '../api/apiConstants';
|
||||
import { ontimeQueryClient } from '../queryClient';
|
||||
|
||||
import { mth, mtm, mts } from './timeConstants';
|
||||
|
||||
|
||||
/**
|
||||
* Returns current time in milliseconds
|
||||
* @returns {number}
|
||||
*/
|
||||
export const nowInMillis = () => {
|
||||
const now = new Date();
|
||||
|
||||
// extract milliseconds since midnight
|
||||
let elapsed = now.getHours() * 3600000;
|
||||
elapsed += now.getMinutes() * 60000;
|
||||
elapsed += now.getSeconds() * 1000;
|
||||
elapsed += now.getMilliseconds();
|
||||
|
||||
return elapsed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Converts milliseconds to string representing time
|
||||
* @param {number | null} ms - time in milliseconds
|
||||
* @param {boolean} showSeconds - weather to show the seconds
|
||||
* @param {string} delim - character between HH MM SS
|
||||
* @param {string} ifNull - what to return if value is null
|
||||
* @returns {string} String representing time 00:12:02
|
||||
*/
|
||||
export const stringFromMillis = (ms, showSeconds = true, delim = ':', ifNull = '...') => {
|
||||
if (ms == null || isNaN(ms)) return ifNull;
|
||||
const isNegative = ms < 0 ? '-' : '';
|
||||
const millis = Math.abs(ms);
|
||||
|
||||
/**
|
||||
* @description ensures value is double digit
|
||||
* @param value
|
||||
* @return {string|*}
|
||||
*/
|
||||
const showWith0 = (value) => (value < 10 ? `0${value}` : value);
|
||||
const hours = showWith0(Math.floor(((millis / mth) % 60) % 24));
|
||||
const minutes = showWith0(Math.floor((millis / mtm) % 60));
|
||||
const seconds = showWith0(Math.floor((millis / mts) % 60));
|
||||
|
||||
return showSeconds
|
||||
? `${isNegative}${
|
||||
parseInt(hours, 10) ? `${hours}${delim}` : `00${delim}`
|
||||
}${minutes}${delim}${seconds}`
|
||||
: `${isNegative}${parseInt(hours, 10) ? `${hours}` : '00'}${delim}${minutes}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Resolves format from url and store
|
||||
* @return {string|undefined}
|
||||
*/
|
||||
export const resolveTimeFormat = () => {
|
||||
const params = new URL(document.location).searchParams;
|
||||
const urlOptions = params.get('format');
|
||||
const settings = ontimeQueryClient.getQueryData(APP_SETTINGS);
|
||||
|
||||
return urlOptions || settings?.timeFormat;
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* @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);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* millis to seconds
|
||||
* @type {number}
|
||||
*/
|
||||
export const mts = 1000;
|
||||
|
||||
/**
|
||||
* millis to minutes
|
||||
* @type {number}
|
||||
*/
|
||||
export const mtm = 1000 * 60;
|
||||
|
||||
|
||||
/**
|
||||
* millis to hours
|
||||
* @type {number}
|
||||
*/
|
||||
export const mth = 1000 * 60 * 60;
|
||||
|
||||
/**
|
||||
* milliseconds in a day
|
||||
* @type {number}
|
||||
*/
|
||||
export const DAY_TO_MS = 86400000;
|
||||
@@ -0,0 +1,45 @@
|
||||
export type TimeEntryField = 'timeStart' |'timeEnd' | 'durationOverride';
|
||||
|
||||
/**
|
||||
* @description Milliseconds in a day
|
||||
*/
|
||||
export const DAY_TO_MS = 86400000;
|
||||
|
||||
/**
|
||||
* @description calculates duration from given values
|
||||
*/
|
||||
export const calculateDuration = (start: number, end: number): number =>
|
||||
start > end ? end + DAY_TO_MS - start : end - start;
|
||||
|
||||
/**
|
||||
* @description Checks which field the value relates to
|
||||
*/
|
||||
export const handleTimeEntry = (field: TimeEntryField, val: number, timeStart: number, timeEnd: number): {start: number, end: number, durationOverride: boolean} => {
|
||||
let start = timeStart;
|
||||
let end = timeEnd;
|
||||
let durationOverride = false;
|
||||
|
||||
if (field === 'timeStart') {
|
||||
start = val;
|
||||
} else if (field === 'timeEnd') {
|
||||
end = val;
|
||||
} else {
|
||||
durationOverride = field === 'durationOverride';
|
||||
}
|
||||
return { start, end, durationOverride };
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Validates time entry
|
||||
*/
|
||||
export const validateEntry = (field: TimeEntryField, value: number, timeStart: number, timeEnd: number): { value: boolean, catch: string } => {
|
||||
const validate = { value: true, catch: '' };
|
||||
|
||||
const { start, end } = handleTimeEntry(field, value, timeStart, timeEnd);
|
||||
|
||||
if (end < start) {
|
||||
validate.catch = 'Start time later than end time';
|
||||
}
|
||||
|
||||
return validate;
|
||||
};
|
||||
Reference in New Issue
Block a user