feat: multiple selection (#703)

feat: multiple event selection

---------

Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com>
Co-authored-by: Alex <ac@omnivox.dk>
This commit is contained in:
Carlos Valente
2024-01-12 13:07:56 +01:00
committed by GitHub
parent f965db148c
commit 7f63cde7a5
20 changed files with 460 additions and 171 deletions
@@ -3,28 +3,44 @@ import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
import { IoCopyOutline } from '@react-icons/all-files/io5/IoCopyOutline';
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useAppMode } from '../../../common/stores/appModeStore';
import useRundown from '../../../common/hooks-query/useRundown';
import copyToClipboard from '../../../common/utils/copyToClipboard';
import { isMacOS } from '../../../common/utils/deviceUtils';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import type { EventItemActions } from '../RundownEntry';
import { useEventIdSwapping } from '../useEventIdSwapping';
import { EditMode, useEventSelection } from '../useEventSelection';
import EventBlockInner from './EventBlockInner';
import style from './EventBlock.module.scss';
const getEditMode = (event: MouseEvent): EditMode => {
if ((isMacOS() && event.metaKey) || event.ctrlKey) {
return 'ctrl';
}
if (event.shiftKey) {
return 'shift';
}
return 'click';
};
interface EventBlockProps {
cue: string;
timeStart: number;
timeEnd: number;
duration: number;
eventId: string;
eventIndex: number;
isPublic: boolean;
endAction: EndAction;
timerType: TimerType;
@@ -61,6 +77,7 @@ export default function EventBlock(props: EventBlockProps) {
timeEnd,
duration,
isPublic = true,
eventIndex,
endAction,
timerType,
title,
@@ -80,37 +97,66 @@ export default function EventBlock(props: EventBlockProps) {
isFirstEvent,
} = props;
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
const moveCursorTo = useAppMode((state) => state.setCursor);
const { selectedEvents, setSelectedEvents } = useEventSelection();
const { data: rundown = [] } = useRundown();
const handleRef = useRef<null | HTMLSpanElement>(null);
const [isVisible, setIsVisible] = useState(false);
const openId = useAppMode((state) => state.editId);
const [onContextMenu] = useContextMenu<HTMLDivElement>([
{ label: `Copy ID: ${eventId}`, icon: IoCopyOutline, onClick: () => copyToClipboard(eventId) },
{
label: 'Toggle public',
icon: IoPeopleOutline,
onClick: () =>
actionHandler('update', {
field: 'isPublic',
value: !isPublic,
}),
},
{
label: 'Add to swap',
icon: IoAdd,
onClick: () => setSelectedEventId(eventId),
withDivider: true,
},
{
label: `Swap this event with ${selectedEventId ?? ''}`,
icon: IoSwapVertical,
onClick: () => {
actionHandler('swap', { field: 'id', value: selectedEventId });
clearSelectedEventId();
},
isDisabled: selectedEventId == null || selectedEventId === eventId,
},
]);
const [onContextMenu] = useContextMenu<HTMLDivElement>(
selectedEvents.size > 1
? [
{
label: 'Visiblity',
group: [
{
label: 'Make public',
icon: IoPeople,
onClick: () =>
actionHandler('update', {
field: 'isPublic',
value: true,
}),
},
{
label: 'Make private',
icon: IoPeopleOutline,
onClick: () =>
actionHandler('update', {
field: 'isPublic',
value: false,
}),
},
],
},
]
: [
{ label: `Copy ID: ${eventId}`, icon: IoCopyOutline, onClick: () => copyToClipboard(eventId) },
{
label: 'Toggle public',
icon: IoPeopleOutline,
onClick: () =>
actionHandler('update', {
field: 'isPublic',
value: !isPublic,
}),
},
{
label: 'Add to swap',
icon: IoAdd,
onClick: () => setSelectedEventId(eventId),
withDivider: true,
},
{
label: `Swap this event with ${selectedEventId ?? ''}`,
icon: IoSwapVertical,
onClick: () => {
actionHandler('swap', { field: 'id', value: selectedEventId });
clearSelectedEventId();
},
isDisabled: selectedEventId == null || selectedEventId === eventId,
},
],
);
const {
isDragging,
@@ -179,12 +225,23 @@ export default function EventBlock(props: EventBlockProps) {
isPast ? style.past : null,
selected ? style.selected : null,
playback ? style[playback] : null,
hasCursor ? style.hasCursor : null,
selectedEvents.has(eventId) ? style.hasCursor : null,
]);
const handleFocusClick = (event: MouseEvent) => {
event.stopPropagation();
moveCursorTo(eventId, true);
// event.button === 2 is a right-click
// disable selection if the user selected events and right clicks
// so the context menu shows up
if (selectedEvents.size > 1 && event.button === 2) {
return;
}
const editMode = getEditMode(event);
return setSelectedEvents({ id: eventId, index: eventIndex, rundown, editMode });
// moveCursorTo(eventId, true);
};
return (
@@ -192,7 +249,7 @@ export default function EventBlock(props: EventBlockProps) {
className={blockClasses}
ref={setNodeRef}
style={dragStyle}
onClick={handleFocusClick}
onMouseDown={handleFocusClick}
onContextMenu={onContextMenu}
id='event-block'
>
@@ -204,11 +261,11 @@ export default function EventBlock(props: EventBlockProps) {
</div>
{isVisible && (
<EventBlockInner
isOpen={openId === eventId}
timeStart={timeStart}
timeEnd={timeEnd}
duration={duration}
eventId={eventId}
eventIndex={eventIndex}
isPublic={isPublic}
endAction={endAction}
timerType={timerType}