diff --git a/apps/client/package.json b/apps/client/package.json index d38f63310..2ae7b96d0 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -13,7 +13,7 @@ "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@fontsource/open-sans": "^5.0.28", - "@mantine/hooks": "^7.17.2", + "@mantine/hooks": "^8.1.2", "@sentry/react": "^8.43.0", "@table-nav/react": "^0.0.7", "@tanstack/react-query": "^5.62.7", diff --git a/apps/client/src/features/rundown/Rundown.tsx b/apps/client/src/features/rundown/Rundown.tsx index bff28fcf8..5698a9b01 100644 --- a/apps/client/src/features/rundown/Rundown.tsx +++ b/apps/client/src/features/rundown/Rundown.tsx @@ -247,13 +247,25 @@ export default function Rundown({ data }: RundownProps) { ['mod + Backspace', () => deleteAtCursor(cursor), { preventDefault: true }], - ['alt + E', () => insertAtId({ type: SupportedEntry.Event }, cursor), { preventDefault: true }], + [ + 'alt + E', + () => insertAtId({ type: SupportedEntry.Event }, cursor), + { preventDefault: true, usePhysicalKeys: true }, + ], ['alt + shift + E', () => insertAtId({ type: SupportedEntry.Event }, cursor, true), { preventDefault: true }], - ['alt + B', () => insertAtId({ type: SupportedEntry.Block }, cursor), { preventDefault: true }], + [ + 'alt + B', + () => insertAtId({ type: SupportedEntry.Block }, cursor), + { preventDefault: true, usePhysicalKeys: true }, + ], ['alt + shift + B', () => insertAtId({ type: SupportedEntry.Block }, cursor, true), { preventDefault: true }], - ['alt + D', () => insertAtId({ type: SupportedEntry.Delay }, cursor), { preventDefault: true }], + [ + 'alt + D', + () => insertAtId({ type: SupportedEntry.Delay }, cursor), + { preventDefault: true, usePhysicalKeys: true }, + ], ['alt + shift + D', () => insertAtId({ type: SupportedEntry.Delay }, cursor, true), { preventDefault: true }], ['mod + C', () => setEntryCopyId(cursor)], diff --git a/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx b/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx index ccfdd0215..450a04965 100644 --- a/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx +++ b/apps/client/src/features/rundown/rundown-block/RundownBlock.tsx @@ -105,6 +105,7 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }: ...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}), ...dragStyle, }} + data-testid='rundown-block' >
+
diff --git a/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx b/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx index 8680406ed..a15fd8285 100644 --- a/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx +++ b/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx @@ -12,6 +12,7 @@ import { import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { EndAction, EntryId, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types'; +import { isPlaybackActive } from 'ontime-utils'; import { useContextMenu } from '../../../common/hooks/useContextMenu'; import { cx, getAccessibleColour } from '../../../common/utils/styleUtils'; @@ -248,6 +249,8 @@ export default function RundownEvent({ setSelectedEvents({ id: eventId, index, selectMode: editMode }); }; + const isPlaying = playback ? isPlaybackActive(playback) : false; + return (
diff --git a/e2e/tests/features/204-editor-crud.spec.ts b/e2e/tests/features/204-editor-crud.spec.ts index 6f0b14a6a..dadb17870 100644 --- a/e2e/tests/features/204-editor-crud.spec.ts +++ b/e2e/tests/features/204-editor-crud.spec.ts @@ -6,22 +6,37 @@ test('CRUD operations on the rundown', async ({ page }) => { await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Clear all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(0); + await expect(page.getByTestId('rundown-delay')).toHaveCount(0); + await expect(page.getByTestId('rundown-block')).toHaveCount(0); // create event from the rundown empty button await page.getByRole('button', { name: 'Create Event' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-delay')).toHaveCount(0); + await expect(page.getByTestId('rundown-block')).toHaveCount(0); // create blocks using the quick add buttons await page.getByRole('button', { name: 'Block' }).nth(1).click(); await page.getByRole('button', { name: 'Delay' }).nth(1).click(); await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(2); + await expect(page.getByTestId('rundown-delay')).toHaveCount(1); + await expect(page.getByTestId('rundown-block')).toHaveCount(1); // test quick add options - star2+5-t is last end await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('20m'); await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); expect(await page.getByTestId('entry-3').getByTestId('time-input-timeStart').inputValue()).toContain('00:30:00'); + await expect(page.getByTestId('rundown-event')).toHaveCount(3); + await expect(page.getByTestId('rundown-delay')).toHaveCount(1); + await expect(page.getByTestId('rundown-block')).toHaveCount(1); // test quick add options await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(4); + await expect(page.getByTestId('rundown-delay')).toHaveCount(1); + await expect(page.getByTestId('rundown-block')).toHaveCount(1); await expect(page.getByTestId('entry-4').locator('#block-status')).toHaveAttribute('data-timerType', 'count-down'); }); diff --git a/e2e/tests/features/209-rundown-shortcuts.spec.ts b/e2e/tests/features/209-rundown-shortcuts.spec.ts index ca94f8727..cc08e2b76 100644 --- a/e2e/tests/features/209-rundown-shortcuts.spec.ts +++ b/e2e/tests/features/209-rundown-shortcuts.spec.ts @@ -26,7 +26,7 @@ test('Copy-paste', async ({ page }) => { // assert await expect(page.getByTestId('entry-2')).toBeVisible(); await expect(page.getByTestId('entry-2').getByTestId('block__title')).toHaveValue('test'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('5'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5'); // copy paste above await page.locator('div').filter({ hasText: /^5$/ }).click(); @@ -36,7 +36,7 @@ test('Copy-paste', async ({ page }) => { // assert await expect(page.getByTestId('entry-2')).toBeVisible(); await expect(page.getByTestId('entry-2').getByTestId('block__title')).toHaveValue('test'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('4.1'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('4.1'); }); test('Move', async ({ page }) => { @@ -52,15 +52,19 @@ test('Move', async ({ page }) => { await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); // copy move down - await page.getByTestId('entry-1').locator('#event-block').getByText('1').click(); - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+Control+ArrowDown'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('1'); + await page.getByTestId('entry-1').getByTestId('rundown-event').getByText('1').click(); + await page + .getByTestId('entry-1') + .getByTestId('rundown-event') + .filter({ hasText: '1' }) + .press('Alt+Control+ArrowDown'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('1'); // copy move up - await page.getByTestId('entry-3').locator('#event-block').getByText('3').click(); - await page.getByTestId('entry-3').locator('#event-block div').filter({ hasText: '3' }).press('Alt+Control+ArrowUp'); + await page.getByTestId('entry-3').getByTestId('rundown-event').getByText('3').click(); + await page.getByTestId('entry-3').getByTestId('rundown-event').filter({ hasText: '3' }).press('Alt+Control+ArrowUp'); await page.getByTestId('entry-2').locator('div').filter({ hasText: /^3$/ }).press('Alt+Control+ArrowUp'); - await expect(page.getByTestId('entry-1').locator('#event-block')).toContainText('3'); + await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toContainText('3'); }); test('Add block', async ({ page }) => { @@ -69,22 +73,27 @@ test('Add block', async ({ page }) => { // clear rundown await page.getByRole('button', { name: 'Clear all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(0); + await expect(page.getByTestId('rundown-block')).toHaveCount(0); // create events await page.getByRole('button', { name: 'Create Event' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-block')).toHaveCount(0); await page.getByPlaceholder(/event title/i).fill('test'); await page.getByTestId('entry-1').click(); // add block below - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+B'); - await page.getByPlaceholder(/block title/i).fill('block below'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+B'); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-block')).toHaveCount(1); + await page.getByTestId('rundown-block').getByTestId('block__title').fill('block below'); // add block above - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+Shift+B'); - await page - .getByPlaceholder(/block title/i) - .first() - .fill('block above'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+Shift+B'); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-block')).toHaveCount(2); + await page.getByTestId('block__title').first().fill('block above'); await expect(page.getByTestId(/block__title/i).first()).toHaveValue('block above'); await expect(page.getByTestId(/block__title/i).nth(2)).toHaveValue('block below'); @@ -97,18 +106,26 @@ test('Add delay', async ({ page }) => { // clear rundown await page.getByRole('button', { name: 'Clear all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(0); + await expect(page.getByTestId('rundown-delay')).toHaveCount(0); //create events await page.getByRole('button', { name: 'Create Event' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-delay')).toHaveCount(0); await page.getByTestId('entry-1').click(); await page.getByTestId('block__title').press('Escape'); //add delay below - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+D'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+D'); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-delay')).toHaveCount(1); await expect(page.getByTestId('delay-input')).toBeVisible(); //add delay above - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+Shift+D'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+Shift+D'); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); + await expect(page.getByTestId('rundown-delay')).toHaveCount(2); await expect(page.getByTestId('entry-0').getByTestId('delay-input')).toBeVisible(); }); @@ -118,19 +135,23 @@ test('Add event', async ({ page }) => { // clear rundown await page.getByRole('button', { name: 'Clear all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(0); //create events await page.getByRole('button', { name: 'Create Event' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); await page.getByTestId('entry-1').click(); await page.getByTestId('block__title').press('Escape'); //add event below - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+E'); - await expect(page.getByTestId('entry-2').locator('#event-block').getByText('2')).toBeVisible(); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+E'); + await expect(page.getByTestId('rundown-event')).toHaveCount(2); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event').getByText('2')).toBeVisible(); //add event above - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+Shift+E'); - await expect(page.getByTestId('entry-1').locator('#event-block')).toContainText('0.1'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+Shift+E'); + await expect(page.getByTestId('rundown-event')).toHaveCount(3); + await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toContainText('0.1'); }); test('Delete event', async ({ page }) => { @@ -140,12 +161,16 @@ test('Delete event', async ({ page }) => { await page.goto('http://localhost:4001/rundown'); await page.getByRole('button', { name: 'Clear all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(0); //create event await page.getByRole('button', { name: 'Create Event' }).click(); + await expect(page.getByTestId('rundown-event')).toHaveCount(1); //delete event - await page.locator('#event-block div').filter({ hasText: '1' }).click(); - await page.getByTestId('entry-1').locator('#event-block div').filter({ hasText: '1' }).press('Alt+Backspace'); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).click(); + await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+Backspace'); + await expect(page.getByTestId('rundown-event')).toHaveCount(0); await expect(page.getByRole('button', { name: 'Create Event' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Create Block' })).toBeVisible(); }); diff --git a/e2e/tests/features/212-time-until.spec.ts b/e2e/tests/features/212-time-until.spec.ts index 1bd8c9065..a40aec894 100644 --- a/e2e/tests/features/212-time-until.spec.ts +++ b/e2e/tests/features/212-time-until.spec.ts @@ -16,25 +16,25 @@ test('time until absolute', async ({ page }) => { await expect(page.getByTestId('offset')).not.toContainText('00:00:00'); // This might be a bad test requires that the test is not run at 0h await page.getByLabel('Pause event').click(); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('9m'); - await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('19m'); - await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('29m'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('9m'); + await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('19m'); + await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('29m'); await page.getByTestId('entry-1').getByTestId('time-input-duration').click(); await page.getByTestId('entry-1').getByTestId('time-input-duration').fill('6h'); await page.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('5h59m'); - await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('6h9m'); - await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('6h19m'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5h59m'); + await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('6h9m'); + await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('6h19m'); await page.getByTestId('entry-1').getByTestId('time-input-duration').click(); await page.getByTestId('entry-1').getByTestId('time-input-duration').fill('30s'); await page.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('30s'); - await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('10m'); - await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('20m'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('30s'); + await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('10m'); + await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('20m'); }); test('time until relative', async ({ page }) => { @@ -53,23 +53,23 @@ test('time until relative', async ({ page }) => { await expect(page.getByTestId('offset')).toContainText('00:00:00'); // This might be a bad test as it ruires the evaluation to happen within 1s await page.getByLabel('Pause event').click(); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('9m'); - await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('19m'); - await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('29m'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('9m'); + await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('19m'); + await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('29m'); await page.getByTestId('entry-1').getByTestId('time-input-duration').click(); await page.getByTestId('entry-1').getByTestId('time-input-duration').fill('6h'); await page.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('5h59m'); - await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('6h9m'); - await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('6h19m'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5h59m'); + await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('6h9m'); + await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('6h19m'); await page.getByTestId('entry-1').getByTestId('time-input-duration').click(); await page.getByTestId('entry-1').getByTestId('time-input-duration').fill('30s'); await page.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter'); - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('30s'); - await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('10m'); - await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('20m'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('30s'); + await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('10m'); + await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('20m'); }); diff --git a/e2e/tests/features/213-arrange-while-playing.spec.ts b/e2e/tests/features/213-arrange-while-playing.spec.ts index 44b449a5b..ca21926f5 100644 --- a/e2e/tests/features/213-arrange-while-playing.spec.ts +++ b/e2e/tests/features/213-arrange-while-playing.spec.ts @@ -14,14 +14,19 @@ test('Rearrange while playing', async ({ page }) => { // start event 2 await page.getByTestId('entry-2').getByRole('button', { name: 'Start event' }).click(); - await expect(page.getByTestId('entry-2').locator('#event-block')).toHaveCSS('background-color', 'rgb(8, 122, 39)'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toHaveAttribute('data-running'); // move event 2 up - await page.getByTestId('entry-2').locator('#event-block').getByText('2').click(); - await page.getByTestId('entry-2').locator('#event-block div').filter({ hasText: '2' }).press('Alt+Control+ArrowUp'); + await page.getByTestId('entry-2').getByTestId('rundown-event').locator('div').filter({ hasText: '2' }).click(); + await page + .getByTestId('entry-2') + .getByTestId('rundown-event') + .locator('div') + .filter({ hasText: '2' }) + .press('Alt+Control+ArrowUp'); // event CUE1 should new be entry 2 - await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('1'); + await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('1'); // but entry 1 should be the one playing (it will be unlinked as it will be the first event) - await expect(page.getByTestId('entry-1').locator('#event-block')).toHaveCSS('background-color', 'rgb(8, 122, 39)'); + await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toHaveAttribute('data-running'); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64cac3caf..83115fd21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,8 +114,8 @@ importers: specifier: ^5.0.28 version: 5.2.6 '@mantine/hooks': - specifier: ^7.17.2 - version: 7.17.8(react@18.3.1) + specifier: ^8.1.2 + version: 8.1.2(react@18.3.1) '@sentry/react': specifier: ^8.43.0 version: 8.55.0(react@18.3.1) @@ -1315,8 +1315,8 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} - '@mantine/hooks@7.17.8': - resolution: {integrity: sha512-96qygbkTjRhdkzd5HDU8fMziemN/h758/EwrFu7TlWrEP10Vw076u+Ap/sG6OT4RGPZYYoHrTlT+mkCZblWHuw==} + '@mantine/hooks@8.1.2': + resolution: {integrity: sha512-BrriTsiazqZ2fLuL7UDasNTQJSaoJ7mN2qYVkdsiYI158lxJdUaFWHhd6BDyzK+W6thvBx2D+R/hh1rsWWefdQ==} peerDependencies: react: ^18.x || ^19.x @@ -6455,7 +6455,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@mantine/hooks@7.17.8(react@18.3.1)': + '@mantine/hooks@8.1.2(react@18.3.1)': dependencies: react: 18.3.1