mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 22:49:18 +00:00
+13
-10
@@ -6,6 +6,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|||||||
|
|
||||||
import ErrorBoundary from './common/components/error-boundary/ErrorBoundary';
|
import ErrorBoundary from './common/components/error-boundary/ErrorBoundary';
|
||||||
import { AppContextProvider } from './common/context/AppContext';
|
import { AppContextProvider } from './common/context/AppContext';
|
||||||
|
import { ContextMenuProvider } from './common/context/ContextMenuContext';
|
||||||
import useElectronEvent from './common/hooks/useElectronEvent';
|
import useElectronEvent from './common/hooks/useElectronEvent';
|
||||||
import { ontimeQueryClient } from './common/queryClient';
|
import { ontimeQueryClient } from './common/queryClient';
|
||||||
import { connectSocket } from './common/utils/socket';
|
import { connectSocket } from './common/utils/socket';
|
||||||
@@ -49,16 +50,18 @@ function App() {
|
|||||||
<ChakraProvider resetCSS theme={theme}>
|
<ChakraProvider resetCSS theme={theme}>
|
||||||
<QueryClientProvider client={ontimeQueryClient}>
|
<QueryClientProvider client={ontimeQueryClient}>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<BrowserRouter>
|
<ContextMenuProvider>
|
||||||
<div className='App'>
|
<BrowserRouter>
|
||||||
<ErrorBoundary>
|
<div className='App'>
|
||||||
<TranslationProvider>
|
<ErrorBoundary>
|
||||||
<AppRouter />
|
<TranslationProvider>
|
||||||
</TranslationProvider>
|
<AppRouter />
|
||||||
</ErrorBoundary>
|
</TranslationProvider>
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
</ErrorBoundary>
|
||||||
</div>
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</BrowserRouter>
|
</div>
|
||||||
|
</BrowserRouter>
|
||||||
|
</ContextMenuProvider>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ChakraProvider>
|
</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 { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
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 { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||||
import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
|
import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import type { EventItemActions } from '../RundownEntry';
|
import type { EventItemActions } from '../RundownEntry';
|
||||||
@@ -75,6 +77,9 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const openId = useAppMode((state) => state.editId);
|
const openId = useAppMode((state) => state.editId);
|
||||||
|
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
||||||
|
{ label: eventId, icon: IoAdd, onClick: () => console.log(eventId) },
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isDragging,
|
isDragging,
|
||||||
@@ -142,7 +147,13 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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}>
|
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
|
||||||
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
||||||
<IoReorderTwo />
|
<IoReorderTwo />
|
||||||
|
|||||||
Reference in New Issue
Block a user