diff --git a/src/component/FileManager/ContextMenu/ContextMenu.tsx b/src/component/FileManager/ContextMenu/ContextMenu.tsx index ba87167..d7e74c9 100644 --- a/src/component/FileManager/ContextMenu/ContextMenu.tsx +++ b/src/component/FileManager/ContextMenu/ContextMenu.tsx @@ -1,20 +1,10 @@ -import { - Box, - Divider, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - styled, - Typography, - useTheme, -} from "@mui/material"; -import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; -import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; -import useActionDisplayOpt from "./useActionDisplayOpt.ts"; +import { Box, Divider, ListItemIcon, ListItemText, Menu, MenuItem, styled, Typography, useTheme } from "@mui/material"; import { useCallback, useMemo } from "react"; -import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; import { useTranslation } from "react-i18next"; +import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; +import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { downloadFiles } from "../../../redux/thunks/download.ts"; import { batchGetDirectLinks, createNew, @@ -30,44 +20,40 @@ import { renameFile, restoreFile, } from "../../../redux/thunks/file.ts"; -import RenameOutlined from "../../Icons/RenameOutlined.tsx"; -import BinFullOutlined from "../../Icons/BinFullOutlined.tsx"; -import { CascadingSubmenu } from "./CascadingMenu.tsx"; -import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; -import CopyOutlined from "../../Icons/CopyOutlined.tsx"; -import Tag from "../../Icons/Tag.tsx"; -import TagMenuItems from "./TagMenuItems.tsx"; -import Download from "../../Icons/Download.tsx"; -import ShareOutlined from "../../Icons/ShareOutlined.tsx"; -import FolderLink from "../../Icons/FolderLink.tsx"; -import { downloadFiles } from "../../../redux/thunks/download.ts"; -import Info from "../../Icons/Info.tsx"; -import WrenchSettings from "../../Icons/WrenchSettings.tsx"; -import Open from "../../Icons/Open.tsx"; +import { refreshFileList, uploadClicked, uploadFromClipboard } from "../../../redux/thunks/filemanager.ts"; import { openViewers } from "../../../redux/thunks/viewer.ts"; import AppFolder from "../../Icons/AppFolder.tsx"; -import OrganizeMenuItems from "./OrganizeMenuItems.tsx"; -import MoreMenuItems from "./MoreMenuItems.tsx"; -import OpenWithMenuItems from "./OpenWithMenuItems.tsx"; -import { - refreshFileList, - uploadClicked, - uploadFromClipboard, -} from "../../../redux/thunks/filemanager.ts"; -import ArrowSync from "../../Icons/ArrowSync.tsx"; -import FolderAdd from "../../Icons/FolderAdd.tsx"; -import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts"; -import FileAdd from "../../Icons/FileAdd.tsx"; -import NewFileTemplateMenuItems from "./NewFileTemplateMenuItems.tsx"; -import Upload from "../../Icons/Upload.tsx"; -import FolderArrowUp from "../../Icons/FolderArrowUp.tsx"; -import { SelectType } from "../../Uploader/core"; -import Clipboard from "../../Icons/Clipboard.tsx"; import ArchiveArrow from "../../Icons/ArchiveArrow.tsx"; +import ArrowSync from "../../Icons/ArrowSync.tsx"; +import BinFullOutlined from "../../Icons/BinFullOutlined.tsx"; +import Clipboard from "../../Icons/Clipboard.tsx"; import CloudDownloadOutlined from "../../Icons/CloudDownloadOutlined.tsx"; +import CopyOutlined from "../../Icons/CopyOutlined.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import Download from "../../Icons/Download.tsx"; import Enter from "../../Icons/Enter.tsx"; +import FileAdd from "../../Icons/FileAdd.tsx"; +import FolderAdd from "../../Icons/FolderAdd.tsx"; +import FolderArrowUp from "../../Icons/FolderArrowUp.tsx"; +import FolderLink from "../../Icons/FolderLink.tsx"; import FolderOutlined from "../../Icons/FolderOutlined.tsx"; +import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; +import Info from "../../Icons/Info.tsx"; import LinkOutlined from "../../Icons/LinkOutlined.tsx"; +import Open from "../../Icons/Open.tsx"; +import RenameOutlined from "../../Icons/RenameOutlined.tsx"; +import ShareOutlined from "../../Icons/ShareOutlined.tsx"; +import Tag from "../../Icons/Tag.tsx"; +import Upload from "../../Icons/Upload.tsx"; +import WrenchSettings from "../../Icons/WrenchSettings.tsx"; +import { SelectType } from "../../Uploader/core"; +import { CascadingSubmenu } from "./CascadingMenu.tsx"; +import MoreMenuItems from "./MoreMenuItems.tsx"; +import NewFileTemplateMenuItems from "./NewFileTemplateMenuItems.tsx"; +import OpenWithMenuItems from "./OpenWithMenuItems.tsx"; +import OrganizeMenuItems from "./OrganizeMenuItems.tsx"; +import TagMenuItems from "./TagMenuItems.tsx"; +import useActionDisplayOpt from "./useActionDisplayOpt.ts"; export const SquareMenu = styled(Menu)(() => ({ "& .MuiPaper-root": { @@ -75,13 +61,11 @@ export const SquareMenu = styled(Menu)(() => ({ }, })); -export const SquareMenuItem = styled(MenuItem)<{ hoverColor?: string }>( - ({ theme, hoverColor }) => ({ - "&:hover .MuiListItemIcon-root": { - color: hoverColor ?? theme.palette.primary.main, - }, - }), -); +export const SquareMenuItem = styled(MenuItem)<{ hoverColor?: string }>(({ theme, hoverColor }) => ({ + "&:hover .MuiListItemIcon-root": { + color: hoverColor ?? theme.palette.primary.main, + }, +})); export const DenseDivider = styled(Divider)(() => ({ margin: "4px 0 !important", @@ -90,14 +74,9 @@ export const DenseDivider = styled(Divider)(() => ({ export const EmptyMenu = () => { const { t } = useTranslation(); return ( - + - - {t("fileManager.noActionsCanBeDone")} - + {t("fileManager.noActionsCanBeDone")} ); }; @@ -110,44 +89,29 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const theme = useTheme(); - const contextMenuOpen = useAppSelector( - (state) => state.fileManager[fmIndex].contextMenuOpen, - ); - const contextMenuType = useAppSelector( - (state) => state.fileManager[fmIndex].contextMenuType, - ); - const contextMenuPos = useAppSelector( - (state) => state.fileManager[fmIndex].contextMenuPos, - ); - const selected = useAppSelector( - (state) => state.fileManager[fmIndex].selected, - ); - const targetOverwrite = useAppSelector( - (state) => state.fileManager[fmIndex].contextMenuTargets, - ); + const contextMenuOpen = useAppSelector((state) => state.fileManager[fmIndex].contextMenuOpen); + const contextMenuType = useAppSelector((state) => state.fileManager[fmIndex].contextMenuType); + const contextMenuPos = useAppSelector((state) => state.fileManager[fmIndex].contextMenuPos); + const selected = useAppSelector((state) => state.fileManager[fmIndex].selected); + const targetOverwrite = useAppSelector((state) => state.fileManager[fmIndex].contextMenuTargets); const targets = useMemo(() => { const targetsMap = targetOverwrite ?? selected; return Object.keys(targetsMap).map((key) => targetsMap[key]); }, [targetOverwrite, selected]); - const parent = useAppSelector( - (state) => state.fileManager[fmIndex].list?.parent, - ); + const parent = useAppSelector((state) => state.fileManager[fmIndex].list?.parent); - const displayOpt = useActionDisplayOpt( - targets, - contextMenuType, - parent, - fmIndex, - ); + const displayOpt = useActionDisplayOpt(targets, contextMenuType, parent, fmIndex); const onClose = useCallback(() => { dispatch(closeContextMenu({ index: fmIndex, value: undefined })); }, [dispatch]); + const showOpenWithCascading = displayOpt.showOpenWithCascading && displayOpt.showOpenWithCascading(); const showOpenWith = displayOpt.showOpenWith && displayOpt.showOpenWith(); let part1 = displayOpt.showOpen || + showOpenWithCascading || showOpenWith || displayOpt.showEnter || displayOpt.showDownload || @@ -163,16 +127,9 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { displayOpt.showCopy || displayOpt.showDirectLink; let part3 = - displayOpt.showTags || - displayOpt.showOrganize || - displayOpt.showMore || - displayOpt.showNewFileFromTemplate; - let part4 = - displayOpt.showInfo || - displayOpt.showGoToParent || - displayOpt.showGoToSharedLink; - let part5 = - displayOpt.showRestore || displayOpt.showDelete || displayOpt.showRefresh; + displayOpt.showTags || displayOpt.showOrganize || displayOpt.showMore || displayOpt.showNewFileFromTemplate; + let part4 = displayOpt.showInfo || displayOpt.showGoToParent || displayOpt.showGoToSharedLink; + let part5 = displayOpt.showRestore || displayOpt.showDelete || displayOpt.showRefresh; const showDivider1 = part1 && part2; const showDivider2 = part2 && part3; const showDivider3 = part3 && part4; @@ -181,15 +138,11 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { const part1Elements = part1 ? ( <> {displayOpt.showUpload && ( - dispatch(uploadClicked(0, SelectType.File))} - > + dispatch(uploadClicked(0, SelectType.File))}> - - {t("application:fileManager.uploadFiles")} - + {t("application:fileManager.uploadFiles")} )} {displayOpt.showEnter && ( @@ -201,15 +154,11 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showUpload && ( - dispatch(uploadClicked(0, SelectType.Directory))} - > + dispatch(uploadClicked(0, SelectType.Directory))}> - - {t("application:fileManager.uploadFolder")} - + {t("application:fileManager.uploadFolder")} )} {displayOpt.showUpload && ( @@ -217,9 +166,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { - - {t("application:uploader.uploadFromClipboard")} - + {t("application:uploader.uploadFromClipboard")} )} {displayOpt.showRemoteDownload && ( @@ -227,22 +174,18 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { - - {t("application:fileManager.newRemoteDownloads")} - + {t("application:fileManager.newRemoteDownloads")} )} {displayOpt.showOpen && ( - dispatch(openViewers(fmIndex, targets[0]))} - > + dispatch(openViewers(fmIndex, targets[0]))}> {t("application:fileManager.open")} )} - {showOpenWith && ( + {showOpenWithCascading && ( { )} + {showOpenWith && ( + dispatch(openViewers(fmIndex, targets[0], targets[0].size, undefined, true))}> + + + + {t("application:fileManager.openWith")} + + )} {displayOpt.showDownload && ( - dispatch(downloadFiles(fmIndex, targets))} - > + dispatch(downloadFiles(fmIndex, targets))}> @@ -262,27 +211,19 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showExtractArchive && ( - dispatch(extractArchive(fmIndex, targets[0]))} - > + dispatch(extractArchive(fmIndex, targets[0]))}> - - {t("application:fileManager.extractArchive")} - + {t("application:fileManager.extractArchive")} )} {displayOpt.showTorrentRemoteDownload && ( - dispatch(newRemoteDownload(fmIndex, targets[0]))} - > + dispatch(newRemoteDownload(fmIndex, targets[0]))}> - - {t("application:fileManager.createRemoteDownloadForTorrent")} - + {t("application:fileManager.createRemoteDownloadForTorrent")} )} @@ -291,11 +232,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { const part2Elements = part2 ? ( <> {displayOpt.showCreateFolder && ( - - dispatch(createNew(fmIndex, CreateNewDialogType.folder)) - } - > + dispatch(createNew(fmIndex, CreateNewDialogType.folder))}> @@ -303,9 +240,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showCreateFile && ( - dispatch(createNew(fmIndex, CreateNewDialogType.file))} - > + dispatch(createNew(fmIndex, CreateNewDialogType.file))}> @@ -313,9 +248,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showShare && ( - dispatch(openShareDialog(fmIndex, targets[0]))} - > + dispatch(openShareDialog(fmIndex, targets[0]))}> @@ -323,9 +256,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showRename && ( - dispatch(renameFile(fmIndex, targets[0]))} - > + dispatch(renameFile(fmIndex, targets[0]))}> @@ -333,9 +264,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showCopy && ( - dispatch(dialogBasedMoveCopy(fmIndex, targets, true))} - > + dispatch(dialogBasedMoveCopy(fmIndex, targets, true))}> @@ -343,15 +272,11 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showDirectLink && ( - dispatch(batchGetDirectLinks(fmIndex, targets))} - > + dispatch(batchGetDirectLinks(fmIndex, targets))}> - - {t("application:fileManager.getSourceLink")} - + {t("application:fileManager.getSourceLink")} )} @@ -360,11 +285,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { const part3Elements = part3 ? ( <> {displayOpt.showTags && ( - } - > + }> )} @@ -386,24 +307,18 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} - {displayOpt.showNewFileFromTemplate && ( - - )} + {displayOpt.showNewFileFromTemplate && } ) : undefined; const part4Elements = part4 ? ( <> {displayOpt.showGoToSharedLink && ( - dispatch(goToSharedLink(fmIndex, targets[0]))} - > + dispatch(goToSharedLink(fmIndex, targets[0]))}> - - {t("application:fileManager.goToSharedLink")} - + {t("application:fileManager.goToSharedLink")} )} {displayOpt.showGoToParent && ( @@ -411,21 +326,15 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { - - {t("application:fileManager.openParentFolder")} - + {t("application:fileManager.openParentFolder")} )} {displayOpt.showInfo && ( - dispatch(openSidebar(fmIndex, targets[0]))} - > + dispatch(openSidebar(fmIndex, targets[0]))}> - - {t("application:fileManager.viewDetails")} - + {t("application:fileManager.viewDetails")} )} @@ -442,10 +351,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { )} {displayOpt.showDelete && ( - dispatch(deleteFile(fmIndex, targets))} - > + dispatch(deleteFile(fmIndex, targets))}> @@ -463,13 +369,9 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { ) : undefined; - const allParts = [ - part1Elements, - part2Elements, - part3Elements, - part4Elements, - part5Elements, - ].filter((p) => p != undefined); + const allParts = [part1Elements, part2Elements, part3Elements, part4Elements, part5Elements].filter( + (p) => p != undefined, + ); return ( boolean; showOpenWith?: () => boolean; showDownload?: boolean; showGoToSharedLink?: boolean; @@ -228,7 +229,13 @@ export const getActionOpt = ( display.orCapability && (currentUserAnonymous?.group?.direct_link_batch_size ?? 0) >= targets.length && display.orCapability.enabled(NavigatorCapability.download_file); - display.showOpen = targets.length == 1 && display.hasFile && display.showDownload && !!viewerSetting; + display.showOpen = + targets.length == 1 && + display.hasFile && + display.showDownload && + !!viewerSetting && + !!firstFileSuffix && + !!viewerSetting?.[firstFileSuffix]; display.showEnter = targets.length == 1 && display.hasFolder && @@ -249,11 +256,13 @@ export const getActionOpt = ( groupBs.enabled(GroupPermission.remote_download) && firstFileSuffix == "torrent"; - display.showOpenWith = () => false; + display.showOpenWithCascading = () => false; + display.showOpenWith = () => targets.length == 1 && !!display.hasFile && !!display.showDownload; if (display.showOpen) { - display.showOpen = !!firstFileSuffix && !!viewerSetting?.[firstFileSuffix]; + display.showOpenWithCascading = () => + !!(display.showOpen && viewerSetting && viewerSetting[firstFileSuffix ?? ""]?.length >= 1); display.showOpenWith = () => - !!(display.showOpen && viewerSetting && viewerSetting[firstFileSuffix ?? ""]?.length > 1); + !!(display.showOpen && viewerSetting && viewerSetting[firstFileSuffix ?? ""]?.length < 1); } display.showOrganize = display.showPin || display.showMove || display.showChangeFolderColor || display.showChangeIcon; display.showGoToSharedLink = diff --git a/src/component/FileManager/Dialogs/OpenWith.tsx b/src/component/FileManager/Dialogs/OpenWith.tsx index 732092f..cfc9a43 100644 --- a/src/component/FileManager/Dialogs/OpenWith.tsx +++ b/src/component/FileManager/Dialogs/OpenWith.tsx @@ -1,4 +1,3 @@ -import { useTranslation } from "react-i18next"; import { Avatar, Box, @@ -12,25 +11,24 @@ import { ListItemText, Stack, } from "@mui/material"; -import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import React, { useCallback, useEffect, useMemo, useState } from "react"; -import DraggableDialog, { - StyledDialogContentText, -} from "../../Dialogs/DraggableDialog.tsx"; -import { closeViewerSelector } from "../../../redux/globalStateSlice.ts"; -import { fileExtension } from "../../../util"; +import { useTranslation } from "react-i18next"; import { Viewer, ViewerType } from "../../../api/explorer.ts"; -import { builtInViewers, openViewer } from "../../../redux/thunks/viewer.ts"; -import Image from "../../Icons/Image.tsx"; -import AutoHeight from "../../Common/AutoHeight.tsx"; -import Markdown from "../../Icons/Markdown.tsx"; -import DocumentPDF from "../../Icons/DocumentPDF.tsx"; -import Book from "../../Icons/Book.tsx"; -import MusicNote1 from "../../Icons/MusicNote1.tsx"; -import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import { closeViewerSelector } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { ViewersByID } from "../../../redux/siteConfigSlice.ts"; +import { builtInViewers, openViewer } from "../../../redux/thunks/viewer.ts"; import SessionManager, { UserSettings } from "../../../session"; +import { fileExtension } from "../../../util"; +import AutoHeight from "../../Common/AutoHeight.tsx"; import { SecondaryButton } from "../../Common/StyledComponents.tsx"; +import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; +import Book from "../../Icons/Book.tsx"; +import DocumentPDF from "../../Icons/DocumentPDF.tsx"; +import Image from "../../Icons/Image.tsx"; +import Markdown from "../../Icons/Markdown.tsx"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import MusicNote1 from "../../Icons/MusicNote1.tsx"; export interface ViewerIconProps { viewer: Viewer; @@ -48,11 +46,7 @@ export const ViewerIDWithDefaultIcons = [ builtInViewers.markdown, ]; -export const ViewerIcon = ({ - viewer, - size = 32, - py = 0.5, -}: ViewerIconProps) => { +export const ViewerIcon = ({ viewer, size = 32, py = 0.5 }: ViewerIconProps) => { const BuiltinIcons = useMemo(() => { if (viewer.icon) { return undefined; @@ -63,23 +57,18 @@ export const ViewerIcon = ({ case builtInViewers.image: return ; case builtInViewers.pdf: - return ( - - ); + return ; case builtInViewers.epub: return ; case builtInViewers.music: - return ( - - ); + return ; case builtInViewers.markdown: return ( - theme.palette.mode == "dark" ? "#cbcbcb" : "#383838", + color: (theme) => (theme.palette.mode == "dark" ? "#cbcbcb" : "#383838"), }} /> ); @@ -106,18 +95,14 @@ export const ViewerIcon = ({ const OpenWith = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const [selectedViewer, setSelectedViewer] = React.useState( - null, - ); + const [selectedViewer, setSelectedViewer] = React.useState(null); const [expanded, setExpanded] = useState(false); - const selectorState = useAppSelector( - (state) => state.globalState.viewerSelector, - ); + const selectorState = useAppSelector((state) => state.globalState.viewerSelector); useEffect(() => { if (selectorState?.open) { - setExpanded(false); + setExpanded(!selectorState.viewers); setSelectedViewer(null); } }, [selectorState]); @@ -140,10 +125,7 @@ const OpenWith = () => { } if (always) { - SessionManager.set( - UserSettings.OpenWithPrefix + ext, - viewer?.id ?? selectedViewer?.id, - ); + SessionManager.set(UserSettings.OpenWithPrefix + ext, viewer?.id ?? selectedViewer?.id); } dispatch( @@ -157,6 +139,15 @@ const OpenWith = () => { dispatch(closeViewerSelector()); }; + const onViewerClick = (viewer: Viewer) => { + if (selectorState?.viewers) { + setSelectedViewer(viewer); + } else { + // For files without matching viewers, open the selected viewer without asking for preference + openWith(false, viewer); + } + }; + return ( { overflow: "auto", }} > - {( - (expanded - ? Object.values(ViewersByID) - : selectorState?.viewers) ?? emptyViewer - ).map((viewer) => ( + {((expanded ? Object.values(ViewersByID) : selectorState?.viewers) ?? emptyViewer).map((viewer) => ( openWith(false, viewer)} - onClick={() => setSelectedViewer(viewer)} + onClick={() => onViewerClick(viewer)} > @@ -221,20 +208,12 @@ const OpenWith = () => { - openWith(true)} - > + openWith(true)}> {t("modals.always")} - openWith(false)} - > + openWith(false)}> {t("modals.justOnce")} diff --git a/src/redux/thunks/viewer.ts b/src/redux/thunks/viewer.ts index a366678..dabf2a7 100644 --- a/src/redux/thunks/viewer.ts +++ b/src/redux/thunks/viewer.ts @@ -78,25 +78,16 @@ export function openViewers( } const viewerOptions = Viewers[ext]; - if (!viewerOptions) { - return; - } - - if (viewerOptions.length > 1) { - // open viewer selection dialog - dispatch( - setViewerSelector({ - open: true, - file, - entitySize, - viewers: viewerOptions, - version: preferredVersion, - }), - ); - return; - } - - dispatch(openViewer(file, viewerOptions[0], entitySize, preferredVersion)); + // open viewer selection dialog + dispatch( + setViewerSelector({ + open: true, + file, + entitySize, + viewers: viewerOptions, + version: preferredVersion, + }), + ); }; }