Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ export { isKnownTimerType, validateTimeStrategy } from './src/validate-events/va
export { calculateDuration, getLinkedTimes, validateTimes } from './src/validate-times/validateTimes.js';

// rundown utils
export { sanitiseCue } from './src/cue-utils/cueUtils.js';
export { getCueCandidate } from './src/cue-utils/cueUtils.js';
export { sanitiseCue, getCueCandidate } from './src/cue-utils/cueUtils.js';
export { generateId } from './src/generate-id/generateId.js';
export {
getEventWithId,
Expand Down
50 changes: 43 additions & 7 deletions packages/utils/src/cue-utils/cueUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,55 @@ import { SupportedEntry } from 'ontime-types';
import { getCueCandidate, getIncrement, sanitiseCue } from './cueUtils.js';

describe('getIncrement()', () => {
it('increments number', () => {
test('simple increment', () => {
expect(getIncrement('1')).toBe('2');
expect(getIncrement('10')).toBe('11');
expect(getIncrement('99')).toBe('100');
expect(getIncrement('101')).toBe('102');
});
it('increments decimal number', () => {

test('simple increment decimal', () => {
expect(getIncrement('1.1')).toBe('1.2');
expect(getIncrement('10.10')).toBe('10.11');
expect(getIncrement('99.99')).toBe('99.100');
expect(getIncrement('101.101')).toBe('101.102');
// NOTE: we know the below would fail, handling this amount of decimals is outside of scope
// expect(getIncrement('101.999')).toBe('101.1000');
expect(getIncrement('10.2')).toBe('10.3');
expect(getIncrement('99.3')).toBe('99.4');
expect(getIncrement('101.4')).toBe('101.5');
});

test('small increment decimal', () => {
expect(getIncrement('1.01')).toBe('1.02');
expect(getIncrement('1.001')).toBe('1.002');
expect(getIncrement('1.009')).toBe('1.010');
});

test('increment decimal to big number', () => {
expect(getIncrement('1.9')).toBe('2');
expect(getIncrement('5.999')).toBe('6');
expect(getIncrement('101.999')).toBe('102');
});

it('increments number a next cue', () => {
expect(getIncrement('1', '5')).toBe('2');
expect(getIncrement('10', '15')).toBe('11');
expect(getIncrement('99', '101')).toBe('100');
expect(getIncrement('101', '500')).toBe('102');
});

it('increments number a next cue', () => {
expect(getIncrement('1', '2')).toBe('1.1');
expect(getIncrement('10', '11')).toBe('10.1');
});

it('increments decimal number with a next cue', () => {
expect(getIncrement('1.1', '2')).toBe('1.2');
expect(getIncrement('10.10', '11')).toBe('10.11');
expect(getIncrement('99.09', '100')).toBe('99.10');
expect(getIncrement('99.09', '100')).toBe('99.10');
expect(getIncrement('99.99', '100')).toBe('99.991');
});

it('increments number a next cue has a decimal', () => {
expect(getIncrement('1', '2.1')).toBe('2');
expect(getIncrement('10.5', '11.5')).toBe('10.6');
});
// NOTE: we also know the following fails since we only handle one decimal
//it('handles multiple decimals', () => {
Expand Down
84 changes: 43 additions & 41 deletions packages/utils/src/cue-utils/cueUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,61 @@ import type { EntryId, OntimeEntry, RundownEntries } from 'ontime-types';
import { isOntimeEvent } from 'ontime-types';

import { getFirstEventNormal, getNextEventNormal, getPreviousEventNormal } from '../rundown-utils/rundownUtils.js';
import { isNumeric } from '../types/types.js';

/**
* Increments a decimal value or returns false if the result should be carried up
* @param decimal
* @returns
*/
function incrementDecimal(decimal: string): string | false {
if (!decimal) return false;
let actualPart = decimal;
while (actualPart.startsWith('0') && actualPart.length > 1) {
actualPart = actualPart.slice(1);
}
const incrementedString = (parseInt(actualPart) + 1).toString();
if (incrementedString.length > decimal.length) return false;
return incrementedString.padStart(decimal.length, '0');
}

// Zero or more non-digit characters at the beginning ((\D*)).
// One or more digits ((\d+)).
// Optionally, a decimal part starting with a dot ((\.\d+)?).
const regex = /^(\D*)(\d+)(\.\d+)?$/;
const regex = /^(\D*)(\d+)(\.)?(\d+)?$/;

/**
* Finds if last characters in input are a number and increments
*/
export function getIncrement(input: string): string {
export function getIncrement(input: string, next?: string): string {
// Check if the input string contains a number at the end
const match = regex.exec(input);
const matchNext = next ? regex.exec(next) : null;

if (match) {
// If a number is found, extract the non-numeric prefix, integer part, and decimal part
// eslint-disable-next-line prefer-const -- some items in the destructuring are modified
let [, prefix, integerPart, decimalPart] = match;
const [, prefix, integerPart, _dot, decimalPart] = match;

if (decimalPart) {
if (decimalPart === '.99') {
decimalPart = '.100';
} else {
const addDecimal = `${'0'.repeat(decimalPart.length - 2)}1`;
const incrementedDecimal = (Number(decimalPart) + Number(`0.${addDecimal}`)).toFixed(decimalPart.length - 1);
decimalPart = incrementedDecimal.toString().replace('0.', '.');
if (matchNext) {
const [, prefix_next, integerPart_next, _dot, decimalPart_next] = matchNext;
// // const floatPart_ext = parseFloat(integerPart_next + '.' + decimalPart_next);
if (prefix === prefix_next) {
const parsedIntPart = parseInt(integerPart);
const parsedNextIntPart = parseInt(integerPart_next);
if (parsedIntPart <= parsedNextIntPart - 2) return `${prefix}${parsedIntPart + 1}`;
if (parsedIntPart <= parsedNextIntPart) {
if (!decimalPart_next) {
const maybeDecimal = incrementDecimal(decimalPart);
if (maybeDecimal) return `${prefix}${integerPart}.${maybeDecimal}`;
return `${prefix}${integerPart}.${(decimalPart ?? '') + '1'}`; // no good way to solve this so add a 1
}
if (!decimalPart) return `${prefix}${parsedIntPart + 1}`;
}
}
return `${prefix}${integerPart}${decimalPart}`;
}
const incrementedInteger = Number(integerPart) + 1;
integerPart = incrementedInteger.toString();
return `${prefix}${integerPart}`;

const maybeDecimal = incrementDecimal(decimalPart);
if (maybeDecimal === false) return `${prefix}${parseInt(integerPart) + 1}`;
return `${prefix}${integerPart}.${maybeDecimal}`;
}
// If no number is found, append "2" to the string and return the updated string
return `${input}2`;
Expand All @@ -56,33 +80,11 @@ export function getCueCandidate(entries: RundownEntries, order: EntryId[], inser
return addAtTop();
}
}

// the cue is based on the previous event cue
const cue = getIncrement(previousEvent.cue);
const { nextEvent } = getNextEventNormal(entries, order, insertAfterId);

// if increment is clashing with next, we add a decimal instead
if (cue !== nextEvent?.cue) {
return cue;
}

// there is a clash, bt the cue is a pure number
if (isNumeric(cue)) {
return incrementDecimal(previousEvent.cue);
}

/**
* at this point, we know the cue is not numeric
* but the increment failed, so we have a numeric ending
* eg. Presenter 1 .... Presenter 2 -> Presenter1.1
* eg. Presenter 1.1 .... Presenter 1.2 -> Presenter1.1.1
*/
return `${previousEvent.cue}.1`;

function incrementDecimal(cue: string) {
const n = Number(cue);
return (n + 0.1).toString();
}
// the cue is based on the previous event cue
const cue = getIncrement(previousEvent.cue, nextEvent?.cue);
return cue;

function addAtTop() {
const firstEventCue = getFirstEventNormal(entries, order).firstEvent?.cue;
Expand Down
Loading