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> {
return async (dispatch, _getState) => {
return await dispatch(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,22 +4,10 @@ export default function Border(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<g fill="none">
<path
d="M14 3.75a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h2.5a.75.75 0 0 1 .75.75z"
fill="currentColor"
/>
<path
d="M4.5 10.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 0 1.5 0v-2.5z"
fill="currentColor"
/>
<path
d="M19.5 10.75a.75.75 0 0 1 1.5 0v2.5a.75.75 0 0 1-1.5 0v-2.5z"
fill="currentColor"
/>
<path
d="M13.25 21a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 0 0 1.5h2.5z"
fill="currentColor"
/>
<path d="M14 3.75a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h2.5a.75.75 0 0 1 .75.75z" fill="currentColor" />
<path d="M4.5 10.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 0 1.5 0v-2.5z" fill="currentColor" />
<path d="M19.5 10.75a.75.75 0 0 1 1.5 0v2.5a.75.75 0 0 1-1.5 0v-2.5z" fill="currentColor" />
<path d="M13.25 21a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 0 0 1.5h2.5z" fill="currentColor" />
<path
d="M7 3.75A.75.75 0 0 0 6.25 3h-.5A2.75 2.75 0 0 0 3 5.75v.5a.75.75 0 0 0 1.5 0v-.5c0-.69.56-1.25 1.25-1.25h.5A.75.75 0 0 0 7 3.75z"
fill="currentColor"

View File

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

View File

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

View File

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

View File

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

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