feat: create group from entry selection

This commit is contained in:
Carlos Valente
2025-05-17 15:17:00 +02:00
committed by Carlos Valente
parent 67f914bdc0
commit 7d5291e5e5
11 changed files with 240 additions and 30 deletions
+3 -3
View File
@@ -90,15 +90,15 @@ export async function requestApplyDelay(delayId: EntryId): Promise<AxiosResponse
/**
* HTTP request for dissolving of a block
*/
export async function requestDissolveBlock(blockId: EntryId): Promise<AxiosResponse<MessageResponse>> {
export async function requestDissolveBlock(blockId: EntryId): Promise<AxiosResponse<Rundown>> {
return axios.post(`${rundownPath}/dissolve/${blockId}`);
}
/**
* HTTP request for grouping a list of entries into a block
*/
export async function requestGroupEntries(entryIds: EntryId[]): Promise<AxiosResponse<MessageResponse>> {
return axios.post(`${rundownPath}/group`, { data: { ids: entryIds } });
export async function requestGroupEntries(entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
return axios.post(`${rundownPath}/group`, { ids: entryIds });
}
/**
+63 -11
View File
@@ -27,6 +27,7 @@ import {
requestDeleteAll,
requestDissolveBlock,
requestEventSwap,
requestGroupEntries,
SwapEntry,
} from '../api/rundown';
import { logAxiosError } from '../api/utils';
@@ -521,6 +522,19 @@ export const useEntryActions = () => {
*/
const _dissolveBlockMutation = useMutation({
mutationFn: requestDissolveBlock,
onSuccess: (response) => {
if (!response.data) return;
const { id, title, order, flatOrder, entries, revision } = response.data;
queryClient.setQueryData<Rundown>(RUNDOWN, {
id,
title,
order,
flatOrder,
entries,
revision,
});
},
onSettled: () => queryClient.invalidateQueries({ queryKey: RUNDOWN }),
});
@@ -537,6 +551,43 @@ export const useEntryActions = () => {
},
[_dissolveBlockMutation],
);
/**
* Calls mutation to create a block with a selection
* @private
*/
const _groupEntriesMutation = useMutation({
mutationFn: requestGroupEntries,
onSuccess: (response) => {
if (!response.data) return;
const { id, title, order, flatOrder, entries, revision } = response.data;
queryClient.setQueryData<Rundown>(RUNDOWN, {
id,
title,
order,
flatOrder,
entries,
revision,
});
},
onSettled: () => queryClient.invalidateQueries({ queryKey: RUNDOWN }),
});
/**
* Create a block with a selection
*/
const groupEntries = useCallback(
async (entryIds: EntryId[]) => {
try {
await _groupEntriesMutation.mutateAsync(entryIds);
} catch (error) {
logAxiosError('Error grouping entries', error);
}
},
[_groupEntriesMutation],
);
/**
* Calls mutation to reorder an entry
* @private
@@ -575,17 +626,17 @@ export const useEntryActions = () => {
// Mutation finished, we update the rundown with the response
onSuccess: (response) => {
if (response.data) {
const { id, title, order, flatOrder, entries, revision } = response.data;
queryClient.setQueryData<Rundown>(RUNDOWN, {
id,
title,
order,
flatOrder,
entries,
revision,
});
}
if (!response.data) return;
const { id, title, order, flatOrder, entries, revision } = response.data;
queryClient.setQueryData<Rundown>(RUNDOWN, {
id,
title,
order,
flatOrder,
entries,
revision,
});
},
// Mutation finished, failed or successful
@@ -688,6 +739,7 @@ export const useEntryActions = () => {
deleteAllEntries,
dissolveBlock,
getEntryById,
groupEntries,
reorderEntry,
swapEvents,
updateEntry,
@@ -19,18 +19,17 @@ import EventBlock from './event-block/EventBlock';
import { useEventSelection } from './useEventSelection';
export type EventItemActions =
| 'set-cursor'
| 'event'
| 'event-before'
| 'delay'
| 'delay-before'
| 'block'
| 'block-before'
| 'swap'
| 'delete'
| 'clone'
| 'update'
| 'swap'
| 'clear-report';
| 'group'
| 'update';
interface RundownEntryProps {
type: SupportedEntry;
@@ -66,7 +65,7 @@ export default function RundownEntry(props: RundownEntryProps) {
isLinkedToLoaded,
} = props;
const { emitError } = useEmitLog();
const { addEntry, updateEntry, batchUpdateEvents, deleteEntry, swapEvents } = useEntryActions();
const { addEntry, updateEntry, batchUpdateEvents, deleteEntry, groupEntries, swapEvents } = useEntryActions();
const { selectedEvents, unselect, clearSelectedEvents } = useEventSelection();
const removeOpenEvent = useCallback(() => {
@@ -129,6 +128,13 @@ export default function RundownEntry(props: RundownEntryProps) {
addEntry(newEvent, { after: data.id });
break;
}
case 'group': {
if (selectedEvents.size > 1) {
clearMultiSelection();
return groupEntries(Array.from(selectedEvents));
}
break;
}
case 'update': {
// Handles and filters update requests
const { field, value } = payload as FieldValue;
@@ -2,6 +2,7 @@ import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react'
import {
IoAdd,
IoDuplicateOutline,
IoFolder,
IoLink,
IoPeople,
IoPeopleOutline,
@@ -26,7 +27,7 @@ import RundownIndicators from './RundownIndicators';
import style from './EventBlock.module.scss';
interface EventBlockProps {
eventId: string;
eventId: EntryId;
cue: string;
timeStart: number;
timeEnd: number;
@@ -144,6 +145,7 @@ export default function EventBlock(props: EventBlockProps) {
value: false,
}),
},
{ withDivider: true, label: 'Group', icon: IoFolder, onClick: () => actionHandler('group') },
{ withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
]
: [
@@ -9,13 +9,13 @@ import { isMacOS } from '../../common/utils/deviceUtils';
type SelectionMode = 'shift' | 'click' | 'ctrl';
interface EventSelectionStore {
selectedEvents: Set<string>;
selectedEvents: Set<EntryId>;
anchoredIndex: MaybeNumber;
cursor: MaybeString;
setSelectedEvents: (selectionArgs: { id: string; index: number; selectMode: SelectionMode }) => void;
setSelectedEvents: (selectionArgs: { id: EntryId; index: number; selectMode: SelectionMode }) => void;
clearSelectedEvents: () => void;
clearMultiSelect: () => void;
unselect: (id: string) => void;
unselect: (id: EntryId) => void;
}
export const useEventSelection = create<EventSelectionStore>()((set, get) => ({