mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-26 04:02:47 +00:00
425 lines
16 KiB
TypeScript
425 lines
16 KiB
TypeScript
import {
|
|
DialogContent,
|
|
FormControlLabel,
|
|
IconButton,
|
|
Link,
|
|
ListItemText,
|
|
Switch,
|
|
Table,
|
|
TableBody,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
useTheme,
|
|
} from "@mui/material";
|
|
import FormControl from "@mui/material/FormControl";
|
|
import Grid from "@mui/material/Grid2";
|
|
import { useSnackbar } from "notistack";
|
|
import React, { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react";
|
|
import { Trans, useTranslation } from "react-i18next";
|
|
import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
|
|
import { builtInViewers } from "../../../../../redux/thunks/viewer.ts";
|
|
import { isTrueVal } from "../../../../../session/utils.ts";
|
|
import CircularProgress from "../../../../Common/CircularProgress.tsx";
|
|
import SizeInput from "../../../../Common/SizeInput.tsx";
|
|
import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar.tsx";
|
|
import {
|
|
DenseFilledTextField,
|
|
DenseSelect,
|
|
NoWrapTableCell,
|
|
SecondaryButton,
|
|
StyledTableContainerPaper,
|
|
} from "../../../../Common/StyledComponents.tsx";
|
|
import DraggableDialog from "../../../../Dialogs/DraggableDialog.tsx";
|
|
import { SquareMenuItem } from "../../../../FileManager/ContextMenu/ContextMenu.tsx";
|
|
import { ViewerIDWithDefaultIcons } from "../../../../FileManager/Dialogs/OpenWith.tsx";
|
|
import Add from "../../../../Icons/Add.tsx";
|
|
import Dismiss from "../../../../Icons/Dismiss.tsx";
|
|
import SettingForm from "../../../../Pages/Setting/SettingForm.tsx";
|
|
import MagicVarDialog, { MagicVar } from "../../../Common/MagicVarDialog.tsx";
|
|
import { NoMarginHelperText } from "../../Settings.tsx";
|
|
|
|
const MonacoEditor = lazy(() => import("../../../../Viewers/CodeViewer/MonacoEditor.tsx"));
|
|
|
|
export interface FileViewerEditDialogProps {
|
|
viewer: Viewer;
|
|
onChange: (viewer: Viewer) => void;
|
|
open: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const magicVars: MagicVar[] = [
|
|
{
|
|
name: "{$src}",
|
|
value: "settings.srcEncodedVar",
|
|
example: "https%3A%2F%2Fcloudreve.org%2Fapi%2Fv4%2Ffile%2Fcontent%2FzOie%2F0%2Ftext.txt%3Fsign%3Dxxx",
|
|
},
|
|
{
|
|
name: "{$src_raw}",
|
|
value: "settings.srcVar",
|
|
example: "https://cloudreve.org/api/v4/file/content/zOie/0/text.txt?sign=xxx",
|
|
},
|
|
{
|
|
name: "{$src_raw_base64}",
|
|
value: "settings.srcBase64Var",
|
|
example: "aHR0cHM6Ly9jbG91ZHJldmUub3JnL2FwaS92NC9maWxlL2NvbnRlbnQvek9pZS8wL3RleHQudHh0P3NpZ249eHh4",
|
|
},
|
|
{
|
|
name: "{$name}",
|
|
value: "settings.nameEncodedVar",
|
|
example: "sampleFile%5B1%5D.txt",
|
|
},
|
|
{
|
|
name: "{$version}",
|
|
value: "settings.versionEntityVar",
|
|
example: "zOie",
|
|
},
|
|
{
|
|
name: "{$id}",
|
|
value: "settings.fileIdVar",
|
|
example: "jm8AF8",
|
|
},
|
|
{
|
|
name: "{$user_id}",
|
|
value: "settings.userIdVar",
|
|
example: "lpua",
|
|
},
|
|
{
|
|
name: "{$user_display_name}",
|
|
value: "settings.userDisplayNameVar",
|
|
example: "Aaron%20Liu",
|
|
},
|
|
];
|
|
|
|
const FileViewerEditDialog = ({ viewer, onChange, open, onClose }: FileViewerEditDialogProps) => {
|
|
const { t } = useTranslation("dashboard");
|
|
const theme = useTheme();
|
|
const { enqueueSnackbar } = useSnackbar();
|
|
const [viewerShadowed, setViewerShadowed] = useState<Viewer | undefined>(undefined);
|
|
const formRef = React.useRef<HTMLFormElement>(null);
|
|
const [magicVarOpen, setMagicVarOpen] = useState(false);
|
|
const [wopiCached, setWopiCached] = useState("");
|
|
const withDefaultIcon = useMemo(() => {
|
|
return ViewerIDWithDefaultIcons.includes(viewer.id);
|
|
}, [viewer.id]);
|
|
|
|
useEffect(() => {
|
|
setViewerShadowed({ ...viewer });
|
|
setWopiCached("");
|
|
}, [viewer, setWopiCached, setViewerShadowed]);
|
|
|
|
const onSubmit = useCallback(() => {
|
|
if (formRef.current && !formRef.current.checkValidity()) {
|
|
formRef.current.reportValidity();
|
|
return;
|
|
}
|
|
|
|
let changed = viewerShadowed;
|
|
if (!viewerShadowed || !changed) {
|
|
return;
|
|
}
|
|
|
|
if (wopiCached != "") {
|
|
try {
|
|
const parsed = JSON.parse(wopiCached);
|
|
changed = { ...viewerShadowed, wopi_actions: parsed };
|
|
setViewerShadowed({ ...changed });
|
|
} catch (e) {
|
|
enqueueSnackbar({
|
|
message: t("settings.invalidWopiActionMapping"),
|
|
variant: "warning",
|
|
action: DefaultCloseAction,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
onChange(changed);
|
|
onClose();
|
|
}, [viewerShadowed, wopiCached, formRef]);
|
|
|
|
const openMagicVar = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
|
setMagicVarOpen(true);
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}, []);
|
|
|
|
if (!viewerShadowed) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<DraggableDialog
|
|
title={t("settings.editViewerTitle", {
|
|
name: t(viewer.display_name, { ns: "application" }),
|
|
})}
|
|
showActions
|
|
showCancel
|
|
onAccept={onSubmit}
|
|
dialogProps={{
|
|
fullWidth: true,
|
|
maxWidth: "lg",
|
|
open,
|
|
onClose,
|
|
}}
|
|
>
|
|
<DialogContent>
|
|
<MagicVarDialog open={magicVarOpen} vars={magicVars} onClose={() => setMagicVarOpen(false)} />
|
|
<form ref={formRef}>
|
|
<Grid spacing={2} container>
|
|
<SettingForm noContainer lgWidth={6} title={t("settings.iconUrl")}>
|
|
<DenseFilledTextField
|
|
fullWidth
|
|
required={!withDefaultIcon}
|
|
value={viewerShadowed.icon}
|
|
onChange={(e) => {
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
icon: e.target.value,
|
|
}));
|
|
}}
|
|
/>
|
|
{withDefaultIcon && <NoMarginHelperText>{t("settings.builtInIconUrlDes")}</NoMarginHelperText>}
|
|
</SettingForm>
|
|
<SettingForm noContainer lgWidth={6} title={t("settings.displayName")}>
|
|
<DenseFilledTextField
|
|
fullWidth
|
|
required
|
|
value={viewerShadowed.display_name}
|
|
onChange={(e) => {
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
display_name: e.target.value,
|
|
}));
|
|
}}
|
|
/>
|
|
<NoMarginHelperText>{t("settings.displayNameDes")}</NoMarginHelperText>
|
|
</SettingForm>
|
|
<SettingForm noContainer lgWidth={6} title={t("settings.exts")}>
|
|
<DenseFilledTextField
|
|
fullWidth
|
|
multiline
|
|
required
|
|
value={viewerShadowed.exts.join()}
|
|
onChange={(e) =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
exts: e.target.value.split(",").map((ext) => ext.trim()),
|
|
}))
|
|
}
|
|
/>
|
|
</SettingForm>
|
|
{viewer.type == ViewerType.custom && (
|
|
<SettingForm noContainer lgWidth={6} title={t("settings.viewerUrl")}>
|
|
<DenseFilledTextField
|
|
fullWidth
|
|
required
|
|
value={viewerShadowed.url}
|
|
onChange={(e) =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
url: e.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
<NoMarginHelperText>
|
|
<Trans
|
|
i18nKey={"settings.viewerUrlDes"}
|
|
ns={"dashboard"}
|
|
components={[<Link onClick={openMagicVar} href={"#"} />]}
|
|
/>
|
|
</NoMarginHelperText>
|
|
</SettingForm>
|
|
)}
|
|
<SettingForm noContainer lgWidth={6} title={t("settings.maxSize")}>
|
|
<FormControl fullWidth>
|
|
<SizeInput
|
|
variant={"outlined"}
|
|
required
|
|
allowZero={true}
|
|
value={viewerShadowed.max_size ?? 0}
|
|
onChange={(e) =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
max_size: e ? e : undefined,
|
|
}))
|
|
}
|
|
/>
|
|
<NoMarginHelperText>{t("settings.maxSizeDes")}</NoMarginHelperText>
|
|
</FormControl>
|
|
</SettingForm>
|
|
{viewer.type == ViewerType.custom && (
|
|
<SettingForm noContainer lgWidth={6}>
|
|
<FormControlLabel
|
|
control={
|
|
<Switch
|
|
checked={isTrueVal(viewerShadowed.props?.openInNew ?? "")}
|
|
onChange={(e) =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
props: {
|
|
...(v?.props ?? {}),
|
|
openInNew: e.target.checked.toString(),
|
|
},
|
|
}))
|
|
}
|
|
/>
|
|
}
|
|
label={t("settings.openInNew")}
|
|
/>
|
|
<NoMarginHelperText>{t("settings.openInNewDes")}</NoMarginHelperText>
|
|
</SettingForm>
|
|
)}
|
|
{viewer.id == builtInViewers.drawio && (
|
|
<SettingForm noContainer title={t("settings.drawioHost")} lgWidth={6}>
|
|
<DenseFilledTextField
|
|
fullWidth
|
|
required
|
|
value={viewerShadowed.props?.host ?? ""}
|
|
onChange={(e) =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
props: {
|
|
...(v?.props ?? {}),
|
|
host: e.target.value,
|
|
},
|
|
}))
|
|
}
|
|
/>
|
|
<NoMarginHelperText>{t("settings.drawioHostDes")}</NoMarginHelperText>
|
|
</SettingForm>
|
|
)}
|
|
{viewer.type == ViewerType.wopi && (
|
|
<SettingForm noContainer title={t("settings.woapiActionMapping")} lgWidth={12}>
|
|
<Suspense fallback={<CircularProgress />}>
|
|
<MonacoEditor
|
|
theme={theme.palette.mode === "dark" ? "vs-dark" : "vs"}
|
|
value={wopiCached == "" ? JSON.stringify(viewerShadowed.wopi_actions, null, 4) : wopiCached}
|
|
height={"300px"}
|
|
minHeight={"300px"}
|
|
language={"json"}
|
|
onChange={(e) => setWopiCached(e as string)}
|
|
/>
|
|
</Suspense>
|
|
</SettingForm>
|
|
)}
|
|
<SettingForm noContainer title={t("settings.newFileAction")} lgWidth={12}>
|
|
{viewerShadowed?.templates && viewerShadowed.templates.length > 0 && (
|
|
<TableContainer sx={{ mt: 1, maxHeight: 440 }} component={StyledTableContainerPaper}>
|
|
<Table
|
|
stickyHeader
|
|
sx={{
|
|
width: "100%",
|
|
maxHeight: 300,
|
|
tableLayout: "fixed",
|
|
}}
|
|
size="small"
|
|
>
|
|
<TableHead>
|
|
<TableRow>
|
|
<NoWrapTableCell width={100}>{t("settings.ext")}</NoWrapTableCell>
|
|
<NoWrapTableCell width={200}>{t("settings.displayName")}</NoWrapTableCell>
|
|
<NoWrapTableCell width={64}></NoWrapTableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{viewerShadowed.templates?.map((t, i) => (
|
|
<TableRow
|
|
key={i}
|
|
sx={{
|
|
"&:last-child td, &:last-child th": { border: 0 },
|
|
}}
|
|
hover
|
|
>
|
|
<NoWrapTableCell>
|
|
<DenseSelect
|
|
value={t.ext}
|
|
required
|
|
onChange={(e) => {
|
|
const newExt = e.target.value as string;
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
templates: (v?.templates ?? []).map((template, index) =>
|
|
index == i ? { ...template, ext: newExt } : template,
|
|
),
|
|
}));
|
|
}}
|
|
>
|
|
{viewerShadowed.exts.map((ext) => (
|
|
<SquareMenuItem value={ext}>
|
|
<ListItemText
|
|
slotProps={{
|
|
primary: {
|
|
variant: "body2",
|
|
},
|
|
}}
|
|
>
|
|
{ext}
|
|
</ListItemText>
|
|
</SquareMenuItem>
|
|
))}
|
|
</DenseSelect>
|
|
</NoWrapTableCell>
|
|
<NoWrapTableCell>
|
|
<DenseFilledTextField
|
|
fullWidth
|
|
required
|
|
value={t.display_name}
|
|
onChange={(e) => {
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
templates: (v?.templates ?? []).map((template, index) =>
|
|
index == i
|
|
? {
|
|
...template,
|
|
display_name: e.target.value,
|
|
}
|
|
: template,
|
|
),
|
|
}));
|
|
}}
|
|
/>
|
|
</NoWrapTableCell>
|
|
<NoWrapTableCell>
|
|
<IconButton
|
|
size={"small"}
|
|
onClick={() =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
templates: (v?.templates ?? []).filter((_, index) => index != i),
|
|
}))
|
|
}
|
|
>
|
|
<Dismiss fontSize={"small"} />
|
|
</IconButton>
|
|
</NoWrapTableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
)}
|
|
<SecondaryButton
|
|
sx={{ mt: 1 }}
|
|
variant={"contained"}
|
|
startIcon={<Add />}
|
|
onClick={() =>
|
|
setViewerShadowed((v) => ({
|
|
...(v as Viewer),
|
|
templates: [...(v?.templates ?? []), { ext: viewerShadowed.exts?.[0] ?? "", display_name: "" }],
|
|
}))
|
|
}
|
|
>
|
|
{t("settings.addNewFileAction")}
|
|
</SecondaryButton>
|
|
<NoMarginHelperText>{t("settings.newFileActionDes")}</NoMarginHelperText>
|
|
</SettingForm>
|
|
</Grid>
|
|
</form>
|
|
</DialogContent>
|
|
</DraggableDialog>
|
|
);
|
|
};
|
|
|
|
export default FileViewerEditDialog;
|