UI
This commit is contained in:
parent
98ca3e239f
commit
b9a10e7585
64
apps/ui/web/src/app/features/ContextMenu.tsx
Normal file
64
apps/ui/web/src/app/features/ContextMenu.tsx
Normal file
@ -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<T> = ContextMenuActionEvent<T> | null;
|
||||||
|
export interface ContextMenuActionEvent<T> {
|
||||||
|
selected: (actionIndex: number | null, value: T | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContextMenu<T>({ actionItems, row = null, onContextMenuItemClicked} : { actionItems: ContextMenuItem[], row?: T | null, onContextMenuItemClicked?: ContextMenuActionEvent<T>}) {
|
||||||
|
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 && (
|
||||||
|
<Box className="contextmenu" sx={{
|
||||||
|
top: position.top,
|
||||||
|
left: position.left,
|
||||||
|
backgroundColor: muiTheme.palette.action.selected,
|
||||||
|
}}>
|
||||||
|
{ actionItems.map((item, index) => (
|
||||||
|
<Box className="contextMenuItem" display="flex" key={index} onClick={() => {
|
||||||
|
onContextMenuItemClicked?.selected(item.actionIndex, row)
|
||||||
|
}} >
|
||||||
|
{item.icon}
|
||||||
|
<Typography>{item.text}</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{actionItems.length === 0 && (
|
||||||
|
<Typography>Nothing to do..</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
31
apps/ui/web/src/app/store/context-menu-slice.ts
Normal file
31
apps/ui/web/src/app/store/context-menu-slice.ts
Normal file
@ -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<boolean>) {
|
||||||
|
state.visible = action.payload
|
||||||
|
},
|
||||||
|
setContextMenuPosition(state, action: PayloadAction<ContextMenuPosition>) {
|
||||||
|
state.position = action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setContextMenuVisible, setContextMenuPosition } = contextMenuSlice.actions;
|
||||||
|
export default contextMenuSlice.reducer;
|
||||||
33
apps/ui/web/src/fileUtil.ts
Normal file
33
apps/ui/web/src/fileUtil.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user