-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1318 from dennis531/context-menu
Add context menu for cutting actions
- Loading branch information
Showing
5 changed files
with
214 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { Menu, MenuItem } from "@mui/material"; | ||
import React, { MouseEventHandler } from "react"; | ||
import { IconType } from "react-icons"; | ||
|
||
import { useTheme } from "../themes"; | ||
import { customIconStyle } from "../cssStyles"; | ||
|
||
export interface ContextMenuItem { | ||
name: string, | ||
action: MouseEventHandler, | ||
ariaLabel: string, | ||
icon?: IconType | React.FunctionComponent, | ||
hotKey?: string, | ||
} | ||
|
||
/** | ||
* Context menu component | ||
* | ||
* @param menuItems Menu items | ||
* @param children Content between the opening and the closing tag where the context menu should be triggered | ||
*/ | ||
export const ThemedContextMenu: React.FC<{ | ||
menuItems: ContextMenuItem[], | ||
children: React.ReactNode, | ||
}> = ({ | ||
menuItems, | ||
children, | ||
}) => { | ||
|
||
const theme = useTheme(); | ||
|
||
// Init state variables | ||
const [contextMenuPosition, setContextMenuPosition] = React.useState<{ | ||
left: number, | ||
top: number, | ||
} | null>(null); | ||
|
||
const handleContextMenu = (e: React.MouseEvent) => { | ||
e.preventDefault(); | ||
|
||
setContextMenuPosition(contextMenuPosition === null | ||
? { left: e.clientX + 5, top: e.clientY } | ||
: null // Prevent relocation of context menu outside the element | ||
); | ||
}; | ||
|
||
const handleClose = () => { | ||
setContextMenuPosition(null); | ||
}; | ||
|
||
/** | ||
* Handles the click on a menu item | ||
* | ||
* @param e mouse event | ||
* @param action menu item action | ||
*/ | ||
const handleAction = (e: React.MouseEvent, action: MouseEventHandler) => { | ||
action(e); | ||
|
||
// Immediately close menu after action | ||
handleClose(); | ||
}; | ||
|
||
const renderMenuItems = () => { | ||
return menuItems.map((menuItem, i) => ( | ||
<MenuItem | ||
key={i} | ||
onClick={e => handleAction(e, menuItem.action)} | ||
sx={{ | ||
fontFamily: "inherit", | ||
gap: "15px", | ||
}} | ||
aria-label={menuItem.ariaLabel} | ||
> | ||
{menuItem.icon && | ||
<menuItem.icon css={customIconStyle}/> | ||
} | ||
<div css={{ flexGrow: 1 }}> | ||
{menuItem.name} | ||
</div> | ||
{menuItem.hotKey && | ||
<div css={{ | ||
fontSize: "0.875em", | ||
opacity: 0.7, | ||
}}> | ||
{menuItem.hotKey} | ||
</div> | ||
} | ||
</MenuItem> | ||
)); | ||
}; | ||
|
||
return ( | ||
<div | ||
onContextMenu={handleContextMenu} | ||
> | ||
{children} | ||
|
||
<Menu | ||
open={contextMenuPosition !== null} | ||
onClose={handleClose} | ||
anchorReference="anchorPosition" | ||
anchorPosition={contextMenuPosition ?? undefined} | ||
slotProps={{ | ||
paper: { | ||
sx: { | ||
backgroundColor: `${theme.contextMenu}`, | ||
color: `${theme.text}`, | ||
}, | ||
}, | ||
}} | ||
transitionDuration={0} // Allow quick re-opening of menu elsewhere by double-clicking the secondary button | ||
> | ||
{ renderMenuItems() } | ||
</Menu> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import React from "react"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
import { LuChevronLeft, LuChevronRight, LuScissors, LuTrash } from "react-icons/lu"; | ||
import TrashRestore from "../img/trash-restore.svg?react"; | ||
|
||
import { ContextMenuItem, ThemedContextMenu } from "./ContextMenu"; | ||
import { KEYMAP, rewriteKeys } from "../globalKeys"; | ||
import { useAppDispatch, useAppSelector } from "../redux/store"; | ||
import { cut, markAsDeletedOrAlive, mergeLeft, mergeRight, selectIsCurrentSegmentAlive } from "../redux/videoSlice"; | ||
|
||
const CuttingActionsContextMenu: React.FC<{ | ||
children: React.ReactNode, | ||
}> = ({ | ||
children, | ||
}) => { | ||
|
||
const { t } = useTranslation(); | ||
|
||
// Init redux variables | ||
const dispatch = useAppDispatch(); | ||
const isCurrentSegmentAlive = useAppSelector(selectIsCurrentSegmentAlive); | ||
|
||
const cuttingContextMenuItems: ContextMenuItem[] = [ | ||
{ | ||
name: t("cuttingActions.cut-button"), | ||
action: () => dispatch(cut()), | ||
icon: LuScissors, | ||
hotKey: KEYMAP.cutting.cut.key, | ||
ariaLabel: t("cuttingActions.cut-tooltip-aria", { | ||
hotkeyName: rewriteKeys(KEYMAP.cutting.cut.key), | ||
}), | ||
}, | ||
{ | ||
name: isCurrentSegmentAlive ? t("cuttingActions.delete-button") : t("cuttingActions.restore-button"), | ||
action: () => dispatch(markAsDeletedOrAlive()), | ||
icon: isCurrentSegmentAlive ? LuTrash : TrashRestore, | ||
hotKey: KEYMAP.cutting.delete.key, | ||
ariaLabel: t("cuttingActions.delete-restore-tooltip-aria", { | ||
hotkeyName: rewriteKeys(KEYMAP.cutting.delete.key), | ||
}), | ||
}, | ||
{ | ||
name: t("cuttingActions.mergeLeft-button"), | ||
action: () => dispatch(mergeLeft()), | ||
icon: LuChevronLeft, | ||
hotKey: KEYMAP.cutting.mergeLeft.key, | ||
ariaLabel: t("cuttingActions.mergeLeft-tooltip-aria", { | ||
hotkeyName: rewriteKeys(KEYMAP.cutting.mergeLeft.key), | ||
}), | ||
}, | ||
{ | ||
name: t("cuttingActions.mergeRight-button"), | ||
action: () => dispatch(mergeRight()), | ||
icon: LuChevronRight, | ||
hotKey: KEYMAP.cutting.mergeRight.key, | ||
ariaLabel: t("cuttingActions.mergeRight-tooltip-aria", { | ||
hotkeyName: rewriteKeys(KEYMAP.cutting.mergeRight.key), | ||
}), | ||
}, | ||
]; | ||
|
||
return ( | ||
<ThemedContextMenu | ||
menuItems={cuttingContextMenuItems} | ||
> | ||
{children} | ||
</ThemedContextMenu> | ||
); | ||
}; | ||
|
||
export default CuttingActionsContextMenu; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters