mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
add shortcut to jump between blocks (#1181)
* add shortcut * chore: rename getRelevantBlock to getPreviousBlock
This commit is contained in:
committed by
GitHub
parent
f708dc0bc4
commit
340f5478fb
@@ -2,8 +2,15 @@ import { Fragment, lazy, useCallback, useEffect, useRef, useState } from 'react'
|
|||||||
import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
import { useHotkeys } from '@mantine/hooks';
|
import { useHotkeys } from '@mantine/hooks';
|
||||||
import { isOntimeEvent, MaybeNumber, Playback, RundownCached, SupportedEvent } from 'ontime-types';
|
import { isOntimeBlock, isOntimeEvent, MaybeNumber, Playback, RundownCached, SupportedEvent } from 'ontime-types';
|
||||||
import { getFirstNormal, getLastNormal, getNextNormal, getPreviousNormal } from 'ontime-utils';
|
import {
|
||||||
|
getFirstNormal,
|
||||||
|
getLastNormal,
|
||||||
|
getNextBlockNormal,
|
||||||
|
getNextNormal,
|
||||||
|
getPreviousBlockNormal,
|
||||||
|
getPreviousNormal,
|
||||||
|
} from 'ontime-utils';
|
||||||
|
|
||||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
||||||
@@ -98,28 +105,61 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
[rundown, order, addEvent],
|
[rundown, order, addEvent],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectBlock = useCallback(
|
||||||
|
(cursor: string | null, direction: 'up' | 'down') => {
|
||||||
|
if (order.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newCursor = cursor;
|
||||||
|
if (cursor === null) {
|
||||||
|
// there is no cursor, we select the first or last depending on direction
|
||||||
|
const selected = direction === 'up' ? getLastNormal(rundown, order) : getFirstNormal(rundown, order);
|
||||||
|
|
||||||
|
if (isOntimeBlock(selected)) {
|
||||||
|
setSelectedEvents({ id: selected.id, selectMode: 'click', index: direction === 'up' ? order.length : 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newCursor = selected?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCursor === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise we select the next or previous
|
||||||
|
const selected =
|
||||||
|
direction === 'up'
|
||||||
|
? getPreviousBlockNormal(rundown, order, newCursor)
|
||||||
|
: getNextBlockNormal(rundown, order, newCursor);
|
||||||
|
|
||||||
|
if (selected.entry !== null && selected.index !== null) {
|
||||||
|
setSelectedEvents({ id: selected.entry.id, selectMode: 'click', index: selected.index });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[order, rundown, setSelectedEvents],
|
||||||
|
);
|
||||||
|
|
||||||
const selectEntry = useCallback(
|
const selectEntry = useCallback(
|
||||||
(cursor: string | null, direction: 'up' | 'down') => {
|
(cursor: string | null, direction: 'up' | 'down') => {
|
||||||
if (order.length < 1) {
|
if (order.length < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let newCursor: string | null;
|
|
||||||
let newIndex: number | null;
|
|
||||||
if (cursor === null) {
|
if (cursor === null) {
|
||||||
// there is no cursor, we select the first or last depending on direction if it exists
|
// there is no cursor, we select the first or last depending on direction if it exists
|
||||||
newCursor =
|
const selected = direction === 'up' ? getLastNormal(rundown, order) : getFirstNormal(rundown, order);
|
||||||
(direction === 'up' ? getLastNormal(rundown, order)?.id : getFirstNormal(rundown, order)?.id) ?? null;
|
if (selected !== null) {
|
||||||
newIndex = direction === 'up' ? order.length : 0;
|
setSelectedEvents({ id: selected.id, selectMode: 'click', index: direction === 'up' ? order.length : 0 });
|
||||||
} else {
|
}
|
||||||
// otherwise we select the next or previous
|
return;
|
||||||
const selected =
|
|
||||||
direction === 'up' ? getPreviousNormal(rundown, order, cursor) : getNextNormal(rundown, order, cursor);
|
|
||||||
newCursor = selected.entry?.id ?? null;
|
|
||||||
newIndex = selected.index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCursor && newIndex !== null) {
|
// otherwise we select the next or previous
|
||||||
setSelectedEvents({ id: newCursor, selectMode: 'click', index: newIndex });
|
const selected =
|
||||||
|
direction === 'up' ? getPreviousNormal(rundown, order, cursor) : getNextNormal(rundown, order, cursor);
|
||||||
|
|
||||||
|
if (selected.entry !== null && selected.index !== null) {
|
||||||
|
setSelectedEvents({ id: selected.entry.id, selectMode: 'click', index: selected.index });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[order, rundown, setSelectedEvents],
|
[order, rundown, setSelectedEvents],
|
||||||
@@ -145,6 +185,10 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
useHotkeys([
|
useHotkeys([
|
||||||
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { preventDefault: true }],
|
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { preventDefault: true }],
|
||||||
['alt + ArrowUp', () => selectEntry(cursor, 'up'), { preventDefault: true }],
|
['alt + ArrowUp', () => selectEntry(cursor, 'up'), { preventDefault: true }],
|
||||||
|
|
||||||
|
['alt + shift + ArrowDown', () => selectBlock(cursor, 'down'), { preventDefault: true }],
|
||||||
|
['alt + shift + ArrowUp', () => selectBlock(cursor, 'up'), { preventDefault: true }],
|
||||||
|
|
||||||
['alt + mod + ArrowDown', () => moveEntry(cursor, 'down'), { preventDefault: true }],
|
['alt + mod + ArrowDown', () => moveEntry(cursor, 'down'), { preventDefault: true }],
|
||||||
['alt + mod + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true }],
|
['alt + mod + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true }],
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
}
|
}
|
||||||
td:nth-child(even) {
|
td:nth-child(even) {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ function EventEditorEmpty() {
|
|||||||
<Kbd>↓</Kbd>
|
<Kbd>↓</Kbd>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Select block</td>
|
||||||
|
<td>
|
||||||
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>Shift</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>/</AuxKey>
|
||||||
|
<Kbd>↓</Kbd>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Deselect entry</td>
|
<td>Deselect entry</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
TimerPhase,
|
TimerPhase,
|
||||||
TimerState,
|
TimerState,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import { calculateDuration, checkIsNow, dayInMs, filterTimedEvents, getRelevantBlock } from 'ontime-utils';
|
import { calculateDuration, checkIsNow, dayInMs, filterTimedEvents, getPreviousBlock } from 'ontime-utils';
|
||||||
|
|
||||||
import { clock } from '../services/Clock.js';
|
import { clock } from '../services/Clock.js';
|
||||||
import { RestorePoint } from '../services/RestoreService.js';
|
import { RestorePoint } from '../services/RestoreService.js';
|
||||||
@@ -664,7 +664,7 @@ function loadBlock(rundown: OntimeRundown) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCurrentBlock = getRelevantBlock(rundown, runtimeState.eventNow.id);
|
const newCurrentBlock = getPreviousBlock(rundown, runtimeState.eventNow.id);
|
||||||
|
|
||||||
// update time only if the block has changed
|
// update time only if the block has changed
|
||||||
if (newCurrentBlock === null || newCurrentBlock.id !== runtimeState.currentBlock.block?.id) {
|
if (newCurrentBlock === null || newCurrentBlock.id !== runtimeState.currentBlock.block?.id) {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ export { sanitiseCue } from './src/cue-utils/cueUtils.js';
|
|||||||
export { getCueCandidate } from './src/cue-utils/cueUtils.js';
|
export { getCueCandidate } from './src/cue-utils/cueUtils.js';
|
||||||
export { generateId } from './src/generate-id/generateId.js';
|
export { generateId } from './src/generate-id/generateId.js';
|
||||||
export {
|
export {
|
||||||
getEventWithId,
|
|
||||||
filterPlayable,
|
filterPlayable,
|
||||||
filterTimedEvents,
|
filterTimedEvents,
|
||||||
|
getEventWithId,
|
||||||
getFirst,
|
getFirst,
|
||||||
getFirstEvent,
|
getFirstEvent,
|
||||||
getFirstEventNormal,
|
getFirstEventNormal,
|
||||||
@@ -19,6 +19,7 @@ export {
|
|||||||
getLastEventNormal,
|
getLastEventNormal,
|
||||||
getLastNormal,
|
getLastNormal,
|
||||||
getNext,
|
getNext,
|
||||||
|
getNextBlockNormal,
|
||||||
getNextEvent,
|
getNextEvent,
|
||||||
getNextEventNormal,
|
getNextEventNormal,
|
||||||
getNextNormal,
|
getNextNormal,
|
||||||
@@ -26,7 +27,8 @@ export {
|
|||||||
getPreviousEvent,
|
getPreviousEvent,
|
||||||
getPreviousEventNormal,
|
getPreviousEventNormal,
|
||||||
getPreviousNormal,
|
getPreviousNormal,
|
||||||
getRelevantBlock,
|
getPreviousBlock,
|
||||||
|
getPreviousBlockNormal,
|
||||||
swapEventData,
|
swapEventData,
|
||||||
} from './src/rundown-utils/rundownUtils.js';
|
} from './src/rundown-utils/rundownUtils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
getNext,
|
getNext,
|
||||||
getNextEvent,
|
getNextEvent,
|
||||||
getPrevious,
|
getPrevious,
|
||||||
|
getPreviousBlock,
|
||||||
getPreviousEvent,
|
getPreviousEvent,
|
||||||
getRelevantBlock,
|
|
||||||
swapEventData,
|
swapEventData,
|
||||||
} from './rundownUtils';
|
} from './rundownUtils';
|
||||||
|
|
||||||
@@ -278,23 +278,23 @@ describe('getLastEvent', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
it('returns the relevant block', () => {
|
it('returns the relevant block', () => {
|
||||||
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'h');
|
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'h');
|
||||||
|
|
||||||
expect(block?.id).toBe('g');
|
expect(block?.id).toBe('g');
|
||||||
});
|
});
|
||||||
it('returns the relevant block', () => {
|
it('returns the relevant block', () => {
|
||||||
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'f');
|
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'f');
|
||||||
|
|
||||||
expect(block?.id).toBe('e');
|
expect(block?.id).toBe('e');
|
||||||
});
|
});
|
||||||
it('returns the relevant block', () => {
|
it('returns the relevant block', () => {
|
||||||
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'a');
|
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'a');
|
||||||
|
|
||||||
expect(block).toBeNull();
|
expect(block).toBeNull();
|
||||||
});
|
});
|
||||||
it('also works on index 0', () => {
|
it('also works on index 0', () => {
|
||||||
testRundown.unshift({ id: '0', type: SupportedEvent.Block });
|
testRundown.unshift({ id: '0', type: SupportedEvent.Block });
|
||||||
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'a');
|
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'a');
|
||||||
expect(block?.id).toBe('0');
|
expect(block?.id).toBe('0');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -302,7 +302,53 @@ export function getEventWithId(rundown: OntimeRundown, id: string): OntimeRundow
|
|||||||
/**
|
/**
|
||||||
* Gets relevant block element for a given ID
|
* Gets relevant block element for a given ID
|
||||||
*/
|
*/
|
||||||
export function getRelevantBlock(rundown: OntimeRundown, currentId: string): OntimeBlock | null {
|
export function getPreviousBlockNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry {
|
||||||
|
let foundCurrentEvent = false;
|
||||||
|
// Iterate backwards through the rundown to find the current event
|
||||||
|
for (let index = order.length - 1; index >= 0; index--) {
|
||||||
|
const id = order[index];
|
||||||
|
if (!foundCurrentEvent && id === currentId) {
|
||||||
|
// set the flag when the current event is found
|
||||||
|
foundCurrentEvent = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// the first block before the current event is the relevant one
|
||||||
|
const entry = rundown[id];
|
||||||
|
if (foundCurrentEvent && isOntimeBlock(entry)) {
|
||||||
|
return { entry, index };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no blocks exist before current event
|
||||||
|
return { entry: null, index: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets next block element for a given ID
|
||||||
|
*/
|
||||||
|
export function getNextBlockNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry {
|
||||||
|
let foundCurrentEvent = false;
|
||||||
|
// Iterate backwards through the rundown to find the current event
|
||||||
|
for (let index = 0; index < order.length; index++) {
|
||||||
|
const id = order[index];
|
||||||
|
if (!foundCurrentEvent && id === currentId) {
|
||||||
|
// set the flag when the current event is found
|
||||||
|
foundCurrentEvent = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// the first block before the current event is the relevant one
|
||||||
|
const entry = rundown[id];
|
||||||
|
if (foundCurrentEvent && isOntimeBlock(entry)) {
|
||||||
|
return { entry, index };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no blocks exist before current event
|
||||||
|
return { entry: null, index: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets relevant block element for a given ID
|
||||||
|
*/
|
||||||
|
export function getPreviousBlock(rundown: OntimeRundown, currentId: string): OntimeBlock | null {
|
||||||
let foundCurrentEvent = false;
|
let foundCurrentEvent = false;
|
||||||
// Iterate backwards through the rundown to find the current event
|
// Iterate backwards through the rundown to find the current event
|
||||||
for (let i = rundown.length - 1; i >= 0; i--) {
|
for (let i = rundown.length - 1; i >= 0; i--) {
|
||||||
|
|||||||
Reference in New Issue
Block a user