mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 14:14:17 +00:00
fix: keyboard shortcuts in rundown
This commit is contained in:
committed by
Carlos Valente
parent
ad2ef3b53f
commit
abe9012f20
@@ -89,25 +89,17 @@ export const useEntryActions = () => {
|
|||||||
|
|
||||||
// ************* CHECK OPTIONS specific to events
|
// ************* CHECK OPTIONS specific to events
|
||||||
if (isOntimeEvent(newEntry)) {
|
if (isOntimeEvent(newEntry)) {
|
||||||
// merge creation time options with event settings
|
if (options?.lastEventId) {
|
||||||
const applicationOptions = {
|
|
||||||
after: options?.after,
|
|
||||||
before: options?.before,
|
|
||||||
lastEventId: options?.lastEventId,
|
|
||||||
linkPrevious: options?.linkPrevious ?? linkPrevious,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (applicationOptions?.lastEventId) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know this is a value
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know this is a value
|
||||||
const rundownData = queryClient.getQueryData<Rundown>(RUNDOWN)!;
|
const rundownData = queryClient.getQueryData<Rundown>(RUNDOWN)!;
|
||||||
const previousEvent = rundownData.entries[applicationOptions.lastEventId];
|
const previousEvent = rundownData.entries[options?.lastEventId];
|
||||||
if (isOntimeEvent(previousEvent)) {
|
if (isOntimeEvent(previousEvent)) {
|
||||||
newEntry.timeStart = previousEvent.timeEnd;
|
newEntry.timeStart = previousEvent.timeEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override event with options from editor settings
|
// Override event with options from editor settings
|
||||||
newEntry.linkStart = applicationOptions.linkPrevious;
|
newEntry.linkStart = options?.linkPrevious ?? linkPrevious;
|
||||||
|
|
||||||
if (newEntry.duration === undefined && newEntry.timeEnd === undefined) {
|
if (newEntry.duration === undefined && newEntry.timeEnd === undefined) {
|
||||||
newEntry.duration = parseUserTime(defaultDuration);
|
newEntry.duration = parseUserTime(defaultDuration);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
reorderArray,
|
reorderArray,
|
||||||
} from 'ontime-utils';
|
} from 'ontime-utils';
|
||||||
|
|
||||||
import { type EventOptions, useEntryActions } from '../../common/hooks/useEntryAction';
|
import { useEntryActions } from '../../common/hooks/useEntryAction';
|
||||||
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
||||||
import { useRundownEditor } from '../../common/hooks/useSocket';
|
import { useRundownEditor } from '../../common/hooks/useSocket';
|
||||||
import { useEntryCopy } from '../../common/stores/entryCopyStore';
|
import { useEntryCopy } from '../../common/stores/entryCopyStore';
|
||||||
@@ -114,20 +114,16 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
[addEntry, order, entries],
|
[addEntry, order, entries],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new item referring to an existing one
|
||||||
|
*/
|
||||||
const insertAtId = useCallback(
|
const insertAtId = useCallback(
|
||||||
(patch: Partial<OntimeEntry> & { type: SupportedEntry }, id: MaybeString, above = false) => {
|
(patch: Partial<OntimeEntry> & { type: SupportedEntry }, id: MaybeString, above = false) => {
|
||||||
const options: EventOptions =
|
addEntry(patch, {
|
||||||
id === null
|
after: id && !above ? id : undefined,
|
||||||
? {}
|
before: id && above ? id : undefined,
|
||||||
: {
|
lastEventId: !above && id ? id : undefined,
|
||||||
after: above ? undefined : id,
|
});
|
||||||
before: above ? id : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!above && id) {
|
|
||||||
options.lastEventId = id;
|
|
||||||
}
|
|
||||||
addEntry(patch, options);
|
|
||||||
},
|
},
|
||||||
[addEntry],
|
[addEntry],
|
||||||
);
|
);
|
||||||
@@ -239,52 +235,72 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
|
|
||||||
// shortcuts
|
// shortcuts
|
||||||
useHotkeys([
|
useHotkeys([
|
||||||
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { preventDefault: true }],
|
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
['alt + ArrowUp', () => selectEntry(cursor, 'up'), { preventDefault: true }],
|
['alt + ArrowUp', () => selectEntry(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
['alt + shift + ArrowDown', () => selectBlock(cursor, 'down'), { preventDefault: true }],
|
['alt + shift + ArrowDown', () => selectBlock(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
['alt + shift + ArrowUp', () => selectBlock(cursor, 'up'), { preventDefault: true }],
|
['alt + shift + ArrowUp', () => selectBlock(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
['alt + mod + ArrowDown', () => moveEntry(cursor, 'down'), { preventDefault: true }],
|
['alt + mod + ArrowDown', () => moveEntry(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
['alt + mod + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true }],
|
['alt + mod + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
['Escape', () => clearSelectedEvents(), { preventDefault: true }],
|
['Escape', () => clearSelectedEvents(), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
['mod + Backspace', () => deleteAtCursor(cursor), { preventDefault: true }],
|
['mod + Backspace', () => deleteAtCursor(cursor), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
[
|
[
|
||||||
'alt + E',
|
'alt + E',
|
||||||
() => insertAtId({ type: SupportedEntry.Event }, cursor),
|
() => insertAtId({ type: SupportedEntry.Event }, cursor),
|
||||||
{ preventDefault: true, usePhysicalKeys: true },
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
],
|
],
|
||||||
['alt + shift + E', () => insertAtId({ type: SupportedEntry.Event }, cursor, true), { preventDefault: true }],
|
[
|
||||||
|
'alt + shift + E',
|
||||||
|
() => insertAtId({ type: SupportedEntry.Event }, cursor, true),
|
||||||
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'alt + G',
|
'alt + G',
|
||||||
() => insertAtId({ type: SupportedEntry.Block }, cursor),
|
() => insertAtId({ type: SupportedEntry.Block }, cursor),
|
||||||
{ preventDefault: true, usePhysicalKeys: true },
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
],
|
],
|
||||||
['alt + shift + G', () => insertAtId({ type: SupportedEntry.Block }, cursor, true), { preventDefault: true }],
|
[
|
||||||
|
'alt + shift + G',
|
||||||
|
() => insertAtId({ type: SupportedEntry.Block }, cursor, true),
|
||||||
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'alt + D',
|
'alt + D',
|
||||||
() => insertAtId({ type: SupportedEntry.Delay }, cursor),
|
() => insertAtId({ type: SupportedEntry.Delay }, cursor),
|
||||||
{ preventDefault: true, usePhysicalKeys: true },
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
],
|
],
|
||||||
['alt + shift + D', () => insertAtId({ type: SupportedEntry.Delay }, cursor, true), { preventDefault: true }],
|
[
|
||||||
|
'alt + shift + D',
|
||||||
|
() => insertAtId({ type: SupportedEntry.Delay }, cursor, true),
|
||||||
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'alt + M',
|
'alt + M',
|
||||||
() => insertAtId({ type: SupportedEntry.Milestone }, cursor),
|
() => insertAtId({ type: SupportedEntry.Milestone }, cursor),
|
||||||
{ preventDefault: true, usePhysicalKeys: true },
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
],
|
],
|
||||||
['alt + shift + M', () => insertAtId({ type: SupportedEntry.Milestone }, cursor, true), { preventDefault: true }],
|
[
|
||||||
|
'alt + shift + M',
|
||||||
|
() => insertAtId({ type: SupportedEntry.Milestone }, cursor, true),
|
||||||
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
|
],
|
||||||
|
|
||||||
['mod + C', () => setEntryCopyId(cursor)],
|
['mod + C', () => setEntryCopyId(cursor)],
|
||||||
['mod + V', () => insertCopyAtId(cursor, entryCopyId)],
|
['mod + V', () => insertCopyAtId(cursor, entryCopyId)],
|
||||||
['mod + shift + V', () => insertCopyAtId(cursor, entryCopyId, true), { preventDefault: true }],
|
[
|
||||||
|
'mod + shift + V',
|
||||||
|
() => insertCopyAtId(cursor, entryCopyId, true),
|
||||||
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
|
],
|
||||||
|
|
||||||
['alt + backspace', () => deleteAtCursor(cursor), { preventDefault: true }],
|
['alt + backspace', () => deleteAtCursor(cursor), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// we copy the state from the store here
|
// we copy the state from the store here
|
||||||
|
|||||||
@@ -35,14 +35,30 @@ export async function addEntry(eventData: EventPostPayload): Promise<OntimeEntry
|
|||||||
throw new Error(`Event with ID ${eventData.id} already exists`);
|
throw new Error(`Event with ID ${eventData.id} already exists`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the user provides a parent (inside a group), we make sure it exists and it is a group
|
// the parent can be provided or inferred from position
|
||||||
let parent: OntimeBlock | null = null;
|
let parent: OntimeBlock | null = null;
|
||||||
|
|
||||||
|
console.log('Adding entry with data:', eventData);
|
||||||
|
|
||||||
if ('parent' in eventData && eventData.parent != null) {
|
if ('parent' in eventData && eventData.parent != null) {
|
||||||
|
// if the user provides a parent (inside a group), we make sure it exists and it is a group
|
||||||
const maybeParent = rundown.entries[eventData.parent];
|
const maybeParent = rundown.entries[eventData.parent];
|
||||||
if (!maybeParent || !isOntimeBlock(maybeParent)) {
|
if (!maybeParent || !isOntimeBlock(maybeParent)) {
|
||||||
throw new Error(`Invalid parent event with ID ${eventData.parent}`);
|
throw new Error(`Invalid parent event with ID ${eventData.parent}`);
|
||||||
}
|
}
|
||||||
parent = maybeParent;
|
parent = maybeParent;
|
||||||
|
} else {
|
||||||
|
// otherwise, we may infer the parent from relative positioning (after/before)
|
||||||
|
const referenceId = eventData?.after ?? eventData?.before;
|
||||||
|
if (referenceId) {
|
||||||
|
const maybeSibling = rundown.entries[referenceId];
|
||||||
|
if (maybeSibling && 'parent' in maybeSibling && maybeSibling.parent) {
|
||||||
|
const maybeParent = rundown.entries[maybeSibling.parent];
|
||||||
|
if (maybeParent && isOntimeBlock(maybeParent)) {
|
||||||
|
parent = maybeParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalise the position of the event in the rundown order
|
// normalise the position of the event in the rundown order
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { OntimeEntry } from '../../definitions/core/OntimeEntry.js';
|
import type { EntryId, OntimeEntry } from '../../definitions/core/OntimeEntry.js';
|
||||||
|
|
||||||
export type PatchWithId<T extends OntimeEntry = OntimeEntry> = Partial<T> & { id: string };
|
export type PatchWithId<T extends OntimeEntry = OntimeEntry> = Partial<T> & { id: EntryId };
|
||||||
|
|
||||||
export type EventPostPayload = Partial<OntimeEntry> & {
|
export type EventPostPayload = Partial<OntimeEntry> & {
|
||||||
after?: string;
|
after?: EntryId;
|
||||||
before?: string;
|
before?: EntryId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TransientEventPayload = Partial<OntimeEntry> & {
|
export type TransientEventPayload = Partial<OntimeEntry> & {
|
||||||
after?: string;
|
after?: EntryId;
|
||||||
before?: string;
|
before?: EntryId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProjectRundown = {
|
export type ProjectRundown = {
|
||||||
|
|||||||
Reference in New Issue
Block a user