375 lines
10 KiB
TypeScript
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} />
|
|
</>
|
|
)
|
|
}
|
|
|