diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx
index c5ac43ace..ab1c38592 100644
--- a/apps/client/src/features/operator/Operator.tsx
+++ b/apps/client/src/features/operator/Operator.tsx
@@ -136,8 +136,8 @@ export default function Operator() {
let isPast = Boolean(featureData.selectedEventId);
const hidePast = isStringBoolean(searchParams.get('hidepast'));
- const firstEvent = getFirstEvent(data);
- const lastEvent = getLastEvent(data);
+ const { firstEvent } = getFirstEvent(data);
+ const { lastEvent } = getLastEvent(data);
return (
diff --git a/apps/client/src/features/rundown/Rundown.tsx b/apps/client/src/features/rundown/Rundown.tsx
index e4601d981..25b4f5fce 100644
--- a/apps/client/src/features/rundown/Rundown.tsx
+++ b/apps/client/src/features/rundown/Rundown.tsx
@@ -88,14 +88,16 @@ export default function Rundown(props: RundownProps) {
(event: KeyboardEvent) => {
// handle held key
if (event.repeat) return;
- // Check if the alt key is pressed
- if (event.altKey && (!event.ctrlKey || !event.shiftKey)) {
+ // Check if the modifier combination
+ const modKeysAlt = event.altKey && !event.ctrlKey && !event.shiftKey;
+ const modKeysCtrlAlt = event.altKey && event.ctrlKey && !event.shiftKey;
+ if (modKeysAlt) {
switch (event.code) {
case 'ArrowDown': {
if (entries.length < 1) {
return;
}
- const nextEvent = cursor == null ? getFirst(entries) : getNext(entries, cursor);
+ const nextEvent = cursor == null ? getFirst(entries) : getNext(entries, cursor)?.nextEvent;
if (nextEvent) {
moveCursorTo(nextEvent.id, nextEvent.type === SupportedEvent.Event);
}
@@ -106,7 +108,7 @@ export default function Rundown(props: RundownProps) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we check for this before
- const previousEvent = cursor == null ? getFirst(entries) : getPrevious(entries, cursor);
+ const previousEvent = cursor == null ? getFirst(entries) : getPrevious(entries, cursor).previousEvent;
if (previousEvent) {
moveCursorTo(previousEvent.id, previousEvent.type === SupportedEvent.Event);
}
@@ -133,9 +135,24 @@ export default function Rundown(props: RundownProps) {
break;
}
}
+ } else if (modKeysCtrlAlt) {
+ if (entries.length < 2 || cursor == null) {
+ return;
+ }
+ if (event.code == 'ArrowDown') {
+ const { nextEvent, nextIndex } = getNext(entries, cursor);
+ if (nextEvent && nextIndex !== null) {
+ reorderEvent(cursor, nextIndex - 1, nextIndex);
+ }
+ } else if (event.code == 'ArrowUp') {
+ const { previousEvent, previousIndex } = getPrevious(entries, cursor);
+ if (previousEvent && previousIndex !== null) {
+ reorderEvent(cursor, previousIndex + 1, previousIndex);
+ }
+ }
}
},
- [cursor, entries, insertAtCursor, moveCursorTo],
+ [cursor, entries, insertAtCursor, moveCursorTo, reorderEvent],
);
// we copy the state from the store here
diff --git a/packages/utils/src/cue-utils/cueUtils.ts b/packages/utils/src/cue-utils/cueUtils.ts
index 0fa66409a..3f5c75a58 100644
--- a/packages/utils/src/cue-utils/cueUtils.ts
+++ b/packages/utils/src/cue-utils/cueUtils.ts
@@ -45,7 +45,7 @@ export function getIncrement(input: string): string {
*/
export function getCueCandidate(rundown: OntimeRundown, insertAfterId?: string): string {
function addAtTop() {
- const firstEventCue = getFirstEvent(rundown)?.cue;
+ const firstEventCue = getFirstEvent(rundown).firstEvent?.cue;
if (isNumeric(firstEventCue)) {
return (Number(firstEventCue) / 10).toString();
@@ -68,11 +68,11 @@ export function getCueCandidate(rundown: OntimeRundown, insertAfterId?: string):
// get elements around
let previousEvent: OntimeRundownEntry | undefined | null | OntimeEvent = rundown.at(afterIndex);
if (!isOntimeEvent(previousEvent)) {
- previousEvent = getPreviousEvent(rundown, insertAfterId) as null | OntimeEvent;
+ previousEvent = getPreviousEvent(rundown, insertAfterId).previousEvent as null | OntimeEvent;
}
let cue = '1';
- const nextEvent = getNextEvent(rundown, insertAfterId);
+ const { nextEvent } = getNextEvent(rundown, insertAfterId);
// try and increment the cue
if (isOntimeEvent(previousEvent)) {
diff --git a/packages/utils/src/rundown-utils/rundownUtils.test.ts b/packages/utils/src/rundown-utils/rundownUtils.test.ts
index 892732965..bb5cf0175 100644
--- a/packages/utils/src/rundown-utils/rundownUtils.test.ts
+++ b/packages/utils/src/rundown-utils/rundownUtils.test.ts
@@ -1,6 +1,43 @@
import { OntimeRundown, SupportedEvent } from 'ontime-types';
-import { getNextEvent, getPreviousEvent } from './rundownUtils';
+import { getNext, getNextEvent, getPrevious, getPreviousEvent } from './rundownUtils';
+
+describe('getNext()', () => {
+ it('returns the next event of type event', () => {
+ const testRundown = [
+ { id: '1', type: SupportedEvent.Event },
+ { id: '2', type: SupportedEvent.Event },
+ { id: '3', type: SupportedEvent.Event },
+ ];
+
+ const { nextEvent, nextIndex } = getNext(testRundown as OntimeRundown, '1');
+ expect(nextEvent?.id).toBe('2');
+ expect(nextIndex).toBe(1);
+ });
+ it('alows other event types', () => {
+ const testRundown = [
+ { id: '1', type: SupportedEvent.Event },
+ { id: '2', type: SupportedEvent.Delay },
+ { id: '3', type: SupportedEvent.Block },
+ { id: '4', type: SupportedEvent.Event },
+ ];
+
+ const { nextEvent, nextIndex } = getNext(testRundown as OntimeRundown, '1');
+ expect(nextEvent?.id).toBe('2');
+ expect(nextIndex).toBe(1);
+ });
+ it('returns null if none found', () => {
+ const testRundown = [
+ { id: '1', type: SupportedEvent.Event },
+ { id: '2', type: SupportedEvent.Delay },
+ { id: '3', type: SupportedEvent.Block },
+ ];
+
+ const { nextEvent, nextIndex } = getNext(testRundown as OntimeRundown, '3');
+ expect(nextEvent).toBe(null);
+ expect(nextIndex).toBe(null);
+ });
+});
describe('getNextEvent()', () => {
it('returns the next event of type event', () => {
@@ -10,8 +47,9 @@ describe('getNextEvent()', () => {
{ id: '3', type: SupportedEvent.Event },
];
- const next = getNextEvent(testRundown as OntimeRundown, '1');
- expect(next?.id).toBe('2');
+ const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
+ expect(nextEvent?.id).toBe('2');
+ expect(nextIndex).toBe(1);
});
it('ignores other event types', () => {
const testRundown = [
@@ -21,8 +59,9 @@ describe('getNextEvent()', () => {
{ id: '4', type: SupportedEvent.Event },
];
- const next = getNextEvent(testRundown as OntimeRundown, '1');
- expect(next?.id).toBe('4');
+ const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
+ expect(nextEvent?.id).toBe('4');
+ expect(nextIndex).toBe(3);
});
it('returns null if none found', () => {
const testRundown = [
@@ -31,8 +70,46 @@ describe('getNextEvent()', () => {
{ id: '3', type: SupportedEvent.Block },
];
- const next = getNextEvent(testRundown as OntimeRundown, '1');
- expect(next).toBe(null);
+ const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
+ expect(nextEvent).toBe(null);
+ expect(nextIndex).toBe(null);
+ });
+});
+
+describe('getPrevious()', () => {
+ it('returns the previous event of type event', () => {
+ const testRundown = [
+ { id: '1', type: SupportedEvent.Event },
+ { id: '2', type: SupportedEvent.Event },
+ { id: '3', type: SupportedEvent.Event },
+ ];
+
+ const { previousEvent, previousIndex } = getPrevious(testRundown as OntimeRundown, '3');
+ expect(previousEvent?.id).toBe('2');
+ expect(previousIndex).toBe(1);
+ });
+ it('allow other event types', () => {
+ const testRundown = [
+ { id: '1', type: SupportedEvent.Event },
+ { id: '2', type: SupportedEvent.Delay },
+ { id: '3', type: SupportedEvent.Block },
+ { id: '4', type: SupportedEvent.Event },
+ ];
+
+ const { previousEvent, previousIndex } = getPrevious(testRundown as OntimeRundown, '3');
+ expect(previousEvent?.id).toBe('2');
+ expect(previousIndex).toBe(1);
+ });
+ it('returns null if none found', () => {
+ const testRundown = [
+ { id: '2', type: SupportedEvent.Delay },
+ { id: '3', type: SupportedEvent.Block },
+ { id: '4', type: SupportedEvent.Event },
+ ];
+
+ const { previousEvent, previousIndex } = getPrevious(testRundown as OntimeRundown, '2');
+ expect(previousEvent).toBe(null);
+ expect(previousIndex).toBe(null);
});
});
@@ -44,8 +121,9 @@ describe('getPreviousEvent()', () => {
{ id: '3', type: SupportedEvent.Event },
];
- const previous = getPreviousEvent(testRundown as OntimeRundown, '3');
- expect(previous?.id).toBe('2');
+ const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '3');
+ expect(previousEvent?.id).toBe('2');
+ expect(previousIndex).toBe(1);
});
it('ignores other event types', () => {
const testRundown = [
@@ -55,8 +133,9 @@ describe('getPreviousEvent()', () => {
{ id: '4', type: SupportedEvent.Event },
];
- const previous = getPreviousEvent(testRundown as OntimeRundown, '4');
- expect(previous?.id).toBe('1');
+ const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '4');
+ expect(previousEvent?.id).toBe('1');
+ expect(previousIndex).toBe(0);
});
it('returns null if none found', () => {
const testRundown = [
@@ -65,7 +144,8 @@ describe('getPreviousEvent()', () => {
{ id: '4', type: SupportedEvent.Event },
];
- const previous = getNextEvent(testRundown as OntimeRundown, '1');
- expect(previous).toBe(null);
+ const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '2');
+ expect(previousEvent).toBe(null);
+ expect(previousIndex).toBe(null);
});
});
diff --git a/packages/utils/src/rundown-utils/rundownUtils.ts b/packages/utils/src/rundown-utils/rundownUtils.ts
index dbf75ad25..a887dd0b1 100644
--- a/packages/utils/src/rundown-utils/rundownUtils.ts
+++ b/packages/utils/src/rundown-utils/rundownUtils.ts
@@ -12,45 +12,55 @@ export function getFirst(rundown: OntimeRundownEntry[]) {
/**
* Gets first scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
- * @return {OntimeEvent | null}
+ * @return {{ firstEvent: OntimeEvent | null; firstIndex: number | null } }
*/
-export function getFirstEvent(rundown: OntimeRundownEntry[]) {
+export function getFirstEvent(rundown: OntimeRundownEntry[]): {
+ firstEvent: OntimeEvent | null;
+ firstIndex: number | null;
+} {
for (let i = 0; i < rundown.length; i++) {
- const event = rundown[i];
- if (isOntimeEvent(event)) {
- return event;
+ const firstEvent = rundown[i];
+ if (isOntimeEvent(firstEvent)) {
+ return { firstEvent, firstIndex: i };
}
}
- return null;
+ return { firstEvent: null, firstIndex: null };
}
-export function getLastEvent(rundown: OntimeRundown): OntimeEvent | null {
+export function getLastEvent(rundown: OntimeRundown): {
+ lastEvent: OntimeEvent | null;
+ lastIndex: number | null;
+} {
if (rundown.length < 1) {
- return null;
+ return { lastEvent: null, lastIndex: null };
}
for (let i = rundown.length - 1; i > 0; i--) {
- const event = rundown.at(i);
- if (isOntimeEvent(event)) {
- return event;
+ const lastEvent = rundown.at(i);
+ if (isOntimeEvent(lastEvent)) {
+ return { lastEvent, lastIndex: i };
}
}
-
- return null;
+ return { lastEvent: null, lastIndex: null };
}
/**
* Gets next event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
- * @return {OntimeRundownEntry | null}
+ * @return {{ nextEvent: OntimeRundownEntry | null; nextIndex: number | null } }
*/
-export function getNext(rundown: OntimeRundownEntry[], currentId: string): OntimeRundownEntry | null {
+export function getNext(
+ rundown: OntimeRundownEntry[],
+ currentId: string,
+): { nextEvent: OntimeRundownEntry | null; nextIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index !== -1 && index + 1 < rundown.length) {
- return rundown[index + 1];
+ const nextIndex = index + 1;
+ const nextEvent = rundown[nextIndex];
+ return { nextEvent, nextIndex };
} else {
- return null;
+ return { nextEvent: null, nextIndex: null };
}
}
@@ -58,35 +68,43 @@ export function getNext(rundown: OntimeRundownEntry[], currentId: string): Ontim
* Gets next scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
- * @return {OntimeEvent | null}
+ * @return {{ nextEvent: OntimeEvent | null; nextIndex: number | null } }
*/
-export function getNextEvent(rundown: OntimeRundownEntry[], currentId: string): OntimeEvent | null {
+export function getNextEvent(
+ rundown: OntimeRundownEntry[],
+ currentId: string,
+): { nextEvent: OntimeEvent | null; nextIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index < 0) {
- return null;
+ return { nextEvent: null, nextIndex: null };
}
for (let i = index + 1; i < rundown.length; i++) {
- const event = rundown[i];
- if (isOntimeEvent(event)) {
- return event;
+ const nextEvent = rundown[i];
+ if (isOntimeEvent(nextEvent)) {
+ return { nextEvent, nextIndex: i };
}
}
- return null;
+ return { nextEvent: null, nextIndex: null };
}
/**
* Gets previous event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
- * @return {OntimeRundownEntry | null}
+ * @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } }
*/
-export function getPrevious(rundown: OntimeRundownEntry[], currentId: string) {
+export function getPrevious(
+ rundown: OntimeRundownEntry[],
+ currentId: string,
+): { previousEvent: OntimeRundownEntry | null; previousIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index !== -1 && index - 1 >= 0) {
- return rundown[index - 1];
+ const previousIndex = index - 1;
+ const previousEvent = rundown[previousIndex];
+ return { previousEvent, previousIndex };
} else {
- return null;
+ return { previousEvent: null, previousIndex: null };
}
}
@@ -94,21 +112,23 @@ export function getPrevious(rundown: OntimeRundownEntry[], currentId: string) {
* Gets previous scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
- * @return {OntimeEvent | null}
+ * @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } }
*/
-export function getPreviousEvent(rundown: OntimeRundownEntry[], currentId: string): OntimeEvent | null {
+export function getPreviousEvent(
+ rundown: OntimeRundownEntry[],
+ currentId: string,
+): { previousEvent: OntimeEvent | null; previousIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index < 0) {
- return null;
+ return { previousEvent: null, previousIndex: null };
}
-
for (let i = index - 1; i >= 0; i--) {
- const event = rundown[i];
- if (isOntimeEvent(event)) {
- return event;
+ const previousEvent = rundown[i];
+ if (isOntimeEvent(previousEvent)) {
+ return { previousEvent, previousIndex: i };
}
}
- return null;
+ return { previousEvent: null, previousIndex: null };
}
/**