mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
feat(thumb): generator settings and test button
This commit is contained in:
parent
bc47a382bd
commit
dc7f69cbfb
|
|
@ -213,7 +213,32 @@
|
|||
"officePreviewServiceSrcB64Des": " Base64 编码后的文件 URL",
|
||||
"officePreviewServiceName": "文件名",
|
||||
"thumbnails": "缩略图",
|
||||
"localOnlyInfo": "以下设置只针对本机存储策略有效。",
|
||||
"thumbnailDoc": "有关配置缩略图的更多信息,请参阅 <0>官方文档</0>。",
|
||||
"thumbnailDocLink":"https://docs.cloudreve.org/use/thumbnails",
|
||||
"thumbnailBasic": "基本设置",
|
||||
"generators": "生成器",
|
||||
"thumbMaxSize": "最大原始文件尺寸",
|
||||
"thumbMaxSizeDes": "可生成缩略图的最大原始文件的大小,超出此大小的文件不会生成缩略图",
|
||||
"generatorProxyWarning": "默认情况下,非本机存储策略只会使用“存储策略原生”生成器。你可以通过开启“生成器代理”功能扩展第三方存储策略的缩略图能力。",
|
||||
"policyBuiltin": "存储策略原生",
|
||||
"policyBuiltinDes": "使用存储提供方原生的图像处理接口。对于本机和 S3 策略,这一生成器不可用,将会自动顺沿其他生成器。对于其他存储策略,支持的原始图像格式和大小限制请参考 Cloudreve 文档。",
|
||||
"cloudreveBuiltin":"Cloudreve 内置",
|
||||
"cloudreveBuiltinDes": "使用 Cloudreve 内置的图像处理能力,仅支持 PNG、JPEG、GIF 格式的图片。",
|
||||
"libreOffice": "LibreOffice",
|
||||
"libreOfficeDes": "使用 LibreOffice 生成 Office 文档的缩略图。这一生成器依赖于任一其他图像生成器(Cloudreve 内置 或 VIPS)。",
|
||||
"vips": "VIPS",
|
||||
"vipsDes": "使用 libvips 处理缩略图图像,支持更多图像格式,资源消耗更低。",
|
||||
"thumbDependencyWarning": "LibreOffice 生成器依赖于 Cloudreve 内置 或 VIPS 生成器,请开启其中任一生成器。",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"ffmpegDes": "使用 FFmpeg 生成视频缩略图。",
|
||||
"executable": "可执行文件",
|
||||
"executableDes": "第三方生成器可执行文件的地址或命令",
|
||||
"executableTest": "测试",
|
||||
"executableTestSuccess": "生成器正常,版本:{{version}}",
|
||||
"generatorExts": "可用扩展名",
|
||||
"generatorExtsDes": "此生成器可用的文件扩展名列表,多个请使用半角逗号 , 隔开",
|
||||
"ffmpegSeek": "缩略图截取位置",
|
||||
"ffmpegSeekDes": "定义缩略图截取的时间,推荐选择较小值以加速生成过程。如果超出视频实际长度,会导致缩略图截取失败。",
|
||||
"thumbWidth": "缩略图宽度",
|
||||
"thumbHeight": "缩略图高度",
|
||||
"thumbSuffix": "缩略图文件后缀",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import FormControlLabel from "@material-ui/core/FormControlLabel";
|
|||
import Switch from "@material-ui/core/Switch";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import ThumbGenerators from "./ThumbGenerators";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
|
|
@ -58,6 +59,20 @@ export default function ImageSetting() {
|
|||
wopi_enabled: "0",
|
||||
wopi_endpoint: "",
|
||||
wopi_session_timeout: "0",
|
||||
thumb_builtin_enabled: "0",
|
||||
thumb_vips_enabled: "0",
|
||||
thumb_vips_exts: "",
|
||||
thumb_ffmpeg_enabled: "0",
|
||||
thumb_vips_path: "",
|
||||
thumb_ffmpeg_path: "",
|
||||
thumb_ffmpeg_exts: "",
|
||||
thumb_ffmpeg_seek: "",
|
||||
thumb_libreoffice_path: "",
|
||||
thumb_libreoffice_enabled: "0",
|
||||
thumb_libreoffice_exts: "",
|
||||
thumb_proxy_enabled: "0",
|
||||
thumb_proxy_policy: "[]",
|
||||
thumb_max_src_size: "",
|
||||
});
|
||||
|
||||
const handleChange = (name) => (event) => {
|
||||
|
|
@ -388,12 +403,26 @@ export default function ImageSetting() {
|
|||
<Typography variant="h6" gutterBottom>
|
||||
{t("thumbnails")}
|
||||
</Typography>
|
||||
<div className={classes.form}>
|
||||
<Alert severity="info">
|
||||
<Trans
|
||||
ns={"dashboard"}
|
||||
i18nKey={"settings.thumbnailDoc"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
target={"_blank"}
|
||||
href={t("thumbnailDocLink")}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</Alert>
|
||||
</div>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{t("thumbnailBasic")}
|
||||
</Typography>
|
||||
|
||||
<div className={classes.formContainer}>
|
||||
<div className={classes.form}>
|
||||
<Alert severity="info">{t("localOnlyInfo")}</Alert>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
|
|
@ -510,6 +539,26 @@ export default function ImageSetting() {
|
|||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
{options.thumb_max_src_size !== "" && (
|
||||
<SizeInput
|
||||
value={options.thumb_max_src_size}
|
||||
onChange={handleChange(
|
||||
"thumb_max_src_size"
|
||||
)}
|
||||
required
|
||||
min={0}
|
||||
max={2147483647}
|
||||
label={t("thumbMaxSize")}
|
||||
/>
|
||||
)}
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("thumbMaxSizeDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
|
|
@ -529,6 +578,24 @@ export default function ImageSetting() {
|
|||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{t("generators")}
|
||||
</Typography>
|
||||
<div className={classes.formContainer}>
|
||||
<div className={classes.form}>
|
||||
<Alert severity="info">
|
||||
{t("generatorProxyWarning")}
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<ThumbGenerators
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.root}>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default function Mail() {
|
|||
|
||||
const sendTestMail = () => {
|
||||
setLoading(true);
|
||||
API.post("/admin/mailTest", {
|
||||
API.post("/admin/test/mail", {
|
||||
to: tesInput,
|
||||
})
|
||||
.then(() => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,248 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Accordion from "@material-ui/core/Accordion";
|
||||
import AccordionSummary from "@material-ui/core/AccordionSummary";
|
||||
import AccordionDetails from "@material-ui/core/AccordionDetails";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import { Button, TextField } from "@material-ui/core";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import API from "../../../middleware/Api";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
width: "100%",
|
||||
},
|
||||
secondaryHeading: {
|
||||
fontSize: theme.typography.pxToRem(15),
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
column: {
|
||||
flexBasis: "33.33%",
|
||||
},
|
||||
details: {
|
||||
display: "block",
|
||||
},
|
||||
}));
|
||||
|
||||
const generators = [
|
||||
{
|
||||
name: "policyBuiltin",
|
||||
des: "policyBuiltinDes",
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
name: "libreOffice",
|
||||
des: "libreOfficeDes",
|
||||
enableFlag: "thumb_libreoffice_enabled",
|
||||
executableSetting: "thumb_libreoffice_path",
|
||||
inputs: [
|
||||
{
|
||||
name: "thumb_libreoffice_exts",
|
||||
label: "generatorExts",
|
||||
des: "generatorExtsDes",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "vips",
|
||||
des: "vipsDes",
|
||||
enableFlag: "thumb_vips_enabled",
|
||||
executableSetting: "thumb_vips_path",
|
||||
inputs: [
|
||||
{
|
||||
name: "thumb_vips_exts",
|
||||
label: "generatorExts",
|
||||
des: "generatorExtsDes",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "ffmpeg",
|
||||
des: "ffmpegDes",
|
||||
enableFlag: "thumb_ffmpeg_enabled",
|
||||
executableSetting: "thumb_ffmpeg_path",
|
||||
inputs: [
|
||||
{
|
||||
name: "thumb_ffmpeg_exts",
|
||||
label: "generatorExts",
|
||||
des: "generatorExtsDes",
|
||||
},
|
||||
{
|
||||
name: "thumb_ffmpeg_seek",
|
||||
label: "ffmpegSeek",
|
||||
des: "ffmpegSeekDes",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "cloudreveBuiltin",
|
||||
des: "cloudreveBuiltinDes",
|
||||
enableFlag: "thumb_builtin_enabled",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ThumbGenerators({ options, setOptions }) {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChange = (name) => (event) => {
|
||||
setOptions({
|
||||
...options,
|
||||
[name]: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const testExecutable = (name, executable) => {
|
||||
setLoading(true);
|
||||
API.post("/admin/test/thumb", {
|
||||
name,
|
||||
executable,
|
||||
})
|
||||
.then((response) => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
t("executableTestSuccess", { version: response.data }),
|
||||
"success"
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleEnableChange = (name) => (event) => {
|
||||
const newOpts = {
|
||||
...options,
|
||||
[name]: event.target.checked ? "1" : "0",
|
||||
};
|
||||
setOptions(newOpts);
|
||||
|
||||
if (
|
||||
newOpts["thumb_libreoffice_enabled"] === "1" &&
|
||||
newOpts["thumb_builtin_enabled"] === "0" &&
|
||||
newOpts["thumb_vips_enabled"] === "0"
|
||||
) {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"center",
|
||||
t("thumbDependencyWarning"),
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{generators.map((generator) => (
|
||||
<Accordion key={generator.name}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-label="Expand"
|
||||
aria-controls="additional-actions1-content"
|
||||
id="additional-actions1-header"
|
||||
>
|
||||
<FormControlLabel
|
||||
aria-label="Acknowledge"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onFocus={(event) => event.stopPropagation()}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
generator.readOnly ||
|
||||
options[generator.enableFlag] === "1"
|
||||
}
|
||||
onChange={handleEnableChange(
|
||||
generator.enableFlag
|
||||
)}
|
||||
/>
|
||||
}
|
||||
label={t(generator.name)}
|
||||
disabled={generator.readOnly}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails className={classes.details}>
|
||||
<Typography color="textSecondary">
|
||||
{t(generator.des)}
|
||||
</Typography>
|
||||
{generator.executableSetting && (
|
||||
<FormControl margin="normal" fullWidth>
|
||||
<TextField
|
||||
label={t("executable")}
|
||||
variant="outlined"
|
||||
value={options[generator.executableSetting]}
|
||||
onChange={handleChange(
|
||||
generator.executableSetting
|
||||
)}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<Button
|
||||
disabled={loading}
|
||||
onClick={() =>
|
||||
testExecutable(
|
||||
generator.name,
|
||||
options[
|
||||
generator
|
||||
.executableSetting
|
||||
]
|
||||
)
|
||||
}
|
||||
color="primary"
|
||||
>
|
||||
{t("executableTest")}
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("executableDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
{generator.inputs &&
|
||||
generator.inputs.map((input) => (
|
||||
<FormControl
|
||||
key={input.name}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
>
|
||||
<TextField
|
||||
label={t(input.label)}
|
||||
variant="outlined"
|
||||
value={options[input.name]}
|
||||
onChange={handleChange(input.name)}
|
||||
required={!!input.required}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t(input.des)}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue