diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx
index 1cb17bfb9..7cedda359 100644
--- a/apps/client/src/App.tsx
+++ b/apps/client/src/App.tsx
@@ -4,9 +4,9 @@ import { ChakraProvider } from '@chakra-ui/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+import { ContextMenu } from './common/components/context-menu/ContextMenu';
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 { socketClientName } from './common/stores/connectionName';
@@ -52,18 +52,18 @@ function App() {
-
-
-
+
diff --git a/apps/client/src/common/api/eventsApi.ts b/apps/client/src/common/api/eventsApi.ts
index c6ad73f67..bb574e340 100644
--- a/apps/client/src/common/api/eventsApi.ts
+++ b/apps/client/src/common/api/eventsApi.ts
@@ -50,6 +50,19 @@ export async function requestApplyDelay(eventId: string) {
return axios.patch(`${rundownURL}/applydelay/${eventId}`);
}
+export type SwapEntry = {
+ from: string;
+ to: string;
+};
+
+/**
+ * @description HTTP request to swap two events
+ * @return {Promise}
+ */
+export async function requestEventSwap(data: SwapEntry) {
+ return axios.patch(`${rundownURL}/swap`, data);
+}
+
/**
* @description HTTP request to delete given event
* @return {Promise}
diff --git a/apps/client/src/common/context/ContextMenuContext.module.scss b/apps/client/src/common/components/context-menu/ContextMenu.module.scss
similarity index 100%
rename from apps/client/src/common/context/ContextMenuContext.module.scss
rename to apps/client/src/common/components/context-menu/ContextMenu.module.scss
diff --git a/apps/client/src/common/components/context-menu/ContextMenu.tsx b/apps/client/src/common/components/context-menu/ContextMenu.tsx
new file mode 100644
index 000000000..68d1932df
--- /dev/null
+++ b/apps/client/src/common/components/context-menu/ContextMenu.tsx
@@ -0,0 +1,84 @@
+// logic (with some modifications) culled from:
+// https://github.com/lukasbach/chakra-ui-contextmenu/blob/main/src/ContextMenu.tsx
+
+import { Fragment, ReactElement } from 'react';
+import { Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
+import { IconType } from '@react-icons/all-files';
+import { create } from 'zustand';
+
+import style from './ContextMenu.module.scss';
+
+type ContextMenuCoords = {
+ x: number;
+ y: number;
+};
+
+export type Option = {
+ label: string;
+ icon: IconType;
+ onClick: () => void;
+ withDivider?: boolean;
+ isDisabled?: boolean;
+};
+
+type ContextMenuStore = {
+ coords: ContextMenuCoords;
+ options: Option[];
+ isOpen: boolean;
+ setContextMenu: (coords: ContextMenuCoords, options: Option[]) => void;
+ setIsOpen: (newIsOpen: boolean) => void;
+};
+
+export const useContextMenuStore = create((set) => ({
+ coords: { x: 0, y: 0 },
+ options: [],
+ isOpen: false,
+ setContextMenu: (coords, options) => set(() => ({ coords, options, isOpen: true })),
+ setIsOpen: (newIsOpen) => set(() => ({ isOpen: newIsOpen })),
+}));
+
+interface ContextMenuProps {
+ // ReactElement type required due to early `return` (line 51) returning {children}
+ children: ReactElement;
+}
+
+export const ContextMenu = ({ children }: ContextMenuProps) => {
+ const { coords, options, isOpen, setIsOpen } = useContextMenuStore();
+
+ const onClose = () => {
+ return setIsOpen(false);
+ };
+
+ if (!isOpen) {
+ return children;
+ }
+
+ return (
+ <>
+ {children}
+
+
+ >
+ );
+};
diff --git a/apps/client/src/common/context/ContextMenuContext.tsx b/apps/client/src/common/context/ContextMenuContext.tsx
deleted file mode 100644
index 2504c332e..000000000
--- a/apps/client/src/common/context/ContextMenuContext.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-// 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(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({ x: 0, y: 0 });
- const [options, setOptions] = useState