mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 07:29:08 +00:00
refactor: use a single menu for table
This commit is contained in:
committed by
Carlos Valente
parent
86bb739d90
commit
84a5b4fe5b
@@ -22,6 +22,7 @@ import BlockRow from './cuesheet-table-elements/BlockRow';
|
|||||||
import CuesheetHeader from './cuesheet-table-elements/CuesheetHeader';
|
import CuesheetHeader from './cuesheet-table-elements/CuesheetHeader';
|
||||||
import DelayRow from './cuesheet-table-elements/DelayRow';
|
import DelayRow from './cuesheet-table-elements/DelayRow';
|
||||||
import EventRow from './cuesheet-table-elements/EventRow';
|
import EventRow from './cuesheet-table-elements/EventRow';
|
||||||
|
import CuesheetTableMenu from './cuesheet-table-menu/CuesheetTableMenu';
|
||||||
import CuesheetTableSettings from './cuesheet-table-settings/CuesheetTableSettings';
|
import CuesheetTableSettings from './cuesheet-table-settings/CuesheetTableSettings';
|
||||||
import useColumnManager from './useColumnManager';
|
import useColumnManager from './useColumnManager';
|
||||||
|
|
||||||
@@ -172,7 +173,6 @@ export default function CuesheetTable(props: CuesheetTableProps) {
|
|||||||
selectedRef={isSelected ? selectedRef : undefined}
|
selectedRef={isSelected ? selectedRef : undefined}
|
||||||
skip={entry.skip}
|
skip={entry.skip}
|
||||||
colour={entry.colour}
|
colour={entry.colour}
|
||||||
showModal={showModal}
|
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => {
|
{row.getVisibleCells().map((cell) => {
|
||||||
return (
|
return (
|
||||||
@@ -191,6 +191,7 @@ export default function CuesheetTable(props: CuesheetTableProps) {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<CuesheetTableMenu showModal={showModal} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-14
@@ -1,11 +1,11 @@
|
|||||||
import { memo, MutableRefObject, PropsWithChildren, useLayoutEffect, useRef, useState } from 'react';
|
import { memo, MutableRefObject, PropsWithChildren, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { IconButton, Menu, MenuButton } from '@chakra-ui/react';
|
import { IconButton } from '@chakra-ui/react';
|
||||||
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
|
|
||||||
import { cx, getAccessibleColour } from '../../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||||
import { useCuesheetOptions } from '../../cuesheet.options';
|
import { useCuesheetOptions } from '../../cuesheet.options';
|
||||||
import CuesheetTableMenu from '../cuesheet-table-menu/CuesheetTableMenu';
|
import { useCuesheetTableMenu } from '../cuesheet-table-menu/useCuesheetTableMenu';
|
||||||
|
|
||||||
import style from '../CuesheetTable.module.scss';
|
import style from '../CuesheetTable.module.scss';
|
||||||
|
|
||||||
@@ -17,15 +17,16 @@ interface EventRowProps {
|
|||||||
selectedRef?: MutableRefObject<HTMLTableRowElement | null>;
|
selectedRef?: MutableRefObject<HTMLTableRowElement | null>;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
colour?: string;
|
colour?: string;
|
||||||
showModal: (eventId: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventRow(props: PropsWithChildren<EventRowProps>) {
|
function EventRow(props: PropsWithChildren<EventRowProps>) {
|
||||||
const { children, eventId, eventIndex, rowIndex, isPast, selectedRef, skip, colour, showModal } = props;
|
const { children, eventId, eventIndex, rowIndex, isPast, selectedRef, skip, colour } = props;
|
||||||
const { hideIndexColumn, showActionMenu } = useCuesheetOptions();
|
const { hideIndexColumn, showActionMenu } = useCuesheetOptions();
|
||||||
const ownRef = useRef<HTMLTableRowElement>(null);
|
const ownRef = useRef<HTMLTableRowElement>(null);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
const { openMenu } = useCuesheetTableMenu();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
@@ -64,16 +65,17 @@ function EventRow(props: PropsWithChildren<EventRowProps>) {
|
|||||||
>
|
>
|
||||||
{showActionMenu && (
|
{showActionMenu && (
|
||||||
<td className={style.actionColumn}>
|
<td className={style.actionColumn}>
|
||||||
<Menu variant='ontime-on-dark' size='sm' isLazy>
|
<IconButton
|
||||||
<MenuButton
|
size='sm'
|
||||||
as={IconButton}
|
aria-label='Options'
|
||||||
size='sm'
|
icon={<IoEllipsisHorizontal />}
|
||||||
aria-label='Options'
|
variant='ontime-subtle'
|
||||||
icon={<IoEllipsisHorizontal />}
|
onClick={(event) => {
|
||||||
variant='ontime-subtle'
|
const rect = event.currentTarget.getBoundingClientRect();
|
||||||
/>
|
const yPos = 8 + rect.y + rect.height / 2;
|
||||||
<CuesheetTableMenu eventId={eventId} entryIndex={rowIndex} showModal={showModal} />
|
openMenu({ x: rect.x, y: yPos }, eventId, rowIndex);
|
||||||
</Menu>
|
}}
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
{!hideIndexColumn && (
|
{!hideIndexColumn && (
|
||||||
|
|||||||
+23
-58
@@ -1,70 +1,35 @@
|
|||||||
import { MenuDivider, MenuItem, MenuList, Portal } from '@chakra-ui/react';
|
import { memo } from 'react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
import { Menu, MenuButton, Portal } from '@chakra-ui/react';
|
||||||
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
|
||||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
|
||||||
import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
|
|
||||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
|
||||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
|
||||||
import { isOntimeEvent, SupportedEvent } from 'ontime-types';
|
|
||||||
|
|
||||||
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
import CuesheetTableMenuActionsProps from './CuesheetTableMenuActions';
|
||||||
import { cloneEvent } from '../../../../common/utils/eventsManager';
|
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
||||||
|
|
||||||
interface CuesheetTableMenuProps {
|
interface CuesheetTableMenuProps {
|
||||||
eventId: string;
|
showModal: (eventId: string) => void;
|
||||||
entryIndex: number;
|
|
||||||
showModal: (entryId: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CuesheetTableMenu(props: CuesheetTableMenuProps) {
|
export default memo(CuesheetTableMenu);
|
||||||
const { eventId, entryIndex, showModal } = props;
|
|
||||||
const { addEvent, getEventById, reorderEvent, deleteEvent } = useEventAction();
|
|
||||||
|
|
||||||
const handleCloneEvent = () => {
|
function CuesheetTableMenu(props: CuesheetTableMenuProps) {
|
||||||
const currentEvent = getEventById(eventId);
|
const { showModal } = props;
|
||||||
if (!currentEvent || !isOntimeEvent(currentEvent)) {
|
const { isOpen, eventId, entryIndex, position, closeMenu } = useCuesheetTableMenu();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newEvent = cloneEvent(currentEvent);
|
|
||||||
try {
|
|
||||||
addEvent(newEvent, { after: eventId });
|
|
||||||
} catch (_error) {
|
|
||||||
// we do not handle errors here
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<MenuList>
|
{isOpen && (
|
||||||
<MenuItem icon={<IoOptions />} onClick={() => showModal(eventId)}>
|
<Menu isOpen size='sm' onClose={closeMenu} isLazy variant='ontime-on-dark'>
|
||||||
Edit ...
|
<MenuButton
|
||||||
</MenuItem>
|
position='absolute'
|
||||||
<MenuDivider />
|
left={position.x}
|
||||||
<MenuItem icon={<IoAdd />} onClick={() => addEvent({ type: SupportedEvent.Event }, { before: eventId })}>
|
top={position.y}
|
||||||
Add event above
|
pointerEvents='none'
|
||||||
</MenuItem>
|
aria-hidden
|
||||||
<MenuItem icon={<IoAdd />} onClick={() => addEvent({ type: SupportedEvent.Event }, { after: eventId })}>
|
w={1}
|
||||||
Add event below
|
h={1}
|
||||||
</MenuItem>
|
/>
|
||||||
<MenuItem icon={<IoDuplicateOutline />} onClick={handleCloneEvent}>
|
<CuesheetTableMenuActionsProps eventId={eventId} entryIndex={entryIndex} showModal={showModal} />
|
||||||
Clone event
|
</Menu>
|
||||||
</MenuItem>
|
)}
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem
|
|
||||||
isDisabled={entryIndex < 1}
|
|
||||||
icon={<IoArrowUp />}
|
|
||||||
onClick={() => reorderEvent(eventId, entryIndex, entryIndex - 1)}
|
|
||||||
>
|
|
||||||
Move up
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<IoArrowDown />} onClick={() => reorderEvent(eventId, entryIndex, entryIndex + 1)}>
|
|
||||||
Move down
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<IoTrash />} onClick={() => deleteEvent([eventId])}>
|
|
||||||
Delete
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import { MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
|
||||||
|
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||||
|
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||||
|
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||||
|
import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
|
||||||
|
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||||
|
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||||
|
import { isOntimeEvent, SupportedEvent } from 'ontime-types';
|
||||||
|
|
||||||
|
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||||
|
import { cloneEvent } from '../../../../common/utils/eventsManager';
|
||||||
|
|
||||||
|
interface CuesheetTableMenuActionsProps {
|
||||||
|
eventId: string;
|
||||||
|
entryIndex: number;
|
||||||
|
showModal: (entryId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CuesheetTableMenuActions(props: CuesheetTableMenuActionsProps) {
|
||||||
|
const { eventId, entryIndex, showModal } = props;
|
||||||
|
const { addEvent, getEventById, reorderEvent, deleteEvent } = useEventAction();
|
||||||
|
|
||||||
|
const handleCloneEvent = () => {
|
||||||
|
const currentEvent = getEventById(eventId);
|
||||||
|
if (!currentEvent || !isOntimeEvent(currentEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newEvent = cloneEvent(currentEvent);
|
||||||
|
try {
|
||||||
|
addEvent(newEvent, { after: eventId });
|
||||||
|
} catch (_error) {
|
||||||
|
// we do not handle errors here
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem icon={<IoOptions />} onClick={() => showModal(eventId)}>
|
||||||
|
Edit ...
|
||||||
|
</MenuItem>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem icon={<IoAdd />} onClick={() => addEvent({ type: SupportedEvent.Event }, { before: eventId })}>
|
||||||
|
Add event above
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<IoAdd />} onClick={() => addEvent({ type: SupportedEvent.Event }, { after: eventId })}>
|
||||||
|
Add event below
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<IoDuplicateOutline />} onClick={handleCloneEvent}>
|
||||||
|
Clone event
|
||||||
|
</MenuItem>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem
|
||||||
|
isDisabled={entryIndex < 1}
|
||||||
|
icon={<IoArrowUp />}
|
||||||
|
onClick={() => reorderEvent(eventId, entryIndex, entryIndex - 1)}
|
||||||
|
>
|
||||||
|
Move up
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<IoArrowDown />} onClick={() => reorderEvent(eventId, entryIndex, entryIndex + 1)}>
|
||||||
|
Move down
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<IoTrash />} onClick={() => deleteEvent([eventId])}>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
);
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
type Anchor = { x: number; y: number };
|
||||||
|
|
||||||
|
type OpenMenu = {
|
||||||
|
isOpen: true;
|
||||||
|
eventId: string;
|
||||||
|
entryIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ClosedMenu = {
|
||||||
|
isOpen: false;
|
||||||
|
eventId: null;
|
||||||
|
entryIndex: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CuesheetTableMenuStore = (OpenMenu | ClosedMenu) & {
|
||||||
|
position: Anchor;
|
||||||
|
openMenu: (position: Anchor, eventId: string, entryIndex: number) => void;
|
||||||
|
closeMenu: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCuesheetTableMenu = create<CuesheetTableMenuStore>((set) => ({
|
||||||
|
isOpen: false,
|
||||||
|
eventId: null,
|
||||||
|
entryIndex: null,
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
openMenu: (position: Anchor, eventId: string, entryIndex: number) =>
|
||||||
|
set({ isOpen: true, position, eventId, entryIndex }),
|
||||||
|
closeMenu: () => set({ isOpen: false }),
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user