add shortcut to jump between blocks (#1181)

* add shortcut

* chore: rename getRelevantBlock to getPreviousBlock
This commit is contained in:
Alex Christoffer Rasmussen
2024-08-19 17:20:58 +02:00
committed by GitHub
parent f708dc0bc4
commit 340f5478fb
7 changed files with 130 additions and 25 deletions
+59 -15
View File
@@ -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 { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useHotkeys } from '@mantine/hooks';
import { isOntimeEvent, MaybeNumber, Playback, RundownCached, SupportedEvent } from 'ontime-types';
import { getFirstNormal, getLastNormal, getNextNormal, getPreviousNormal } from 'ontime-utils';
import { isOntimeBlock, isOntimeEvent, MaybeNumber, Playback, RundownCached, SupportedEvent } from 'ontime-types';
import {
getFirstNormal,
getLastNormal,
getNextBlockNormal,
getNextNormal,
getPreviousBlockNormal,
getPreviousNormal,
} from 'ontime-utils';
import { useEventAction } from '../../common/hooks/useEventAction';
import useFollowComponent from '../../common/hooks/useFollowComponent';
@@ -98,28 +105,61 @@ export default function Rundown({ data }: RundownProps) {
[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(
(cursor: string | null, direction: 'up' | 'down') => {
if (order.length < 1) {
return;
}
let newCursor: string | null;
let newIndex: number | null;
if (cursor === null) {
// there is no cursor, we select the first or last depending on direction if it exists
newCursor =
(direction === 'up' ? getLastNormal(rundown, order)?.id : getFirstNormal(rundown, order)?.id) ?? null;
newIndex = direction === 'up' ? order.length : 0;
} else {
// otherwise we select the next or previous
const selected =
direction === 'up' ? getPreviousNormal(rundown, order, cursor) : getNextNormal(rundown, order, cursor);
newCursor = selected.entry?.id ?? null;
newIndex = selected.index;
const selected = direction === 'up' ? getLastNormal(rundown, order) : getFirstNormal(rundown, order);
if (selected !== null) {
setSelectedEvents({ id: selected.id, selectMode: 'click', index: direction === 'up' ? order.length : 0 });
}
return;
}
if (newCursor && newIndex !== null) {
setSelectedEvents({ id: newCursor, selectMode: 'click', index: newIndex });
// otherwise we select the next or previous
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],
@@ -145,6 +185,10 @@ export default function Rundown({ data }: RundownProps) {
useHotkeys([
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { 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 + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true }],
@@ -28,6 +28,7 @@
}
td:nth-child(even) {
text-align: right;
white-space: nowrap;
}
}
}
@@ -24,6 +24,18 @@ function EventEditorEmpty() {
<Kbd></Kbd>
</td>
</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>
<td>Deselect entry</td>
<td>
+2 -2
View File
@@ -11,7 +11,7 @@ import {
TimerPhase,
TimerState,
} 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 { RestorePoint } from '../services/RestoreService.js';
@@ -664,7 +664,7 @@ function loadBlock(rundown: OntimeRundown) {
return;
}
const newCurrentBlock = getRelevantBlock(rundown, runtimeState.eventNow.id);
const newCurrentBlock = getPreviousBlock(rundown, runtimeState.eventNow.id);
// update time only if the block has changed
if (newCurrentBlock === null || newCurrentBlock.id !== runtimeState.currentBlock.block?.id) {
+4 -2
View File
@@ -8,9 +8,9 @@ export { sanitiseCue } from './src/cue-utils/cueUtils.js';
export { getCueCandidate } from './src/cue-utils/cueUtils.js';
export { generateId } from './src/generate-id/generateId.js';
export {
getEventWithId,
filterPlayable,
filterTimedEvents,
getEventWithId,
getFirst,
getFirstEvent,
getFirstEventNormal,
@@ -19,6 +19,7 @@ export {
getLastEventNormal,
getLastNormal,
getNext,
getNextBlockNormal,
getNextEvent,
getNextEventNormal,
getNextNormal,
@@ -26,7 +27,8 @@ export {
getPreviousEvent,
getPreviousEventNormal,
getPreviousNormal,
getRelevantBlock,
getPreviousBlock,
getPreviousBlockNormal,
swapEventData,
} from './src/rundown-utils/rundownUtils.js';
@@ -8,8 +8,8 @@ import {
getNext,
getNextEvent,
getPrevious,
getPreviousBlock,
getPreviousEvent,
getRelevantBlock,
swapEventData,
} from './rundownUtils';
@@ -278,23 +278,23 @@ describe('getLastEvent', () => {
];
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');
});
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');
});
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();
});
it('also works on index 0', () => {
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');
});
});
@@ -302,7 +302,53 @@ export function getEventWithId(rundown: OntimeRundown, id: string): OntimeRundow
/**
* 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;
// Iterate backwards through the rundown to find the current event
for (let i = rundown.length - 1; i >= 0; i--) {