From 7d8cb0bb455804fbe0fcabbcfd09b9d09a6336bf Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 7 Jan 2023 18:16:19 -0800 Subject: [PATCH] Refactor context menu handler into hook --- .../components/album-detail-content.tsx | 29 ++------------ .../albums/components/album-list-content.tsx | 28 +------------- .../components/album-artist-list-content.tsx | 31 +++------------ .../context-menu/context-menu-provider.tsx | 31 ++++++++++----- src/renderer/features/context-menu/events.ts | 5 +++ .../hooks/use-handle-context-menu.ts | 38 +++++++++++++++++++ src/renderer/features/context-menu/index.tsx | 1 + .../components/playlist-detail-content.tsx | 29 ++------------ .../playlist-detail-song-list-content.tsx | 28 +------------- .../components/playlist-list-content.tsx | 31 +++------------ .../songs/components/song-list-content.tsx | 28 +------------- 11 files changed, 88 insertions(+), 191 deletions(-) create mode 100644 src/renderer/features/context-menu/hooks/use-handle-context-menu.ts diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index b0278a1e..fb1b52a4 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -8,11 +8,10 @@ import { useFixedTableHeader, VirtualTable, } from '/@/renderer/components'; -import { CellContextMenuEvent, ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; +import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { Box, Group, Stack } from '@mantine/core'; import { useSetState } from '@mantine/hooks'; -import sortBy from 'lodash/sortBy'; import { RiHeartLine, RiMoreFill } from 'react-icons/ri'; import { useParams } from 'react-router'; import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query'; @@ -21,7 +20,7 @@ import styled from 'styled-components'; import { AppRoute } from '/@/renderer/router/routes'; import { useContainerQuery } from '/@/renderer/hooks'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { Play } from '/@/renderer/types'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { PlayButton, PLAY_TYPES } from '/@/renderer/features/shared'; @@ -136,29 +135,7 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => { }); }; - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: SONG_CONTEXT_MENU_ITEMS, - type: LibraryItem.SONG, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data) return; diff --git a/src/renderer/features/albums/components/album-list-content.tsx b/src/renderer/features/albums/components/album-list-content.tsx index 5abdbee5..0a5a99b7 100644 --- a/src/renderer/features/albums/components/album-list-content.tsx +++ b/src/renderer/features/albums/components/album-list-content.tsx @@ -29,7 +29,6 @@ import { import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { BodyScrollEvent, - CellContextMenuEvent, ColDef, GridReadyEvent, IDatasource, @@ -38,9 +37,8 @@ import { } from '@ag-grid-community/core'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import sortBy from 'lodash/sortBy'; import { generatePath, useNavigate } from 'react-router'; import { usePlayQueueAdd } from '/@/renderer/features/player'; @@ -261,29 +259,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont return rows; }, [page.filter.sortBy]); - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: ALBUM_CONTEXT_MENU_ITEMS, - type: LibraryItem.ALBUM, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu(LibraryItem.ALBUM, ALBUM_CONTEXT_MENU_ITEMS); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id })); diff --git a/src/renderer/features/artists/components/album-artist-list-content.tsx b/src/renderer/features/artists/components/album-artist-list-content.tsx index a1013946..4810d202 100644 --- a/src/renderer/features/artists/components/album-artist-list-content.tsx +++ b/src/renderer/features/artists/components/album-artist-list-content.tsx @@ -28,7 +28,6 @@ import { import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { BodyScrollEvent, - CellContextMenuEvent, ColDef, GridReadyEvent, IDatasource, @@ -37,9 +36,8 @@ import { } from '@ag-grid-community/core'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import sortBy from 'lodash/sortBy'; import { generatePath, useNavigate } from 'react-router'; import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query'; import { usePlayQueueAdd } from '/@/renderer/features/player'; @@ -250,29 +248,10 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon return rows; }, [page.filter.sortBy]); - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: ALBUM_CONTEXT_MENU_ITEMS, - type: LibraryItem.ALBUM_ARTIST, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu( + LibraryItem.ALBUM_ARTIST, + ALBUM_CONTEXT_MENU_ITEMS, + ); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { navigate(generatePath(AppRoute.LIBRARY_ALBUMARTISTS_DETAIL, { albumArtistId: e.data.id })); diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index aa965e6c..e66c27ef 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -34,15 +34,11 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const clickOutsideRef = useClickOutside(() => setOpened(false)); const viewport = useViewportSize(); const [ref, menuRect] = useResizeObserver(); - const [ctx, setCtx] = useSetState<{ - data: any[]; - menuItems: SetContextMenuItems; - type: LibraryItem; - xPos: number; - yPos: number; - }>({ + const [ctx, setCtx] = useSetState({ data: [], + dataNodes: [], menuItems: [], + tableRef: undefined, type: LibraryItem.SONG, xPos: 0, yPos: 0, @@ -51,7 +47,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const handlePlayQueueAdd = usePlayQueueAdd(); const openContextMenu = (args: OpenContextMenuProps) => { - const { xPos, yPos, menuItems, data, type } = args; + const { xPos, yPos, menuItems, data, type, tableRef, dataNodes } = args; const shouldReverseY = yPos + menuRect.height > viewport.height; const shouldReverseX = xPos + menuRect.width > viewport.width; @@ -59,12 +55,29 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const calculatedXPos = shouldReverseX ? xPos - menuRect.width : xPos; const calculatedYPos = shouldReverseY ? yPos - menuRect.height : yPos; - setCtx({ data, menuItems, type, xPos: calculatedXPos, yPos: calculatedYPos }); + setCtx({ + data, + dataNodes, + menuItems, + tableRef, + type, + xPos: calculatedXPos, + yPos: calculatedYPos, + }); setOpened(true); }; const closeContextMenu = () => { setOpened(false); + setCtx({ + data: [], + dataNodes: [], + menuItems: [], + tableRef: undefined, + type: LibraryItem.SONG, + xPos: 0, + yPos: 0, + }); }; useContextMenuEvents({ diff --git a/src/renderer/features/context-menu/events.ts b/src/renderer/features/context-menu/events.ts index eb0fa076..1385945f 100644 --- a/src/renderer/features/context-menu/events.ts +++ b/src/renderer/features/context-menu/events.ts @@ -1,9 +1,14 @@ +import { RowNode } from '@ag-grid-community/core'; +import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { createUseExternalEvents } from '@mantine/utils'; +import { MutableRefObject } from 'react'; import { LibraryItem } from '/@/renderer/api/types'; export type OpenContextMenuProps = { data: any[]; + dataNodes?: RowNode[]; menuItems: SetContextMenuItems; + tableRef?: MutableRefObject; type: LibraryItem; xPos: number; yPos: number; diff --git a/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts b/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts new file mode 100644 index 00000000..cbb18d70 --- /dev/null +++ b/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts @@ -0,0 +1,38 @@ +import { CellContextMenuEvent } from '@ag-grid-community/core'; +import { sortBy } from 'lodash'; +import { LibraryItem } from '/@/renderer/api/types'; +import { openContextMenu, SetContextMenuItems } from '/@/renderer/features/context-menu/events'; + +export const useHandleTableContextMenu = ( + itemType: LibraryItem, + contextMenuItems: SetContextMenuItems, +) => { + const handleContextMenu = (e: CellContextMenuEvent) => { + if (!e.event) return; + const clickEvent = e.event as MouseEvent; + clickEvent.preventDefault(); + + let selectedNodes = sortBy(e.api.getSelectedNodes(), ['rowIndex']); + let selectedRows = selectedNodes.map((node) => node.data); + + const shouldReplaceSelected = !selectedNodes.map((node) => node.data.id).includes(e.data.id); + + if (shouldReplaceSelected) { + e.api.deselectAll(); + e.node.setSelected(true); + selectedRows = [e.data]; + selectedNodes = e.api.getSelectedNodes(); + } + + openContextMenu({ + data: selectedRows, + dataNodes: selectedNodes, + menuItems: contextMenuItems, + type: itemType, + xPos: clickEvent.clientX, + yPos: clickEvent.clientY, + }); + }; + + return handleContextMenu; +}; diff --git a/src/renderer/features/context-menu/index.tsx b/src/renderer/features/context-menu/index.tsx index ddb6383a..adec7b07 100644 --- a/src/renderer/features/context-menu/index.tsx +++ b/src/renderer/features/context-menu/index.tsx @@ -1,2 +1,3 @@ export * from './events'; export * from './context-menu-provider'; +export * from './hooks/use-handle-context-menu'; diff --git a/src/renderer/features/playlists/components/playlist-detail-content.tsx b/src/renderer/features/playlists/components/playlist-detail-content.tsx index 18c42d2a..5eca9651 100644 --- a/src/renderer/features/playlists/components/playlist-detail-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-content.tsx @@ -1,9 +1,8 @@ -import { CellContextMenuEvent, ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; +import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { Box, Group } from '@mantine/core'; import { closeAllModals, openModal } from '@mantine/modals'; import { useQueryClient } from '@tanstack/react-query'; -import { sortBy } from 'lodash'; import { MutableRefObject, useMemo, useRef } from 'react'; import { RiMoreFill } from 'react-icons/ri'; import { generatePath, useNavigate, useParams } from 'react-router'; @@ -28,7 +27,7 @@ import { useFixedTableHeader, VirtualTable, } from '/@/renderer/components'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { usePlayQueueAdd } from '/@/renderer/features/player'; import { UpdatePlaylistForm } from '/@/renderer/features/playlists/components/update-playlist-form'; @@ -87,29 +86,7 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps) [page.table.columns], ); - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: SONG_CONTEXT_MENU_ITEMS, - type: LibraryItem.SONG, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS); const playlistSongData = useMemo( () => playlistSongsQueryInfinite.data?.pages.flatMap((p) => p.items), diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx index d5654346..eef06feb 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx @@ -1,7 +1,6 @@ import { MutableRefObject, useCallback, useMemo } from 'react'; import type { BodyScrollEvent, - CellContextMenuEvent, ColDef, GridReadyEvent, IDatasource, @@ -21,9 +20,8 @@ import { ListDisplayType } from '/@/renderer/types'; import { useQueryClient } from '@tanstack/react-query'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import sortBy from 'lodash/sortBy'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { LibraryItem, @@ -176,29 +174,7 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten setPagination(playlistId, { scrollOffset }); }; - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: SONG_CONTEXT_MENU_ITEMS, - type: LibraryItem.SONG, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data) return; diff --git a/src/renderer/features/playlists/components/playlist-list-content.tsx b/src/renderer/features/playlists/components/playlist-list-content.tsx index 27e821fd..33270f88 100644 --- a/src/renderer/features/playlists/components/playlist-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-list-content.tsx @@ -1,7 +1,6 @@ import { MutableRefObject, useCallback, useMemo } from 'react'; import type { BodyScrollEvent, - CellContextMenuEvent, ColDef, GridReadyEvent, IDatasource, @@ -29,9 +28,8 @@ import { import { ListDisplayType } from '/@/renderer/types'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { PLAYLIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import sortBy from 'lodash/sortBy'; import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query'; import { generatePath, useNavigate } from 'react-router'; import { AppRoute } from '/@/renderer/router/routes'; @@ -165,29 +163,10 @@ export const PlaylistListContent = ({ tableRef }: PlaylistListContentProps) => { setTable({ scrollOffset }); }; - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: PLAYLIST_CONTEXT_MENU_ITEMS, - type: LibraryItem.PLAYLIST, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu( + LibraryItem.PLAYLIST, + PLAYLIST_CONTEXT_MENU_ITEMS, + ); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data) return; diff --git a/src/renderer/features/songs/components/song-list-content.tsx b/src/renderer/features/songs/components/song-list-content.tsx index 2b547a98..2f73ee36 100644 --- a/src/renderer/features/songs/components/song-list-content.tsx +++ b/src/renderer/features/songs/components/song-list-content.tsx @@ -1,7 +1,6 @@ import { MutableRefObject, useCallback, useMemo } from 'react'; import type { BodyScrollEvent, - CellContextMenuEvent, ColDef, GridReadyEvent, IDatasource, @@ -29,9 +28,8 @@ import { import { ListDisplayType } from '/@/renderer/types'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; -import { openContextMenu } from '/@/renderer/features/context-menu'; +import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import sortBy from 'lodash/sortBy'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { LibraryItem, QueueSong } from '/@/renderer/api/types'; import { usePlayQueueAdd } from '/@/renderer/features/player'; @@ -160,29 +158,7 @@ export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) = setTable({ scrollOffset }); }; - const handleContextMenu = (e: CellContextMenuEvent) => { - if (!e.event) return; - const clickEvent = e.event as MouseEvent; - clickEvent.preventDefault(); - - const selectedNodes = e.api.getSelectedNodes(); - const selectedIds = selectedNodes.map((node) => node.data.id); - let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data); - - if (!selectedIds.includes(e.data.id)) { - e.api.deselectAll(); - e.node.setSelected(true); - selectedRows = [e.data]; - } - - openContextMenu({ - data: selectedRows, - menuItems: SONG_CONTEXT_MENU_ITEMS, - type: LibraryItem.SONG, - xPos: clickEvent.clientX, - yPos: clickEvent.clientY, - }); - }; + const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS); const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data) return;