feat(explorer): always show "Open with" in context menu (https://github.com/cloudreve/cloudreve/issues/2342)

This commit is contained in:
Aaron Liu 2025-06-22 10:55:58 +08:00
parent bb3a5f9908
commit 34adaeb5dc
4 changed files with 147 additions and 266 deletions

View File

@ -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 (
<Box
sx={{ py: 0.5, px: 1, display: "flex", alignItems: "center" }}
color={"text.secondary"}
>
<Box sx={{ py: 0.5, px: 1, display: "flex", alignItems: "center" }} color={"text.secondary"}>
<Info sx={{ mr: 1 }} />
<Typography variant="body2">
{t("fileManager.noActionsCanBeDone")}
</Typography>
<Typography variant="body2">{t("fileManager.noActionsCanBeDone")}</Typography>
</Box>
);
};
@ -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 && (
<SquareMenuItem
onClick={() => dispatch(uploadClicked(0, SelectType.File))}
>
<SquareMenuItem onClick={() => dispatch(uploadClicked(0, SelectType.File))}>
<ListItemIcon>
<Upload fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.uploadFiles")}
</ListItemText>
<ListItemText>{t("application:fileManager.uploadFiles")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showEnter && (
@ -201,15 +154,11 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showUpload && (
<SquareMenuItem
onClick={() => dispatch(uploadClicked(0, SelectType.Directory))}
>
<SquareMenuItem onClick={() => dispatch(uploadClicked(0, SelectType.Directory))}>
<ListItemIcon>
<FolderArrowUp fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.uploadFolder")}
</ListItemText>
<ListItemText>{t("application:fileManager.uploadFolder")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showUpload && (
@ -217,9 +166,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
<ListItemIcon>
<Clipboard fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:uploader.uploadFromClipboard")}
</ListItemText>
<ListItemText>{t("application:uploader.uploadFromClipboard")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showRemoteDownload && (
@ -227,22 +174,18 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
<ListItemIcon>
<CloudDownloadOutlined fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.newRemoteDownloads")}
</ListItemText>
<ListItemText>{t("application:fileManager.newRemoteDownloads")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showOpen && (
<SquareMenuItem
onClick={() => dispatch(openViewers(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(openViewers(fmIndex, targets[0]))}>
<ListItemIcon>
<Open fontSize="small" />
</ListItemIcon>
<ListItemText>{t("application:fileManager.open")}</ListItemText>
</SquareMenuItem>
)}
{showOpenWith && (
{showOpenWithCascading && (
<CascadingSubmenu
popupId={"openWith"}
title={t("application:fileManager.openWith")}
@ -251,10 +194,16 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
<OpenWithMenuItems displayOpt={displayOpt} targets={targets} />
</CascadingSubmenu>
)}
{showOpenWith && (
<SquareMenuItem onClick={() => dispatch(openViewers(fmIndex, targets[0], targets[0].size, undefined, true))}>
<ListItemIcon>
<AppFolder fontSize="small" />
</ListItemIcon>
<ListItemText>{t("application:fileManager.openWith")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showDownload && (
<SquareMenuItem
onClick={() => dispatch(downloadFiles(fmIndex, targets))}
>
<SquareMenuItem onClick={() => dispatch(downloadFiles(fmIndex, targets))}>
<ListItemIcon>
<Download fontSize="small" />
</ListItemIcon>
@ -262,27 +211,19 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showExtractArchive && (
<SquareMenuItem
onClick={() => dispatch(extractArchive(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(extractArchive(fmIndex, targets[0]))}>
<ListItemIcon>
<ArchiveArrow fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.extractArchive")}
</ListItemText>
<ListItemText>{t("application:fileManager.extractArchive")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showTorrentRemoteDownload && (
<SquareMenuItem
onClick={() => dispatch(newRemoteDownload(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(newRemoteDownload(fmIndex, targets[0]))}>
<ListItemIcon>
<CloudDownloadOutlined fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.createRemoteDownloadForTorrent")}
</ListItemText>
<ListItemText>{t("application:fileManager.createRemoteDownloadForTorrent")}</ListItemText>
</SquareMenuItem>
)}
</>
@ -291,11 +232,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
const part2Elements = part2 ? (
<>
{displayOpt.showCreateFolder && (
<SquareMenuItem
onClick={() =>
dispatch(createNew(fmIndex, CreateNewDialogType.folder))
}
>
<SquareMenuItem onClick={() => dispatch(createNew(fmIndex, CreateNewDialogType.folder))}>
<ListItemIcon>
<FolderAdd fontSize="small" />
</ListItemIcon>
@ -303,9 +240,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showCreateFile && (
<SquareMenuItem
onClick={() => dispatch(createNew(fmIndex, CreateNewDialogType.file))}
>
<SquareMenuItem onClick={() => dispatch(createNew(fmIndex, CreateNewDialogType.file))}>
<ListItemIcon>
<FileAdd fontSize="small" />
</ListItemIcon>
@ -313,9 +248,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showShare && (
<SquareMenuItem
onClick={() => dispatch(openShareDialog(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(openShareDialog(fmIndex, targets[0]))}>
<ListItemIcon>
<ShareOutlined fontSize="small" />
</ListItemIcon>
@ -323,9 +256,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showRename && (
<SquareMenuItem
onClick={() => dispatch(renameFile(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(renameFile(fmIndex, targets[0]))}>
<ListItemIcon>
<RenameOutlined fontSize="small" />
</ListItemIcon>
@ -333,9 +264,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showCopy && (
<SquareMenuItem
onClick={() => dispatch(dialogBasedMoveCopy(fmIndex, targets, true))}
>
<SquareMenuItem onClick={() => dispatch(dialogBasedMoveCopy(fmIndex, targets, true))}>
<ListItemIcon>
<CopyOutlined fontSize="small" />
</ListItemIcon>
@ -343,15 +272,11 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showDirectLink && (
<SquareMenuItem
onClick={() => dispatch(batchGetDirectLinks(fmIndex, targets))}
>
<SquareMenuItem onClick={() => dispatch(batchGetDirectLinks(fmIndex, targets))}>
<ListItemIcon>
<LinkOutlined fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.getSourceLink")}
</ListItemText>
<ListItemText>{t("application:fileManager.getSourceLink")}</ListItemText>
</SquareMenuItem>
)}
</>
@ -360,11 +285,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
const part3Elements = part3 ? (
<>
{displayOpt.showTags && (
<CascadingSubmenu
popupId={"tags"}
title={t("application:fileManager.tags")}
icon={<Tag fontSize="small" />}
>
<CascadingSubmenu popupId={"tags"} title={t("application:fileManager.tags")} icon={<Tag fontSize="small" />}>
<TagMenuItems displayOpt={displayOpt} targets={targets} />
</CascadingSubmenu>
)}
@ -386,24 +307,18 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
<MoreMenuItems displayOpt={displayOpt} targets={targets} />
</CascadingSubmenu>
)}
{displayOpt.showNewFileFromTemplate && (
<NewFileTemplateMenuItems displayOpt={displayOpt} targets={targets} />
)}
{displayOpt.showNewFileFromTemplate && <NewFileTemplateMenuItems displayOpt={displayOpt} targets={targets} />}
</>
) : undefined;
const part4Elements = part4 ? (
<>
{displayOpt.showGoToSharedLink && (
<SquareMenuItem
onClick={() => dispatch(goToSharedLink(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(goToSharedLink(fmIndex, targets[0]))}>
<ListItemIcon>
<FolderLink fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.goToSharedLink")}
</ListItemText>
<ListItemText>{t("application:fileManager.goToSharedLink")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showGoToParent && (
@ -411,21 +326,15 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
<ListItemIcon>
<FolderOutlined fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.openParentFolder")}
</ListItemText>
<ListItemText>{t("application:fileManager.openParentFolder")}</ListItemText>
</SquareMenuItem>
)}
{displayOpt.showInfo && (
<SquareMenuItem
onClick={() => dispatch(openSidebar(fmIndex, targets[0]))}
>
<SquareMenuItem onClick={() => dispatch(openSidebar(fmIndex, targets[0]))}>
<ListItemIcon>
<Info fontSize="small" />
</ListItemIcon>
<ListItemText>
{t("application:fileManager.viewDetails")}
</ListItemText>
<ListItemText>{t("application:fileManager.viewDetails")}</ListItemText>
</SquareMenuItem>
)}
</>
@ -442,10 +351,7 @@ const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => {
</SquareMenuItem>
)}
{displayOpt.showDelete && (
<SquareMenuItem
hoverColor={theme.palette.error.light}
onClick={() => dispatch(deleteFile(fmIndex, targets))}
>
<SquareMenuItem hoverColor={theme.palette.error.light} onClick={() => dispatch(deleteFile(fmIndex, targets))}>
<ListItemIcon>
<DeleteOutlined fontSize="small" />
</ListItemIcon>
@ -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 (
<SquareMenu

View File

@ -48,6 +48,7 @@ export interface DisplayOption {
showEnter?: boolean;
showOpen?: boolean;
showOpenWithCascading?: () => 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 =

View File

@ -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 <Image sx={{ width: size, height: size, color: "#d32f2f" }} />;
case builtInViewers.pdf:
return (
<DocumentPDF sx={{ width: size, height: size, color: "#f44336" }} />
);
return <DocumentPDF sx={{ width: size, height: size, color: "#f44336" }} />;
case builtInViewers.epub:
return <Book sx={{ width: size, height: size, color: "#81b315" }} />;
case builtInViewers.music:
return (
<MusicNote1 sx={{ width: size, height: size, color: "#651fff" }} />
);
return <MusicNote1 sx={{ width: size, height: size, color: "#651fff" }} />;
case builtInViewers.markdown:
return (
<Markdown
sx={{
width: size,
height: size,
color: (theme) =>
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<Viewer | null>(
null,
);
const [selectedViewer, setSelectedViewer] = React.useState<Viewer | null>(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 (
<DraggableDialog
title={t("application:fileManager.openWith")}
@ -183,16 +174,12 @@ const OpenWith = () => {
overflow: "auto",
}}
>
{(
(expanded
? Object.values(ViewersByID)
: selectorState?.viewers) ?? emptyViewer
).map((viewer) => (
{((expanded ? Object.values(ViewersByID) : selectorState?.viewers) ?? emptyViewer).map((viewer) => (
<ListItem
disablePadding
key={viewer.id}
onDoubleClick={() => openWith(false, viewer)}
onClick={() => setSelectedViewer(viewer)}
onClick={() => onViewerClick(viewer)}
>
<ListItemButton selected={viewer.id == selectedViewer?.id}>
<ListItemAvatar sx={{ minWidth: "48px" }}>
@ -221,20 +208,12 @@ const OpenWith = () => {
<Divider />
<Grid container spacing={2} sx={{ p: 2 }}>
<Grid md={6} xs={12} item>
<SecondaryButton
fullWidth
variant={"contained"}
onClick={() => openWith(true)}
>
<SecondaryButton fullWidth variant={"contained"} onClick={() => openWith(true)}>
{t("modals.always")}
</SecondaryButton>
</Grid>
<Grid md={6} xs={12} item>
<SecondaryButton
fullWidth
variant={"contained"}
onClick={() => openWith(false)}
>
<SecondaryButton fullWidth variant={"contained"} onClick={() => openWith(false)}>
{t("modals.justOnce")}
</SecondaryButton>
</Grid>

View File

@ -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,
}),
);
};
}