mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-26 09:29:10 +00:00
refactor: stabilise actionHandler (#683)
This commit is contained in:
@@ -13,6 +13,7 @@ export const RUNTIME = ['runtimeStore'];
|
|||||||
const location = window.location;
|
const location = window.location;
|
||||||
const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
export const isProduction = import.meta.env.MODE === 'production';
|
export const isProduction = import.meta.env.MODE === 'production';
|
||||||
|
export const isDev = !isProduction;
|
||||||
|
|
||||||
const STATIC_PORT = 4001;
|
const STATIC_PORT = 4001;
|
||||||
export const serverPort = isProduction ? location.port : STATIC_PORT;
|
export const serverPort = isProduction ? location.port : STATIC_PORT;
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Shamelessly from https://ahooks.js.org/hooks/use-memoized-fn/
|
||||||
|
* Interesting technique discussed by Dan Abramov
|
||||||
|
* https://overreacted.io/making-setinterval-declarative-with-react-hooks/
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import { isDev } from '../api/apiConstants';
|
||||||
|
|
||||||
|
type noop = (this: any, ...args: any[]) => any;
|
||||||
|
|
||||||
|
type PickFunction<T extends noop> = (this: ThisParameterType<T>, ...args: Parameters<T>) => ReturnType<T>;
|
||||||
|
|
||||||
|
export const isFunction = (value: unknown): value is (...args: any) => any => typeof value === 'function';
|
||||||
|
|
||||||
|
export default function useMemoisedFn<T extends noop>(fn: T) {
|
||||||
|
if (isDev) {
|
||||||
|
if (!isFunction(fn)) {
|
||||||
|
console.error(`useMemoisedFn expected function as parameter, got ${typeof fn}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fnRef = useRef<T>(fn);
|
||||||
|
|
||||||
|
// why not write `fnRef.current = fn`?
|
||||||
|
// https://github.com/alibaba/hooks/issues/728
|
||||||
|
fnRef.current = useMemo(() => fn, [fn]);
|
||||||
|
|
||||||
|
const memoizedFn = useRef<PickFunction<T>>();
|
||||||
|
if (!memoizedFn.current) {
|
||||||
|
memoizedFn.current = function (this, ...args) {
|
||||||
|
return fnRef.current.apply(this, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return memoizedFn.current as T;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { calculateDuration, getCueCandidate } from 'ontime-utils';
|
|||||||
|
|
||||||
import { RUNDOWN } from '../../common/api/apiConstants';
|
import { RUNDOWN } from '../../common/api/apiConstants';
|
||||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
|
import useMemoisedFn from '../../common/hooks/useMemoisedFn';
|
||||||
import { ontimeQueryClient } from '../../common/queryClient';
|
import { ontimeQueryClient } from '../../common/queryClient';
|
||||||
import { useAppMode } from '../../common/stores/appModeStore';
|
import { useAppMode } from '../../common/stores/appModeStore';
|
||||||
import { useEditorSettings } from '../../common/stores/editorSettings';
|
import { useEditorSettings } from '../../common/stores/editorSettings';
|
||||||
@@ -61,95 +62,78 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
value: unknown;
|
value: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
// we assume the data is not changing in the lifecycle of this component
|
const actionHandler = useMemoisedFn((action: EventItemActions, payload?: number | FieldValue) => {
|
||||||
// changes to the data would make rundown re-render, also re-rendering this component
|
switch (action) {
|
||||||
const actionHandler = useCallback(
|
case 'event': {
|
||||||
(action: EventItemActions, payload?: number | FieldValue) => {
|
const newEvent = { type: SupportedEvent.Event };
|
||||||
switch (action) {
|
const options = {
|
||||||
case 'event': {
|
startTimeIsLastEnd,
|
||||||
const newEvent = { type: SupportedEvent.Event };
|
defaultPublic,
|
||||||
const options = {
|
lastEventId: previousEventId,
|
||||||
startTimeIsLastEnd,
|
after: data.id,
|
||||||
defaultPublic,
|
};
|
||||||
lastEventId: previousEventId,
|
addEvent(newEvent, options);
|
||||||
after: data.id,
|
break;
|
||||||
};
|
|
||||||
addEvent(newEvent, options);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delay': {
|
|
||||||
addEvent({ type: SupportedEvent.Delay }, { after: data.id });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'block': {
|
|
||||||
addEvent({ type: SupportedEvent.Block }, { after: data.id });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'swap': {
|
|
||||||
const { value } = payload as FieldValue;
|
|
||||||
swapEvents({ from: value as string, to: data.id });
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete': {
|
|
||||||
if (openId === data.id) {
|
|
||||||
removeOpenEvent();
|
|
||||||
}
|
|
||||||
deleteEvent(data.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'clone': {
|
|
||||||
const newEvent = cloneEvent(data as OntimeEvent, data.id);
|
|
||||||
const rundown = ontimeQueryClient.getQueryData<GetRundownCached>(RUNDOWN)?.rundown ?? []
|
|
||||||
newEvent.cue = getCueCandidate(rundown, data.id);
|
|
||||||
addEvent(newEvent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'update': {
|
|
||||||
// Handles and filters update requests
|
|
||||||
const { field, value } = payload as FieldValue;
|
|
||||||
const newData: Partial<OntimeEvent> = { id: data.id };
|
|
||||||
|
|
||||||
if (field === 'durationOverride' && data.type === SupportedEvent.Event) {
|
|
||||||
// duration defines timeEnd
|
|
||||||
newData.duration = value as number;
|
|
||||||
newData.timeEnd = data.timeStart + (value as number);
|
|
||||||
updateEvent(newData);
|
|
||||||
} else if (field === 'timeStart' && data.type === SupportedEvent.Event) {
|
|
||||||
newData.duration = calculateDuration(value as number, data.timeEnd);
|
|
||||||
newData.timeStart = value as number;
|
|
||||||
updateEvent(newData);
|
|
||||||
} else if (field === 'timeEnd' && data.type === SupportedEvent.Event) {
|
|
||||||
newData.duration = calculateDuration(data.timeStart, value as number);
|
|
||||||
newData.timeEnd = value as number;
|
|
||||||
updateEvent(newData);
|
|
||||||
} else if (field in data) {
|
|
||||||
// @ts-expect-error not sure how to type this
|
|
||||||
newData[field] = value;
|
|
||||||
updateEvent(newData);
|
|
||||||
} else {
|
|
||||||
emitError(`Unknown field: ${field}`);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(`Unhandled event ${action}`);
|
|
||||||
}
|
}
|
||||||
},
|
case 'delay': {
|
||||||
[
|
addEvent({ type: SupportedEvent.Delay }, { after: data.id });
|
||||||
addEvent,
|
break;
|
||||||
data,
|
}
|
||||||
defaultPublic,
|
case 'block': {
|
||||||
deleteEvent,
|
addEvent({ type: SupportedEvent.Block }, { after: data.id });
|
||||||
emitError,
|
break;
|
||||||
openId,
|
}
|
||||||
previousEventId,
|
case 'swap': {
|
||||||
removeOpenEvent,
|
const { value } = payload as FieldValue;
|
||||||
startTimeIsLastEnd,
|
swapEvents({ from: value as string, to: data.id });
|
||||||
updateEvent,
|
|
||||||
swapEvents,
|
break;
|
||||||
],
|
}
|
||||||
);
|
case 'delete': {
|
||||||
|
if (openId === data.id) {
|
||||||
|
removeOpenEvent();
|
||||||
|
}
|
||||||
|
deleteEvent(data.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'clone': {
|
||||||
|
const newEvent = cloneEvent(data as OntimeEvent, data.id);
|
||||||
|
const rundown = ontimeQueryClient.getQueryData<GetRundownCached>(RUNDOWN)?.rundown ?? [];
|
||||||
|
newEvent.cue = getCueCandidate(rundown, data.id);
|
||||||
|
addEvent(newEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'update': {
|
||||||
|
// Handles and filters update requests
|
||||||
|
const { field, value } = payload as FieldValue;
|
||||||
|
const newData: Partial<OntimeEvent> = { id: data.id };
|
||||||
|
|
||||||
|
if (field === 'durationOverride' && data.type === SupportedEvent.Event) {
|
||||||
|
// duration defines timeEnd
|
||||||
|
newData.duration = value as number;
|
||||||
|
newData.timeEnd = data.timeStart + (value as number);
|
||||||
|
updateEvent(newData);
|
||||||
|
} else if (field === 'timeStart' && data.type === SupportedEvent.Event) {
|
||||||
|
newData.duration = calculateDuration(value as number, data.timeEnd);
|
||||||
|
newData.timeStart = value as number;
|
||||||
|
updateEvent(newData);
|
||||||
|
} else if (field === 'timeEnd' && data.type === SupportedEvent.Event) {
|
||||||
|
newData.duration = calculateDuration(data.timeStart, value as number);
|
||||||
|
newData.timeEnd = value as number;
|
||||||
|
updateEvent(newData);
|
||||||
|
} else if (field in data) {
|
||||||
|
// @ts-expect-error not sure how to type this
|
||||||
|
newData[field] = value;
|
||||||
|
updateEvent(newData);
|
||||||
|
} else {
|
||||||
|
emitError(`Unknown field: ${field}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unhandled event ${action}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (data.type === SupportedEvent.Event) {
|
if (data.type === SupportedEvent.Event) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user