feat: duplicate groups

This commit is contained in:
Carlos Valente
2025-05-19 20:01:03 +02:00
committed by arc-alex
parent 11e0215530
commit 2f26f20db5
20 changed files with 448 additions and 88 deletions
+7
View File
@@ -87,6 +87,13 @@ export async function requestApplyDelay(delayId: EntryId): Promise<AxiosResponse
return axios.patch(`${rundownPath}/applydelay/${delayId}`);
}
/**
* HTTP request for cloning an entry
*/
export async function postCloneEntry(entryId: EntryId): Promise<AxiosResponse<Rundown>> {
return axios.post(`${rundownPath}/clone/${entryId}`);
}
/**
* HTTP request for dissolving of a block
*/
@@ -20,6 +20,7 @@ import {
deleteEntries,
patchReorderEntry,
postAddEntry,
postCloneEntry,
putBatchEditEvents,
putEditEntry,
ReorderEntry,
@@ -164,6 +165,29 @@ export const useEntryActions = () => {
],
);
/**
* Calls mutation to clone a selection
* @private
*/
const _cloneMutation = useMutation({
mutationFn: postCloneEntry,
onSettled: () => queryClient.invalidateQueries({ queryKey: RUNDOWN }),
});
/**
* Clone a selection
*/
const clone = useCallback(
async (entryId: EntryId) => {
try {
await _cloneMutation.mutateAsync(entryId);
} catch (error) {
logAxiosError('Error cloning entry', error);
}
},
[_cloneMutation],
);
/**
* Calls mutation to update existing entry
* @private
@@ -735,6 +759,7 @@ export const useEntryActions = () => {
addEntry,
applyDelay,
batchUpdateEvents,
clone,
deleteEntry,
deleteAllEntries,
dissolveBlock,
@@ -1,6 +1,6 @@
import { EndAction, EntryCustomFields, OntimeEvent, SupportedEntry, TimerType, TimeStrategy } from 'ontime-types';
import { cloneEvent } from '../eventsManager';
import { cloneEvent } from '../clone';
describe('cloneEvent()', () => {
it('creates a stem from a given event', () => {
+2 -2
View File
@@ -37,7 +37,7 @@ import useFollowComponent from '../../common/hooks/useFollowComponent';
import { useRundownEditor } from '../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
import { useEntryCopy } from '../../common/stores/entryCopyStore';
import { cloneEvent } from '../../common/utils/eventsManager';
import { cloneEvent } from '../../common/utils/clone';
import BlockBlock from './block-block/BlockBlock';
import BlockEnd from './block-block/BlockEnd';
@@ -329,7 +329,7 @@ export default function Rundown({ data }: RundownProps) {
};
if (sortableData.length < 1) {
return <RundownEmpty handleAddNew={(type: SupportedEntry) => insertAtId({ type }, cursor)} />;
return <RundownEmpty handleAddNew={(type: SupportedEntry) => addEntry({ type })} />;
}
// 1. gather presentation options
@@ -12,7 +12,7 @@ import {
import { useEntryActions } from '../../common/hooks/useEntryAction';
import useMemoisedFn from '../../common/hooks/useMemoisedFn';
import { useEmitLog } from '../../common/stores/logger';
import { cloneEvent } from '../../common/utils/eventsManager';
import { cloneEvent } from '../../common/utils/clone';
import DelayBlock from './delay-block/DelayBlock';
import EventBlock from './event-block/EventBlock';
@@ -1,6 +1,14 @@
import { useRef } from 'react';
import { IoChevronDown, IoChevronUp, IoEllipsisHorizontal, IoReorderTwo } from 'react-icons/io5';
import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
import {
IoChevronDown,
IoChevronUp,
IoDuplicateOutline,
IoEllipsisHorizontal,
IoFolderOpenOutline,
IoReorderTwo,
IoTrash,
} from 'react-icons/io5';
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Portal } from '@chakra-ui/react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { EntryId, OntimeBlock } from 'ontime-types';
@@ -23,7 +31,7 @@ interface BlockBlockProps {
export default function BlockBlock(props: BlockBlockProps) {
const { data, hasCursor, collapsed, onCollapse } = props;
const handleRef = useRef<null | HTMLSpanElement>(null);
const { dissolveBlock } = useEntryActions();
const { clone, dissolveBlock, deleteEntry } = useEntryActions();
const {
attributes: dragAttributes,
@@ -52,6 +60,8 @@ export default function BlockBlock(props: BlockBlockProps) {
cursor: isOver ? (isValidDrop ? 'grabbing' : 'no-drop') : 'default',
};
const hasChildren = data.events.length > 0;
return (
<div
className={cx([style.block, hasCursor && style.hasCursor, !collapsed && style.expanded])}
@@ -83,12 +93,24 @@ export default function BlockBlock(props: BlockBlockProps) {
variant='ontime-ghosted'
size='sm'
/>
<MenuList>
<MenuItem onClick={() => dissolveBlock(data.id)}>Dissolve Block</MenuItem>
</MenuList>
<Portal>
<MenuList>
<MenuItem icon={<IoDuplicateOutline />} onClick={() => clone(data.id)}>
Clone Block
</MenuItem>
{hasChildren && (
<MenuItem icon={<IoFolderOpenOutline />} onClick={() => dissolveBlock(data.id)}>
Dissolve Block
</MenuItem>
)}
<MenuItem icon={<IoTrash />} onClick={() => deleteEntry([data.id])}>
Delete Block
</MenuItem>
</MenuList>
</Portal>
</Menu>
<IconButton
aria-label='Dissolve'
aria-label='Collapse'
onClick={() => onCollapse(!collapsed, data.id)}
color='#e2e2e2' // $gray-200
variant='ontime-ghosted'
@@ -3,7 +3,7 @@ import { MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
import { isOntimeEvent, SupportedEntry } from 'ontime-types';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { cloneEvent } from '../../../../common/utils/eventsManager';
import { cloneEvent } from '../../../../common/utils/clone';
interface CuesheetTableMenuActionsProps {
eventId: string;