diff --git a/apps/ui/web/src/app/features/ContextMenu.tsx b/apps/ui/web/src/app/features/ContextMenu.tsx new file mode 100644 index 00000000..5ea85220 --- /dev/null +++ b/apps/ui/web/src/app/features/ContextMenu.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../store"; +import { setContextMenuVisible } from "../store/context-menu-slice"; +import { Box, Typography, useTheme } from "@mui/material"; + +export interface ContextMenuItem { + actionIndex: number, + icon: JSX.Element | null, + text: string +} + +type NullableContextMenuActionEvent = ContextMenuActionEvent | null; +export interface ContextMenuActionEvent { + selected: (actionIndex: number | null, value: T | null) => void; +} + +export default function ContextMenu({ actionItems, row = null, onContextMenuItemClicked} : { actionItems: ContextMenuItem[], row?: T | null, onContextMenuItemClicked?: ContextMenuActionEvent}) { + const muiTheme = useTheme(); + const state = useSelector((state: RootState) => state.contextMenu) + const dispatch = useDispatch(); + const [visible, setVisible] = useState(false); + const [position, setPosition] = useState({ top: 0, left: 0 }); + + useEffect(() => { + setVisible(state.visible) + const position = state.position + if (position) { + setPosition({top: position.y, left: position.x}) + } + }, [state]) + + useEffect(() => { + const handleClick = () => dispatch(setContextMenuVisible(false)); + window.addEventListener("click", handleClick); + return () => { + window.removeEventListener("click", handleClick); + }; + }, [dispatch]); + + return ( + <> + {visible && ( + + { actionItems.map((item, index) => ( + { + onContextMenuItemClicked?.selected(item.actionIndex, row) + }} > + {item.icon} + {item.text} + + ))} + {actionItems.length === 0 && ( + Nothing to do.. + )} + + )} + + ) +} \ No newline at end of file diff --git a/apps/ui/web/src/app/store/context-menu-slice.ts b/apps/ui/web/src/app/store/context-menu-slice.ts new file mode 100644 index 00000000..936e3b88 --- /dev/null +++ b/apps/ui/web/src/app/store/context-menu-slice.ts @@ -0,0 +1,31 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit" + +interface ContextMenuPosition { + x: number, + y: number +} + +interface ContextMenuState { + visible: boolean, + position?: ContextMenuPosition +} + +const initialState: ContextMenuState = { + visible: false +} + +const contextMenuSlice = createSlice({ + name: 'ContextMenu', + initialState, + reducers: { + setContextMenuVisible(state, action: PayloadAction) { + state.visible = action.payload + }, + setContextMenuPosition(state, action: PayloadAction) { + state.position = action.payload + } + } +}); + +export const { setContextMenuVisible, setContextMenuPosition } = contextMenuSlice.actions; +export default contextMenuSlice.reducer; \ No newline at end of file diff --git a/apps/ui/web/src/fileUtil.ts b/apps/ui/web/src/fileUtil.ts new file mode 100644 index 00000000..cbfa891e --- /dev/null +++ b/apps/ui/web/src/fileUtil.ts @@ -0,0 +1,33 @@ + + +export function canEncode(extension: string): boolean { + const supported = [ + "mkv", + "avi", + "mp4", + "wmv", + "webm", + "mov" + ] + const found = supported.find((item) => item === extension) + return (found) ? true : false; +} + +export function canExtract(extension: string): boolean { + const supported = [ + "mkv" + ] + const found = supported.find((item) => item === extension) + return (found) ? true : false; +} + +export function canConvert(extension: string): boolean { + const supported = [ + "ass", + "srt", + "vtt", + "smi" + ] + const found = supported.find((item) => item === extension) + return (found) ? true : false; +} \ No newline at end of file