chore: format code

This commit is contained in:
Aaron Liu 2025-06-22 10:57:50 +08:00
parent 34adaeb5dc
commit 27de5db3e7
193 changed files with 2576 additions and 4514 deletions

View File

@ -1973,7 +1973,6 @@ export function sendImport(req: ImportWorkflowService): ThunkResponse<TaskRespon
}; };
} }
export function sendPatchViewSync(args: PatchViewSyncService): ThunkResponse<void> { export function sendPatchViewSync(args: PatchViewSyncService): ThunkResponse<void> {
return async (dispatch, _getState) => { return async (dispatch, _getState) => {
return await dispatch( return await dispatch(

View File

@ -8,15 +8,13 @@ import { DenseAutocomplete, DenseFilledTextField, NoWrapBox, SquareChip } from "
import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx"; import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx";
import LinkDismiss from "../../Icons/LinkDismiss.tsx"; import LinkDismiss from "../../Icons/LinkDismiss.tsx";
export interface SharesInputProps { export interface SharesInputProps {}
}
const SharesInput = (props: SharesInputProps) => { const SharesInput = (props: SharesInputProps) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [options, setOptions] = useState<number[]>([]); const [options, setOptions] = useState<number[]>([]);
return ( return (
<DenseAutocomplete <DenseAutocomplete
multiple multiple

View File

@ -134,10 +134,7 @@ const FileForm = () => {
{!fileParentLoading && ( {!fileParentLoading && (
<> <>
{fileParent && ( {fileParent && (
<StyledFileBadge <StyledFileBadge variant={"outlined"} simplifiedFile={{ path: fileParent, type: FileType.folder }} />
variant={"outlined"}
simplifiedFile={{ path: fileParent, type: FileType.folder }}
/>
)} )}
{!fileParent && "-"} {!fileParent && "-"}
</> </>

View File

@ -14,7 +14,12 @@ import { useSnackbar } from "notistack";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; 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 Add from "../../../Icons/Add";
import Delete from "../../../Icons/Delete"; import Delete from "../../../Icons/Delete";
import Edit from "../../../Icons/Edit"; import Edit from "../../../Icons/Edit";
@ -79,30 +84,39 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
} }
}, [value]); }, [value]);
const handleSave = useCallback((newOptions: Record<string, ThemeOption["config"]>) => { const handleSave = useCallback(
onChange(JSON.stringify(newOptions)); (newOptions: Record<string, ThemeOption["config"]>) => {
}, [onChange]); onChange(JSON.stringify(newOptions));
},
[onChange],
);
const handleDelete = useCallback((id: string) => { const handleDelete = useCallback(
// Prevent deleting the default theme (id: string) => {
if (id === defaultTheme) { // Prevent deleting the default theme
enqueueSnackbar({ if (id === defaultTheme) {
message: t("settings.cannotDeleteDefaultTheme"), enqueueSnackbar({
variant: "warning", message: t("settings.cannotDeleteDefaultTheme"),
action: DefaultCloseAction, variant: "warning",
}); action: DefaultCloseAction,
return; });
} return;
}
const newOptions = { ...options }; const newOptions = { ...options };
delete newOptions[id]; delete newOptions[id];
handleSave(newOptions); handleSave(newOptions);
}, [options, handleSave, defaultTheme, enqueueSnackbar, t]); },
[options, handleSave, defaultTheme, enqueueSnackbar, t],
);
const handleEdit = useCallback((id: string) => { const handleEdit = useCallback(
setEditingOption({ id, config: options[id] }); (id: string) => {
setIsDialogOpen(true); setEditingOption({ id, config: options[id] });
}, [options]); setIsDialogOpen(true);
},
[options],
);
const handleAdd = useCallback(() => { const handleAdd = useCallback(() => {
// Generate a new default theme option with a random color // Generate a new default theme option with a random color
@ -113,16 +127,16 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
light: { light: {
palette: { palette: {
primary: { main: randomColor }, primary: { main: randomColor },
secondary: { main: "#f50057" } secondary: { main: "#f50057" },
} },
}, },
dark: { dark: {
palette: { palette: {
primary: { main: randomColor }, primary: { main: randomColor },
secondary: { main: "#f50057" } secondary: { main: "#f50057" },
} },
} },
} },
}); });
setIsDialogOpen(true); setIsDialogOpen(true);
}, []); }, []);
@ -132,15 +146,58 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
setEditingOption(null); setEditingOption(null);
}, []); }, []);
const handleDialogSave = useCallback((id: string, newId: string, config: string) => { const handleDialogSave = useCallback(
try { (id: string, newId: string, config: string) => {
const parsedConfig = JSON.parse(config); 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 }; const newOptions = { ...options };
// If ID has changed (primary color changed), delete the old entry and create a new one if (type === "primary" && mode === "light") {
if (id !== newId) { // 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 // Check if the new ID already exists
if (newOptions[newId]) { if (newOptions[newId] && newId !== id) {
enqueueSnackbar({ enqueueSnackbar({
message: t("settings.duplicateThemeColor"), message: t("settings.duplicateThemeColor"),
variant: "warning", variant: "warning",
@ -149,67 +206,33 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
return; 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 we're changing the ID of the default theme, update the default theme reference
if (id === defaultTheme) { if (id === defaultTheme) {
onDefaultThemeChange(newId); onDefaultThemeChange(newId);
} }
} else {
delete newOptions[id]; // For other colors, just update the value
newOptions[id][mode].palette[type].main = color;
} }
newOptions[newId] = parsedConfig;
handleSave(newOptions); handleSave(newOptions);
setIsDialogOpen(false); },
setEditingOption(null); [options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange],
} 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 handleDefaultThemeChange = useCallback(
const newOptions = { ...options }; (id: string) => {
onDefaultThemeChange(id);
if (type === 'primary' && mode === 'light') { },
// If changing the primary color (which is the ID), we need to create a new entry [onDefaultThemeChange],
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 optionsArray = useMemo(() => { const optionsArray = useMemo(() => {
return Object.entries(options).map(([id, config]) => ({ return Object.entries(options).map(([id, config]) => ({
@ -229,8 +252,7 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
{optionsArray.length > 0 && ( {optionsArray.length > 0 && (
<TableContainer component={StyledTableContainerPaper} sx={{ mt: 2 }}> <TableContainer component={StyledTableContainerPaper} sx={{ mt: 2 }}>
<Table size="small" stickyHeader <Table size="small" stickyHeader sx={{ width: "100%", tableLayout: "fixed" }}>
sx={{ width: "100%", tableLayout: "fixed" }}>
<TableHead> <TableHead>
<TableRow> <TableRow>
<NoWrapTableCell width={50}>{t("settings.defaultTheme")}</NoWrapTableCell> <NoWrapTableCell width={50}>{t("settings.defaultTheme")}</NoWrapTableCell>
@ -255,25 +277,25 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
<HexColorInput <HexColorInput
required required
currentColor={option.config.light.palette.primary.main} 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>
<TableCell> <TableCell>
<HexColorInput <HexColorInput
currentColor={option.config.light.palette.secondary.main} 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>
<TableCell> <TableCell>
<HexColorInput <HexColorInput
currentColor={option.config.dark.palette.primary.main} 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>
<TableCell> <TableCell>
<HexColorInput <HexColorInput
currentColor={option.config.dark.palette.secondary.main} 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>
<TableCell align="right"> <TableCell align="right">
@ -295,12 +317,7 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
</TableContainer> </TableContainer>
)} )}
<SecondaryButton <SecondaryButton variant="contained" startIcon={<Add />} onClick={handleAdd} sx={{ mt: 2 }}>
variant="contained"
startIcon={<Add />}
onClick={handleAdd}
sx={{ mt: 2 }}
>
{t("settings.addThemeOption")} {t("settings.addThemeOption")}
</SecondaryButton> </SecondaryButton>
@ -317,4 +334,4 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
); );
}; };
export default ThemeOptions; export default ThemeOptions;

View File

@ -32,13 +32,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
<Trans <Trans
i18nKey="settings.capInstanceURLDes" i18nKey="settings.capInstanceURLDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
<Link
key={0}
href={"https://capjs.js.org/guide/standalone.html"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>
@ -58,13 +52,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
<Trans <Trans
i18nKey="settings.capKeyIDDes" i18nKey="settings.capKeyIDDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
<Link
key={0}
href={"https://capjs.js.org/guide/standalone.html"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>
@ -85,13 +73,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
<Trans <Trans
i18nKey="settings.capKeySecretDes" i18nKey="settings.capKeySecretDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
<Link
key={0}
href={"https://capjs.js.org/guide/standalone.html"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>
@ -100,4 +82,4 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
); );
}; };
export default CapCaptcha; export default CapCaptcha;

View File

@ -2,21 +2,8 @@ import { useTranslation } from "react-i18next";
import * as React from "react"; import * as React from "react";
import { useContext } from "react"; import { useContext } from "react";
import { SettingContext } from "../SettingWrapper.tsx"; import { SettingContext } from "../SettingWrapper.tsx";
import { import { Box, Collapse, FormControl, FormControlLabel, ListItemText, Stack, Switch, Typography } from "@mui/material";
Box, import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
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 SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { isTrueVal } from "../../../../session/utils.ts"; import { isTrueVal } from "../../../../session/utils.ts";
import { DenseSelect } from "../../../Common/StyledComponents.tsx"; import { DenseSelect } from "../../../Common/StyledComponents.tsx";
@ -54,9 +41,7 @@ const Captcha = () => {
} }
label={t("settings.captchaForLogin")} label={t("settings.captchaForLogin")}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.captchaForLoginDes")}</NoMarginHelperText>
{t("settings.captchaForLoginDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
<SettingForm lgWidth={5}> <SettingForm lgWidth={5}>
@ -74,9 +59,7 @@ const Captcha = () => {
} }
label={t("settings.captchaForSignup")} label={t("settings.captchaForSignup")}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.captchaForSignupDes")}</NoMarginHelperText>
{t("settings.captchaForSignupDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
<SettingForm lgWidth={5}> <SettingForm lgWidth={5}>
@ -94,9 +77,7 @@ const Captcha = () => {
} }
label={t("settings.captchaForReset")} label={t("settings.captchaForReset")}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.captchaForResetDes")}</NoMarginHelperText>
{t("settings.captchaForResetDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
</SettingSectionContent> </SettingSectionContent>
@ -117,61 +98,55 @@ const Captcha = () => {
value={values.captcha_type} value={values.captcha_type}
> >
<SquareMenuItem value={CaptchaType.NORMAL}> <SquareMenuItem value={CaptchaType.NORMAL}>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t("settings.plainCaptcha")} {t("settings.plainCaptcha")}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>
<SquareMenuItem value={CaptchaType.RECAPTCHA}> <SquareMenuItem value={CaptchaType.RECAPTCHA}>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t("settings.reCaptchaV2")} {t("settings.reCaptchaV2")}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>
<SquareMenuItem value={CaptchaType.TURNSTILE}> <SquareMenuItem value={CaptchaType.TURNSTILE}>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t("settings.turnstile")} {t("settings.turnstile")}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>
<SquareMenuItem value={CaptchaType.CAP}> <SquareMenuItem value={CaptchaType.CAP}>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t("settings.cap")} {t("settings.cap")}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>
</DenseSelect> </DenseSelect>
<NoMarginHelperText> <NoMarginHelperText>{t("settings.captchaTypeDes")}</NoMarginHelperText>
{t("settings.captchaTypeDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
<Collapse <Collapse in={values.captcha_type === CaptchaType.NORMAL} unmountOnExit>
in={values.captcha_type === CaptchaType.NORMAL}
unmountOnExit
>
<GraphicCaptcha setSettings={setSettings} values={values} /> <GraphicCaptcha setSettings={setSettings} values={values} />
</Collapse> </Collapse>
<Collapse <Collapse in={values.captcha_type === CaptchaType.RECAPTCHA} unmountOnExit>
in={values.captcha_type === CaptchaType.RECAPTCHA}
unmountOnExit
>
<ReCaptcha setSettings={setSettings} values={values} /> <ReCaptcha setSettings={setSettings} values={values} />
</Collapse> </Collapse>
<Collapse <Collapse in={values.captcha_type === CaptchaType.TURNSTILE} unmountOnExit>
in={values.captcha_type === CaptchaType.TURNSTILE}
unmountOnExit
>
<TurnstileCaptcha setSettings={setSettings} values={values} /> <TurnstileCaptcha setSettings={setSettings} values={values} />
</Collapse> </Collapse>
<Collapse <Collapse in={values.captcha_type === CaptchaType.CAP} unmountOnExit>
in={values.captcha_type === CaptchaType.CAP}
unmountOnExit
>
<CapCaptcha setSettings={setSettings} values={values} /> <CapCaptcha setSettings={setSettings} values={values} />
</Collapse> </Collapse>
</SettingSectionContent> </SettingSectionContent>

View File

@ -1,11 +1,5 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { FormControl, FormControlLabel, ListItemText, Stack, Switch } from "@mui/material";
FormControl,
FormControlLabel,
ListItemText,
Stack,
Switch,
} from "@mui/material";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { DenseSelect } from "../../../Common/StyledComponents.tsx"; import { DenseSelect } from "../../../Common/StyledComponents.tsx";
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
@ -33,16 +27,13 @@ const GraphicCaptcha = ({ values, setSettings }: GraphicCaptchaProps) => {
} }
value={values.captcha_mode} value={values.captcha_mode}
> >
{[ {["captchaModeNumber", "captchaModeLetter", "captchaModeMath", "captchaModeNumberLetter"].map((k, i) => (
"captchaModeNumber",
"captchaModeLetter",
"captchaModeMath",
"captchaModeNumberLetter",
].map((k, i) => (
<SquareMenuItem key={k} value={i.toString()}> <SquareMenuItem key={k} value={i.toString()}>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t(`settings.${k}`)} {t(`settings.${k}`)}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>

View File

@ -31,13 +31,7 @@ const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => {
<Trans <Trans
i18nKey="settings.siteKeyDes" i18nKey="settings.siteKeyDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://www.google.com/recaptcha/admin/create"} target={"_blank"} />]}
<Link
key={0}
href={"https://www.google.com/recaptcha/admin/create"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>
@ -57,13 +51,7 @@ const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => {
<Trans <Trans
i18nKey="settings.siteSecretDes" i18nKey="settings.siteSecretDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://www.google.com/recaptcha/admin/create"} target={"_blank"} />]}
<Link
key={0}
href={"https://www.google.com/recaptcha/admin/create"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>

View File

@ -31,13 +31,7 @@ const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => {
<Trans <Trans
i18nKey="settings.siteKeyDes" i18nKey="settings.siteKeyDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://dash.cloudflare.com/"} target={"_blank"} />]}
<Link
key={0}
href={"https://dash.cloudflare.com/"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>
@ -57,13 +51,7 @@ const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => {
<Trans <Trans
i18nKey="settings.siteSecretDes" i18nKey="settings.siteSecretDes"
ns={"dashboard"} ns={"dashboard"}
components={[ components={[<Link key={0} href={"https://dash.cloudflare.com/"} target={"_blank"} />]}
<Link
key={0}
href={"https://dash.cloudflare.com/"}
target={"_blank"}
/>,
]}
/> />
</NoMarginHelperText> </NoMarginHelperText>
</FormControl> </FormControl>

View File

@ -1,12 +1,4 @@
import { import { Box, DialogContent, FormControl, FormControlLabel, Stack, Switch, Typography } from "@mui/material";
Box,
DialogContent,
FormControl,
FormControlLabel,
Stack,
Switch,
Typography
} from "@mui/material";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -18,11 +10,7 @@ import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledCom
import DraggableDialog, { StyledDialogContentText } from "../../../Dialogs/DraggableDialog.tsx"; import DraggableDialog, { StyledDialogContentText } from "../../../Dialogs/DraggableDialog.tsx";
import MailOutlined from "../../../Icons/MailOutlined.tsx"; import MailOutlined from "../../../Icons/MailOutlined.tsx";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
NoMarginHelperText,
SettingSection,
SettingSectionContent,
} from "../Settings.tsx";
import { SettingContext } from "../SettingWrapper.tsx"; import { SettingContext } from "../SettingWrapper.tsx";
import EmailTemplates from "./EmailTemplates.tsx"; import EmailTemplates from "./EmailTemplates.tsx";
@ -38,10 +26,12 @@ const Email = () => {
const handleTestEmail = async () => { const handleTestEmail = async () => {
setSending(true); setSending(true);
try { try {
await dispatch(sendTestSMTP({ await dispatch(
to: testEmailAddress, sendTestSMTP({
settings: values, to: testEmailAddress,
})); settings: values,
}),
);
enqueueSnackbar({ enqueueSnackbar({
message: t("settings.testMailSent"), message: t("settings.testMailSent"),
variant: "success", variant: "success",
@ -69,9 +59,7 @@ const Email = () => {
title={t("settings.testSMTPSettings")} title={t("settings.testSMTPSettings")}
> >
<DialogContent> <DialogContent>
<StyledDialogContentText sx={{ mb: 2 }}> <StyledDialogContentText sx={{ mb: 2 }}>{t("settings.testSMTPTooltip")}</StyledDialogContentText>
{t("settings.testSMTPTooltip")}
</StyledDialogContentText>
<SettingForm title={t("settings.recipient")} lgWidth={12}> <SettingForm title={t("settings.recipient")} lgWidth={12}>
<DenseFilledTextField <DenseFilledTextField
required required
@ -86,7 +74,9 @@ const Email = () => {
</DraggableDialog> </DraggableDialog>
<SettingSection> <SettingSection>
<Typography variant="h6" gutterBottom>{t("settings.smtp")}</Typography> <Typography variant="h6" gutterBottom>
{t("settings.smtp")}
</Typography>
<SettingSectionContent> <SettingSectionContent>
<SettingForm title={t("settings.senderName")} lgWidth={5}> <SettingForm title={t("settings.senderName")} lgWidth={5}>
<FormControl fullWidth> <FormControl fullWidth>
@ -95,9 +85,7 @@ const Email = () => {
value={values.fromName ?? ""} value={values.fromName ?? ""}
onChange={(e) => setSettings({ fromName: e.target.value })} onChange={(e) => setSettings({ fromName: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.senderNameDes")}</NoMarginHelperText>
{t("settings.senderNameDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -109,9 +97,7 @@ const Email = () => {
value={values.fromAdress ?? ""} value={values.fromAdress ?? ""}
onChange={(e) => setSettings({ fromAdress: e.target.value })} onChange={(e) => setSettings({ fromAdress: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.senderAddressDes")}</NoMarginHelperText>
{t("settings.senderAddressDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -122,9 +108,7 @@ const Email = () => {
value={values.smtpHost ?? ""} value={values.smtpHost ?? ""}
onChange={(e) => setSettings({ smtpHost: e.target.value })} onChange={(e) => setSettings({ smtpHost: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.smtpServerDes")}</NoMarginHelperText>
{t("settings.smtpServerDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -137,9 +121,7 @@ const Email = () => {
value={values.smtpPort ?? ""} value={values.smtpPort ?? ""}
onChange={(e) => setSettings({ smtpPort: e.target.value })} onChange={(e) => setSettings({ smtpPort: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.smtpPortDes")}</NoMarginHelperText>
{t("settings.smtpPortDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -150,9 +132,7 @@ const Email = () => {
value={values.smtpUser ?? ""} value={values.smtpUser ?? ""}
onChange={(e) => setSettings({ smtpUser: e.target.value })} onChange={(e) => setSettings({ smtpUser: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.smtpUsernameDes")}</NoMarginHelperText>
{t("settings.smtpUsernameDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -164,9 +144,7 @@ const Email = () => {
value={values.smtpPass ?? ""} value={values.smtpPass ?? ""}
onChange={(e) => setSettings({ smtpPass: e.target.value })} onChange={(e) => setSettings({ smtpPass: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.smtpPasswordDes")}</NoMarginHelperText>
{t("settings.smtpPasswordDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -177,9 +155,7 @@ const Email = () => {
value={values.replyTo ?? ""} value={values.replyTo ?? ""}
onChange={(e) => setSettings({ replyTo: e.target.value })} onChange={(e) => setSettings({ replyTo: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.replyToAddressDes")}</NoMarginHelperText>
{t("settings.replyToAddressDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -189,16 +165,12 @@ const Email = () => {
control={ control={
<Switch <Switch
checked={isTrueVal(values.smtpEncryption)} checked={isTrueVal(values.smtpEncryption)}
onChange={(e) => onChange={(e) => setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })}
setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })
}
/> />
} }
label={t("settings.enforceSSL")} label={t("settings.enforceSSL")}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.enforceSSLDes")}</NoMarginHelperText>
{t("settings.enforceSSLDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -211,18 +183,12 @@ const Email = () => {
value={values.mail_keepalive ?? "30"} value={values.mail_keepalive ?? "30"}
onChange={(e) => setSettings({ mail_keepalive: e.target.value })} onChange={(e) => setSettings({ mail_keepalive: e.target.value })}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.smtpTTLDes")}</NoMarginHelperText>
{t("settings.smtpTTLDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
<Box display="flex" gap={2} mt={2}> <Box display="flex" gap={2} mt={2}>
<SecondaryButton <SecondaryButton variant="contained" startIcon={<MailOutlined />} onClick={() => setTestEmailOpen(true)}>
variant="contained"
startIcon={<MailOutlined />}
onClick={() => setTestEmailOpen(true)}
>
{t("settings.sendTestEmail")} {t("settings.sendTestEmail")}
</SecondaryButton> </SecondaryButton>
</Box> </Box>
@ -236,4 +202,4 @@ const Email = () => {
); );
}; };
export default Email; export default Email;

View File

@ -1,18 +1,7 @@
import { import { Box, IconButton, Table, TableBody, TableContainer, TableHead, TableRow } from "@mui/material";
Box,
IconButton,
Table,
TableBody,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import * as React from "react"; import * as React from "react";
import { memo, useMemo, useState } from "react"; import { memo, useMemo, useState } from "react";
import { import { builtInIcons, FileTypeIconSetting } from "../../../FileManager/Explorer/FileTypeIcon.tsx";
builtInIcons,
FileTypeIconSetting,
} from "../../../FileManager/Explorer/FileTypeIcon.tsx";
import { import {
DenseFilledTextField, DenseFilledTextField,
NoWrapCell, NoWrapCell,
@ -71,52 +60,28 @@ const IconPreview = ({ icon }: { icon: FileTypeIconSetting }) => {
const FileIconList = memo(({ config, onChange }: FileIconListProps) => { const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const configParsed = useMemo( const configParsed = useMemo((): FileTypeIconSetting[] => JSON.parse(config), [config]);
(): FileTypeIconSetting[] => JSON.parse(config),
[config],
);
const [inputCache, setInputCache] = useState<{ const [inputCache, setInputCache] = useState<{
[key: number]: string | undefined; [key: number]: string | undefined;
}>({}); }>({});
return ( return (
<Box> <Box>
{configParsed?.length > 0 && ( {configParsed?.length > 0 && (
<TableContainer <TableContainer sx={{ mt: 1, maxHeight: 440 }} component={StyledTableContainerPaper}>
sx={{ mt: 1, maxHeight: 440 }} <Table stickyHeader sx={{ width: "100%", tableLayout: "fixed" }} size="small">
component={StyledTableContainerPaper}
>
<Table
stickyHeader
sx={{ width: "100%", tableLayout: "fixed" }}
size="small"
>
<TableHead> <TableHead>
<TableRow> <TableRow>
<NoWrapTableCell width={64}> <NoWrapTableCell width={64}>{t("settings.icon")}</NoWrapTableCell>
{t("settings.icon")} <NoWrapTableCell width={200}>{t("settings.iconUrl")}</NoWrapTableCell>
</NoWrapTableCell> <NoWrapTableCell width={150}>{t("settings.iconColor")}</NoWrapTableCell>
<NoWrapTableCell width={200}> <NoWrapTableCell width={150}>{t("settings.iconColorDark")}</NoWrapTableCell>
{t("settings.iconUrl")} <NoWrapTableCell width={250}>{t("settings.exts")}</NoWrapTableCell>
</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> <NoWrapTableCell width={64}></NoWrapTableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{configParsed.map((r, i) => ( {configParsed.map((r, i) => (
<TableRow <TableRow key={i} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover>
key={i}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
hover
>
<NoWrapCell> <NoWrapCell>
<IconPreview icon={r} /> <IconPreview icon={r} />
</NoWrapCell> </NoWrapCell>
@ -215,13 +180,7 @@ const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
<NoWrapCell> <NoWrapCell>
{!r.icon && ( {!r.icon && (
<IconButton <IconButton
onClick={() => onClick={() => onChange(JSON.stringify(configParsed.filter((_, index) => index !== i)))}
onChange(
JSON.stringify(
configParsed.filter((_, index) => index !== i),
),
)
}
size={"small"} size={"small"}
> >
<Dismiss fontSize={"small"} /> <Dismiss fontSize={"small"} />

View File

@ -11,11 +11,7 @@ export interface HexColorInputProps {
required?: boolean; required?: boolean;
} }
const HexColorInput = ({ const HexColorInput = ({ currentColor, onColorChange, ...rest }: HexColorInputProps) => {
currentColor,
onColorChange,
...rest
}: HexColorInputProps) => {
return ( return (
<DenseFilledTextField <DenseFilledTextField
value={currentColor} value={currentColor}

View File

@ -3,11 +3,7 @@ import { memo, useCallback, useState } from "react";
import { Viewer, ViewerType } from "../../../../../api/explorer.ts"; import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { IconButton, TableRow } from "@mui/material"; import { IconButton, TableRow } from "@mui/material";
import { import { DenseFilledTextField, NoWrapCell, StyledCheckbox } from "../../../../Common/StyledComponents.tsx";
DenseFilledTextField,
NoWrapCell,
StyledCheckbox,
} from "../../../../Common/StyledComponents.tsx";
import { ViewerIcon } from "../../../../FileManager/Dialogs/OpenWith.tsx"; import { ViewerIcon } from "../../../../FileManager/Dialogs/OpenWith.tsx";
import Dismiss from "../../../../Icons/Dismiss.tsx"; import Dismiss from "../../../../Icons/Dismiss.tsx";
import Edit from "../../../../Icons/Edit.tsx"; import Edit from "../../../../Icons/Edit.tsx";
@ -19,83 +15,68 @@ export interface FileViewerRowProps {
onDelete: (e: React.MouseEvent<HTMLElement>) => void; onDelete: (e: React.MouseEvent<HTMLElement>) => void;
} }
const FileViewerRow = memo( const FileViewerRow = memo(({ viewer, onChange, onDelete }: FileViewerRowProps) => {
({ viewer, onChange, onDelete }: FileViewerRowProps) => { const { t } = useTranslation("dashboard");
const { t } = useTranslation("dashboard"); const [extCached, setExtCached] = useState("");
const [extCached, setExtCached] = useState(""); const [editOpen, setEditOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false); const onClose = useCallback(() => {
const onClose = useCallback(() => { setEditOpen(false);
setEditOpen(false); }, [setEditOpen]);
}, [setEditOpen]); return (
return ( <TableRow sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover>
<TableRow <FileViewerEditDialog viewer={viewer} onChange={onChange} open={editOpen} onClose={onClose} />
sx={{ "&:last-child td, &:last-child th": { border: 0 } }} <NoWrapCell>
hover <ViewerIcon viewer={viewer} />
> </NoWrapCell>
<FileViewerEditDialog <NoWrapCell>{t(`settings.${viewer.type}ViewerType`)}</NoWrapCell>
viewer={viewer} <NoWrapCell>
onChange={onChange} {t(viewer.display_name, {
open={editOpen} ns: "application",
onClose={onClose} })}
</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>
<ViewerIcon viewer={viewer} /> <NoWrapCell>
</NoWrapCell> {viewer.templates?.length ? t("settings.nMapping", { num: viewer.templates?.length }) : t("share.none")}
<NoWrapCell>{t(`settings.${viewer.type}ViewerType`)}</NoWrapCell> </NoWrapCell>
<NoWrapCell> <NoWrapCell>
{t(viewer.display_name, { <StyledCheckbox
ns: "application", size={"small"}
})} checked={!viewer.disabled}
</NoWrapCell> onChange={(e) =>
<NoWrapCell> onChange({
<DenseFilledTextField ...viewer,
fullWidth disabled: !e.target.checked,
multiline })
required }
value={extCached == "" ? viewer.exts.join() : extCached} />
onBlur={() => { </NoWrapCell>
onChange({ <NoWrapCell>
...viewer, <IconButton size={"small"} onClick={() => setEditOpen(true)}>
exts: <Edit fontSize={"small"} />
extCached == "" </IconButton>
? viewer.exts {viewer.type != ViewerType.builtin && (
: extCached?.split(",")?.map((ext) => ext.trim()), <IconButton size={"small"} onClick={onDelete}>
}); <Dismiss fontSize={"small"} />
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"} />
</IconButton> </IconButton>
{viewer.type != ViewerType.builtin && ( )}
<IconButton size={"small"} onClick={onDelete}> </NoWrapCell>
<Dismiss fontSize={"small"} /> </TableRow>
</IconButton> );
)} });
</NoWrapCell>
</TableRow>
);
},
);
export default FileViewerRow; export default FileViewerRow;

View File

@ -18,16 +18,10 @@ import { useAppDispatch } from "../../../../redux/hooks.ts";
import { isTrueVal } from "../../../../session/utils.ts"; import { isTrueVal } from "../../../../session/utils.ts";
import SizeInput from "../../../Common/SizeInput.tsx"; import SizeInput from "../../../Common/SizeInput.tsx";
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
import { import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
DenseFilledTextField,
StyledCheckbox,
} from "../../../Common/StyledComponents.tsx";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx"; import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
import { import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
AccordionSummary,
StyledAccordion,
} from "../UserSession/SSOSettings.tsx";
export interface ExtractorsProps { export interface ExtractorsProps {
values: { values: {
@ -90,12 +84,11 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
const [testing, setTesting] = useState(false); const [testing, setTesting] = useState(false);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const handleEnableChange = const handleEnableChange = (name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => { setSetting({
setSetting({ [name]: e.target.checked ? "1" : "0",
[name]: e.target.checked ? "1" : "0", });
}); };
};
const doTest = (name: string, executable: string) => { const doTest = (name: string, executable: string) => {
setTesting(true); setTesting(true);
@ -150,12 +143,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
<LoadingButton <LoadingButton
onClick={() => onClick={() => doTest(e.name, values[e.executableSetting ?? ""])}
doTest(
e.name,
values[e.executableSetting ?? ""],
)
}
loading={testing} loading={testing}
color="primary" color="primary"
> >
@ -170,9 +158,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
}) })
} }
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.executableDes")}</NoMarginHelperText>
{t("settings.executableDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}
@ -189,9 +175,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
}) })
} }
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.maxSizeLocalDes")}</NoMarginHelperText>
{t("settings.maxSizeLocalDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}
@ -208,17 +192,12 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
}) })
} }
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.maxSizeRemoteDes")}</NoMarginHelperText>
{t("settings.maxSizeRemoteDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}
{e.additionalSettings?.map((setting) => ( {e.additionalSettings?.map((setting) => (
<SettingForm <SettingForm key={setting.name} lgWidth={12}>
key={setting.name}
lgWidth={12}
>
<FormControl fullWidth> <FormControl fullWidth>
{setting.type === "switch" ? ( {setting.type === "switch" ? (
<FormControlLabel <FormControlLabel
@ -245,9 +224,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
} }
/> />
)} )}
<NoMarginHelperText> <NoMarginHelperText>{t(`settings.${setting.des}`)}</NoMarginHelperText>
{t(`settings.${setting.des}`)}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
))} ))}
@ -259,4 +236,4 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
); );
}; };
export default Extractors; export default Extractors;

View File

@ -1,22 +1,9 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { AccordionDetails, Box, FormControl, FormControlLabel, InputAdornment, Typography } from "@mui/material";
AccordionDetails,
Box,
FormControl,
FormControlLabel,
InputAdornment,
Typography,
} from "@mui/material";
import { isTrueVal } from "../../../../session/utils.ts"; import { isTrueVal } from "../../../../session/utils.ts";
import { import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
AccordionSummary,
StyledAccordion,
} from "../UserSession/SSOSettings.tsx";
import { ExpandMoreRounded } from "@mui/icons-material"; import { ExpandMoreRounded } from "@mui/icons-material";
import { import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
DenseFilledTextField,
StyledCheckbox,
} from "../../../Common/StyledComponents.tsx";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx"; import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
@ -131,25 +118,23 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
const [testing, setTesting] = useState(false); const [testing, setTesting] = useState(false);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const handleEnableChange = const handleEnableChange = (name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => { setSetting({
setSetting({ [name]: e.target.checked ? "1" : "0",
[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) => { const doTest = (name: string, executable: string) => {
setTesting(true); setTesting(true);
@ -205,12 +190,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
<LoadingButton <LoadingButton
onClick={() => onClick={() => doTest(g.name, values[g.executableSetting ?? ""])}
doTest(
g.name,
values[g.executableSetting ?? ""],
)
}
loading={testing} loading={testing}
color="primary" color="primary"
> >
@ -225,9 +205,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
}) })
} }
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.executableDes")}</NoMarginHelperText>
{t("settings.executableDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}
@ -245,18 +223,12 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
}) })
} }
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.thumbMaxSizeDes")}</NoMarginHelperText>
{t("settings.thumbMaxSizeDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}
{g.inputs?.map((input) => ( {g.inputs?.map((input) => (
<SettingForm <SettingForm key={input.name} lgWidth={12} title={t(`settings.${input.label}`)}>
key={input.name}
lgWidth={12}
title={t(`settings.${input.label}`)}
>
<FormControl fullWidth> <FormControl fullWidth>
<DenseFilledTextField <DenseFilledTextField
value={values[input.name]} value={values[input.name]}
@ -267,9 +239,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
} }
required={!!input.required} required={!!input.required}
/> />
<NoMarginHelperText> <NoMarginHelperText>{t(`settings.${input.des}`)}</NoMarginHelperText>
{t(`settings.${input.des}`)}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
))} ))}

View File

@ -21,7 +21,8 @@ const Queue = () => {
dispatch(getQueueMetrics()) dispatch(getQueueMetrics())
.then((res) => { .then((res) => {
setMetrics(res); setMetrics(res);
}).finally(() => { })
.finally(() => {
setLoading(false); setLoading(false);
}); });
}; };
@ -30,26 +31,32 @@ const Queue = () => {
fetchQueueMetrics(); fetchQueueMetrics();
}, []); }, []);
return <Box component={"form"} ref={formRef} sx={{ p: 2, pt: 0 }}> return (
<Stack direction="row" spacing={2} sx={{ mb: 2 }}> <Box component={"form"} ref={formRef} sx={{ p: 2, pt: 0 }}>
<SecondaryButton <Stack direction="row" spacing={2} sx={{ mb: 2 }}>
onClick={fetchQueueMetrics} <SecondaryButton onClick={fetchQueueMetrics} disabled={loading} variant={"contained"} startIcon={<ArrowSync />}>
disabled={loading} {t("node.refresh")}
variant={"contained"} </SecondaryButton>
startIcon={<ArrowSync />} </Stack>
> <Grid container spacing={2}>
{t("node.refresh")} {!loading &&
</SecondaryButton> metrics.map((metric) => (
</Stack> <QueueCard
<Grid container spacing={2}> key={metric.name}
{!loading && metrics.map((metric) => ( metrics={metric}
<QueueCard key={metric.name} metrics={metric} queue={metric.name} settings={values} setSettings={setSettings} loading={loading} /> queue={metric.name}
))} settings={values}
{loading && Array.from(Array(5)).map((_, index) => ( setSettings={setSettings}
<QueueCard key={`loading-${index}`} settings={values} setSettings={setSettings} loading={true} /> loading={loading}
))} />
</Grid> ))}
</Box>; {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;

View File

@ -34,11 +34,7 @@ export const QueueCard = ({ queue, settings, metrics, setSettings, loading }: Qu
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} /> <Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} />
<Divider sx={{ my: 2 }} /> <Divider sx={{ my: 2 }} />
<Skeleton variant="rectangular" height={8} width="100%" sx={{ borderRadius: 1 }} /> <Skeleton variant="rectangular" height={8} width="100%" sx={{ borderRadius: 1 }} />
<Stack <Stack spacing={isMobile ? 1 : 2} direction={isMobile ? "column" : "row"} sx={{ mt: 1 }}>
spacing={isMobile ? 1 : 2}
direction={isMobile ? "column" : "row"}
sx={{ mt: 1 }}
>
{Array.from(Array(5)).map((_, index) => ( {Array.from(Array(5)).map((_, index) => (
<Skeleton key={index} variant="text" width={isMobile ? "100%" : 80} height={20} /> <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}> return (
<BorderedCard> <Grid item xs={12} md={6} lg={4}>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}> <BorderedCard>
<Typography variant="subtitle1" fontWeight={600}>{t(`queue.queueName_${queue}`)}</Typography> <Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<IconButton size="small" onClick={() => setSettingDialogOpen(true)}> <Typography variant="subtitle1" fontWeight={600}>
<Setting fontSize="small" /> {t(`queue.queueName_${queue}`)}
</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>
<Typography variant={"caption"}> <IconButton size="small" onClick={() => setSettingDialogOpen(true)}>
<StorageBlock <Setting fontSize="small" />
sx={{ </IconButton>
backgroundColor: (theme) => theme.palette.error.light, </Box>
}} <Typography variant="body2" color="text.secondary">
/> {t(`queue.queueName_${queue}Des`)}
{t("queue.failed", { </Typography>
count: metrics.failure_tasks, <Divider sx={{ my: 2 }} />
})} {metrics && (
</Typography> <>
<Typography variant={"caption"}> <StorageBar>
<StorageBlock <StoragePart
sx={{ sx={{
backgroundColor: (theme) => theme.palette.info.light, backgroundColor: (theme) => theme.palette.success.light,
}} width: `${(metrics.success_tasks / metrics.submitted_tasks) * 100}%`,
/> }}
{t("queue.busyWorker", { />
count: metrics.busy_workers, <StoragePart
})} sx={{
</Typography> backgroundColor: (theme) => theme.palette.error.light,
<Typography variant={"caption"}> width: `${(metrics.failure_tasks / metrics.submitted_tasks) * 100}%`,
<StorageBlock }}
sx={{ />
backgroundColor: (theme) => theme.palette.action.active, <StoragePart
}} sx={{
/> backgroundColor: (theme) => theme.palette.action.active,
{t("queue.suspending", { width: `${(metrics.suspending_tasks / metrics.submitted_tasks) * 100}%`,
count: metrics.suspending_tasks, }}
})} />
</Typography> <StoragePart
<Typography variant={"caption"}> sx={{
<StorageBlock backgroundColor: (theme) => theme.palette.info.light,
sx={{ width: `${(metrics.busy_workers / metrics.submitted_tasks) * 100}%`,
backgroundColor: (theme) => }}
theme.palette.grey[ />
theme.palette.mode === "light" ? 200 : 800 </StorageBar>
], <Stack spacing={isMobile ? 1 : 2} direction={isMobile ? "column" : "row"} sx={{ mt: 1 }}>
}} <Typography variant={"caption"}>
/> <StorageBlock
{t("queue.submited", { sx={{
count: metrics.submitted_tasks, backgroundColor: (theme) => theme.palette.success.light,
})} }}
</Typography> />
</Stack> {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 && ( {queue && (
<QueueSettingDialog <QueueSettingDialog
open={settingDialogOpen} open={settingDialogOpen}
onClose={() => setSettingDialogOpen(false)} onClose={() => setSettingDialogOpen(false)}
queue={queue} queue={queue}
settings={settings} settings={settings}
setSettings={setSettings} setSettings={setSettings}
/> />
)} )}
</BorderedCard> </BorderedCard>
</Grid>; </Grid>
);
}; };
export default QueueCard; export default QueueCard;

View File

@ -27,13 +27,7 @@ const NoMarginHelperText = (props: any) => (
/> />
); );
const QueueSettingDialog = ({ const QueueSettingDialog = ({ open, onClose, queue, settings, setSettings }: QueueSettingDialogProps) => {
open,
onClose,
queue,
settings,
setSettings,
}: QueueSettingDialogProps) => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null);
const [localSettings, setLocalSettings] = useState<{ [key: string]: string }>({}); const [localSettings, setLocalSettings] = useState<{ [key: string]: string }>({});
@ -48,10 +42,10 @@ const QueueSettingDialog = ({
"backoff_factor", "backoff_factor",
"backoff_max_duration", "backoff_max_duration",
"max_retry", "max_retry",
"retry_delay" "retry_delay",
]; ];
settingKeys.forEach(key => { settingKeys.forEach((key) => {
const fullKey = `queue_${queue}_${key}`; const fullKey = `queue_${queue}_${key}`;
queueSettings[key] = settings[fullKey] || ""; queueSettings[key] = settings[fullKey] || "";
}); });
@ -76,7 +70,7 @@ const QueueSettingDialog = ({
const updateLocalSetting = (key: string, value: string) => { const updateLocalSetting = (key: string, value: string) => {
setLocalSettings((prev: { [key: string]: string }) => ({ setLocalSettings((prev: { [key: string]: string }) => ({
...prev, ...prev,
[key]: value [key]: value,
})); }));
}; };
@ -95,25 +89,25 @@ const QueueSettingDialog = ({
maxWidth: "sm", 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}> <SettingForm title={t("queue.workerNum")} lgWidth={12}>
<FormControl fullWidth> <FormControl fullWidth>
<DenseFilledTextField <DenseFilledTextField
value={localSettings.worker_num || ""} value={localSettings.worker_num || ""}
onChange={(e) => updateLocalSetting("worker_num", e.target.value)} onChange={(e) => updateLocalSetting("worker_num", e.target.value)}
type="number" type="number"
slotProps={ slotProps={{
{ htmlInput: {
htmlInput: { min: 1,
min: 1, },
} }}
}
}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("queue.workerNumDes")}</NoMarginHelperText>
{t("queue.workerNumDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -128,9 +122,7 @@ const QueueSettingDialog = ({
}} }}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("queue.maxExecutionDes")}</NoMarginHelperText>
{t("queue.maxExecutionDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -140,19 +132,15 @@ const QueueSettingDialog = ({
value={localSettings.backoff_factor || ""} value={localSettings.backoff_factor || ""}
onChange={(e) => updateLocalSetting("backoff_factor", e.target.value)} onChange={(e) => updateLocalSetting("backoff_factor", e.target.value)}
type="number" type="number"
slotProps={ slotProps={{
{ htmlInput: {
htmlInput: { min: 1,
min: 1, step: 0.1,
step: 0.1, },
} }}
}
}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("queue.backoffFactorDes")}</NoMarginHelperText>
{t("queue.backoffFactorDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -162,18 +150,14 @@ const QueueSettingDialog = ({
value={localSettings.backoff_max_duration || ""} value={localSettings.backoff_max_duration || ""}
onChange={(e) => updateLocalSetting("backoff_max_duration", e.target.value)} onChange={(e) => updateLocalSetting("backoff_max_duration", e.target.value)}
type="number" type="number"
slotProps={ slotProps={{
{ htmlInput: {
htmlInput: { min: 1,
min: 1, },
} }}
}
}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("queue.backoffMaxDurationDes")}</NoMarginHelperText>
{t("queue.backoffMaxDurationDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -183,18 +167,14 @@ const QueueSettingDialog = ({
value={localSettings.max_retry || ""} value={localSettings.max_retry || ""}
onChange={(e) => updateLocalSetting("max_retry", e.target.value)} onChange={(e) => updateLocalSetting("max_retry", e.target.value)}
type="number" type="number"
slotProps={ slotProps={{
{ htmlInput: {
htmlInput: { min: 0,
min: 0, },
} }}
}
}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("queue.maxRetryDes")}</NoMarginHelperText>
{t("queue.maxRetryDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
@ -204,18 +184,14 @@ const QueueSettingDialog = ({
value={localSettings.retry_delay || ""} value={localSettings.retry_delay || ""}
onChange={(e) => updateLocalSetting("retry_delay", e.target.value)} onChange={(e) => updateLocalSetting("retry_delay", e.target.value)}
type="number" type="number"
slotProps={ slotProps={{
{ htmlInput: {
htmlInput: { min: 0,
min: 0, },
} }}
}
}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("queue.retryDelayDes")}</NoMarginHelperText>
{t("queue.retryDelayDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
</Box> </Box>
@ -223,4 +199,4 @@ const QueueSettingDialog = ({
); );
}; };
export default QueueSettingDialog; export default QueueSettingDialog;

View File

@ -1,17 +1,7 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMemo } from "react"; import { useMemo } from "react";
import { import { Box, Collapse, Divider, IconButton, InputAdornment, Stack } from "@mui/material";
Box, import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents.tsx";
Collapse,
Divider,
IconButton,
InputAdornment,
Stack,
} from "@mui/material";
import {
DenseFilledTextField,
SecondaryButton,
} from "../../../Common/StyledComponents.tsx";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Dismiss from "../../../Icons/Dismiss.tsx"; import Dismiss from "../../../Icons/Dismiss.tsx";
import Add from "../../../Icons/Add.tsx"; import Add from "../../../Icons/Add.tsx";
@ -29,12 +19,11 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
return urls.split(",").map((url) => url); return urls.split(",").map((url) => url);
}, [urls]); }, [urls]);
const onUrlChange = const onUrlChange = (index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => { const newUrls = [...urlSplit];
const newUrls = [...urlSplit]; newUrls[index] = e.target.value;
newUrls[index] = e.target.value; onChange(newUrls.join(","));
onChange(newUrls.join(",")); };
};
const removeUrl = (index: number) => () => { const removeUrl = (index: number) => () => {
const newUrls = [...urlSplit]; const newUrls = [...urlSplit];
@ -58,9 +47,7 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
}} }}
required required
/> />
<NoMarginHelperText> <NoMarginHelperText>{t("settings.primarySiteURLDes")}</NoMarginHelperText>
{t("settings.primarySiteURLDes")}
</NoMarginHelperText>
</FormControl> </FormControl>
<Divider /> <Divider />
<NoMarginHelperText>{t("settings.secondaryDes")}</NoMarginHelperText> <NoMarginHelperText>{t("settings.secondaryDes")}</NoMarginHelperText>
@ -93,11 +80,7 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
))} ))}
</TransitionGroup> </TransitionGroup>
<Box sx={{ mt: "0!important" }}> <Box sx={{ mt: "0!important" }}>
<SecondaryButton <SecondaryButton variant={"contained"} startIcon={<Add />} onClick={() => onChange(`${urls},`)}>
variant={"contained"}
startIcon={<Add />}
onClick={() => onChange(`${urls},`)}
>
{t("settings.addSecondary")} {t("settings.addSecondary")}
</SecondaryButton> </SecondaryButton>
</Box> </Box>

View File

@ -147,7 +147,7 @@ const UserSession = () => {
control={<Switch checked={false} />} control={<Switch checked={false} />}
label={ label={
<> <>
{t("vas.disableSubAddressEmail")} {t("vas.disableSubAddressEmail")}
<ProChip label="Pro" color="primary" size="small" /> <ProChip label="Pro" color="primary" size="small" />
</> </>
} }

View File

@ -1,7 +1,6 @@
export { default as BasicInfoSection } from './BasicInfoSection'; export { default as BasicInfoSection } from "./BasicInfoSection";
export { default as DownloadSection } from './DownloadSection'; export { default as DownloadSection } from "./DownloadSection";
export * from './magicVars'; export * from "./magicVars";
export { default as MediaMetadataSection } from './MediaMetadataSection'; export { default as MediaMetadataSection } from "./MediaMetadataSection";
export { default as StorageAndUploadSection } from './StorageAndUploadSection'; export { default as StorageAndUploadSection } from "./StorageAndUploadSection";
export { default as ThumbnailsSection } from './ThumbnailsSection'; export { default as ThumbnailsSection } from "./ThumbnailsSection";

View File

@ -22,7 +22,7 @@ export const commonMagicVars: MagicVar[] = [
export const pathMagicVars: MagicVar[] = [ export const pathMagicVars: MagicVar[] = [
...commonMagicVars, ...commonMagicVars,
{ name: "{path}", value: "policy.magicVar.path", example: "/path/to/" } { name: "{path}", value: "policy.magicVar.path", example: "/path/to/" },
]; ];
export const fileMagicVars: MagicVar[] = [ export const fileMagicVars: MagicVar[] = [
@ -31,4 +31,4 @@ export const fileMagicVars: MagicVar[] = [
{ name: "{ext}", value: "policy.magicVar.extension", example: ".jpg" }, { name: "{ext}", value: "policy.magicVar.extension", example: ".jpg" },
{ name: "{originname_without_ext}", value: "policy.magicVar.originFileNameNoext", example: "example" }, { name: "{originname_without_ext}", value: "policy.magicVar.originFileNameNoext", example: "example" },
{ name: "{uuid}", value: "policy.magicVar.uuidV4", example: "550e8400-e29b-41d4-a716-446655440000" }, { name: "{uuid}", value: "policy.magicVar.uuidV4", example: "550e8400-e29b-41d4-a716-446655440000" },
]; ];

View File

@ -5,8 +5,7 @@ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 8, height: 8,
borderRadius: 5, borderRadius: 5,
[`&.${linearProgressClasses.colorPrimary}`]: { [`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
}, },
[`& .${linearProgressClasses.bar}`]: { [`& .${linearProgressClasses.bar}`]: {
borderRadius: 5, borderRadius: 5,

View File

@ -19,14 +19,10 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const scriptLoadedRef = useRef(false); const scriptLoadedRef = useRef(false);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation("common"); const { t } = useTranslation("common");
const capInstanceURL = useAppSelector( const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url);
(state) => state.siteConfig.basic.config.captcha_cap_instance_url, const capKeyID = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_key_id);
);
const capKeyID = useAppSelector(
(state) => state.siteConfig.basic.config.captcha_cap_key_id,
);
// Keep callback reference up to date // Keep callback reference up to date
useEffect(() => { useEffect(() => {
onStateChangeRef.current = onStateChange; onStateChangeRef.current = onStateChange;
@ -36,18 +32,18 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const applyFullWidthStyles = (widget: HTMLElement) => { const applyFullWidthStyles = (widget: HTMLElement) => {
const applyStyles = () => { const applyStyles = () => {
// Style widget container // Style widget container
widget.style.width = '100%'; widget.style.width = "100%";
widget.style.display = 'block'; widget.style.display = "block";
widget.style.boxSizing = 'border-box'; widget.style.boxSizing = "border-box";
// Style internal captcha element // Style internal captcha element
const captchaElement = widget.shadowRoot?.querySelector('.captcha') || widget.querySelector('.captcha'); const captchaElement = widget.shadowRoot?.querySelector(".captcha") || widget.querySelector(".captcha");
if (captchaElement) { if (captchaElement) {
const captchaEl = captchaElement as HTMLElement; const captchaEl = captchaElement as HTMLElement;
captchaEl.style.width = '100%'; captchaEl.style.width = "100%";
captchaEl.style.maxWidth = 'none'; captchaEl.style.maxWidth = "none";
captchaEl.style.minWidth = '0'; captchaEl.style.minWidth = "0";
captchaEl.style.boxSizing = 'border-box'; captchaEl.style.boxSizing = "border-box";
return true; return true;
} }
return false; return false;
@ -60,13 +56,13 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
observer.disconnect(); observer.disconnect();
} }
}); });
observer.observe(widget, { observer.observe(widget, {
childList: true, childList: true,
subtree: true, subtree: true,
attributes: true attributes: true,
}); });
// Fallback timeout // Fallback timeout
setTimeout(() => { setTimeout(() => {
applyStyles(); applyStyles();
@ -85,34 +81,34 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
widgetRef.current.remove?.(); widgetRef.current.remove?.();
widgetRef.current = null; widgetRef.current = null;
} }
// Clear container // Clear container
captchaRef.current.innerHTML = ""; captchaRef.current.innerHTML = "";
if (typeof window !== "undefined" && (window as any).Cap) { if (typeof window !== "undefined" && (window as any).Cap) {
const widget = document.createElement("cap-widget"); const widget = document.createElement("cap-widget");
widget.setAttribute("data-cap-api-endpoint", `${capInstanceURL.replace(/\/$/, "")}/${capKeyID}/api/`); widget.setAttribute("data-cap-api-endpoint", `${capInstanceURL.replace(/\/$/, "")}/${capKeyID}/api/`);
widget.id = "cap-widget"; widget.id = "cap-widget";
// Set internationalization attributes (Cap official i18n format) // Set internationalization attributes (Cap official i18n format)
widget.setAttribute("data-cap-i18n-initial-state", t("captcha.cap.human")); 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-verifying-label", t("captcha.cap.verifying"));
widget.setAttribute("data-cap-i18n-solved-label", t("captcha.cap.verified")); widget.setAttribute("data-cap-i18n-solved-label", t("captcha.cap.verified"));
captchaRef.current.appendChild(widget); captchaRef.current.appendChild(widget);
widget.addEventListener("solve", (e: any) => { widget.addEventListener("solve", (e: any) => {
const token = e.detail.token; const token = e.detail.token;
if (token) { if (token) {
onStateChangeRef.current({ ticket: token }); onStateChangeRef.current({ ticket: token });
} }
}); });
// Apply fullWidth styles if needed // Apply fullWidth styles if needed
if (fullWidth) { if (fullWidth) {
applyFullWidthStyles(widget); applyFullWidthStyles(widget);
} }
widgetRef.current = widget; widgetRef.current = widget;
} }
}; };
@ -130,7 +126,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const scriptId = "cap-widget-script"; const scriptId = "cap-widget-script";
let script = document.getElementById(scriptId) as HTMLScriptElement; let script = document.getElementById(scriptId) as HTMLScriptElement;
const initWidget = () => { const initWidget = () => {
scriptLoadedRef.current = true; scriptLoadedRef.current = true;
// Add a small delay to ensure DOM is ready // Add a small delay to ensure DOM is ready
@ -174,11 +170,11 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
} }
return ( return (
<Box <Box
sx={{ sx={{
// Container full width when needed // Container full width when needed
...(fullWidth && { width: "100%" }), ...(fullWidth && { width: "100%" }),
// CSS variables for Cloudreve theme adaptation // CSS variables for Cloudreve theme adaptation
"& cap-widget": { "& cap-widget": {
"--cap-border-radius": `${theme.shape.borderRadius}px`, "--cap-border-radius": `${theme.shape.borderRadius}px`,
@ -205,4 +201,4 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
); );
}; };
export default CapCaptcha; export default CapCaptcha;

View File

@ -16,9 +16,7 @@ export interface CaptchaParams {
} }
export const Captcha = (props: CaptchaProps) => { export const Captcha = (props: CaptchaProps) => {
const captchaType = useAppSelector( const captchaType = useAppSelector((state) => state.siteConfig.basic.config.captcha_type);
(state) => state.siteConfig.basic.config.captcha_type,
);
// const recaptcha = useRecaptcha(setCaptchaLoading); // const recaptcha = useRecaptcha(setCaptchaLoading);
// const tcaptcha = useTCaptcha(setCaptchaLoading); // const tcaptcha = useTCaptcha(setCaptchaLoading);

View File

@ -10,11 +10,7 @@ export interface DefaultCaptchaProps {
generation: number; generation: number;
} }
const DefaultCaptcha = ({ const DefaultCaptcha = ({ onStateChange, generation, ...rest }: DefaultCaptchaProps) => {
onStateChange,
generation,
...rest
}: DefaultCaptchaProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -92,8 +88,9 @@ const DefaultCaptcha = ({
htmlInput: { htmlInput: {
name: "captcha", name: "captcha",
id: "captcha", id: "captcha",
} },
}} /> }}
/>
); );
}; };

View File

@ -22,17 +22,11 @@ window.recaptchaOptions = {
useRecaptchaNet: true, useRecaptchaNet: true,
}; };
const ReCaptchaV2 = ({ const ReCaptchaV2 = ({ onStateChange, generation, ...rest }: ReCaptchaV2Props) => {
onStateChange,
generation,
...rest
}: ReCaptchaV2Props) => {
const theme = useTheme(); const theme = useTheme();
const captchaRef = useRef(); const captchaRef = useRef();
const reCaptchaKey = useAppSelector( const reCaptchaKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_ReCaptchaKey);
(state) => state.siteConfig.basic.config.captcha_ReCaptchaKey,
);
const refreshCaptcha = async () => { const refreshCaptcha = async () => {
captchaRef.current?.reset(); captchaRef.current?.reset();

View File

@ -10,17 +10,11 @@ export interface TurnstileProps {
generation: number; generation: number;
} }
const TurnstileCaptcha = ({ const TurnstileCaptcha = ({ onStateChange, generation, ...rest }: TurnstileProps) => {
onStateChange,
generation,
...rest
}: TurnstileProps) => {
const theme = useTheme(); const theme = useTheme();
const captchaRef = useRef(); const captchaRef = useRef();
const turnstileKey = useAppSelector( const turnstileKey = useAppSelector((state) => state.siteConfig.basic.config.turnstile_site_id);
(state) => state.siteConfig.basic.config.turnstile_site_id,
);
const refreshCaptcha = async () => { const refreshCaptcha = async () => {
captchaRef.current?.reset(); captchaRef.current?.reset();

View File

@ -1,9 +1,4 @@
import { import { Box, CircularProgress, circularProgressClasses, CircularProgressProps } from "@mui/material";
Box,
CircularProgress,
circularProgressClasses,
CircularProgressProps,
} from "@mui/material";
import { forwardRef } from "react"; import { forwardRef } from "react";
export interface FacebookCircularProgressProps extends CircularProgressProps { export interface FacebookCircularProgressProps extends CircularProgressProps {
@ -11,38 +6,34 @@ export interface FacebookCircularProgressProps extends CircularProgressProps {
fgColor?: string; fgColor?: string;
} }
const FacebookCircularProgress = forwardRef( const FacebookCircularProgress = forwardRef(({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => {
({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => { return (
return ( <Box sx={{ position: "relative", ...sx }} ref={ref}>
<Box sx={{ position: "relative", ...sx }} ref={ref}> <CircularProgress
<CircularProgress variant="determinate"
variant="determinate" sx={{
sx={{ color: (theme) => bgColor ?? theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
color: (theme) => }}
bgColor ?? size={40}
theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], thickness={4}
}} {...rest}
size={40} value={100}
thickness={4} />
{...rest} <CircularProgress
value={100} sx={{
/> color: (theme) => fgColor ?? theme.palette.primary.main,
<CircularProgress position: "absolute",
sx={{ left: 0,
color: (theme) => fgColor ?? theme.palette.primary.main, [`& .${circularProgressClasses.circle}`]: {
position: "absolute", strokeLinecap: "round",
left: 0, },
[`& .${circularProgressClasses.circle}`]: { }}
strokeLinecap: "round", size={40}
}, thickness={4}
}} {...rest}
size={40} />
thickness={4} </Box>
{...rest} );
/> });
</Box>
);
},
);
export default FacebookCircularProgress; export default FacebookCircularProgress;

View File

@ -1,17 +1,17 @@
.fade-enter { .fade-enter {
opacity: 0; opacity: 0;
} }
.fade-enter-active { .fade-enter-active {
opacity: 1; opacity: 1;
} }
.fade-exit { .fade-exit {
opacity: 1; opacity: 1;
} }
.fade-exit-active { .fade-exit-active {
opacity: 0; opacity: 0;
} }
.fade-enter-active, .fade-enter-active,
.fade-exit-active { .fade-exit-active {
transition: opacity 150ms; transition: opacity 150ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
} }

View File

@ -1,19 +1,10 @@
import { import { InputAdornment, TextField, TextFieldProps, useMediaQuery, useTheme } from "@mui/material";
InputAdornment,
TextField,
TextFieldProps,
useMediaQuery,
useTheme,
} from "@mui/material";
export interface OutlineIconTextFieldProps extends TextFieldProps<"outlined"> { export interface OutlineIconTextFieldProps extends TextFieldProps<"outlined"> {
icon: React.ReactNode; icon: React.ReactNode;
} }
export const OutlineIconTextField = ({ export const OutlineIconTextField = ({ icon, ...rest }: OutlineIconTextFieldProps) => {
icon,
...rest
}: OutlineIconTextFieldProps) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
return ( return (
@ -21,11 +12,10 @@ export const OutlineIconTextField = ({
{...rest} {...rest}
slotProps={{ slotProps={{
input: { input: {
startAdornment: !isMobile && ( startAdornment: !isMobile && <InputAdornment position="start">{icon}</InputAdornment>,
<InputAdornment position="start">{icon}</InputAdornment>
),
...rest.InputProps, ...rest.InputProps,
} },
}} /> }}
/>
); );
}; };

View File

@ -9,12 +9,7 @@ export interface NothingProps {
size?: number; size?: number;
} }
export default function Nothing({ export default function Nothing({ primary, secondary, top = 20, size = 1 }: NothingProps) {
primary,
secondary,
top = 20,
size = 1,
}: NothingProps) {
return ( return (
<Box <Box
sx={{ sx={{
@ -41,10 +36,7 @@ export default function Nothing({
{primary} {primary}
</Typography> </Typography>
{secondary && ( {secondary && (
<Typography <Typography variant={"body2"} sx={{ color: (theme) => theme.palette.action.disabled }}>
variant={"body2"}
sx={{ color: (theme) => theme.palette.action.disabled }}
>
{secondary} {secondary}
</Typography> </Typography>
)} )}

View File

@ -1,23 +1,10 @@
import * as React from "react"; import * as React from "react";
import { useLayoutEffect, useRef, useState } from "react"; import { useLayoutEffect, useRef, useState } from "react";
import { import { Box, ListItemIcon, ListItemText, Menu, MenuItem, Typography, useMediaQuery, useTheme } from "@mui/material";
Box,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { StyledTab, StyledTabs } from "./StyledComponents.tsx"; import { StyledTab, StyledTabs } from "./StyledComponents.tsx";
import CaretDown from "../Icons/CaretDown.tsx"; import CaretDown from "../Icons/CaretDown.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
bindMenu,
bindTrigger,
usePopupState,
} from "material-ui-popup-state/hooks";
export interface Tab<T> { export interface Tab<T> {
label: React.ReactNode; label: React.ReactNode;
@ -31,11 +18,7 @@ export interface ResponsiveTabsProps<T> {
onChange: (event: React.SyntheticEvent, value: T) => void; onChange: (event: React.SyntheticEvent, value: T) => void;
} }
const ResponsiveTabs = <T,>({ const ResponsiveTabs = <T,>({ tabs, value, onChange }: ResponsiveTabsProps<T>) => {
tabs,
value,
onChange,
}: ResponsiveTabsProps<T>) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [hideTabs, setHideTabs] = useState(false); const [hideTabs, setHideTabs] = useState(false);

View File

@ -14,78 +14,71 @@ export const DefaultCloseAction = (snackbarId: SnackbarKey | undefined) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
<Button <Button onClick={() => closeSnackbar(snackbarId)} color="inherit" size="small">
onClick={() => closeSnackbar(snackbarId)}
color="inherit"
size="small"
>
{t("dismiss", { ns: "common" })} {t("dismiss", { ns: "common" })}
</Button> </Button>
</> </>
); );
}; };
export const ErrorListDetailAction = export const ErrorListDetailAction = (error: Response<any>) => (snackbarId: SnackbarKey | undefined) => {
(error: Response<any>) => (snackbarId: SnackbarKey | undefined) => { const { t } = useTranslation();
const { t } = useTranslation(); const dispatch = useAppDispatch();
const dispatch = useAppDispatch();
const Close = DefaultCloseAction(snackbarId); const Close = DefaultCloseAction(snackbarId);
const showDetails = useCallback(() => { const showDetails = useCallback(() => {
dispatch(showAggregatedErrorDialog(error)); dispatch(showAggregatedErrorDialog(error));
closeSnackbar(snackbarId); closeSnackbar(snackbarId);
}, [dispatch, error, snackbarId]); }, [dispatch, error, snackbarId]);
return ( return (
<> <>
<Button onClick={showDetails} color="inherit" size="small"> <Button onClick={showDetails} color="inherit" size="small">
{t("common:errorDetails")} {t("common:errorDetails")}
</Button> </Button>
{Close} {Close}
</> </>
); );
}; };
export const ViewDstAction = export const ViewDstAction = (dst: string) => (snackbarId: SnackbarKey | undefined) => {
(dst: string) => (snackbarId: SnackbarKey | undefined) => { const { t } = useTranslation();
const { t } = useTranslation(); const dispatch = useAppDispatch();
const dispatch = useAppDispatch();
const Close = DefaultCloseAction(snackbarId); const Close = DefaultCloseAction(snackbarId);
const viewDst = useCallback(() => { const viewDst = useCallback(() => {
dispatch(navigateToPath(FileManagerIndex.main, dst)); dispatch(navigateToPath(FileManagerIndex.main, dst));
closeSnackbar(snackbarId); closeSnackbar(snackbarId);
}, [dispatch, snackbarId]); }, [dispatch, snackbarId]);
return ( return (
<> <>
<Button onClick={viewDst} color="inherit" size="small"> <Button onClick={viewDst} color="inherit" size="small">
{t("application:modals.view")} {t("application:modals.view")}
</Button> </Button>
{Close} {Close}
</> </>
); );
}; };
export const ViewDownloadLogAction = export const ViewDownloadLogAction = (downloadId: string) => (_snackbarId: SnackbarKey | undefined) => {
(downloadId: string) => (_snackbarId: SnackbarKey | undefined) => { const { t } = useTranslation();
const { t } = useTranslation(); const dispatch = useAppDispatch();
const dispatch = useAppDispatch();
const viewLogs = useCallback(() => { const viewLogs = useCallback(() => {
dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId })); dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId }));
}, [dispatch, downloadId]); }, [dispatch, downloadId]);
return ( return (
<> <>
<Button onClick={viewLogs} color="inherit" size="small"> <Button onClick={viewLogs} color="inherit" size="small">
{t("application:fileManager.details")} {t("application:fileManager.details")}
</Button> </Button>
</> </>
); );
}; };
export const ViewTaskAction = export const ViewTaskAction =
(path: string = "/tasks") => (path: string = "/tasks") =>

View File

@ -13,12 +13,7 @@ export interface TimeBadgeProps extends TypographyProps {
timeAgoThreshold?: number; timeAgoThreshold?: number;
} }
const TimeBadge = ({ const TimeBadge = ({ timeAgoThreshold = defaultTimeAgoThreshold, datetime, sx, ...rest }: TimeBadgeProps) => {
timeAgoThreshold = defaultTimeAgoThreshold,
datetime,
sx,
...rest
}: TimeBadgeProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const timeStr = useMemo(() => { const timeStr = useMemo(() => {
if (typeof datetime === "string") { if (typeof datetime === "string") {

View File

@ -31,13 +31,7 @@ const UserBadge = ({ textProps, user, uid, ...rest }: UserBadgeProps) => {
maxWidth: "150px", maxWidth: "150px",
}} }}
> >
<UserAvatar <UserAvatar overwriteTextSize user={user} onUserLoaded={(u) => setUserLoaded(u)} uid={uid} {...rest} />
overwriteTextSize
user={user}
onUserLoaded={(u) => setUserLoaded(u)}
uid={uid}
{...rest}
/>
<BadgeText {...textProps}> <BadgeText {...textProps}>
{userLoaded ? ( {userLoaded ? (
userLoaded.id ? ( userLoaded.id ? (
@ -50,9 +44,7 @@ const UserBadge = ({ textProps, user, uid, ...rest }: UserBadgeProps) => {
)} )}
</BadgeText> </BadgeText>
</DefaultButton> </DefaultButton>
{userLoaded && ( {userLoaded && <UserPopover user={userLoaded} {...bindPopover(popupState)} />}
<UserPopover user={userLoaded} {...bindPopover(popupState)} />
)}
</> </>
); );
}; };

View File

@ -1,11 +1,4 @@
import { import { Box, Button, PopoverProps, styled, Tooltip, Typography } from "@mui/material";
Box,
Button,
PopoverProps,
styled,
Tooltip,
Typography,
} from "@mui/material";
import HoverPopover from "material-ui-popup-state/HoverPopover"; import HoverPopover from "material-ui-popup-state/HoverPopover";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
@ -26,15 +19,7 @@ const ActionButton = styled(Button)({
minWidth: "initial", minWidth: "initial",
}); });
export const UserProfile = ({ export const UserProfile = ({ user, open, displayOnly }: { user: User; open: boolean; displayOnly?: boolean }) => {
user,
open,
displayOnly,
}: {
user: User;
open: boolean;
displayOnly?: boolean;
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
@ -55,22 +40,14 @@ export const UserProfile = ({
display: "flex", display: "flex",
}} }}
> >
<UserAvatar <UserAvatar overwriteTextSize user={user} sx={{ width: 80, height: 80 }} />
overwriteTextSize
user={user}
sx={{ width: 80, height: 80 }}
/>
<Box sx={{ ml: 2 }}> <Box sx={{ ml: 2 }}>
<Box sx={{ display: "flex", alignItems: "center" }}> <Box sx={{ display: "flex", alignItems: "center" }}>
<Typography variant={"h6"} fontWeight={600}> <Typography variant={"h6"} fontWeight={600}>
{user.id ? user.nickname : t("application:modals.anonymous")} {user.id ? user.nickname : t("application:modals.anonymous")}
</Typography> </Typography>
{displayOnly && ( {displayOnly && (
<Typography <Typography variant={"body2"} sx={{ ml: 1 }} color={"text.secondary"}>
variant={"body2"}
sx={{ ml: 1 }}
color={"text.secondary"}
>
{loadedUser?.group ? loadedUser.group.name : ""} {loadedUser?.group ? loadedUser.group.name : ""}
</Typography> </Typography>
)} )}
@ -85,12 +62,7 @@ export const UserProfile = ({
<Trans <Trans
i18nKey={"setting.accountCreatedAt"} i18nKey={"setting.accountCreatedAt"}
ns={"application"} ns={"application"}
components={[ components={[<TimeBadge variant={"inherit"} datetime={loadedUser.created_at} />]}
<TimeBadge
variant={"inherit"}
datetime={loadedUser.created_at}
/>,
]}
/> />
</Typography> </Typography>
)} )}

View File

@ -1,8 +1,6 @@
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import DraggableDialog, { import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx";
StyledDialogContentText,
} from "./DraggableDialog.tsx";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { closeAggregatedErrorDialog } from "../../redux/globalStateSlice.ts"; import { closeAggregatedErrorDialog } from "../../redux/globalStateSlice.ts";
import { import {
@ -44,18 +42,9 @@ const ErrorTable = (props: ErrorTableProps) => {
</TableHead> </TableHead>
<TableBody> <TableBody>
{Object.keys(props.errors).map((id) => ( {Object.keys(props.errors).map((id) => (
<TableRow <TableRow hover key={id} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
hover
key={id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{props.files[id] && ( {props.files[id] && <FileBadge sx={{ maxWidth: "250px" }} file={props.files[id]} />}
<FileBadge
sx={{ maxWidth: "250px" }}
file={props.files[id]}
/>
)}
{!props.files[id] && !id.startsWith(CrUriPrefix) && id} {!props.files[id] && !id.startsWith(CrUriPrefix) && id}
{!props.files[id] && id.startsWith(CrUriPrefix) && ( {!props.files[id] && id.startsWith(CrUriPrefix) && (
<FileBadge <FileBadge
@ -80,12 +69,8 @@ const AggregatedErrorDetail = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const open = useAppSelector( const open = useAppSelector((state) => state.globalState.aggregatedErrorDialogOpen);
(state) => state.globalState.aggregatedErrorDialogOpen, const files = useAppSelector((state) => state.globalState.aggregatedErrorFile);
);
const files = useAppSelector(
(state) => state.globalState.aggregatedErrorFile,
);
const error = useAppSelector((state) => state.globalState.aggregatedError); const error = useAppSelector((state) => state.globalState.aggregatedError);
const onClose = useCallback(() => { const onClose = useCallback(() => {
dispatch(closeAggregatedErrorDialog()); dispatch(closeAggregatedErrorDialog());
@ -119,9 +104,7 @@ const AggregatedErrorDetail = () => {
> >
<DialogContent> <DialogContent>
<Stack spacing={2}> <Stack spacing={2}>
<StyledDialogContentText> <StyledDialogContentText>{rootError && rootError.message}</StyledDialogContentText>
{rootError && rootError.message}
</StyledDialogContentText>
{files && errors && <ErrorTable errors={errors} files={files} />} {files && errors && <ErrorTable errors={errors} files={files} />}
{rootError && rootError.cid && ( {rootError && rootError.cid && (
<DialogContentText variant={"caption"}> <DialogContentText variant={"caption"}>

View File

@ -12,15 +12,9 @@ const BatchDownloadLog = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const logRef = useRef<HTMLInputElement>(null); const logRef = useRef<HTMLInputElement>(null);
const open = useAppSelector( const open = useAppSelector((state) => state.globalState.batchDownloadLogDialogOpen);
(state) => state.globalState.batchDownloadLogDialogOpen, const downloadId = useAppSelector((state) => state.globalState.batchDownloadLogDialogId);
); const logs = useAppSelector((state) => state.globalState.batchDownloadLogDialogLogs?.[downloadId ?? ""]);
const downloadId = useAppSelector(
(state) => state.globalState.batchDownloadLogDialogId,
);
const logs = useAppSelector(
(state) => state.globalState.batchDownloadLogDialogLogs?.[downloadId ?? ""],
);
const onClose = useCallback(() => { const onClose = useCallback(() => {
dispatch(closeBatchDownloadLogDialog()); dispatch(closeBatchDownloadLogDialog());

View File

@ -2,9 +2,7 @@ import { useTranslation } from "react-i18next";
import { DialogContent, Stack } from "@mui/material"; import { DialogContent, Stack } from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
import { useCallback } from "react"; import { useCallback } from "react";
import DraggableDialog, { import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx";
StyledDialogContentText,
} from "./DraggableDialog.tsx";
import { generalDialogPromisePool } from "../../redux/thunks/dialog.ts"; import { generalDialogPromisePool } from "../../redux/thunks/dialog.ts";
import { closeConfirmDialog } from "../../redux/globalStateSlice.ts"; import { closeConfirmDialog } from "../../redux/globalStateSlice.ts";
@ -13,12 +11,8 @@ const Confirmation = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const open = useAppSelector((state) => state.globalState.confirmDialogOpen); const open = useAppSelector((state) => state.globalState.confirmDialogOpen);
const message = useAppSelector( const message = useAppSelector((state) => state.globalState.confirmDialogMessage);
(state) => state.globalState.confirmDialogMessage, const promiseId = useAppSelector((state) => state.globalState.confirmPromiseId);
);
const promiseId = useAppSelector(
(state) => state.globalState.confirmPromiseId,
);
const onClose = useCallback(() => { const onClose = useCallback(() => {
dispatch(closeConfirmDialog()); dispatch(closeConfirmDialog());

View File

@ -1,33 +1,29 @@
import { AccordionDetailsProps, Box, styled } from "@mui/material"; import { AccordionDetailsProps, Box, styled } from "@mui/material";
import MuiAccordion, { AccordionProps } from "@mui/material/Accordion"; import MuiAccordion, { AccordionProps } from "@mui/material/Accordion";
import MuiAccordionSummary, { import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary";
AccordionSummaryProps,
} from "@mui/material/AccordionSummary";
import MuiAccordionDetails from "@mui/material/AccordionDetails"; import MuiAccordionDetails from "@mui/material/AccordionDetails";
import { useState } from "react"; import { useState } from "react";
import { CaretDownIcon } from "../FileManager/TreeView/TreeFile.tsx"; import { CaretDownIcon } from "../FileManager/TreeView/TreeFile.tsx";
import { DefaultButton } from "../Common/StyledComponents.tsx"; import { DefaultButton } from "../Common/StyledComponents.tsx";
const Accordion = styled((props: AccordionProps) => ( const Accordion = styled((props: AccordionProps) => <MuiAccordion disableGutters elevation={0} square {...props} />)(
<MuiAccordion disableGutters elevation={0} square {...props} /> ({ theme, expanded }) => ({
))(({ theme, expanded }) => ({ borderRadius: theme.shape.borderRadius,
borderRadius: theme.shape.borderRadius, backgroundColor: expanded
backgroundColor: expanded ? theme.palette.mode == "light"
? theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)"
? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)"
: "rgba(255, 255, 255, 0.09)" : "initial",
: "initial", "&:not(:last-child)": {
"&:not(:last-child)": { borderBottom: 0,
borderBottom: 0, },
}, "&::before": {
"&::before": { display: "none",
display: "none", },
}, }),
})); );
const AccordionSummary = styled((props: AccordionSummaryProps) => ( const AccordionSummary = styled((props: AccordionSummaryProps) => <MuiAccordionSummary {...props} />)(() => ({
<MuiAccordionSummary {...props} />
))(() => ({
flexDirection: "row-reverse", flexDirection: "row-reverse",
minHeight: 0, minHeight: 0,
padding: 0, padding: 0,
@ -40,22 +36,17 @@ const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
padding: theme.spacing(2), padding: theme.spacing(2),
})); }));
const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>( const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>(({ theme, expanded }) => ({
({ theme, expanded }) => ({ justifyContent: "flex-start",
justifyContent: "flex-start", backgroundColor: expanded
backgroundColor: expanded ? "initial"
? "initial" : theme.palette.mode == "light"
: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)"
? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)",
: "rgba(255, 255, 255, 0.09)", "&:hover": {
"&:hover": { backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.09)" : "rgba(255, 255, 255, 0.13)",
backgroundColor: },
theme.palette.mode == "light" }));
? "rgba(0, 0, 0, 0.09)"
: "rgba(255, 255, 255, 0.13)",
},
}),
);
export interface DialogAccordionProps { export interface DialogAccordionProps {
children?: React.ReactNode; children?: React.ReactNode;
@ -73,17 +64,11 @@ const DialogAccordion = (props: DialogAccordionProps) => {
<Box> <Box>
<Accordion expanded={expanded} onChange={handleChange}> <Accordion expanded={expanded} onChange={handleChange}>
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header"> <AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
<SummaryButton <SummaryButton expanded={expanded} fullWidth startIcon={<CaretDownIcon expanded={expanded} />}>
expanded={expanded}
fullWidth
startIcon={<CaretDownIcon expanded={expanded} />}
>
{props.title} {props.title}
</SummaryButton> </SummaryButton>
</AccordionSummary> </AccordionSummary>
<AccordionDetails {...props.accordionDetailProps}> <AccordionDetails {...props.accordionDetailProps}>{props.children}</AccordionDetails>
{props.children}
</AccordionDetails>
</Accordion> </Accordion>
</Box> </Box>
); );

View File

@ -1,10 +1,5 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { DialogContent, List, ListItemButton, ListItemText } from "@mui/material";
DialogContent,
List,
ListItemButton,
ListItemText,
} from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import DraggableDialog from "./DraggableDialog.tsx"; import DraggableDialog from "./DraggableDialog.tsx";
@ -15,16 +10,10 @@ const SelectOption = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const open = useAppSelector( const open = useAppSelector((state) => state.globalState.selectOptionDialogOpen);
(state) => state.globalState.selectOptionDialogOpen,
);
const title = useAppSelector((state) => state.globalState.selectOptionTitle); const title = useAppSelector((state) => state.globalState.selectOptionTitle);
const promiseId = useAppSelector( const promiseId = useAppSelector((state) => state.globalState.selectOptionPromiseId);
(state) => state.globalState.selectOptionPromiseId, const options = useAppSelector((state) => state.globalState.selectOptionDialogOptions);
);
const options = useAppSelector(
(state) => state.globalState.selectOptionDialogOptions,
);
const onClose = useCallback(() => { const onClose = useCallback(() => {
dispatch(closeSelectOptionDialog()); dispatch(closeSelectOptionDialog());
@ -65,8 +54,9 @@ const SelectOption = () => {
fontWeight: "bold", fontWeight: "bold",
}, },
secondary: { variant: "body2" } secondary: { variant: "body2" },
}} /> }}
/>
</ListItemButton> </ListItemButton>
))} ))}
</List> </List>

View File

@ -1,25 +1,23 @@
import * as React from "react"; import * as React from "react";
import { Menu, type MenuProps } from "@mui/material"; import { Menu, type MenuProps } from "@mui/material";
const HoverMenu: React.ComponentType<MenuProps> = React.forwardRef( const HoverMenu: React.ComponentType<MenuProps> = React.forwardRef(function HoverMenu(props: MenuProps, ref): any {
function HoverMenu(props: MenuProps, ref): any { return (
return ( <Menu
<Menu {...props}
{...props} ref={ref}
ref={ref} style={{ pointerEvents: "none", ...props.style }}
style={{ pointerEvents: "none", ...props.style }} slotProps={{
slotProps={{ ...props.slotProps,
...props.slotProps, paper: {
paper: { ...props.slotProps?.paper,
...props.slotProps?.paper, style: {
style: { pointerEvents: "auto",
pointerEvents: "auto",
},
}, },
}} },
/> }}
); />
}, );
); });
export default HoverMenu; export default HoverMenu;

View File

@ -6,11 +6,7 @@ import { ViewersByID } from "../../../redux/siteConfigSlice.ts";
import { ListItemIcon, ListItemText } from "@mui/material"; import { ListItemIcon, ListItemText } from "@mui/material";
import { ViewerIcon } from "../Dialogs/OpenWith.tsx"; import { ViewerIcon } from "../Dialogs/OpenWith.tsx";
import { SquareMenuItem } from "./ContextMenu.tsx"; import { SquareMenuItem } from "./ContextMenu.tsx";
import { import { CascadingContext, CascadingMenuItem, CascadingSubmenu } from "./CascadingMenu.tsx";
CascadingContext,
CascadingMenuItem,
CascadingSubmenu,
} from "./CascadingMenu.tsx";
import { NewFileTemplate, Viewer } from "../../../api/explorer.ts"; import { NewFileTemplate, Viewer } from "../../../api/explorer.ts";
import { createNew } from "../../../redux/thunks/file.ts"; import { createNew } from "../../../redux/thunks/file.ts";
import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts"; import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
@ -72,10 +68,7 @@ const NewFileTemplateMenuItems = (props: SubMenuItemsProps) => {
if (viewer.templates.length == 1) { if (viewer.templates.length == 1) {
return ( return (
<SquareMenuItem <SquareMenuItem key={viewer.id} onClick={onClick(viewer, viewer.templates[0])}>
key={viewer.id}
onClick={onClick(viewer, viewer.templates[0])}
>
<ListItemIcon> <ListItemIcon>
<ViewerIcon size={20} viewer={viewer} py={0} /> <ViewerIcon size={20} viewer={viewer} py={0} />
</ListItemIcon> </ListItemIcon>

View File

@ -5,16 +5,10 @@ import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
import { import { applyIconColor, dialogBasedMoveCopy } from "../../../redux/thunks/file.ts";
applyIconColor,
dialogBasedMoveCopy,
} from "../../../redux/thunks/file.ts";
import { ListItemIcon, ListItemText } from "@mui/material"; import { ListItemIcon, ListItemText } from "@mui/material";
import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx"; import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx";
import { import { setChangeIconDialog, setPinFileDialog } from "../../../redux/globalStateSlice.ts";
setChangeIconDialog,
setPinFileDialog,
} from "../../../redux/globalStateSlice.ts";
import { getFileLinkedUri } from "../../../util"; import { getFileLinkedUri } from "../../../util";
import PinOutlined from "../../Icons/PinOutlined.tsx"; import PinOutlined from "../../Icons/PinOutlined.tsx";
import EmojiEdit from "../../Icons/EmojiEdit.tsx"; import EmojiEdit from "../../Icons/EmojiEdit.tsx";
@ -45,16 +39,11 @@ const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
[dispatch, targets], [dispatch, targets],
); );
const showDivider = const showDivider =
(displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) && (displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) && displayOpt.showChangeFolderColor;
displayOpt.showChangeFolderColor;
return ( return (
<> <>
{displayOpt.showMove && ( {displayOpt.showMove && (
<CascadingMenuItem <CascadingMenuItem onClick={onClick(() => dispatch(dialogBasedMoveCopy(0, targets, false)))}>
onClick={onClick(() =>
dispatch(dialogBasedMoveCopy(0, targets, false)),
)}
>
<ListItemIcon> <ListItemIcon>
<FolderArrowRightOutlined fontSize="small" /> <FolderArrowRightOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
@ -92,18 +81,14 @@ const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
<ListItemIcon> <ListItemIcon>
<EmojiEdit fontSize="small" /> <EmojiEdit fontSize="small" />
</ListItemIcon> </ListItemIcon>
<ListItemText> <ListItemText>{t("application:fileManager.customizeIcon")}</ListItemText>
{t("application:fileManager.customizeIcon")}
</ListItemText>
</CascadingMenuItem> </CascadingMenuItem>
)} )}
{showDivider && <DenseDivider />} {showDivider && <DenseDivider />}
{displayOpt.showChangeFolderColor && ( {displayOpt.showChangeFolderColor && (
<FolderColorQuickAction <FolderColorQuickAction
file={targets[0]} file={targets[0]}
onColorChange={(color) => onColorChange={(color) => onClick(() => dispatch(applyIconColor(0, targets, color, true)))()}
onClick(() => dispatch(applyIconColor(0, targets, color, true)))()
}
sx={{ sx={{
maxWidth: "204px", maxWidth: "204px",
margin: (theme) => `0 ${theme.spacing(0.5)}`, margin: (theme) => `0 ${theme.spacing(0.5)}`,

View File

@ -8,11 +8,7 @@ import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
import { setTagsDialog } from "../../../redux/globalStateSlice.ts"; import { setTagsDialog } from "../../../redux/globalStateSlice.ts";
import { ListItemIcon, ListItemText } from "@mui/material"; import { ListItemIcon, ListItemText } from "@mui/material";
import Tags from "../../Icons/Tags.tsx"; import Tags from "../../Icons/Tags.tsx";
import { import { DenseDivider, SquareMenuItem, SubMenuItemsProps } from "./ContextMenu.tsx";
DenseDivider,
SquareMenuItem,
SubMenuItemsProps,
} from "./ContextMenu.tsx";
import SessionManager, { UserSettings } from "../../../session"; import SessionManager, { UserSettings } from "../../../session";
import { UsedTags } from "../../../session/utils.ts"; import { UsedTags } from "../../../session/utils.ts";
import Checkmark from "../../Icons/Checkmark.tsx"; import Checkmark from "../../Icons/Checkmark.tsx";
@ -111,33 +107,20 @@ const TagMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
</CascadingMenuItem> </CascadingMenuItem>
{tags.length > 0 && <DenseDivider />} {tags.length > 0 && <DenseDivider />}
{tags.map((tag) => ( {tags.map((tag) => (
<SquareMenuItem <SquareMenuItem key={tag.key} onClick={() => onTagChange(tag, !tag.selected)}>
key={tag.key}
onClick={() => onTagChange(tag, !tag.selected)}
>
{tag.selected && ( {tag.selected && (
<> <>
<ListItemIcon> <ListItemIcon>
<Checkmark /> <Checkmark />
</ListItemIcon> </ListItemIcon>
<ListItemText> <ListItemText>
<FileTag <FileTag disableClick spacing={1} label={tag.key} tagColor={tag.color} />
disableClick
spacing={1}
label={tag.key}
tagColor={tag.color}
/>
</ListItemText> </ListItemText>
</> </>
)} )}
{!tag.selected && ( {!tag.selected && (
<ListItemText inset> <ListItemText inset>
<FileTag <FileTag disableClick spacing={1} label={tag.key} tagColor={tag.color} />
disableClick
spacing={1}
label={tag.key}
tagColor={tag.color}
/>
</ListItemText> </ListItemText>
)} )}
</SquareMenuItem> </SquareMenuItem>

View File

@ -1,14 +1,5 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { Box, Button, DialogContent, Skeleton, styled, Tab, Tabs, useTheme } from "@mui/material";
Box,
Button,
DialogContent,
Skeleton,
styled,
Tab,
Tabs,
useTheme,
} from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
@ -73,18 +64,10 @@ const ChangeIcon = () => {
const [tabValue, setTabValue] = useState(0); const [tabValue, setTabValue] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const open = useAppSelector( const open = useAppSelector((state) => state.globalState.changeIconDialogOpen);
(state) => state.globalState.changeIconDialogOpen, const targets = useAppSelector((state) => state.globalState.changeIconDialogFile);
); const emojiStr = useAppSelector((state) => state.siteConfig.emojis.config.emoji_preset);
const targets = useAppSelector( const emojiStrLoaded = useAppSelector((state) => state.siteConfig.emojis.loaded);
(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 => { const emojiSetting = useMemo((): EmojiSetting => {
if (!emojiStr) return {}; if (!emojiStr) return {};
@ -163,9 +146,7 @@ const ChangeIcon = () => {
onChange={handleTabChange} onChange={handleTabChange}
> >
{emojiStrLoaded ? ( {emojiStrLoaded ? (
Object.keys(emojiSetting).map((key) => ( Object.keys(emojiSetting).map((key) => <StyledTab label={key} key={key} />)
<StyledTab label={key} key={key} />
))
) : ( ) : (
<StyledTab label={<Skeleton sx={{ minWidth: "20px" }} />} /> <StyledTab label={<Skeleton sx={{ minWidth: "20px" }} />} />
)} )}
@ -177,9 +158,7 @@ const ChangeIcon = () => {
<CustomTabPanel value={tabValue} index={index}> <CustomTabPanel value={tabValue} index={index}>
<SelectorBox> <SelectorBox>
{emojiSetting[key].map((emoji) => ( {emojiSetting[key].map((emoji) => (
<EmojiButton onClick={onAccept(emoji)}> <EmojiButton onClick={onAccept(emoji)}>{emoji}</EmojiButton>
{emoji}
</EmojiButton>
))} ))}
</SelectorBox> </SelectorBox>
</CustomTabPanel> </CustomTabPanel>

View File

@ -6,10 +6,7 @@ import { setRenameFileModalError } from "../../../redux/fileManagerSlice.ts";
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
import { createNewDialogPromisePool } from "../../../redux/thunks/dialog.ts"; import { createNewDialogPromisePool } from "../../../redux/thunks/dialog.ts";
import { FilledTextField } from "../../Common/StyledComponents.tsx"; import { FilledTextField } from "../../Common/StyledComponents.tsx";
import { import { closeCreateNewDialog, CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
closeCreateNewDialog,
CreateNewDialogType,
} from "../../../redux/globalStateSlice.ts";
import { submitCreateNew } from "../../../redux/thunks/file.ts"; import { submitCreateNew } from "../../../redux/thunks/file.ts";
import { FileType } from "../../../api/explorer.ts"; import { FileType } from "../../../api/explorer.ts";
@ -24,15 +21,10 @@ const CreateNew = () => {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const open = useAppSelector((state) => state.globalState.createNewDialogOpen); const open = useAppSelector((state) => state.globalState.createNewDialogOpen);
const promiseId = useAppSelector( const promiseId = useAppSelector((state) => state.globalState.createNewPromiseId);
(state) => state.globalState.createNewPromiseId,
);
const type = useAppSelector((state) => state.globalState.createNewDialogType); const type = useAppSelector((state) => state.globalState.createNewDialogType);
const defaultName = useAppSelector( const defaultName = useAppSelector((state) => state.globalState.createNewDialogDefault);
(state) => state.globalState.createNewDialogDefault, const fmIndex = useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0;
);
const fmIndex =
useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0;
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@ -54,13 +46,7 @@ const CreateNew = () => {
} }
setLoading(true); setLoading(true);
dispatch( dispatch(submitCreateNew(fmIndex, name, type == CreateNewDialogType.folder ? FileType.folder : FileType.file))
submitCreateNew(
fmIndex,
name,
type == CreateNewDialogType.folder ? FileType.folder : FileType.file,
),
)
.then((f) => { .then((f) => {
if (promiseId) { if (promiseId) {
createNewDialogPromisePool[promiseId]?.resolve(f); createNewDialogPromisePool[promiseId]?.resolve(f);
@ -86,12 +72,7 @@ const CreateNew = () => {
if (open) { if (open) {
const lastDot = name.lastIndexOf("."); const lastDot = name.lastIndexOf(".");
setTimeout( setTimeout(
() => () => inputRef.current && inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : name.length),
inputRef.current &&
inputRef.current.setSelectionRange(
0,
lastDot > 0 ? lastDot : name.length,
),
200, 200,
); );
} }
@ -110,9 +91,7 @@ const CreateNew = () => {
return ( return (
<DraggableDialog <DraggableDialog
title={t( title={t(
type == CreateNewDialogType.folder type == CreateNewDialogType.folder ? "application:fileManager.newFolder" : "application:fileManager.newFile",
? "application:fileManager.newFolder"
: "application:fileManager.newFile",
)} )}
showActions showActions
loading={loading} loading={loading}
@ -137,9 +116,7 @@ const CreateNew = () => {
helperText={error} helperText={error}
margin="dense" margin="dense"
label={t( label={t(
type == CreateNewDialogType.folder type == CreateNewDialogType.folder ? "application:modals.folderName" : "application:modals.fileName",
? "application:modals.folderName"
: "application:modals.fileName",
)} )}
type="text" type="text"
value={name} value={name}

View File

@ -13,9 +13,7 @@ const DirectLinks = () => {
const [showFileName, setShowFileName] = useState(false); const [showFileName, setShowFileName] = useState(false);
const open = useAppSelector( const open = useAppSelector((state) => state.globalState.directLinkDialogOpen);
(state) => state.globalState.directLinkDialogOpen,
);
const targets = useAppSelector((state) => state.globalState.directLinkRes); const targets = useAppSelector((state) => state.globalState.directLinkRes);
const contents = useMemo(() => { const contents = useMemo(() => {
@ -86,7 +84,7 @@ const DirectLinks = () => {
variant="outlined" variant="outlined"
fullWidth fullWidth
slotProps={{ slotProps={{
htmlInput: { readonly: true } htmlInput: { readonly: true },
}} }}
/> />
</DialogContent> </DialogContent>

View File

@ -1,21 +1,9 @@
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { DialogContent, Stack } from "@mui/material"; import { DialogContent, Stack } from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { import { ChangeEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
ChangeEvent, import { closeRenameFileModal, setRenameFileModalError } from "../../../redux/fileManagerSlice.ts";
useCallback, import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx";
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 { renameDialogPromisePool } from "../../../redux/thunks/dialog.ts";
import { validateFileName } from "../../../redux/thunks/file.ts"; import { validateFileName } from "../../../redux/thunks/file.ts";
import { FileType } from "../../../api/explorer.ts"; import { FileType } from "../../../api/explorer.ts";
@ -33,21 +21,11 @@ const Rename = () => {
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const open = useAppSelector( const open = useAppSelector((state) => state.fileManager[0].renameFileModalOpen);
(state) => state.fileManager[0].renameFileModalOpen, const targets = useAppSelector((state) => state.fileManager[0].renameFileModalSelected);
); const promiseId = useAppSelector((state) => state.fileManager[0].renameFileModalPromiseId);
const targets = useAppSelector( const loading = useAppSelector((state) => state.fileManager[0].renameFileModalLoading);
(state) => state.fileManager[0].renameFileModalSelected, const error = useAppSelector((state) => state.fileManager[0].renameFileModalError);
);
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(() => { const onClose = useCallback(() => {
dispatch( dispatch(
@ -67,13 +45,7 @@ const Rename = () => {
e.preventDefault(); e.preventDefault();
} }
if (promiseId) { if (promiseId) {
dispatch( dispatch(validateFileName(0, renameDialogPromisePool[promiseId]?.resolve, name));
validateFileName(
0,
renameDialogPromisePool[promiseId]?.resolve,
name,
),
);
} }
}, },
[promiseId, name], [promiseId, name],
@ -95,12 +67,8 @@ const Rename = () => {
useEffect(() => { useEffect(() => {
if (targets && open && inputRef.current) { if (targets && open && inputRef.current) {
const lastDot = const lastDot = targets.type == FileType.folder ? 0 : targets.name.lastIndexOf(".");
targets.type == FileType.folder ? 0 : targets.name.lastIndexOf("."); inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : targets.name.length);
inputRef.current.setSelectionRange(
0,
lastDot > 0 ? lastDot : targets.name.length,
);
} }
}, [inputRef.current, open]); }, [inputRef.current, open]);

View File

@ -2,13 +2,8 @@ import { Trans, useTranslation } from "react-i18next";
import { Button, DialogContent, Stack } from "@mui/material"; import { Button, DialogContent, Stack } from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { useCallback } from "react"; import { useCallback } from "react";
import DraggableDialog, { import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx";
StyledDialogContentText, import { askSaveAs, staleVersionDialogPromisePool } from "../../../redux/thunks/dialog.ts";
} from "../../Dialogs/DraggableDialog.tsx";
import {
askSaveAs,
staleVersionDialogPromisePool,
} from "../../../redux/thunks/dialog.ts";
import { closeStaleVersionDialog } from "../../../redux/globalStateSlice.ts"; import { closeStaleVersionDialog } from "../../../redux/globalStateSlice.ts";
import CrUri from "../../../util/uri.ts"; import CrUri from "../../../util/uri.ts";
@ -16,13 +11,9 @@ const StaleVersionConfirm = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const open = useAppSelector( const open = useAppSelector((state) => state.globalState.staleVersionDialogOpen);
(state) => state.globalState.staleVersionDialogOpen,
);
const uri = useAppSelector((state) => state.globalState.staleVersionUri); const uri = useAppSelector((state) => state.globalState.staleVersionUri);
const promiseId = useAppSelector( const promiseId = useAppSelector((state) => state.globalState.staleVersionPromiseId);
(state) => state.globalState.staleVersionPromiseId,
);
const onClose = useCallback(() => { const onClose = useCallback(() => {
dispatch(closeStaleVersionDialog()); dispatch(closeStaleVersionDialog());
@ -88,11 +79,7 @@ const StaleVersionConfirm = () => {
<StyledDialogContentText> <StyledDialogContentText>
{t("modals.conflictDes1")} {t("modals.conflictDes1")}
<ul> <ul>
<Trans <Trans i18nKey="modals.conflictDes2" ns={"application"} components={[<li key={0} />, <li key={1} />]} />
i18nKey="modals.conflictDes2"
ns={"application"}
components={[<li key={0} />, <li key={1} />]}
/>
</ul> </ul>
</StyledDialogContentText> </StyledDialogContentText>
</Stack> </Stack>

View File

@ -55,8 +55,7 @@ const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => {
zIndex: 1610 - i - 1, zIndex: 1610 - i - 1,
top: (i + 1) * 4, top: (i + 1) * 4,
left: (i + 1) * 4, left: (i + 1) * 4,
transition: (theme) => transition: (theme) => theme.transitions.create(["width", "height"]),
theme.transitions.create(["width", "height"]),
}} }}
elevation={3} elevation={3}
/> />
@ -68,14 +67,12 @@ const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => {
const DragLayer = () => { const DragLayer = () => {
DisableDropDelay(); DisableDropDelay();
const { itemType, isDragging, item, pointerOffset } = useDragLayer( const { itemType, isDragging, item, pointerOffset } = useDragLayer((monitor) => ({
(monitor) => ({ item: monitor.getItem(),
item: monitor.getItem(), itemType: monitor.getItemType(),
itemType: monitor.getItemType(), pointerOffset: monitor.getClientOffset(),
pointerOffset: monitor.getClientOffset(), isDragging: monitor.isDragging(),
isDragging: monitor.isDragging(), }));
}),
);
const selected = useAppSelector((state) => state.fileManager[0].selected); const selected = useAppSelector((state) => state.fileManager[0].selected);
const draggingFiles = useMemo(() => { const draggingFiles = useMemo(() => {

View File

@ -5,9 +5,7 @@ const threshold = 0.1;
const useDragScrolling = (containers: string[]) => { const useDragScrolling = (containers: string[]) => {
const isScrolling = useRef(false); const isScrolling = useRef(false);
const targets = containers.map( const targets = containers.map((id) => document.querySelector(id) as HTMLElement);
(id) => document.querySelector(id) as HTMLElement,
);
const rects = useRef<DOMRect[]>([]); const rects = useRef<DOMRect[]>([]);
const goDown = (target: HTMLElement) => { const goDown = (target: HTMLElement) => {
@ -41,16 +39,10 @@ const useDragScrolling = (containers: string[]) => {
} }
const height = rect.bottom - rect.top; const height = rect.bottom - rect.top;
if ( if (event.clientY > rect.top && event.clientY < rect.top + threshold * height) {
event.clientY > rect.top &&
event.clientY < rect.top + threshold * height
) {
isScrolling.current = true; isScrolling.current = true;
window.requestAnimationFrame(goUp(targets[index])); window.requestAnimationFrame(goUp(targets[index]));
} else if ( } else if (event.clientY < rect.bottom && event.clientY > rect.bottom - threshold * height) {
event.clientY < rect.bottom &&
event.clientY > rect.bottom - threshold * height
) {
isScrolling.current = true; isScrolling.current = true;
window.requestAnimationFrame(goDown(targets[index])); window.requestAnimationFrame(goDown(targets[index]));
} else { } else {

View File

@ -1,20 +1,11 @@
import { import { Chip, ChipProps, darken, styled, Tooltip, useTheme } from "@mui/material";
Chip,
ChipProps,
darken,
styled,
Tooltip,
useTheme,
} from "@mui/material";
import { useCallback, useContext } from "react"; import { useCallback, useContext } from "react";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { Metadata } from "../../../api/explorer.ts"; import { Metadata } from "../../../api/explorer.ts";
import { searchMetadata } from "../../../redux/thunks/filemanager.ts"; import { searchMetadata } from "../../../redux/thunks/filemanager.ts";
import { FmIndexContext } from "../FmIndexContext.tsx"; import { FmIndexContext } from "../FmIndexContext.tsx";
export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({ export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({ defaultStyle }) => {
defaultStyle,
}) => {
const base = { const base = {
"& .MuiChip-deleteIcon": {}, "& .MuiChip-deleteIcon": {},
}; };
@ -30,16 +21,7 @@ export interface FileTagProps extends ChipProps {
disableClick?: boolean; disableClick?: boolean;
} }
const FileTag = ({ const FileTag = ({ disableClick, tagColor, sx, label, defaultStyle, spacing, openInNewTab, ...rest }: FileTagProps) => {
disableClick,
tagColor,
sx,
label,
defaultStyle,
spacing,
openInNewTab,
...rest
}: FileTagProps) => {
const theme = useTheme(); const theme = useTheme();
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -56,23 +38,13 @@ const FileTag = ({
return; return;
} }
e.stopPropagation(); e.stopPropagation();
dispatch( dispatch(searchMetadata(fmIndex, Metadata.tag_prefix + label, tagColor, openInNewTab));
searchMetadata(
fmIndex,
Metadata.tag_prefix + label,
tagColor,
openInNewTab,
),
);
}, },
[root, dispatch, fmIndex, disableClick], [root, dispatch, fmIndex, disableClick],
); );
const hackColor = const hackColor =
!!tagColor && !!tagColor && theme.palette.getContrastText(tagColor) != theme.palette.text.primary ? "error" : undefined;
theme.palette.getContrastText(tagColor) != theme.palette.text.primary
? "error"
: undefined;
return ( return (
<Tooltip title={label}> <Tooltip title={label}>
<TagChip <TagChip

View File

@ -12,86 +12,80 @@ export interface FileTagSummaryProps {
[key: string]: any; [key: string]: any;
} }
const FileTagSummary = memo( const FileTagSummary = memo(({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => {
({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => { const theme = useTheme();
const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const isMobile = useMediaQuery(theme.breakpoints.down("md")); const popupState = usePopupState({
const popupState = usePopupState({ variant: "popover",
variant: "popover", popupId: "demoMenu",
popupId: "demoMenu", });
});
const { open, ...rest } = bindPopover(popupState); const { open, ...rest } = bindPopover(popupState);
const stopPropagation = useCallback((e: any) => e.stopPropagation(), []); const stopPropagation = useCallback((e: any) => e.stopPropagation(), []);
const [shown, hidden] = useMemo(() => { const [shown, hidden] = useMemo(() => {
if (tags.length <= max) { if (tags.length <= max) {
return [tags, []]; return [tags, []];
} }
return [tags.slice(0, max), tags.slice(max)]; return [tags.slice(0, max), tags.slice(max)];
}, [tags, max]); }, [tags, max]);
const { onClick, ...triggerProps } = bindTrigger(popupState); const { onClick, ...triggerProps } = bindTrigger(popupState);
const onMobileClick = (e: React.MouseEvent<HTMLElement>) => { const onMobileClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation(); e.stopPropagation();
onClick(e); onClick(e);
}; };
const PopoverComponent = isMobile ? Popover : HoverPopover; const PopoverComponent = isMobile ? Popover : HoverPopover;
return ( return (
<Stack direction={"row"} spacing={1} sx={{ ...sx }} {...restProps}> <Stack direction={"row"} spacing={1} sx={{ ...sx }} {...restProps}>
{shown.map((tag) => ( {shown.map((tag) => (
<FileTag <FileTag tagColor={tag.value == "" ? undefined : tag.value} label={tag.key} key={tag.key} />
tagColor={tag.value == "" ? undefined : tag.value} ))}
label={tag.key} {hidden.length > 0 && (
key={tag.key} <TagChip
/> size="small"
))} variant={"outlined"}
{hidden.length > 0 && ( label={`+${hidden.length}`}
<TagChip {...(isMobile
size="small" ? {
variant={"outlined"} onClick: onMobileClick,
label={`+${hidden.length}`} ...triggerProps,
{...(isMobile }
? { : bindHover(popupState))}
onClick: onMobileClick, />
...triggerProps, )}
}
: bindHover(popupState))}
/>
)}
{open && ( {open && (
<PopoverComponent <PopoverComponent
onMouseDown={stopPropagation} onMouseDown={stopPropagation}
onMouseUp={stopPropagation} onMouseUp={stopPropagation}
onClick={stopPropagation} onClick={stopPropagation}
open={open} open={open}
{...rest} {...rest}
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: "bottom",
horizontal: "center", horizontal: "center",
}} }}
transformOrigin={{ transformOrigin={{
vertical: "top", vertical: "top",
horizontal: "center", horizontal: "center",
}} }}
> >
<Box sx={{ p: 1, maxWidth: "300px" }}> <Box sx={{ p: 1, maxWidth: "300px" }}>
{hidden.map((tag, i) => ( {hidden.map((tag, i) => (
<FileTag <FileTag
tagColor={tag.value == "" ? undefined : tag.value} tagColor={tag.value == "" ? undefined : tag.value}
label={tag.key} label={tag.key}
spacing={i === hidden.length - 1 ? undefined : 1} spacing={i === hidden.length - 1 ? undefined : 1}
key={tag.key} key={tag.key}
/> />
))} ))}
</Box> </Box>
</PopoverComponent> </PopoverComponent>
)} )}
</Stack> </Stack>
); );
}, });
);
export default FileTagSummary; export default FileTagSummary;

View File

@ -44,21 +44,11 @@ export const loadingPlaceHolderNumb = 3;
const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => { const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
const { t } = useTranslation("application"); const { t } = useTranslation("application");
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const files = useAppSelector( const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
(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 mixedType = useAppSelector( const showThumb = useAppSelector((state) => state.fileManager[fmIndex].showThumb);
(state) => state.fileManager[fmIndex].list?.mixed_type, const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params);
);
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 = useMemo(() => {
const list: listComponents = { const list: listComponents = {
Files: [], Files: [],
@ -115,9 +105,7 @@ const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
</GridItem> </GridItem>
); );
const _ = const _ =
list.Files.length > 0 list.Files.length > 0 ? list.Files.push(loadingPlaceholder) : list.Folders?.push(loadingPlaceholder);
? list.Files.push(loadingPlaceholder)
: list.Folders?.push(loadingPlaceholder);
} }
} }
} }

View File

@ -13,18 +13,10 @@ export interface ListBodyProps {
const ListBody = ({ columns }: ListBodyProps) => { const ListBody = ({ columns }: ListBodyProps) => {
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const files = useAppSelector( const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
(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 mixedType = useAppSelector( const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params);
(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 = useMemo(() => {
const list: FmFile[] = []; const list: FmFile[] = [];

View File

@ -23,13 +23,7 @@ export interface FileBadgeProps extends ButtonProps {
clickable?: boolean; clickable?: boolean;
} }
const FileBadge = ({ const FileBadge = ({ file, clickable, simplifiedFile, unknown, ...rest }: FileBadgeProps) => {
file,
clickable,
simplifiedFile,
unknown,
...rest
}: FileBadgeProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const popupState = usePopupState({ const popupState = usePopupState({
variant: "popover", variant: "popover",
@ -131,9 +125,7 @@ const FileBadge = ({
/> />
)} )}
<BadgeText variant={"body2"}> <BadgeText variant={"body2"}>{name == "" ? displayName : name}</BadgeText>
{name == "" ? displayName : name}
</BadgeText>
</DefaultButton> </DefaultButton>
</span> </span>
</Tooltip> </Tooltip>

View File

@ -1,12 +1,4 @@
import { import { Box, Button, Divider, Popover, styled, Tooltip, useTheme } from "@mui/material";
Box,
Button,
Divider,
Popover,
styled,
Tooltip,
useTheme,
} from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { CSSProperties, useCallback, useState } from "react"; import { CSSProperties, useCallback, useState } from "react";
import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
@ -72,11 +64,7 @@ const ColorCircleBox = styled("div")(({
}; };
}); });
const ColorCircleBoxChild = styled("div")(({ const ColorCircleBoxChild = styled("div")(({ selected }: { selected: boolean }) => {
selected,
}: {
selected: boolean;
}) => {
const theme = useTheme(); const theme = useTheme();
return { return {
"--circle-point-background-color": theme.palette.background.default, "--circle-point-background-color": theme.palette.background.default,
@ -90,14 +78,7 @@ const ColorCircleBoxChild = styled("div")(({
}; };
}); });
export const ColorCircle = ({ export const ColorCircle = ({ color, selected, isCustomization, onClick, size, noMb }: ColorCircleProps) => {
color,
selected,
isCustomization,
onClick,
size,
noMb,
}: ColorCircleProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const displayColor = isCustomization const displayColor = isCustomization
? "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)" ? "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%)" ? "linear-gradient(45deg, rgba(217,217,217,1) 46%, rgba(217,217,217,1) 47%, rgba(128,128,128,1) 47%)"
: color; : color;
return ( return (
<Tooltip <Tooltip title={isCustomization ? t("application:fileManager.customizeColor") : ""}>
title={isCustomization ? t("application:fileManager.customizeColor") : ""} <ColorCircleBox size={size} onClick={onClick} color={displayColor} selected={selected} noMb={noMb}>
>
<ColorCircleBox
size={size}
onClick={onClick}
color={displayColor}
selected={selected}
noMb={noMb}
>
<ColorCircleBoxChild selected={selected} /> <ColorCircleBoxChild selected={selected} />
</ColorCircleBox> </ColorCircleBox>
</Tooltip> </Tooltip>
@ -124,9 +97,7 @@ export const ColorCircle = ({
const CircleColorSelector = (props: CircleColorSelectorProps) => { const CircleColorSelector = (props: CircleColorSelectorProps) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [customizeColor, setCustomizeColor] = useState<string>( const [customizeColor, setCustomizeColor] = useState<string>(props.selectedColor);
props.selectedColor,
);
const popupState = usePopupState({ const popupState = usePopupState({
variant: "popover", variant: "popover",
popupId: "color-picker", popupId: "color-picker",
@ -152,13 +123,8 @@ const CircleColorSelector = (props: CircleColorSelectorProps) => {
{props.colors.map((color) => ( {props.colors.map((color) => (
<ColorCircle <ColorCircle
noMb={props.showColorValueInCustomization} noMb={props.showColorValueInCustomization}
isCustomization={ isCustomization={color === customizeMagicColor && !props.showColorValueInCustomization}
color === customizeMagicColor && color={!props.showColorValueInCustomization ? color : props.selectedColor}
!props.showColorValueInCustomization
}
color={
!props.showColorValueInCustomization ? color : props.selectedColor
}
onClick={onClick(color)} onClick={onClick(color)}
selected={color === props.selectedColor} selected={color === props.selectedColor}
{...(color === customizeMagicColor && bindTrigger(popupState))} {...(color === customizeMagicColor && bindTrigger(popupState))}
@ -195,12 +161,7 @@ const CircleColorSelector = (props: CircleColorSelectorProps) => {
/> />
<Divider /> <Divider />
<Box sx={{ p: 1 }}> <Box sx={{ p: 1 }}>
<Button <Button size={"small"} onClick={onApply} fullWidth variant={"contained"}>
size={"small"}
onClick={onApply}
fullWidth
variant={"contained"}
>
{t("application:fileManager.apply")} {t("application:fileManager.apply")}
</Button> </Button>
</Box> </Box>

View File

@ -1,17 +1,8 @@
import { import { Box, BoxProps, Stack, styled, Typography, useTheme } from "@mui/material";
Box,
BoxProps,
Stack,
styled,
Typography,
useTheme,
} from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { FileResponse, Metadata } from "../../../api/explorer.ts"; import { FileResponse, Metadata } from "../../../api/explorer.ts";
import CircleColorSelector, { import CircleColorSelector, { customizeMagicColor } from "./ColorCircle/CircleColorSelector.tsx";
customizeMagicColor,
} from "./ColorCircle/CircleColorSelector.tsx";
import SessionManager, { UserSettings } from "../../../session"; import SessionManager, { UserSettings } from "../../../session";
import { defaultColors } from "../../../constants"; import { defaultColors } from "../../../constants";
@ -25,23 +16,16 @@ export interface FolderColorQuickActionProps extends BoxProps {
onColorChange: (color?: string) => void; onColorChange: (color?: string) => void;
} }
const FolderColorQuickAction = ({ const FolderColorQuickAction = ({ file, onColorChange, ...rest }: FolderColorQuickActionProps) => {
file,
onColorChange,
...rest
}: FolderColorQuickActionProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const [hex, setHex] = useState<string>( const [hex, setHex] = useState<string>(
(file.metadata && file.metadata[Metadata.icon_color]) ?? (file.metadata && file.metadata[Metadata.icon_color]) ?? theme.palette.action.active,
theme.palette.action.active,
); );
const presetColors = useMemo(() => { const presetColors = useMemo(() => {
const colors = new Set(defaultColors); const colors = new Set(defaultColors);
const recentColors = SessionManager.get( const recentColors = SessionManager.get(UserSettings.UsedCustomizedIconColors) as string[] | undefined;
UserSettings.UsedCustomizedIconColors,
) as string[] | undefined;
if (recentColors) { if (recentColors) {
recentColors.forEach((color) => { recentColors.forEach((color) => {
@ -54,20 +38,12 @@ const FolderColorQuickAction = ({
return ( return (
<StyledBox {...rest}> <StyledBox {...rest}>
<Stack spacing={1}> <Stack spacing={1}>
<Typography variant={"caption"}> <Typography variant={"caption"}>{t("application:fileManager.folderColor")}</Typography>
{t("application:fileManager.folderColor")}
</Typography>
<CircleColorSelector <CircleColorSelector
colors={[ colors={[theme.palette.action.active, ...presetColors, customizeMagicColor]}
theme.palette.action.active,
...presetColors,
customizeMagicColor,
]}
selectedColor={hex} selectedColor={hex}
onChange={(color) => { onChange={(color) => {
onColorChange( onColorChange(color == theme.palette.action.active ? undefined : color);
color == theme.palette.action.active ? undefined : color,
);
setHex(color); setHex(color);
}} }}
/> />

View File

@ -1,3 +1,3 @@
import { createContext } from "react"; import { createContext } from "react";
export const FmIndexContext = createContext(0); export const FmIndexContext = createContext(0);

View File

@ -13,9 +13,7 @@ const NewButton = () => {
if (isMobile) { if (isMobile) {
return ( return (
<IconButton <IconButton onClick={(e) => dispatch(openNewContextMenu(FileManagerIndex.main, e))}>
onClick={(e) => dispatch(openNewContextMenu(FileManagerIndex.main, e))}
>
<Add /> <Add />
</IconButton> </IconButton>
); );

View File

@ -1,12 +1,5 @@
import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; import { RadiusFrame } from "../../Frame/RadiusFrame.tsx";
import { import { Box, Pagination, Slide, styled, useMediaQuery, useTheme } from "@mui/material";
Box,
Pagination,
Slide,
styled,
useMediaQuery,
useTheme,
} from "@mui/material";
import { forwardRef, useContext } from "react"; import { forwardRef, useContext } from "react";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { MinPageSize } from "../TopBar/ViewOptionPopover.tsx"; import { MinPageSize } from "../TopBar/ViewOptionPopover.tsx";
@ -29,25 +22,20 @@ export interface PaginationState {
} }
export const usePaginationState = (fmIndex: number) => { export const usePaginationState = (fmIndex: number) => {
const pagination = useAppSelector( const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
(state) => state.fileManager[fmIndex].list?.pagination,
);
const totalItems = pagination?.total_items; const totalItems = pagination?.total_items;
const page = pagination?.page; const page = pagination?.page;
const pageSize = pagination?.page_size; const pageSize = pagination?.page_size;
const currentPage = (page ?? 0) + 1; const currentPage = (page ?? 0) + 1;
const totalPages = Math.ceil( const totalPages = Math.ceil((totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize));
(totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize),
);
const usePagination = totalPages > 1; const usePagination = totalPages > 1;
return { return {
currentPage, currentPage,
totalPages, totalPages,
usePagination, usePagination,
useEndlessLoading: !usePagination, useEndlessLoading: !usePagination,
moreItems: moreItems: pagination?.next_token || (usePagination && currentPage < totalPages),
pagination?.next_token || (usePagination && currentPage < totalPages),
nextToken: pagination?.next_token, nextToken: pagination?.next_token,
} as PaginationState; } as PaginationState;
}; };
@ -64,10 +52,7 @@ const PaginationFooter = forwardRef((_props, ref) => {
return ( return (
<Slide direction={"up"} unmountOnExit in={paginationState.usePagination}> <Slide direction={"up"} unmountOnExit in={paginationState.usePagination}>
<Box <Box ref={ref} sx={{ display: "flex", px: isMobile ? 1 : 0, pb: isMobile ? 1 : 0 }}>
ref={ref}
sx={{ display: "flex", px: isMobile ? 1 : 0, pb: isMobile ? 1 : 0 }}
>
<PaginationFrame withBorder> <PaginationFrame withBorder>
<Pagination <Pagination
renderItem={(item) => <PaginationItem {...item} />} renderItem={(item) => <PaginationItem {...item} />}

View File

@ -5,22 +5,15 @@ import { mergeRefs } from "../../../util";
let timeOut: ReturnType<typeof setTimeout> | undefined = undefined; let timeOut: ReturnType<typeof setTimeout> | undefined = undefined;
const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>( const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({
({ theme, isDropOver }) => ({ transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important", transitionProperty: "background-color,opacity,box-shadow",
transitionProperty: "background-color,opacity,box-shadow", boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
boxShadow: isDropOver }));
? `inset 0 0 0 2px ${theme.palette.primary.light}`
: "none",
}),
);
const CustomPaginationItem = (props: PaginationItemProps) => { const CustomPaginationItem = (props: PaginationItemProps) => {
const [drag, drop, isOver, isDragging] = useFileDrag({ const [drag, drop, isOver, isDragging] = useFileDrag({
dropUri: dropUri: props.type !== "start-ellipsis" && props.type !== "end-ellipsis" ? NoOpDropUri : undefined,
props.type !== "start-ellipsis" && props.type !== "end-ellipsis"
? NoOpDropUri
: undefined,
}); });
const buttonRef = useRef<HTMLElement>(); const buttonRef = useRef<HTMLElement>();
@ -47,9 +40,7 @@ const CustomPaginationItem = (props: PaginationItemProps) => {
[drop, buttonRef], [drop, buttonRef],
); );
return ( return <StyledPaginationItem isDropOver={isOver} ref={mergedRef} {...props} />;
<StyledPaginationItem isDropOver={isOver} ref={mergedRef} {...props} />
);
}; };
export default CustomPaginationItem; export default CustomPaginationItem;

View File

@ -2,11 +2,7 @@ import { useTranslation } from "react-i18next";
import { SecondaryButton } from "../../../Common/StyledComponents.tsx"; import { SecondaryButton } from "../../../Common/StyledComponents.tsx";
import Add from "../../../Icons/Add.tsx"; import Add from "../../../Icons/Add.tsx";
import { import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
bindMenu,
bindTrigger,
usePopupState,
} from "material-ui-popup-state/hooks";
import { ListItemIcon, ListItemText, Menu } from "@mui/material"; import { ListItemIcon, ListItemText, Menu } from "@mui/material";
import { Condition, ConditionType } from "./ConditionBox.tsx"; import { Condition, ConditionType } from "./ConditionBox.tsx";
import React from "react"; import React from "react";
@ -154,20 +150,13 @@ const AddCondition = (props: AddConditionProps) => {
const onConditionAdd = (condition: Condition) => { const onConditionAdd = (condition: Condition) => {
props.onConditionAdd({ props.onConditionAdd({
...condition, ...condition,
id: id: condition.type == ConditionType.metadata && !condition.id ? Math.random().toString() : condition.id,
condition.type == ConditionType.metadata && !condition.id
? Math.random().toString()
: condition.id,
}); });
onClose(); onClose();
}; };
return ( return (
<> <>
<SecondaryButton <SecondaryButton {...bindTrigger(conditionPopupState)} startIcon={<Add />} sx={{ px: "15px" }}>
{...bindTrigger(conditionPopupState)}
startIcon={<Add />}
sx={{ px: "15px" }}
>
{t("navbar.addCondition")} {t("navbar.addCondition")}
</SecondaryButton> </SecondaryButton>
<Menu <Menu
@ -183,11 +172,7 @@ const AddCondition = (props: AddConditionProps) => {
{...menuProps} {...menuProps}
> >
{options.map((option, index) => ( {options.map((option, index) => (
<SquareMenuItem <SquareMenuItem dense key={index} onClick={() => onConditionAdd(option.condition)}>
dense
key={index}
onClick={() => onConditionAdd(option.condition)}
>
<ListItemIcon>{option.icon}</ListItemIcon> <ListItemIcon>{option.icon}</ListItemIcon>
{t(option.name)} {t(option.name)}
</SquareMenuItem> </SquareMenuItem>
@ -198,14 +183,12 @@ const AddCondition = (props: AddConditionProps) => {
title={t("application:fileManager.mediaInfo")} title={t("application:fileManager.mediaInfo")}
> >
{mediaMetaOptions.map((option, index) => ( {mediaMetaOptions.map((option, index) => (
<SquareMenuItem <SquareMenuItem key={index} dense onClick={() => onConditionAdd(option.condition)}>
key={index} <ListItemText
dense slotProps={{
onClick={() => onConditionAdd(option.condition)} primary: { variant: "body2" },
> }}
<ListItemText slotProps={{ >
primary: { variant: "body2" }
}}>
{t(option.name)} {t(option.name)}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>

View File

@ -15,10 +15,7 @@ import { defaultPath } from "../../../../hooks/useNavigation.tsx";
import { advancedSearch } from "../../../../redux/thunks/filemanager.ts"; import { advancedSearch } from "../../../../redux/thunks/filemanager.ts";
import { Metadata } from "../../../../api/explorer.ts"; import { Metadata } from "../../../../api/explorer.ts";
const searchParamToConditions = ( const searchParamToConditions = (search_params: SearchParam, base: string): Condition[] => {
search_params: SearchParam,
base: string,
): Condition[] => {
const applied: Condition[] = [ const applied: Condition[] = [
{ {
type: ConditionType.base, type: ConditionType.base,
@ -41,10 +38,7 @@ const searchParamToConditions = (
}); });
} }
if ( if (search_params.size_gte != undefined || search_params.size_lte != undefined) {
search_params.size_gte != undefined ||
search_params.size_lte != undefined
) {
applied.push({ applied.push({
type: ConditionType.size, type: ConditionType.size,
size_gte: search_params.size_gte, size_gte: search_params.size_gte,
@ -52,10 +46,7 @@ const searchParamToConditions = (
}); });
} }
if ( if (search_params.created_at_gte != undefined || search_params.created_at_lte != undefined) {
search_params.created_at_gte != undefined ||
search_params.created_at_lte != undefined
) {
applied.push({ applied.push({
type: ConditionType.created, type: ConditionType.created,
created_gte: search_params.created_at_gte, created_gte: search_params.created_at_gte,
@ -63,10 +54,7 @@ const searchParamToConditions = (
}); });
} }
if ( if (search_params.updated_at_gte != undefined || search_params.updated_at_lte != undefined) {
search_params.updated_at_gte != undefined ||
search_params.updated_at_lte != undefined
) {
applied.push({ applied.push({
type: ConditionType.modified, type: ConditionType.modified,
updated_gte: search_params.updated_at_gte, updated_gte: search_params.updated_at_gte,
@ -106,18 +94,10 @@ const AdvanceSearch = () => {
const [conditions, setConditions] = useState<Condition[]>([]); const [conditions, setConditions] = useState<Condition[]>([]);
const open = useAppSelector((state) => state.globalState.advanceSearchOpen); const open = useAppSelector((state) => state.globalState.advanceSearchOpen);
const base = useAppSelector( const base = useAppSelector((state) => state.globalState.advanceSearchBasePath);
(state) => state.globalState.advanceSearchBasePath, const initialNames = useAppSelector((state) => state.globalState.advanceSearchInitialNameCondition);
); const search_params = useAppSelector((state) => state.fileManager[FileManagerIndex.main].search_params);
const initialNames = useAppSelector( const current_base = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path);
(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(() => { const onClose = useCallback(() => {
dispatch(closeAdvanceSearch()); dispatch(closeAdvanceSearch());
@ -141,10 +121,7 @@ const AdvanceSearch = () => {
} }
if (search_params) { if (search_params) {
const existedConditions = searchParamToConditions( const existedConditions = searchParamToConditions(search_params, current_base ?? defaultPath);
search_params,
current_base ?? defaultPath,
);
if (existedConditions.length > 0) { if (existedConditions.length > 0) {
setConditions(existedConditions); setConditions(existedConditions);
} }
@ -157,9 +134,7 @@ const AdvanceSearch = () => {
}; };
const onConditionAdd = (condition: Condition) => { const onConditionAdd = (condition: Condition) => {
if ( if (conditions.find((c) => c.type === condition.type && c.id === condition.id)) {
conditions.find((c) => c.type === condition.type && c.id === condition.id)
) {
enqueueSnackbar(t("application:navbar.conditionDuplicate"), { enqueueSnackbar(t("application:navbar.conditionDuplicate"), {
variant: "warning", variant: "warning",
action: DefaultCloseAction, action: DefaultCloseAction,
@ -194,11 +169,7 @@ const AdvanceSearch = () => {
<Collapse key={`${condition.type} ${condition.id}`}> <Collapse key={`${condition.type} ${condition.id}`}>
<ConditionBox <ConditionBox
index={index} index={index}
onRemove={ onRemove={conditions.length > 2 && condition.type != ConditionType.base ? onConditionRemove : undefined}
conditions.length > 2 && condition.type != ConditionType.base
? onConditionRemove
: undefined
}
condition={condition} condition={condition}
onChange={(condition) => { onChange={(condition) => {
const new_conditions = [...conditions]; const new_conditions = [...conditions];

View File

@ -137,49 +137,25 @@ const ConditionBox = forwardRef((props: ConditionProps, ref) => {
<Icon sx={{ width: "20px", height: "20px" }} /> <Icon sx={{ width: "20px", height: "20px" }} />
<Box sx={{ flexGrow: 1 }}>{title}</Box> <Box sx={{ flexGrow: 1 }}>{title}</Box>
<Grow in={hovered && !!onRemove}> <Grow in={hovered && !!onRemove}>
<IconButton <IconButton onClick={onRemove ? () => onRemove(condition) : undefined}>
onClick={onRemove ? () => onRemove(condition) : undefined}
>
<Dismiss fontSize={"small"} /> <Dismiss fontSize={"small"} />
</IconButton> </IconButton>
</Grow> </Grow>
</Typography> </Typography>
<Box> <Box>
{condition.type == ConditionType.name && ( {condition.type == ConditionType.name && (
<FileNameCondition <FileNameCondition condition={condition} onChange={onChange} onNameConditionAdded={onNameConditionAdded} />
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.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 && ( {condition.type == ConditionType.created && (
<DateTimeCondition <DateTimeCondition condition={condition} onChange={onChange} field={"created"} />
condition={condition}
onChange={onChange}
field={"created"}
/>
)} )}
{condition.type == ConditionType.modified && ( {condition.type == ConditionType.modified && (
<DateTimeCondition <DateTimeCondition condition={condition} onChange={onChange} field={"updated"} />
condition={condition}
onChange={onChange}
field={"updated"}
/>
)} )}
</Box> </Box>
</StyledBox> </StyledBox>

View File

@ -1,15 +1,6 @@
import { import { Autocomplete, Box, Chip, FormControlLabel, styled } from "@mui/material";
Autocomplete,
Box,
Chip,
FormControlLabel,
styled,
} from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { FilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
FilledTextField,
StyledCheckbox,
} from "../../../Common/StyledComponents.tsx";
import { Condition } from "./ConditionBox.tsx"; import { Condition } from "./ConditionBox.tsx";
export const StyledBox = styled(Box)(({ theme }) => ({ export const StyledBox = styled(Box)(({ theme }) => ({
@ -41,9 +32,7 @@ export const FileNameCondition = ({
renderTags={(value: readonly string[], getTagProps) => renderTags={(value: readonly string[], getTagProps) =>
value.map((option: string, index: number) => { value.map((option: string, index: number) => {
const { key, ...tagProps } = getTagProps({ index }); const { key, ...tagProps } = getTagProps({ index });
return ( return <Chip variant="outlined" label={option} key={key} {...tagProps} />;
<Chip variant="outlined" label={option} key={key} {...tagProps} />
);
}) })
} }
renderInput={(params) => ( renderInput={(params) => (

View File

@ -31,9 +31,11 @@ export const FileTypeCondition = ({
<ListItemIcon> <ListItemIcon>
<Document fontSize="small" /> <Document fontSize="small" />
</ListItemIcon> </ListItemIcon>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t("application:fileManager.file")} {t("application:fileManager.file")}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>
@ -41,9 +43,11 @@ export const FileTypeCondition = ({
<ListItemIcon> <ListItemIcon>
<Folder fontSize="small" /> <Folder fontSize="small" />
</ListItemIcon> </ListItemIcon>
<ListItemText slotProps={{ <ListItemText
primary: { variant: "body2" } slotProps={{
}}> primary: { variant: "body2" },
}}
>
{t("application:fileManager.folder")} {t("application:fileManager.folder")}
</ListItemText> </ListItemText>
</SquareMenuItem> </SquareMenuItem>

View File

@ -22,9 +22,7 @@ export const MetadataCondition = ({
variant="filled" variant="filled"
label={t("application:fileManager.metadataKey")} label={t("application:fileManager.metadataKey")}
value={condition.metadata_key ?? ""} value={condition.metadata_key ?? ""}
onChange={(e) => onChange={(e) => onChange({ ...condition, metadata_key: e.target.value })}
onChange({ ...condition, metadata_key: e.target.value })
}
disabled={condition.metadata_key_readonly} disabled={condition.metadata_key_readonly}
type="text" type="text"
fullWidth fullWidth
@ -33,9 +31,7 @@ export const MetadataCondition = ({
variant="filled" variant="filled"
label={t("application:fileManager.metadataValue")} label={t("application:fileManager.metadataValue")}
value={condition.metadata_value ?? ""} value={condition.metadata_value ?? ""}
onChange={(e) => onChange={(e) => onChange({ ...condition, metadata_value: e.target.value })}
onChange({ ...condition, metadata_value: e.target.value })
}
type="text" type="text"
fullWidth fullWidth
/> />

View File

@ -23,9 +23,7 @@ export const TagCondition = ({
renderTags={(value: readonly string[], getTagProps) => renderTags={(value: readonly string[], getTagProps) =>
value.map((option: string, index: number) => { value.map((option: string, index: number) => {
const { key, ...tagProps } = getTagProps({ index }); const { key, ...tagProps } = getTagProps({ index });
return ( return <Chip variant="outlined" label={option} key={key} {...tagProps} />;
<Chip variant="outlined" label={option} key={key} {...tagProps} />
);
}) })
} }
renderInput={(params) => ( renderInput={(params) => (

View File

@ -1,11 +1,4 @@
import { import { Box, List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
Box,
List,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
} from "@mui/material";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
// @ts-ignore // @ts-ignore
@ -27,8 +20,7 @@ const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onClick = useCallback( const onClick = useCallback(
(base: string) => () => (base: string) => () => dispatch(quickSearch(FileManagerIndex.main, base, keyword)),
dispatch(quickSearch(FileManagerIndex.main, base, keyword)),
[keyword, dispatch], [keyword, dispatch],
); );
@ -56,15 +48,13 @@ const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => {
values={{ values={{
keywords: keyword, keywords: keyword,
}} }}
components={[ components={[<Box component={"span"} sx={{ fontWeight: 600 }} />]}
<Box component={"span"} sx={{ fontWeight: 600 }} />,
]}
/> />
} }
slotProps={{ slotProps={{
primary: { primary: {
variant: "body2", variant: "body2",
} },
}} }}
/> />
<FileBadge <FileBadge

View File

@ -1,11 +1,5 @@
import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts"; import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts";
import { import { List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
List,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
} from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { sizeToString } from "../../../util"; import { sizeToString } from "../../../util";
import FileIcon from "../Explorer/FileIcon.tsx"; import FileIcon from "../Explorer/FileIcon.tsx";
@ -49,15 +43,7 @@ const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => {
<ListItemButton <ListItemButton
sx={{ py: 0 }} sx={{ py: 0 }}
onClick={(e) => onClick={(e) =>
dispatch( dispatch(openFileContextMenu(FileManagerIndex.main, file, true, e, ContextMenuTypes.searchResult))
openFileContextMenu(
FileManagerIndex.main,
file,
true,
e,
ContextMenuTypes.searchResult,
),
)
} }
> >
<ListItemAvatar sx={{ minWidth: 48 }}> <ListItemAvatar sx={{ minWidth: 48 }}>
@ -91,8 +77,9 @@ const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => {
secondary: { secondary: {
variant: "body2", variant: "body2",
} },
}} /> }}
/>
<FileBadge <FileBadge
clickable clickable
variant={"outlined"} variant={"outlined"}

View File

@ -2,21 +2,10 @@ import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { useContext, useMemo } from "react"; import { useContext, useMemo } from "react";
import { FmIndexContext } from "../FmIndexContext.tsx"; import { FmIndexContext } from "../FmIndexContext.tsx";
import { import { alpha, Button, ButtonGroup, Grow, styled, useMediaQuery, useTheme } from "@mui/material";
alpha,
Button,
ButtonGroup,
Grow,
styled,
useMediaQuery,
useTheme,
} from "@mui/material";
import Search from "../../Icons/Search.tsx"; import Search from "../../Icons/Search.tsx";
import Dismiss from "../../Icons/Dismiss.tsx"; import Dismiss from "../../Icons/Dismiss.tsx";
import { import { clearSearch, openAdvancedSearch } from "../../../redux/thunks/filemanager.ts";
clearSearch,
openAdvancedSearch,
} from "../../../redux/thunks/filemanager.ts";
import { FileManagerIndex } from "../FileManager.tsx"; import { FileManagerIndex } from "../FileManager.tsx";
export const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({ export const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({
@ -44,9 +33,7 @@ export const SearchIndicator = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const search_params = useAppSelector( const search_params = useAppSelector((state) => state.fileManager[fmIndex].search_params);
(state) => state.fileManager[fmIndex].search_params,
);
const searchConditionsCount = useMemo(() => { const searchConditionsCount = useMemo(() => {
if (!search_params) { if (!search_params) {
@ -90,10 +77,7 @@ export const SearchIndicator = () => {
num: searchConditionsCount, num: searchConditionsCount,
})} })}
</StyledButton> </StyledButton>
<StyledButton <StyledButton size={"small"} onClick={() => dispatch(clearSearch(fmIndex))}>
size={"small"}
onClick={() => dispatch(clearSearch(fmIndex))}
>
<Dismiss fontSize={"small"} sx={{ width: 16, height: 16 }} /> <Dismiss fontSize={"small"} sx={{ width: 16, height: 16 }} />
</StyledButton> </StyledButton>
</StyledButtonGroup> </StyledButtonGroup>

View File

@ -27,10 +27,7 @@ import SessionManager from "../../../session";
import { defaultPath } from "../../../hooks/useNavigation.tsx"; import { defaultPath } from "../../../hooks/useNavigation.tsx";
import FullSearchOption from "./FullSearchOptions.tsx"; import FullSearchOption from "./FullSearchOptions.tsx";
import { TransitionProps } from "@mui/material/transitions"; import { TransitionProps } from "@mui/material/transitions";
import { import { openAdvancedSearch, quickSearch } from "../../../redux/thunks/filemanager.ts";
openAdvancedSearch,
quickSearch,
} from "../../../redux/thunks/filemanager.ts";
import Options from "../../Icons/Options.tsx"; import Options from "../../Icons/Options.tsx";
const StyledDialog = styled(Dialog)<{ const StyledDialog = styled(Dialog)<{
@ -66,9 +63,7 @@ const SearchPopup = () => {
const [keywords, setKeywords] = useState(""); const [keywords, setKeywords] = useState("");
const [searchedKeyword, setSearchedKeyword] = useState(""); const [searchedKeyword, setSearchedKeyword] = useState("");
const [treeSearchResults, setTreeSearchResults] = useState<FileResponse[]>( const [treeSearchResults, setTreeSearchResults] = useState<FileResponse[]>([]);
[],
);
const onClose = () => { const onClose = () => {
dispatch(setSearchPopup(false)); dispatch(setSearchPopup(false));
@ -77,48 +72,36 @@ const SearchPopup = () => {
}; };
const open = useAppSelector((state) => state.globalState.searchPopupOpen); const open = useAppSelector((state) => state.globalState.searchPopupOpen);
const tree = useAppSelector( const tree = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.tree);
(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 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( const searchTree = useMemo(
() => () =>
debounce( debounce((request: { input: string }, callback: (results?: FileResponse[]) => void) => {
( const options = {
request: { input: string }, includeScore: true,
callback: (results?: FileResponse[]) => void, // Search in `author` and in `tags` array
) => { keys: ["file.name"],
const options = { };
includeScore: true, const fuse = new Fuse(Object.values(tree), options);
// Search in `author` and in `tags` array const result = fuse.search(
keys: ["file.name"], request.input
}; .split(" ")
const fuse = new Fuse(Object.values(tree), options); .filter((k) => k != "")
const result = fuse.search( .join(" "),
request.input { limit: 50 },
.split(" ") );
.filter((k) => k != "") const res: FileResponse[] = [];
.join(" "), result
{ limit: 50 }, .filter((r) => r.item.file != undefined)
); .forEach((r) => {
const res: FileResponse[] = []; if (r.item.file) {
result res.push(r.item.file);
.filter((r) => r.item.file != undefined) }
.forEach((r) => { });
if (r.item.file) { callback(res);
res.push(r.item.file); }, 400),
}
});
callback(res);
},
400,
),
[tree], [tree],
); );
@ -158,10 +141,7 @@ const SearchPopup = () => {
res.push(current.base()); res.push(current.base());
} }
// my files - user login and not my fs // my files - user login and not my fs
if ( if (SessionManager.currentLoginOrNull() && !(current.fs() == Filesystem.my)) {
SessionManager.currentLoginOrNull() &&
!(current.fs() == Filesystem.my)
) {
res.push(defaultPath); res.push(defaultPath);
} }
return res; return res;
@ -173,9 +153,7 @@ const SearchPopup = () => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (fullSearchOptions.length > 0) { if (fullSearchOptions.length > 0) {
dispatch( dispatch(quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords));
quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords),
);
} }
} }
}, },
@ -214,9 +192,7 @@ const SearchPopup = () => {
height: 40, height: 40,
mr: 1.5, mr: 1.5,
}} }}
onClick={() => onClick={() => dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))}
dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))
}
> >
<Options /> <Options />
</IconButton> </IconButton>
@ -244,10 +220,7 @@ const SearchPopup = () => {
> >
{t("navbar.searchFilesTitle")} {t("navbar.searchFilesTitle")}
</Typography> </Typography>
<FullSearchOption <FullSearchOption keyword={keywords} options={fullSearchOptions} />
keyword={keywords}
options={fullSearchOptions}
/>
{treeSearchResults.length > 0 && <Divider />} {treeSearchResults.length > 0 && <Divider />}
</> </>
)} )}
@ -264,10 +237,7 @@ const SearchPopup = () => {
> >
{t("navbar.recentlyViewed")} {t("navbar.recentlyViewed")}
</Typography> </Typography>
<FuzzySearchResult <FuzzySearchResult keyword={searchedKeyword} files={treeSearchResults} />
keyword={searchedKeyword}
files={treeSearchResults}
/>
</> </>
)} )}
</AutoHeight> </AutoHeight>

View File

@ -14,13 +14,7 @@ export interface DetailsProps {
target: FileResponse; target: FileResponse;
} }
const InfoBlock = ({ const InfoBlock = ({ title, children }: { title: string; children: React.ReactNode }) => {
title,
children,
}: {
title: string;
children: React.ReactNode;
}) => {
return ( return (
<Box> <Box>
<Typography variant={"body2"}>{title}</Typography> <Typography variant={"body2"}>{title}</Typography>
@ -35,11 +29,7 @@ const Details = ({ target, inPhotoViewer }: DetailsProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [thumbSrc, setThumbSrc] = useState<string | null>(null); const [thumbSrc, setThumbSrc] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if ( if (target.type == FileType.file && (!target.metadata || target.metadata[Metadata.thumbDisabled] === undefined)) {
target.type == FileType.file &&
(!target.metadata ||
target.metadata[Metadata.thumbDisabled] === undefined)
) {
dispatch(loadFileThumb(FileManagerIndex.main, target)).then((src) => { dispatch(loadFileThumb(FileManagerIndex.main, target)).then((src) => {
setThumbSrc(src); setThumbSrc(src);
}); });

View File

@ -12,21 +12,10 @@ const Header = ({ target }: HeaderProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
return ( return (
<Box sx={{ display: "flex", p: 2 }}> <Box sx={{ display: "flex", p: 2 }}>
{target !== null && ( {target !== null && <FileIcon sx={{ p: 0 }} loading={target == undefined} file={target} type={target?.type} />}
<FileIcon
sx={{ p: 0 }}
loading={target == undefined}
file={target}
type={target?.type}
/>
)}
{target !== null && ( {target !== null && (
<Box sx={{ flexGrow: 1, ml: 1.5 }}> <Box sx={{ flexGrow: 1, ml: 1.5 }}>
<Typography <Typography color="textPrimary" sx={{ wordBreak: "break-all" }} variant={"subtitle2"}>
color="textPrimary"
sx={{ wordBreak: "break-all" }}
variant={"subtitle2"}
>
{target && target.name} {target && target.name}
{!target && <Skeleton variant={"text"} width={75} />} {!target && <Skeleton variant={"text"} width={75} />}
</Typography> </Typography>

View File

@ -12,23 +12,11 @@ export interface MapLoaderProps extends BoxProps {
} }
const MapLoader = (props: MapLoaderProps) => { const MapLoader = (props: MapLoaderProps) => {
const mapProvider = useAppSelector( const mapProvider = useAppSelector((state) => state.siteConfig.explorer.config.map_provider);
(state) => state.siteConfig.explorer.config.map_provider, const googleTileType = useAppSelector((state) => state.siteConfig.explorer.config.google_map_tile_type);
);
const googleTileType = useAppSelector(
(state) => state.siteConfig.explorer.config.google_map_tile_type,
);
return ( return (
<Suspense <Suspense fallback={<Skeleton variant="rounded" width={"100%"} height={props.height} />}>
fallback={ <MapBox mapProvider={mapProvider} googleTileType={googleTileType} {...props} />
<Skeleton variant="rounded" width={"100%"} height={props.height} />
}
>
<MapBox
mapProvider={mapProvider}
googleTileType={googleTileType}
{...props}
/>
</Suspense> </Suspense>
); );
}; };

View File

@ -37,14 +37,8 @@ export interface MediaMetaCardProps {
} }
const StyledButtonBase = styled(Box)(({ theme }) => { const StyledButtonBase = styled(Box)(({ theme }) => {
let bgColor = let bgColor = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
theme.palette.mode === "light" let bgColorHover = theme.palette.mode === "light" ? theme.palette.grey[300] : theme.palette.grey[700];
? theme.palette.grey[100]
: theme.palette.grey[900];
let bgColorHover =
theme.palette.mode === "light"
? theme.palette.grey[300]
: theme.palette.grey[700];
return { return {
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
backgroundColor: bgColor, backgroundColor: bgColor,
@ -69,21 +63,12 @@ export interface MediaMetaElementsProps extends LinkProps {
element: MediaMetaElements; element: MediaMetaElements;
} }
export const MediaMetaElements = ({ export const MediaMetaElements = ({ element, ...rest }: MediaMetaElementsProps) => {
element,
...rest
}: MediaMetaElementsProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const handleSearch = () => { const handleSearch = () => {
dispatch( dispatch(searchMetadata(FileManagerIndex.main, element.searchKey, element.searchValue));
searchMetadata(
FileManagerIndex.main,
element.searchKey,
element.searchValue,
),
);
handleClose(); handleClose();
}; };
@ -113,18 +98,10 @@ export const MediaMetaElements = ({
<ListItemIcon> <ListItemIcon>
<Clipboard fontSize="small" /> <Clipboard fontSize="small" />
</ListItemIcon> </ListItemIcon>
<ListItemText> <ListItemText>{t("application:fileManager.copyToClipboard")}</ListItemText>
{t("application:fileManager.copyToClipboard")}
</ListItemText>
</SquareMenuItem> </SquareMenuItem>
</Menu> </Menu>
<Link <Link color="inherit" href={"#"} onClick={(e) => setAnchorEl(e.currentTarget)} underline="hover" {...rest}>
color="inherit"
href={"#"}
onClick={(e) => setAnchorEl(e.currentTarget)}
underline="hover"
{...rest}
>
{element.display} {element.display}
</Link> </Link>
</> </>
@ -147,26 +124,14 @@ const MediaMetaCard = ({ contents, icon }: MediaMetaCardProps) => {
> >
{contents.map(({ title, content }) => ( {contents.map(({ title, content }) => (
<Box> <Box>
<Typography <Typography variant={"body2"} color="textPrimary" fontWeight={500}>
variant={"body2"}
color="textPrimary"
fontWeight={500}
>
{title.map((element) => {title.map((element) =>
typeof element === "string" ? ( typeof element === "string" ? element : <MediaMetaElements element={element} />,
element
) : (
<MediaMetaElements element={element} />
),
)} )}
</Typography> </Typography>
<Typography variant={"body2"} color={"text.secondary"}> <Typography variant={"body2"} color={"text.secondary"}>
{content.map((element) => {content.map((element) =>
typeof element === "string" ? ( typeof element === "string" ? element : <MediaMetaElements element={element} />,
element
) : (
<MediaMetaElements element={element} />
),
)} )}
</Typography> </Typography>
</Box> </Box>

View File

@ -13,13 +13,9 @@ export interface SideBarProps {
const Sidebar = ({ inPhotoViewer }: SideBarProps) => { const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen); const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
const sidebarTarget = useAppSelector( const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget);
(state) => state.globalState.sidebarTarget,
);
// null: not valid, undefined: not loaded, FileResponse: loaded // null: not valid, undefined: not loaded, FileResponse: loaded
const [target, setTarget] = useState<FileResponse | undefined | null>( const [target, setTarget] = useState<FileResponse | undefined | null>(undefined);
undefined,
);
const loadExtendedInfo = useCallback( const loadExtendedInfo = useCallback(
(path: string) => { (path: string) => {
@ -72,8 +68,7 @@ const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
width: "300px", width: "300px",
height: "100%", height: "100%",
ml: 1, ml: 1,
borderRadius: (theme) => borderRadius: (theme) => (inPhotoViewer ? 0 : theme.shape.borderRadius / 8),
inPhotoViewer ? 0 : theme.shape.borderRadius / 8,
}} }}
withBorder={!inPhotoViewer} withBorder={!inPhotoViewer}
> >

View File

@ -20,13 +20,9 @@ const Transition = forwardRef(function Transition(
const SidebarDialog = ({ inPhotoViewer }: SideBarProps) => { const SidebarDialog = ({ inPhotoViewer }: SideBarProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen); const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
const sidebarTarget = useAppSelector( const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget);
(state) => state.globalState.sidebarTarget,
);
// null: not valid, undefined: not loaded, FileResponse: loaded // null: not valid, undefined: not loaded, FileResponse: loaded
const [target, setTarget] = useState<FileResponse | undefined | null>( const [target, setTarget] = useState<FileResponse | undefined | null>(undefined);
undefined,
);
const loadExtendedInfo = useCallback( const loadExtendedInfo = useCallback(
(path: string) => { (path: string) => {

View File

@ -37,12 +37,7 @@ const Tags = ({ target }: TagsProps) => {
return ( return (
<> <>
<Typography <Typography sx={{ pt: 1 }} color="textPrimary" fontWeight={500} variant={"subtitle1"}>
sx={{ pt: 1 }}
color="textPrimary"
fontWeight={500}
variant={"subtitle1"}
>
{t("application:fileManager.tags")} {t("application:fileManager.tags")}
</Typography> </Typography>
<Box> <Box>

View File

@ -1,14 +1,5 @@
import { import { BreadcrumbButtonProps, useBreadcrumbButtons } from "./BreadcrumbButton.tsx";
BreadcrumbButtonProps, import { ListItemIcon, ListItemText, MenuItem, Skeleton, styled } from "@mui/material";
useBreadcrumbButtons,
} from "./BreadcrumbButton.tsx";
import {
ListItemIcon,
ListItemText,
MenuItem,
Skeleton,
styled,
} from "@mui/material";
import { useContext, useMemo } from "react"; import { useContext, useMemo } from "react";
import { useAppSelector } from "../../../redux/hooks.ts"; import { useAppSelector } from "../../../redux/hooks.ts";
import FileIcon from "../Explorer/FileIcon.tsx"; import FileIcon from "../Explorer/FileIcon.tsx";
@ -20,24 +11,13 @@ export interface BreadcrumbHiddenItem extends BreadcrumbButtonProps {
onClose: () => void; onClose: () => void;
} }
export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>( export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({
({ theme, isDropOver }) => ({ transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important", transitionProperty: "background-color,opacity,box-shadow",
transitionProperty: "background-color,opacity,box-shadow", boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
boxShadow: isDropOver }));
? `inset 0 0 0 2px ${theme.palette.primary.light}`
: "none",
}),
);
const BreadcrumbHiddenItem = ({ const BreadcrumbHiddenItem = ({ name, is_root, is_latest, path, onClose, ...rest }: BreadcrumbHiddenItem) => {
name,
is_root,
is_latest,
path,
onClose,
...rest
}: BreadcrumbHiddenItem) => {
const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({ const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({
name, name,
is_latest, is_latest,
@ -70,12 +50,7 @@ const BreadcrumbHiddenItem = ({
}); });
return ( return (
<StyledMenuItem <StyledMenuItem isDropOver={isOver} ref={drop} {...rest} onClick={onItemClick}>
isDropOver={isOver}
ref={drop}
{...rest}
onClick={onItemClick}
>
<ListItemIcon> <ListItemIcon>
{StartIcon ? ( {StartIcon ? (
StartIcon StartIcon
@ -91,9 +66,7 @@ const BreadcrumbHiddenItem = ({
/> />
)} )}
</ListItemIcon> </ListItemIcon>
<ListItemText> <ListItemText>{loading ? <Skeleton variant={"text"} width={75} /> : displayName}</ListItemText>
{loading ? <Skeleton variant={"text"} width={75} /> : displayName}
</ListItemText>
</StyledMenuItem> </StyledMenuItem>
); );
}; };

View File

@ -1,16 +1,6 @@
import { import { Button, ButtonGroup, styled, useMediaQuery, useTheme } from "@mui/material";
Button,
ButtonGroup,
styled,
useMediaQuery,
useTheme,
} from "@mui/material";
import { bindPopover } from "material-ui-popup-state"; import { bindPopover } from "material-ui-popup-state";
import { import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
bindMenu,
bindTrigger,
usePopupState,
} from "material-ui-popup-state/hooks";
import { useContext } from "react"; import { useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppSelector } from "../../../redux/hooks.ts"; import { useAppSelector } from "../../../redux/hooks.ts";
@ -42,12 +32,8 @@ const TopActions = () => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const sortOptions = useAppSelector( const sortOptions = useAppSelector((state) => state.fileManager[fmIndex].list?.props.order_by_options);
(state) => state.fileManager[fmIndex].list?.props.order_by_options, const isSingleFileView = useAppSelector((state) => state.fileManager[fmIndex].list?.single_file_view);
);
const isSingleFileView = useAppSelector(
(state) => state.fileManager[fmIndex].list?.single_file_view,
);
const viewPopupState = usePopupState({ const viewPopupState = usePopupState({
variant: "popover", variant: "popover",
popupId: "viewOption", popupId: "viewOption",
@ -68,11 +54,7 @@ const TopActions = () => {
{...bindTrigger(viewPopupState)} {...bindTrigger(viewPopupState)}
startIcon={!isMobile && <TableSettingsOutlined />} startIcon={!isMobile && <TableSettingsOutlined />}
> >
{isMobile ? ( {isMobile ? <TableSettingsOutlined fontSize={"small"} /> : t("application:fileManager.view")}
<TableSettingsOutlined fontSize={"small"} />
) : (
t("application:fileManager.view")
)}
</ActionButton> </ActionButton>
{(!(!sortOptions || isSingleFileView) || !isMobile) && ( {(!(!sortOptions || isSingleFileView) || !isMobile) && (
<ActionButton <ActionButton
@ -80,11 +62,7 @@ const TopActions = () => {
startIcon={!isMobile && <ArrowSort />} startIcon={!isMobile && <ArrowSort />}
{...bindTrigger(sortPopupState)} {...bindTrigger(sortPopupState)}
> >
{isMobile ? ( {isMobile ? <ArrowSort fontSize={"small"} /> : t("application:fileManager.sortMethod")}
<ArrowSort fontSize={"small"} />
) : (
t("application:fileManager.sortMethod")
)}
</ActionButton> </ActionButton>
)} )}
{isMobile && ( {isMobile && (

View File

@ -14,12 +14,8 @@ interface PinnedItem {
export const usePinned = () => { export const usePinned = () => {
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const generation = useAppSelector( const generation = useAppSelector((state) => state.globalState.pinedGeneration);
(state) => state.globalState.pinedGeneration, const path_root = useAppSelector((state) => state.fileManager[fmIndex].path_root);
);
const path_root = useAppSelector(
(state) => state.fileManager[fmIndex].path_root,
);
const pined = useMemo(() => { const pined = useMemo(() => {
try { try {
return SessionManager.currentLogin().user.pined?.map((p): PinnedItem => { return SessionManager.currentLogin().user.pined?.map((p): PinnedItem => {
@ -38,9 +34,7 @@ export const usePinned = () => {
const Pinned = memo(() => { const Pinned = memo(() => {
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const elements = useAppSelector( const elements = useAppSelector((state) => state.fileManager[fmIndex].path_elements);
(state) => state.fileManager[fmIndex].path_elements,
);
const pined = usePinned(); const pined = usePinned();
return ( return (
@ -50,14 +44,9 @@ const Pinned = memo(() => {
sx={{ mt: index == 0 ? 2 : 0 }} sx={{ mt: index == 0 ? 2 : 0 }}
flatten={ flatten={
p.crUri.fs() != Filesystem.share || p.crUri.fs() != Filesystem.share ||
(p.crUri.fs() == Filesystem.share && (p.crUri.fs() == Filesystem.share && (!p.crUri.is_root() || p.crUri.is_search()))
(!p.crUri.is_root() || p.crUri.is_search()))
}
canDrop={
(p.crUri.fs() == Filesystem.share ||
p.crUri.fs() == Filesystem.my) &&
!p.crUri.is_search()
} }
canDrop={(p.crUri.fs() == Filesystem.share || p.crUri.fs() == Filesystem.my) && !p.crUri.is_search()}
level={0} level={0}
labelOverwrite={p.name} labelOverwrite={p.name}
path={p.uri} path={p.uri}

View File

@ -5,20 +5,11 @@ import { SideNavItemBase } from "../../Frame/NavBar/SideNavItem.tsx";
import clsx from "clsx"; import clsx from "clsx";
import FileIcon from "../Explorer/FileIcon.tsx"; import FileIcon from "../Explorer/FileIcon.tsx";
import { FileResponse } from "../../../api/explorer.ts"; import { FileResponse } from "../../../api/explorer.ts";
import { import { TreeItem, treeItemClasses, TreeItemContentProps, TreeItemProps, useTreeItem } from "@mui/x-tree-view";
TreeItem,
treeItemClasses,
TreeItemContentProps,
TreeItemProps,
useTreeItem,
} from "@mui/x-tree-view";
import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx"; import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx";
import { mergeRefs } from "../../../util"; import { mergeRefs } from "../../../util";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import { import { loadChild, navigateToPath } from "../../../redux/thunks/filemanager.ts";
loadChild,
navigateToPath,
} from "../../../redux/thunks/filemanager.ts";
import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
import CaretDown from "../../Icons/CaretDown.tsx"; import CaretDown from "../../Icons/CaretDown.tsx";
import { StartIcon } from "../TopBar/BreadcrumbButton.tsx"; import { StartIcon } from "../TopBar/BreadcrumbButton.tsx";
@ -41,9 +32,7 @@ const CustomContentRoot = styled(SideNavItemBase)<{
opacity: isDragging ? 0.5 : 1, opacity: isDragging ? 0.5 : 1,
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms",
transitionProperty: "opacity,box-shadow,background-color", transitionProperty: "opacity,box-shadow,background-color",
boxShadow: isDropOver boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
? `inset 0 0 0 2px ${theme.palette.primary.light}`
: "none",
height: "32px", height: "32px",
})); }));
@ -56,16 +45,14 @@ const StyledTreeItemRoot = styled(TreeItem)(() => ({
}, },
})) as unknown as typeof TreeItem; })) as unknown as typeof TreeItem;
export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>( export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>(({ theme, expanded }) => ({
({ theme, expanded }) => ({ fontSize: "12px!important",
fontSize: "12px!important", transform: `rotate(${expanded ? 0 : -90}deg)`,
transform: `rotate(${expanded ? 0 : -90}deg)`, transition: theme.transitions.create("transform", {
transition: theme.transitions.create("transform", { duration: theme.transitions.duration.shortest,
duration: theme.transitions.duration.shortest, easing: theme.transitions.easing.easeInOut,
easing: theme.transitions.easing.easeInOut,
}),
}), }),
); }));
export interface CustomContentProps extends TreeItemContentProps { export interface CustomContentProps extends TreeItemContentProps {
parent?: string; parent?: string;
@ -117,12 +104,7 @@ const UnpinButton = (props: UnpinButton) => {
return ( return (
<Fade in={props.show} unmountOnExit> <Fade in={props.show} unmountOnExit>
<Tooltip title={t("application:fileManager.unpin")}> <Tooltip title={t("application:fileManager.unpin")}>
<SmallIconButton <SmallIconButton disabled={loading} onMouseDown={(e) => e.stopPropagation()} onClick={onClick} size="small">
disabled={loading}
onMouseDown={(e) => e.stopPropagation()}
onClick={onClick}
size="small"
>
<Dismiss fontSize={"inherit"} /> <Dismiss fontSize={"inherit"} />
</SmallIconButton> </SmallIconButton>
</Tooltip> </Tooltip>
@ -136,27 +118,10 @@ const CustomContent = React.memo(
const fmIndex = useContext(FmIndexContext); const fmIndex = useContext(FmIndexContext);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showDelete, setShowDelete] = useState(false); const [showDelete, setShowDelete] = useState(false);
const { const { classes, className, label, nodeId, icon: iconProp, expansionIcon, displayIcon, file, fileIcon } = props;
classes,
className,
label,
nodeId,
icon: iconProp,
expansionIcon,
displayIcon,
file,
fileIcon,
} = props;
const { const { disabled, expanded, selected, focused, handleExpansion, handleSelection, preventSelection } =
disabled, useTreeItem(nodeId);
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection,
} = useTreeItem(nodeId);
const uri = useMemo(() => { const uri = useMemo(() => {
// Trim 'pinedPrefix' if exist in prefix // Trim 'pinedPrefix' if exist in prefix
@ -169,9 +134,7 @@ const CustomContent = React.memo(
const icon = iconProp || expansionIcon || displayIcon; const icon = iconProp || expansionIcon || displayIcon;
const handleMouseDown = ( const handleMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
preventSelection(event); preventSelection(event);
}; };
@ -182,13 +145,7 @@ const CustomContent = React.memo(
handleExpansion(event); handleExpansion(event);
if (!expanded) { if (!expanded) {
try { try {
await dispatch( await dispatch(loadChild(fmIndex, uri, () => (timeOutID = setTimeout(() => setLoading(true), 300))));
loadChild(
fmIndex,
uri,
() => (timeOutID = setTimeout(() => setLoading(true), 300)),
),
);
} finally { } finally {
if (timeOutID) { if (timeOutID) {
clearTimeout(timeOutID); clearTimeout(timeOutID);
@ -211,14 +168,7 @@ const CustomContent = React.memo(
const FileItemIcon = useMemo(() => { const FileItemIcon = useMemo(() => {
if (props.loading) { if (props.loading) {
return ( return <Skeleton sx={{ mr: "14px" }} variant={"rounded"} width={20} height={20} />;
<Skeleton
sx={{ mr: "14px" }}
variant={"rounded"}
width={20}
height={20}
/>
);
} }
if (fileIcon && fileIcon.Icons) { if (fileIcon && fileIcon.Icons) {
return ( 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 */} {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<div onClick={handleExpansionClick} className={classes.iconContainer}> <div onClick={handleExpansionClick} className={classes.iconContainer}>
{icon && !loading && <CaretDownIcon expanded={expanded} />} {icon && !loading && <CaretDownIcon expanded={expanded} />}
{icon && loading && ( {icon && loading && <FacebookCircularProgress size={15} sx={{ pt: 0.5 }} />}
<FacebookCircularProgress size={15} sx={{ pt: 0.5 }} />
)}
</div> </div>
{FileItemIcon} {FileItemIcon}
{fileName} {fileName}
@ -333,23 +281,11 @@ const CustomContent = React.memo(
); );
const TreeFile = React.memo( const TreeFile = React.memo(
React.forwardRef(function CustomTreeItem( React.forwardRef(function CustomTreeItem(props: TreeFileProps, ref: React.Ref<HTMLLIElement>) {
props: TreeFileProps,
ref: React.Ref<HTMLLIElement>,
) {
const contentProps = useMemo(() => { const contentProps = useMemo(() => {
const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } = const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } = props;
props;
return { level, file, notLoaded, fileIcon, loading, pinned, canDrop }; 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 ( return (
<StyledTreeItemRoot <StyledTreeItemRoot
ContentComponent={CustomContent} ContentComponent={CustomContent}

View File

@ -21,9 +21,7 @@ const Loading = () => {
}; };
const HeadlessFrame = () => { const HeadlessFrame = () => {
const loading = useAppSelector( const loading = useAppSelector((state) => state.globalState.loading.headlessFrame);
(state) => state.globalState.loading.headlessFrame,
);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
let navigation = useNavigation(); let navigation = useNavigation();
@ -31,9 +29,7 @@ const HeadlessFrame = () => {
<Box <Box
sx={{ sx={{
backgroundColor: (theme) => backgroundColor: (theme) =>
theme.palette.mode === "light" theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900],
? theme.palette.grey[100]
: theme.palette.grey[900],
flexGrow: 1, flexGrow: 1,
height: "100vh", height: "100vh",
overflow: "auto", overflow: "auto",
@ -51,8 +47,7 @@ const HeadlessFrame = () => {
<Box sx={{ width: "100%", py: 2 }}> <Box sx={{ width: "100%", py: 2 }}>
<Paper <Paper
sx={{ sx={{
padding: (theme) => padding: (theme) => `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
`${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
}} }}
> >
<Logo <Logo
@ -66,10 +61,7 @@ const HeadlessFrame = () => {
<div> <div>
<Box <Box
sx={{ sx={{
display: display: loading || navigation.state !== "idle" ? "none" : "block",
loading || navigation.state !== "idle"
? "none"
: "block",
}} }}
> >
<Outlet /> <Outlet />

View File

@ -1,12 +1,4 @@
import { import { Box, Drawer, Popover, PopoverProps, Stack, useMediaQuery, useTheme } from "@mui/material";
Box,
Drawer,
Popover,
PopoverProps,
Stack,
useMediaQuery,
useTheme,
} from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import DrawerHeader from "./DrawerHeader.tsx"; import DrawerHeader from "./DrawerHeader.tsx";
import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx"; import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx";
@ -68,10 +60,7 @@ const AppDrawer = () => {
const theme = useTheme(); const theme = useTheme();
const open = useAppSelector((state) => state.globalState.drawerOpen); const open = useAppSelector((state) => state.globalState.drawerOpen);
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
const appBarBg = const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[900];
return ( return (
<Drawer <Drawer

View File

@ -1,10 +1,4 @@
import { import { IconButton, Popover, ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material";
IconButton,
Popover,
ToggleButton,
ToggleButtonGroup,
Tooltip,
} from "@mui/material";
import DarkTheme from "../../Icons/DarkTheme.tsx"; import DarkTheme from "../../Icons/DarkTheme.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
@ -21,11 +15,7 @@ interface SwitchPopoverProps {
onClose?: () => void; onClose?: () => void;
} }
export const SwitchPopover = ({ export const SwitchPopover = ({ open, anchorEl, onClose }: SwitchPopoverProps) => {
open,
anchorEl,
onClose,
}: SwitchPopoverProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -36,10 +26,7 @@ export const SwitchPopover = ({
} }
return darkMode ? "dark" : "light"; return darkMode ? "dark" : "light";
}, [darkMode]); }, [darkMode]);
const handleChange = ( const handleChange = (_event: React.MouseEvent<HTMLElement>, newMode: string) => {
_event: React.MouseEvent<HTMLElement>,
newMode: string,
) => {
let newSetting: boolean | undefined; let newSetting: boolean | undefined;
if (newMode === "light") { if (newMode === "light") {
newSetting = false; newSetting = false;
@ -107,11 +94,7 @@ const DarkThemeSwitcher = () => {
<DarkTheme /> <DarkTheme />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<SwitchPopover <SwitchPopover open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={() => setAnchorEl(null)} />
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
/>
</> </>
); );
}; };

View File

@ -1,11 +1,4 @@
import { import { Box, Fade, IconButton, styled, useMediaQuery, useTheme } from "@mui/material";
Box,
Fade,
IconButton,
styled,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setDrawerOpen } from "../../../redux/globalStateSlice.ts"; import { setDrawerOpen } from "../../../redux/globalStateSlice.ts";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import { ChevronLeft } from "@mui/icons-material"; import { ChevronLeft } from "@mui/icons-material";
@ -29,10 +22,7 @@ const DrawerHeader = () => {
const [showCollapse, setShowCollapse] = useState(false); const [showCollapse, setShowCollapse] = useState(false);
return ( return (
<DrawerHeaderContainer <DrawerHeaderContainer onMouseEnter={() => setShowCollapse(true)} onMouseLeave={() => setShowCollapse(false)}>
onMouseEnter={() => setShowCollapse(true)}
onMouseLeave={() => setShowCollapse(false)}
>
<Box sx={{ width: "100%", pl: 2 }}> <Box sx={{ width: "100%", pl: 2 }}>
<Logo <Logo
sx={{ sx={{

View File

@ -7,9 +7,7 @@ import FileSelectedActions from "./FileSelectedActions.tsx";
import { FileManagerIndex } from "../../FileManager/FileManager.tsx"; import { FileManagerIndex } from "../../FileManager/FileManager.tsx";
const NavBarMainActions = () => { const NavBarMainActions = () => {
const selected = useAppSelector( const selected = useAppSelector((state) => state.fileManager[FileManagerIndex.main].selected);
(state) => state.fileManager[FileManagerIndex.main].selected,
);
const targets = useMemo(() => { const targets = useMemo(() => {
return Object.keys(selected).map((key) => selected[key]); return Object.keys(selected).map((key) => selected[key]);
}, [selected]); }, [selected]);
@ -17,9 +15,7 @@ const NavBarMainActions = () => {
<> <>
<SwitchTransition> <SwitchTransition>
<CSSTransition <CSSTransition
addEndListener={(node, done) => addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
node.addEventListener("transitionend", done, false)
}
classNames="fade" classNames="fade"
key={`${targets.length > 0}`} key={`${targets.length > 0}`}
> >

View File

@ -10,12 +10,7 @@ export interface NavIconTransitionProps {
iconProps?: SvgIconProps; iconProps?: SvgIconProps;
} }
const NavIconTransition = ({ const NavIconTransition = ({ fileIcon, active, iconProps, ...rest }: NavIconTransitionProps) => {
fileIcon,
active,
iconProps,
...rest
}: NavIconTransitionProps) => {
const [Active, InActive] = fileIcon; const [Active, InActive] = fileIcon;
return ( return (
<Box {...rest}> <Box {...rest}>
@ -30,11 +25,7 @@ const NavIconTransition = ({
{!active && ( {!active && (
<Fade key={"inactive"}> <Fade key={"inactive"}>
<span> <span>
<InActive <InActive sx={{ position: "absolute" }} key={"inactive"} {...iconProps} />
sx={{ position: "absolute" }}
key={"inactive"}
{...iconProps}
/>
</span> </span>
</Fade> </Fade>
)} )}

View File

@ -1,12 +1,4 @@
import { import { alpha, Button, IconButton, styled, Typography, useMediaQuery, useTheme } from "@mui/material";
alpha,
Button,
IconButton,
styled,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { setSearchPopup } from "../../../redux/globalStateSlice.ts"; import { setSearchPopup } from "../../../redux/globalStateSlice.ts";
@ -14,10 +6,7 @@ import { useAppDispatch } from "../../../redux/hooks.ts";
import Search from "../../Icons/Search.tsx"; import Search from "../../Icons/Search.tsx";
export const KeyIndicator = styled("code")(({ theme }) => ({ export const KeyIndicator = styled("code")(({ theme }) => ({
backgroundColor: backgroundColor: theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900],
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[900],
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
boxShadow: boxShadow:
theme.palette.mode === "light" theme.palette.mode === "light"
@ -36,7 +25,7 @@ const SearchButton = styled(Button)(({ theme }) => ({
" :hover": { " :hover": {
border: `1px solid ${theme.palette.primary.main}`, border: `1px solid ${theme.palette.primary.main}`,
backgroundColor: alpha(theme.palette.primary.main, 0.04), backgroundColor: alpha(theme.palette.primary.main, 0.04),
} },
})); }));
const SearchBar = () => { const SearchBar = () => {

View File

@ -28,10 +28,7 @@ const SplitHandle = (_props: SplitHandleProps) => {
function onMouseMove(e: MouseEvent) { function onMouseMove(e: MouseEvent) {
e.preventDefault(); e.preventDefault();
const newWidth = e.clientX - document.body.offsetLeft; const newWidth = e.clientX - document.body.offsetLeft;
const cappedWidth = Math.max( const cappedWidth = Math.max(Math.min(newWidth, window.innerWidth / 2), minDrawerWidth);
Math.min(newWidth, window.innerWidth / 2),
minDrawerWidth,
);
setCursor(cappedWidth); setCursor(cappedWidth);
finalWidth.current = cappedWidth; finalWidth.current = cappedWidth;
} }

View File

@ -1,21 +1,8 @@
import { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; import { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
import { import { AppBar, Box, Collapse, IconButton, Stack, Toolbar, Tooltip, useMediaQuery, useTheme } from "@mui/material";
AppBar,
Box,
Collapse,
IconButton,
Stack,
Toolbar,
Tooltip,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Menu } from "@mui/icons-material"; import { Menu } from "@mui/icons-material";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { import { setDrawerOpen, setMobileDrawerOpen } from "../../../redux/globalStateSlice.ts";
setDrawerOpen,
setMobileDrawerOpen,
} from "../../../redux/globalStateSlice.ts";
import NewButton from "../../FileManager/NewButton.tsx"; import NewButton from "../../FileManager/NewButton.tsx";
import UserAction from "./UserAction.tsx"; import UserAction from "./UserAction.tsx";
import Setting from "../../Icons/Setting.tsx"; import Setting from "../../Icons/Setting.tsx";
@ -43,19 +30,12 @@ const TopAppBar = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const open = useAppSelector((state) => state.globalState.drawerOpen); const open = useAppSelector((state) => state.globalState.drawerOpen);
const mobileDrawerOpen = useAppSelector( const mobileDrawerOpen = useAppSelector((state) => state.globalState.mobileDrawerOpen);
(state) => state.globalState.mobileDrawerOpen,
);
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
const musicPlayer = useAppSelector((state) => state.globalState.musicPlayer); const musicPlayer = useAppSelector((state) => state.globalState.musicPlayer);
const [mobileMenuAnchor, setMobileMenuAnchor] = useState<null | HTMLElement>( const [mobileMenuAnchor, setMobileMenuAnchor] = useState<null | HTMLElement>(null);
null,
);
const appBarBg = const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[900];
const isLogin = !!SessionManager.currentLoginOrNull(); const isLogin = !!SessionManager.currentLoginOrNull();
const onMobileMenuClicked = (e: React.MouseEvent<HTMLElement>) => { const onMobileMenuClicked = (e: React.MouseEvent<HTMLElement>) => {
@ -94,10 +74,7 @@ const TopAppBar = () => {
<Toolbar <Toolbar
sx={{ sx={{
"&.MuiToolbar-root.MuiToolbar-gutters": { "&.MuiToolbar-root.MuiToolbar-gutters": {
paddingLeft: paddingLeft: open && !isMobile ? theme.spacing(0) : theme.spacing(isMobile ? 2 : 3),
open && !isMobile
? theme.spacing(0)
: theme.spacing(isMobile ? 2 : 3),
transition: theme.transitions.create("padding", { transition: theme.transitions.create("padding", {
easing: theme.transitions.easing.easeInOut, easing: theme.transitions.easing.easeInOut,
duration: theme.transitions.duration.standard, duration: theme.transitions.duration.standard,
@ -110,11 +87,7 @@ const TopAppBar = () => {
color="inherit" color="inherit"
aria-label="open drawer" aria-label="open drawer"
// @ts-ignore // @ts-ignore
onClick={ onClick={isMobile ? onMobileMenuClicked : () => dispatch(setDrawerOpen(true))}
isMobile
? onMobileMenuClicked
: () => dispatch(setDrawerOpen(true))
}
edge="start" edge="start"
sx={{ sx={{
mr: isMobile ? 1 : 2, mr: isMobile ? 1 : 2,
@ -126,11 +99,7 @@ const TopAppBar = () => {
</Collapse> </Collapse>
{isMobile && ( {isMobile && (
<> <>
<DrawerPopover <DrawerPopover open={!!mobileDrawerOpen} anchorEl={mobileMenuAnchor} onClose={onCloseMobileMenu} />
open={!!mobileDrawerOpen}
anchorEl={mobileMenuAnchor}
onClose={onCloseMobileMenu}
/>
</> </>
)} )}
{!isMobile && isMainPage && ( {!isMobile && isMainPage && (
@ -154,10 +123,7 @@ const TopAppBar = () => {
<DarkThemeSwitcher /> <DarkThemeSwitcher />
{isLogin && ( {isLogin && (
<Tooltip title={t("navbar.setting")}> <Tooltip title={t("navbar.setting")}>
<IconButton <IconButton size="large" onClick={() => navigate("/settings")}>
size="large"
onClick={() => navigate("/settings")}
>
<Setting /> <Setting />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View File

@ -4,22 +4,10 @@ export default function Border(props: SvgIconProps) {
return ( return (
<SvgIcon {...props}> <SvgIcon {...props}>
<g fill="none"> <g fill="none">
<path <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" />
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" <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" />
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="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 <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" 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" fill="currentColor"

View File

@ -1,13 +1,5 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { DialogContent, DialogProps, FilledInput, IconButton, InputAdornment, InputLabel, Stack } from "@mui/material";
DialogContent,
DialogProps,
FilledInput,
IconButton,
InputAdornment,
InputLabel,
Stack,
} from "@mui/material";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
import { DavAccount } from "../../../api/setting.ts"; import { DavAccount } from "../../../api/setting.ts";
@ -47,11 +39,7 @@ const InfoTextField = ({ label, value }: { label: string; value: string }) => {
); );
}; };
const ConnectionInfoDialog = ({ const ConnectionInfoDialog = ({ onClose, account, ...rest }: ConnectionInfoDialogProps) => {
onClose,
account,
...rest
}: ConnectionInfoDialogProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -70,18 +58,12 @@ const ConnectionInfoDialog = ({
> >
<DialogContent sx={{ pt: 1 }}> <DialogContent sx={{ pt: 1 }}>
<Stack spacing={2} direction={"column"}> <Stack spacing={2} direction={"column"}>
<InfoTextField <InfoTextField label={t("application:setting.webdavServer")} value={window.location.origin + "/dav"} />
label={t("application:setting.webdavServer")}
value={window.location.origin + "/dav"}
/>
<InfoTextField <InfoTextField
label={t("application:setting.userName")} label={t("application:setting.userName")}
value={SessionManager.currentLoginOrNull()?.user.email ?? ""} value={SessionManager.currentLoginOrNull()?.user.email ?? ""}
/> />
<InfoTextField <InfoTextField label={t("application:login.password")} value={account?.password ?? ""} />
label={t("application:login.password")}
value={account?.password ?? ""}
/>
</Stack> </Stack>
</DialogContent> </DialogContent>
</DraggableDialog> </DraggableDialog>

View File

@ -1,13 +1,5 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { Checkbox, DialogContent, DialogProps, FormGroup, Stack, Typography, useTheme } from "@mui/material";
Checkbox,
DialogContent,
DialogProps,
FormGroup,
Stack,
Typography,
useTheme,
} from "@mui/material";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
@ -22,10 +14,7 @@ import Boolset from "../../../util/boolset.ts";
import { GroupPermission } from "../../../api/user.ts"; import { GroupPermission } from "../../../api/user.ts";
import SessionManager from "../../../session"; import SessionManager from "../../../session";
import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx"; import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx";
import { import { sendCreateDavAccounts, sendUpdateDavAccounts } from "../../../api/api.ts";
sendCreateDavAccounts,
sendUpdateDavAccounts,
} from "../../../api/api.ts";
import { DavAccount, DavAccountOption } from "../../../api/setting.ts"; import { DavAccount, DavAccountOption } from "../../../api/setting.ts";
export interface CreateDAVAccountDialogProps extends DialogProps { export interface CreateDAVAccountDialogProps extends DialogProps {
@ -55,9 +44,7 @@ const CreateDAVAccountDialog = ({
const theme = useTheme(); const theme = useTheme();
const groupProxyEnabled = useMemo(() => { const groupProxyEnabled = useMemo(() => {
const perm = new Boolset( const perm = new Boolset(SessionManager.currentLoginOrNull()?.user.group?.permission);
SessionManager.currentLoginOrNull()?.user.group?.permission,
);
return perm.enabled(GroupPermission.webdav_proxy); return perm.enabled(GroupPermission.webdav_proxy);
}, []); }, []);
@ -83,11 +70,7 @@ const CreateDAVAccountDialog = ({
proxy, proxy,
readonly, readonly,
}; };
dispatch( dispatch(editTarget ? sendUpdateDavAccounts(editTarget.id, req) : sendCreateDavAccounts(req))
editTarget
? sendUpdateDavAccounts(editTarget.id, req)
: sendCreateDavAccounts(req),
)
.then((account) => { .then((account) => {
onClose && onClose({}, "escapeKeyDown"); onClose && onClose({}, "escapeKeyDown");
!editTarget && onAccountAdded && onAccountAdded(account); !editTarget && onAccountAdded && onAccountAdded(account);
@ -146,39 +129,19 @@ const CreateDAVAccountDialog = ({
> >
<FormGroup> <FormGroup>
<SmallFormControlLabel <SmallFormControlLabel
control={ control={<Checkbox size="small" onChange={(e) => setReadonly(e.target.checked)} checked={readonly} />}
<Checkbox
size="small"
onChange={(e) => setReadonly(e.target.checked)}
checked={readonly}
/>
}
label={t("application:setting.readonlyOn")} label={t("application:setting.readonlyOn")}
/> />
<Typography <Typography sx={{ pl: "27px" }} variant={"caption"} color={"text.secondary"}>
sx={{ pl: "27px" }}
variant={"caption"}
color={"text.secondary"}
>
{t("application:setting.readonlyTooltip")} {t("application:setting.readonlyTooltip")}
</Typography> </Typography>
{groupProxyEnabled && ( {groupProxyEnabled && (
<> <>
<SmallFormControlLabel <SmallFormControlLabel
control={ control={<Checkbox size="small" onChange={(e) => setProxy(e.target.checked)} checked={proxy} />}
<Checkbox
size="small"
onChange={(e) => setProxy(e.target.checked)}
checked={proxy}
/>
}
label={t("application:setting.proxy")} label={t("application:setting.proxy")}
/> />
<Typography <Typography sx={{ pl: "27px" }} variant={"caption"} color={"text.secondary"}>
sx={{ pl: "27px" }}
variant={"caption"}
color={"text.secondary"}
>
{t("application:setting.proxyTooltip")} {t("application:setting.proxyTooltip")}
</Typography> </Typography>
</> </>

View File

@ -1,14 +1,6 @@
import * as React from "react"; import * as React from "react";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { import { Box, Table, TableBody, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
Box,
Table,
TableBody,
TableContainer,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getDavAccounts } from "../../../api/api.ts"; import { getDavAccounts } from "../../../api/api.ts";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
@ -26,19 +18,14 @@ export interface DavAccountListProps {
setCreateAccountDialog: (value: boolean) => void; setCreateAccountDialog: (value: boolean) => void;
} }
const DavAccountList = ({ const DavAccountList = ({ creatAccountDialog, setCreateAccountDialog }: DavAccountListProps) => {
creatAccountDialog,
setCreateAccountDialog,
}: DavAccountListProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [nextPageToken, setNextPageToken] = useState<string | undefined>(""); const [nextPageToken, setNextPageToken] = useState<string | undefined>("");
const [accounts, setAccounts] = useState<DavAccount[]>([]); const [accounts, setAccounts] = useState<DavAccount[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [accountInfoTarget, setAccountInfoTarget] = useState< const [accountInfoTarget, setAccountInfoTarget] = useState<DavAccount | undefined>();
DavAccount | undefined
>();
const [editTarget, setEditTarget] = useState<DavAccount | undefined>(); const [editTarget, setEditTarget] = useState<DavAccount | undefined>();
const [editOpen, setEditOpen] = useState(false); const [editOpen, setEditOpen] = useState(false);
@ -75,9 +62,7 @@ const DavAccountList = ({
const onAccountDeleted = useCallback( const onAccountDeleted = useCallback(
(id: string) => { (id: string) => {
setAccounts((accounts) => setAccounts((accounts) => accounts.filter((account) => account.id !== id));
accounts.filter((account) => account.id !== id),
);
}, },
[setAccounts], [setAccounts],
); );
@ -103,9 +88,7 @@ const DavAccountList = ({
open={editOpen} open={editOpen}
editTarget={editTarget} editTarget={editTarget}
onClose={() => setEditOpen(false)} onClose={() => setEditOpen(false)}
onAccountUpdated={(account) => onAccountUpdated={(account) => setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))}
setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))
}
/> />
<ConnectionInfoDialog <ConnectionInfoDialog
open={accountInfoTarget != undefined} open={accountInfoTarget != undefined}
@ -116,26 +99,14 @@ const DavAccountList = ({
<Table sx={{ width: "100%", tableLayout: "fixed" }} size="small"> <Table sx={{ width: "100%", tableLayout: "fixed" }} size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<NoWrapTableCell width={200}>{t("setting.annotation")}</NoWrapTableCell>
<NoWrapTableCell width={200}> <NoWrapTableCell width={200}>
{t("setting.annotation")} <CellHeaderWithPadding>{t("setting.rootFolder")}</CellHeaderWithPadding>
</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")}
</NoWrapTableCell> </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> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -152,9 +123,7 @@ const DavAccountList = ({
<> <>
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<DavAccountRow <DavAccountRow
onLoad={ onLoad={i == 0 ? loadNextPage(accounts, nextPageToken) : undefined}
i == 0 ? loadNextPage(accounts, nextPageToken) : undefined
}
loading={true} loading={true}
key={i} key={i}
onAccountDeleted={onAccountDeleted} onAccountDeleted={onAccountDeleted}

View File

@ -1,12 +1,6 @@
import * as React from "react"; import * as React from "react";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { import { IconButton, ListItemText, Menu, Skeleton, TableRow } from "@mui/material";
IconButton,
ListItemText,
Menu,
Skeleton,
TableRow,
} from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import { NoWrapCell, SquareChip } from "../../Common/StyledComponents.tsx"; import { NoWrapCell, SquareChip } from "../../Common/StyledComponents.tsx";
@ -20,11 +14,7 @@ import { useInView } from "react-intersection-observer";
import Eye from "../../Icons/Eye.tsx"; import Eye from "../../Icons/Eye.tsx";
import { Edit } from "@mui/icons-material"; import { Edit } from "@mui/icons-material";
import CloudFilled from "../../Icons/CloudFilled.tsx"; import CloudFilled from "../../Icons/CloudFilled.tsx";
import { import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
bindMenu,
bindTrigger,
usePopupState,
} from "material-ui-popup-state/hooks";
import MoreVertical from "../../Icons/MoreVertical.tsx"; import MoreVertical from "../../Icons/MoreVertical.tsx";
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
import { sendDeleteDavAccount } from "../../../api/api.ts"; import { sendDeleteDavAccount } from "../../../api/api.ts";
@ -38,14 +28,7 @@ export interface DavAccountRowProps {
onEditClicked?: (account: DavAccount) => void; onEditClicked?: (account: DavAccount) => void;
} }
const DavAccountRow = ({ const DavAccountRow = ({ account, onAccountDeleted, onLoad, loading, onClick, onEditClicked }: DavAccountRowProps) => {
account,
onAccountDeleted,
onLoad,
loading,
onClick,
onEditClicked,
}: DavAccountRowProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -73,10 +56,7 @@ const DavAccountRow = ({
const [readOnly, proxy] = useMemo(() => { const [readOnly, proxy] = useMemo(() => {
if (!account?.options) return [false, false] as const; if (!account?.options) return [false, false] as const;
const bs = new Boolset(account.options); const bs = new Boolset(account.options);
return [ return [bs.enabled(DavAccountOption.readonly), bs.enabled(DavAccountOption.proxy)] as const;
bs.enabled(DavAccountOption.readonly),
bs.enabled(DavAccountOption.proxy),
] as const;
}, [account?.options]); }, [account?.options]);
const { onClose, ...rest } = bindMenu(actionMenuState); const { onClose, ...rest } = bindMenu(actionMenuState);
@ -101,11 +81,7 @@ const DavAccountRow = ({
return ( return (
<TableRow ref={ref} hover sx={{ cursor: "pointer" }} onClick={onClick}> <TableRow ref={ref} hover sx={{ cursor: "pointer" }} onClick={onClick}>
<NoWrapCell component="th" scope="row"> <NoWrapCell component="th" scope="row">
{loading ? ( {loading ? <Skeleton variant={"text"} width={200} sx={{ my: "6px" }} /> : account?.name}
<Skeleton variant={"text"} width={200} sx={{ my: "6px" }} />
) : (
account?.name
)}
</NoWrapCell> </NoWrapCell>
<NoWrapCell> <NoWrapCell>
{loading ? ( {loading ? (
@ -126,17 +102,9 @@ const DavAccountRow = ({
{loading ? ( {loading ? (
<Skeleton variant={"text"} width={100} /> <Skeleton variant={"text"} width={100} />
) : readOnly ? ( ) : readOnly ? (
<SquareChip <SquareChip icon={<Eye />} size={"small"} label={t("setting.readonlyOn")} />
icon={<Eye />}
size={"small"}
label={t("setting.readonlyOn")}
/>
) : ( ) : (
<SquareChip <SquareChip icon={<Edit />} size={"small"} label={t("setting.readonlyOff")} />
icon={<Edit />}
size={"small"}
label={t("setting.readonlyOff")}
/>
)} )}
</NoWrapCell> </NoWrapCell>
<NoWrapCell> <NoWrapCell>

Some files were not shown because too many files have changed in this diff Show More