From 51c31adaf1da60db252b42f40b3eb252745d9e44 Mon Sep 17 00:00:00 2001
From: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com>
Date: Tue, 15 Aug 2023 16:04:53 -0400
Subject: [PATCH] Swap Event Data (#446)
* use zustand store in `ContextMenuContext`
* add `withDivider` prop to context menu `Option`
* add `isDisabled` prop to `Option`
* create `eventIdSwapping` store
* create swapping context menu options
* fix `key` in ContextMenu
* address `tanstack-eslint` errors
* create initial `requestEventSwap` event
* create initial `swapEvents` action
* use `swapEvents` in `EventBlock`
* move from `emitError` to `logAxiosError`
* remove extra curly brace from Copy ID
* add `clearEventId` func
* finalize context menu swapping logic
* write optimistic swapping logic
* remove unused import and type out handler in `rundownController`
* create `rundownSwapValidator`
* move index increase to `EventBlock`
* move swapping logic from `id` to `index`
* create `swapEvents` endpoint
* move `useEventIdSwapping` hook into its own file
* revert to using event `id`
* logic now uses indexes to swap events
* add `todo` to `swapEvents`
* revert index increment
* create `swapOntimeEvents` and export in `index.ts`
* use `swapOntimeEvents` in frontend & server
* update import path
* remove extra `setCached`
---
apps/client/src/App.tsx | 24 ++---
apps/client/src/common/api/eventsApi.ts | 13 +++
.../context-menu/ContextMenu.module.scss} | 0
.../components/context-menu/ContextMenu.tsx | 84 ++++++++++++++++++
.../src/common/context/ContextMenuContext.tsx | 76 ----------------
.../src/common/hooks/useContextMenu.tsx | 14 +--
.../client/src/common/hooks/useEventAction.ts | 87 +++++++++++++++++--
.../src/features/rundown/RundownEntry.tsx | 11 ++-
.../rundown/event-block/EventBlock.tsx | 29 +++++--
.../features/rundown/useEventIdSwapping.ts | 13 +++
.../src/controllers/rundownController.ts | 31 +++++--
.../controllers/rundownController.validate.ts | 10 +++
apps/server/src/routes/rundownRouter.ts | 4 +
.../rundown-service/RundownService.ts | 17 ++++
.../rundown-service/delayedRundown.utils.ts | 30 ++++++-
packages/utils/index.ts | 1 +
.../utils/src/rundown-utils/rundownUtils.ts | 42 +++++++++
17 files changed, 363 insertions(+), 123 deletions(-)
rename apps/client/src/common/{context/ContextMenuContext.module.scss => components/context-menu/ContextMenu.module.scss} (100%)
create mode 100644 apps/client/src/common/components/context-menu/ContextMenu.tsx
delete mode 100644 apps/client/src/common/context/ContextMenuContext.tsx
create mode 100644 apps/client/src/features/rundown/useEventIdSwapping.ts
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