MediaProcessing/apps/ui/web/src/app/page/ExplorePage.tsx
2025-03-19 18:48:38 +01:00

375 lines
10 KiB
TypeScript

import { useEffect, useState } from 'react';
import { UnixTimestamp } from '../features/UxTc';
import { Box, Button, IconButton, Typography, useTheme } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../store';
import { TableCellCustomizer, TablePropetyConfig, TableRowActionEvents } from '../features/table/table';
import { useStompClient } from 'react-stomp-hooks';
import { useWsSubscription } from '../ws/subscriptions';
import { updateItems } from '../store/explorer-slice';
import { setContextMenuPosition, setContextMenuVisible } from '../store/context-menu-slice';
import FolderIcon from '@mui/icons-material/Folder';
import IconForward from '@mui/icons-material/ArrowForwardIosRounded';
import IconHome from '@mui/icons-material/Home';
import { ExplorerItem, ExplorerCursor, ExplorerItemType } from '../../types';
import ContextMenu, { ContextMenuActionEvent, ContextMenuItem } from '../features/ContextMenu';
import { canConvert, canEncode, canExtract } from '../../fileUtil';
import SimpleTable from '../features/table/sortableTable';
import TagIcon from '@mui/icons-material/Tag';
const createTableCell: TableCellCustomizer<ExplorerItem> = (accessor, data) => {
switch (accessor) {
case "created": {
if (typeof data[accessor] === "number") {
const timestampObject = { timestamp: data[accessor] as number }; // Opprett et objekt med riktig struktur
return UnixTimestamp(timestampObject);
} else {
return null;
}
}
case "extension": {
if (data[accessor] === null) {
return <FolderIcon sx={{ margin: 1 }} />
} else {
return <Typography>{data[accessor]}</Typography>
}
}
default: return null;
}
};
const columns: Array<TablePropetyConfig> = [
{ label: "Name", accessor: "name" },
{ label: "Format", accessor: "extension" },
{ label: "Created", accessor: "created" },
];
type Segment = {
name: string;
absolutePath: string;
};
function isWindowsPath(path: string): boolean {
return /^[A-Za-z]:\\/.test(path);
}
function getPartFor(path: string, index: number): string | null {
const separator = path.includes("/") ? "/" : "\\";
let parts: string[];
if (isWindowsPath(path)) {
parts = [path.slice(0, 3), ...path.slice(3).split(separator)];
} else if (path.startsWith(separator)) {
parts = [separator, ...path.slice(1).split(separator)];
} else {
parts = path.split(separator);
}
if (index < parts.length) {
// Unngå å legge til ekstra backslash for første element i Windows-path
if (isWindowsPath(path) && index === 0) {
return parts[0];
}
return parts.slice(0, index + 1).join(separator);
}
return null;
}
function getSegments(absolutePath: string): Array<Segment> {
if (!absolutePath) return [];
const isWindows = isWindowsPath(absolutePath);
const separator = isWindows ? "\\" : "/";
let parts: string[];
if (isWindows) {
parts = [absolutePath.slice(0, 3), ...absolutePath.slice(3).split(separator)];
} else if (absolutePath.startsWith(separator)) {
parts = [separator, ...absolutePath.slice(1).split(separator)];
} else {
parts = absolutePath.split(separator);
}
const segments = parts.map((value, index) => {
const name = isWindows && index === 0 ? value[0] : value;
return {
name: name.replaceAll(":", ""),
absolutePath: getPartFor(absolutePath, index)!
};
});
console.log({
segments: segments,
path: absolutePath
});
return segments.filter((segment) => segment.name !== "");
}
function getSegmentedNaviagatablePath(rootClick: () => void, navigateTo: (path: string | null) => void, path: string | null): JSX.Element {
const segments = getSegments(path!)
const utElements = segments.map((segment: Segment, index: number) => {
return (
<Box key={index} sx={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}>
<Button sx={{
borderRadius: 5,
textTransform: 'none'
}} onClick={() => navigateTo(segment.absolutePath!)}>
<Typography>{segment.name}</Typography>
</Button>
{ segments.length > 1 && index < segments.length - 1 && <IconForward fontSize="small" />}
</Box>
)
});
return (
<Box display="flex">
{isWindowsPath(path!) && (
<Box sx={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}>
<IconButton sx={{
borderRadius: 5,
textTransform: 'none'
}} onClick={() => rootClick()} >
<TagIcon />
</IconButton>
<IconForward fontSize="small" />
</Box>
)}
{utElements}
</Box>
)
}
function getContextMenuFileActionMenuItems(row: ExplorerItem | null): ContextMenuItem[] {
const ext = row?.extension;
const items: Array<ContextMenuItem> = [];
if (!ext) {return items;}
if (canEncode(ext) && canExtract(ext)) {
items.push({
actionIndex: 0,
icon: null,
text: "All available"
} as ContextMenuItem)
}
if (canEncode(ext)) {
items.push({
actionIndex: 1,
icon: null,
text: "Encode"
} as ContextMenuItem)
}
if (canExtract(ext)) {
items.push({
actionIndex: 2,
icon: null,
text: "Extract"
} as ContextMenuItem)
}
if (canConvert(ext)) {
items.push({
actionIndex: 3,
icon: null,
text: "Convert"
} as ContextMenuItem)
}
console.log(items);
return items;
}
interface ExplorerOperationRequest {
destination: string;
file: string;
source: string;
mode: "FLOW" | "MANUAL";
}
export default function ExplorePage() {
const muiTheme = useTheme();
const dispatch = useDispatch();
const client = useStompClient();
const cursor = useSelector((state: RootState) => state.explorer)
const [selectedRow, setSelectedRow] = useState<ExplorerItem|null>(null);
const [actionableItems, setActionableItems] = useState<Array<ContextMenuItem>>([]);
const navigateTo = (path: string | null) => {
console.log(path)
if (path) {
client?.publish({
destination: "/app/explorer/navigate",
body: path
})
}
}
const onItemSelectedEvent: TableRowActionEvents<ExplorerItem> = {
click: (row: ExplorerItem) => {
setSelectedRow(row)
},
doubleClick: (row: ExplorerItem) => {
console.log(row);
if (row.type === "FOLDER") {
navigateTo(row.path);
}
},
contextMenu: (row: ExplorerItem, x: number, y: number) => {
if (row.type === "FOLDER") {
return;
}
dispatch(setContextMenuVisible(true))
dispatch(setContextMenuPosition({x: x, y: y}))
setActionableItems(getContextMenuFileActionMenuItems(row))
}
}
const onContextMenuItemClickedEvent: ContextMenuActionEvent<ExplorerItem> = {
selected:(actionIndex: number | null, value: ExplorerItem | null) => {
if (!value) {
return;
}
const payload = (() => {
switch(actionIndex) {
case 0: {
return {
destination: "request/all",
file: value.path,
source: `Web UI @ ${window.location.href}`,
mode: "FLOW"
} as ExplorerOperationRequest
}
case 1: {
return {
destination: "request/encode",
file: value.path,
source: `Web UI @ ${window.location.href}`,
mode: "FLOW"
} as ExplorerOperationRequest
}
case 2: {
return {
destination: "request/extract",
file: value.path,
source: `Web UI @ ${window.location.href}`,
mode: "FLOW"
} as ExplorerOperationRequest
}
case 3: {
return {
destination: "request/convert",
file: value.path,
source: `Web UI @ ${window.location.href}`,
mode: "FLOW"
} as ExplorerOperationRequest
}
default: {
return null;
}
}
})();
if (payload) {
client?.publish({
destination: "/app/"+payload.destination,
body: JSON.stringify(payload)
})
}
}
}
const onHomeClick = () => {
client?.publish({
destination: "/app/explorer/home"
})
}
const onRootClick = () => {
client?.publish({
destination: "/app/explorer/root"
})
}
useWsSubscription<ExplorerCursor>("/topic/explorer/go", (response) => {
dispatch(updateItems(response))
});
useEffect(() => {
// Kjør din funksjon her når komponenten lastes inn for første gang
// Sjekk om cursor er null
if (cursor.path === null && client !== null) {
console.log(cursor)
// Kjør din funksjon her når cursor er null og client ikke er null
client?.publish({
destination: "/app/explorer/home"
});
// Alternativt, du kan dispatche en Redux handling her
// dispatch(fetchDataAction()); // Eksempel på å dispatche en handling
}
}, [cursor, client, dispatch]);
return (
<>
<Box display="block">
<Box sx={{
height: 50,
width: "100%",
maxHeight: "100%",
overflow: "hidden",
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
backgroundColor: muiTheme.palette.background.paper
}}>
<Box sx={{
display: "flex",
}}>
<Button onClick={onHomeClick} sx={{
borderRadius: 5
}}>
<IconHome />
</Button>
<Box sx={{
borderRadius: 5,
backgroundColor: muiTheme.palette.divider
}}>
{getSegmentedNaviagatablePath(onRootClick, navigateTo, cursor?.path)}
</Box>
</Box>
</Box>
<Box sx={{
display: "block",
height: "calc(100% - 120px)",
overflow: "hidden",
position: "absolute",
width: "100%"
}}>
<SimpleTable items={cursor?.items ?? []} columns={columns} customizer={createTableCell} onRowClickedEvent={onItemSelectedEvent} />
</Box>
</Box>
<ContextMenu row={selectedRow} actionItems={actionableItems} onContextMenuItemClicked={onContextMenuItemClickedEvent} />
</>
)
}