mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 17:03:53 +00:00
+13
-10
@@ -6,6 +6,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
|
||||
import ErrorBoundary from './common/components/error-boundary/ErrorBoundary';
|
||||
import { AppContextProvider } from './common/context/AppContext';
|
||||
import { ContextMenuProvider } from './common/context/ContextMenuContext';
|
||||
import useElectronEvent from './common/hooks/useElectronEvent';
|
||||
import { ontimeQueryClient } from './common/queryClient';
|
||||
import { connectSocket } from './common/utils/socket';
|
||||
@@ -49,16 +50,18 @@ function App() {
|
||||
<ChakraProvider resetCSS theme={theme}>
|
||||
<QueryClientProvider client={ontimeQueryClient}>
|
||||
<AppContextProvider>
|
||||
<BrowserRouter>
|
||||
<div className='App'>
|
||||
<ErrorBoundary>
|
||||
<TranslationProvider>
|
||||
<AppRouter />
|
||||
</TranslationProvider>
|
||||
</ErrorBoundary>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
<ContextMenuProvider>
|
||||
<BrowserRouter>
|
||||
<div className='App'>
|
||||
<ErrorBoundary>
|
||||
<TranslationProvider>
|
||||
<AppRouter />
|
||||
</TranslationProvider>
|
||||
</ErrorBoundary>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
</ContextMenuProvider>
|
||||
</AppContextProvider>
|
||||
</QueryClientProvider>
|
||||
</ChakraProvider>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.contextMenuButton {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contextMenuBackdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// logic (with some modifications) culled from:
|
||||
// https://github.com/lukasbach/chakra-ui-contextmenu/blob/main/src/ContextMenu.tsx
|
||||
|
||||
import { createContext, ReactNode, useState } from 'react';
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { IconType } from '@react-icons/all-files';
|
||||
|
||||
import style from './ContextMenuContext.module.scss';
|
||||
|
||||
type ContextMenuCoords = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
type ContextMenuContextType = {
|
||||
createContextMenu: (options: Option[], menuCoordinates: ContextMenuCoords) => void;
|
||||
};
|
||||
|
||||
export const ContextMenuContext = createContext<ContextMenuContextType | null>(null);
|
||||
|
||||
export type Option = {
|
||||
label: string;
|
||||
icon: IconType;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
interface ContextMenuProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [coords, setCoords] = useState<ContextMenuCoords>({ x: 0, y: 0 });
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
|
||||
const onClose = () => {
|
||||
return setIsOpen(false);
|
||||
};
|
||||
|
||||
const createContextMenu = (options: Option[], menuCoords: ContextMenuCoords) => {
|
||||
setCoords(menuCoords);
|
||||
setOptions(options);
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<ContextMenuContext.Provider value={{ createContextMenu }}>
|
||||
{children}
|
||||
{isOpen && (
|
||||
<>
|
||||
<div className={style.contextMenuBackdrop} />
|
||||
<Menu isOpen gutter={0} onClose={onClose} isLazy lazyBehavior='unmount' variant='ontime-on-dark'>
|
||||
<MenuButton
|
||||
className={style.contextMenuButton}
|
||||
aria-hidden
|
||||
w={1}
|
||||
h={1}
|
||||
style={{
|
||||
left: coords.x,
|
||||
top: coords.y,
|
||||
}}
|
||||
/>
|
||||
<MenuList>
|
||||
{options.map(({ label, icon: Icon, onClick }, i) => (
|
||||
<MenuItem key={i} icon={<Icon />} onClick={onClick}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
</ContextMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MouseEvent, useContext } from 'react';
|
||||
|
||||
import { ContextMenuContext, Option } from '../context/ContextMenuContext';
|
||||
|
||||
export const useContextMenu = <T extends HTMLElement>(options: Option[]) => {
|
||||
const contextMenuContext = useContext(ContextMenuContext);
|
||||
|
||||
if (contextMenuContext === null) {
|
||||
throw new Error('useContextMenu should be wrapped by ContextMenuProvider');
|
||||
}
|
||||
|
||||
const { createContextMenu } = contextMenuContext;
|
||||
|
||||
const localCreateContextMenu = (contextMenuEvent: MouseEvent<T, globalThis.MouseEvent>) => {
|
||||
// prevent browser default context menu from showing up
|
||||
contextMenuEvent.preventDefault();
|
||||
|
||||
const { pageX, pageY } = contextMenuEvent;
|
||||
return createContextMenu(options, { x: pageX, y: pageY });
|
||||
};
|
||||
|
||||
return [localCreateContextMenu];
|
||||
};
|
||||
@@ -1,9 +1,11 @@
|
||||
import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||
import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
|
||||
|
||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||
import type { EventItemActions } from '../RundownEntry';
|
||||
@@ -75,6 +77,9 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const openId = useAppMode((state) => state.editId);
|
||||
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
||||
{ label: eventId, icon: IoAdd, onClick: () => console.log(eventId) },
|
||||
]);
|
||||
|
||||
const {
|
||||
isDragging,
|
||||
@@ -142,7 +147,13 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={blockClasses} ref={setNodeRef} style={dragStyle} onClick={handleFocusClick}>
|
||||
<div
|
||||
className={blockClasses}
|
||||
ref={setNodeRef}
|
||||
style={dragStyle}
|
||||
onClick={handleFocusClick}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
|
||||
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
||||
<IoReorderTwo />
|
||||
|
||||
Reference in New Issue
Block a user