mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 14:14:17 +00:00
refactor: show countdown if less than 10 min from start
This commit is contained in:
committed by
Carlos Valente
parent
9f2db10548
commit
50599eccd2
@@ -1,9 +1,8 @@
|
|||||||
import { EntryId, MaybeNumber, OffsetMode, OntimeEntry, OntimeEvent, OntimeReport, Playback } from 'ontime-types';
|
import { EntryId, MaybeNumber, OffsetMode, OntimeEntry, OntimeEvent, OntimeReport, Playback } from 'ontime-types';
|
||||||
import { getExpectedStart, MILLIS_PER_MINUTE, removeSeconds } from 'ontime-utils';
|
import { getExpectedStart, MILLIS_PER_MINUTE, millisToString, removeLeadingZero } from 'ontime-utils';
|
||||||
|
|
||||||
import { useCountdownSocket } from '../../common/hooks/useSocket';
|
import { useCountdownSocket } from '../../common/hooks/useSocket';
|
||||||
import { ExtendedEntry } from '../../common/utils/rundownMetadata';
|
import { ExtendedEntry } from '../../common/utils/rundownMetadata';
|
||||||
import { timerPlaceholderMin } from '../../common/utils/styleUtils';
|
|
||||||
import { formatDuration, formatTime } from '../../common/utils/time';
|
import { formatDuration, formatTime } from '../../common/utils/time';
|
||||||
import { type TranslationKey, useTranslation } from '../../translation/TranslationProvider';
|
import { type TranslationKey, useTranslation } from '../../translation/TranslationProvider';
|
||||||
|
|
||||||
@@ -36,23 +35,6 @@ export const timerProgress: TimerMessage = {
|
|||||||
done: 'countdown.ended',
|
done: 'countdown.ended',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getFormattedTime(
|
|
||||||
value: MaybeNumber,
|
|
||||||
status: ProgressStatus,
|
|
||||||
minText: string,
|
|
||||||
secText: string,
|
|
||||||
dueText: string,
|
|
||||||
) {
|
|
||||||
if (value === null) return timerPlaceholderMin;
|
|
||||||
if (status === 'future' || status === 'live') {
|
|
||||||
if (value <= 0) return dueText.toUpperCase();
|
|
||||||
return formatDuration(value, value > MILLIS_PER_MINUTE * 2)
|
|
||||||
.replace('m', `${minText} `)
|
|
||||||
.replace('s', secText);
|
|
||||||
}
|
|
||||||
return removeSeconds(formatTime(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a parsed timer and relevant status message
|
* Returns a parsed timer and relevant status message
|
||||||
* Handles events in different days but disregards whether an event has actually played
|
* Handles events in different days but disregards whether an event has actually played
|
||||||
@@ -65,7 +47,11 @@ export function useSubscriptionDisplayData(
|
|||||||
|
|
||||||
const bigDuration = (value: number) => {
|
const bigDuration = (value: number) => {
|
||||||
if (value <= 0) return getLocalizedString('countdown.overtime').toUpperCase();
|
if (value <= 0) return getLocalizedString('countdown.overtime').toUpperCase();
|
||||||
return formatDuration(value, value > MILLIS_PER_MINUTE * 2)
|
if (value < MILLIS_PER_MINUTE * 10) {
|
||||||
|
return removeLeadingZero(millisToString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatDuration(value, value > MILLIS_PER_MINUTE * 10)
|
||||||
.replace('m', `${getLocalizedString('common.minutes')} `)
|
.replace('m', `${getLocalizedString('common.minutes')} `)
|
||||||
.replace('s', getLocalizedString('common.seconds'));
|
.replace('s', getLocalizedString('common.seconds'));
|
||||||
};
|
};
|
||||||
@@ -90,7 +76,7 @@ export function useSubscriptionDisplayData(
|
|||||||
return {
|
return {
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
statusDisplay: getLocalizedString(timerProgress['pending']),
|
statusDisplay: getLocalizedString(timerProgress['pending']),
|
||||||
timeDisplay: ' ',
|
timeDisplay: ' ',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function TimelineSections({ now, next, followedBy }: TimelineSect
|
|||||||
if (timeToStart <= 0) {
|
if (timeToStart <= 0) {
|
||||||
nextStatus = dueText;
|
nextStatus = dueText;
|
||||||
} else {
|
} else {
|
||||||
nextStatus = formatDuration(timeToStart, timeToStart > MILLIS_PER_MINUTE * 2);
|
nextStatus = formatDuration(timeToStart, timeToStart > MILLIS_PER_MINUTE * 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export default function TimelineSections({ now, next, followedBy }: TimelineSect
|
|||||||
if (timeToStart <= 0) {
|
if (timeToStart <= 0) {
|
||||||
followedByStatus = dueText;
|
followedByStatus = dueText;
|
||||||
} else {
|
} else {
|
||||||
followedByStatus = formatDuration(timeToStart, timeToStart > MILLIS_PER_MINUTE * 2);
|
followedByStatus = formatDuration(timeToStart, timeToStart > MILLIS_PER_MINUTE * 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function getStatusLabel(timeToStart: number, status: ProgressStatus): str
|
|||||||
return 'pending';
|
return 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatDuration(timeToStart, timeToStart > MILLIS_PER_MINUTE * 2);
|
return formatDuration(timeToStart, timeToStart > MILLIS_PER_MINUTE * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScopedRundownData {
|
interface ScopedRundownData {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { expect, Locator, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
test('time until absolute', async ({ context }) => {
|
test('time until absolute', async ({ context }) => {
|
||||||
const editor = await context.newPage();
|
const editor = await context.newPage();
|
||||||
@@ -27,45 +27,76 @@ test('time until absolute', async ({ context }) => {
|
|||||||
await editor.getByTestId('entry-4').getByTestId('rundown-event').click();
|
await editor.getByTestId('entry-4').getByTestId('rundown-event').click();
|
||||||
ids.push(await editor.getByTestId('editor-container').getByLabel('Event ID (read only)').inputValue());
|
ids.push(await editor.getByTestId('editor-container').getByLabel('Event ID (read only)').inputValue());
|
||||||
|
|
||||||
await countdown.goto('/countdown?' + ids.join('&sub='));
|
await countdown.goto(`/countdown?${ids.join('&sub=')}`);
|
||||||
|
|
||||||
const locatorElements2 = [
|
// Create reusable locator references for different elements
|
||||||
editor.getByTestId('entry-2').getByTestId('rundown-event'),
|
const entry2 = {
|
||||||
op.getByTestId('2').getByTestId('time-until'),
|
editorEvent: editor.getByTestId('entry-2').getByTestId('rundown-event'),
|
||||||
timeline.getByTestId('2'),
|
opTimeUntil: op.getByTestId('2').getByTestId('time-until'),
|
||||||
timeline.getByTestId('next'),
|
timelineItem: timeline.getByTestId('2'),
|
||||||
countdown.getByTestId('2'),
|
timelineNext: timeline.getByTestId('next'),
|
||||||
];
|
countdownItem: countdown.getByTestId('2'),
|
||||||
const locatorElements3 = [
|
};
|
||||||
editor.getByTestId('entry-3').getByTestId('rundown-event'),
|
|
||||||
op.getByTestId('3').getByTestId('time-until'),
|
const entry3 = {
|
||||||
timeline.getByTestId('3'),
|
editorEvent: editor.getByTestId('entry-3').getByTestId('rundown-event'),
|
||||||
timeline.getByTestId('followedBy'),
|
opTimeUntil: op.getByTestId('3').getByTestId('time-until'),
|
||||||
countdown.getByTestId('3'),
|
timelineItem: timeline.getByTestId('3'),
|
||||||
];
|
timelineFollowedBy: timeline.getByTestId('followedBy'),
|
||||||
const locatorElements4 = [
|
countdownItem: countdown.getByTestId('3'),
|
||||||
editor.getByTestId('entry-4').getByTestId('rundown-event'),
|
};
|
||||||
op.getByTestId('4').getByTestId('time-until'),
|
|
||||||
timeline.getByTestId('4'),
|
const entry4 = {
|
||||||
countdown.getByTestId('4'),
|
editorEvent: editor.getByTestId('entry-4').getByTestId('rundown-event'),
|
||||||
];
|
opTimeUntil: op.getByTestId('4').getByTestId('time-until'),
|
||||||
|
timelineItem: timeline.getByTestId('4'),
|
||||||
|
countdownItem: countdown.getByTestId('4'),
|
||||||
|
};
|
||||||
|
|
||||||
await editor.getByRole('button', { name: 'Absolute' }).click();
|
await editor.getByRole('button', { name: 'Absolute' }).click();
|
||||||
await editor.getByTestId('entry-1').getByLabel('Start event').click();
|
await editor.getByTestId('entry-1').getByLabel('Start event').click();
|
||||||
await expect(editor.getByTestId('offset')).not.toContainText('00:00:00'); // This might be a bad test requires that the test is not run at 0h
|
await expect(editor.getByTestId('offset')).not.toContainText('00:00:00'); // This might be a bad test requires that the test is not run at 0h
|
||||||
await editor.getByLabel('Pause event').click();
|
await editor.getByLabel('Pause event').click();
|
||||||
|
|
||||||
await testTimes(locatorElements2, '9m');
|
// 1. initial check
|
||||||
await testTimes(locatorElements3, '19m');
|
await expect(entry2.editorEvent).toContainText('9m');
|
||||||
await testTimes(locatorElements4, '29m');
|
await expect(entry2.opTimeUntil).toContainText('9m');
|
||||||
|
await expect(entry2.timelineItem).toContainText('9m59s');
|
||||||
|
await expect(entry2.timelineNext).toContainText('9m59s');
|
||||||
|
await expect(entry2.countdownItem).toContainText('10:00');
|
||||||
|
|
||||||
|
await expect(entry3.editorEvent).toContainText('19m');
|
||||||
|
await expect(entry3.opTimeUntil).toContainText('19m');
|
||||||
|
await expect(entry3.timelineItem).toContainText('19m');
|
||||||
|
await expect(entry3.timelineFollowedBy).toContainText('19m');
|
||||||
|
await expect(entry3.countdownItem).toContainText('19m');
|
||||||
|
|
||||||
|
await expect(entry4.editorEvent).toContainText('29m');
|
||||||
|
await expect(entry4.opTimeUntil).toContainText('29m');
|
||||||
|
await expect(entry4.timelineItem).toContainText('29m');
|
||||||
|
await expect(entry4.countdownItem).toContainText('29m');
|
||||||
|
|
||||||
await editor.getByTestId('entry-1').getByTestId('time-input-duration').click();
|
await editor.getByTestId('entry-1').getByTestId('time-input-duration').click();
|
||||||
await editor.getByTestId('entry-1').getByTestId('time-input-duration').fill('6h');
|
await editor.getByTestId('entry-1').getByTestId('time-input-duration').fill('6h');
|
||||||
await editor.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter');
|
await editor.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter');
|
||||||
|
|
||||||
await testTimes(locatorElements2, '5h59m');
|
// 2. check after duration change
|
||||||
await testTimes(locatorElements3, '6h9m');
|
await expect(entry2.editorEvent).toContainText('5h59m');
|
||||||
await testTimes(locatorElements4, '6h19m');
|
await expect(entry2.opTimeUntil).toContainText('5h59m');
|
||||||
|
await expect(entry2.timelineItem).toContainText('5h59m');
|
||||||
|
await expect(entry2.timelineNext).toContainText('5h59m');
|
||||||
|
await expect(entry2.countdownItem).toContainText('5h59m');
|
||||||
|
|
||||||
|
await expect(entry3.editorEvent).toContainText('6h9m');
|
||||||
|
await expect(entry3.opTimeUntil).toContainText('6h9m');
|
||||||
|
await expect(entry3.timelineItem).toContainText('6h9m');
|
||||||
|
await expect(entry3.timelineFollowedBy).toContainText('6h9m');
|
||||||
|
await expect(entry3.countdownItem).toContainText('6h9m');
|
||||||
|
|
||||||
|
await expect(entry4.editorEvent).toContainText('6h19m');
|
||||||
|
await expect(entry4.opTimeUntil).toContainText('6h19m');
|
||||||
|
await expect(entry4.timelineItem).toContainText('6h19m');
|
||||||
|
await expect(entry4.countdownItem).toContainText('6h19m');
|
||||||
|
|
||||||
await editor.getByTestId('entry-1').getByTestId('time-input-duration').click();
|
await editor.getByTestId('entry-1').getByTestId('time-input-duration').click();
|
||||||
await editor.getByTestId('entry-1').getByTestId('time-input-duration').fill('30s');
|
await editor.getByTestId('entry-1').getByTestId('time-input-duration').fill('30s');
|
||||||
@@ -75,14 +106,24 @@ test('time until absolute', async ({ context }) => {
|
|||||||
await expect(editor.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('10m');
|
await expect(editor.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('10m');
|
||||||
await expect(editor.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('20m');
|
await expect(editor.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('20m');
|
||||||
|
|
||||||
await testTimes(locatorElements2, '30s');
|
// 3. check after final duration change
|
||||||
await testTimes(locatorElements3, '10m');
|
await expect(entry2.editorEvent).toContainText('30s');
|
||||||
await testTimes(locatorElements4, '20m');
|
await expect(entry2.opTimeUntil).toContainText('30s');
|
||||||
});
|
await expect(entry2.timelineItem).toContainText('30s');
|
||||||
|
await expect(entry2.timelineNext).toContainText('30s');
|
||||||
|
await expect(entry2.countdownItem).toContainText('0:30');
|
||||||
|
|
||||||
async function testTimes(locators: Locator[], value: string) {
|
await expect(entry3.editorEvent).toContainText('10m');
|
||||||
await locators.map(async (locator) => await expect(locator).toContainText(value));
|
await expect(entry3.opTimeUntil).toContainText('10m');
|
||||||
}
|
await expect(entry3.timelineItem).toContainText('10m');
|
||||||
|
await expect(entry3.timelineFollowedBy).toContainText('10m');
|
||||||
|
await expect(entry3.countdownItem).toContainText('10m');
|
||||||
|
|
||||||
|
await expect(entry4.editorEvent).toContainText('20m');
|
||||||
|
await expect(entry4.opTimeUntil).toContainText('20m');
|
||||||
|
await expect(entry4.timelineItem).toContainText('20m');
|
||||||
|
await expect(entry4.countdownItem).toContainText('20m');
|
||||||
|
});
|
||||||
|
|
||||||
test('time until relative', async ({ context }) => {
|
test('time until relative', async ({ context }) => {
|
||||||
const editor = await context.newPage();
|
const editor = await context.newPage();
|
||||||
|
|||||||
Reference in New Issue
Block a user