import { ExpandMoreRounded } from "@mui/icons-material"; import { AccordionDetails, Box, Link, ListItemIcon, Menu, Table, TableBody, TableContainer, TableHead, TableRow, Typography, } from "@mui/material"; import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; import * as React from "react"; import { memo, useCallback, useMemo, useState } from "react"; import { DndProvider, useDrag, useDrop } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { useTranslation } from "react-i18next"; import { Viewer, ViewerGroup, ViewerType } from "../../../../api/explorer.ts"; import { uuidv4 } from "../../../../util"; import { NoWrapTableCell, SecondaryButton } from "../../../Common/StyledComponents.tsx"; import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; import Add from "../../../Icons/Add.tsx"; import DesktopFlow from "../../../Icons/DesktopFlow.tsx"; import DocumentDataLink from "../../../Icons/DocumentDataLink.tsx"; import { AccordionSummary, StyledAccordion } from "../../Settings/UserSession/SSOSettings.tsx"; import FileViewerEditDialog from "./FileViewerEditDialog.tsx"; import FileViewerRow from "./FileViewerRow.tsx"; import ImportWopiDialog from "./ImportWopiDialog.tsx"; interface ViewerGroupProps { group: ViewerGroup; index: number; onDelete: (e: React.MouseEvent) => void; onGroupChange: (g: ViewerGroup) => void; dndType: string; } const DND_TYPE = "viewer-row"; const DraggableViewerRow = memo(function DraggableViewerRow({ viewer, index, moveRow, onChange, onDelete, onMoveUp, onMoveDown, isLast, isFirst, dndType, }: any) { const ref = React.useRef(null); const [, drop] = useDrop({ accept: dndType, hover(item: any, monitor) { if (!ref.current) return; const dragIndex = item.index; const hoverIndex = index; if (dragIndex === hoverIndex) return; const hoverBoundingRect = ref.current.getBoundingClientRect(); const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; const clientOffset = monitor.getClientOffset(); if (!clientOffset) return; const hoverClientY = clientOffset.y - hoverBoundingRect.top; if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; moveRow(dragIndex, hoverIndex); item.index = hoverIndex; }, }); const [{ isDragging }, drag] = useDrag({ type: dndType, item: { index }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }); drag(drop(ref)); return ( ); }); const ViewerGroupRow = memo(({ group, index, onDelete, onGroupChange, dndType }: ViewerGroupProps) => { const { t } = useTranslation("dashboard"); const onViewerChange = useMemo(() => { return group.viewers.map((_, index) => (vChanged: Viewer) => { onGroupChange({ viewers: group.viewers.map((v, i) => (i == index ? vChanged : v)), }); }); }, [group.viewers]); const onViewerDeleted = useMemo(() => { return group.viewers.map((_, index) => (e: React.MouseEvent) => { onGroupChange({ viewers: group.viewers.filter((_, i) => i != index), }); e.preventDefault(); e.stopPropagation(); }); }, [group.viewers]); const [viewers, setViewers] = useState(group.viewers); React.useEffect(() => { setViewers(group.viewers); }, [group.viewers]); const moveRow = useCallback( (from: number, to: number) => { if (from === to) return; const updated = [...viewers]; const [moved] = updated.splice(from, 1); updated.splice(to, 0, moved); setViewers(updated); onGroupChange({ viewers: updated }); }, [viewers, onGroupChange], ); const handleMoveUp = (idx: number) => { if (idx <= 0) return; moveRow(idx, idx - 1); }; const handleMoveDown = (idx: number) => { if (idx >= viewers.length - 1) return; moveRow(idx, idx + 1); }; return ( }> {t("settings.viewerGroupTitle", { index: index + 1 })} {index > 0 && ( {t("policy.delete")} )} {t("settings.icon")} {t("settings.viewerType")} {t("settings.displayName")} {t("settings.exts")} {t("settings.viewerPlatform")} {t("settings.newFileAction")} {t("settings.viewerEnabled")} {t("settings.actions")} {viewers.map((viewer, idx) => ( handleMoveUp(idx)} onMoveDown={() => handleMoveDown(idx)} isFirst={idx === 0} isLast={idx === viewers.length - 1} dndType={dndType} /> ))}
); }); export interface FileViewerListProps { config: string; onChange: (value: string) => void; } const FileViewerList = memo(({ config, onChange }: FileViewerListProps) => { const { t } = useTranslation("dashboard"); const addNewPopupState = usePopupState({ variant: "popover", popupId: "addNewViewer", }); const [createNewOpen, setCreateNewOpen] = useState(false); const [newViewer, setNewViewer] = useState(undefined); const [importOpen, setImportOpen] = useState(false); const configParsed = useMemo((): ViewerGroup[] => JSON.parse(config), [config]); const onNewViewerChange = useCallback( (v: Viewer) => { setNewViewer(v); const newViewerSetting = [...configParsed]; newViewerSetting[0].viewers.push(v); onChange(JSON.stringify(newViewerSetting)); }, [configParsed], ); const onGroupDelete = useMemo(() => { return configParsed.map((_, index) => (e: React.MouseEvent) => { onChange(JSON.stringify([...configParsed].filter((_, i) => i != index))); e.preventDefault(); e.stopPropagation(); }); }, [configParsed]); const onGroupChange = useMemo(() => { return configParsed.map((_, index) => (g: ViewerGroup) => { onChange(JSON.stringify([...configParsed].map((item, i) => (i == index ? g : item)))); }); }, [configParsed]); const { onClose, ...menuProps } = bindMenu(addNewPopupState); const onCreateNewClosed = useCallback(() => { setCreateNewOpen(false); }, []); const openCreateNew = useCallback(() => { setNewViewer({ id: uuidv4(), icon: "", type: ViewerType.custom, display_name: "", exts: [], }); setCreateNewOpen(true); onClose(); }, [onClose, setNewViewer]); const openImportNew = useCallback(() => { setImportOpen(true); onClose(); }, [onClose, setImportOpen]); const onImportedNew = useCallback( (v: ViewerGroup) => { const newViewerSetting = [...configParsed]; newViewerSetting.push(v); onChange(JSON.stringify(newViewerSetting)); }, [configParsed], ); return ( } sx={{ mb: 1 }}> {t("settings.addViewer")} {configParsed?.length > 0 && configParsed.map((item: ViewerGroup, index) => ( ))} {t("settings.embeddedWebpageViewer")} {t("settings.wopiViewer")} {newViewer && ( )} setImportOpen(false)} open={importOpen} /> ); }); export default FileViewerList;