mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
chore: format code
This commit is contained in:
parent
34adaeb5dc
commit
27de5db3e7
|
|
@ -1973,7 +1973,6 @@ export function sendImport(req: ImportWorkflowService): ThunkResponse<TaskRespon
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export function sendPatchViewSync(args: PatchViewSyncService): ThunkResponse<void> {
|
||||
return async (dispatch, _getState) => {
|
||||
return await dispatch(
|
||||
|
|
|
|||
|
|
@ -8,15 +8,13 @@ import { DenseAutocomplete, DenseFilledTextField, NoWrapBox, SquareChip } from "
|
|||
import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx";
|
||||
import LinkDismiss from "../../Icons/LinkDismiss.tsx";
|
||||
|
||||
export interface SharesInputProps {
|
||||
}
|
||||
export interface SharesInputProps {}
|
||||
|
||||
const SharesInput = (props: SharesInputProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [options, setOptions] = useState<number[]>([]);
|
||||
|
||||
|
||||
return (
|
||||
<DenseAutocomplete
|
||||
multiple
|
||||
|
|
|
|||
|
|
@ -134,10 +134,7 @@ const FileForm = () => {
|
|||
{!fileParentLoading && (
|
||||
<>
|
||||
{fileParent && (
|
||||
<StyledFileBadge
|
||||
variant={"outlined"}
|
||||
simplifiedFile={{ path: fileParent, type: FileType.folder }}
|
||||
/>
|
||||
<StyledFileBadge variant={"outlined"} simplifiedFile={{ path: fileParent, type: FileType.folder }} />
|
||||
)}
|
||||
{!fileParent && "-"}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ import { useSnackbar } from "notistack";
|
|||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar";
|
||||
import { NoWrapTableCell, SecondaryButton, StyledCheckbox, StyledTableContainerPaper } from "../../../Common/StyledComponents";
|
||||
import {
|
||||
NoWrapTableCell,
|
||||
SecondaryButton,
|
||||
StyledCheckbox,
|
||||
StyledTableContainerPaper,
|
||||
} from "../../../Common/StyledComponents";
|
||||
import Add from "../../../Icons/Add";
|
||||
import Delete from "../../../Icons/Delete";
|
||||
import Edit from "../../../Icons/Edit";
|
||||
|
|
@ -79,30 +84,39 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
}
|
||||
}, [value]);
|
||||
|
||||
const handleSave = useCallback((newOptions: Record<string, ThemeOption["config"]>) => {
|
||||
onChange(JSON.stringify(newOptions));
|
||||
}, [onChange]);
|
||||
const handleSave = useCallback(
|
||||
(newOptions: Record<string, ThemeOption["config"]>) => {
|
||||
onChange(JSON.stringify(newOptions));
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleDelete = useCallback((id: string) => {
|
||||
// Prevent deleting the default theme
|
||||
if (id === defaultTheme) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.cannotDeleteDefaultTheme"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const handleDelete = useCallback(
|
||||
(id: string) => {
|
||||
// Prevent deleting the default theme
|
||||
if (id === defaultTheme) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.cannotDeleteDefaultTheme"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newOptions = { ...options };
|
||||
delete newOptions[id];
|
||||
handleSave(newOptions);
|
||||
}, [options, handleSave, defaultTheme, enqueueSnackbar, t]);
|
||||
const newOptions = { ...options };
|
||||
delete newOptions[id];
|
||||
handleSave(newOptions);
|
||||
},
|
||||
[options, handleSave, defaultTheme, enqueueSnackbar, t],
|
||||
);
|
||||
|
||||
const handleEdit = useCallback((id: string) => {
|
||||
setEditingOption({ id, config: options[id] });
|
||||
setIsDialogOpen(true);
|
||||
}, [options]);
|
||||
const handleEdit = useCallback(
|
||||
(id: string) => {
|
||||
setEditingOption({ id, config: options[id] });
|
||||
setIsDialogOpen(true);
|
||||
},
|
||||
[options],
|
||||
);
|
||||
|
||||
const handleAdd = useCallback(() => {
|
||||
// Generate a new default theme option with a random color
|
||||
|
|
@ -113,16 +127,16 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
light: {
|
||||
palette: {
|
||||
primary: { main: randomColor },
|
||||
secondary: { main: "#f50057" }
|
||||
}
|
||||
secondary: { main: "#f50057" },
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
primary: { main: randomColor },
|
||||
secondary: { main: "#f50057" }
|
||||
}
|
||||
}
|
||||
}
|
||||
secondary: { main: "#f50057" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
setIsDialogOpen(true);
|
||||
}, []);
|
||||
|
|
@ -132,15 +146,58 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
setEditingOption(null);
|
||||
}, []);
|
||||
|
||||
const handleDialogSave = useCallback((id: string, newId: string, config: string) => {
|
||||
try {
|
||||
const parsedConfig = JSON.parse(config);
|
||||
const handleDialogSave = useCallback(
|
||||
(id: string, newId: string, config: string) => {
|
||||
try {
|
||||
const parsedConfig = JSON.parse(config);
|
||||
const newOptions = { ...options };
|
||||
|
||||
// If ID has changed (primary color changed), delete the old entry and create a new one
|
||||
if (id !== newId) {
|
||||
// Check if the new ID already exists
|
||||
if (newOptions[newId]) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.duplicateThemeColor"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're changing the ID of the default theme, update the default theme reference
|
||||
if (id === defaultTheme) {
|
||||
onDefaultThemeChange(newId);
|
||||
}
|
||||
|
||||
delete newOptions[id];
|
||||
}
|
||||
|
||||
newOptions[newId] = parsedConfig;
|
||||
handleSave(newOptions);
|
||||
setIsDialogOpen(false);
|
||||
setEditingOption(null);
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
enqueueSnackbar({
|
||||
message: t("settings.invalidThemeConfig"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
}
|
||||
},
|
||||
[options, handleSave, enqueueSnackbar, defaultTheme, onDefaultThemeChange, t],
|
||||
);
|
||||
|
||||
const handleColorChange = useCallback(
|
||||
(id: string, type: "primary" | "secondary", mode: "light" | "dark", color: string) => {
|
||||
const newOptions = { ...options };
|
||||
|
||||
// If ID has changed (primary color changed), delete the old entry and create a new one
|
||||
if (id !== newId) {
|
||||
if (type === "primary" && mode === "light") {
|
||||
// If changing the primary color (which is the ID), we need to create a new entry
|
||||
const newId = color;
|
||||
|
||||
// Check if the new ID already exists
|
||||
if (newOptions[newId]) {
|
||||
if (newOptions[newId] && newId !== id) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.duplicateThemeColor"),
|
||||
variant: "warning",
|
||||
|
|
@ -149,67 +206,33 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
return;
|
||||
}
|
||||
|
||||
const config = { ...newOptions[id] };
|
||||
config[mode].palette[type].main = color;
|
||||
|
||||
// Delete old entry and create new one with the updated ID
|
||||
delete newOptions[id];
|
||||
newOptions[newId] = config;
|
||||
|
||||
// If we're changing the ID of the default theme, update the default theme reference
|
||||
if (id === defaultTheme) {
|
||||
onDefaultThemeChange(newId);
|
||||
}
|
||||
|
||||
delete newOptions[id];
|
||||
} else {
|
||||
// For other colors, just update the value
|
||||
newOptions[id][mode].palette[type].main = color;
|
||||
}
|
||||
|
||||
newOptions[newId] = parsedConfig;
|
||||
handleSave(newOptions);
|
||||
setIsDialogOpen(false);
|
||||
setEditingOption(null);
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
enqueueSnackbar({
|
||||
message: t("settings.invalidThemeConfig"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
}
|
||||
}, [options, handleSave, enqueueSnackbar, defaultTheme, onDefaultThemeChange, t]);
|
||||
},
|
||||
[options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange],
|
||||
);
|
||||
|
||||
const handleColorChange = useCallback((id: string, type: 'primary' | 'secondary', mode: 'light' | 'dark', color: string) => {
|
||||
const newOptions = { ...options };
|
||||
|
||||
if (type === 'primary' && mode === 'light') {
|
||||
// If changing the primary color (which is the ID), we need to create a new entry
|
||||
const newId = color;
|
||||
|
||||
// Check if the new ID already exists
|
||||
if (newOptions[newId] && newId !== id) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.duplicateThemeColor"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const config = { ...newOptions[id] };
|
||||
config[mode].palette[type].main = color;
|
||||
|
||||
// Delete old entry and create new one with the updated ID
|
||||
delete newOptions[id];
|
||||
newOptions[newId] = config;
|
||||
|
||||
// If we're changing the ID of the default theme, update the default theme reference
|
||||
if (id === defaultTheme) {
|
||||
onDefaultThemeChange(newId);
|
||||
}
|
||||
} else {
|
||||
// For other colors, just update the value
|
||||
newOptions[id][mode].palette[type].main = color;
|
||||
}
|
||||
|
||||
handleSave(newOptions);
|
||||
}, [options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange]);
|
||||
|
||||
const handleDefaultThemeChange = useCallback((id: string) => {
|
||||
onDefaultThemeChange(id);
|
||||
}, [onDefaultThemeChange]);
|
||||
const handleDefaultThemeChange = useCallback(
|
||||
(id: string) => {
|
||||
onDefaultThemeChange(id);
|
||||
},
|
||||
[onDefaultThemeChange],
|
||||
);
|
||||
|
||||
const optionsArray = useMemo(() => {
|
||||
return Object.entries(options).map(([id, config]) => ({
|
||||
|
|
@ -229,8 +252,7 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
|
||||
{optionsArray.length > 0 && (
|
||||
<TableContainer component={StyledTableContainerPaper} sx={{ mt: 2 }}>
|
||||
<Table size="small" stickyHeader
|
||||
sx={{ width: "100%", tableLayout: "fixed" }}>
|
||||
<Table size="small" stickyHeader sx={{ width: "100%", tableLayout: "fixed" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<NoWrapTableCell width={50}>{t("settings.defaultTheme")}</NoWrapTableCell>
|
||||
|
|
@ -255,25 +277,25 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
<HexColorInput
|
||||
required
|
||||
currentColor={option.config.light.palette.primary.main}
|
||||
onColorChange={(color) => handleColorChange(option.id, 'primary', 'light', color)}
|
||||
onColorChange={(color) => handleColorChange(option.id, "primary", "light", color)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<HexColorInput
|
||||
currentColor={option.config.light.palette.secondary.main}
|
||||
onColorChange={(color) => handleColorChange(option.id, 'secondary', 'light', color)}
|
||||
onColorChange={(color) => handleColorChange(option.id, "secondary", "light", color)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<HexColorInput
|
||||
currentColor={option.config.dark.palette.primary.main}
|
||||
onColorChange={(color) => handleColorChange(option.id, 'primary', 'dark', color)}
|
||||
onColorChange={(color) => handleColorChange(option.id, "primary", "dark", color)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<HexColorInput
|
||||
currentColor={option.config.dark.palette.secondary.main}
|
||||
onColorChange={(color) => handleColorChange(option.id, 'secondary', 'dark', color)}
|
||||
onColorChange={(color) => handleColorChange(option.id, "secondary", "dark", color)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
|
|
@ -295,12 +317,7 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
</TableContainer>
|
||||
)}
|
||||
|
||||
<SecondaryButton
|
||||
variant="contained"
|
||||
startIcon={<Add />}
|
||||
onClick={handleAdd}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<SecondaryButton variant="contained" startIcon={<Add />} onClick={handleAdd} sx={{ mt: 2 }}>
|
||||
{t("settings.addThemeOption")}
|
||||
</SecondaryButton>
|
||||
|
||||
|
|
@ -317,4 +334,4 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
|||
);
|
||||
};
|
||||
|
||||
export default ThemeOptions;
|
||||
export default ThemeOptions;
|
||||
|
|
|
|||
|
|
@ -32,13 +32,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.capInstanceURLDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://capjs.js.org/guide/standalone.html"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -58,13 +52,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.capKeyIDDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://capjs.js.org/guide/standalone.html"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -85,13 +73,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.capKeySecretDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://capjs.js.org/guide/standalone.html"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -100,4 +82,4 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default CapCaptcha;
|
||||
export default CapCaptcha;
|
||||
|
|
|
|||
|
|
@ -2,21 +2,8 @@ import { useTranslation } from "react-i18next";
|
|||
import * as React from "react";
|
||||
import { useContext } from "react";
|
||||
import { SettingContext } from "../SettingWrapper.tsx";
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
ListItemText,
|
||||
Stack,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
NoMarginHelperText,
|
||||
SettingSection,
|
||||
SettingSectionContent,
|
||||
} from "../Settings.tsx";
|
||||
import { Box, Collapse, FormControl, FormControlLabel, ListItemText, Stack, Switch, Typography } from "@mui/material";
|
||||
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
|
||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||
import { isTrueVal } from "../../../../session/utils.ts";
|
||||
import { DenseSelect } from "../../../Common/StyledComponents.tsx";
|
||||
|
|
@ -54,9 +41,7 @@ const Captcha = () => {
|
|||
}
|
||||
label={t("settings.captchaForLogin")}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.captchaForLoginDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.captchaForLoginDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
<SettingForm lgWidth={5}>
|
||||
|
|
@ -74,9 +59,7 @@ const Captcha = () => {
|
|||
}
|
||||
label={t("settings.captchaForSignup")}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.captchaForSignupDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.captchaForSignupDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
<SettingForm lgWidth={5}>
|
||||
|
|
@ -94,9 +77,7 @@ const Captcha = () => {
|
|||
}
|
||||
label={t("settings.captchaForReset")}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.captchaForResetDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.captchaForResetDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
</SettingSectionContent>
|
||||
|
|
@ -117,61 +98,55 @@ const Captcha = () => {
|
|||
value={values.captcha_type}
|
||||
>
|
||||
<SquareMenuItem value={CaptchaType.NORMAL}>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.plainCaptcha")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value={CaptchaType.RECAPTCHA}>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.reCaptchaV2")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value={CaptchaType.TURNSTILE}>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.turnstile")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value={CaptchaType.CAP}>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.cap")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
</DenseSelect>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.captchaTypeDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.captchaTypeDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
<Collapse
|
||||
in={values.captcha_type === CaptchaType.NORMAL}
|
||||
unmountOnExit
|
||||
>
|
||||
<Collapse in={values.captcha_type === CaptchaType.NORMAL} unmountOnExit>
|
||||
<GraphicCaptcha setSettings={setSettings} values={values} />
|
||||
</Collapse>
|
||||
<Collapse
|
||||
in={values.captcha_type === CaptchaType.RECAPTCHA}
|
||||
unmountOnExit
|
||||
>
|
||||
<Collapse in={values.captcha_type === CaptchaType.RECAPTCHA} unmountOnExit>
|
||||
<ReCaptcha setSettings={setSettings} values={values} />
|
||||
</Collapse>
|
||||
<Collapse
|
||||
in={values.captcha_type === CaptchaType.TURNSTILE}
|
||||
unmountOnExit
|
||||
>
|
||||
<Collapse in={values.captcha_type === CaptchaType.TURNSTILE} unmountOnExit>
|
||||
<TurnstileCaptcha setSettings={setSettings} values={values} />
|
||||
</Collapse>
|
||||
<Collapse
|
||||
in={values.captcha_type === CaptchaType.CAP}
|
||||
unmountOnExit
|
||||
>
|
||||
<Collapse in={values.captcha_type === CaptchaType.CAP} unmountOnExit>
|
||||
<CapCaptcha setSettings={setSettings} values={values} />
|
||||
</Collapse>
|
||||
</SettingSectionContent>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
ListItemText,
|
||||
Stack,
|
||||
Switch,
|
||||
} from "@mui/material";
|
||||
import { FormControl, FormControlLabel, ListItemText, Stack, Switch } from "@mui/material";
|
||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||
import { DenseSelect } from "../../../Common/StyledComponents.tsx";
|
||||
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
|
||||
|
|
@ -33,16 +27,13 @@ const GraphicCaptcha = ({ values, setSettings }: GraphicCaptchaProps) => {
|
|||
}
|
||||
value={values.captcha_mode}
|
||||
>
|
||||
{[
|
||||
"captchaModeNumber",
|
||||
"captchaModeLetter",
|
||||
"captchaModeMath",
|
||||
"captchaModeNumberLetter",
|
||||
].map((k, i) => (
|
||||
{["captchaModeNumber", "captchaModeLetter", "captchaModeMath", "captchaModeNumberLetter"].map((k, i) => (
|
||||
<SquareMenuItem key={k} value={i.toString()}>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t(`settings.${k}`)}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
|
|
|
|||
|
|
@ -31,13 +31,7 @@ const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.siteKeyDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://www.google.com/recaptcha/admin/create"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://www.google.com/recaptcha/admin/create"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -57,13 +51,7 @@ const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.siteSecretDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://www.google.com/recaptcha/admin/create"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://www.google.com/recaptcha/admin/create"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
|
|||
|
|
@ -31,13 +31,7 @@ const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.siteKeyDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://dash.cloudflare.com/"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://dash.cloudflare.com/"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -57,13 +51,7 @@ const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => {
|
|||
<Trans
|
||||
i18nKey="settings.siteSecretDes"
|
||||
ns={"dashboard"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={"https://dash.cloudflare.com/"}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
components={[<Link key={0} href={"https://dash.cloudflare.com/"} target={"_blank"} />]}
|
||||
/>
|
||||
</NoMarginHelperText>
|
||||
</FormControl>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
DialogContent,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Stack,
|
||||
Switch,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Box, DialogContent, FormControl, FormControlLabel, Stack, Switch, Typography } from "@mui/material";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
@ -18,11 +10,7 @@ import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledCom
|
|||
import DraggableDialog, { StyledDialogContentText } from "../../../Dialogs/DraggableDialog.tsx";
|
||||
import MailOutlined from "../../../Icons/MailOutlined.tsx";
|
||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||
import {
|
||||
NoMarginHelperText,
|
||||
SettingSection,
|
||||
SettingSectionContent,
|
||||
} from "../Settings.tsx";
|
||||
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
|
||||
import { SettingContext } from "../SettingWrapper.tsx";
|
||||
import EmailTemplates from "./EmailTemplates.tsx";
|
||||
|
||||
|
|
@ -38,10 +26,12 @@ const Email = () => {
|
|||
const handleTestEmail = async () => {
|
||||
setSending(true);
|
||||
try {
|
||||
await dispatch(sendTestSMTP({
|
||||
to: testEmailAddress,
|
||||
settings: values,
|
||||
}));
|
||||
await dispatch(
|
||||
sendTestSMTP({
|
||||
to: testEmailAddress,
|
||||
settings: values,
|
||||
}),
|
||||
);
|
||||
enqueueSnackbar({
|
||||
message: t("settings.testMailSent"),
|
||||
variant: "success",
|
||||
|
|
@ -69,9 +59,7 @@ const Email = () => {
|
|||
title={t("settings.testSMTPSettings")}
|
||||
>
|
||||
<DialogContent>
|
||||
<StyledDialogContentText sx={{ mb: 2 }}>
|
||||
{t("settings.testSMTPTooltip")}
|
||||
</StyledDialogContentText>
|
||||
<StyledDialogContentText sx={{ mb: 2 }}>{t("settings.testSMTPTooltip")}</StyledDialogContentText>
|
||||
<SettingForm title={t("settings.recipient")} lgWidth={12}>
|
||||
<DenseFilledTextField
|
||||
required
|
||||
|
|
@ -86,7 +74,9 @@ const Email = () => {
|
|||
</DraggableDialog>
|
||||
|
||||
<SettingSection>
|
||||
<Typography variant="h6" gutterBottom>{t("settings.smtp")}</Typography>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{t("settings.smtp")}
|
||||
</Typography>
|
||||
<SettingSectionContent>
|
||||
<SettingForm title={t("settings.senderName")} lgWidth={5}>
|
||||
<FormControl fullWidth>
|
||||
|
|
@ -95,9 +85,7 @@ const Email = () => {
|
|||
value={values.fromName ?? ""}
|
||||
onChange={(e) => setSettings({ fromName: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.senderNameDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.senderNameDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -109,9 +97,7 @@ const Email = () => {
|
|||
value={values.fromAdress ?? ""}
|
||||
onChange={(e) => setSettings({ fromAdress: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.senderAddressDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.senderAddressDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -122,9 +108,7 @@ const Email = () => {
|
|||
value={values.smtpHost ?? ""}
|
||||
onChange={(e) => setSettings({ smtpHost: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.smtpServerDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.smtpServerDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -137,9 +121,7 @@ const Email = () => {
|
|||
value={values.smtpPort ?? ""}
|
||||
onChange={(e) => setSettings({ smtpPort: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.smtpPortDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.smtpPortDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -150,9 +132,7 @@ const Email = () => {
|
|||
value={values.smtpUser ?? ""}
|
||||
onChange={(e) => setSettings({ smtpUser: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.smtpUsernameDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.smtpUsernameDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -164,9 +144,7 @@ const Email = () => {
|
|||
value={values.smtpPass ?? ""}
|
||||
onChange={(e) => setSettings({ smtpPass: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.smtpPasswordDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.smtpPasswordDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -177,9 +155,7 @@ const Email = () => {
|
|||
value={values.replyTo ?? ""}
|
||||
onChange={(e) => setSettings({ replyTo: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.replyToAddressDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.replyToAddressDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -189,16 +165,12 @@ const Email = () => {
|
|||
control={
|
||||
<Switch
|
||||
checked={isTrueVal(values.smtpEncryption)}
|
||||
onChange={(e) =>
|
||||
setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })
|
||||
}
|
||||
onChange={(e) => setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })}
|
||||
/>
|
||||
}
|
||||
label={t("settings.enforceSSL")}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.enforceSSLDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.enforceSSLDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -211,18 +183,12 @@ const Email = () => {
|
|||
value={values.mail_keepalive ?? "30"}
|
||||
onChange={(e) => setSettings({ mail_keepalive: e.target.value })}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.smtpTTLDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.smtpTTLDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
<Box display="flex" gap={2} mt={2}>
|
||||
<SecondaryButton
|
||||
variant="contained"
|
||||
startIcon={<MailOutlined />}
|
||||
onClick={() => setTestEmailOpen(true)}
|
||||
>
|
||||
<SecondaryButton variant="contained" startIcon={<MailOutlined />} onClick={() => setTestEmailOpen(true)}>
|
||||
{t("settings.sendTestEmail")}
|
||||
</SecondaryButton>
|
||||
</Box>
|
||||
|
|
@ -236,4 +202,4 @@ const Email = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Email;
|
||||
export default Email;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import { Box, IconButton, Table, TableBody, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { memo, useMemo, useState } from "react";
|
||||
import {
|
||||
builtInIcons,
|
||||
FileTypeIconSetting,
|
||||
} from "../../../FileManager/Explorer/FileTypeIcon.tsx";
|
||||
import { builtInIcons, FileTypeIconSetting } from "../../../FileManager/Explorer/FileTypeIcon.tsx";
|
||||
import {
|
||||
DenseFilledTextField,
|
||||
NoWrapCell,
|
||||
|
|
@ -71,52 +60,28 @@ const IconPreview = ({ icon }: { icon: FileTypeIconSetting }) => {
|
|||
|
||||
const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const configParsed = useMemo(
|
||||
(): FileTypeIconSetting[] => JSON.parse(config),
|
||||
[config],
|
||||
);
|
||||
const configParsed = useMemo((): FileTypeIconSetting[] => JSON.parse(config), [config]);
|
||||
const [inputCache, setInputCache] = useState<{
|
||||
[key: number]: string | undefined;
|
||||
}>({});
|
||||
return (
|
||||
<Box>
|
||||
{configParsed?.length > 0 && (
|
||||
<TableContainer
|
||||
sx={{ mt: 1, maxHeight: 440 }}
|
||||
component={StyledTableContainerPaper}
|
||||
>
|
||||
<Table
|
||||
stickyHeader
|
||||
sx={{ width: "100%", tableLayout: "fixed" }}
|
||||
size="small"
|
||||
>
|
||||
<TableContainer sx={{ mt: 1, maxHeight: 440 }} component={StyledTableContainerPaper}>
|
||||
<Table stickyHeader sx={{ width: "100%", tableLayout: "fixed" }} size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<NoWrapTableCell width={64}>
|
||||
{t("settings.icon")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>
|
||||
{t("settings.iconUrl")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>
|
||||
{t("settings.iconColor")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>
|
||||
{t("settings.iconColorDark")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={250}>
|
||||
{t("settings.exts")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={64}>{t("settings.icon")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>{t("settings.iconUrl")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>{t("settings.iconColor")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>{t("settings.iconColorDark")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={250}>{t("settings.exts")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={64}></NoWrapTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{configParsed.map((r, i) => (
|
||||
<TableRow
|
||||
key={i}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
hover
|
||||
>
|
||||
<TableRow key={i} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover>
|
||||
<NoWrapCell>
|
||||
<IconPreview icon={r} />
|
||||
</NoWrapCell>
|
||||
|
|
@ -215,13 +180,7 @@ const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
|
|||
<NoWrapCell>
|
||||
{!r.icon && (
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
onChange(
|
||||
JSON.stringify(
|
||||
configParsed.filter((_, index) => index !== i),
|
||||
),
|
||||
)
|
||||
}
|
||||
onClick={() => onChange(JSON.stringify(configParsed.filter((_, index) => index !== i)))}
|
||||
size={"small"}
|
||||
>
|
||||
<Dismiss fontSize={"small"} />
|
||||
|
|
|
|||
|
|
@ -11,11 +11,7 @@ export interface HexColorInputProps {
|
|||
required?: boolean;
|
||||
}
|
||||
|
||||
const HexColorInput = ({
|
||||
currentColor,
|
||||
onColorChange,
|
||||
...rest
|
||||
}: HexColorInputProps) => {
|
||||
const HexColorInput = ({ currentColor, onColorChange, ...rest }: HexColorInputProps) => {
|
||||
return (
|
||||
<DenseFilledTextField
|
||||
value={currentColor}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@ import { memo, useCallback, useState } from "react";
|
|||
import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton, TableRow } from "@mui/material";
|
||||
import {
|
||||
DenseFilledTextField,
|
||||
NoWrapCell,
|
||||
StyledCheckbox,
|
||||
} from "../../../../Common/StyledComponents.tsx";
|
||||
import { DenseFilledTextField, NoWrapCell, StyledCheckbox } from "../../../../Common/StyledComponents.tsx";
|
||||
import { ViewerIcon } from "../../../../FileManager/Dialogs/OpenWith.tsx";
|
||||
import Dismiss from "../../../../Icons/Dismiss.tsx";
|
||||
import Edit from "../../../../Icons/Edit.tsx";
|
||||
|
|
@ -19,83 +15,68 @@ export interface FileViewerRowProps {
|
|||
onDelete: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
}
|
||||
|
||||
const FileViewerRow = memo(
|
||||
({ viewer, onChange, onDelete }: FileViewerRowProps) => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const [extCached, setExtCached] = useState("");
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const onClose = useCallback(() => {
|
||||
setEditOpen(false);
|
||||
}, [setEditOpen]);
|
||||
return (
|
||||
<TableRow
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
hover
|
||||
>
|
||||
<FileViewerEditDialog
|
||||
viewer={viewer}
|
||||
onChange={onChange}
|
||||
open={editOpen}
|
||||
onClose={onClose}
|
||||
const FileViewerRow = memo(({ viewer, onChange, onDelete }: FileViewerRowProps) => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const [extCached, setExtCached] = useState("");
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const onClose = useCallback(() => {
|
||||
setEditOpen(false);
|
||||
}, [setEditOpen]);
|
||||
return (
|
||||
<TableRow sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover>
|
||||
<FileViewerEditDialog viewer={viewer} onChange={onChange} open={editOpen} onClose={onClose} />
|
||||
<NoWrapCell>
|
||||
<ViewerIcon viewer={viewer} />
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>{t(`settings.${viewer.type}ViewerType`)}</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
{t(viewer.display_name, {
|
||||
ns: "application",
|
||||
})}
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
multiline
|
||||
required
|
||||
value={extCached == "" ? viewer.exts.join() : extCached}
|
||||
onBlur={() => {
|
||||
onChange({
|
||||
...viewer,
|
||||
exts: extCached == "" ? viewer.exts : extCached?.split(",")?.map((ext) => ext.trim()),
|
||||
});
|
||||
setExtCached("");
|
||||
}}
|
||||
onChange={(e) => setExtCached(e.target.value)}
|
||||
/>
|
||||
<NoWrapCell>
|
||||
<ViewerIcon viewer={viewer} />
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>{t(`settings.${viewer.type}ViewerType`)}</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
{t(viewer.display_name, {
|
||||
ns: "application",
|
||||
})}
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
multiline
|
||||
required
|
||||
value={extCached == "" ? viewer.exts.join() : extCached}
|
||||
onBlur={() => {
|
||||
onChange({
|
||||
...viewer,
|
||||
exts:
|
||||
extCached == ""
|
||||
? viewer.exts
|
||||
: extCached?.split(",")?.map((ext) => ext.trim()),
|
||||
});
|
||||
setExtCached("");
|
||||
}}
|
||||
onChange={(e) => setExtCached(e.target.value)}
|
||||
/>
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
{viewer.templates?.length
|
||||
? t("settings.nMapping", { num: viewer.templates?.length })
|
||||
: t("share.none")}
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<StyledCheckbox
|
||||
size={"small"}
|
||||
checked={!viewer.disabled}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...viewer,
|
||||
disabled: !e.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<IconButton size={"small"} onClick={() => setEditOpen(true)}>
|
||||
<Edit fontSize={"small"} />
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
{viewer.templates?.length ? t("settings.nMapping", { num: viewer.templates?.length }) : t("share.none")}
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<StyledCheckbox
|
||||
size={"small"}
|
||||
checked={!viewer.disabled}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...viewer,
|
||||
disabled: !e.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<IconButton size={"small"} onClick={() => setEditOpen(true)}>
|
||||
<Edit fontSize={"small"} />
|
||||
</IconButton>
|
||||
{viewer.type != ViewerType.builtin && (
|
||||
<IconButton size={"small"} onClick={onDelete}>
|
||||
<Dismiss fontSize={"small"} />
|
||||
</IconButton>
|
||||
{viewer.type != ViewerType.builtin && (
|
||||
<IconButton size={"small"} onClick={onDelete}>
|
||||
<Dismiss fontSize={"small"} />
|
||||
</IconButton>
|
||||
)}
|
||||
</NoWrapCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
);
|
||||
)}
|
||||
</NoWrapCell>
|
||||
</TableRow>
|
||||
);
|
||||
});
|
||||
|
||||
export default FileViewerRow;
|
||||
|
|
|
|||
|
|
@ -18,16 +18,10 @@ import { useAppDispatch } from "../../../../redux/hooks.ts";
|
|||
import { isTrueVal } from "../../../../session/utils.ts";
|
||||
import SizeInput from "../../../Common/SizeInput.tsx";
|
||||
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
|
||||
import {
|
||||
DenseFilledTextField,
|
||||
StyledCheckbox,
|
||||
} from "../../../Common/StyledComponents.tsx";
|
||||
import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
|
||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
|
||||
import {
|
||||
AccordionSummary,
|
||||
StyledAccordion,
|
||||
} from "../UserSession/SSOSettings.tsx";
|
||||
import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
|
||||
|
||||
export interface ExtractorsProps {
|
||||
values: {
|
||||
|
|
@ -90,12 +84,11 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
const [testing, setTesting] = useState(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const handleEnableChange =
|
||||
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSetting({
|
||||
[name]: e.target.checked ? "1" : "0",
|
||||
});
|
||||
};
|
||||
const handleEnableChange = (name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSetting({
|
||||
[name]: e.target.checked ? "1" : "0",
|
||||
});
|
||||
};
|
||||
|
||||
const doTest = (name: string, executable: string) => {
|
||||
setTesting(true);
|
||||
|
|
@ -150,12 +143,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<LoadingButton
|
||||
onClick={() =>
|
||||
doTest(
|
||||
e.name,
|
||||
values[e.executableSetting ?? ""],
|
||||
)
|
||||
}
|
||||
onClick={() => doTest(e.name, values[e.executableSetting ?? ""])}
|
||||
loading={testing}
|
||||
color="primary"
|
||||
>
|
||||
|
|
@ -170,9 +158,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
})
|
||||
}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.executableDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.executableDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
)}
|
||||
|
|
@ -189,9 +175,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
})
|
||||
}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.maxSizeLocalDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.maxSizeLocalDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
)}
|
||||
|
|
@ -208,17 +192,12 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
})
|
||||
}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.maxSizeRemoteDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.maxSizeRemoteDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
)}
|
||||
{e.additionalSettings?.map((setting) => (
|
||||
<SettingForm
|
||||
key={setting.name}
|
||||
lgWidth={12}
|
||||
>
|
||||
<SettingForm key={setting.name} lgWidth={12}>
|
||||
<FormControl fullWidth>
|
||||
{setting.type === "switch" ? (
|
||||
<FormControlLabel
|
||||
|
|
@ -245,9 +224,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
<NoMarginHelperText>
|
||||
{t(`settings.${setting.des}`)}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t(`settings.${setting.des}`)}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
))}
|
||||
|
|
@ -259,4 +236,4 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Extractors;
|
||||
export default Extractors;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,9 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
AccordionDetails,
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
InputAdornment,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { AccordionDetails, Box, FormControl, FormControlLabel, InputAdornment, Typography } from "@mui/material";
|
||||
import { isTrueVal } from "../../../../session/utils.ts";
|
||||
import {
|
||||
AccordionSummary,
|
||||
StyledAccordion,
|
||||
} from "../UserSession/SSOSettings.tsx";
|
||||
import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
|
||||
import { ExpandMoreRounded } from "@mui/icons-material";
|
||||
import {
|
||||
DenseFilledTextField,
|
||||
StyledCheckbox,
|
||||
} from "../../../Common/StyledComponents.tsx";
|
||||
import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
|
||||
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
|
||||
|
|
@ -131,25 +118,23 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
|||
const [testing, setTesting] = useState(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const handleEnableChange =
|
||||
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSetting({
|
||||
[name]: e.target.checked ? "1" : "0",
|
||||
const handleEnableChange = (name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSetting({
|
||||
[name]: e.target.checked ? "1" : "0",
|
||||
});
|
||||
const newValues = { ...values, [name]: e.target.checked ? "1" : "0" };
|
||||
if (
|
||||
(newValues["thumb_libreoffice_enabled"] === "1" || newValues["thumb_music_cover_enabled"] === "1") &&
|
||||
newValues["thumb_builtin_enabled"] === "0" &&
|
||||
newValues["thumb_vips_enabled"] === "0"
|
||||
) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.thumbDependencyWarning"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
const newValues = { ...values, [name]: e.target.checked ? "1" : "0" };
|
||||
if (
|
||||
(newValues["thumb_libreoffice_enabled"] === "1" ||
|
||||
newValues["thumb_music_cover_enabled"] === "1") &&
|
||||
newValues["thumb_builtin_enabled"] === "0" &&
|
||||
newValues["thumb_vips_enabled"] === "0"
|
||||
) {
|
||||
enqueueSnackbar({
|
||||
message: t("settings.thumbDependencyWarning"),
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const doTest = (name: string, executable: string) => {
|
||||
setTesting(true);
|
||||
|
|
@ -205,12 +190,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
|||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<LoadingButton
|
||||
onClick={() =>
|
||||
doTest(
|
||||
g.name,
|
||||
values[g.executableSetting ?? ""],
|
||||
)
|
||||
}
|
||||
onClick={() => doTest(g.name, values[g.executableSetting ?? ""])}
|
||||
loading={testing}
|
||||
color="primary"
|
||||
>
|
||||
|
|
@ -225,9 +205,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
|||
})
|
||||
}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.executableDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.executableDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
)}
|
||||
|
|
@ -245,18 +223,12 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
|||
})
|
||||
}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.thumbMaxSizeDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.thumbMaxSizeDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
)}
|
||||
{g.inputs?.map((input) => (
|
||||
<SettingForm
|
||||
key={input.name}
|
||||
lgWidth={12}
|
||||
title={t(`settings.${input.label}`)}
|
||||
>
|
||||
<SettingForm key={input.name} lgWidth={12} title={t(`settings.${input.label}`)}>
|
||||
<FormControl fullWidth>
|
||||
<DenseFilledTextField
|
||||
value={values[input.name]}
|
||||
|
|
@ -267,9 +239,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
|||
}
|
||||
required={!!input.required}
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t(`settings.${input.des}`)}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t(`settings.${input.des}`)}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ const Queue = () => {
|
|||
dispatch(getQueueMetrics())
|
||||
.then((res) => {
|
||||
setMetrics(res);
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
|
@ -30,26 +31,32 @@ const Queue = () => {
|
|||
fetchQueueMetrics();
|
||||
}, []);
|
||||
|
||||
return <Box component={"form"} ref={formRef} sx={{ p: 2, pt: 0 }}>
|
||||
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
|
||||
<SecondaryButton
|
||||
onClick={fetchQueueMetrics}
|
||||
disabled={loading}
|
||||
variant={"contained"}
|
||||
startIcon={<ArrowSync />}
|
||||
>
|
||||
{t("node.refresh")}
|
||||
</SecondaryButton>
|
||||
</Stack>
|
||||
<Grid container spacing={2}>
|
||||
{!loading && metrics.map((metric) => (
|
||||
<QueueCard key={metric.name} metrics={metric} queue={metric.name} settings={values} setSettings={setSettings} loading={loading} />
|
||||
))}
|
||||
{loading && Array.from(Array(5)).map((_, index) => (
|
||||
<QueueCard key={`loading-${index}`} settings={values} setSettings={setSettings} loading={true} />
|
||||
))}
|
||||
</Grid>
|
||||
</Box>;
|
||||
return (
|
||||
<Box component={"form"} ref={formRef} sx={{ p: 2, pt: 0 }}>
|
||||
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
|
||||
<SecondaryButton onClick={fetchQueueMetrics} disabled={loading} variant={"contained"} startIcon={<ArrowSync />}>
|
||||
{t("node.refresh")}
|
||||
</SecondaryButton>
|
||||
</Stack>
|
||||
<Grid container spacing={2}>
|
||||
{!loading &&
|
||||
metrics.map((metric) => (
|
||||
<QueueCard
|
||||
key={metric.name}
|
||||
metrics={metric}
|
||||
queue={metric.name}
|
||||
settings={values}
|
||||
setSettings={setSettings}
|
||||
loading={loading}
|
||||
/>
|
||||
))}
|
||||
{loading &&
|
||||
Array.from(Array(5)).map((_, index) => (
|
||||
<QueueCard key={`loading-${index}`} settings={values} setSettings={setSettings} loading={true} />
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Queue;
|
||||
export default Queue;
|
||||
|
|
|
|||
|
|
@ -34,11 +34,7 @@ export const QueueCard = ({ queue, settings, metrics, setSettings, loading }: Qu
|
|||
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} />
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Skeleton variant="rectangular" height={8} width="100%" sx={{ borderRadius: 1 }} />
|
||||
<Stack
|
||||
spacing={isMobile ? 1 : 2}
|
||||
direction={isMobile ? "column" : "row"}
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<Stack spacing={isMobile ? 1 : 2} direction={isMobile ? "column" : "row"} sx={{ mt: 1 }}>
|
||||
{Array.from(Array(5)).map((_, index) => (
|
||||
<Skeleton key={index} variant="text" width={isMobile ? "100%" : 80} height={20} />
|
||||
))}
|
||||
|
|
@ -48,115 +44,116 @@ export const QueueCard = ({ queue, settings, metrics, setSettings, loading }: Qu
|
|||
);
|
||||
}
|
||||
|
||||
return <Grid item xs={12} md={6} lg={4}>
|
||||
<BorderedCard>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Typography variant="subtitle1" fontWeight={600}>{t(`queue.queueName_${queue}`)}</Typography>
|
||||
<IconButton size="small" onClick={() => setSettingDialogOpen(true)}>
|
||||
<Setting fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">{t(`queue.queueName_${queue}Des`)}</Typography>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
{metrics && <>
|
||||
<StorageBar>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.success.light,
|
||||
width: `${(metrics.success_tasks / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.error.light,
|
||||
width: `${(metrics.failure_tasks / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.action.active,
|
||||
width: `${(metrics.suspending_tasks / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.info.light,
|
||||
width: `${(metrics.busy_workers / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
</StorageBar>
|
||||
<Stack
|
||||
spacing={isMobile ? 1 : 2}
|
||||
direction={isMobile ? "column" : "row"}
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.success.light,
|
||||
}}
|
||||
/>
|
||||
{t("queue.success", {
|
||||
count: metrics.success_tasks,
|
||||
})}
|
||||
return (
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<BorderedCard>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Typography variant="subtitle1" fontWeight={600}>
|
||||
{t(`queue.queueName_${queue}`)}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.error.light,
|
||||
}}
|
||||
/>
|
||||
{t("queue.failed", {
|
||||
count: metrics.failure_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.info.light,
|
||||
}}
|
||||
/>
|
||||
{t("queue.busyWorker", {
|
||||
count: metrics.busy_workers,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.action.active,
|
||||
}}
|
||||
/>
|
||||
{t("queue.suspending", {
|
||||
count: metrics.suspending_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.grey[
|
||||
theme.palette.mode === "light" ? 200 : 800
|
||||
],
|
||||
}}
|
||||
/>
|
||||
{t("queue.submited", {
|
||||
count: metrics.submitted_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</>}
|
||||
<IconButton size="small" onClick={() => setSettingDialogOpen(true)}>
|
||||
<Setting fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t(`queue.queueName_${queue}Des`)}
|
||||
</Typography>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
{metrics && (
|
||||
<>
|
||||
<StorageBar>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.success.light,
|
||||
width: `${(metrics.success_tasks / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.error.light,
|
||||
width: `${(metrics.failure_tasks / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.action.active,
|
||||
width: `${(metrics.suspending_tasks / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<StoragePart
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.info.light,
|
||||
width: `${(metrics.busy_workers / metrics.submitted_tasks) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
</StorageBar>
|
||||
<Stack spacing={isMobile ? 1 : 2} direction={isMobile ? "column" : "row"} sx={{ mt: 1 }}>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.success.light,
|
||||
}}
|
||||
/>
|
||||
{t("queue.success", {
|
||||
count: metrics.success_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.error.light,
|
||||
}}
|
||||
/>
|
||||
{t("queue.failed", {
|
||||
count: metrics.failure_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.info.light,
|
||||
}}
|
||||
/>
|
||||
{t("queue.busyWorker", {
|
||||
count: metrics.busy_workers,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.action.active,
|
||||
}}
|
||||
/>
|
||||
{t("queue.suspending", {
|
||||
count: metrics.suspending_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>
|
||||
<StorageBlock
|
||||
sx={{
|
||||
backgroundColor: (theme) => theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||
}}
|
||||
/>
|
||||
{t("queue.submited", {
|
||||
count: metrics.submitted_tasks,
|
||||
})}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{queue && (
|
||||
<QueueSettingDialog
|
||||
open={settingDialogOpen}
|
||||
onClose={() => setSettingDialogOpen(false)}
|
||||
queue={queue}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
/>
|
||||
)}
|
||||
</BorderedCard>
|
||||
</Grid>;
|
||||
{queue && (
|
||||
<QueueSettingDialog
|
||||
open={settingDialogOpen}
|
||||
onClose={() => setSettingDialogOpen(false)}
|
||||
queue={queue}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
/>
|
||||
)}
|
||||
</BorderedCard>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueueCard;
|
||||
export default QueueCard;
|
||||
|
|
|
|||
|
|
@ -27,13 +27,7 @@ const NoMarginHelperText = (props: any) => (
|
|||
/>
|
||||
);
|
||||
|
||||
const QueueSettingDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
queue,
|
||||
settings,
|
||||
setSettings,
|
||||
}: QueueSettingDialogProps) => {
|
||||
const QueueSettingDialog = ({ open, onClose, queue, settings, setSettings }: QueueSettingDialogProps) => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [localSettings, setLocalSettings] = useState<{ [key: string]: string }>({});
|
||||
|
|
@ -48,10 +42,10 @@ const QueueSettingDialog = ({
|
|||
"backoff_factor",
|
||||
"backoff_max_duration",
|
||||
"max_retry",
|
||||
"retry_delay"
|
||||
"retry_delay",
|
||||
];
|
||||
|
||||
settingKeys.forEach(key => {
|
||||
settingKeys.forEach((key) => {
|
||||
const fullKey = `queue_${queue}_${key}`;
|
||||
queueSettings[key] = settings[fullKey] || "";
|
||||
});
|
||||
|
|
@ -76,7 +70,7 @@ const QueueSettingDialog = ({
|
|||
const updateLocalSetting = (key: string, value: string) => {
|
||||
setLocalSettings((prev: { [key: string]: string }) => ({
|
||||
...prev,
|
||||
[key]: value
|
||||
[key]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
|
|
@ -95,25 +89,25 @@ const QueueSettingDialog = ({
|
|||
maxWidth: "sm",
|
||||
}}
|
||||
>
|
||||
<Box component={"form"} ref={formRef} sx={{ display: "flex", flexDirection: "column", gap: 2, mt: 1, px: 3, pb: 2 }}>
|
||||
<Box
|
||||
component={"form"}
|
||||
ref={formRef}
|
||||
sx={{ display: "flex", flexDirection: "column", gap: 2, mt: 1, px: 3, pb: 2 }}
|
||||
>
|
||||
<SettingForm title={t("queue.workerNum")} lgWidth={12}>
|
||||
<FormControl fullWidth>
|
||||
<DenseFilledTextField
|
||||
value={localSettings.worker_num || ""}
|
||||
onChange={(e) => updateLocalSetting("worker_num", e.target.value)}
|
||||
type="number"
|
||||
slotProps={
|
||||
{
|
||||
htmlInput: {
|
||||
min: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: 1,
|
||||
},
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("queue.workerNumDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("queue.workerNumDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -128,9 +122,7 @@ const QueueSettingDialog = ({
|
|||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("queue.maxExecutionDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("queue.maxExecutionDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -140,19 +132,15 @@ const QueueSettingDialog = ({
|
|||
value={localSettings.backoff_factor || ""}
|
||||
onChange={(e) => updateLocalSetting("backoff_factor", e.target.value)}
|
||||
type="number"
|
||||
slotProps={
|
||||
{
|
||||
htmlInput: {
|
||||
min: 1,
|
||||
step: 0.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: 1,
|
||||
step: 0.1,
|
||||
},
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("queue.backoffFactorDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("queue.backoffFactorDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -162,18 +150,14 @@ const QueueSettingDialog = ({
|
|||
value={localSettings.backoff_max_duration || ""}
|
||||
onChange={(e) => updateLocalSetting("backoff_max_duration", e.target.value)}
|
||||
type="number"
|
||||
slotProps={
|
||||
{
|
||||
htmlInput: {
|
||||
min: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: 1,
|
||||
},
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("queue.backoffMaxDurationDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("queue.backoffMaxDurationDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -183,18 +167,14 @@ const QueueSettingDialog = ({
|
|||
value={localSettings.max_retry || ""}
|
||||
onChange={(e) => updateLocalSetting("max_retry", e.target.value)}
|
||||
type="number"
|
||||
slotProps={
|
||||
{
|
||||
htmlInput: {
|
||||
min: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: 0,
|
||||
},
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("queue.maxRetryDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("queue.maxRetryDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
|
||||
|
|
@ -204,18 +184,14 @@ const QueueSettingDialog = ({
|
|||
value={localSettings.retry_delay || ""}
|
||||
onChange={(e) => updateLocalSetting("retry_delay", e.target.value)}
|
||||
type="number"
|
||||
slotProps={
|
||||
{
|
||||
htmlInput: {
|
||||
min: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: 0,
|
||||
},
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("queue.retryDelayDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("queue.retryDelayDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
</Box>
|
||||
|
|
@ -223,4 +199,4 @@ const QueueSettingDialog = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default QueueSettingDialog;
|
||||
export default QueueSettingDialog;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
Divider,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
DenseFilledTextField,
|
||||
SecondaryButton,
|
||||
} from "../../../Common/StyledComponents.tsx";
|
||||
import { Box, Collapse, Divider, IconButton, InputAdornment, Stack } from "@mui/material";
|
||||
import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents.tsx";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import Dismiss from "../../../Icons/Dismiss.tsx";
|
||||
import Add from "../../../Icons/Add.tsx";
|
||||
|
|
@ -29,12 +19,11 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
|
|||
return urls.split(",").map((url) => url);
|
||||
}, [urls]);
|
||||
|
||||
const onUrlChange =
|
||||
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newUrls = [...urlSplit];
|
||||
newUrls[index] = e.target.value;
|
||||
onChange(newUrls.join(","));
|
||||
};
|
||||
const onUrlChange = (index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newUrls = [...urlSplit];
|
||||
newUrls[index] = e.target.value;
|
||||
onChange(newUrls.join(","));
|
||||
};
|
||||
|
||||
const removeUrl = (index: number) => () => {
|
||||
const newUrls = [...urlSplit];
|
||||
|
|
@ -58,9 +47,7 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
|
|||
}}
|
||||
required
|
||||
/>
|
||||
<NoMarginHelperText>
|
||||
{t("settings.primarySiteURLDes")}
|
||||
</NoMarginHelperText>
|
||||
<NoMarginHelperText>{t("settings.primarySiteURLDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
<Divider />
|
||||
<NoMarginHelperText>{t("settings.secondaryDes")}</NoMarginHelperText>
|
||||
|
|
@ -93,11 +80,7 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
|
|||
))}
|
||||
</TransitionGroup>
|
||||
<Box sx={{ mt: "0!important" }}>
|
||||
<SecondaryButton
|
||||
variant={"contained"}
|
||||
startIcon={<Add />}
|
||||
onClick={() => onChange(`${urls},`)}
|
||||
>
|
||||
<SecondaryButton variant={"contained"} startIcon={<Add />} onClick={() => onChange(`${urls},`)}>
|
||||
{t("settings.addSecondary")}
|
||||
</SecondaryButton>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ const UserSession = () => {
|
|||
control={<Switch checked={false} />}
|
||||
label={
|
||||
<>
|
||||
{t("vas.disableSubAddressEmail")}
|
||||
{t("vas.disableSubAddressEmail")}
|
||||
<ProChip label="Pro" color="primary" size="small" />
|
||||
</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export { default as BasicInfoSection } from './BasicInfoSection';
|
||||
export { default as DownloadSection } from './DownloadSection';
|
||||
export * from './magicVars';
|
||||
export { default as MediaMetadataSection } from './MediaMetadataSection';
|
||||
export { default as StorageAndUploadSection } from './StorageAndUploadSection';
|
||||
export { default as ThumbnailsSection } from './ThumbnailsSection';
|
||||
|
||||
export { default as BasicInfoSection } from "./BasicInfoSection";
|
||||
export { default as DownloadSection } from "./DownloadSection";
|
||||
export * from "./magicVars";
|
||||
export { default as MediaMetadataSection } from "./MediaMetadataSection";
|
||||
export { default as StorageAndUploadSection } from "./StorageAndUploadSection";
|
||||
export { default as ThumbnailsSection } from "./ThumbnailsSection";
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export const commonMagicVars: MagicVar[] = [
|
|||
|
||||
export const pathMagicVars: MagicVar[] = [
|
||||
...commonMagicVars,
|
||||
{ name: "{path}", value: "policy.magicVar.path", example: "/path/to/" }
|
||||
{ name: "{path}", value: "policy.magicVar.path", example: "/path/to/" },
|
||||
];
|
||||
|
||||
export const fileMagicVars: MagicVar[] = [
|
||||
|
|
@ -31,4 +31,4 @@ export const fileMagicVars: MagicVar[] = [
|
|||
{ name: "{ext}", value: "policy.magicVar.extension", example: ".jpg" },
|
||||
{ name: "{originname_without_ext}", value: "policy.magicVar.originFileNameNoext", example: "example" },
|
||||
{ name: "{uuid}", value: "policy.magicVar.uuidV4", example: "550e8400-e29b-41d4-a716-446655440000" },
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
|||
height: 8,
|
||||
borderRadius: 5,
|
||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||
backgroundColor:
|
||||
theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||
backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||
},
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 5,
|
||||
|
|
|
|||
|
|
@ -19,14 +19,10 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
const scriptLoadedRef = useRef(false);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const capInstanceURL = useAppSelector(
|
||||
(state) => state.siteConfig.basic.config.captcha_cap_instance_url,
|
||||
);
|
||||
const capKeyID = useAppSelector(
|
||||
(state) => state.siteConfig.basic.config.captcha_cap_key_id,
|
||||
);
|
||||
|
||||
|
||||
const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url);
|
||||
const capKeyID = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_key_id);
|
||||
|
||||
// Keep callback reference up to date
|
||||
useEffect(() => {
|
||||
onStateChangeRef.current = onStateChange;
|
||||
|
|
@ -36,18 +32,18 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
const applyFullWidthStyles = (widget: HTMLElement) => {
|
||||
const applyStyles = () => {
|
||||
// Style widget container
|
||||
widget.style.width = '100%';
|
||||
widget.style.display = 'block';
|
||||
widget.style.boxSizing = 'border-box';
|
||||
|
||||
widget.style.width = "100%";
|
||||
widget.style.display = "block";
|
||||
widget.style.boxSizing = "border-box";
|
||||
|
||||
// Style internal captcha element
|
||||
const captchaElement = widget.shadowRoot?.querySelector('.captcha') || widget.querySelector('.captcha');
|
||||
const captchaElement = widget.shadowRoot?.querySelector(".captcha") || widget.querySelector(".captcha");
|
||||
if (captchaElement) {
|
||||
const captchaEl = captchaElement as HTMLElement;
|
||||
captchaEl.style.width = '100%';
|
||||
captchaEl.style.maxWidth = 'none';
|
||||
captchaEl.style.minWidth = '0';
|
||||
captchaEl.style.boxSizing = 'border-box';
|
||||
captchaEl.style.width = "100%";
|
||||
captchaEl.style.maxWidth = "none";
|
||||
captchaEl.style.minWidth = "0";
|
||||
captchaEl.style.boxSizing = "border-box";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -60,13 +56,13 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
observer.observe(widget, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
attributes: true,
|
||||
});
|
||||
|
||||
|
||||
// Fallback timeout
|
||||
setTimeout(() => {
|
||||
applyStyles();
|
||||
|
|
@ -85,34 +81,34 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
widgetRef.current.remove?.();
|
||||
widgetRef.current = null;
|
||||
}
|
||||
|
||||
|
||||
// Clear container
|
||||
captchaRef.current.innerHTML = "";
|
||||
|
||||
|
||||
if (typeof window !== "undefined" && (window as any).Cap) {
|
||||
const widget = document.createElement("cap-widget");
|
||||
widget.setAttribute("data-cap-api-endpoint", `${capInstanceURL.replace(/\/$/, "")}/${capKeyID}/api/`);
|
||||
widget.id = "cap-widget";
|
||||
|
||||
|
||||
// Set internationalization attributes (Cap official i18n format)
|
||||
widget.setAttribute("data-cap-i18n-initial-state", t("captcha.cap.human"));
|
||||
widget.setAttribute("data-cap-i18n-verifying-label", t("captcha.cap.verifying"));
|
||||
widget.setAttribute("data-cap-i18n-solved-label", t("captcha.cap.verified"));
|
||||
|
||||
|
||||
captchaRef.current.appendChild(widget);
|
||||
|
||||
|
||||
widget.addEventListener("solve", (e: any) => {
|
||||
const token = e.detail.token;
|
||||
if (token) {
|
||||
onStateChangeRef.current({ ticket: token });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Apply fullWidth styles if needed
|
||||
if (fullWidth) {
|
||||
applyFullWidthStyles(widget);
|
||||
}
|
||||
|
||||
|
||||
widgetRef.current = widget;
|
||||
}
|
||||
};
|
||||
|
|
@ -130,7 +126,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
|
||||
const scriptId = "cap-widget-script";
|
||||
let script = document.getElementById(scriptId) as HTMLScriptElement;
|
||||
|
||||
|
||||
const initWidget = () => {
|
||||
scriptLoadedRef.current = true;
|
||||
// Add a small delay to ensure DOM is ready
|
||||
|
|
@ -174,11 +170,11 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
<Box
|
||||
sx={{
|
||||
// Container full width when needed
|
||||
...(fullWidth && { width: "100%" }),
|
||||
|
||||
|
||||
// CSS variables for Cloudreve theme adaptation
|
||||
"& cap-widget": {
|
||||
"--cap-border-radius": `${theme.shape.borderRadius}px`,
|
||||
|
|
@ -205,4 +201,4 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
|||
);
|
||||
};
|
||||
|
||||
export default CapCaptcha;
|
||||
export default CapCaptcha;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ export interface CaptchaParams {
|
|||
}
|
||||
|
||||
export const Captcha = (props: CaptchaProps) => {
|
||||
const captchaType = useAppSelector(
|
||||
(state) => state.siteConfig.basic.config.captcha_type,
|
||||
);
|
||||
const captchaType = useAppSelector((state) => state.siteConfig.basic.config.captcha_type);
|
||||
|
||||
// const recaptcha = useRecaptcha(setCaptchaLoading);
|
||||
// const tcaptcha = useTCaptcha(setCaptchaLoading);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,7 @@ export interface DefaultCaptchaProps {
|
|||
generation: number;
|
||||
}
|
||||
|
||||
const DefaultCaptcha = ({
|
||||
onStateChange,
|
||||
generation,
|
||||
...rest
|
||||
}: DefaultCaptchaProps) => {
|
||||
const DefaultCaptcha = ({ onStateChange, generation, ...rest }: DefaultCaptchaProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
|
@ -92,8 +88,9 @@ const DefaultCaptcha = ({
|
|||
htmlInput: {
|
||||
name: "captcha",
|
||||
id: "captcha",
|
||||
}
|
||||
}} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,17 +22,11 @@ window.recaptchaOptions = {
|
|||
useRecaptchaNet: true,
|
||||
};
|
||||
|
||||
const ReCaptchaV2 = ({
|
||||
onStateChange,
|
||||
generation,
|
||||
...rest
|
||||
}: ReCaptchaV2Props) => {
|
||||
const ReCaptchaV2 = ({ onStateChange, generation, ...rest }: ReCaptchaV2Props) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const captchaRef = useRef();
|
||||
const reCaptchaKey = useAppSelector(
|
||||
(state) => state.siteConfig.basic.config.captcha_ReCaptchaKey,
|
||||
);
|
||||
const reCaptchaKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_ReCaptchaKey);
|
||||
|
||||
const refreshCaptcha = async () => {
|
||||
captchaRef.current?.reset();
|
||||
|
|
|
|||
|
|
@ -10,17 +10,11 @@ export interface TurnstileProps {
|
|||
generation: number;
|
||||
}
|
||||
|
||||
const TurnstileCaptcha = ({
|
||||
onStateChange,
|
||||
generation,
|
||||
...rest
|
||||
}: TurnstileProps) => {
|
||||
const TurnstileCaptcha = ({ onStateChange, generation, ...rest }: TurnstileProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const captchaRef = useRef();
|
||||
const turnstileKey = useAppSelector(
|
||||
(state) => state.siteConfig.basic.config.turnstile_site_id,
|
||||
);
|
||||
const turnstileKey = useAppSelector((state) => state.siteConfig.basic.config.turnstile_site_id);
|
||||
|
||||
const refreshCaptcha = async () => {
|
||||
captchaRef.current?.reset();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
circularProgressClasses,
|
||||
CircularProgressProps,
|
||||
} from "@mui/material";
|
||||
import { Box, CircularProgress, circularProgressClasses, CircularProgressProps } from "@mui/material";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export interface FacebookCircularProgressProps extends CircularProgressProps {
|
||||
|
|
@ -11,38 +6,34 @@ export interface FacebookCircularProgressProps extends CircularProgressProps {
|
|||
fgColor?: string;
|
||||
}
|
||||
|
||||
const FacebookCircularProgress = forwardRef(
|
||||
({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => {
|
||||
return (
|
||||
<Box sx={{ position: "relative", ...sx }} ref={ref}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
bgColor ??
|
||||
theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||
}}
|
||||
size={40}
|
||||
thickness={4}
|
||||
{...rest}
|
||||
value={100}
|
||||
/>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: (theme) => fgColor ?? theme.palette.primary.main,
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
[`& .${circularProgressClasses.circle}`]: {
|
||||
strokeLinecap: "round",
|
||||
},
|
||||
}}
|
||||
size={40}
|
||||
thickness={4}
|
||||
{...rest}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
const FacebookCircularProgress = forwardRef(({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => {
|
||||
return (
|
||||
<Box sx={{ position: "relative", ...sx }} ref={ref}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
sx={{
|
||||
color: (theme) => bgColor ?? theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||
}}
|
||||
size={40}
|
||||
thickness={4}
|
||||
{...rest}
|
||||
value={100}
|
||||
/>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: (theme) => fgColor ?? theme.palette.primary.main,
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
[`& .${circularProgressClasses.circle}`]: {
|
||||
strokeLinecap: "round",
|
||||
},
|
||||
}}
|
||||
size={40}
|
||||
thickness={4}
|
||||
{...rest}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
export default FacebookCircularProgress;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
.fade-enter {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-active {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
.fade-exit {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
.fade-exit-active {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-exit-active {
|
||||
transition: opacity 150ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: opacity 150ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
import {
|
||||
InputAdornment,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { InputAdornment, TextField, TextFieldProps, useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
export interface OutlineIconTextFieldProps extends TextFieldProps<"outlined"> {
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
export const OutlineIconTextField = ({
|
||||
icon,
|
||||
...rest
|
||||
}: OutlineIconTextFieldProps) => {
|
||||
export const OutlineIconTextField = ({ icon, ...rest }: OutlineIconTextFieldProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
return (
|
||||
|
|
@ -21,11 +12,10 @@ export const OutlineIconTextField = ({
|
|||
{...rest}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: !isMobile && (
|
||||
<InputAdornment position="start">{icon}</InputAdornment>
|
||||
),
|
||||
startAdornment: !isMobile && <InputAdornment position="start">{icon}</InputAdornment>,
|
||||
...rest.InputProps,
|
||||
}
|
||||
}} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,12 +9,7 @@ export interface NothingProps {
|
|||
size?: number;
|
||||
}
|
||||
|
||||
export default function Nothing({
|
||||
primary,
|
||||
secondary,
|
||||
top = 20,
|
||||
size = 1,
|
||||
}: NothingProps) {
|
||||
export default function Nothing({ primary, secondary, top = 20, size = 1 }: NothingProps) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -41,10 +36,7 @@ export default function Nothing({
|
|||
{primary}
|
||||
</Typography>
|
||||
{secondary && (
|
||||
<Typography
|
||||
variant={"body2"}
|
||||
sx={{ color: (theme) => theme.palette.action.disabled }}
|
||||
>
|
||||
<Typography variant={"body2"} sx={{ color: (theme) => theme.palette.action.disabled }}>
|
||||
{secondary}
|
||||
</Typography>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,10 @@
|
|||
import * as React from "react";
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, ListItemIcon, ListItemText, Menu, MenuItem, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { StyledTab, StyledTabs } from "./StyledComponents.tsx";
|
||||
import CaretDown from "../Icons/CaretDown.tsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
bindMenu,
|
||||
bindTrigger,
|
||||
usePopupState,
|
||||
} from "material-ui-popup-state/hooks";
|
||||
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||
|
||||
export interface Tab<T> {
|
||||
label: React.ReactNode;
|
||||
|
|
@ -31,11 +18,7 @@ export interface ResponsiveTabsProps<T> {
|
|||
onChange: (event: React.SyntheticEvent, value: T) => void;
|
||||
}
|
||||
|
||||
const ResponsiveTabs = <T,>({
|
||||
tabs,
|
||||
value,
|
||||
onChange,
|
||||
}: ResponsiveTabsProps<T>) => {
|
||||
const ResponsiveTabs = <T,>({ tabs, value, onChange }: ResponsiveTabsProps<T>) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [hideTabs, setHideTabs] = useState(false);
|
||||
|
|
|
|||
|
|
@ -14,78 +14,71 @@ export const DefaultCloseAction = (snackbarId: SnackbarKey | undefined) => {
|
|||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => closeSnackbar(snackbarId)}
|
||||
color="inherit"
|
||||
size="small"
|
||||
>
|
||||
<Button onClick={() => closeSnackbar(snackbarId)} color="inherit" size="small">
|
||||
{t("dismiss", { ns: "common" })}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ErrorListDetailAction =
|
||||
(error: Response<any>) => (snackbarId: SnackbarKey | undefined) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
export const ErrorListDetailAction = (error: Response<any>) => (snackbarId: SnackbarKey | undefined) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const Close = DefaultCloseAction(snackbarId);
|
||||
const Close = DefaultCloseAction(snackbarId);
|
||||
|
||||
const showDetails = useCallback(() => {
|
||||
dispatch(showAggregatedErrorDialog(error));
|
||||
closeSnackbar(snackbarId);
|
||||
}, [dispatch, error, snackbarId]);
|
||||
const showDetails = useCallback(() => {
|
||||
dispatch(showAggregatedErrorDialog(error));
|
||||
closeSnackbar(snackbarId);
|
||||
}, [dispatch, error, snackbarId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={showDetails} color="inherit" size="small">
|
||||
{t("common:errorDetails")}
|
||||
</Button>
|
||||
{Close}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button onClick={showDetails} color="inherit" size="small">
|
||||
{t("common:errorDetails")}
|
||||
</Button>
|
||||
{Close}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewDstAction =
|
||||
(dst: string) => (snackbarId: SnackbarKey | undefined) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
export const ViewDstAction = (dst: string) => (snackbarId: SnackbarKey | undefined) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const Close = DefaultCloseAction(snackbarId);
|
||||
const Close = DefaultCloseAction(snackbarId);
|
||||
|
||||
const viewDst = useCallback(() => {
|
||||
dispatch(navigateToPath(FileManagerIndex.main, dst));
|
||||
closeSnackbar(snackbarId);
|
||||
}, [dispatch, snackbarId]);
|
||||
const viewDst = useCallback(() => {
|
||||
dispatch(navigateToPath(FileManagerIndex.main, dst));
|
||||
closeSnackbar(snackbarId);
|
||||
}, [dispatch, snackbarId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={viewDst} color="inherit" size="small">
|
||||
{t("application:modals.view")}
|
||||
</Button>
|
||||
{Close}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button onClick={viewDst} color="inherit" size="small">
|
||||
{t("application:modals.view")}
|
||||
</Button>
|
||||
{Close}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewDownloadLogAction =
|
||||
(downloadId: string) => (_snackbarId: SnackbarKey | undefined) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
export const ViewDownloadLogAction = (downloadId: string) => (_snackbarId: SnackbarKey | undefined) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const viewLogs = useCallback(() => {
|
||||
dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId }));
|
||||
}, [dispatch, downloadId]);
|
||||
const viewLogs = useCallback(() => {
|
||||
dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId }));
|
||||
}, [dispatch, downloadId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={viewLogs} color="inherit" size="small">
|
||||
{t("application:fileManager.details")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button onClick={viewLogs} color="inherit" size="small">
|
||||
{t("application:fileManager.details")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewTaskAction =
|
||||
(path: string = "/tasks") =>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,7 @@ export interface TimeBadgeProps extends TypographyProps {
|
|||
timeAgoThreshold?: number;
|
||||
}
|
||||
|
||||
const TimeBadge = ({
|
||||
timeAgoThreshold = defaultTimeAgoThreshold,
|
||||
datetime,
|
||||
sx,
|
||||
...rest
|
||||
}: TimeBadgeProps) => {
|
||||
const TimeBadge = ({ timeAgoThreshold = defaultTimeAgoThreshold, datetime, sx, ...rest }: TimeBadgeProps) => {
|
||||
const { t } = useTranslation();
|
||||
const timeStr = useMemo(() => {
|
||||
if (typeof datetime === "string") {
|
||||
|
|
|
|||
|
|
@ -31,13 +31,7 @@ const UserBadge = ({ textProps, user, uid, ...rest }: UserBadgeProps) => {
|
|||
maxWidth: "150px",
|
||||
}}
|
||||
>
|
||||
<UserAvatar
|
||||
overwriteTextSize
|
||||
user={user}
|
||||
onUserLoaded={(u) => setUserLoaded(u)}
|
||||
uid={uid}
|
||||
{...rest}
|
||||
/>
|
||||
<UserAvatar overwriteTextSize user={user} onUserLoaded={(u) => setUserLoaded(u)} uid={uid} {...rest} />
|
||||
<BadgeText {...textProps}>
|
||||
{userLoaded ? (
|
||||
userLoaded.id ? (
|
||||
|
|
@ -50,9 +44,7 @@ const UserBadge = ({ textProps, user, uid, ...rest }: UserBadgeProps) => {
|
|||
)}
|
||||
</BadgeText>
|
||||
</DefaultButton>
|
||||
{userLoaded && (
|
||||
<UserPopover user={userLoaded} {...bindPopover(popupState)} />
|
||||
)}
|
||||
{userLoaded && <UserPopover user={userLoaded} {...bindPopover(popupState)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
PopoverProps,
|
||||
styled,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Box, Button, PopoverProps, styled, Tooltip, Typography } from "@mui/material";
|
||||
import HoverPopover from "material-ui-popup-state/HoverPopover";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
|
@ -26,15 +19,7 @@ const ActionButton = styled(Button)({
|
|||
minWidth: "initial",
|
||||
});
|
||||
|
||||
export const UserProfile = ({
|
||||
user,
|
||||
open,
|
||||
displayOnly,
|
||||
}: {
|
||||
user: User;
|
||||
open: boolean;
|
||||
displayOnly?: boolean;
|
||||
}) => {
|
||||
export const UserProfile = ({ user, open, displayOnly }: { user: User; open: boolean; displayOnly?: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -55,22 +40,14 @@ export const UserProfile = ({
|
|||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<UserAvatar
|
||||
overwriteTextSize
|
||||
user={user}
|
||||
sx={{ width: 80, height: 80 }}
|
||||
/>
|
||||
<UserAvatar overwriteTextSize user={user} sx={{ width: 80, height: 80 }} />
|
||||
<Box sx={{ ml: 2 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Typography variant={"h6"} fontWeight={600}>
|
||||
{user.id ? user.nickname : t("application:modals.anonymous")}
|
||||
</Typography>
|
||||
{displayOnly && (
|
||||
<Typography
|
||||
variant={"body2"}
|
||||
sx={{ ml: 1 }}
|
||||
color={"text.secondary"}
|
||||
>
|
||||
<Typography variant={"body2"} sx={{ ml: 1 }} color={"text.secondary"}>
|
||||
{loadedUser?.group ? loadedUser.group.name : ""}
|
||||
</Typography>
|
||||
)}
|
||||
|
|
@ -85,12 +62,7 @@ export const UserProfile = ({
|
|||
<Trans
|
||||
i18nKey={"setting.accountCreatedAt"}
|
||||
ns={"application"}
|
||||
components={[
|
||||
<TimeBadge
|
||||
variant={"inherit"}
|
||||
datetime={loadedUser.created_at}
|
||||
/>,
|
||||
]}
|
||||
components={[<TimeBadge variant={"inherit"} datetime={loadedUser.created_at} />]}
|
||||
/>
|
||||
</Typography>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DraggableDialog, {
|
||||
StyledDialogContentText,
|
||||
} from "./DraggableDialog.tsx";
|
||||
import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { closeAggregatedErrorDialog } from "../../redux/globalStateSlice.ts";
|
||||
import {
|
||||
|
|
@ -44,18 +42,9 @@ const ErrorTable = (props: ErrorTableProps) => {
|
|||
</TableHead>
|
||||
<TableBody>
|
||||
{Object.keys(props.errors).map((id) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={id}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
<TableRow hover key={id} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
||||
<TableCell component="th" scope="row">
|
||||
{props.files[id] && (
|
||||
<FileBadge
|
||||
sx={{ maxWidth: "250px" }}
|
||||
file={props.files[id]}
|
||||
/>
|
||||
)}
|
||||
{props.files[id] && <FileBadge sx={{ maxWidth: "250px" }} file={props.files[id]} />}
|
||||
{!props.files[id] && !id.startsWith(CrUriPrefix) && id}
|
||||
{!props.files[id] && id.startsWith(CrUriPrefix) && (
|
||||
<FileBadge
|
||||
|
|
@ -80,12 +69,8 @@ const AggregatedErrorDetail = () => {
|
|||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.globalState.aggregatedErrorDialogOpen,
|
||||
);
|
||||
const files = useAppSelector(
|
||||
(state) => state.globalState.aggregatedErrorFile,
|
||||
);
|
||||
const open = useAppSelector((state) => state.globalState.aggregatedErrorDialogOpen);
|
||||
const files = useAppSelector((state) => state.globalState.aggregatedErrorFile);
|
||||
const error = useAppSelector((state) => state.globalState.aggregatedError);
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(closeAggregatedErrorDialog());
|
||||
|
|
@ -119,9 +104,7 @@ const AggregatedErrorDetail = () => {
|
|||
>
|
||||
<DialogContent>
|
||||
<Stack spacing={2}>
|
||||
<StyledDialogContentText>
|
||||
{rootError && rootError.message}
|
||||
</StyledDialogContentText>
|
||||
<StyledDialogContentText>{rootError && rootError.message}</StyledDialogContentText>
|
||||
{files && errors && <ErrorTable errors={errors} files={files} />}
|
||||
{rootError && rootError.cid && (
|
||||
<DialogContentText variant={"caption"}>
|
||||
|
|
|
|||
|
|
@ -12,15 +12,9 @@ const BatchDownloadLog = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
const logRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.globalState.batchDownloadLogDialogOpen,
|
||||
);
|
||||
const downloadId = useAppSelector(
|
||||
(state) => state.globalState.batchDownloadLogDialogId,
|
||||
);
|
||||
const logs = useAppSelector(
|
||||
(state) => state.globalState.batchDownloadLogDialogLogs?.[downloadId ?? ""],
|
||||
);
|
||||
const open = useAppSelector((state) => state.globalState.batchDownloadLogDialogOpen);
|
||||
const downloadId = useAppSelector((state) => state.globalState.batchDownloadLogDialogId);
|
||||
const logs = useAppSelector((state) => state.globalState.batchDownloadLogDialogLogs?.[downloadId ?? ""]);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(closeBatchDownloadLogDialog());
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { DialogContent, Stack } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||
import { useCallback } from "react";
|
||||
import DraggableDialog, {
|
||||
StyledDialogContentText,
|
||||
} from "./DraggableDialog.tsx";
|
||||
import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx";
|
||||
import { generalDialogPromisePool } from "../../redux/thunks/dialog.ts";
|
||||
import { closeConfirmDialog } from "../../redux/globalStateSlice.ts";
|
||||
|
||||
|
|
@ -13,12 +11,8 @@ const Confirmation = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const open = useAppSelector((state) => state.globalState.confirmDialogOpen);
|
||||
const message = useAppSelector(
|
||||
(state) => state.globalState.confirmDialogMessage,
|
||||
);
|
||||
const promiseId = useAppSelector(
|
||||
(state) => state.globalState.confirmPromiseId,
|
||||
);
|
||||
const message = useAppSelector((state) => state.globalState.confirmDialogMessage);
|
||||
const promiseId = useAppSelector((state) => state.globalState.confirmPromiseId);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(closeConfirmDialog());
|
||||
|
|
|
|||
|
|
@ -1,33 +1,29 @@
|
|||
import { AccordionDetailsProps, Box, styled } from "@mui/material";
|
||||
import MuiAccordion, { AccordionProps } from "@mui/material/Accordion";
|
||||
import MuiAccordionSummary, {
|
||||
AccordionSummaryProps,
|
||||
} from "@mui/material/AccordionSummary";
|
||||
import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary";
|
||||
import MuiAccordionDetails from "@mui/material/AccordionDetails";
|
||||
import { useState } from "react";
|
||||
import { CaretDownIcon } from "../FileManager/TreeView/TreeFile.tsx";
|
||||
import { DefaultButton } from "../Common/StyledComponents.tsx";
|
||||
|
||||
const Accordion = styled((props: AccordionProps) => (
|
||||
<MuiAccordion disableGutters elevation={0} square {...props} />
|
||||
))(({ theme, expanded }) => ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: expanded
|
||||
? theme.palette.mode == "light"
|
||||
? "rgba(0, 0, 0, 0.06)"
|
||||
: "rgba(255, 255, 255, 0.09)"
|
||||
: "initial",
|
||||
"&:not(:last-child)": {
|
||||
borderBottom: 0,
|
||||
},
|
||||
"&::before": {
|
||||
display: "none",
|
||||
},
|
||||
}));
|
||||
const Accordion = styled((props: AccordionProps) => <MuiAccordion disableGutters elevation={0} square {...props} />)(
|
||||
({ theme, expanded }) => ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: expanded
|
||||
? theme.palette.mode == "light"
|
||||
? "rgba(0, 0, 0, 0.06)"
|
||||
: "rgba(255, 255, 255, 0.09)"
|
||||
: "initial",
|
||||
"&:not(:last-child)": {
|
||||
borderBottom: 0,
|
||||
},
|
||||
"&::before": {
|
||||
display: "none",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const AccordionSummary = styled((props: AccordionSummaryProps) => (
|
||||
<MuiAccordionSummary {...props} />
|
||||
))(() => ({
|
||||
const AccordionSummary = styled((props: AccordionSummaryProps) => <MuiAccordionSummary {...props} />)(() => ({
|
||||
flexDirection: "row-reverse",
|
||||
minHeight: 0,
|
||||
padding: 0,
|
||||
|
|
@ -40,22 +36,17 @@ const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
|||
padding: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>(
|
||||
({ theme, expanded }) => ({
|
||||
justifyContent: "flex-start",
|
||||
backgroundColor: expanded
|
||||
? "initial"
|
||||
: theme.palette.mode == "light"
|
||||
? "rgba(0, 0, 0, 0.06)"
|
||||
: "rgba(255, 255, 255, 0.09)",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
theme.palette.mode == "light"
|
||||
? "rgba(0, 0, 0, 0.09)"
|
||||
: "rgba(255, 255, 255, 0.13)",
|
||||
},
|
||||
}),
|
||||
);
|
||||
const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>(({ theme, expanded }) => ({
|
||||
justifyContent: "flex-start",
|
||||
backgroundColor: expanded
|
||||
? "initial"
|
||||
: theme.palette.mode == "light"
|
||||
? "rgba(0, 0, 0, 0.06)"
|
||||
: "rgba(255, 255, 255, 0.09)",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.09)" : "rgba(255, 255, 255, 0.13)",
|
||||
},
|
||||
}));
|
||||
|
||||
export interface DialogAccordionProps {
|
||||
children?: React.ReactNode;
|
||||
|
|
@ -73,17 +64,11 @@ const DialogAccordion = (props: DialogAccordionProps) => {
|
|||
<Box>
|
||||
<Accordion expanded={expanded} onChange={handleChange}>
|
||||
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
|
||||
<SummaryButton
|
||||
expanded={expanded}
|
||||
fullWidth
|
||||
startIcon={<CaretDownIcon expanded={expanded} />}
|
||||
>
|
||||
<SummaryButton expanded={expanded} fullWidth startIcon={<CaretDownIcon expanded={expanded} />}>
|
||||
{props.title}
|
||||
</SummaryButton>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails {...props.accordionDetailProps}>
|
||||
{props.children}
|
||||
</AccordionDetails>
|
||||
<AccordionDetails {...props.accordionDetailProps}>{props.children}</AccordionDetails>
|
||||
</Accordion>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
DialogContent,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { DialogContent, List, ListItemButton, ListItemText } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||
import React, { useCallback } from "react";
|
||||
import DraggableDialog from "./DraggableDialog.tsx";
|
||||
|
|
@ -15,16 +10,10 @@ const SelectOption = () => {
|
|||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.globalState.selectOptionDialogOpen,
|
||||
);
|
||||
const open = useAppSelector((state) => state.globalState.selectOptionDialogOpen);
|
||||
const title = useAppSelector((state) => state.globalState.selectOptionTitle);
|
||||
const promiseId = useAppSelector(
|
||||
(state) => state.globalState.selectOptionPromiseId,
|
||||
);
|
||||
const options = useAppSelector(
|
||||
(state) => state.globalState.selectOptionDialogOptions,
|
||||
);
|
||||
const promiseId = useAppSelector((state) => state.globalState.selectOptionPromiseId);
|
||||
const options = useAppSelector((state) => state.globalState.selectOptionDialogOptions);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(closeSelectOptionDialog());
|
||||
|
|
@ -65,8 +54,9 @@ const SelectOption = () => {
|
|||
fontWeight: "bold",
|
||||
},
|
||||
|
||||
secondary: { variant: "body2" }
|
||||
}} />
|
||||
secondary: { variant: "body2" },
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
import * as React from "react";
|
||||
import { Menu, type MenuProps } from "@mui/material";
|
||||
|
||||
const HoverMenu: React.ComponentType<MenuProps> = React.forwardRef(
|
||||
function HoverMenu(props: MenuProps, ref): any {
|
||||
return (
|
||||
<Menu
|
||||
{...props}
|
||||
ref={ref}
|
||||
style={{ pointerEvents: "none", ...props.style }}
|
||||
slotProps={{
|
||||
...props.slotProps,
|
||||
paper: {
|
||||
...props.slotProps?.paper,
|
||||
style: {
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
const HoverMenu: React.ComponentType<MenuProps> = React.forwardRef(function HoverMenu(props: MenuProps, ref): any {
|
||||
return (
|
||||
<Menu
|
||||
{...props}
|
||||
ref={ref}
|
||||
style={{ pointerEvents: "none", ...props.style }}
|
||||
slotProps={{
|
||||
...props.slotProps,
|
||||
paper: {
|
||||
...props.slotProps?.paper,
|
||||
style: {
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default HoverMenu;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@ import { ViewersByID } from "../../../redux/siteConfigSlice.ts";
|
|||
import { ListItemIcon, ListItemText } from "@mui/material";
|
||||
import { ViewerIcon } from "../Dialogs/OpenWith.tsx";
|
||||
import { SquareMenuItem } from "./ContextMenu.tsx";
|
||||
import {
|
||||
CascadingContext,
|
||||
CascadingMenuItem,
|
||||
CascadingSubmenu,
|
||||
} from "./CascadingMenu.tsx";
|
||||
import { CascadingContext, CascadingMenuItem, CascadingSubmenu } from "./CascadingMenu.tsx";
|
||||
import { NewFileTemplate, Viewer } from "../../../api/explorer.ts";
|
||||
import { createNew } from "../../../redux/thunks/file.ts";
|
||||
import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
|
||||
|
|
@ -72,10 +68,7 @@ const NewFileTemplateMenuItems = (props: SubMenuItemsProps) => {
|
|||
|
||||
if (viewer.templates.length == 1) {
|
||||
return (
|
||||
<SquareMenuItem
|
||||
key={viewer.id}
|
||||
onClick={onClick(viewer, viewer.templates[0])}
|
||||
>
|
||||
<SquareMenuItem key={viewer.id} onClick={onClick(viewer, viewer.templates[0])}>
|
||||
<ListItemIcon>
|
||||
<ViewerIcon size={20} viewer={viewer} py={0} />
|
||||
</ListItemIcon>
|
||||
|
|
|
|||
|
|
@ -5,16 +5,10 @@ import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
|
||||
import {
|
||||
applyIconColor,
|
||||
dialogBasedMoveCopy,
|
||||
} from "../../../redux/thunks/file.ts";
|
||||
import { applyIconColor, dialogBasedMoveCopy } from "../../../redux/thunks/file.ts";
|
||||
import { ListItemIcon, ListItemText } from "@mui/material";
|
||||
import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx";
|
||||
import {
|
||||
setChangeIconDialog,
|
||||
setPinFileDialog,
|
||||
} from "../../../redux/globalStateSlice.ts";
|
||||
import { setChangeIconDialog, setPinFileDialog } from "../../../redux/globalStateSlice.ts";
|
||||
import { getFileLinkedUri } from "../../../util";
|
||||
import PinOutlined from "../../Icons/PinOutlined.tsx";
|
||||
import EmojiEdit from "../../Icons/EmojiEdit.tsx";
|
||||
|
|
@ -45,16 +39,11 @@ const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
|
|||
[dispatch, targets],
|
||||
);
|
||||
const showDivider =
|
||||
(displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) &&
|
||||
displayOpt.showChangeFolderColor;
|
||||
(displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) && displayOpt.showChangeFolderColor;
|
||||
return (
|
||||
<>
|
||||
{displayOpt.showMove && (
|
||||
<CascadingMenuItem
|
||||
onClick={onClick(() =>
|
||||
dispatch(dialogBasedMoveCopy(0, targets, false)),
|
||||
)}
|
||||
>
|
||||
<CascadingMenuItem onClick={onClick(() => dispatch(dialogBasedMoveCopy(0, targets, false)))}>
|
||||
<ListItemIcon>
|
||||
<FolderArrowRightOutlined fontSize="small" />
|
||||
</ListItemIcon>
|
||||
|
|
@ -92,18 +81,14 @@ const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
|
|||
<ListItemIcon>
|
||||
<EmojiEdit fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
{t("application:fileManager.customizeIcon")}
|
||||
</ListItemText>
|
||||
<ListItemText>{t("application:fileManager.customizeIcon")}</ListItemText>
|
||||
</CascadingMenuItem>
|
||||
)}
|
||||
{showDivider && <DenseDivider />}
|
||||
{displayOpt.showChangeFolderColor && (
|
||||
<FolderColorQuickAction
|
||||
file={targets[0]}
|
||||
onColorChange={(color) =>
|
||||
onClick(() => dispatch(applyIconColor(0, targets, color, true)))()
|
||||
}
|
||||
onColorChange={(color) => onClick(() => dispatch(applyIconColor(0, targets, color, true)))()}
|
||||
sx={{
|
||||
maxWidth: "204px",
|
||||
margin: (theme) => `0 ${theme.spacing(0.5)}`,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
|
|||
import { setTagsDialog } from "../../../redux/globalStateSlice.ts";
|
||||
import { ListItemIcon, ListItemText } from "@mui/material";
|
||||
import Tags from "../../Icons/Tags.tsx";
|
||||
import {
|
||||
DenseDivider,
|
||||
SquareMenuItem,
|
||||
SubMenuItemsProps,
|
||||
} from "./ContextMenu.tsx";
|
||||
import { DenseDivider, SquareMenuItem, SubMenuItemsProps } from "./ContextMenu.tsx";
|
||||
import SessionManager, { UserSettings } from "../../../session";
|
||||
import { UsedTags } from "../../../session/utils.ts";
|
||||
import Checkmark from "../../Icons/Checkmark.tsx";
|
||||
|
|
@ -111,33 +107,20 @@ const TagMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
|
|||
</CascadingMenuItem>
|
||||
{tags.length > 0 && <DenseDivider />}
|
||||
{tags.map((tag) => (
|
||||
<SquareMenuItem
|
||||
key={tag.key}
|
||||
onClick={() => onTagChange(tag, !tag.selected)}
|
||||
>
|
||||
<SquareMenuItem key={tag.key} onClick={() => onTagChange(tag, !tag.selected)}>
|
||||
{tag.selected && (
|
||||
<>
|
||||
<ListItemIcon>
|
||||
<Checkmark />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<FileTag
|
||||
disableClick
|
||||
spacing={1}
|
||||
label={tag.key}
|
||||
tagColor={tag.color}
|
||||
/>
|
||||
<FileTag disableClick spacing={1} label={tag.key} tagColor={tag.color} />
|
||||
</ListItemText>
|
||||
</>
|
||||
)}
|
||||
{!tag.selected && (
|
||||
<ListItemText inset>
|
||||
<FileTag
|
||||
disableClick
|
||||
spacing={1}
|
||||
label={tag.key}
|
||||
tagColor={tag.color}
|
||||
/>
|
||||
<FileTag disableClick spacing={1} label={tag.key} tagColor={tag.color} />
|
||||
</ListItemText>
|
||||
)}
|
||||
</SquareMenuItem>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
DialogContent,
|
||||
Skeleton,
|
||||
styled,
|
||||
Tab,
|
||||
Tabs,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Button, DialogContent, Skeleton, styled, Tab, Tabs, useTheme } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||
|
|
@ -73,18 +64,10 @@ const ChangeIcon = () => {
|
|||
const [tabValue, setTabValue] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.globalState.changeIconDialogOpen,
|
||||
);
|
||||
const targets = useAppSelector(
|
||||
(state) => state.globalState.changeIconDialogFile,
|
||||
);
|
||||
const emojiStr = useAppSelector(
|
||||
(state) => state.siteConfig.emojis.config.emoji_preset,
|
||||
);
|
||||
const emojiStrLoaded = useAppSelector(
|
||||
(state) => state.siteConfig.emojis.loaded,
|
||||
);
|
||||
const open = useAppSelector((state) => state.globalState.changeIconDialogOpen);
|
||||
const targets = useAppSelector((state) => state.globalState.changeIconDialogFile);
|
||||
const emojiStr = useAppSelector((state) => state.siteConfig.emojis.config.emoji_preset);
|
||||
const emojiStrLoaded = useAppSelector((state) => state.siteConfig.emojis.loaded);
|
||||
|
||||
const emojiSetting = useMemo((): EmojiSetting => {
|
||||
if (!emojiStr) return {};
|
||||
|
|
@ -163,9 +146,7 @@ const ChangeIcon = () => {
|
|||
onChange={handleTabChange}
|
||||
>
|
||||
{emojiStrLoaded ? (
|
||||
Object.keys(emojiSetting).map((key) => (
|
||||
<StyledTab label={key} key={key} />
|
||||
))
|
||||
Object.keys(emojiSetting).map((key) => <StyledTab label={key} key={key} />)
|
||||
) : (
|
||||
<StyledTab label={<Skeleton sx={{ minWidth: "20px" }} />} />
|
||||
)}
|
||||
|
|
@ -177,9 +158,7 @@ const ChangeIcon = () => {
|
|||
<CustomTabPanel value={tabValue} index={index}>
|
||||
<SelectorBox>
|
||||
{emojiSetting[key].map((emoji) => (
|
||||
<EmojiButton onClick={onAccept(emoji)}>
|
||||
{emoji}
|
||||
</EmojiButton>
|
||||
<EmojiButton onClick={onAccept(emoji)}>{emoji}</EmojiButton>
|
||||
))}
|
||||
</SelectorBox>
|
||||
</CustomTabPanel>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import { setRenameFileModalError } from "../../../redux/fileManagerSlice.ts";
|
|||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||
import { createNewDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
||||
import { FilledTextField } from "../../Common/StyledComponents.tsx";
|
||||
import {
|
||||
closeCreateNewDialog,
|
||||
CreateNewDialogType,
|
||||
} from "../../../redux/globalStateSlice.ts";
|
||||
import { closeCreateNewDialog, CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
|
||||
import { submitCreateNew } from "../../../redux/thunks/file.ts";
|
||||
import { FileType } from "../../../api/explorer.ts";
|
||||
|
||||
|
|
@ -24,15 +21,10 @@ const CreateNew = () => {
|
|||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const open = useAppSelector((state) => state.globalState.createNewDialogOpen);
|
||||
const promiseId = useAppSelector(
|
||||
(state) => state.globalState.createNewPromiseId,
|
||||
);
|
||||
const promiseId = useAppSelector((state) => state.globalState.createNewPromiseId);
|
||||
const type = useAppSelector((state) => state.globalState.createNewDialogType);
|
||||
const defaultName = useAppSelector(
|
||||
(state) => state.globalState.createNewDialogDefault,
|
||||
);
|
||||
const fmIndex =
|
||||
useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0;
|
||||
const defaultName = useAppSelector((state) => state.globalState.createNewDialogDefault);
|
||||
const fmIndex = useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
|
|
@ -54,13 +46,7 @@ const CreateNew = () => {
|
|||
}
|
||||
|
||||
setLoading(true);
|
||||
dispatch(
|
||||
submitCreateNew(
|
||||
fmIndex,
|
||||
name,
|
||||
type == CreateNewDialogType.folder ? FileType.folder : FileType.file,
|
||||
),
|
||||
)
|
||||
dispatch(submitCreateNew(fmIndex, name, type == CreateNewDialogType.folder ? FileType.folder : FileType.file))
|
||||
.then((f) => {
|
||||
if (promiseId) {
|
||||
createNewDialogPromisePool[promiseId]?.resolve(f);
|
||||
|
|
@ -86,12 +72,7 @@ const CreateNew = () => {
|
|||
if (open) {
|
||||
const lastDot = name.lastIndexOf(".");
|
||||
setTimeout(
|
||||
() =>
|
||||
inputRef.current &&
|
||||
inputRef.current.setSelectionRange(
|
||||
0,
|
||||
lastDot > 0 ? lastDot : name.length,
|
||||
),
|
||||
() => inputRef.current && inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : name.length),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
|
@ -110,9 +91,7 @@ const CreateNew = () => {
|
|||
return (
|
||||
<DraggableDialog
|
||||
title={t(
|
||||
type == CreateNewDialogType.folder
|
||||
? "application:fileManager.newFolder"
|
||||
: "application:fileManager.newFile",
|
||||
type == CreateNewDialogType.folder ? "application:fileManager.newFolder" : "application:fileManager.newFile",
|
||||
)}
|
||||
showActions
|
||||
loading={loading}
|
||||
|
|
@ -137,9 +116,7 @@ const CreateNew = () => {
|
|||
helperText={error}
|
||||
margin="dense"
|
||||
label={t(
|
||||
type == CreateNewDialogType.folder
|
||||
? "application:modals.folderName"
|
||||
: "application:modals.fileName",
|
||||
type == CreateNewDialogType.folder ? "application:modals.folderName" : "application:modals.fileName",
|
||||
)}
|
||||
type="text"
|
||||
value={name}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ const DirectLinks = () => {
|
|||
|
||||
const [showFileName, setShowFileName] = useState(false);
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.globalState.directLinkDialogOpen,
|
||||
);
|
||||
const open = useAppSelector((state) => state.globalState.directLinkDialogOpen);
|
||||
const targets = useAppSelector((state) => state.globalState.directLinkRes);
|
||||
|
||||
const contents = useMemo(() => {
|
||||
|
|
@ -86,7 +84,7 @@ const DirectLinks = () => {
|
|||
variant="outlined"
|
||||
fullWidth
|
||||
slotProps={{
|
||||
htmlInput: { readonly: true }
|
||||
htmlInput: { readonly: true },
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,9 @@
|
|||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { DialogContent, Stack } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import {
|
||||
ChangeEvent,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
closeRenameFileModal,
|
||||
setRenameFileModalError,
|
||||
} from "../../../redux/fileManagerSlice.ts";
|
||||
import DraggableDialog, {
|
||||
StyledDialogContentText,
|
||||
} from "../../Dialogs/DraggableDialog.tsx";
|
||||
import { ChangeEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { closeRenameFileModal, setRenameFileModalError } from "../../../redux/fileManagerSlice.ts";
|
||||
import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx";
|
||||
import { renameDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
||||
import { validateFileName } from "../../../redux/thunks/file.ts";
|
||||
import { FileType } from "../../../api/explorer.ts";
|
||||
|
|
@ -33,21 +21,11 @@ const Rename = () => {
|
|||
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.fileManager[0].renameFileModalOpen,
|
||||
);
|
||||
const targets = useAppSelector(
|
||||
(state) => state.fileManager[0].renameFileModalSelected,
|
||||
);
|
||||
const promiseId = useAppSelector(
|
||||
(state) => state.fileManager[0].renameFileModalPromiseId,
|
||||
);
|
||||
const loading = useAppSelector(
|
||||
(state) => state.fileManager[0].renameFileModalLoading,
|
||||
);
|
||||
const error = useAppSelector(
|
||||
(state) => state.fileManager[0].renameFileModalError,
|
||||
);
|
||||
const open = useAppSelector((state) => state.fileManager[0].renameFileModalOpen);
|
||||
const targets = useAppSelector((state) => state.fileManager[0].renameFileModalSelected);
|
||||
const promiseId = useAppSelector((state) => state.fileManager[0].renameFileModalPromiseId);
|
||||
const loading = useAppSelector((state) => state.fileManager[0].renameFileModalLoading);
|
||||
const error = useAppSelector((state) => state.fileManager[0].renameFileModalError);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(
|
||||
|
|
@ -67,13 +45,7 @@ const Rename = () => {
|
|||
e.preventDefault();
|
||||
}
|
||||
if (promiseId) {
|
||||
dispatch(
|
||||
validateFileName(
|
||||
0,
|
||||
renameDialogPromisePool[promiseId]?.resolve,
|
||||
name,
|
||||
),
|
||||
);
|
||||
dispatch(validateFileName(0, renameDialogPromisePool[promiseId]?.resolve, name));
|
||||
}
|
||||
},
|
||||
[promiseId, name],
|
||||
|
|
@ -95,12 +67,8 @@ const Rename = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (targets && open && inputRef.current) {
|
||||
const lastDot =
|
||||
targets.type == FileType.folder ? 0 : targets.name.lastIndexOf(".");
|
||||
inputRef.current.setSelectionRange(
|
||||
0,
|
||||
lastDot > 0 ? lastDot : targets.name.length,
|
||||
);
|
||||
const lastDot = targets.type == FileType.folder ? 0 : targets.name.lastIndexOf(".");
|
||||
inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : targets.name.length);
|
||||
}
|
||||
}, [inputRef.current, open]);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,8 @@ import { Trans, useTranslation } from "react-i18next";
|
|||
import { Button, DialogContent, Stack } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import { useCallback } from "react";
|
||||
import DraggableDialog, {
|
||||
StyledDialogContentText,
|
||||
} from "../../Dialogs/DraggableDialog.tsx";
|
||||
import {
|
||||
askSaveAs,
|
||||
staleVersionDialogPromisePool,
|
||||
} from "../../../redux/thunks/dialog.ts";
|
||||
import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx";
|
||||
import { askSaveAs, staleVersionDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
||||
import { closeStaleVersionDialog } from "../../../redux/globalStateSlice.ts";
|
||||
import CrUri from "../../../util/uri.ts";
|
||||
|
||||
|
|
@ -16,13 +11,9 @@ const StaleVersionConfirm = () => {
|
|||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const open = useAppSelector(
|
||||
(state) => state.globalState.staleVersionDialogOpen,
|
||||
);
|
||||
const open = useAppSelector((state) => state.globalState.staleVersionDialogOpen);
|
||||
const uri = useAppSelector((state) => state.globalState.staleVersionUri);
|
||||
const promiseId = useAppSelector(
|
||||
(state) => state.globalState.staleVersionPromiseId,
|
||||
);
|
||||
const promiseId = useAppSelector((state) => state.globalState.staleVersionPromiseId);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(closeStaleVersionDialog());
|
||||
|
|
@ -88,11 +79,7 @@ const StaleVersionConfirm = () => {
|
|||
<StyledDialogContentText>
|
||||
{t("modals.conflictDes1")}
|
||||
<ul>
|
||||
<Trans
|
||||
i18nKey="modals.conflictDes2"
|
||||
ns={"application"}
|
||||
components={[<li key={0} />, <li key={1} />]}
|
||||
/>
|
||||
<Trans i18nKey="modals.conflictDes2" ns={"application"} components={[<li key={0} />, <li key={1} />]} />
|
||||
</ul>
|
||||
</StyledDialogContentText>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -55,8 +55,7 @@ const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => {
|
|||
zIndex: 1610 - i - 1,
|
||||
top: (i + 1) * 4,
|
||||
left: (i + 1) * 4,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["width", "height"]),
|
||||
transition: (theme) => theme.transitions.create(["width", "height"]),
|
||||
}}
|
||||
elevation={3}
|
||||
/>
|
||||
|
|
@ -68,14 +67,12 @@ const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => {
|
|||
const DragLayer = () => {
|
||||
DisableDropDelay();
|
||||
|
||||
const { itemType, isDragging, item, pointerOffset } = useDragLayer(
|
||||
(monitor) => ({
|
||||
item: monitor.getItem(),
|
||||
itemType: monitor.getItemType(),
|
||||
pointerOffset: monitor.getClientOffset(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
);
|
||||
const { itemType, isDragging, item, pointerOffset } = useDragLayer((monitor) => ({
|
||||
item: monitor.getItem(),
|
||||
itemType: monitor.getItemType(),
|
||||
pointerOffset: monitor.getClientOffset(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}));
|
||||
|
||||
const selected = useAppSelector((state) => state.fileManager[0].selected);
|
||||
const draggingFiles = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ const threshold = 0.1;
|
|||
|
||||
const useDragScrolling = (containers: string[]) => {
|
||||
const isScrolling = useRef(false);
|
||||
const targets = containers.map(
|
||||
(id) => document.querySelector(id) as HTMLElement,
|
||||
);
|
||||
const targets = containers.map((id) => document.querySelector(id) as HTMLElement);
|
||||
const rects = useRef<DOMRect[]>([]);
|
||||
|
||||
const goDown = (target: HTMLElement) => {
|
||||
|
|
@ -41,16 +39,10 @@ const useDragScrolling = (containers: string[]) => {
|
|||
}
|
||||
|
||||
const height = rect.bottom - rect.top;
|
||||
if (
|
||||
event.clientY > rect.top &&
|
||||
event.clientY < rect.top + threshold * height
|
||||
) {
|
||||
if (event.clientY > rect.top && event.clientY < rect.top + threshold * height) {
|
||||
isScrolling.current = true;
|
||||
window.requestAnimationFrame(goUp(targets[index]));
|
||||
} else if (
|
||||
event.clientY < rect.bottom &&
|
||||
event.clientY > rect.bottom - threshold * height
|
||||
) {
|
||||
} else if (event.clientY < rect.bottom && event.clientY > rect.bottom - threshold * height) {
|
||||
isScrolling.current = true;
|
||||
window.requestAnimationFrame(goDown(targets[index]));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,11 @@
|
|||
import {
|
||||
Chip,
|
||||
ChipProps,
|
||||
darken,
|
||||
styled,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Chip, ChipProps, darken, styled, Tooltip, useTheme } from "@mui/material";
|
||||
import { useCallback, useContext } from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import { Metadata } from "../../../api/explorer.ts";
|
||||
import { searchMetadata } from "../../../redux/thunks/filemanager.ts";
|
||||
import { FmIndexContext } from "../FmIndexContext.tsx";
|
||||
|
||||
export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({
|
||||
defaultStyle,
|
||||
}) => {
|
||||
export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({ defaultStyle }) => {
|
||||
const base = {
|
||||
"& .MuiChip-deleteIcon": {},
|
||||
};
|
||||
|
|
@ -30,16 +21,7 @@ export interface FileTagProps extends ChipProps {
|
|||
disableClick?: boolean;
|
||||
}
|
||||
|
||||
const FileTag = ({
|
||||
disableClick,
|
||||
tagColor,
|
||||
sx,
|
||||
label,
|
||||
defaultStyle,
|
||||
spacing,
|
||||
openInNewTab,
|
||||
...rest
|
||||
}: FileTagProps) => {
|
||||
const FileTag = ({ disableClick, tagColor, sx, label, defaultStyle, spacing, openInNewTab, ...rest }: FileTagProps) => {
|
||||
const theme = useTheme();
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
const dispatch = useAppDispatch();
|
||||
|
|
@ -56,23 +38,13 @@ const FileTag = ({
|
|||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
dispatch(
|
||||
searchMetadata(
|
||||
fmIndex,
|
||||
Metadata.tag_prefix + label,
|
||||
tagColor,
|
||||
openInNewTab,
|
||||
),
|
||||
);
|
||||
dispatch(searchMetadata(fmIndex, Metadata.tag_prefix + label, tagColor, openInNewTab));
|
||||
},
|
||||
[root, dispatch, fmIndex, disableClick],
|
||||
);
|
||||
|
||||
const hackColor =
|
||||
!!tagColor &&
|
||||
theme.palette.getContrastText(tagColor) != theme.palette.text.primary
|
||||
? "error"
|
||||
: undefined;
|
||||
!!tagColor && theme.palette.getContrastText(tagColor) != theme.palette.text.primary ? "error" : undefined;
|
||||
return (
|
||||
<Tooltip title={label}>
|
||||
<TagChip
|
||||
|
|
|
|||
|
|
@ -12,86 +12,80 @@ export interface FileTagSummaryProps {
|
|||
[key: string]: any;
|
||||
}
|
||||
|
||||
const FileTagSummary = memo(
|
||||
({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const popupState = usePopupState({
|
||||
variant: "popover",
|
||||
popupId: "demoMenu",
|
||||
});
|
||||
const FileTagSummary = memo(({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const popupState = usePopupState({
|
||||
variant: "popover",
|
||||
popupId: "demoMenu",
|
||||
});
|
||||
|
||||
const { open, ...rest } = bindPopover(popupState);
|
||||
const stopPropagation = useCallback((e: any) => e.stopPropagation(), []);
|
||||
const [shown, hidden] = useMemo(() => {
|
||||
if (tags.length <= max) {
|
||||
return [tags, []];
|
||||
}
|
||||
return [tags.slice(0, max), tags.slice(max)];
|
||||
}, [tags, max]);
|
||||
const { open, ...rest } = bindPopover(popupState);
|
||||
const stopPropagation = useCallback((e: any) => e.stopPropagation(), []);
|
||||
const [shown, hidden] = useMemo(() => {
|
||||
if (tags.length <= max) {
|
||||
return [tags, []];
|
||||
}
|
||||
return [tags.slice(0, max), tags.slice(max)];
|
||||
}, [tags, max]);
|
||||
|
||||
const { onClick, ...triggerProps } = bindTrigger(popupState);
|
||||
const onMobileClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
onClick(e);
|
||||
};
|
||||
const { onClick, ...triggerProps } = bindTrigger(popupState);
|
||||
const onMobileClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
onClick(e);
|
||||
};
|
||||
|
||||
const PopoverComponent = isMobile ? Popover : HoverPopover;
|
||||
const PopoverComponent = isMobile ? Popover : HoverPopover;
|
||||
|
||||
return (
|
||||
<Stack direction={"row"} spacing={1} sx={{ ...sx }} {...restProps}>
|
||||
{shown.map((tag) => (
|
||||
<FileTag
|
||||
tagColor={tag.value == "" ? undefined : tag.value}
|
||||
label={tag.key}
|
||||
key={tag.key}
|
||||
/>
|
||||
))}
|
||||
{hidden.length > 0 && (
|
||||
<TagChip
|
||||
size="small"
|
||||
variant={"outlined"}
|
||||
label={`+${hidden.length}`}
|
||||
{...(isMobile
|
||||
? {
|
||||
onClick: onMobileClick,
|
||||
...triggerProps,
|
||||
}
|
||||
: bindHover(popupState))}
|
||||
/>
|
||||
)}
|
||||
return (
|
||||
<Stack direction={"row"} spacing={1} sx={{ ...sx }} {...restProps}>
|
||||
{shown.map((tag) => (
|
||||
<FileTag tagColor={tag.value == "" ? undefined : tag.value} label={tag.key} key={tag.key} />
|
||||
))}
|
||||
{hidden.length > 0 && (
|
||||
<TagChip
|
||||
size="small"
|
||||
variant={"outlined"}
|
||||
label={`+${hidden.length}`}
|
||||
{...(isMobile
|
||||
? {
|
||||
onClick: onMobileClick,
|
||||
...triggerProps,
|
||||
}
|
||||
: bindHover(popupState))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{open && (
|
||||
<PopoverComponent
|
||||
onMouseDown={stopPropagation}
|
||||
onMouseUp={stopPropagation}
|
||||
onClick={stopPropagation}
|
||||
open={open}
|
||||
{...rest}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 1, maxWidth: "300px" }}>
|
||||
{hidden.map((tag, i) => (
|
||||
<FileTag
|
||||
tagColor={tag.value == "" ? undefined : tag.value}
|
||||
label={tag.key}
|
||||
spacing={i === hidden.length - 1 ? undefined : 1}
|
||||
key={tag.key}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</PopoverComponent>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
);
|
||||
{open && (
|
||||
<PopoverComponent
|
||||
onMouseDown={stopPropagation}
|
||||
onMouseUp={stopPropagation}
|
||||
onClick={stopPropagation}
|
||||
open={open}
|
||||
{...rest}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 1, maxWidth: "300px" }}>
|
||||
{hidden.map((tag, i) => (
|
||||
<FileTag
|
||||
tagColor={tag.value == "" ? undefined : tag.value}
|
||||
label={tag.key}
|
||||
spacing={i === hidden.length - 1 ? undefined : 1}
|
||||
key={tag.key}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</PopoverComponent>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default FileTagSummary;
|
||||
|
|
|
|||
|
|
@ -44,21 +44,11 @@ export const loadingPlaceHolderNumb = 3;
|
|||
const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
|
||||
const { t } = useTranslation("application");
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
const files = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.files,
|
||||
);
|
||||
const mixedType = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.mixed_type,
|
||||
);
|
||||
const pagination = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.pagination,
|
||||
);
|
||||
const showThumb = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].showThumb,
|
||||
);
|
||||
const search_params = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex]?.search_params,
|
||||
);
|
||||
const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
|
||||
const mixedType = useAppSelector((state) => state.fileManager[fmIndex].list?.mixed_type);
|
||||
const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
|
||||
const showThumb = useAppSelector((state) => state.fileManager[fmIndex].showThumb);
|
||||
const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params);
|
||||
const list = useMemo(() => {
|
||||
const list: listComponents = {
|
||||
Files: [],
|
||||
|
|
@ -115,9 +105,7 @@ const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
|
|||
</GridItem>
|
||||
);
|
||||
const _ =
|
||||
list.Files.length > 0
|
||||
? list.Files.push(loadingPlaceholder)
|
||||
: list.Folders?.push(loadingPlaceholder);
|
||||
list.Files.length > 0 ? list.Files.push(loadingPlaceholder) : list.Folders?.push(loadingPlaceholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,10 @@ export interface ListBodyProps {
|
|||
|
||||
const ListBody = ({ columns }: ListBodyProps) => {
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
const files = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.files,
|
||||
);
|
||||
const mixedType = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.mixed_type,
|
||||
);
|
||||
const pagination = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.pagination,
|
||||
);
|
||||
const search_params = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex]?.search_params,
|
||||
);
|
||||
const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
|
||||
const mixedType = useAppSelector((state) => state.fileManager[fmIndex].list?.mixed_type);
|
||||
const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
|
||||
const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params);
|
||||
|
||||
const list = useMemo(() => {
|
||||
const list: FmFile[] = [];
|
||||
|
|
|
|||
|
|
@ -23,13 +23,7 @@ export interface FileBadgeProps extends ButtonProps {
|
|||
clickable?: boolean;
|
||||
}
|
||||
|
||||
const FileBadge = ({
|
||||
file,
|
||||
clickable,
|
||||
simplifiedFile,
|
||||
unknown,
|
||||
...rest
|
||||
}: FileBadgeProps) => {
|
||||
const FileBadge = ({ file, clickable, simplifiedFile, unknown, ...rest }: FileBadgeProps) => {
|
||||
const { t } = useTranslation();
|
||||
const popupState = usePopupState({
|
||||
variant: "popover",
|
||||
|
|
@ -131,9 +125,7 @@ const FileBadge = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
<BadgeText variant={"body2"}>
|
||||
{name == "" ? displayName : name}
|
||||
</BadgeText>
|
||||
<BadgeText variant={"body2"}>{name == "" ? displayName : name}</BadgeText>
|
||||
</DefaultButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Popover,
|
||||
styled,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Button, Divider, Popover, styled, Tooltip, useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CSSProperties, useCallback, useState } from "react";
|
||||
import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||
|
|
@ -72,11 +64,7 @@ const ColorCircleBox = styled("div")(({
|
|||
};
|
||||
});
|
||||
|
||||
const ColorCircleBoxChild = styled("div")(({
|
||||
selected,
|
||||
}: {
|
||||
selected: boolean;
|
||||
}) => {
|
||||
const ColorCircleBoxChild = styled("div")(({ selected }: { selected: boolean }) => {
|
||||
const theme = useTheme();
|
||||
return {
|
||||
"--circle-point-background-color": theme.palette.background.default,
|
||||
|
|
@ -90,14 +78,7 @@ const ColorCircleBoxChild = styled("div")(({
|
|||
};
|
||||
});
|
||||
|
||||
export const ColorCircle = ({
|
||||
color,
|
||||
selected,
|
||||
isCustomization,
|
||||
onClick,
|
||||
size,
|
||||
noMb,
|
||||
}: ColorCircleProps) => {
|
||||
export const ColorCircle = ({ color, selected, isCustomization, onClick, size, noMb }: ColorCircleProps) => {
|
||||
const { t } = useTranslation();
|
||||
const displayColor = isCustomization
|
||||
? "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)"
|
||||
|
|
@ -105,16 +86,8 @@ export const ColorCircle = ({
|
|||
? "linear-gradient(45deg, rgba(217,217,217,1) 46%, rgba(217,217,217,1) 47%, rgba(128,128,128,1) 47%)"
|
||||
: color;
|
||||
return (
|
||||
<Tooltip
|
||||
title={isCustomization ? t("application:fileManager.customizeColor") : ""}
|
||||
>
|
||||
<ColorCircleBox
|
||||
size={size}
|
||||
onClick={onClick}
|
||||
color={displayColor}
|
||||
selected={selected}
|
||||
noMb={noMb}
|
||||
>
|
||||
<Tooltip title={isCustomization ? t("application:fileManager.customizeColor") : ""}>
|
||||
<ColorCircleBox size={size} onClick={onClick} color={displayColor} selected={selected} noMb={noMb}>
|
||||
<ColorCircleBoxChild selected={selected} />
|
||||
</ColorCircleBox>
|
||||
</Tooltip>
|
||||
|
|
@ -124,9 +97,7 @@ export const ColorCircle = ({
|
|||
const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [customizeColor, setCustomizeColor] = useState<string>(
|
||||
props.selectedColor,
|
||||
);
|
||||
const [customizeColor, setCustomizeColor] = useState<string>(props.selectedColor);
|
||||
const popupState = usePopupState({
|
||||
variant: "popover",
|
||||
popupId: "color-picker",
|
||||
|
|
@ -152,13 +123,8 @@ const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
|||
{props.colors.map((color) => (
|
||||
<ColorCircle
|
||||
noMb={props.showColorValueInCustomization}
|
||||
isCustomization={
|
||||
color === customizeMagicColor &&
|
||||
!props.showColorValueInCustomization
|
||||
}
|
||||
color={
|
||||
!props.showColorValueInCustomization ? color : props.selectedColor
|
||||
}
|
||||
isCustomization={color === customizeMagicColor && !props.showColorValueInCustomization}
|
||||
color={!props.showColorValueInCustomization ? color : props.selectedColor}
|
||||
onClick={onClick(color)}
|
||||
selected={color === props.selectedColor}
|
||||
{...(color === customizeMagicColor && bindTrigger(popupState))}
|
||||
|
|
@ -195,12 +161,7 @@ const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
|||
/>
|
||||
<Divider />
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Button
|
||||
size={"small"}
|
||||
onClick={onApply}
|
||||
fullWidth
|
||||
variant={"contained"}
|
||||
>
|
||||
<Button size={"small"} onClick={onApply} fullWidth variant={"contained"}>
|
||||
{t("application:fileManager.apply")}
|
||||
</Button>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,8 @@
|
|||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Stack,
|
||||
styled,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, BoxProps, Stack, styled, Typography, useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo, useState } from "react";
|
||||
import { FileResponse, Metadata } from "../../../api/explorer.ts";
|
||||
import CircleColorSelector, {
|
||||
customizeMagicColor,
|
||||
} from "./ColorCircle/CircleColorSelector.tsx";
|
||||
import CircleColorSelector, { customizeMagicColor } from "./ColorCircle/CircleColorSelector.tsx";
|
||||
import SessionManager, { UserSettings } from "../../../session";
|
||||
import { defaultColors } from "../../../constants";
|
||||
|
||||
|
|
@ -25,23 +16,16 @@ export interface FolderColorQuickActionProps extends BoxProps {
|
|||
onColorChange: (color?: string) => void;
|
||||
}
|
||||
|
||||
const FolderColorQuickAction = ({
|
||||
file,
|
||||
onColorChange,
|
||||
...rest
|
||||
}: FolderColorQuickActionProps) => {
|
||||
const FolderColorQuickAction = ({ file, onColorChange, ...rest }: FolderColorQuickActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [hex, setHex] = useState<string>(
|
||||
(file.metadata && file.metadata[Metadata.icon_color]) ??
|
||||
theme.palette.action.active,
|
||||
(file.metadata && file.metadata[Metadata.icon_color]) ?? theme.palette.action.active,
|
||||
);
|
||||
const presetColors = useMemo(() => {
|
||||
const colors = new Set(defaultColors);
|
||||
|
||||
const recentColors = SessionManager.get(
|
||||
UserSettings.UsedCustomizedIconColors,
|
||||
) as string[] | undefined;
|
||||
const recentColors = SessionManager.get(UserSettings.UsedCustomizedIconColors) as string[] | undefined;
|
||||
|
||||
if (recentColors) {
|
||||
recentColors.forEach((color) => {
|
||||
|
|
@ -54,20 +38,12 @@ const FolderColorQuickAction = ({
|
|||
return (
|
||||
<StyledBox {...rest}>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant={"caption"}>
|
||||
{t("application:fileManager.folderColor")}
|
||||
</Typography>
|
||||
<Typography variant={"caption"}>{t("application:fileManager.folderColor")}</Typography>
|
||||
<CircleColorSelector
|
||||
colors={[
|
||||
theme.palette.action.active,
|
||||
...presetColors,
|
||||
customizeMagicColor,
|
||||
]}
|
||||
colors={[theme.palette.action.active, ...presetColors, customizeMagicColor]}
|
||||
selectedColor={hex}
|
||||
onChange={(color) => {
|
||||
onColorChange(
|
||||
color == theme.palette.action.active ? undefined : color,
|
||||
);
|
||||
onColorChange(color == theme.palette.action.active ? undefined : color);
|
||||
setHex(color);
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { createContext } from "react";
|
||||
|
||||
export const FmIndexContext = createContext(0);
|
||||
export const FmIndexContext = createContext(0);
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ const NewButton = () => {
|
|||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<IconButton
|
||||
onClick={(e) => dispatch(openNewContextMenu(FileManagerIndex.main, e))}
|
||||
>
|
||||
<IconButton onClick={(e) => dispatch(openNewContextMenu(FileManagerIndex.main, e))}>
|
||||
<Add />
|
||||
</IconButton>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
import { RadiusFrame } from "../../Frame/RadiusFrame.tsx";
|
||||
import {
|
||||
Box,
|
||||
Pagination,
|
||||
Slide,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Pagination, Slide, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { forwardRef, useContext } from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import { MinPageSize } from "../TopBar/ViewOptionPopover.tsx";
|
||||
|
|
@ -29,25 +22,20 @@ export interface PaginationState {
|
|||
}
|
||||
|
||||
export const usePaginationState = (fmIndex: number) => {
|
||||
const pagination = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.pagination,
|
||||
);
|
||||
const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
|
||||
const totalItems = pagination?.total_items;
|
||||
const page = pagination?.page;
|
||||
const pageSize = pagination?.page_size;
|
||||
|
||||
const currentPage = (page ?? 0) + 1;
|
||||
const totalPages = Math.ceil(
|
||||
(totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize),
|
||||
);
|
||||
const totalPages = Math.ceil((totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize));
|
||||
const usePagination = totalPages > 1;
|
||||
return {
|
||||
currentPage,
|
||||
totalPages,
|
||||
usePagination,
|
||||
useEndlessLoading: !usePagination,
|
||||
moreItems:
|
||||
pagination?.next_token || (usePagination && currentPage < totalPages),
|
||||
moreItems: pagination?.next_token || (usePagination && currentPage < totalPages),
|
||||
nextToken: pagination?.next_token,
|
||||
} as PaginationState;
|
||||
};
|
||||
|
|
@ -64,10 +52,7 @@ const PaginationFooter = forwardRef((_props, ref) => {
|
|||
|
||||
return (
|
||||
<Slide direction={"up"} unmountOnExit in={paginationState.usePagination}>
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{ display: "flex", px: isMobile ? 1 : 0, pb: isMobile ? 1 : 0 }}
|
||||
>
|
||||
<Box ref={ref} sx={{ display: "flex", px: isMobile ? 1 : 0, pb: isMobile ? 1 : 0 }}>
|
||||
<PaginationFrame withBorder>
|
||||
<Pagination
|
||||
renderItem={(item) => <PaginationItem {...item} />}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,15 @@ import { mergeRefs } from "../../../util";
|
|||
|
||||
let timeOut: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
|
||||
const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>(
|
||||
({ theme, isDropOver }) => ({
|
||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
||||
transitionProperty: "background-color,opacity,box-shadow",
|
||||
boxShadow: isDropOver
|
||||
? `inset 0 0 0 2px ${theme.palette.primary.light}`
|
||||
: "none",
|
||||
}),
|
||||
);
|
||||
const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({
|
||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
||||
transitionProperty: "background-color,opacity,box-shadow",
|
||||
boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
|
||||
}));
|
||||
|
||||
const CustomPaginationItem = (props: PaginationItemProps) => {
|
||||
const [drag, drop, isOver, isDragging] = useFileDrag({
|
||||
dropUri:
|
||||
props.type !== "start-ellipsis" && props.type !== "end-ellipsis"
|
||||
? NoOpDropUri
|
||||
: undefined,
|
||||
dropUri: props.type !== "start-ellipsis" && props.type !== "end-ellipsis" ? NoOpDropUri : undefined,
|
||||
});
|
||||
const buttonRef = useRef<HTMLElement>();
|
||||
|
||||
|
|
@ -47,9 +40,7 @@ const CustomPaginationItem = (props: PaginationItemProps) => {
|
|||
[drop, buttonRef],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledPaginationItem isDropOver={isOver} ref={mergedRef} {...props} />
|
||||
);
|
||||
return <StyledPaginationItem isDropOver={isOver} ref={mergedRef} {...props} />;
|
||||
};
|
||||
|
||||
export default CustomPaginationItem;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@ import { useTranslation } from "react-i18next";
|
|||
|
||||
import { SecondaryButton } from "../../../Common/StyledComponents.tsx";
|
||||
import Add from "../../../Icons/Add.tsx";
|
||||
import {
|
||||
bindMenu,
|
||||
bindTrigger,
|
||||
usePopupState,
|
||||
} from "material-ui-popup-state/hooks";
|
||||
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||
import { ListItemIcon, ListItemText, Menu } from "@mui/material";
|
||||
import { Condition, ConditionType } from "./ConditionBox.tsx";
|
||||
import React from "react";
|
||||
|
|
@ -154,20 +150,13 @@ const AddCondition = (props: AddConditionProps) => {
|
|||
const onConditionAdd = (condition: Condition) => {
|
||||
props.onConditionAdd({
|
||||
...condition,
|
||||
id:
|
||||
condition.type == ConditionType.metadata && !condition.id
|
||||
? Math.random().toString()
|
||||
: condition.id,
|
||||
id: condition.type == ConditionType.metadata && !condition.id ? Math.random().toString() : condition.id,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<SecondaryButton
|
||||
{...bindTrigger(conditionPopupState)}
|
||||
startIcon={<Add />}
|
||||
sx={{ px: "15px" }}
|
||||
>
|
||||
<SecondaryButton {...bindTrigger(conditionPopupState)} startIcon={<Add />} sx={{ px: "15px" }}>
|
||||
{t("navbar.addCondition")}
|
||||
</SecondaryButton>
|
||||
<Menu
|
||||
|
|
@ -183,11 +172,7 @@ const AddCondition = (props: AddConditionProps) => {
|
|||
{...menuProps}
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<SquareMenuItem
|
||||
dense
|
||||
key={index}
|
||||
onClick={() => onConditionAdd(option.condition)}
|
||||
>
|
||||
<SquareMenuItem dense key={index} onClick={() => onConditionAdd(option.condition)}>
|
||||
<ListItemIcon>{option.icon}</ListItemIcon>
|
||||
{t(option.name)}
|
||||
</SquareMenuItem>
|
||||
|
|
@ -198,14 +183,12 @@ const AddCondition = (props: AddConditionProps) => {
|
|||
title={t("application:fileManager.mediaInfo")}
|
||||
>
|
||||
{mediaMetaOptions.map((option, index) => (
|
||||
<SquareMenuItem
|
||||
key={index}
|
||||
dense
|
||||
onClick={() => onConditionAdd(option.condition)}
|
||||
>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<SquareMenuItem key={index} dense onClick={() => onConditionAdd(option.condition)}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t(option.name)}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
|
|
|
|||
|
|
@ -15,10 +15,7 @@ import { defaultPath } from "../../../../hooks/useNavigation.tsx";
|
|||
import { advancedSearch } from "../../../../redux/thunks/filemanager.ts";
|
||||
import { Metadata } from "../../../../api/explorer.ts";
|
||||
|
||||
const searchParamToConditions = (
|
||||
search_params: SearchParam,
|
||||
base: string,
|
||||
): Condition[] => {
|
||||
const searchParamToConditions = (search_params: SearchParam, base: string): Condition[] => {
|
||||
const applied: Condition[] = [
|
||||
{
|
||||
type: ConditionType.base,
|
||||
|
|
@ -41,10 +38,7 @@ const searchParamToConditions = (
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
search_params.size_gte != undefined ||
|
||||
search_params.size_lte != undefined
|
||||
) {
|
||||
if (search_params.size_gte != undefined || search_params.size_lte != undefined) {
|
||||
applied.push({
|
||||
type: ConditionType.size,
|
||||
size_gte: search_params.size_gte,
|
||||
|
|
@ -52,10 +46,7 @@ const searchParamToConditions = (
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
search_params.created_at_gte != undefined ||
|
||||
search_params.created_at_lte != undefined
|
||||
) {
|
||||
if (search_params.created_at_gte != undefined || search_params.created_at_lte != undefined) {
|
||||
applied.push({
|
||||
type: ConditionType.created,
|
||||
created_gte: search_params.created_at_gte,
|
||||
|
|
@ -63,10 +54,7 @@ const searchParamToConditions = (
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
search_params.updated_at_gte != undefined ||
|
||||
search_params.updated_at_lte != undefined
|
||||
) {
|
||||
if (search_params.updated_at_gte != undefined || search_params.updated_at_lte != undefined) {
|
||||
applied.push({
|
||||
type: ConditionType.modified,
|
||||
updated_gte: search_params.updated_at_gte,
|
||||
|
|
@ -106,18 +94,10 @@ const AdvanceSearch = () => {
|
|||
|
||||
const [conditions, setConditions] = useState<Condition[]>([]);
|
||||
const open = useAppSelector((state) => state.globalState.advanceSearchOpen);
|
||||
const base = useAppSelector(
|
||||
(state) => state.globalState.advanceSearchBasePath,
|
||||
);
|
||||
const initialNames = useAppSelector(
|
||||
(state) => state.globalState.advanceSearchInitialNameCondition,
|
||||
);
|
||||
const search_params = useAppSelector(
|
||||
(state) => state.fileManager[FileManagerIndex.main].search_params,
|
||||
);
|
||||
const current_base = useAppSelector(
|
||||
(state) => state.fileManager[FileManagerIndex.main].pure_path,
|
||||
);
|
||||
const base = useAppSelector((state) => state.globalState.advanceSearchBasePath);
|
||||
const initialNames = useAppSelector((state) => state.globalState.advanceSearchInitialNameCondition);
|
||||
const search_params = useAppSelector((state) => state.fileManager[FileManagerIndex.main].search_params);
|
||||
const current_base = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(closeAdvanceSearch());
|
||||
|
|
@ -141,10 +121,7 @@ const AdvanceSearch = () => {
|
|||
}
|
||||
|
||||
if (search_params) {
|
||||
const existedConditions = searchParamToConditions(
|
||||
search_params,
|
||||
current_base ?? defaultPath,
|
||||
);
|
||||
const existedConditions = searchParamToConditions(search_params, current_base ?? defaultPath);
|
||||
if (existedConditions.length > 0) {
|
||||
setConditions(existedConditions);
|
||||
}
|
||||
|
|
@ -157,9 +134,7 @@ const AdvanceSearch = () => {
|
|||
};
|
||||
|
||||
const onConditionAdd = (condition: Condition) => {
|
||||
if (
|
||||
conditions.find((c) => c.type === condition.type && c.id === condition.id)
|
||||
) {
|
||||
if (conditions.find((c) => c.type === condition.type && c.id === condition.id)) {
|
||||
enqueueSnackbar(t("application:navbar.conditionDuplicate"), {
|
||||
variant: "warning",
|
||||
action: DefaultCloseAction,
|
||||
|
|
@ -194,11 +169,7 @@ const AdvanceSearch = () => {
|
|||
<Collapse key={`${condition.type} ${condition.id}`}>
|
||||
<ConditionBox
|
||||
index={index}
|
||||
onRemove={
|
||||
conditions.length > 2 && condition.type != ConditionType.base
|
||||
? onConditionRemove
|
||||
: undefined
|
||||
}
|
||||
onRemove={conditions.length > 2 && condition.type != ConditionType.base ? onConditionRemove : undefined}
|
||||
condition={condition}
|
||||
onChange={(condition) => {
|
||||
const new_conditions = [...conditions];
|
||||
|
|
|
|||
|
|
@ -137,49 +137,25 @@ const ConditionBox = forwardRef((props: ConditionProps, ref) => {
|
|||
<Icon sx={{ width: "20px", height: "20px" }} />
|
||||
<Box sx={{ flexGrow: 1 }}>{title}</Box>
|
||||
<Grow in={hovered && !!onRemove}>
|
||||
<IconButton
|
||||
onClick={onRemove ? () => onRemove(condition) : undefined}
|
||||
>
|
||||
<IconButton onClick={onRemove ? () => onRemove(condition) : undefined}>
|
||||
<Dismiss fontSize={"small"} />
|
||||
</IconButton>
|
||||
</Grow>
|
||||
</Typography>
|
||||
<Box>
|
||||
{condition.type == ConditionType.name && (
|
||||
<FileNameCondition
|
||||
condition={condition}
|
||||
onChange={onChange}
|
||||
onNameConditionAdded={onNameConditionAdded}
|
||||
/>
|
||||
)}
|
||||
{condition.type == ConditionType.type && (
|
||||
<FileTypeCondition condition={condition} onChange={onChange} />
|
||||
)}
|
||||
{condition.type == ConditionType.base && (
|
||||
<SearchBaseCondition condition={condition} onChange={onChange} />
|
||||
)}
|
||||
{condition.type == ConditionType.tag && (
|
||||
<TagCondition onChange={onChange} condition={condition} />
|
||||
)}
|
||||
{condition.type == ConditionType.metadata && (
|
||||
<MetadataCondition onChange={onChange} condition={condition} />
|
||||
)}
|
||||
{condition.type == ConditionType.size && (
|
||||
<SizeCondition condition={condition} onChange={onChange} />
|
||||
<FileNameCondition condition={condition} onChange={onChange} onNameConditionAdded={onNameConditionAdded} />
|
||||
)}
|
||||
{condition.type == ConditionType.type && <FileTypeCondition condition={condition} onChange={onChange} />}
|
||||
{condition.type == ConditionType.base && <SearchBaseCondition condition={condition} onChange={onChange} />}
|
||||
{condition.type == ConditionType.tag && <TagCondition onChange={onChange} condition={condition} />}
|
||||
{condition.type == ConditionType.metadata && <MetadataCondition onChange={onChange} condition={condition} />}
|
||||
{condition.type == ConditionType.size && <SizeCondition condition={condition} onChange={onChange} />}
|
||||
{condition.type == ConditionType.created && (
|
||||
<DateTimeCondition
|
||||
condition={condition}
|
||||
onChange={onChange}
|
||||
field={"created"}
|
||||
/>
|
||||
<DateTimeCondition condition={condition} onChange={onChange} field={"created"} />
|
||||
)}
|
||||
{condition.type == ConditionType.modified && (
|
||||
<DateTimeCondition
|
||||
condition={condition}
|
||||
onChange={onChange}
|
||||
field={"updated"}
|
||||
/>
|
||||
<DateTimeCondition condition={condition} onChange={onChange} field={"updated"} />
|
||||
)}
|
||||
</Box>
|
||||
</StyledBox>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
Chip,
|
||||
FormControlLabel,
|
||||
styled,
|
||||
} from "@mui/material";
|
||||
import { Autocomplete, Box, Chip, FormControlLabel, styled } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FilledTextField,
|
||||
StyledCheckbox,
|
||||
} from "../../../Common/StyledComponents.tsx";
|
||||
import { FilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
|
||||
import { Condition } from "./ConditionBox.tsx";
|
||||
|
||||
export const StyledBox = styled(Box)(({ theme }) => ({
|
||||
|
|
@ -41,9 +32,7 @@ export const FileNameCondition = ({
|
|||
renderTags={(value: readonly string[], getTagProps) =>
|
||||
value.map((option: string, index: number) => {
|
||||
const { key, ...tagProps } = getTagProps({ index });
|
||||
return (
|
||||
<Chip variant="outlined" label={option} key={key} {...tagProps} />
|
||||
);
|
||||
return <Chip variant="outlined" label={option} key={key} {...tagProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
|
|
|
|||
|
|
@ -31,9 +31,11 @@ export const FileTypeCondition = ({
|
|||
<ListItemIcon>
|
||||
<Document fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("application:fileManager.file")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
|
|
@ -41,9 +43,11 @@ export const FileTypeCondition = ({
|
|||
<ListItemIcon>
|
||||
<Folder fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText slotProps={{
|
||||
primary: { variant: "body2" }
|
||||
}}>
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("application:fileManager.folder")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ export const MetadataCondition = ({
|
|||
variant="filled"
|
||||
label={t("application:fileManager.metadataKey")}
|
||||
value={condition.metadata_key ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({ ...condition, metadata_key: e.target.value })
|
||||
}
|
||||
onChange={(e) => onChange({ ...condition, metadata_key: e.target.value })}
|
||||
disabled={condition.metadata_key_readonly}
|
||||
type="text"
|
||||
fullWidth
|
||||
|
|
@ -33,9 +31,7 @@ export const MetadataCondition = ({
|
|||
variant="filled"
|
||||
label={t("application:fileManager.metadataValue")}
|
||||
value={condition.metadata_value ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({ ...condition, metadata_value: e.target.value })
|
||||
}
|
||||
onChange={(e) => onChange({ ...condition, metadata_value: e.target.value })}
|
||||
type="text"
|
||||
fullWidth
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ export const TagCondition = ({
|
|||
renderTags={(value: readonly string[], getTagProps) =>
|
||||
value.map((option: string, index: number) => {
|
||||
const { key, ...tagProps } = getTagProps({ index });
|
||||
return (
|
||||
<Chip variant="outlined" label={option} key={key} {...tagProps} />
|
||||
);
|
||||
return <Chip variant="outlined" label={option} key={key} {...tagProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { Box, List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import React, { useCallback } from "react";
|
||||
// @ts-ignore
|
||||
|
|
@ -27,8 +20,7 @@ const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const onClick = useCallback(
|
||||
(base: string) => () =>
|
||||
dispatch(quickSearch(FileManagerIndex.main, base, keyword)),
|
||||
(base: string) => () => dispatch(quickSearch(FileManagerIndex.main, base, keyword)),
|
||||
[keyword, dispatch],
|
||||
);
|
||||
|
||||
|
|
@ -56,15 +48,13 @@ const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => {
|
|||
values={{
|
||||
keywords: keyword,
|
||||
}}
|
||||
components={[
|
||||
<Box component={"span"} sx={{ fontWeight: 600 }} />,
|
||||
]}
|
||||
components={[<Box component={"span"} sx={{ fontWeight: 600 }} />]}
|
||||
/>
|
||||
}
|
||||
slotProps={{
|
||||
primary: {
|
||||
variant: "body2",
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FileBadge
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts";
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { sizeToString } from "../../../util";
|
||||
import FileIcon from "../Explorer/FileIcon.tsx";
|
||||
|
|
@ -49,15 +43,7 @@ const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => {
|
|||
<ListItemButton
|
||||
sx={{ py: 0 }}
|
||||
onClick={(e) =>
|
||||
dispatch(
|
||||
openFileContextMenu(
|
||||
FileManagerIndex.main,
|
||||
file,
|
||||
true,
|
||||
e,
|
||||
ContextMenuTypes.searchResult,
|
||||
),
|
||||
)
|
||||
dispatch(openFileContextMenu(FileManagerIndex.main, file, true, e, ContextMenuTypes.searchResult))
|
||||
}
|
||||
>
|
||||
<ListItemAvatar sx={{ minWidth: 48 }}>
|
||||
|
|
@ -91,8 +77,9 @@ const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => {
|
|||
|
||||
secondary: {
|
||||
variant: "body2",
|
||||
}
|
||||
}} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FileBadge
|
||||
clickable
|
||||
variant={"outlined"}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,10 @@ import { useTranslation } from "react-i18next";
|
|||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import { useContext, useMemo } from "react";
|
||||
import { FmIndexContext } from "../FmIndexContext.tsx";
|
||||
import {
|
||||
alpha,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Grow,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { alpha, Button, ButtonGroup, Grow, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||
import Search from "../../Icons/Search.tsx";
|
||||
import Dismiss from "../../Icons/Dismiss.tsx";
|
||||
import {
|
||||
clearSearch,
|
||||
openAdvancedSearch,
|
||||
} from "../../../redux/thunks/filemanager.ts";
|
||||
import { clearSearch, openAdvancedSearch } from "../../../redux/thunks/filemanager.ts";
|
||||
import { FileManagerIndex } from "../FileManager.tsx";
|
||||
|
||||
export const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({
|
||||
|
|
@ -44,9 +33,7 @@ export const SearchIndicator = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
|
||||
const search_params = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].search_params,
|
||||
);
|
||||
const search_params = useAppSelector((state) => state.fileManager[fmIndex].search_params);
|
||||
|
||||
const searchConditionsCount = useMemo(() => {
|
||||
if (!search_params) {
|
||||
|
|
@ -90,10 +77,7 @@ export const SearchIndicator = () => {
|
|||
num: searchConditionsCount,
|
||||
})}
|
||||
</StyledButton>
|
||||
<StyledButton
|
||||
size={"small"}
|
||||
onClick={() => dispatch(clearSearch(fmIndex))}
|
||||
>
|
||||
<StyledButton size={"small"} onClick={() => dispatch(clearSearch(fmIndex))}>
|
||||
<Dismiss fontSize={"small"} sx={{ width: 16, height: 16 }} />
|
||||
</StyledButton>
|
||||
</StyledButtonGroup>
|
||||
|
|
|
|||
|
|
@ -27,10 +27,7 @@ import SessionManager from "../../../session";
|
|||
import { defaultPath } from "../../../hooks/useNavigation.tsx";
|
||||
import FullSearchOption from "./FullSearchOptions.tsx";
|
||||
import { TransitionProps } from "@mui/material/transitions";
|
||||
import {
|
||||
openAdvancedSearch,
|
||||
quickSearch,
|
||||
} from "../../../redux/thunks/filemanager.ts";
|
||||
import { openAdvancedSearch, quickSearch } from "../../../redux/thunks/filemanager.ts";
|
||||
import Options from "../../Icons/Options.tsx";
|
||||
|
||||
const StyledDialog = styled(Dialog)<{
|
||||
|
|
@ -66,9 +63,7 @@ const SearchPopup = () => {
|
|||
|
||||
const [keywords, setKeywords] = useState("");
|
||||
const [searchedKeyword, setSearchedKeyword] = useState("");
|
||||
const [treeSearchResults, setTreeSearchResults] = useState<FileResponse[]>(
|
||||
[],
|
||||
);
|
||||
const [treeSearchResults, setTreeSearchResults] = useState<FileResponse[]>([]);
|
||||
|
||||
const onClose = () => {
|
||||
dispatch(setSearchPopup(false));
|
||||
|
|
@ -77,48 +72,36 @@ const SearchPopup = () => {
|
|||
};
|
||||
|
||||
const open = useAppSelector((state) => state.globalState.searchPopupOpen);
|
||||
const tree = useAppSelector(
|
||||
(state) => state.fileManager[FileManagerIndex.main]?.tree,
|
||||
);
|
||||
const path = useAppSelector(
|
||||
(state) => state.fileManager[FileManagerIndex.main]?.path,
|
||||
);
|
||||
const single_file_view = useAppSelector(
|
||||
(state) => state.fileManager[FileManagerIndex.main]?.list?.single_file_view,
|
||||
);
|
||||
const tree = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.tree);
|
||||
const path = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.path);
|
||||
const single_file_view = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.list?.single_file_view);
|
||||
|
||||
const searchTree = useMemo(
|
||||
() =>
|
||||
debounce(
|
||||
(
|
||||
request: { input: string },
|
||||
callback: (results?: FileResponse[]) => void,
|
||||
) => {
|
||||
const options = {
|
||||
includeScore: true,
|
||||
// Search in `author` and in `tags` array
|
||||
keys: ["file.name"],
|
||||
};
|
||||
const fuse = new Fuse(Object.values(tree), options);
|
||||
const result = fuse.search(
|
||||
request.input
|
||||
.split(" ")
|
||||
.filter((k) => k != "")
|
||||
.join(" "),
|
||||
{ limit: 50 },
|
||||
);
|
||||
const res: FileResponse[] = [];
|
||||
result
|
||||
.filter((r) => r.item.file != undefined)
|
||||
.forEach((r) => {
|
||||
if (r.item.file) {
|
||||
res.push(r.item.file);
|
||||
}
|
||||
});
|
||||
callback(res);
|
||||
},
|
||||
400,
|
||||
),
|
||||
debounce((request: { input: string }, callback: (results?: FileResponse[]) => void) => {
|
||||
const options = {
|
||||
includeScore: true,
|
||||
// Search in `author` and in `tags` array
|
||||
keys: ["file.name"],
|
||||
};
|
||||
const fuse = new Fuse(Object.values(tree), options);
|
||||
const result = fuse.search(
|
||||
request.input
|
||||
.split(" ")
|
||||
.filter((k) => k != "")
|
||||
.join(" "),
|
||||
{ limit: 50 },
|
||||
);
|
||||
const res: FileResponse[] = [];
|
||||
result
|
||||
.filter((r) => r.item.file != undefined)
|
||||
.forEach((r) => {
|
||||
if (r.item.file) {
|
||||
res.push(r.item.file);
|
||||
}
|
||||
});
|
||||
callback(res);
|
||||
}, 400),
|
||||
[tree],
|
||||
);
|
||||
|
||||
|
|
@ -158,10 +141,7 @@ const SearchPopup = () => {
|
|||
res.push(current.base());
|
||||
}
|
||||
// my files - user login and not my fs
|
||||
if (
|
||||
SessionManager.currentLoginOrNull() &&
|
||||
!(current.fs() == Filesystem.my)
|
||||
) {
|
||||
if (SessionManager.currentLoginOrNull() && !(current.fs() == Filesystem.my)) {
|
||||
res.push(defaultPath);
|
||||
}
|
||||
return res;
|
||||
|
|
@ -173,9 +153,7 @@ const SearchPopup = () => {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (fullSearchOptions.length > 0) {
|
||||
dispatch(
|
||||
quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords),
|
||||
);
|
||||
dispatch(quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -214,9 +192,7 @@ const SearchPopup = () => {
|
|||
height: 40,
|
||||
mr: 1.5,
|
||||
}}
|
||||
onClick={() =>
|
||||
dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))
|
||||
}
|
||||
onClick={() => dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))}
|
||||
>
|
||||
<Options />
|
||||
</IconButton>
|
||||
|
|
@ -244,10 +220,7 @@ const SearchPopup = () => {
|
|||
>
|
||||
{t("navbar.searchFilesTitle")}
|
||||
</Typography>
|
||||
<FullSearchOption
|
||||
keyword={keywords}
|
||||
options={fullSearchOptions}
|
||||
/>
|
||||
<FullSearchOption keyword={keywords} options={fullSearchOptions} />
|
||||
{treeSearchResults.length > 0 && <Divider />}
|
||||
</>
|
||||
)}
|
||||
|
|
@ -264,10 +237,7 @@ const SearchPopup = () => {
|
|||
>
|
||||
{t("navbar.recentlyViewed")}
|
||||
</Typography>
|
||||
<FuzzySearchResult
|
||||
keyword={searchedKeyword}
|
||||
files={treeSearchResults}
|
||||
/>
|
||||
<FuzzySearchResult keyword={searchedKeyword} files={treeSearchResults} />
|
||||
</>
|
||||
)}
|
||||
</AutoHeight>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,7 @@ export interface DetailsProps {
|
|||
target: FileResponse;
|
||||
}
|
||||
|
||||
const InfoBlock = ({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const InfoBlock = ({ title, children }: { title: string; children: React.ReactNode }) => {
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant={"body2"}>{title}</Typography>
|
||||
|
|
@ -35,11 +29,7 @@ const Details = ({ target, inPhotoViewer }: DetailsProps) => {
|
|||
const dispatch = useAppDispatch();
|
||||
const [thumbSrc, setThumbSrc] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (
|
||||
target.type == FileType.file &&
|
||||
(!target.metadata ||
|
||||
target.metadata[Metadata.thumbDisabled] === undefined)
|
||||
) {
|
||||
if (target.type == FileType.file && (!target.metadata || target.metadata[Metadata.thumbDisabled] === undefined)) {
|
||||
dispatch(loadFileThumb(FileManagerIndex.main, target)).then((src) => {
|
||||
setThumbSrc(src);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,21 +12,10 @@ const Header = ({ target }: HeaderProps) => {
|
|||
const dispatch = useAppDispatch();
|
||||
return (
|
||||
<Box sx={{ display: "flex", p: 2 }}>
|
||||
{target !== null && (
|
||||
<FileIcon
|
||||
sx={{ p: 0 }}
|
||||
loading={target == undefined}
|
||||
file={target}
|
||||
type={target?.type}
|
||||
/>
|
||||
)}
|
||||
{target !== null && <FileIcon sx={{ p: 0 }} loading={target == undefined} file={target} type={target?.type} />}
|
||||
{target !== null && (
|
||||
<Box sx={{ flexGrow: 1, ml: 1.5 }}>
|
||||
<Typography
|
||||
color="textPrimary"
|
||||
sx={{ wordBreak: "break-all" }}
|
||||
variant={"subtitle2"}
|
||||
>
|
||||
<Typography color="textPrimary" sx={{ wordBreak: "break-all" }} variant={"subtitle2"}>
|
||||
{target && target.name}
|
||||
{!target && <Skeleton variant={"text"} width={75} />}
|
||||
</Typography>
|
||||
|
|
|
|||
|
|
@ -12,23 +12,11 @@ export interface MapLoaderProps extends BoxProps {
|
|||
}
|
||||
|
||||
const MapLoader = (props: MapLoaderProps) => {
|
||||
const mapProvider = useAppSelector(
|
||||
(state) => state.siteConfig.explorer.config.map_provider,
|
||||
);
|
||||
const googleTileType = useAppSelector(
|
||||
(state) => state.siteConfig.explorer.config.google_map_tile_type,
|
||||
);
|
||||
const mapProvider = useAppSelector((state) => state.siteConfig.explorer.config.map_provider);
|
||||
const googleTileType = useAppSelector((state) => state.siteConfig.explorer.config.google_map_tile_type);
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<Skeleton variant="rounded" width={"100%"} height={props.height} />
|
||||
}
|
||||
>
|
||||
<MapBox
|
||||
mapProvider={mapProvider}
|
||||
googleTileType={googleTileType}
|
||||
{...props}
|
||||
/>
|
||||
<Suspense fallback={<Skeleton variant="rounded" width={"100%"} height={props.height} />}>
|
||||
<MapBox mapProvider={mapProvider} googleTileType={googleTileType} {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,14 +37,8 @@ export interface MediaMetaCardProps {
|
|||
}
|
||||
|
||||
const StyledButtonBase = styled(Box)(({ theme }) => {
|
||||
let bgColor =
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900];
|
||||
let bgColorHover =
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[300]
|
||||
: theme.palette.grey[700];
|
||||
let bgColor = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
|
||||
let bgColorHover = theme.palette.mode === "light" ? theme.palette.grey[300] : theme.palette.grey[700];
|
||||
return {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: bgColor,
|
||||
|
|
@ -69,21 +63,12 @@ export interface MediaMetaElementsProps extends LinkProps {
|
|||
element: MediaMetaElements;
|
||||
}
|
||||
|
||||
export const MediaMetaElements = ({
|
||||
element,
|
||||
...rest
|
||||
}: MediaMetaElementsProps) => {
|
||||
export const MediaMetaElements = ({ element, ...rest }: MediaMetaElementsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSearch = () => {
|
||||
dispatch(
|
||||
searchMetadata(
|
||||
FileManagerIndex.main,
|
||||
element.searchKey,
|
||||
element.searchValue,
|
||||
),
|
||||
);
|
||||
dispatch(searchMetadata(FileManagerIndex.main, element.searchKey, element.searchValue));
|
||||
handleClose();
|
||||
};
|
||||
|
||||
|
|
@ -113,18 +98,10 @@ export const MediaMetaElements = ({
|
|||
<ListItemIcon>
|
||||
<Clipboard fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
{t("application:fileManager.copyToClipboard")}
|
||||
</ListItemText>
|
||||
<ListItemText>{t("application:fileManager.copyToClipboard")}</ListItemText>
|
||||
</SquareMenuItem>
|
||||
</Menu>
|
||||
<Link
|
||||
color="inherit"
|
||||
href={"#"}
|
||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
||||
underline="hover"
|
||||
{...rest}
|
||||
>
|
||||
<Link color="inherit" href={"#"} onClick={(e) => setAnchorEl(e.currentTarget)} underline="hover" {...rest}>
|
||||
{element.display}
|
||||
</Link>
|
||||
</>
|
||||
|
|
@ -147,26 +124,14 @@ const MediaMetaCard = ({ contents, icon }: MediaMetaCardProps) => {
|
|||
>
|
||||
{contents.map(({ title, content }) => (
|
||||
<Box>
|
||||
<Typography
|
||||
variant={"body2"}
|
||||
color="textPrimary"
|
||||
fontWeight={500}
|
||||
>
|
||||
<Typography variant={"body2"} color="textPrimary" fontWeight={500}>
|
||||
{title.map((element) =>
|
||||
typeof element === "string" ? (
|
||||
element
|
||||
) : (
|
||||
<MediaMetaElements element={element} />
|
||||
),
|
||||
typeof element === "string" ? element : <MediaMetaElements element={element} />,
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant={"body2"} color={"text.secondary"}>
|
||||
{content.map((element) =>
|
||||
typeof element === "string" ? (
|
||||
element
|
||||
) : (
|
||||
<MediaMetaElements element={element} />
|
||||
),
|
||||
typeof element === "string" ? element : <MediaMetaElements element={element} />,
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -13,13 +13,9 @@ export interface SideBarProps {
|
|||
const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
|
||||
const sidebarTarget = useAppSelector(
|
||||
(state) => state.globalState.sidebarTarget,
|
||||
);
|
||||
const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget);
|
||||
// null: not valid, undefined: not loaded, FileResponse: loaded
|
||||
const [target, setTarget] = useState<FileResponse | undefined | null>(
|
||||
undefined,
|
||||
);
|
||||
const [target, setTarget] = useState<FileResponse | undefined | null>(undefined);
|
||||
|
||||
const loadExtendedInfo = useCallback(
|
||||
(path: string) => {
|
||||
|
|
@ -72,8 +68,7 @@ const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
|
|||
width: "300px",
|
||||
height: "100%",
|
||||
ml: 1,
|
||||
borderRadius: (theme) =>
|
||||
inPhotoViewer ? 0 : theme.shape.borderRadius / 8,
|
||||
borderRadius: (theme) => (inPhotoViewer ? 0 : theme.shape.borderRadius / 8),
|
||||
}}
|
||||
withBorder={!inPhotoViewer}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -20,13 +20,9 @@ const Transition = forwardRef(function Transition(
|
|||
const SidebarDialog = ({ inPhotoViewer }: SideBarProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
|
||||
const sidebarTarget = useAppSelector(
|
||||
(state) => state.globalState.sidebarTarget,
|
||||
);
|
||||
const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget);
|
||||
// null: not valid, undefined: not loaded, FileResponse: loaded
|
||||
const [target, setTarget] = useState<FileResponse | undefined | null>(
|
||||
undefined,
|
||||
);
|
||||
const [target, setTarget] = useState<FileResponse | undefined | null>(undefined);
|
||||
|
||||
const loadExtendedInfo = useCallback(
|
||||
(path: string) => {
|
||||
|
|
|
|||
|
|
@ -37,12 +37,7 @@ const Tags = ({ target }: TagsProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
sx={{ pt: 1 }}
|
||||
color="textPrimary"
|
||||
fontWeight={500}
|
||||
variant={"subtitle1"}
|
||||
>
|
||||
<Typography sx={{ pt: 1 }} color="textPrimary" fontWeight={500} variant={"subtitle1"}>
|
||||
{t("application:fileManager.tags")}
|
||||
</Typography>
|
||||
<Box>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,5 @@
|
|||
import {
|
||||
BreadcrumbButtonProps,
|
||||
useBreadcrumbButtons,
|
||||
} from "./BreadcrumbButton.tsx";
|
||||
import {
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Skeleton,
|
||||
styled,
|
||||
} from "@mui/material";
|
||||
import { BreadcrumbButtonProps, useBreadcrumbButtons } from "./BreadcrumbButton.tsx";
|
||||
import { ListItemIcon, ListItemText, MenuItem, Skeleton, styled } from "@mui/material";
|
||||
import { useContext, useMemo } from "react";
|
||||
import { useAppSelector } from "../../../redux/hooks.ts";
|
||||
import FileIcon from "../Explorer/FileIcon.tsx";
|
||||
|
|
@ -20,24 +11,13 @@ export interface BreadcrumbHiddenItem extends BreadcrumbButtonProps {
|
|||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>(
|
||||
({ theme, isDropOver }) => ({
|
||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
||||
transitionProperty: "background-color,opacity,box-shadow",
|
||||
boxShadow: isDropOver
|
||||
? `inset 0 0 0 2px ${theme.palette.primary.light}`
|
||||
: "none",
|
||||
}),
|
||||
);
|
||||
export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({
|
||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
||||
transitionProperty: "background-color,opacity,box-shadow",
|
||||
boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
|
||||
}));
|
||||
|
||||
const BreadcrumbHiddenItem = ({
|
||||
name,
|
||||
is_root,
|
||||
is_latest,
|
||||
path,
|
||||
onClose,
|
||||
...rest
|
||||
}: BreadcrumbHiddenItem) => {
|
||||
const BreadcrumbHiddenItem = ({ name, is_root, is_latest, path, onClose, ...rest }: BreadcrumbHiddenItem) => {
|
||||
const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({
|
||||
name,
|
||||
is_latest,
|
||||
|
|
@ -70,12 +50,7 @@ const BreadcrumbHiddenItem = ({
|
|||
});
|
||||
|
||||
return (
|
||||
<StyledMenuItem
|
||||
isDropOver={isOver}
|
||||
ref={drop}
|
||||
{...rest}
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<StyledMenuItem isDropOver={isOver} ref={drop} {...rest} onClick={onItemClick}>
|
||||
<ListItemIcon>
|
||||
{StartIcon ? (
|
||||
StartIcon
|
||||
|
|
@ -91,9 +66,7 @@ const BreadcrumbHiddenItem = ({
|
|||
/>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
{loading ? <Skeleton variant={"text"} width={75} /> : displayName}
|
||||
</ListItemText>
|
||||
<ListItemText>{loading ? <Skeleton variant={"text"} width={75} /> : displayName}</ListItemText>
|
||||
</StyledMenuItem>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Button, ButtonGroup, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { bindPopover } from "material-ui-popup-state";
|
||||
import {
|
||||
bindMenu,
|
||||
bindTrigger,
|
||||
usePopupState,
|
||||
} from "material-ui-popup-state/hooks";
|
||||
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||
import { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../../../redux/hooks.ts";
|
||||
|
|
@ -42,12 +32,8 @@ const TopActions = () => {
|
|||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
const sortOptions = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.props.order_by_options,
|
||||
);
|
||||
const isSingleFileView = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].list?.single_file_view,
|
||||
);
|
||||
const sortOptions = useAppSelector((state) => state.fileManager[fmIndex].list?.props.order_by_options);
|
||||
const isSingleFileView = useAppSelector((state) => state.fileManager[fmIndex].list?.single_file_view);
|
||||
const viewPopupState = usePopupState({
|
||||
variant: "popover",
|
||||
popupId: "viewOption",
|
||||
|
|
@ -68,11 +54,7 @@ const TopActions = () => {
|
|||
{...bindTrigger(viewPopupState)}
|
||||
startIcon={!isMobile && <TableSettingsOutlined />}
|
||||
>
|
||||
{isMobile ? (
|
||||
<TableSettingsOutlined fontSize={"small"} />
|
||||
) : (
|
||||
t("application:fileManager.view")
|
||||
)}
|
||||
{isMobile ? <TableSettingsOutlined fontSize={"small"} /> : t("application:fileManager.view")}
|
||||
</ActionButton>
|
||||
{(!(!sortOptions || isSingleFileView) || !isMobile) && (
|
||||
<ActionButton
|
||||
|
|
@ -80,11 +62,7 @@ const TopActions = () => {
|
|||
startIcon={!isMobile && <ArrowSort />}
|
||||
{...bindTrigger(sortPopupState)}
|
||||
>
|
||||
{isMobile ? (
|
||||
<ArrowSort fontSize={"small"} />
|
||||
) : (
|
||||
t("application:fileManager.sortMethod")
|
||||
)}
|
||||
{isMobile ? <ArrowSort fontSize={"small"} /> : t("application:fileManager.sortMethod")}
|
||||
</ActionButton>
|
||||
)}
|
||||
{isMobile && (
|
||||
|
|
|
|||
|
|
@ -14,12 +14,8 @@ interface PinnedItem {
|
|||
|
||||
export const usePinned = () => {
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
const generation = useAppSelector(
|
||||
(state) => state.globalState.pinedGeneration,
|
||||
);
|
||||
const path_root = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].path_root,
|
||||
);
|
||||
const generation = useAppSelector((state) => state.globalState.pinedGeneration);
|
||||
const path_root = useAppSelector((state) => state.fileManager[fmIndex].path_root);
|
||||
const pined = useMemo(() => {
|
||||
try {
|
||||
return SessionManager.currentLogin().user.pined?.map((p): PinnedItem => {
|
||||
|
|
@ -38,9 +34,7 @@ export const usePinned = () => {
|
|||
|
||||
const Pinned = memo(() => {
|
||||
const fmIndex = useContext(FmIndexContext);
|
||||
const elements = useAppSelector(
|
||||
(state) => state.fileManager[fmIndex].path_elements,
|
||||
);
|
||||
const elements = useAppSelector((state) => state.fileManager[fmIndex].path_elements);
|
||||
const pined = usePinned();
|
||||
|
||||
return (
|
||||
|
|
@ -50,14 +44,9 @@ const Pinned = memo(() => {
|
|||
sx={{ mt: index == 0 ? 2 : 0 }}
|
||||
flatten={
|
||||
p.crUri.fs() != Filesystem.share ||
|
||||
(p.crUri.fs() == Filesystem.share &&
|
||||
(!p.crUri.is_root() || p.crUri.is_search()))
|
||||
}
|
||||
canDrop={
|
||||
(p.crUri.fs() == Filesystem.share ||
|
||||
p.crUri.fs() == Filesystem.my) &&
|
||||
!p.crUri.is_search()
|
||||
(p.crUri.fs() == Filesystem.share && (!p.crUri.is_root() || p.crUri.is_search()))
|
||||
}
|
||||
canDrop={(p.crUri.fs() == Filesystem.share || p.crUri.fs() == Filesystem.my) && !p.crUri.is_search()}
|
||||
level={0}
|
||||
labelOverwrite={p.name}
|
||||
path={p.uri}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,11 @@ import { SideNavItemBase } from "../../Frame/NavBar/SideNavItem.tsx";
|
|||
import clsx from "clsx";
|
||||
import FileIcon from "../Explorer/FileIcon.tsx";
|
||||
import { FileResponse } from "../../../api/explorer.ts";
|
||||
import {
|
||||
TreeItem,
|
||||
treeItemClasses,
|
||||
TreeItemContentProps,
|
||||
TreeItemProps,
|
||||
useTreeItem,
|
||||
} from "@mui/x-tree-view";
|
||||
import { TreeItem, treeItemClasses, TreeItemContentProps, TreeItemProps, useTreeItem } from "@mui/x-tree-view";
|
||||
import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx";
|
||||
import { mergeRefs } from "../../../util";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import {
|
||||
loadChild,
|
||||
navigateToPath,
|
||||
} from "../../../redux/thunks/filemanager.ts";
|
||||
import { loadChild, navigateToPath } from "../../../redux/thunks/filemanager.ts";
|
||||
import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
|
||||
import CaretDown from "../../Icons/CaretDown.tsx";
|
||||
import { StartIcon } from "../TopBar/BreadcrumbButton.tsx";
|
||||
|
|
@ -41,9 +32,7 @@ const CustomContentRoot = styled(SideNavItemBase)<{
|
|||
opacity: isDragging ? 0.5 : 1,
|
||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms",
|
||||
transitionProperty: "opacity,box-shadow,background-color",
|
||||
boxShadow: isDropOver
|
||||
? `inset 0 0 0 2px ${theme.palette.primary.light}`
|
||||
: "none",
|
||||
boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
|
||||
height: "32px",
|
||||
}));
|
||||
|
||||
|
|
@ -56,16 +45,14 @@ const StyledTreeItemRoot = styled(TreeItem)(() => ({
|
|||
},
|
||||
})) as unknown as typeof TreeItem;
|
||||
|
||||
export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>(
|
||||
({ theme, expanded }) => ({
|
||||
fontSize: "12px!important",
|
||||
transform: `rotate(${expanded ? 0 : -90}deg)`,
|
||||
transition: theme.transitions.create("transform", {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
easing: theme.transitions.easing.easeInOut,
|
||||
}),
|
||||
export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>(({ theme, expanded }) => ({
|
||||
fontSize: "12px!important",
|
||||
transform: `rotate(${expanded ? 0 : -90}deg)`,
|
||||
transition: theme.transitions.create("transform", {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
easing: theme.transitions.easing.easeInOut,
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
||||
export interface CustomContentProps extends TreeItemContentProps {
|
||||
parent?: string;
|
||||
|
|
@ -117,12 +104,7 @@ const UnpinButton = (props: UnpinButton) => {
|
|||
return (
|
||||
<Fade in={props.show} unmountOnExit>
|
||||
<Tooltip title={t("application:fileManager.unpin")}>
|
||||
<SmallIconButton
|
||||
disabled={loading}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={onClick}
|
||||
size="small"
|
||||
>
|
||||
<SmallIconButton disabled={loading} onMouseDown={(e) => e.stopPropagation()} onClick={onClick} size="small">
|
||||
<Dismiss fontSize={"inherit"} />
|
||||
</SmallIconButton>
|
||||
</Tooltip>
|
||||
|
|
@ -136,27 +118,10 @@ const CustomContent = React.memo(
|
|||
const fmIndex = useContext(FmIndexContext);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showDelete, setShowDelete] = useState(false);
|
||||
const {
|
||||
classes,
|
||||
className,
|
||||
label,
|
||||
nodeId,
|
||||
icon: iconProp,
|
||||
expansionIcon,
|
||||
displayIcon,
|
||||
file,
|
||||
fileIcon,
|
||||
} = props;
|
||||
const { classes, className, label, nodeId, icon: iconProp, expansionIcon, displayIcon, file, fileIcon } = props;
|
||||
|
||||
const {
|
||||
disabled,
|
||||
expanded,
|
||||
selected,
|
||||
focused,
|
||||
handleExpansion,
|
||||
handleSelection,
|
||||
preventSelection,
|
||||
} = useTreeItem(nodeId);
|
||||
const { disabled, expanded, selected, focused, handleExpansion, handleSelection, preventSelection } =
|
||||
useTreeItem(nodeId);
|
||||
|
||||
const uri = useMemo(() => {
|
||||
// Trim 'pinedPrefix' if exist in prefix
|
||||
|
|
@ -169,9 +134,7 @@ const CustomContent = React.memo(
|
|||
|
||||
const icon = iconProp || expansionIcon || displayIcon;
|
||||
|
||||
const handleMouseDown = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
) => {
|
||||
const handleMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
preventSelection(event);
|
||||
};
|
||||
|
||||
|
|
@ -182,13 +145,7 @@ const CustomContent = React.memo(
|
|||
handleExpansion(event);
|
||||
if (!expanded) {
|
||||
try {
|
||||
await dispatch(
|
||||
loadChild(
|
||||
fmIndex,
|
||||
uri,
|
||||
() => (timeOutID = setTimeout(() => setLoading(true), 300)),
|
||||
),
|
||||
);
|
||||
await dispatch(loadChild(fmIndex, uri, () => (timeOutID = setTimeout(() => setLoading(true), 300))));
|
||||
} finally {
|
||||
if (timeOutID) {
|
||||
clearTimeout(timeOutID);
|
||||
|
|
@ -211,14 +168,7 @@ const CustomContent = React.memo(
|
|||
|
||||
const FileItemIcon = useMemo(() => {
|
||||
if (props.loading) {
|
||||
return (
|
||||
<Skeleton
|
||||
sx={{ mr: "14px" }}
|
||||
variant={"rounded"}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
);
|
||||
return <Skeleton sx={{ mr: "14px" }} variant={"rounded"} width={20} height={20} />;
|
||||
}
|
||||
if (fileIcon && fileIcon.Icons) {
|
||||
return (
|
||||
|
|
@ -320,9 +270,7 @@ const CustomContent = React.memo(
|
|||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={handleExpansionClick} className={classes.iconContainer}>
|
||||
{icon && !loading && <CaretDownIcon expanded={expanded} />}
|
||||
{icon && loading && (
|
||||
<FacebookCircularProgress size={15} sx={{ pt: 0.5 }} />
|
||||
)}
|
||||
{icon && loading && <FacebookCircularProgress size={15} sx={{ pt: 0.5 }} />}
|
||||
</div>
|
||||
{FileItemIcon}
|
||||
{fileName}
|
||||
|
|
@ -333,23 +281,11 @@ const CustomContent = React.memo(
|
|||
);
|
||||
|
||||
const TreeFile = React.memo(
|
||||
React.forwardRef(function CustomTreeItem(
|
||||
props: TreeFileProps,
|
||||
ref: React.Ref<HTMLLIElement>,
|
||||
) {
|
||||
React.forwardRef(function CustomTreeItem(props: TreeFileProps, ref: React.Ref<HTMLLIElement>) {
|
||||
const contentProps = useMemo(() => {
|
||||
const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } =
|
||||
props;
|
||||
const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } = props;
|
||||
return { level, file, notLoaded, fileIcon, loading, pinned, canDrop };
|
||||
}, [
|
||||
props.level,
|
||||
props.file,
|
||||
props.notLoaded,
|
||||
props.fileIcon,
|
||||
props.loading,
|
||||
props.canDrop,
|
||||
props.pinned,
|
||||
]);
|
||||
}, [props.level, props.file, props.notLoaded, props.fileIcon, props.loading, props.canDrop, props.pinned]);
|
||||
return (
|
||||
<StyledTreeItemRoot
|
||||
ContentComponent={CustomContent}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ const Loading = () => {
|
|||
};
|
||||
|
||||
const HeadlessFrame = () => {
|
||||
const loading = useAppSelector(
|
||||
(state) => state.globalState.loading.headlessFrame,
|
||||
);
|
||||
const loading = useAppSelector((state) => state.globalState.loading.headlessFrame);
|
||||
const dispatch = useAppDispatch();
|
||||
let navigation = useNavigation();
|
||||
|
||||
|
|
@ -31,9 +29,7 @@ const HeadlessFrame = () => {
|
|||
<Box
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900],
|
||||
theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900],
|
||||
flexGrow: 1,
|
||||
height: "100vh",
|
||||
overflow: "auto",
|
||||
|
|
@ -51,8 +47,7 @@ const HeadlessFrame = () => {
|
|||
<Box sx={{ width: "100%", py: 2 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
padding: (theme) =>
|
||||
`${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
|
||||
padding: (theme) => `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
|
||||
}}
|
||||
>
|
||||
<Logo
|
||||
|
|
@ -66,10 +61,7 @@ const HeadlessFrame = () => {
|
|||
<div>
|
||||
<Box
|
||||
sx={{
|
||||
display:
|
||||
loading || navigation.state !== "idle"
|
||||
? "none"
|
||||
: "block",
|
||||
display: loading || navigation.state !== "idle" ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
Popover,
|
||||
PopoverProps,
|
||||
Stack,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Drawer, Popover, PopoverProps, Stack, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import DrawerHeader from "./DrawerHeader.tsx";
|
||||
import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx";
|
||||
|
|
@ -68,10 +60,7 @@ const AppDrawer = () => {
|
|||
const theme = useTheme();
|
||||
const open = useAppSelector((state) => state.globalState.drawerOpen);
|
||||
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
|
||||
const appBarBg =
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900];
|
||||
const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
IconButton,
|
||||
Popover,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { IconButton, Popover, ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material";
|
||||
import DarkTheme from "../../Icons/DarkTheme.tsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo, useState } from "react";
|
||||
|
|
@ -21,11 +15,7 @@ interface SwitchPopoverProps {
|
|||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const SwitchPopover = ({
|
||||
open,
|
||||
anchorEl,
|
||||
onClose,
|
||||
}: SwitchPopoverProps) => {
|
||||
export const SwitchPopover = ({ open, anchorEl, onClose }: SwitchPopoverProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
|
@ -36,10 +26,7 @@ export const SwitchPopover = ({
|
|||
}
|
||||
return darkMode ? "dark" : "light";
|
||||
}, [darkMode]);
|
||||
const handleChange = (
|
||||
_event: React.MouseEvent<HTMLElement>,
|
||||
newMode: string,
|
||||
) => {
|
||||
const handleChange = (_event: React.MouseEvent<HTMLElement>, newMode: string) => {
|
||||
let newSetting: boolean | undefined;
|
||||
if (newMode === "light") {
|
||||
newSetting = false;
|
||||
|
|
@ -107,11 +94,7 @@ const DarkThemeSwitcher = () => {
|
|||
<DarkTheme />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<SwitchPopover
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
/>
|
||||
<SwitchPopover open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={() => setAnchorEl(null)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
Fade,
|
||||
IconButton,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Fade, IconButton, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setDrawerOpen } from "../../../redux/globalStateSlice.ts";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import { ChevronLeft } from "@mui/icons-material";
|
||||
|
|
@ -29,10 +22,7 @@ const DrawerHeader = () => {
|
|||
const [showCollapse, setShowCollapse] = useState(false);
|
||||
|
||||
return (
|
||||
<DrawerHeaderContainer
|
||||
onMouseEnter={() => setShowCollapse(true)}
|
||||
onMouseLeave={() => setShowCollapse(false)}
|
||||
>
|
||||
<DrawerHeaderContainer onMouseEnter={() => setShowCollapse(true)} onMouseLeave={() => setShowCollapse(false)}>
|
||||
<Box sx={{ width: "100%", pl: 2 }}>
|
||||
<Logo
|
||||
sx={{
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ import FileSelectedActions from "./FileSelectedActions.tsx";
|
|||
import { FileManagerIndex } from "../../FileManager/FileManager.tsx";
|
||||
|
||||
const NavBarMainActions = () => {
|
||||
const selected = useAppSelector(
|
||||
(state) => state.fileManager[FileManagerIndex.main].selected,
|
||||
);
|
||||
const selected = useAppSelector((state) => state.fileManager[FileManagerIndex.main].selected);
|
||||
const targets = useMemo(() => {
|
||||
return Object.keys(selected).map((key) => selected[key]);
|
||||
}, [selected]);
|
||||
|
|
@ -17,9 +15,7 @@ const NavBarMainActions = () => {
|
|||
<>
|
||||
<SwitchTransition>
|
||||
<CSSTransition
|
||||
addEndListener={(node, done) =>
|
||||
node.addEventListener("transitionend", done, false)
|
||||
}
|
||||
addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
|
||||
classNames="fade"
|
||||
key={`${targets.length > 0}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,7 @@ export interface NavIconTransitionProps {
|
|||
iconProps?: SvgIconProps;
|
||||
}
|
||||
|
||||
const NavIconTransition = ({
|
||||
fileIcon,
|
||||
active,
|
||||
iconProps,
|
||||
...rest
|
||||
}: NavIconTransitionProps) => {
|
||||
const NavIconTransition = ({ fileIcon, active, iconProps, ...rest }: NavIconTransitionProps) => {
|
||||
const [Active, InActive] = fileIcon;
|
||||
return (
|
||||
<Box {...rest}>
|
||||
|
|
@ -30,11 +25,7 @@ const NavIconTransition = ({
|
|||
{!active && (
|
||||
<Fade key={"inactive"}>
|
||||
<span>
|
||||
<InActive
|
||||
sx={{ position: "absolute" }}
|
||||
key={"inactive"}
|
||||
{...iconProps}
|
||||
/>
|
||||
<InActive sx={{ position: "absolute" }} key={"inactive"} {...iconProps} />
|
||||
</span>
|
||||
</Fade>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
alpha,
|
||||
Button,
|
||||
IconButton,
|
||||
styled,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { alpha, Button, IconButton, styled, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { setSearchPopup } from "../../../redux/globalStateSlice.ts";
|
||||
|
|
@ -14,10 +6,7 @@ import { useAppDispatch } from "../../../redux/hooks.ts";
|
|||
import Search from "../../Icons/Search.tsx";
|
||||
|
||||
export const KeyIndicator = styled("code")(({ theme }) => ({
|
||||
backgroundColor:
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900],
|
||||
backgroundColor: theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900],
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
boxShadow:
|
||||
theme.palette.mode === "light"
|
||||
|
|
@ -36,7 +25,7 @@ const SearchButton = styled(Button)(({ theme }) => ({
|
|||
" :hover": {
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.04),
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const SearchBar = () => {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,7 @@ const SplitHandle = (_props: SplitHandleProps) => {
|
|||
function onMouseMove(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
const newWidth = e.clientX - document.body.offsetLeft;
|
||||
const cappedWidth = Math.max(
|
||||
Math.min(newWidth, window.innerWidth / 2),
|
||||
minDrawerWidth,
|
||||
);
|
||||
const cappedWidth = Math.max(Math.min(newWidth, window.innerWidth / 2), minDrawerWidth);
|
||||
setCursor(cappedWidth);
|
||||
finalWidth.current = cappedWidth;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,8 @@
|
|||
import { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
Collapse,
|
||||
IconButton,
|
||||
Stack,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { AppBar, Box, Collapse, IconButton, Stack, Toolbar, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { Menu } from "@mui/icons-material";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import {
|
||||
setDrawerOpen,
|
||||
setMobileDrawerOpen,
|
||||
} from "../../../redux/globalStateSlice.ts";
|
||||
import { setDrawerOpen, setMobileDrawerOpen } from "../../../redux/globalStateSlice.ts";
|
||||
import NewButton from "../../FileManager/NewButton.tsx";
|
||||
import UserAction from "./UserAction.tsx";
|
||||
import Setting from "../../Icons/Setting.tsx";
|
||||
|
|
@ -43,19 +30,12 @@ const TopAppBar = () => {
|
|||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const open = useAppSelector((state) => state.globalState.drawerOpen);
|
||||
const mobileDrawerOpen = useAppSelector(
|
||||
(state) => state.globalState.mobileDrawerOpen,
|
||||
);
|
||||
const mobileDrawerOpen = useAppSelector((state) => state.globalState.mobileDrawerOpen);
|
||||
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
|
||||
const musicPlayer = useAppSelector((state) => state.globalState.musicPlayer);
|
||||
const [mobileMenuAnchor, setMobileMenuAnchor] = useState<null | HTMLElement>(
|
||||
null,
|
||||
);
|
||||
const [mobileMenuAnchor, setMobileMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
|
||||
const appBarBg =
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900];
|
||||
const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
|
||||
const isLogin = !!SessionManager.currentLoginOrNull();
|
||||
|
||||
const onMobileMenuClicked = (e: React.MouseEvent<HTMLElement>) => {
|
||||
|
|
@ -94,10 +74,7 @@ const TopAppBar = () => {
|
|||
<Toolbar
|
||||
sx={{
|
||||
"&.MuiToolbar-root.MuiToolbar-gutters": {
|
||||
paddingLeft:
|
||||
open && !isMobile
|
||||
? theme.spacing(0)
|
||||
: theme.spacing(isMobile ? 2 : 3),
|
||||
paddingLeft: open && !isMobile ? theme.spacing(0) : theme.spacing(isMobile ? 2 : 3),
|
||||
transition: theme.transitions.create("padding", {
|
||||
easing: theme.transitions.easing.easeInOut,
|
||||
duration: theme.transitions.duration.standard,
|
||||
|
|
@ -110,11 +87,7 @@ const TopAppBar = () => {
|
|||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
// @ts-ignore
|
||||
onClick={
|
||||
isMobile
|
||||
? onMobileMenuClicked
|
||||
: () => dispatch(setDrawerOpen(true))
|
||||
}
|
||||
onClick={isMobile ? onMobileMenuClicked : () => dispatch(setDrawerOpen(true))}
|
||||
edge="start"
|
||||
sx={{
|
||||
mr: isMobile ? 1 : 2,
|
||||
|
|
@ -126,11 +99,7 @@ const TopAppBar = () => {
|
|||
</Collapse>
|
||||
{isMobile && (
|
||||
<>
|
||||
<DrawerPopover
|
||||
open={!!mobileDrawerOpen}
|
||||
anchorEl={mobileMenuAnchor}
|
||||
onClose={onCloseMobileMenu}
|
||||
/>
|
||||
<DrawerPopover open={!!mobileDrawerOpen} anchorEl={mobileMenuAnchor} onClose={onCloseMobileMenu} />
|
||||
</>
|
||||
)}
|
||||
{!isMobile && isMainPage && (
|
||||
|
|
@ -154,10 +123,7 @@ const TopAppBar = () => {
|
|||
<DarkThemeSwitcher />
|
||||
{isLogin && (
|
||||
<Tooltip title={t("navbar.setting")}>
|
||||
<IconButton
|
||||
size="large"
|
||||
onClick={() => navigate("/settings")}
|
||||
>
|
||||
<IconButton size="large" onClick={() => navigate("/settings")}>
|
||||
<Setting />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -4,22 +4,10 @@ export default function Border(props: SvgIconProps) {
|
|||
return (
|
||||
<SvgIcon {...props}>
|
||||
<g fill="none">
|
||||
<path
|
||||
d="M14 3.75a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h2.5a.75.75 0 0 1 .75.75z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.5 10.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 0 1.5 0v-2.5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M19.5 10.75a.75.75 0 0 1 1.5 0v2.5a.75.75 0 0 1-1.5 0v-2.5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.25 21a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 0 0 1.5h2.5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M14 3.75a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h2.5a.75.75 0 0 1 .75.75z" fill="currentColor" />
|
||||
<path d="M4.5 10.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 0 1.5 0v-2.5z" fill="currentColor" />
|
||||
<path d="M19.5 10.75a.75.75 0 0 1 1.5 0v2.5a.75.75 0 0 1-1.5 0v-2.5z" fill="currentColor" />
|
||||
<path d="M13.25 21a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 0 0 1.5h2.5z" fill="currentColor" />
|
||||
<path
|
||||
d="M7 3.75A.75.75 0 0 0 6.25 3h-.5A2.75 2.75 0 0 0 3 5.75v.5a.75.75 0 0 0 1.5 0v-.5c0-.69.56-1.25 1.25-1.25h.5A.75.75 0 0 0 7 3.75z"
|
||||
fill="currentColor"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
DialogContent,
|
||||
DialogProps,
|
||||
FilledInput,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { DialogContent, DialogProps, FilledInput, IconButton, InputAdornment, InputLabel, Stack } from "@mui/material";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||
import { DavAccount } from "../../../api/setting.ts";
|
||||
|
|
@ -47,11 +39,7 @@ const InfoTextField = ({ label, value }: { label: string; value: string }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const ConnectionInfoDialog = ({
|
||||
onClose,
|
||||
account,
|
||||
...rest
|
||||
}: ConnectionInfoDialogProps) => {
|
||||
const ConnectionInfoDialog = ({ onClose, account, ...rest }: ConnectionInfoDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
|
@ -70,18 +58,12 @@ const ConnectionInfoDialog = ({
|
|||
>
|
||||
<DialogContent sx={{ pt: 1 }}>
|
||||
<Stack spacing={2} direction={"column"}>
|
||||
<InfoTextField
|
||||
label={t("application:setting.webdavServer")}
|
||||
value={window.location.origin + "/dav"}
|
||||
/>
|
||||
<InfoTextField label={t("application:setting.webdavServer")} value={window.location.origin + "/dav"} />
|
||||
<InfoTextField
|
||||
label={t("application:setting.userName")}
|
||||
value={SessionManager.currentLoginOrNull()?.user.email ?? ""}
|
||||
/>
|
||||
<InfoTextField
|
||||
label={t("application:login.password")}
|
||||
value={account?.password ?? ""}
|
||||
/>
|
||||
<InfoTextField label={t("application:login.password")} value={account?.password ?? ""} />
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
</DraggableDialog>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Checkbox,
|
||||
DialogContent,
|
||||
DialogProps,
|
||||
FormGroup,
|
||||
Stack,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Checkbox, DialogContent, DialogProps, FormGroup, Stack, Typography, useTheme } from "@mui/material";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
|
@ -22,10 +14,7 @@ import Boolset from "../../../util/boolset.ts";
|
|||
import { GroupPermission } from "../../../api/user.ts";
|
||||
import SessionManager from "../../../session";
|
||||
import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx";
|
||||
import {
|
||||
sendCreateDavAccounts,
|
||||
sendUpdateDavAccounts,
|
||||
} from "../../../api/api.ts";
|
||||
import { sendCreateDavAccounts, sendUpdateDavAccounts } from "../../../api/api.ts";
|
||||
import { DavAccount, DavAccountOption } from "../../../api/setting.ts";
|
||||
|
||||
export interface CreateDAVAccountDialogProps extends DialogProps {
|
||||
|
|
@ -55,9 +44,7 @@ const CreateDAVAccountDialog = ({
|
|||
const theme = useTheme();
|
||||
|
||||
const groupProxyEnabled = useMemo(() => {
|
||||
const perm = new Boolset(
|
||||
SessionManager.currentLoginOrNull()?.user.group?.permission,
|
||||
);
|
||||
const perm = new Boolset(SessionManager.currentLoginOrNull()?.user.group?.permission);
|
||||
return perm.enabled(GroupPermission.webdav_proxy);
|
||||
}, []);
|
||||
|
||||
|
|
@ -83,11 +70,7 @@ const CreateDAVAccountDialog = ({
|
|||
proxy,
|
||||
readonly,
|
||||
};
|
||||
dispatch(
|
||||
editTarget
|
||||
? sendUpdateDavAccounts(editTarget.id, req)
|
||||
: sendCreateDavAccounts(req),
|
||||
)
|
||||
dispatch(editTarget ? sendUpdateDavAccounts(editTarget.id, req) : sendCreateDavAccounts(req))
|
||||
.then((account) => {
|
||||
onClose && onClose({}, "escapeKeyDown");
|
||||
!editTarget && onAccountAdded && onAccountAdded(account);
|
||||
|
|
@ -146,39 +129,19 @@ const CreateDAVAccountDialog = ({
|
|||
>
|
||||
<FormGroup>
|
||||
<SmallFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
onChange={(e) => setReadonly(e.target.checked)}
|
||||
checked={readonly}
|
||||
/>
|
||||
}
|
||||
control={<Checkbox size="small" onChange={(e) => setReadonly(e.target.checked)} checked={readonly} />}
|
||||
label={t("application:setting.readonlyOn")}
|
||||
/>
|
||||
<Typography
|
||||
sx={{ pl: "27px" }}
|
||||
variant={"caption"}
|
||||
color={"text.secondary"}
|
||||
>
|
||||
<Typography sx={{ pl: "27px" }} variant={"caption"} color={"text.secondary"}>
|
||||
{t("application:setting.readonlyTooltip")}
|
||||
</Typography>
|
||||
{groupProxyEnabled && (
|
||||
<>
|
||||
<SmallFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
onChange={(e) => setProxy(e.target.checked)}
|
||||
checked={proxy}
|
||||
/>
|
||||
}
|
||||
control={<Checkbox size="small" onChange={(e) => setProxy(e.target.checked)} checked={proxy} />}
|
||||
label={t("application:setting.proxy")}
|
||||
/>
|
||||
<Typography
|
||||
sx={{ pl: "27px" }}
|
||||
variant={"caption"}
|
||||
color={"text.secondary"}
|
||||
>
|
||||
<Typography sx={{ pl: "27px" }} variant={"caption"} color={"text.secondary"}>
|
||||
{t("application:setting.proxyTooltip")}
|
||||
</Typography>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Table,
|
||||
TableBody,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Box, Table, TableBody, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getDavAccounts } from "../../../api/api.ts";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
|
|
@ -26,19 +18,14 @@ export interface DavAccountListProps {
|
|||
setCreateAccountDialog: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const DavAccountList = ({
|
||||
creatAccountDialog,
|
||||
setCreateAccountDialog,
|
||||
}: DavAccountListProps) => {
|
||||
const DavAccountList = ({ creatAccountDialog, setCreateAccountDialog }: DavAccountListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [nextPageToken, setNextPageToken] = useState<string | undefined>("");
|
||||
const [accounts, setAccounts] = useState<DavAccount[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [accountInfoTarget, setAccountInfoTarget] = useState<
|
||||
DavAccount | undefined
|
||||
>();
|
||||
const [accountInfoTarget, setAccountInfoTarget] = useState<DavAccount | undefined>();
|
||||
const [editTarget, setEditTarget] = useState<DavAccount | undefined>();
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
|
||||
|
|
@ -75,9 +62,7 @@ const DavAccountList = ({
|
|||
|
||||
const onAccountDeleted = useCallback(
|
||||
(id: string) => {
|
||||
setAccounts((accounts) =>
|
||||
accounts.filter((account) => account.id !== id),
|
||||
);
|
||||
setAccounts((accounts) => accounts.filter((account) => account.id !== id));
|
||||
},
|
||||
[setAccounts],
|
||||
);
|
||||
|
|
@ -103,9 +88,7 @@ const DavAccountList = ({
|
|||
open={editOpen}
|
||||
editTarget={editTarget}
|
||||
onClose={() => setEditOpen(false)}
|
||||
onAccountUpdated={(account) =>
|
||||
setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))
|
||||
}
|
||||
onAccountUpdated={(account) => setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))}
|
||||
/>
|
||||
<ConnectionInfoDialog
|
||||
open={accountInfoTarget != undefined}
|
||||
|
|
@ -116,26 +99,14 @@ const DavAccountList = ({
|
|||
<Table sx={{ width: "100%", tableLayout: "fixed" }} size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<NoWrapTableCell width={200}>{t("setting.annotation")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>
|
||||
{t("setting.annotation")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>
|
||||
<CellHeaderWithPadding>
|
||||
{t("setting.rootFolder")}
|
||||
</CellHeaderWithPadding>
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>
|
||||
{t("fileManager.permissions")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>
|
||||
{t("setting.proxy")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>
|
||||
{t("fileManager.createdAt")}
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={100}>
|
||||
{t("fileManager.actions")}
|
||||
<CellHeaderWithPadding>{t("setting.rootFolder")}</CellHeaderWithPadding>
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>{t("fileManager.permissions")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>{t("setting.proxy")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>{t("fileManager.createdAt")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={100}>{t("fileManager.actions")}</NoWrapTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
@ -152,9 +123,7 @@ const DavAccountList = ({
|
|||
<>
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<DavAccountRow
|
||||
onLoad={
|
||||
i == 0 ? loadNextPage(accounts, nextPageToken) : undefined
|
||||
}
|
||||
onLoad={i == 0 ? loadNextPage(accounts, nextPageToken) : undefined}
|
||||
loading={true}
|
||||
key={i}
|
||||
onAccountDeleted={onAccountDeleted}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import {
|
||||
IconButton,
|
||||
ListItemText,
|
||||
Menu,
|
||||
Skeleton,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import { IconButton, ListItemText, Menu, Skeleton, TableRow } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import { NoWrapCell, SquareChip } from "../../Common/StyledComponents.tsx";
|
||||
|
|
@ -20,11 +14,7 @@ import { useInView } from "react-intersection-observer";
|
|||
import Eye from "../../Icons/Eye.tsx";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import CloudFilled from "../../Icons/CloudFilled.tsx";
|
||||
import {
|
||||
bindMenu,
|
||||
bindTrigger,
|
||||
usePopupState,
|
||||
} from "material-ui-popup-state/hooks";
|
||||
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||
import MoreVertical from "../../Icons/MoreVertical.tsx";
|
||||
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
|
||||
import { sendDeleteDavAccount } from "../../../api/api.ts";
|
||||
|
|
@ -38,14 +28,7 @@ export interface DavAccountRowProps {
|
|||
onEditClicked?: (account: DavAccount) => void;
|
||||
}
|
||||
|
||||
const DavAccountRow = ({
|
||||
account,
|
||||
onAccountDeleted,
|
||||
onLoad,
|
||||
loading,
|
||||
onClick,
|
||||
onEditClicked,
|
||||
}: DavAccountRowProps) => {
|
||||
const DavAccountRow = ({ account, onAccountDeleted, onLoad, loading, onClick, onEditClicked }: DavAccountRowProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
|
@ -73,10 +56,7 @@ const DavAccountRow = ({
|
|||
const [readOnly, proxy] = useMemo(() => {
|
||||
if (!account?.options) return [false, false] as const;
|
||||
const bs = new Boolset(account.options);
|
||||
return [
|
||||
bs.enabled(DavAccountOption.readonly),
|
||||
bs.enabled(DavAccountOption.proxy),
|
||||
] as const;
|
||||
return [bs.enabled(DavAccountOption.readonly), bs.enabled(DavAccountOption.proxy)] as const;
|
||||
}, [account?.options]);
|
||||
|
||||
const { onClose, ...rest } = bindMenu(actionMenuState);
|
||||
|
|
@ -101,11 +81,7 @@ const DavAccountRow = ({
|
|||
return (
|
||||
<TableRow ref={ref} hover sx={{ cursor: "pointer" }} onClick={onClick}>
|
||||
<NoWrapCell component="th" scope="row">
|
||||
{loading ? (
|
||||
<Skeleton variant={"text"} width={200} sx={{ my: "6px" }} />
|
||||
) : (
|
||||
account?.name
|
||||
)}
|
||||
{loading ? <Skeleton variant={"text"} width={200} sx={{ my: "6px" }} /> : account?.name}
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
{loading ? (
|
||||
|
|
@ -126,17 +102,9 @@ const DavAccountRow = ({
|
|||
{loading ? (
|
||||
<Skeleton variant={"text"} width={100} />
|
||||
) : readOnly ? (
|
||||
<SquareChip
|
||||
icon={<Eye />}
|
||||
size={"small"}
|
||||
label={t("setting.readonlyOn")}
|
||||
/>
|
||||
<SquareChip icon={<Eye />} size={"small"} label={t("setting.readonlyOn")} />
|
||||
) : (
|
||||
<SquareChip
|
||||
icon={<Edit />}
|
||||
size={"small"}
|
||||
label={t("setting.readonlyOff")}
|
||||
/>
|
||||
<SquareChip icon={<Edit />} size={"small"} label={t("setting.readonlyOff")} />
|
||||
)}
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue