mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-05 15:33:59 +00:00
refactor: delete several events
This commit is contained in:
committed by
Carlos Valente
parent
16f31a07b7
commit
a09aa922bb
@@ -74,8 +74,8 @@ export async function requestApplyDelay(eventId: string): Promise<AxiosResponse<
|
||||
/**
|
||||
* HTTP request to delete given event
|
||||
*/
|
||||
export async function requestDelete(eventId: string): Promise<AxiosResponse<MessageResponse>> {
|
||||
return axios.delete(`${rundownPath}/${eventId}`);
|
||||
export async function requestDelete(eventIds: string[]): Promise<AxiosResponse<MessageResponse>> {
|
||||
return axios.delete(rundownPath, { data: { ids: eventIds } });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -337,7 +337,7 @@ export const useEventAction = () => {
|
||||
const _deleteEventMutation = useMutation({
|
||||
mutationFn: requestDelete,
|
||||
// we optimistically update here
|
||||
onMutate: async (eventId) => {
|
||||
onMutate: async (eventIds: string[]) => {
|
||||
// cancel ongoing queries
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
@@ -346,9 +346,11 @@ export const useEventAction = () => {
|
||||
|
||||
if (previousData) {
|
||||
// optimistically update object
|
||||
const newOrder = previousData.order.filter((id) => id !== eventId);
|
||||
const newOrder = previousData.order.filter((id) => !eventIds.includes(id));
|
||||
const newRundown = { ...previousData.rundown };
|
||||
delete newRundown[eventId];
|
||||
for (const eventId of eventIds) {
|
||||
delete newRundown[eventId];
|
||||
}
|
||||
|
||||
queryClient.setQueryData(RUNDOWN, {
|
||||
order: newOrder,
|
||||
@@ -377,9 +379,9 @@ export const useEventAction = () => {
|
||||
* Deletes an event form the list
|
||||
*/
|
||||
const deleteEvent = useCallback(
|
||||
async (eventId: string) => {
|
||||
async (eventIds: string[]) => {
|
||||
try {
|
||||
await _deleteEventMutation.mutateAsync(eventId);
|
||||
await _deleteEventMutation.mutateAsync(eventIds);
|
||||
} catch (error) {
|
||||
logAxiosError('Error deleting event', error);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function Rundown({ data }: RundownProps) {
|
||||
(cursor: string | null) => {
|
||||
if (!cursor) return;
|
||||
const previous = getPreviousNormal(rundown, order, cursor).entry?.id ?? null;
|
||||
deleteEvent(cursor);
|
||||
deleteEvent([cursor]);
|
||||
setCursor(previous);
|
||||
},
|
||||
[deleteEvent, order, rundown, setCursor],
|
||||
|
||||
@@ -58,18 +58,20 @@ export default function RundownEntry(props: RundownEntryProps) {
|
||||
const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction();
|
||||
const cursor = useAppMode((state) => state.cursor);
|
||||
const setCursor = useAppMode((state) => state.setCursor);
|
||||
const { selectedEvents, clearSelectedEvents } = useEventSelection();
|
||||
const { selectedEvents, unselect, clearSelectedEvents } = useEventSelection();
|
||||
|
||||
const removeOpenEvent = useCallback(() => {
|
||||
if (selectedEvents.has(data.id)) {
|
||||
clearSelectedEvents();
|
||||
}
|
||||
|
||||
unselect(data.id);
|
||||
// clear cursor if we are deleting the event that is currently selected
|
||||
if (cursor === data.id) {
|
||||
setCursor(null);
|
||||
}
|
||||
}, [selectedEvents, data.id, cursor, clearSelectedEvents, setCursor]);
|
||||
}, [unselect, data.id, cursor, setCursor]);
|
||||
|
||||
const clearMultiSelection = useCallback(() => {
|
||||
clearSelectedEvents();
|
||||
setCursor(null);
|
||||
}, [clearSelectedEvents, setCursor]);
|
||||
|
||||
// Create / delete new events
|
||||
type FieldValue = {
|
||||
@@ -111,10 +113,12 @@ export default function RundownEntry(props: RundownEntryProps) {
|
||||
return swapEvents({ from: value as string, to: data.id });
|
||||
}
|
||||
case 'delete': {
|
||||
if (selectedEvents.has(data.id)) {
|
||||
removeOpenEvent();
|
||||
if (selectedEvents.size > 1) {
|
||||
clearMultiSelection();
|
||||
return deleteEvent(Array.from(selectedEvents));
|
||||
}
|
||||
return deleteEvent(data.id);
|
||||
removeOpenEvent();
|
||||
return deleteEvent([data.id]);
|
||||
}
|
||||
case 'clone': {
|
||||
const newEvent = cloneEvent(data as OntimeEvent, data.id);
|
||||
@@ -134,7 +138,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
||||
if (selectedEvents.size > 1) {
|
||||
const changes: Partial<OntimeEvent> = { [field]: value };
|
||||
batchUpdateEvents(changes, Array.from(selectedEvents));
|
||||
return clearSelectedEvents();
|
||||
return;
|
||||
}
|
||||
if (field in data) {
|
||||
// @ts-expect-error -- not sure how to type this
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function DelayBlock(props: DelayBlockProps) {
|
||||
};
|
||||
|
||||
const cancelDelayHandler = () => {
|
||||
deleteEvent(data.id);
|
||||
deleteEvent([data.id]);
|
||||
};
|
||||
|
||||
const blockClasses = cx([style.delay, hasCursor ? style.hasCursor : null]);
|
||||
|
||||
@@ -113,6 +113,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
value: false,
|
||||
}),
|
||||
},
|
||||
{ withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
|
||||
]
|
||||
: [
|
||||
{
|
||||
|
||||
@@ -13,6 +13,8 @@ interface EventSelectionStore {
|
||||
anchoredIndex: number | null;
|
||||
setSelectedEvents: (selectionArgs: { id: string; index: number; selectMode: SelectionMode }) => void;
|
||||
clearSelectedEvents: () => void;
|
||||
clearMultiSelect: () => void;
|
||||
unselect: (id: string) => void;
|
||||
}
|
||||
|
||||
export const useEventSelection = create<EventSelectionStore>()((set, get) => ({
|
||||
@@ -81,7 +83,17 @@ export const useEventSelection = create<EventSelectionStore>()((set, get) => ({
|
||||
});
|
||||
}
|
||||
},
|
||||
clearSelectedEvents: () => set({ selectedEvents: new Set() }),
|
||||
clearSelectedEvents: () => set({ selectedEvents: new Set(), anchoredIndex: null }),
|
||||
clearMultiSelect: () => {
|
||||
const { selectedEvents } = get();
|
||||
const [firstSelected] = selectedEvents;
|
||||
set({ selectedEvents: new Set(firstSelected || undefined), anchoredIndex: null });
|
||||
},
|
||||
unselect: (id: string) => {
|
||||
const { selectedEvents } = get();
|
||||
selectedEvents.delete(id);
|
||||
set({ selectedEvents });
|
||||
},
|
||||
}));
|
||||
|
||||
export function getSelectionMode(event: MouseEvent): SelectionMode {
|
||||
|
||||
@@ -119,10 +119,10 @@ export async function rundownDelete(_req: Request, res: Response<MessageResponse
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteEventById(req: Request, res: Response<MessageResponse | ErrorResponse>) {
|
||||
export async function deletesEventById(req: Request, res: Response<MessageResponse | ErrorResponse>) {
|
||||
try {
|
||||
await deleteEvent(req.params.eventId);
|
||||
res.status(204).send({ message: 'Event deleted' });
|
||||
await deleteEvent(req.body.ids);
|
||||
res.status(204).send({ message: 'Events deleted' });
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).send({ message });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express from 'express';
|
||||
|
||||
import {
|
||||
deleteEventById,
|
||||
deletesEventById,
|
||||
rundownApplyDelay,
|
||||
rundownBatchPut,
|
||||
rundownDelete,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from './rundown.controller.js';
|
||||
import {
|
||||
paramsMustHaveEventId,
|
||||
rundownArrayOfIds,
|
||||
rundownBatchPutValidator,
|
||||
rundownPostValidator,
|
||||
rundownPutValidator,
|
||||
@@ -35,5 +36,5 @@ router.patch('/reorder/', rundownReorderValidator, rundownReorder);
|
||||
router.patch('/swap', rundownSwapValidator, rundownSwap);
|
||||
router.patch('/applydelay/:eventId', paramsMustHaveEventId, rundownApplyDelay);
|
||||
|
||||
router.delete('/', rundownArrayOfIds, deletesEventById);
|
||||
router.delete('/all', rundownDelete);
|
||||
router.delete('/:eventId', paramsMustHaveEventId, deleteEventById);
|
||||
|
||||
@@ -64,3 +64,14 @@ export const paramsMustHaveEventId = [
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
export const rundownArrayOfIds = [
|
||||
body('ids').isArray().exists(),
|
||||
body('ids.*').isString(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
@@ -90,9 +90,9 @@ export async function addEvent(eventData: PatchWithId & { after?: string }): Pro
|
||||
* deletes event by its ID
|
||||
* @param eventId
|
||||
*/
|
||||
export async function deleteEvent(eventId: string) {
|
||||
export async function deleteEvent(eventIds: string[]) {
|
||||
const scopedMutation = cache.mutateCache(cache.remove);
|
||||
const { didMutate } = await scopedMutation({ eventId });
|
||||
const { didMutate } = await scopedMutation({ eventIds });
|
||||
|
||||
if (didMutate === false) {
|
||||
return;
|
||||
@@ -102,7 +102,7 @@ export async function deleteEvent(eventId: string) {
|
||||
updateRuntimeOnChange();
|
||||
|
||||
// notify timer and external services of change
|
||||
notifyChanges({ timer: [eventId], external: true });
|
||||
notifyChanges({ timer: eventIds, external: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -347,9 +347,22 @@ describe('remove() mutation', () => {
|
||||
test('deletes an event from the rundown', () => {
|
||||
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
|
||||
const testRundown: OntimeRundown = [mockEvent];
|
||||
const { newRundown } = remove({ eventId: mockEvent.id, persistedRundown: testRundown });
|
||||
const { newRundown } = remove({ eventIds: [mockEvent.id], persistedRundown: testRundown });
|
||||
expect(newRundown.length).toBe(0);
|
||||
});
|
||||
test('deletes multiple events from the rundown', () => {
|
||||
const testRundown: OntimeRundown = [
|
||||
{ type: SupportedEvent.Event, id: '1' } as OntimeEvent,
|
||||
{ type: SupportedEvent.Block, id: '2' } as OntimeBlock,
|
||||
{ type: SupportedEvent.Delay, id: '3' } as OntimeDelay,
|
||||
{ type: SupportedEvent.Event, id: '4' } as OntimeEvent,
|
||||
{ type: SupportedEvent.Event, id: '5' } as OntimeEvent,
|
||||
{ type: SupportedEvent.Event, id: '6' } as OntimeEvent,
|
||||
];
|
||||
const { newRundown } = remove({ eventIds: ['1', '2', '3'], persistedRundown: testRundown });
|
||||
expect(newRundown.length).toBe(3);
|
||||
expect(newRundown.at(0).id).toBe('4');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit() mutation', () => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
OntimeRundown,
|
||||
OntimeRundownEntry,
|
||||
} from 'ontime-types';
|
||||
import { generateId, deleteAtIndex, insertAtIndex, reorderArray, swapEventData, checkIsNextDay } from 'ontime-utils';
|
||||
import { generateId, insertAtIndex, reorderArray, swapEventData, checkIsNextDay } from 'ontime-utils';
|
||||
|
||||
import { DataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
import { createPatch } from '../../utils/parser.js';
|
||||
@@ -263,13 +263,12 @@ export function add({ persistedRundown, atIndex, event }: AddArgs): Required<Mut
|
||||
return { newRundown, newEvent, didMutate: true };
|
||||
}
|
||||
|
||||
type RemoveArgs = MutationParams<{ eventId: string }>;
|
||||
type RemoveArgs = MutationParams<{ eventIds: string[] }>;
|
||||
|
||||
export function remove({ persistedRundown, eventId }: RemoveArgs): MutatingReturn {
|
||||
const atIndex = persistedRundown.findIndex((event) => event.id === eventId);
|
||||
const newRundown = deleteAtIndex(atIndex, persistedRundown);
|
||||
export function remove({ persistedRundown, eventIds }: RemoveArgs): MutatingReturn {
|
||||
const newRundown = persistedRundown.filter((event) => !eventIds.includes(event.id));
|
||||
|
||||
return { newRundown, didMutate: atIndex !== -1 };
|
||||
return { newRundown, didMutate: persistedRundown.length !== newRundown.length };
|
||||
}
|
||||
|
||||
export function removeAll(): MutatingReturn {
|
||||
|
||||
@@ -34,6 +34,12 @@ export function deleteAtIndex<T>(index: number, array: T[]) {
|
||||
return array.filter((_, i) => i !== index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders two objects in an array
|
||||
* @param array
|
||||
* @param fromIndex
|
||||
* @param toIndex
|
||||
*/
|
||||
export function reorderArray<T>(array: T[], fromIndex: number, toIndex: number) {
|
||||
if (fromIndex === toIndex) {
|
||||
return array; // No change needed, return the original array
|
||||
|
||||
Reference in New Issue
Block a user