mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
chore: format code
This commit is contained in:
parent
34adaeb5dc
commit
27de5db3e7
|
|
@ -1973,7 +1973,6 @@ export function sendImport(req: ImportWorkflowService): ThunkResponse<TaskRespon
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function sendPatchViewSync(args: PatchViewSyncService): ThunkResponse<void> {
|
export function sendPatchViewSync(args: PatchViewSyncService): ThunkResponse<void> {
|
||||||
return async (dispatch, _getState) => {
|
return async (dispatch, _getState) => {
|
||||||
return await dispatch(
|
return await dispatch(
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,13 @@ import { DenseAutocomplete, DenseFilledTextField, NoWrapBox, SquareChip } from "
|
||||||
import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx";
|
import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx";
|
||||||
import LinkDismiss from "../../Icons/LinkDismiss.tsx";
|
import LinkDismiss from "../../Icons/LinkDismiss.tsx";
|
||||||
|
|
||||||
export interface SharesInputProps {
|
export interface SharesInputProps {}
|
||||||
}
|
|
||||||
|
|
||||||
const SharesInput = (props: SharesInputProps) => {
|
const SharesInput = (props: SharesInputProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [options, setOptions] = useState<number[]>([]);
|
const [options, setOptions] = useState<number[]>([]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DenseAutocomplete
|
<DenseAutocomplete
|
||||||
multiple
|
multiple
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,7 @@ const FileForm = () => {
|
||||||
{!fileParentLoading && (
|
{!fileParentLoading && (
|
||||||
<>
|
<>
|
||||||
{fileParent && (
|
{fileParent && (
|
||||||
<StyledFileBadge
|
<StyledFileBadge variant={"outlined"} simplifiedFile={{ path: fileParent, type: FileType.folder }} />
|
||||||
variant={"outlined"}
|
|
||||||
simplifiedFile={{ path: fileParent, type: FileType.folder }}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{!fileParent && "-"}
|
{!fileParent && "-"}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ import { useSnackbar } from "notistack";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar";
|
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar";
|
||||||
import { NoWrapTableCell, SecondaryButton, StyledCheckbox, StyledTableContainerPaper } from "../../../Common/StyledComponents";
|
import {
|
||||||
|
NoWrapTableCell,
|
||||||
|
SecondaryButton,
|
||||||
|
StyledCheckbox,
|
||||||
|
StyledTableContainerPaper,
|
||||||
|
} from "../../../Common/StyledComponents";
|
||||||
import Add from "../../../Icons/Add";
|
import Add from "../../../Icons/Add";
|
||||||
import Delete from "../../../Icons/Delete";
|
import Delete from "../../../Icons/Delete";
|
||||||
import Edit from "../../../Icons/Edit";
|
import Edit from "../../../Icons/Edit";
|
||||||
|
|
@ -79,30 +84,39 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleSave = useCallback((newOptions: Record<string, ThemeOption["config"]>) => {
|
const handleSave = useCallback(
|
||||||
onChange(JSON.stringify(newOptions));
|
(newOptions: Record<string, ThemeOption["config"]>) => {
|
||||||
}, [onChange]);
|
onChange(JSON.stringify(newOptions));
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
const handleDelete = useCallback((id: string) => {
|
const handleDelete = useCallback(
|
||||||
// Prevent deleting the default theme
|
(id: string) => {
|
||||||
if (id === defaultTheme) {
|
// Prevent deleting the default theme
|
||||||
enqueueSnackbar({
|
if (id === defaultTheme) {
|
||||||
message: t("settings.cannotDeleteDefaultTheme"),
|
enqueueSnackbar({
|
||||||
variant: "warning",
|
message: t("settings.cannotDeleteDefaultTheme"),
|
||||||
action: DefaultCloseAction,
|
variant: "warning",
|
||||||
});
|
action: DefaultCloseAction,
|
||||||
return;
|
});
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newOptions = { ...options };
|
const newOptions = { ...options };
|
||||||
delete newOptions[id];
|
delete newOptions[id];
|
||||||
handleSave(newOptions);
|
handleSave(newOptions);
|
||||||
}, [options, handleSave, defaultTheme, enqueueSnackbar, t]);
|
},
|
||||||
|
[options, handleSave, defaultTheme, enqueueSnackbar, t],
|
||||||
|
);
|
||||||
|
|
||||||
const handleEdit = useCallback((id: string) => {
|
const handleEdit = useCallback(
|
||||||
setEditingOption({ id, config: options[id] });
|
(id: string) => {
|
||||||
setIsDialogOpen(true);
|
setEditingOption({ id, config: options[id] });
|
||||||
}, [options]);
|
setIsDialogOpen(true);
|
||||||
|
},
|
||||||
|
[options],
|
||||||
|
);
|
||||||
|
|
||||||
const handleAdd = useCallback(() => {
|
const handleAdd = useCallback(() => {
|
||||||
// Generate a new default theme option with a random color
|
// Generate a new default theme option with a random color
|
||||||
|
|
@ -113,16 +127,16 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
light: {
|
light: {
|
||||||
palette: {
|
palette: {
|
||||||
primary: { main: randomColor },
|
primary: { main: randomColor },
|
||||||
secondary: { main: "#f50057" }
|
secondary: { main: "#f50057" },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
palette: {
|
palette: {
|
||||||
primary: { main: randomColor },
|
primary: { main: randomColor },
|
||||||
secondary: { main: "#f50057" }
|
secondary: { main: "#f50057" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -132,15 +146,58 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
setEditingOption(null);
|
setEditingOption(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDialogSave = useCallback((id: string, newId: string, config: string) => {
|
const handleDialogSave = useCallback(
|
||||||
try {
|
(id: string, newId: string, config: string) => {
|
||||||
const parsedConfig = JSON.parse(config);
|
try {
|
||||||
|
const parsedConfig = JSON.parse(config);
|
||||||
|
const newOptions = { ...options };
|
||||||
|
|
||||||
|
// If ID has changed (primary color changed), delete the old entry and create a new one
|
||||||
|
if (id !== newId) {
|
||||||
|
// Check if the new ID already exists
|
||||||
|
if (newOptions[newId]) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: t("settings.duplicateThemeColor"),
|
||||||
|
variant: "warning",
|
||||||
|
action: DefaultCloseAction,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're changing the ID of the default theme, update the default theme reference
|
||||||
|
if (id === defaultTheme) {
|
||||||
|
onDefaultThemeChange(newId);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete newOptions[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
newOptions[newId] = parsedConfig;
|
||||||
|
handleSave(newOptions);
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
setEditingOption(null);
|
||||||
|
} catch (e) {
|
||||||
|
// Handle error
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: t("settings.invalidThemeConfig"),
|
||||||
|
variant: "warning",
|
||||||
|
action: DefaultCloseAction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[options, handleSave, enqueueSnackbar, defaultTheme, onDefaultThemeChange, t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleColorChange = useCallback(
|
||||||
|
(id: string, type: "primary" | "secondary", mode: "light" | "dark", color: string) => {
|
||||||
const newOptions = { ...options };
|
const newOptions = { ...options };
|
||||||
|
|
||||||
// If ID has changed (primary color changed), delete the old entry and create a new one
|
if (type === "primary" && mode === "light") {
|
||||||
if (id !== newId) {
|
// If changing the primary color (which is the ID), we need to create a new entry
|
||||||
|
const newId = color;
|
||||||
|
|
||||||
// Check if the new ID already exists
|
// Check if the new ID already exists
|
||||||
if (newOptions[newId]) {
|
if (newOptions[newId] && newId !== id) {
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: t("settings.duplicateThemeColor"),
|
message: t("settings.duplicateThemeColor"),
|
||||||
variant: "warning",
|
variant: "warning",
|
||||||
|
|
@ -149,67 +206,33 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const config = { ...newOptions[id] };
|
||||||
|
config[mode].palette[type].main = color;
|
||||||
|
|
||||||
|
// Delete old entry and create new one with the updated ID
|
||||||
|
delete newOptions[id];
|
||||||
|
newOptions[newId] = config;
|
||||||
|
|
||||||
// If we're changing the ID of the default theme, update the default theme reference
|
// If we're changing the ID of the default theme, update the default theme reference
|
||||||
if (id === defaultTheme) {
|
if (id === defaultTheme) {
|
||||||
onDefaultThemeChange(newId);
|
onDefaultThemeChange(newId);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
delete newOptions[id];
|
// For other colors, just update the value
|
||||||
|
newOptions[id][mode].palette[type].main = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
newOptions[newId] = parsedConfig;
|
|
||||||
handleSave(newOptions);
|
handleSave(newOptions);
|
||||||
setIsDialogOpen(false);
|
},
|
||||||
setEditingOption(null);
|
[options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange],
|
||||||
} catch (e) {
|
);
|
||||||
// Handle error
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: t("settings.invalidThemeConfig"),
|
|
||||||
variant: "warning",
|
|
||||||
action: DefaultCloseAction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [options, handleSave, enqueueSnackbar, defaultTheme, onDefaultThemeChange, t]);
|
|
||||||
|
|
||||||
const handleColorChange = useCallback((id: string, type: 'primary' | 'secondary', mode: 'light' | 'dark', color: string) => {
|
const handleDefaultThemeChange = useCallback(
|
||||||
const newOptions = { ...options };
|
(id: string) => {
|
||||||
|
onDefaultThemeChange(id);
|
||||||
if (type === 'primary' && mode === 'light') {
|
},
|
||||||
// If changing the primary color (which is the ID), we need to create a new entry
|
[onDefaultThemeChange],
|
||||||
const newId = color;
|
);
|
||||||
|
|
||||||
// Check if the new ID already exists
|
|
||||||
if (newOptions[newId] && newId !== id) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: t("settings.duplicateThemeColor"),
|
|
||||||
variant: "warning",
|
|
||||||
action: DefaultCloseAction,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = { ...newOptions[id] };
|
|
||||||
config[mode].palette[type].main = color;
|
|
||||||
|
|
||||||
// Delete old entry and create new one with the updated ID
|
|
||||||
delete newOptions[id];
|
|
||||||
newOptions[newId] = config;
|
|
||||||
|
|
||||||
// If we're changing the ID of the default theme, update the default theme reference
|
|
||||||
if (id === defaultTheme) {
|
|
||||||
onDefaultThemeChange(newId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For other colors, just update the value
|
|
||||||
newOptions[id][mode].palette[type].main = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSave(newOptions);
|
|
||||||
}, [options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange]);
|
|
||||||
|
|
||||||
const handleDefaultThemeChange = useCallback((id: string) => {
|
|
||||||
onDefaultThemeChange(id);
|
|
||||||
}, [onDefaultThemeChange]);
|
|
||||||
|
|
||||||
const optionsArray = useMemo(() => {
|
const optionsArray = useMemo(() => {
|
||||||
return Object.entries(options).map(([id, config]) => ({
|
return Object.entries(options).map(([id, config]) => ({
|
||||||
|
|
@ -229,8 +252,7 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
|
|
||||||
{optionsArray.length > 0 && (
|
{optionsArray.length > 0 && (
|
||||||
<TableContainer component={StyledTableContainerPaper} sx={{ mt: 2 }}>
|
<TableContainer component={StyledTableContainerPaper} sx={{ mt: 2 }}>
|
||||||
<Table size="small" stickyHeader
|
<Table size="small" stickyHeader sx={{ width: "100%", tableLayout: "fixed" }}>
|
||||||
sx={{ width: "100%", tableLayout: "fixed" }}>
|
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<NoWrapTableCell width={50}>{t("settings.defaultTheme")}</NoWrapTableCell>
|
<NoWrapTableCell width={50}>{t("settings.defaultTheme")}</NoWrapTableCell>
|
||||||
|
|
@ -255,25 +277,25 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
<HexColorInput
|
<HexColorInput
|
||||||
required
|
required
|
||||||
currentColor={option.config.light.palette.primary.main}
|
currentColor={option.config.light.palette.primary.main}
|
||||||
onColorChange={(color) => handleColorChange(option.id, 'primary', 'light', color)}
|
onColorChange={(color) => handleColorChange(option.id, "primary", "light", color)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<HexColorInput
|
<HexColorInput
|
||||||
currentColor={option.config.light.palette.secondary.main}
|
currentColor={option.config.light.palette.secondary.main}
|
||||||
onColorChange={(color) => handleColorChange(option.id, 'secondary', 'light', color)}
|
onColorChange={(color) => handleColorChange(option.id, "secondary", "light", color)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<HexColorInput
|
<HexColorInput
|
||||||
currentColor={option.config.dark.palette.primary.main}
|
currentColor={option.config.dark.palette.primary.main}
|
||||||
onColorChange={(color) => handleColorChange(option.id, 'primary', 'dark', color)}
|
onColorChange={(color) => handleColorChange(option.id, "primary", "dark", color)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<HexColorInput
|
<HexColorInput
|
||||||
currentColor={option.config.dark.palette.secondary.main}
|
currentColor={option.config.dark.palette.secondary.main}
|
||||||
onColorChange={(color) => handleColorChange(option.id, 'secondary', 'dark', color)}
|
onColorChange={(color) => handleColorChange(option.id, "secondary", "dark", color)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
|
|
@ -295,12 +317,7 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SecondaryButton
|
<SecondaryButton variant="contained" startIcon={<Add />} onClick={handleAdd} sx={{ mt: 2 }}>
|
||||||
variant="contained"
|
|
||||||
startIcon={<Add />}
|
|
||||||
onClick={handleAdd}
|
|
||||||
sx={{ mt: 2 }}
|
|
||||||
>
|
|
||||||
{t("settings.addThemeOption")}
|
{t("settings.addThemeOption")}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
|
|
||||||
|
|
@ -317,4 +334,4 @@ const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: T
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThemeOptions;
|
export default ThemeOptions;
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.capInstanceURLDes"
|
i18nKey="settings.capInstanceURLDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://capjs.js.org/guide/standalone.html"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -58,13 +52,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.capKeyIDDes"
|
i18nKey="settings.capKeyIDDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://capjs.js.org/guide/standalone.html"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -85,13 +73,7 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.capKeySecretDes"
|
i18nKey="settings.capKeySecretDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://capjs.js.org/guide/standalone.html"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://capjs.js.org/guide/standalone.html"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -100,4 +82,4 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CapCaptcha;
|
export default CapCaptcha;
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,8 @@ import { useTranslation } from "react-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { SettingContext } from "../SettingWrapper.tsx";
|
import { SettingContext } from "../SettingWrapper.tsx";
|
||||||
import {
|
import { Box, Collapse, FormControl, FormControlLabel, ListItemText, Stack, Switch, Typography } from "@mui/material";
|
||||||
Box,
|
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
|
||||||
Collapse,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
ListItemText,
|
|
||||||
Stack,
|
|
||||||
Switch,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import {
|
|
||||||
NoMarginHelperText,
|
|
||||||
SettingSection,
|
|
||||||
SettingSectionContent,
|
|
||||||
} from "../Settings.tsx";
|
|
||||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||||
import { isTrueVal } from "../../../../session/utils.ts";
|
import { isTrueVal } from "../../../../session/utils.ts";
|
||||||
import { DenseSelect } from "../../../Common/StyledComponents.tsx";
|
import { DenseSelect } from "../../../Common/StyledComponents.tsx";
|
||||||
|
|
@ -54,9 +41,7 @@ const Captcha = () => {
|
||||||
}
|
}
|
||||||
label={t("settings.captchaForLogin")}
|
label={t("settings.captchaForLogin")}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.captchaForLoginDes")}</NoMarginHelperText>
|
||||||
{t("settings.captchaForLoginDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
<SettingForm lgWidth={5}>
|
<SettingForm lgWidth={5}>
|
||||||
|
|
@ -74,9 +59,7 @@ const Captcha = () => {
|
||||||
}
|
}
|
||||||
label={t("settings.captchaForSignup")}
|
label={t("settings.captchaForSignup")}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.captchaForSignupDes")}</NoMarginHelperText>
|
||||||
{t("settings.captchaForSignupDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
<SettingForm lgWidth={5}>
|
<SettingForm lgWidth={5}>
|
||||||
|
|
@ -94,9 +77,7 @@ const Captcha = () => {
|
||||||
}
|
}
|
||||||
label={t("settings.captchaForReset")}
|
label={t("settings.captchaForReset")}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.captchaForResetDes")}</NoMarginHelperText>
|
||||||
{t("settings.captchaForResetDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
</SettingSectionContent>
|
</SettingSectionContent>
|
||||||
|
|
@ -117,61 +98,55 @@ const Captcha = () => {
|
||||||
value={values.captcha_type}
|
value={values.captcha_type}
|
||||||
>
|
>
|
||||||
<SquareMenuItem value={CaptchaType.NORMAL}>
|
<SquareMenuItem value={CaptchaType.NORMAL}>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("settings.plainCaptcha")}
|
{t("settings.plainCaptcha")}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
<SquareMenuItem value={CaptchaType.RECAPTCHA}>
|
<SquareMenuItem value={CaptchaType.RECAPTCHA}>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("settings.reCaptchaV2")}
|
{t("settings.reCaptchaV2")}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
<SquareMenuItem value={CaptchaType.TURNSTILE}>
|
<SquareMenuItem value={CaptchaType.TURNSTILE}>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("settings.turnstile")}
|
{t("settings.turnstile")}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
<SquareMenuItem value={CaptchaType.CAP}>
|
<SquareMenuItem value={CaptchaType.CAP}>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("settings.cap")}
|
{t("settings.cap")}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
</DenseSelect>
|
</DenseSelect>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.captchaTypeDes")}</NoMarginHelperText>
|
||||||
{t("settings.captchaTypeDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
<Collapse
|
<Collapse in={values.captcha_type === CaptchaType.NORMAL} unmountOnExit>
|
||||||
in={values.captcha_type === CaptchaType.NORMAL}
|
|
||||||
unmountOnExit
|
|
||||||
>
|
|
||||||
<GraphicCaptcha setSettings={setSettings} values={values} />
|
<GraphicCaptcha setSettings={setSettings} values={values} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<Collapse
|
<Collapse in={values.captcha_type === CaptchaType.RECAPTCHA} unmountOnExit>
|
||||||
in={values.captcha_type === CaptchaType.RECAPTCHA}
|
|
||||||
unmountOnExit
|
|
||||||
>
|
|
||||||
<ReCaptcha setSettings={setSettings} values={values} />
|
<ReCaptcha setSettings={setSettings} values={values} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<Collapse
|
<Collapse in={values.captcha_type === CaptchaType.TURNSTILE} unmountOnExit>
|
||||||
in={values.captcha_type === CaptchaType.TURNSTILE}
|
|
||||||
unmountOnExit
|
|
||||||
>
|
|
||||||
<TurnstileCaptcha setSettings={setSettings} values={values} />
|
<TurnstileCaptcha setSettings={setSettings} values={values} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<Collapse
|
<Collapse in={values.captcha_type === CaptchaType.CAP} unmountOnExit>
|
||||||
in={values.captcha_type === CaptchaType.CAP}
|
|
||||||
unmountOnExit
|
|
||||||
>
|
|
||||||
<CapCaptcha setSettings={setSettings} values={values} />
|
<CapCaptcha setSettings={setSettings} values={values} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</SettingSectionContent>
|
</SettingSectionContent>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { FormControl, FormControlLabel, ListItemText, Stack, Switch } from "@mui/material";
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
ListItemText,
|
|
||||||
Stack,
|
|
||||||
Switch,
|
|
||||||
} from "@mui/material";
|
|
||||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||||
import { DenseSelect } from "../../../Common/StyledComponents.tsx";
|
import { DenseSelect } from "../../../Common/StyledComponents.tsx";
|
||||||
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
|
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
|
||||||
|
|
@ -33,16 +27,13 @@ const GraphicCaptcha = ({ values, setSettings }: GraphicCaptchaProps) => {
|
||||||
}
|
}
|
||||||
value={values.captcha_mode}
|
value={values.captcha_mode}
|
||||||
>
|
>
|
||||||
{[
|
{["captchaModeNumber", "captchaModeLetter", "captchaModeMath", "captchaModeNumberLetter"].map((k, i) => (
|
||||||
"captchaModeNumber",
|
|
||||||
"captchaModeLetter",
|
|
||||||
"captchaModeMath",
|
|
||||||
"captchaModeNumberLetter",
|
|
||||||
].map((k, i) => (
|
|
||||||
<SquareMenuItem key={k} value={i.toString()}>
|
<SquareMenuItem key={k} value={i.toString()}>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t(`settings.${k}`)}
|
{t(`settings.${k}`)}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,7 @@ const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.siteKeyDes"
|
i18nKey="settings.siteKeyDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://www.google.com/recaptcha/admin/create"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://www.google.com/recaptcha/admin/create"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -57,13 +51,7 @@ const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.siteSecretDes"
|
i18nKey="settings.siteSecretDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://www.google.com/recaptcha/admin/create"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://www.google.com/recaptcha/admin/create"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,7 @@ const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.siteKeyDes"
|
i18nKey="settings.siteKeyDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://dash.cloudflare.com/"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://dash.cloudflare.com/"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -57,13 +51,7 @@ const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => {
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="settings.siteSecretDes"
|
i18nKey="settings.siteSecretDes"
|
||||||
ns={"dashboard"}
|
ns={"dashboard"}
|
||||||
components={[
|
components={[<Link key={0} href={"https://dash.cloudflare.com/"} target={"_blank"} />]}
|
||||||
<Link
|
|
||||||
key={0}
|
|
||||||
href={"https://dash.cloudflare.com/"}
|
|
||||||
target={"_blank"}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</NoMarginHelperText>
|
</NoMarginHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { Box, DialogContent, FormControl, FormControlLabel, Stack, Switch, Typography } from "@mui/material";
|
||||||
Box,
|
|
||||||
DialogContent,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
Stack,
|
|
||||||
Switch,
|
|
||||||
Typography
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
@ -18,11 +10,7 @@ import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledCom
|
||||||
import DraggableDialog, { StyledDialogContentText } from "../../../Dialogs/DraggableDialog.tsx";
|
import DraggableDialog, { StyledDialogContentText } from "../../../Dialogs/DraggableDialog.tsx";
|
||||||
import MailOutlined from "../../../Icons/MailOutlined.tsx";
|
import MailOutlined from "../../../Icons/MailOutlined.tsx";
|
||||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||||
import {
|
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
|
||||||
NoMarginHelperText,
|
|
||||||
SettingSection,
|
|
||||||
SettingSectionContent,
|
|
||||||
} from "../Settings.tsx";
|
|
||||||
import { SettingContext } from "../SettingWrapper.tsx";
|
import { SettingContext } from "../SettingWrapper.tsx";
|
||||||
import EmailTemplates from "./EmailTemplates.tsx";
|
import EmailTemplates from "./EmailTemplates.tsx";
|
||||||
|
|
||||||
|
|
@ -38,10 +26,12 @@ const Email = () => {
|
||||||
const handleTestEmail = async () => {
|
const handleTestEmail = async () => {
|
||||||
setSending(true);
|
setSending(true);
|
||||||
try {
|
try {
|
||||||
await dispatch(sendTestSMTP({
|
await dispatch(
|
||||||
to: testEmailAddress,
|
sendTestSMTP({
|
||||||
settings: values,
|
to: testEmailAddress,
|
||||||
}));
|
settings: values,
|
||||||
|
}),
|
||||||
|
);
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: t("settings.testMailSent"),
|
message: t("settings.testMailSent"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
|
|
@ -69,9 +59,7 @@ const Email = () => {
|
||||||
title={t("settings.testSMTPSettings")}
|
title={t("settings.testSMTPSettings")}
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<StyledDialogContentText sx={{ mb: 2 }}>
|
<StyledDialogContentText sx={{ mb: 2 }}>{t("settings.testSMTPTooltip")}</StyledDialogContentText>
|
||||||
{t("settings.testSMTPTooltip")}
|
|
||||||
</StyledDialogContentText>
|
|
||||||
<SettingForm title={t("settings.recipient")} lgWidth={12}>
|
<SettingForm title={t("settings.recipient")} lgWidth={12}>
|
||||||
<DenseFilledTextField
|
<DenseFilledTextField
|
||||||
required
|
required
|
||||||
|
|
@ -86,7 +74,9 @@ const Email = () => {
|
||||||
</DraggableDialog>
|
</DraggableDialog>
|
||||||
|
|
||||||
<SettingSection>
|
<SettingSection>
|
||||||
<Typography variant="h6" gutterBottom>{t("settings.smtp")}</Typography>
|
<Typography variant="h6" gutterBottom>
|
||||||
|
{t("settings.smtp")}
|
||||||
|
</Typography>
|
||||||
<SettingSectionContent>
|
<SettingSectionContent>
|
||||||
<SettingForm title={t("settings.senderName")} lgWidth={5}>
|
<SettingForm title={t("settings.senderName")} lgWidth={5}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
|
|
@ -95,9 +85,7 @@ const Email = () => {
|
||||||
value={values.fromName ?? ""}
|
value={values.fromName ?? ""}
|
||||||
onChange={(e) => setSettings({ fromName: e.target.value })}
|
onChange={(e) => setSettings({ fromName: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.senderNameDes")}</NoMarginHelperText>
|
||||||
{t("settings.senderNameDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -109,9 +97,7 @@ const Email = () => {
|
||||||
value={values.fromAdress ?? ""}
|
value={values.fromAdress ?? ""}
|
||||||
onChange={(e) => setSettings({ fromAdress: e.target.value })}
|
onChange={(e) => setSettings({ fromAdress: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.senderAddressDes")}</NoMarginHelperText>
|
||||||
{t("settings.senderAddressDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -122,9 +108,7 @@ const Email = () => {
|
||||||
value={values.smtpHost ?? ""}
|
value={values.smtpHost ?? ""}
|
||||||
onChange={(e) => setSettings({ smtpHost: e.target.value })}
|
onChange={(e) => setSettings({ smtpHost: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.smtpServerDes")}</NoMarginHelperText>
|
||||||
{t("settings.smtpServerDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -137,9 +121,7 @@ const Email = () => {
|
||||||
value={values.smtpPort ?? ""}
|
value={values.smtpPort ?? ""}
|
||||||
onChange={(e) => setSettings({ smtpPort: e.target.value })}
|
onChange={(e) => setSettings({ smtpPort: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.smtpPortDes")}</NoMarginHelperText>
|
||||||
{t("settings.smtpPortDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -150,9 +132,7 @@ const Email = () => {
|
||||||
value={values.smtpUser ?? ""}
|
value={values.smtpUser ?? ""}
|
||||||
onChange={(e) => setSettings({ smtpUser: e.target.value })}
|
onChange={(e) => setSettings({ smtpUser: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.smtpUsernameDes")}</NoMarginHelperText>
|
||||||
{t("settings.smtpUsernameDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -164,9 +144,7 @@ const Email = () => {
|
||||||
value={values.smtpPass ?? ""}
|
value={values.smtpPass ?? ""}
|
||||||
onChange={(e) => setSettings({ smtpPass: e.target.value })}
|
onChange={(e) => setSettings({ smtpPass: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.smtpPasswordDes")}</NoMarginHelperText>
|
||||||
{t("settings.smtpPasswordDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -177,9 +155,7 @@ const Email = () => {
|
||||||
value={values.replyTo ?? ""}
|
value={values.replyTo ?? ""}
|
||||||
onChange={(e) => setSettings({ replyTo: e.target.value })}
|
onChange={(e) => setSettings({ replyTo: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.replyToAddressDes")}</NoMarginHelperText>
|
||||||
{t("settings.replyToAddressDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -189,16 +165,12 @@ const Email = () => {
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
checked={isTrueVal(values.smtpEncryption)}
|
checked={isTrueVal(values.smtpEncryption)}
|
||||||
onChange={(e) =>
|
onChange={(e) => setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })}
|
||||||
setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={t("settings.enforceSSL")}
|
label={t("settings.enforceSSL")}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.enforceSSLDes")}</NoMarginHelperText>
|
||||||
{t("settings.enforceSSLDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -211,18 +183,12 @@ const Email = () => {
|
||||||
value={values.mail_keepalive ?? "30"}
|
value={values.mail_keepalive ?? "30"}
|
||||||
onChange={(e) => setSettings({ mail_keepalive: e.target.value })}
|
onChange={(e) => setSettings({ mail_keepalive: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.smtpTTLDes")}</NoMarginHelperText>
|
||||||
{t("settings.smtpTTLDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
<Box display="flex" gap={2} mt={2}>
|
<Box display="flex" gap={2} mt={2}>
|
||||||
<SecondaryButton
|
<SecondaryButton variant="contained" startIcon={<MailOutlined />} onClick={() => setTestEmailOpen(true)}>
|
||||||
variant="contained"
|
|
||||||
startIcon={<MailOutlined />}
|
|
||||||
onClick={() => setTestEmailOpen(true)}
|
|
||||||
>
|
|
||||||
{t("settings.sendTestEmail")}
|
{t("settings.sendTestEmail")}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -236,4 +202,4 @@ const Email = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Email;
|
export default Email;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,7 @@
|
||||||
import {
|
import { Box, IconButton, Table, TableBody, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||||
Box,
|
|
||||||
IconButton,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableContainer,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
} from "@mui/material";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { memo, useMemo, useState } from "react";
|
import { memo, useMemo, useState } from "react";
|
||||||
import {
|
import { builtInIcons, FileTypeIconSetting } from "../../../FileManager/Explorer/FileTypeIcon.tsx";
|
||||||
builtInIcons,
|
|
||||||
FileTypeIconSetting,
|
|
||||||
} from "../../../FileManager/Explorer/FileTypeIcon.tsx";
|
|
||||||
import {
|
import {
|
||||||
DenseFilledTextField,
|
DenseFilledTextField,
|
||||||
NoWrapCell,
|
NoWrapCell,
|
||||||
|
|
@ -71,52 +60,28 @@ const IconPreview = ({ icon }: { icon: FileTypeIconSetting }) => {
|
||||||
|
|
||||||
const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
|
const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
|
||||||
const { t } = useTranslation("dashboard");
|
const { t } = useTranslation("dashboard");
|
||||||
const configParsed = useMemo(
|
const configParsed = useMemo((): FileTypeIconSetting[] => JSON.parse(config), [config]);
|
||||||
(): FileTypeIconSetting[] => JSON.parse(config),
|
|
||||||
[config],
|
|
||||||
);
|
|
||||||
const [inputCache, setInputCache] = useState<{
|
const [inputCache, setInputCache] = useState<{
|
||||||
[key: number]: string | undefined;
|
[key: number]: string | undefined;
|
||||||
}>({});
|
}>({});
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{configParsed?.length > 0 && (
|
{configParsed?.length > 0 && (
|
||||||
<TableContainer
|
<TableContainer sx={{ mt: 1, maxHeight: 440 }} component={StyledTableContainerPaper}>
|
||||||
sx={{ mt: 1, maxHeight: 440 }}
|
<Table stickyHeader sx={{ width: "100%", tableLayout: "fixed" }} size="small">
|
||||||
component={StyledTableContainerPaper}
|
|
||||||
>
|
|
||||||
<Table
|
|
||||||
stickyHeader
|
|
||||||
sx={{ width: "100%", tableLayout: "fixed" }}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<NoWrapTableCell width={64}>
|
<NoWrapTableCell width={64}>{t("settings.icon")}</NoWrapTableCell>
|
||||||
{t("settings.icon")}
|
<NoWrapTableCell width={200}>{t("settings.iconUrl")}</NoWrapTableCell>
|
||||||
</NoWrapTableCell>
|
<NoWrapTableCell width={150}>{t("settings.iconColor")}</NoWrapTableCell>
|
||||||
<NoWrapTableCell width={200}>
|
<NoWrapTableCell width={150}>{t("settings.iconColorDark")}</NoWrapTableCell>
|
||||||
{t("settings.iconUrl")}
|
<NoWrapTableCell width={250}>{t("settings.exts")}</NoWrapTableCell>
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={150}>
|
|
||||||
{t("settings.iconColor")}
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={150}>
|
|
||||||
{t("settings.iconColorDark")}
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={250}>
|
|
||||||
{t("settings.exts")}
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={64}></NoWrapTableCell>
|
<NoWrapTableCell width={64}></NoWrapTableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{configParsed.map((r, i) => (
|
{configParsed.map((r, i) => (
|
||||||
<TableRow
|
<TableRow key={i} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover>
|
||||||
key={i}
|
|
||||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
|
||||||
hover
|
|
||||||
>
|
|
||||||
<NoWrapCell>
|
<NoWrapCell>
|
||||||
<IconPreview icon={r} />
|
<IconPreview icon={r} />
|
||||||
</NoWrapCell>
|
</NoWrapCell>
|
||||||
|
|
@ -215,13 +180,7 @@ const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
|
||||||
<NoWrapCell>
|
<NoWrapCell>
|
||||||
{!r.icon && (
|
{!r.icon && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() =>
|
onClick={() => onChange(JSON.stringify(configParsed.filter((_, index) => index !== i)))}
|
||||||
onChange(
|
|
||||||
JSON.stringify(
|
|
||||||
configParsed.filter((_, index) => index !== i),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
size={"small"}
|
size={"small"}
|
||||||
>
|
>
|
||||||
<Dismiss fontSize={"small"} />
|
<Dismiss fontSize={"small"} />
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,7 @@ export interface HexColorInputProps {
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HexColorInput = ({
|
const HexColorInput = ({ currentColor, onColorChange, ...rest }: HexColorInputProps) => {
|
||||||
currentColor,
|
|
||||||
onColorChange,
|
|
||||||
...rest
|
|
||||||
}: HexColorInputProps) => {
|
|
||||||
return (
|
return (
|
||||||
<DenseFilledTextField
|
<DenseFilledTextField
|
||||||
value={currentColor}
|
value={currentColor}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,7 @@ import { memo, useCallback, useState } from "react";
|
||||||
import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
|
import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { IconButton, TableRow } from "@mui/material";
|
import { IconButton, TableRow } from "@mui/material";
|
||||||
import {
|
import { DenseFilledTextField, NoWrapCell, StyledCheckbox } from "../../../../Common/StyledComponents.tsx";
|
||||||
DenseFilledTextField,
|
|
||||||
NoWrapCell,
|
|
||||||
StyledCheckbox,
|
|
||||||
} from "../../../../Common/StyledComponents.tsx";
|
|
||||||
import { ViewerIcon } from "../../../../FileManager/Dialogs/OpenWith.tsx";
|
import { ViewerIcon } from "../../../../FileManager/Dialogs/OpenWith.tsx";
|
||||||
import Dismiss from "../../../../Icons/Dismiss.tsx";
|
import Dismiss from "../../../../Icons/Dismiss.tsx";
|
||||||
import Edit from "../../../../Icons/Edit.tsx";
|
import Edit from "../../../../Icons/Edit.tsx";
|
||||||
|
|
@ -19,83 +15,68 @@ export interface FileViewerRowProps {
|
||||||
onDelete: (e: React.MouseEvent<HTMLElement>) => void;
|
onDelete: (e: React.MouseEvent<HTMLElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileViewerRow = memo(
|
const FileViewerRow = memo(({ viewer, onChange, onDelete }: FileViewerRowProps) => {
|
||||||
({ viewer, onChange, onDelete }: FileViewerRowProps) => {
|
const { t } = useTranslation("dashboard");
|
||||||
const { t } = useTranslation("dashboard");
|
const [extCached, setExtCached] = useState("");
|
||||||
const [extCached, setExtCached] = useState("");
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const onClose = useCallback(() => {
|
||||||
const onClose = useCallback(() => {
|
setEditOpen(false);
|
||||||
setEditOpen(false);
|
}, [setEditOpen]);
|
||||||
}, [setEditOpen]);
|
return (
|
||||||
return (
|
<TableRow sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover>
|
||||||
<TableRow
|
<FileViewerEditDialog viewer={viewer} onChange={onChange} open={editOpen} onClose={onClose} />
|
||||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
<NoWrapCell>
|
||||||
hover
|
<ViewerIcon viewer={viewer} />
|
||||||
>
|
</NoWrapCell>
|
||||||
<FileViewerEditDialog
|
<NoWrapCell>{t(`settings.${viewer.type}ViewerType`)}</NoWrapCell>
|
||||||
viewer={viewer}
|
<NoWrapCell>
|
||||||
onChange={onChange}
|
{t(viewer.display_name, {
|
||||||
open={editOpen}
|
ns: "application",
|
||||||
onClose={onClose}
|
})}
|
||||||
|
</NoWrapCell>
|
||||||
|
<NoWrapCell>
|
||||||
|
<DenseFilledTextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
required
|
||||||
|
value={extCached == "" ? viewer.exts.join() : extCached}
|
||||||
|
onBlur={() => {
|
||||||
|
onChange({
|
||||||
|
...viewer,
|
||||||
|
exts: extCached == "" ? viewer.exts : extCached?.split(",")?.map((ext) => ext.trim()),
|
||||||
|
});
|
||||||
|
setExtCached("");
|
||||||
|
}}
|
||||||
|
onChange={(e) => setExtCached(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<NoWrapCell>
|
</NoWrapCell>
|
||||||
<ViewerIcon viewer={viewer} />
|
<NoWrapCell>
|
||||||
</NoWrapCell>
|
{viewer.templates?.length ? t("settings.nMapping", { num: viewer.templates?.length }) : t("share.none")}
|
||||||
<NoWrapCell>{t(`settings.${viewer.type}ViewerType`)}</NoWrapCell>
|
</NoWrapCell>
|
||||||
<NoWrapCell>
|
<NoWrapCell>
|
||||||
{t(viewer.display_name, {
|
<StyledCheckbox
|
||||||
ns: "application",
|
size={"small"}
|
||||||
})}
|
checked={!viewer.disabled}
|
||||||
</NoWrapCell>
|
onChange={(e) =>
|
||||||
<NoWrapCell>
|
onChange({
|
||||||
<DenseFilledTextField
|
...viewer,
|
||||||
fullWidth
|
disabled: !e.target.checked,
|
||||||
multiline
|
})
|
||||||
required
|
}
|
||||||
value={extCached == "" ? viewer.exts.join() : extCached}
|
/>
|
||||||
onBlur={() => {
|
</NoWrapCell>
|
||||||
onChange({
|
<NoWrapCell>
|
||||||
...viewer,
|
<IconButton size={"small"} onClick={() => setEditOpen(true)}>
|
||||||
exts:
|
<Edit fontSize={"small"} />
|
||||||
extCached == ""
|
</IconButton>
|
||||||
? viewer.exts
|
{viewer.type != ViewerType.builtin && (
|
||||||
: extCached?.split(",")?.map((ext) => ext.trim()),
|
<IconButton size={"small"} onClick={onDelete}>
|
||||||
});
|
<Dismiss fontSize={"small"} />
|
||||||
setExtCached("");
|
|
||||||
}}
|
|
||||||
onChange={(e) => setExtCached(e.target.value)}
|
|
||||||
/>
|
|
||||||
</NoWrapCell>
|
|
||||||
<NoWrapCell>
|
|
||||||
{viewer.templates?.length
|
|
||||||
? t("settings.nMapping", { num: viewer.templates?.length })
|
|
||||||
: t("share.none")}
|
|
||||||
</NoWrapCell>
|
|
||||||
<NoWrapCell>
|
|
||||||
<StyledCheckbox
|
|
||||||
size={"small"}
|
|
||||||
checked={!viewer.disabled}
|
|
||||||
onChange={(e) =>
|
|
||||||
onChange({
|
|
||||||
...viewer,
|
|
||||||
disabled: !e.target.checked,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</NoWrapCell>
|
|
||||||
<NoWrapCell>
|
|
||||||
<IconButton size={"small"} onClick={() => setEditOpen(true)}>
|
|
||||||
<Edit fontSize={"small"} />
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{viewer.type != ViewerType.builtin && (
|
)}
|
||||||
<IconButton size={"small"} onClick={onDelete}>
|
</NoWrapCell>
|
||||||
<Dismiss fontSize={"small"} />
|
</TableRow>
|
||||||
</IconButton>
|
);
|
||||||
)}
|
});
|
||||||
</NoWrapCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default FileViewerRow;
|
export default FileViewerRow;
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,10 @@ import { useAppDispatch } from "../../../../redux/hooks.ts";
|
||||||
import { isTrueVal } from "../../../../session/utils.ts";
|
import { isTrueVal } from "../../../../session/utils.ts";
|
||||||
import SizeInput from "../../../Common/SizeInput.tsx";
|
import SizeInput from "../../../Common/SizeInput.tsx";
|
||||||
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
|
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
|
||||||
import {
|
import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
|
||||||
DenseFilledTextField,
|
|
||||||
StyledCheckbox,
|
|
||||||
} from "../../../Common/StyledComponents.tsx";
|
|
||||||
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
|
||||||
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
|
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
|
||||||
import {
|
import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
|
||||||
AccordionSummary,
|
|
||||||
StyledAccordion,
|
|
||||||
} from "../UserSession/SSOSettings.tsx";
|
|
||||||
|
|
||||||
export interface ExtractorsProps {
|
export interface ExtractorsProps {
|
||||||
values: {
|
values: {
|
||||||
|
|
@ -90,12 +84,11 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
const [testing, setTesting] = useState(false);
|
const [testing, setTesting] = useState(false);
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
const handleEnableChange =
|
const handleEnableChange = (name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
setSetting({
|
||||||
setSetting({
|
[name]: e.target.checked ? "1" : "0",
|
||||||
[name]: e.target.checked ? "1" : "0",
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const doTest = (name: string, executable: string) => {
|
const doTest = (name: string, executable: string) => {
|
||||||
setTesting(true);
|
setTesting(true);
|
||||||
|
|
@ -150,12 +143,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
onClick={() =>
|
onClick={() => doTest(e.name, values[e.executableSetting ?? ""])}
|
||||||
doTest(
|
|
||||||
e.name,
|
|
||||||
values[e.executableSetting ?? ""],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
loading={testing}
|
loading={testing}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
|
|
@ -170,9 +158,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.executableDes")}</NoMarginHelperText>
|
||||||
{t("settings.executableDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
)}
|
)}
|
||||||
|
|
@ -189,9 +175,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.maxSizeLocalDes")}</NoMarginHelperText>
|
||||||
{t("settings.maxSizeLocalDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
)}
|
)}
|
||||||
|
|
@ -208,17 +192,12 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.maxSizeRemoteDes")}</NoMarginHelperText>
|
||||||
{t("settings.maxSizeRemoteDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
)}
|
)}
|
||||||
{e.additionalSettings?.map((setting) => (
|
{e.additionalSettings?.map((setting) => (
|
||||||
<SettingForm
|
<SettingForm key={setting.name} lgWidth={12}>
|
||||||
key={setting.name}
|
|
||||||
lgWidth={12}
|
|
||||||
>
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
{setting.type === "switch" ? (
|
{setting.type === "switch" ? (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
|
@ -245,9 +224,7 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t(`settings.${setting.des}`)}</NoMarginHelperText>
|
||||||
{t(`settings.${setting.des}`)}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
))}
|
))}
|
||||||
|
|
@ -259,4 +236,4 @@ const Extractors = ({ values, setSetting }: ExtractorsProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Extractors;
|
export default Extractors;
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,9 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { AccordionDetails, Box, FormControl, FormControlLabel, InputAdornment, Typography } from "@mui/material";
|
||||||
AccordionDetails,
|
|
||||||
Box,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
InputAdornment,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { isTrueVal } from "../../../../session/utils.ts";
|
import { isTrueVal } from "../../../../session/utils.ts";
|
||||||
import {
|
import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
|
||||||
AccordionSummary,
|
|
||||||
StyledAccordion,
|
|
||||||
} from "../UserSession/SSOSettings.tsx";
|
|
||||||
import { ExpandMoreRounded } from "@mui/icons-material";
|
import { ExpandMoreRounded } from "@mui/icons-material";
|
||||||
import {
|
import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
|
||||||
DenseFilledTextField,
|
|
||||||
StyledCheckbox,
|
|
||||||
} from "../../../Common/StyledComponents.tsx";
|
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
|
import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx";
|
||||||
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
|
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
|
||||||
|
|
@ -131,25 +118,23 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
||||||
const [testing, setTesting] = useState(false);
|
const [testing, setTesting] = useState(false);
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
const handleEnableChange =
|
const handleEnableChange = (name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
setSetting({
|
||||||
setSetting({
|
[name]: e.target.checked ? "1" : "0",
|
||||||
[name]: e.target.checked ? "1" : "0",
|
});
|
||||||
|
const newValues = { ...values, [name]: e.target.checked ? "1" : "0" };
|
||||||
|
if (
|
||||||
|
(newValues["thumb_libreoffice_enabled"] === "1" || newValues["thumb_music_cover_enabled"] === "1") &&
|
||||||
|
newValues["thumb_builtin_enabled"] === "0" &&
|
||||||
|
newValues["thumb_vips_enabled"] === "0"
|
||||||
|
) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: t("settings.thumbDependencyWarning"),
|
||||||
|
variant: "warning",
|
||||||
|
action: DefaultCloseAction,
|
||||||
});
|
});
|
||||||
const newValues = { ...values, [name]: e.target.checked ? "1" : "0" };
|
}
|
||||||
if (
|
};
|
||||||
(newValues["thumb_libreoffice_enabled"] === "1" ||
|
|
||||||
newValues["thumb_music_cover_enabled"] === "1") &&
|
|
||||||
newValues["thumb_builtin_enabled"] === "0" &&
|
|
||||||
newValues["thumb_vips_enabled"] === "0"
|
|
||||||
) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: t("settings.thumbDependencyWarning"),
|
|
||||||
variant: "warning",
|
|
||||||
action: DefaultCloseAction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const doTest = (name: string, executable: string) => {
|
const doTest = (name: string, executable: string) => {
|
||||||
setTesting(true);
|
setTesting(true);
|
||||||
|
|
@ -205,12 +190,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
onClick={() =>
|
onClick={() => doTest(g.name, values[g.executableSetting ?? ""])}
|
||||||
doTest(
|
|
||||||
g.name,
|
|
||||||
values[g.executableSetting ?? ""],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
loading={testing}
|
loading={testing}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
|
|
@ -225,9 +205,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.executableDes")}</NoMarginHelperText>
|
||||||
{t("settings.executableDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
)}
|
)}
|
||||||
|
|
@ -245,18 +223,12 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.thumbMaxSizeDes")}</NoMarginHelperText>
|
||||||
{t("settings.thumbMaxSizeDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
)}
|
)}
|
||||||
{g.inputs?.map((input) => (
|
{g.inputs?.map((input) => (
|
||||||
<SettingForm
|
<SettingForm key={input.name} lgWidth={12} title={t(`settings.${input.label}`)}>
|
||||||
key={input.name}
|
|
||||||
lgWidth={12}
|
|
||||||
title={t(`settings.${input.label}`)}
|
|
||||||
>
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<DenseFilledTextField
|
<DenseFilledTextField
|
||||||
value={values[input.name]}
|
value={values[input.name]}
|
||||||
|
|
@ -267,9 +239,7 @@ const Generators = ({ values, setSetting }: GeneratorsProps) => {
|
||||||
}
|
}
|
||||||
required={!!input.required}
|
required={!!input.required}
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t(`settings.${input.des}`)}</NoMarginHelperText>
|
||||||
{t(`settings.${input.des}`)}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ const Queue = () => {
|
||||||
dispatch(getQueueMetrics())
|
dispatch(getQueueMetrics())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setMetrics(res);
|
setMetrics(res);
|
||||||
}).finally(() => {
|
})
|
||||||
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -30,26 +31,32 @@ const Queue = () => {
|
||||||
fetchQueueMetrics();
|
fetchQueueMetrics();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <Box component={"form"} ref={formRef} sx={{ p: 2, pt: 0 }}>
|
return (
|
||||||
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
|
<Box component={"form"} ref={formRef} sx={{ p: 2, pt: 0 }}>
|
||||||
<SecondaryButton
|
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
|
||||||
onClick={fetchQueueMetrics}
|
<SecondaryButton onClick={fetchQueueMetrics} disabled={loading} variant={"contained"} startIcon={<ArrowSync />}>
|
||||||
disabled={loading}
|
{t("node.refresh")}
|
||||||
variant={"contained"}
|
</SecondaryButton>
|
||||||
startIcon={<ArrowSync />}
|
</Stack>
|
||||||
>
|
<Grid container spacing={2}>
|
||||||
{t("node.refresh")}
|
{!loading &&
|
||||||
</SecondaryButton>
|
metrics.map((metric) => (
|
||||||
</Stack>
|
<QueueCard
|
||||||
<Grid container spacing={2}>
|
key={metric.name}
|
||||||
{!loading && metrics.map((metric) => (
|
metrics={metric}
|
||||||
<QueueCard key={metric.name} metrics={metric} queue={metric.name} settings={values} setSettings={setSettings} loading={loading} />
|
queue={metric.name}
|
||||||
))}
|
settings={values}
|
||||||
{loading && Array.from(Array(5)).map((_, index) => (
|
setSettings={setSettings}
|
||||||
<QueueCard key={`loading-${index}`} settings={values} setSettings={setSettings} loading={true} />
|
loading={loading}
|
||||||
))}
|
/>
|
||||||
</Grid>
|
))}
|
||||||
</Box>;
|
{loading &&
|
||||||
|
Array.from(Array(5)).map((_, index) => (
|
||||||
|
<QueueCard key={`loading-${index}`} settings={values} setSettings={setSettings} loading={true} />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Queue;
|
export default Queue;
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,7 @@ export const QueueCard = ({ queue, settings, metrics, setSettings, loading }: Qu
|
||||||
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} />
|
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} />
|
||||||
<Divider sx={{ my: 2 }} />
|
<Divider sx={{ my: 2 }} />
|
||||||
<Skeleton variant="rectangular" height={8} width="100%" sx={{ borderRadius: 1 }} />
|
<Skeleton variant="rectangular" height={8} width="100%" sx={{ borderRadius: 1 }} />
|
||||||
<Stack
|
<Stack spacing={isMobile ? 1 : 2} direction={isMobile ? "column" : "row"} sx={{ mt: 1 }}>
|
||||||
spacing={isMobile ? 1 : 2}
|
|
||||||
direction={isMobile ? "column" : "row"}
|
|
||||||
sx={{ mt: 1 }}
|
|
||||||
>
|
|
||||||
{Array.from(Array(5)).map((_, index) => (
|
{Array.from(Array(5)).map((_, index) => (
|
||||||
<Skeleton key={index} variant="text" width={isMobile ? "100%" : 80} height={20} />
|
<Skeleton key={index} variant="text" width={isMobile ? "100%" : 80} height={20} />
|
||||||
))}
|
))}
|
||||||
|
|
@ -48,115 +44,116 @@ export const QueueCard = ({ queue, settings, metrics, setSettings, loading }: Qu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Grid item xs={12} md={6} lg={4}>
|
return (
|
||||||
<BorderedCard>
|
<Grid item xs={12} md={6} lg={4}>
|
||||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
<BorderedCard>
|
||||||
<Typography variant="subtitle1" fontWeight={600}>{t(`queue.queueName_${queue}`)}</Typography>
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
<IconButton size="small" onClick={() => setSettingDialogOpen(true)}>
|
<Typography variant="subtitle1" fontWeight={600}>
|
||||||
<Setting fontSize="small" />
|
{t(`queue.queueName_${queue}`)}
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
<Typography variant="body2" color="text.secondary">{t(`queue.queueName_${queue}Des`)}</Typography>
|
|
||||||
<Divider sx={{ my: 2 }} />
|
|
||||||
{metrics && <>
|
|
||||||
<StorageBar>
|
|
||||||
<StoragePart
|
|
||||||
sx={{
|
|
||||||
backgroundColor: (theme) => theme.palette.success.light,
|
|
||||||
width: `${(metrics.success_tasks / metrics.submitted_tasks) * 100}%`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<StoragePart
|
|
||||||
sx={{
|
|
||||||
backgroundColor: (theme) => theme.palette.error.light,
|
|
||||||
width: `${(metrics.failure_tasks / metrics.submitted_tasks) * 100}%`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<StoragePart
|
|
||||||
sx={{
|
|
||||||
backgroundColor: (theme) => theme.palette.action.active,
|
|
||||||
width: `${(metrics.suspending_tasks / metrics.submitted_tasks) * 100}%`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<StoragePart
|
|
||||||
sx={{
|
|
||||||
backgroundColor: (theme) => theme.palette.info.light,
|
|
||||||
width: `${(metrics.busy_workers / metrics.submitted_tasks) * 100}%`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</StorageBar>
|
|
||||||
<Stack
|
|
||||||
spacing={isMobile ? 1 : 2}
|
|
||||||
direction={isMobile ? "column" : "row"}
|
|
||||||
sx={{ mt: 1 }}
|
|
||||||
>
|
|
||||||
<Typography variant={"caption"}>
|
|
||||||
<StorageBlock
|
|
||||||
sx={{
|
|
||||||
backgroundColor: (theme) => theme.palette.success.light,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{t("queue.success", {
|
|
||||||
count: metrics.success_tasks,
|
|
||||||
})}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant={"caption"}>
|
<IconButton size="small" onClick={() => setSettingDialogOpen(true)}>
|
||||||
<StorageBlock
|
<Setting fontSize="small" />
|
||||||
sx={{
|
</IconButton>
|
||||||
backgroundColor: (theme) => theme.palette.error.light,
|
</Box>
|
||||||
}}
|
<Typography variant="body2" color="text.secondary">
|
||||||
/>
|
{t(`queue.queueName_${queue}Des`)}
|
||||||
{t("queue.failed", {
|
</Typography>
|
||||||
count: metrics.failure_tasks,
|
<Divider sx={{ my: 2 }} />
|
||||||
})}
|
{metrics && (
|
||||||
</Typography>
|
<>
|
||||||
<Typography variant={"caption"}>
|
<StorageBar>
|
||||||
<StorageBlock
|
<StoragePart
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: (theme) => theme.palette.info.light,
|
backgroundColor: (theme) => theme.palette.success.light,
|
||||||
}}
|
width: `${(metrics.success_tasks / metrics.submitted_tasks) * 100}%`,
|
||||||
/>
|
}}
|
||||||
{t("queue.busyWorker", {
|
/>
|
||||||
count: metrics.busy_workers,
|
<StoragePart
|
||||||
})}
|
sx={{
|
||||||
</Typography>
|
backgroundColor: (theme) => theme.palette.error.light,
|
||||||
<Typography variant={"caption"}>
|
width: `${(metrics.failure_tasks / metrics.submitted_tasks) * 100}%`,
|
||||||
<StorageBlock
|
}}
|
||||||
sx={{
|
/>
|
||||||
backgroundColor: (theme) => theme.palette.action.active,
|
<StoragePart
|
||||||
}}
|
sx={{
|
||||||
/>
|
backgroundColor: (theme) => theme.palette.action.active,
|
||||||
{t("queue.suspending", {
|
width: `${(metrics.suspending_tasks / metrics.submitted_tasks) * 100}%`,
|
||||||
count: metrics.suspending_tasks,
|
}}
|
||||||
})}
|
/>
|
||||||
</Typography>
|
<StoragePart
|
||||||
<Typography variant={"caption"}>
|
sx={{
|
||||||
<StorageBlock
|
backgroundColor: (theme) => theme.palette.info.light,
|
||||||
sx={{
|
width: `${(metrics.busy_workers / metrics.submitted_tasks) * 100}%`,
|
||||||
backgroundColor: (theme) =>
|
}}
|
||||||
theme.palette.grey[
|
/>
|
||||||
theme.palette.mode === "light" ? 200 : 800
|
</StorageBar>
|
||||||
],
|
<Stack spacing={isMobile ? 1 : 2} direction={isMobile ? "column" : "row"} sx={{ mt: 1 }}>
|
||||||
}}
|
<Typography variant={"caption"}>
|
||||||
/>
|
<StorageBlock
|
||||||
{t("queue.submited", {
|
sx={{
|
||||||
count: metrics.submitted_tasks,
|
backgroundColor: (theme) => theme.palette.success.light,
|
||||||
})}
|
}}
|
||||||
</Typography>
|
/>
|
||||||
</Stack>
|
{t("queue.success", {
|
||||||
</>}
|
count: metrics.success_tasks,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant={"caption"}>
|
||||||
|
<StorageBlock
|
||||||
|
sx={{
|
||||||
|
backgroundColor: (theme) => theme.palette.error.light,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t("queue.failed", {
|
||||||
|
count: metrics.failure_tasks,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant={"caption"}>
|
||||||
|
<StorageBlock
|
||||||
|
sx={{
|
||||||
|
backgroundColor: (theme) => theme.palette.info.light,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t("queue.busyWorker", {
|
||||||
|
count: metrics.busy_workers,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant={"caption"}>
|
||||||
|
<StorageBlock
|
||||||
|
sx={{
|
||||||
|
backgroundColor: (theme) => theme.palette.action.active,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t("queue.suspending", {
|
||||||
|
count: metrics.suspending_tasks,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant={"caption"}>
|
||||||
|
<StorageBlock
|
||||||
|
sx={{
|
||||||
|
backgroundColor: (theme) => theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t("queue.submited", {
|
||||||
|
count: metrics.submitted_tasks,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{queue && (
|
{queue && (
|
||||||
<QueueSettingDialog
|
<QueueSettingDialog
|
||||||
open={settingDialogOpen}
|
open={settingDialogOpen}
|
||||||
onClose={() => setSettingDialogOpen(false)}
|
onClose={() => setSettingDialogOpen(false)}
|
||||||
queue={queue}
|
queue={queue}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setSettings={setSettings}
|
setSettings={setSettings}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</BorderedCard>
|
</BorderedCard>
|
||||||
</Grid>;
|
</Grid>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QueueCard;
|
export default QueueCard;
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,7 @@ const NoMarginHelperText = (props: any) => (
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const QueueSettingDialog = ({
|
const QueueSettingDialog = ({ open, onClose, queue, settings, setSettings }: QueueSettingDialogProps) => {
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
queue,
|
|
||||||
settings,
|
|
||||||
setSettings,
|
|
||||||
}: QueueSettingDialogProps) => {
|
|
||||||
const { t } = useTranslation("dashboard");
|
const { t } = useTranslation("dashboard");
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
const [localSettings, setLocalSettings] = useState<{ [key: string]: string }>({});
|
const [localSettings, setLocalSettings] = useState<{ [key: string]: string }>({});
|
||||||
|
|
@ -48,10 +42,10 @@ const QueueSettingDialog = ({
|
||||||
"backoff_factor",
|
"backoff_factor",
|
||||||
"backoff_max_duration",
|
"backoff_max_duration",
|
||||||
"max_retry",
|
"max_retry",
|
||||||
"retry_delay"
|
"retry_delay",
|
||||||
];
|
];
|
||||||
|
|
||||||
settingKeys.forEach(key => {
|
settingKeys.forEach((key) => {
|
||||||
const fullKey = `queue_${queue}_${key}`;
|
const fullKey = `queue_${queue}_${key}`;
|
||||||
queueSettings[key] = settings[fullKey] || "";
|
queueSettings[key] = settings[fullKey] || "";
|
||||||
});
|
});
|
||||||
|
|
@ -76,7 +70,7 @@ const QueueSettingDialog = ({
|
||||||
const updateLocalSetting = (key: string, value: string) => {
|
const updateLocalSetting = (key: string, value: string) => {
|
||||||
setLocalSettings((prev: { [key: string]: string }) => ({
|
setLocalSettings((prev: { [key: string]: string }) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[key]: value
|
[key]: value,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -95,25 +89,25 @@ const QueueSettingDialog = ({
|
||||||
maxWidth: "sm",
|
maxWidth: "sm",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box component={"form"} ref={formRef} sx={{ display: "flex", flexDirection: "column", gap: 2, mt: 1, px: 3, pb: 2 }}>
|
<Box
|
||||||
|
component={"form"}
|
||||||
|
ref={formRef}
|
||||||
|
sx={{ display: "flex", flexDirection: "column", gap: 2, mt: 1, px: 3, pb: 2 }}
|
||||||
|
>
|
||||||
<SettingForm title={t("queue.workerNum")} lgWidth={12}>
|
<SettingForm title={t("queue.workerNum")} lgWidth={12}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<DenseFilledTextField
|
<DenseFilledTextField
|
||||||
value={localSettings.worker_num || ""}
|
value={localSettings.worker_num || ""}
|
||||||
onChange={(e) => updateLocalSetting("worker_num", e.target.value)}
|
onChange={(e) => updateLocalSetting("worker_num", e.target.value)}
|
||||||
type="number"
|
type="number"
|
||||||
slotProps={
|
slotProps={{
|
||||||
{
|
htmlInput: {
|
||||||
htmlInput: {
|
min: 1,
|
||||||
min: 1,
|
},
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("queue.workerNumDes")}</NoMarginHelperText>
|
||||||
{t("queue.workerNumDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -128,9 +122,7 @@ const QueueSettingDialog = ({
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("queue.maxExecutionDes")}</NoMarginHelperText>
|
||||||
{t("queue.maxExecutionDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -140,19 +132,15 @@ const QueueSettingDialog = ({
|
||||||
value={localSettings.backoff_factor || ""}
|
value={localSettings.backoff_factor || ""}
|
||||||
onChange={(e) => updateLocalSetting("backoff_factor", e.target.value)}
|
onChange={(e) => updateLocalSetting("backoff_factor", e.target.value)}
|
||||||
type="number"
|
type="number"
|
||||||
slotProps={
|
slotProps={{
|
||||||
{
|
htmlInput: {
|
||||||
htmlInput: {
|
min: 1,
|
||||||
min: 1,
|
step: 0.1,
|
||||||
step: 0.1,
|
},
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("queue.backoffFactorDes")}</NoMarginHelperText>
|
||||||
{t("queue.backoffFactorDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -162,18 +150,14 @@ const QueueSettingDialog = ({
|
||||||
value={localSettings.backoff_max_duration || ""}
|
value={localSettings.backoff_max_duration || ""}
|
||||||
onChange={(e) => updateLocalSetting("backoff_max_duration", e.target.value)}
|
onChange={(e) => updateLocalSetting("backoff_max_duration", e.target.value)}
|
||||||
type="number"
|
type="number"
|
||||||
slotProps={
|
slotProps={{
|
||||||
{
|
htmlInput: {
|
||||||
htmlInput: {
|
min: 1,
|
||||||
min: 1,
|
},
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("queue.backoffMaxDurationDes")}</NoMarginHelperText>
|
||||||
{t("queue.backoffMaxDurationDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -183,18 +167,14 @@ const QueueSettingDialog = ({
|
||||||
value={localSettings.max_retry || ""}
|
value={localSettings.max_retry || ""}
|
||||||
onChange={(e) => updateLocalSetting("max_retry", e.target.value)}
|
onChange={(e) => updateLocalSetting("max_retry", e.target.value)}
|
||||||
type="number"
|
type="number"
|
||||||
slotProps={
|
slotProps={{
|
||||||
{
|
htmlInput: {
|
||||||
htmlInput: {
|
min: 0,
|
||||||
min: 0,
|
},
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("queue.maxRetryDes")}</NoMarginHelperText>
|
||||||
{t("queue.maxRetryDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
|
|
||||||
|
|
@ -204,18 +184,14 @@ const QueueSettingDialog = ({
|
||||||
value={localSettings.retry_delay || ""}
|
value={localSettings.retry_delay || ""}
|
||||||
onChange={(e) => updateLocalSetting("retry_delay", e.target.value)}
|
onChange={(e) => updateLocalSetting("retry_delay", e.target.value)}
|
||||||
type="number"
|
type="number"
|
||||||
slotProps={
|
slotProps={{
|
||||||
{
|
htmlInput: {
|
||||||
htmlInput: {
|
min: 0,
|
||||||
min: 0,
|
},
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("queue.retryDelayDes")}</NoMarginHelperText>
|
||||||
{t("queue.retryDelayDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</SettingForm>
|
</SettingForm>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -223,4 +199,4 @@ const QueueSettingDialog = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QueueSettingDialog;
|
export default QueueSettingDialog;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,7 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import {
|
import { Box, Collapse, Divider, IconButton, InputAdornment, Stack } from "@mui/material";
|
||||||
Box,
|
import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents.tsx";
|
||||||
Collapse,
|
|
||||||
Divider,
|
|
||||||
IconButton,
|
|
||||||
InputAdornment,
|
|
||||||
Stack,
|
|
||||||
} from "@mui/material";
|
|
||||||
import {
|
|
||||||
DenseFilledTextField,
|
|
||||||
SecondaryButton,
|
|
||||||
} from "../../../Common/StyledComponents.tsx";
|
|
||||||
import FormControl from "@mui/material/FormControl";
|
import FormControl from "@mui/material/FormControl";
|
||||||
import Dismiss from "../../../Icons/Dismiss.tsx";
|
import Dismiss from "../../../Icons/Dismiss.tsx";
|
||||||
import Add from "../../../Icons/Add.tsx";
|
import Add from "../../../Icons/Add.tsx";
|
||||||
|
|
@ -29,12 +19,11 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
|
||||||
return urls.split(",").map((url) => url);
|
return urls.split(",").map((url) => url);
|
||||||
}, [urls]);
|
}, [urls]);
|
||||||
|
|
||||||
const onUrlChange =
|
const onUrlChange = (index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
const newUrls = [...urlSplit];
|
||||||
const newUrls = [...urlSplit];
|
newUrls[index] = e.target.value;
|
||||||
newUrls[index] = e.target.value;
|
onChange(newUrls.join(","));
|
||||||
onChange(newUrls.join(","));
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const removeUrl = (index: number) => () => {
|
const removeUrl = (index: number) => () => {
|
||||||
const newUrls = [...urlSplit];
|
const newUrls = [...urlSplit];
|
||||||
|
|
@ -58,9 +47,7 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.primarySiteURLDes")}</NoMarginHelperText>
|
||||||
{t("settings.primarySiteURLDes")}
|
|
||||||
</NoMarginHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Divider />
|
<Divider />
|
||||||
<NoMarginHelperText>{t("settings.secondaryDes")}</NoMarginHelperText>
|
<NoMarginHelperText>{t("settings.secondaryDes")}</NoMarginHelperText>
|
||||||
|
|
@ -93,11 +80,7 @@ const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => {
|
||||||
))}
|
))}
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
<Box sx={{ mt: "0!important" }}>
|
<Box sx={{ mt: "0!important" }}>
|
||||||
<SecondaryButton
|
<SecondaryButton variant={"contained"} startIcon={<Add />} onClick={() => onChange(`${urls},`)}>
|
||||||
variant={"contained"}
|
|
||||||
startIcon={<Add />}
|
|
||||||
onClick={() => onChange(`${urls},`)}
|
|
||||||
>
|
|
||||||
{t("settings.addSecondary")}
|
{t("settings.addSecondary")}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ const UserSession = () => {
|
||||||
control={<Switch checked={false} />}
|
control={<Switch checked={false} />}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
{t("vas.disableSubAddressEmail")}
|
{t("vas.disableSubAddressEmail")}
|
||||||
<ProChip label="Pro" color="primary" size="small" />
|
<ProChip label="Pro" color="primary" size="small" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
export { default as BasicInfoSection } from './BasicInfoSection';
|
export { default as BasicInfoSection } from "./BasicInfoSection";
|
||||||
export { default as DownloadSection } from './DownloadSection';
|
export { default as DownloadSection } from "./DownloadSection";
|
||||||
export * from './magicVars';
|
export * from "./magicVars";
|
||||||
export { default as MediaMetadataSection } from './MediaMetadataSection';
|
export { default as MediaMetadataSection } from "./MediaMetadataSection";
|
||||||
export { default as StorageAndUploadSection } from './StorageAndUploadSection';
|
export { default as StorageAndUploadSection } from "./StorageAndUploadSection";
|
||||||
export { default as ThumbnailsSection } from './ThumbnailsSection';
|
export { default as ThumbnailsSection } from "./ThumbnailsSection";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export const commonMagicVars: MagicVar[] = [
|
||||||
|
|
||||||
export const pathMagicVars: MagicVar[] = [
|
export const pathMagicVars: MagicVar[] = [
|
||||||
...commonMagicVars,
|
...commonMagicVars,
|
||||||
{ name: "{path}", value: "policy.magicVar.path", example: "/path/to/" }
|
{ name: "{path}", value: "policy.magicVar.path", example: "/path/to/" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const fileMagicVars: MagicVar[] = [
|
export const fileMagicVars: MagicVar[] = [
|
||||||
|
|
@ -31,4 +31,4 @@ export const fileMagicVars: MagicVar[] = [
|
||||||
{ name: "{ext}", value: "policy.magicVar.extension", example: ".jpg" },
|
{ name: "{ext}", value: "policy.magicVar.extension", example: ".jpg" },
|
||||||
{ name: "{originname_without_ext}", value: "policy.magicVar.originFileNameNoext", example: "example" },
|
{ name: "{originname_without_ext}", value: "policy.magicVar.originFileNameNoext", example: "example" },
|
||||||
{ name: "{uuid}", value: "policy.magicVar.uuidV4", example: "550e8400-e29b-41d4-a716-446655440000" },
|
{ name: "{uuid}", value: "policy.magicVar.uuidV4", example: "550e8400-e29b-41d4-a716-446655440000" },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
||||||
height: 8,
|
height: 8,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||||
backgroundColor:
|
backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||||
theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
|
||||||
},
|
},
|
||||||
[`& .${linearProgressClasses.bar}`]: {
|
[`& .${linearProgressClasses.bar}`]: {
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,10 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
const scriptLoadedRef = useRef(false);
|
const scriptLoadedRef = useRef(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
|
|
||||||
const capInstanceURL = useAppSelector(
|
const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url);
|
||||||
(state) => state.siteConfig.basic.config.captcha_cap_instance_url,
|
const capKeyID = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_key_id);
|
||||||
);
|
|
||||||
const capKeyID = useAppSelector(
|
|
||||||
(state) => state.siteConfig.basic.config.captcha_cap_key_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Keep callback reference up to date
|
// Keep callback reference up to date
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onStateChangeRef.current = onStateChange;
|
onStateChangeRef.current = onStateChange;
|
||||||
|
|
@ -36,18 +32,18 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
const applyFullWidthStyles = (widget: HTMLElement) => {
|
const applyFullWidthStyles = (widget: HTMLElement) => {
|
||||||
const applyStyles = () => {
|
const applyStyles = () => {
|
||||||
// Style widget container
|
// Style widget container
|
||||||
widget.style.width = '100%';
|
widget.style.width = "100%";
|
||||||
widget.style.display = 'block';
|
widget.style.display = "block";
|
||||||
widget.style.boxSizing = 'border-box';
|
widget.style.boxSizing = "border-box";
|
||||||
|
|
||||||
// Style internal captcha element
|
// Style internal captcha element
|
||||||
const captchaElement = widget.shadowRoot?.querySelector('.captcha') || widget.querySelector('.captcha');
|
const captchaElement = widget.shadowRoot?.querySelector(".captcha") || widget.querySelector(".captcha");
|
||||||
if (captchaElement) {
|
if (captchaElement) {
|
||||||
const captchaEl = captchaElement as HTMLElement;
|
const captchaEl = captchaElement as HTMLElement;
|
||||||
captchaEl.style.width = '100%';
|
captchaEl.style.width = "100%";
|
||||||
captchaEl.style.maxWidth = 'none';
|
captchaEl.style.maxWidth = "none";
|
||||||
captchaEl.style.minWidth = '0';
|
captchaEl.style.minWidth = "0";
|
||||||
captchaEl.style.boxSizing = 'border-box';
|
captchaEl.style.boxSizing = "border-box";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -60,13 +56,13 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(widget, {
|
observer.observe(widget, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
attributes: true
|
attributes: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fallback timeout
|
// Fallback timeout
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
applyStyles();
|
applyStyles();
|
||||||
|
|
@ -85,34 +81,34 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
widgetRef.current.remove?.();
|
widgetRef.current.remove?.();
|
||||||
widgetRef.current = null;
|
widgetRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear container
|
// Clear container
|
||||||
captchaRef.current.innerHTML = "";
|
captchaRef.current.innerHTML = "";
|
||||||
|
|
||||||
if (typeof window !== "undefined" && (window as any).Cap) {
|
if (typeof window !== "undefined" && (window as any).Cap) {
|
||||||
const widget = document.createElement("cap-widget");
|
const widget = document.createElement("cap-widget");
|
||||||
widget.setAttribute("data-cap-api-endpoint", `${capInstanceURL.replace(/\/$/, "")}/${capKeyID}/api/`);
|
widget.setAttribute("data-cap-api-endpoint", `${capInstanceURL.replace(/\/$/, "")}/${capKeyID}/api/`);
|
||||||
widget.id = "cap-widget";
|
widget.id = "cap-widget";
|
||||||
|
|
||||||
// Set internationalization attributes (Cap official i18n format)
|
// Set internationalization attributes (Cap official i18n format)
|
||||||
widget.setAttribute("data-cap-i18n-initial-state", t("captcha.cap.human"));
|
widget.setAttribute("data-cap-i18n-initial-state", t("captcha.cap.human"));
|
||||||
widget.setAttribute("data-cap-i18n-verifying-label", t("captcha.cap.verifying"));
|
widget.setAttribute("data-cap-i18n-verifying-label", t("captcha.cap.verifying"));
|
||||||
widget.setAttribute("data-cap-i18n-solved-label", t("captcha.cap.verified"));
|
widget.setAttribute("data-cap-i18n-solved-label", t("captcha.cap.verified"));
|
||||||
|
|
||||||
captchaRef.current.appendChild(widget);
|
captchaRef.current.appendChild(widget);
|
||||||
|
|
||||||
widget.addEventListener("solve", (e: any) => {
|
widget.addEventListener("solve", (e: any) => {
|
||||||
const token = e.detail.token;
|
const token = e.detail.token;
|
||||||
if (token) {
|
if (token) {
|
||||||
onStateChangeRef.current({ ticket: token });
|
onStateChangeRef.current({ ticket: token });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply fullWidth styles if needed
|
// Apply fullWidth styles if needed
|
||||||
if (fullWidth) {
|
if (fullWidth) {
|
||||||
applyFullWidthStyles(widget);
|
applyFullWidthStyles(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetRef.current = widget;
|
widgetRef.current = widget;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -130,7 +126,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
|
|
||||||
const scriptId = "cap-widget-script";
|
const scriptId = "cap-widget-script";
|
||||||
let script = document.getElementById(scriptId) as HTMLScriptElement;
|
let script = document.getElementById(scriptId) as HTMLScriptElement;
|
||||||
|
|
||||||
const initWidget = () => {
|
const initWidget = () => {
|
||||||
scriptLoadedRef.current = true;
|
scriptLoadedRef.current = true;
|
||||||
// Add a small delay to ensure DOM is ready
|
// Add a small delay to ensure DOM is ready
|
||||||
|
|
@ -174,11 +170,11 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// Container full width when needed
|
// Container full width when needed
|
||||||
...(fullWidth && { width: "100%" }),
|
...(fullWidth && { width: "100%" }),
|
||||||
|
|
||||||
// CSS variables for Cloudreve theme adaptation
|
// CSS variables for Cloudreve theme adaptation
|
||||||
"& cap-widget": {
|
"& cap-widget": {
|
||||||
"--cap-border-radius": `${theme.shape.borderRadius}px`,
|
"--cap-border-radius": `${theme.shape.borderRadius}px`,
|
||||||
|
|
@ -205,4 +201,4 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CapCaptcha;
|
export default CapCaptcha;
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@ export interface CaptchaParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Captcha = (props: CaptchaProps) => {
|
export const Captcha = (props: CaptchaProps) => {
|
||||||
const captchaType = useAppSelector(
|
const captchaType = useAppSelector((state) => state.siteConfig.basic.config.captcha_type);
|
||||||
(state) => state.siteConfig.basic.config.captcha_type,
|
|
||||||
);
|
|
||||||
|
|
||||||
// const recaptcha = useRecaptcha(setCaptchaLoading);
|
// const recaptcha = useRecaptcha(setCaptchaLoading);
|
||||||
// const tcaptcha = useTCaptcha(setCaptchaLoading);
|
// const tcaptcha = useTCaptcha(setCaptchaLoading);
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,7 @@ export interface DefaultCaptchaProps {
|
||||||
generation: number;
|
generation: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultCaptcha = ({
|
const DefaultCaptcha = ({ onStateChange, generation, ...rest }: DefaultCaptchaProps) => {
|
||||||
onStateChange,
|
|
||||||
generation,
|
|
||||||
...rest
|
|
||||||
}: DefaultCaptchaProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
@ -92,8 +88,9 @@ const DefaultCaptcha = ({
|
||||||
htmlInput: {
|
htmlInput: {
|
||||||
name: "captcha",
|
name: "captcha",
|
||||||
id: "captcha",
|
id: "captcha",
|
||||||
}
|
},
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,11 @@ window.recaptchaOptions = {
|
||||||
useRecaptchaNet: true,
|
useRecaptchaNet: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReCaptchaV2 = ({
|
const ReCaptchaV2 = ({ onStateChange, generation, ...rest }: ReCaptchaV2Props) => {
|
||||||
onStateChange,
|
|
||||||
generation,
|
|
||||||
...rest
|
|
||||||
}: ReCaptchaV2Props) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const captchaRef = useRef();
|
const captchaRef = useRef();
|
||||||
const reCaptchaKey = useAppSelector(
|
const reCaptchaKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_ReCaptchaKey);
|
||||||
(state) => state.siteConfig.basic.config.captcha_ReCaptchaKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
const refreshCaptcha = async () => {
|
const refreshCaptcha = async () => {
|
||||||
captchaRef.current?.reset();
|
captchaRef.current?.reset();
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,11 @@ export interface TurnstileProps {
|
||||||
generation: number;
|
generation: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TurnstileCaptcha = ({
|
const TurnstileCaptcha = ({ onStateChange, generation, ...rest }: TurnstileProps) => {
|
||||||
onStateChange,
|
|
||||||
generation,
|
|
||||||
...rest
|
|
||||||
}: TurnstileProps) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const captchaRef = useRef();
|
const captchaRef = useRef();
|
||||||
const turnstileKey = useAppSelector(
|
const turnstileKey = useAppSelector((state) => state.siteConfig.basic.config.turnstile_site_id);
|
||||||
(state) => state.siteConfig.basic.config.turnstile_site_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
const refreshCaptcha = async () => {
|
const refreshCaptcha = async () => {
|
||||||
captchaRef.current?.reset();
|
captchaRef.current?.reset();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { Box, CircularProgress, circularProgressClasses, CircularProgressProps } from "@mui/material";
|
||||||
Box,
|
|
||||||
CircularProgress,
|
|
||||||
circularProgressClasses,
|
|
||||||
CircularProgressProps,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
export interface FacebookCircularProgressProps extends CircularProgressProps {
|
export interface FacebookCircularProgressProps extends CircularProgressProps {
|
||||||
|
|
@ -11,38 +6,34 @@ export interface FacebookCircularProgressProps extends CircularProgressProps {
|
||||||
fgColor?: string;
|
fgColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FacebookCircularProgress = forwardRef(
|
const FacebookCircularProgress = forwardRef(({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => {
|
||||||
({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => {
|
return (
|
||||||
return (
|
<Box sx={{ position: "relative", ...sx }} ref={ref}>
|
||||||
<Box sx={{ position: "relative", ...sx }} ref={ref}>
|
<CircularProgress
|
||||||
<CircularProgress
|
variant="determinate"
|
||||||
variant="determinate"
|
sx={{
|
||||||
sx={{
|
color: (theme) => bgColor ?? theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
||||||
color: (theme) =>
|
}}
|
||||||
bgColor ??
|
size={40}
|
||||||
theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
|
thickness={4}
|
||||||
}}
|
{...rest}
|
||||||
size={40}
|
value={100}
|
||||||
thickness={4}
|
/>
|
||||||
{...rest}
|
<CircularProgress
|
||||||
value={100}
|
sx={{
|
||||||
/>
|
color: (theme) => fgColor ?? theme.palette.primary.main,
|
||||||
<CircularProgress
|
position: "absolute",
|
||||||
sx={{
|
left: 0,
|
||||||
color: (theme) => fgColor ?? theme.palette.primary.main,
|
[`& .${circularProgressClasses.circle}`]: {
|
||||||
position: "absolute",
|
strokeLinecap: "round",
|
||||||
left: 0,
|
},
|
||||||
[`& .${circularProgressClasses.circle}`]: {
|
}}
|
||||||
strokeLinecap: "round",
|
size={40}
|
||||||
},
|
thickness={4}
|
||||||
}}
|
{...rest}
|
||||||
size={40}
|
/>
|
||||||
thickness={4}
|
</Box>
|
||||||
{...rest}
|
);
|
||||||
/>
|
});
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default FacebookCircularProgress;
|
export default FacebookCircularProgress;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
.fade-enter {
|
.fade-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.fade-enter-active {
|
.fade-enter-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.fade-exit {
|
.fade-exit {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.fade-exit-active {
|
.fade-exit-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
.fade-exit-active {
|
.fade-exit-active {
|
||||||
transition: opacity 150ms;
|
transition: opacity 150ms;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,10 @@
|
||||||
import {
|
import { InputAdornment, TextField, TextFieldProps, useMediaQuery, useTheme } from "@mui/material";
|
||||||
InputAdornment,
|
|
||||||
TextField,
|
|
||||||
TextFieldProps,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
export interface OutlineIconTextFieldProps extends TextFieldProps<"outlined"> {
|
export interface OutlineIconTextFieldProps extends TextFieldProps<"outlined"> {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OutlineIconTextField = ({
|
export const OutlineIconTextField = ({ icon, ...rest }: OutlineIconTextFieldProps) => {
|
||||||
icon,
|
|
||||||
...rest
|
|
||||||
}: OutlineIconTextFieldProps) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
return (
|
return (
|
||||||
|
|
@ -21,11 +12,10 @@ export const OutlineIconTextField = ({
|
||||||
{...rest}
|
{...rest}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
startAdornment: !isMobile && (
|
startAdornment: !isMobile && <InputAdornment position="start">{icon}</InputAdornment>,
|
||||||
<InputAdornment position="start">{icon}</InputAdornment>
|
|
||||||
),
|
|
||||||
...rest.InputProps,
|
...rest.InputProps,
|
||||||
}
|
},
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,7 @@ export interface NothingProps {
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Nothing({
|
export default function Nothing({ primary, secondary, top = 20, size = 1 }: NothingProps) {
|
||||||
primary,
|
|
||||||
secondary,
|
|
||||||
top = 20,
|
|
||||||
size = 1,
|
|
||||||
}: NothingProps) {
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -41,10 +36,7 @@ export default function Nothing({
|
||||||
{primary}
|
{primary}
|
||||||
</Typography>
|
</Typography>
|
||||||
{secondary && (
|
{secondary && (
|
||||||
<Typography
|
<Typography variant={"body2"} sx={{ color: (theme) => theme.palette.action.disabled }}>
|
||||||
variant={"body2"}
|
|
||||||
sx={{ color: (theme) => theme.palette.action.disabled }}
|
|
||||||
>
|
|
||||||
{secondary}
|
{secondary}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,10 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useLayoutEffect, useRef, useState } from "react";
|
import { useLayoutEffect, useRef, useState } from "react";
|
||||||
import {
|
import { Box, ListItemIcon, ListItemText, Menu, MenuItem, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { StyledTab, StyledTabs } from "./StyledComponents.tsx";
|
import { StyledTab, StyledTabs } from "./StyledComponents.tsx";
|
||||||
import CaretDown from "../Icons/CaretDown.tsx";
|
import CaretDown from "../Icons/CaretDown.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||||
bindMenu,
|
|
||||||
bindTrigger,
|
|
||||||
usePopupState,
|
|
||||||
} from "material-ui-popup-state/hooks";
|
|
||||||
|
|
||||||
export interface Tab<T> {
|
export interface Tab<T> {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
|
|
@ -31,11 +18,7 @@ export interface ResponsiveTabsProps<T> {
|
||||||
onChange: (event: React.SyntheticEvent, value: T) => void;
|
onChange: (event: React.SyntheticEvent, value: T) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResponsiveTabs = <T,>({
|
const ResponsiveTabs = <T,>({ tabs, value, onChange }: ResponsiveTabsProps<T>) => {
|
||||||
tabs,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}: ResponsiveTabsProps<T>) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
const [hideTabs, setHideTabs] = useState(false);
|
const [hideTabs, setHideTabs] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -14,78 +14,71 @@ export const DefaultCloseAction = (snackbarId: SnackbarKey | undefined) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button onClick={() => closeSnackbar(snackbarId)} color="inherit" size="small">
|
||||||
onClick={() => closeSnackbar(snackbarId)}
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t("dismiss", { ns: "common" })}
|
{t("dismiss", { ns: "common" })}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ErrorListDetailAction =
|
export const ErrorListDetailAction = (error: Response<any>) => (snackbarId: SnackbarKey | undefined) => {
|
||||||
(error: Response<any>) => (snackbarId: SnackbarKey | undefined) => {
|
const { t } = useTranslation();
|
||||||
const { t } = useTranslation();
|
const dispatch = useAppDispatch();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const Close = DefaultCloseAction(snackbarId);
|
const Close = DefaultCloseAction(snackbarId);
|
||||||
|
|
||||||
const showDetails = useCallback(() => {
|
const showDetails = useCallback(() => {
|
||||||
dispatch(showAggregatedErrorDialog(error));
|
dispatch(showAggregatedErrorDialog(error));
|
||||||
closeSnackbar(snackbarId);
|
closeSnackbar(snackbarId);
|
||||||
}, [dispatch, error, snackbarId]);
|
}, [dispatch, error, snackbarId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={showDetails} color="inherit" size="small">
|
<Button onClick={showDetails} color="inherit" size="small">
|
||||||
{t("common:errorDetails")}
|
{t("common:errorDetails")}
|
||||||
</Button>
|
</Button>
|
||||||
{Close}
|
{Close}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewDstAction =
|
export const ViewDstAction = (dst: string) => (snackbarId: SnackbarKey | undefined) => {
|
||||||
(dst: string) => (snackbarId: SnackbarKey | undefined) => {
|
const { t } = useTranslation();
|
||||||
const { t } = useTranslation();
|
const dispatch = useAppDispatch();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const Close = DefaultCloseAction(snackbarId);
|
const Close = DefaultCloseAction(snackbarId);
|
||||||
|
|
||||||
const viewDst = useCallback(() => {
|
const viewDst = useCallback(() => {
|
||||||
dispatch(navigateToPath(FileManagerIndex.main, dst));
|
dispatch(navigateToPath(FileManagerIndex.main, dst));
|
||||||
closeSnackbar(snackbarId);
|
closeSnackbar(snackbarId);
|
||||||
}, [dispatch, snackbarId]);
|
}, [dispatch, snackbarId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={viewDst} color="inherit" size="small">
|
<Button onClick={viewDst} color="inherit" size="small">
|
||||||
{t("application:modals.view")}
|
{t("application:modals.view")}
|
||||||
</Button>
|
</Button>
|
||||||
{Close}
|
{Close}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewDownloadLogAction =
|
export const ViewDownloadLogAction = (downloadId: string) => (_snackbarId: SnackbarKey | undefined) => {
|
||||||
(downloadId: string) => (_snackbarId: SnackbarKey | undefined) => {
|
const { t } = useTranslation();
|
||||||
const { t } = useTranslation();
|
const dispatch = useAppDispatch();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const viewLogs = useCallback(() => {
|
const viewLogs = useCallback(() => {
|
||||||
dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId }));
|
dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId }));
|
||||||
}, [dispatch, downloadId]);
|
}, [dispatch, downloadId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={viewLogs} color="inherit" size="small">
|
<Button onClick={viewLogs} color="inherit" size="small">
|
||||||
{t("application:fileManager.details")}
|
{t("application:fileManager.details")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewTaskAction =
|
export const ViewTaskAction =
|
||||||
(path: string = "/tasks") =>
|
(path: string = "/tasks") =>
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,7 @@ export interface TimeBadgeProps extends TypographyProps {
|
||||||
timeAgoThreshold?: number;
|
timeAgoThreshold?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimeBadge = ({
|
const TimeBadge = ({ timeAgoThreshold = defaultTimeAgoThreshold, datetime, sx, ...rest }: TimeBadgeProps) => {
|
||||||
timeAgoThreshold = defaultTimeAgoThreshold,
|
|
||||||
datetime,
|
|
||||||
sx,
|
|
||||||
...rest
|
|
||||||
}: TimeBadgeProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const timeStr = useMemo(() => {
|
const timeStr = useMemo(() => {
|
||||||
if (typeof datetime === "string") {
|
if (typeof datetime === "string") {
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,7 @@ const UserBadge = ({ textProps, user, uid, ...rest }: UserBadgeProps) => {
|
||||||
maxWidth: "150px",
|
maxWidth: "150px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<UserAvatar overwriteTextSize user={user} onUserLoaded={(u) => setUserLoaded(u)} uid={uid} {...rest} />
|
||||||
overwriteTextSize
|
|
||||||
user={user}
|
|
||||||
onUserLoaded={(u) => setUserLoaded(u)}
|
|
||||||
uid={uid}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
<BadgeText {...textProps}>
|
<BadgeText {...textProps}>
|
||||||
{userLoaded ? (
|
{userLoaded ? (
|
||||||
userLoaded.id ? (
|
userLoaded.id ? (
|
||||||
|
|
@ -50,9 +44,7 @@ const UserBadge = ({ textProps, user, uid, ...rest }: UserBadgeProps) => {
|
||||||
)}
|
)}
|
||||||
</BadgeText>
|
</BadgeText>
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
{userLoaded && (
|
{userLoaded && <UserPopover user={userLoaded} {...bindPopover(popupState)} />}
|
||||||
<UserPopover user={userLoaded} {...bindPopover(popupState)} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { Box, Button, PopoverProps, styled, Tooltip, Typography } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
PopoverProps,
|
|
||||||
styled,
|
|
||||||
Tooltip,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import HoverPopover from "material-ui-popup-state/HoverPopover";
|
import HoverPopover from "material-ui-popup-state/HoverPopover";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
@ -26,15 +19,7 @@ const ActionButton = styled(Button)({
|
||||||
minWidth: "initial",
|
minWidth: "initial",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UserProfile = ({
|
export const UserProfile = ({ user, open, displayOnly }: { user: User; open: boolean; displayOnly?: boolean }) => {
|
||||||
user,
|
|
||||||
open,
|
|
||||||
displayOnly,
|
|
||||||
}: {
|
|
||||||
user: User;
|
|
||||||
open: boolean;
|
|
||||||
displayOnly?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -55,22 +40,14 @@ export const UserProfile = ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<UserAvatar overwriteTextSize user={user} sx={{ width: 80, height: 80 }} />
|
||||||
overwriteTextSize
|
|
||||||
user={user}
|
|
||||||
sx={{ width: 80, height: 80 }}
|
|
||||||
/>
|
|
||||||
<Box sx={{ ml: 2 }}>
|
<Box sx={{ ml: 2 }}>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<Typography variant={"h6"} fontWeight={600}>
|
<Typography variant={"h6"} fontWeight={600}>
|
||||||
{user.id ? user.nickname : t("application:modals.anonymous")}
|
{user.id ? user.nickname : t("application:modals.anonymous")}
|
||||||
</Typography>
|
</Typography>
|
||||||
{displayOnly && (
|
{displayOnly && (
|
||||||
<Typography
|
<Typography variant={"body2"} sx={{ ml: 1 }} color={"text.secondary"}>
|
||||||
variant={"body2"}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
color={"text.secondary"}
|
|
||||||
>
|
|
||||||
{loadedUser?.group ? loadedUser.group.name : ""}
|
{loadedUser?.group ? loadedUser.group.name : ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
@ -85,12 +62,7 @@ export const UserProfile = ({
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey={"setting.accountCreatedAt"}
|
i18nKey={"setting.accountCreatedAt"}
|
||||||
ns={"application"}
|
ns={"application"}
|
||||||
components={[
|
components={[<TimeBadge variant={"inherit"} datetime={loadedUser.created_at} />]}
|
||||||
<TimeBadge
|
|
||||||
variant={"inherit"}
|
|
||||||
datetime={loadedUser.created_at}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DraggableDialog, {
|
import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx";
|
||||||
StyledDialogContentText,
|
|
||||||
} from "./DraggableDialog.tsx";
|
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { closeAggregatedErrorDialog } from "../../redux/globalStateSlice.ts";
|
import { closeAggregatedErrorDialog } from "../../redux/globalStateSlice.ts";
|
||||||
import {
|
import {
|
||||||
|
|
@ -44,18 +42,9 @@ const ErrorTable = (props: ErrorTableProps) => {
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{Object.keys(props.errors).map((id) => (
|
{Object.keys(props.errors).map((id) => (
|
||||||
<TableRow
|
<TableRow hover key={id} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
||||||
hover
|
|
||||||
key={id}
|
|
||||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
|
||||||
>
|
|
||||||
<TableCell component="th" scope="row">
|
<TableCell component="th" scope="row">
|
||||||
{props.files[id] && (
|
{props.files[id] && <FileBadge sx={{ maxWidth: "250px" }} file={props.files[id]} />}
|
||||||
<FileBadge
|
|
||||||
sx={{ maxWidth: "250px" }}
|
|
||||||
file={props.files[id]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!props.files[id] && !id.startsWith(CrUriPrefix) && id}
|
{!props.files[id] && !id.startsWith(CrUriPrefix) && id}
|
||||||
{!props.files[id] && id.startsWith(CrUriPrefix) && (
|
{!props.files[id] && id.startsWith(CrUriPrefix) && (
|
||||||
<FileBadge
|
<FileBadge
|
||||||
|
|
@ -80,12 +69,8 @@ const AggregatedErrorDetail = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.globalState.aggregatedErrorDialogOpen);
|
||||||
(state) => state.globalState.aggregatedErrorDialogOpen,
|
const files = useAppSelector((state) => state.globalState.aggregatedErrorFile);
|
||||||
);
|
|
||||||
const files = useAppSelector(
|
|
||||||
(state) => state.globalState.aggregatedErrorFile,
|
|
||||||
);
|
|
||||||
const error = useAppSelector((state) => state.globalState.aggregatedError);
|
const error = useAppSelector((state) => state.globalState.aggregatedError);
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(closeAggregatedErrorDialog());
|
dispatch(closeAggregatedErrorDialog());
|
||||||
|
|
@ -119,9 +104,7 @@ const AggregatedErrorDetail = () => {
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<StyledDialogContentText>
|
<StyledDialogContentText>{rootError && rootError.message}</StyledDialogContentText>
|
||||||
{rootError && rootError.message}
|
|
||||||
</StyledDialogContentText>
|
|
||||||
{files && errors && <ErrorTable errors={errors} files={files} />}
|
{files && errors && <ErrorTable errors={errors} files={files} />}
|
||||||
{rootError && rootError.cid && (
|
{rootError && rootError.cid && (
|
||||||
<DialogContentText variant={"caption"}>
|
<DialogContentText variant={"caption"}>
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,9 @@ const BatchDownloadLog = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const logRef = useRef<HTMLInputElement>(null);
|
const logRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.globalState.batchDownloadLogDialogOpen);
|
||||||
(state) => state.globalState.batchDownloadLogDialogOpen,
|
const downloadId = useAppSelector((state) => state.globalState.batchDownloadLogDialogId);
|
||||||
);
|
const logs = useAppSelector((state) => state.globalState.batchDownloadLogDialogLogs?.[downloadId ?? ""]);
|
||||||
const downloadId = useAppSelector(
|
|
||||||
(state) => state.globalState.batchDownloadLogDialogId,
|
|
||||||
);
|
|
||||||
const logs = useAppSelector(
|
|
||||||
(state) => state.globalState.batchDownloadLogDialogLogs?.[downloadId ?? ""],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(closeBatchDownloadLogDialog());
|
dispatch(closeBatchDownloadLogDialog());
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ import { useTranslation } from "react-i18next";
|
||||||
import { DialogContent, Stack } from "@mui/material";
|
import { DialogContent, Stack } from "@mui/material";
|
||||||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import DraggableDialog, {
|
import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx";
|
||||||
StyledDialogContentText,
|
|
||||||
} from "./DraggableDialog.tsx";
|
|
||||||
import { generalDialogPromisePool } from "../../redux/thunks/dialog.ts";
|
import { generalDialogPromisePool } from "../../redux/thunks/dialog.ts";
|
||||||
import { closeConfirmDialog } from "../../redux/globalStateSlice.ts";
|
import { closeConfirmDialog } from "../../redux/globalStateSlice.ts";
|
||||||
|
|
||||||
|
|
@ -13,12 +11,8 @@ const Confirmation = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const open = useAppSelector((state) => state.globalState.confirmDialogOpen);
|
const open = useAppSelector((state) => state.globalState.confirmDialogOpen);
|
||||||
const message = useAppSelector(
|
const message = useAppSelector((state) => state.globalState.confirmDialogMessage);
|
||||||
(state) => state.globalState.confirmDialogMessage,
|
const promiseId = useAppSelector((state) => state.globalState.confirmPromiseId);
|
||||||
);
|
|
||||||
const promiseId = useAppSelector(
|
|
||||||
(state) => state.globalState.confirmPromiseId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(closeConfirmDialog());
|
dispatch(closeConfirmDialog());
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,29 @@
|
||||||
import { AccordionDetailsProps, Box, styled } from "@mui/material";
|
import { AccordionDetailsProps, Box, styled } from "@mui/material";
|
||||||
import MuiAccordion, { AccordionProps } from "@mui/material/Accordion";
|
import MuiAccordion, { AccordionProps } from "@mui/material/Accordion";
|
||||||
import MuiAccordionSummary, {
|
import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary";
|
||||||
AccordionSummaryProps,
|
|
||||||
} from "@mui/material/AccordionSummary";
|
|
||||||
import MuiAccordionDetails from "@mui/material/AccordionDetails";
|
import MuiAccordionDetails from "@mui/material/AccordionDetails";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CaretDownIcon } from "../FileManager/TreeView/TreeFile.tsx";
|
import { CaretDownIcon } from "../FileManager/TreeView/TreeFile.tsx";
|
||||||
import { DefaultButton } from "../Common/StyledComponents.tsx";
|
import { DefaultButton } from "../Common/StyledComponents.tsx";
|
||||||
|
|
||||||
const Accordion = styled((props: AccordionProps) => (
|
const Accordion = styled((props: AccordionProps) => <MuiAccordion disableGutters elevation={0} square {...props} />)(
|
||||||
<MuiAccordion disableGutters elevation={0} square {...props} />
|
({ theme, expanded }) => ({
|
||||||
))(({ theme, expanded }) => ({
|
borderRadius: theme.shape.borderRadius,
|
||||||
borderRadius: theme.shape.borderRadius,
|
backgroundColor: expanded
|
||||||
backgroundColor: expanded
|
? theme.palette.mode == "light"
|
||||||
? theme.palette.mode == "light"
|
? "rgba(0, 0, 0, 0.06)"
|
||||||
? "rgba(0, 0, 0, 0.06)"
|
: "rgba(255, 255, 255, 0.09)"
|
||||||
: "rgba(255, 255, 255, 0.09)"
|
: "initial",
|
||||||
: "initial",
|
"&:not(:last-child)": {
|
||||||
"&:not(:last-child)": {
|
borderBottom: 0,
|
||||||
borderBottom: 0,
|
},
|
||||||
},
|
"&::before": {
|
||||||
"&::before": {
|
display: "none",
|
||||||
display: "none",
|
},
|
||||||
},
|
}),
|
||||||
}));
|
);
|
||||||
|
|
||||||
const AccordionSummary = styled((props: AccordionSummaryProps) => (
|
const AccordionSummary = styled((props: AccordionSummaryProps) => <MuiAccordionSummary {...props} />)(() => ({
|
||||||
<MuiAccordionSummary {...props} />
|
|
||||||
))(() => ({
|
|
||||||
flexDirection: "row-reverse",
|
flexDirection: "row-reverse",
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
|
@ -40,22 +36,17 @@ const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>(
|
const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>(({ theme, expanded }) => ({
|
||||||
({ theme, expanded }) => ({
|
justifyContent: "flex-start",
|
||||||
justifyContent: "flex-start",
|
backgroundColor: expanded
|
||||||
backgroundColor: expanded
|
? "initial"
|
||||||
? "initial"
|
: theme.palette.mode == "light"
|
||||||
: theme.palette.mode == "light"
|
? "rgba(0, 0, 0, 0.06)"
|
||||||
? "rgba(0, 0, 0, 0.06)"
|
: "rgba(255, 255, 255, 0.09)",
|
||||||
: "rgba(255, 255, 255, 0.09)",
|
"&:hover": {
|
||||||
"&:hover": {
|
backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.09)" : "rgba(255, 255, 255, 0.13)",
|
||||||
backgroundColor:
|
},
|
||||||
theme.palette.mode == "light"
|
}));
|
||||||
? "rgba(0, 0, 0, 0.09)"
|
|
||||||
: "rgba(255, 255, 255, 0.13)",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface DialogAccordionProps {
|
export interface DialogAccordionProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -73,17 +64,11 @@ const DialogAccordion = (props: DialogAccordionProps) => {
|
||||||
<Box>
|
<Box>
|
||||||
<Accordion expanded={expanded} onChange={handleChange}>
|
<Accordion expanded={expanded} onChange={handleChange}>
|
||||||
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
|
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
|
||||||
<SummaryButton
|
<SummaryButton expanded={expanded} fullWidth startIcon={<CaretDownIcon expanded={expanded} />}>
|
||||||
expanded={expanded}
|
|
||||||
fullWidth
|
|
||||||
startIcon={<CaretDownIcon expanded={expanded} />}
|
|
||||||
>
|
|
||||||
{props.title}
|
{props.title}
|
||||||
</SummaryButton>
|
</SummaryButton>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails {...props.accordionDetailProps}>
|
<AccordionDetails {...props.accordionDetailProps}>{props.children}</AccordionDetails>
|
||||||
{props.children}
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { DialogContent, List, ListItemButton, ListItemText } from "@mui/material";
|
||||||
DialogContent,
|
|
||||||
List,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemText,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import DraggableDialog from "./DraggableDialog.tsx";
|
import DraggableDialog from "./DraggableDialog.tsx";
|
||||||
|
|
@ -15,16 +10,10 @@ const SelectOption = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.globalState.selectOptionDialogOpen);
|
||||||
(state) => state.globalState.selectOptionDialogOpen,
|
|
||||||
);
|
|
||||||
const title = useAppSelector((state) => state.globalState.selectOptionTitle);
|
const title = useAppSelector((state) => state.globalState.selectOptionTitle);
|
||||||
const promiseId = useAppSelector(
|
const promiseId = useAppSelector((state) => state.globalState.selectOptionPromiseId);
|
||||||
(state) => state.globalState.selectOptionPromiseId,
|
const options = useAppSelector((state) => state.globalState.selectOptionDialogOptions);
|
||||||
);
|
|
||||||
const options = useAppSelector(
|
|
||||||
(state) => state.globalState.selectOptionDialogOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(closeSelectOptionDialog());
|
dispatch(closeSelectOptionDialog());
|
||||||
|
|
@ -65,8 +54,9 @@ const SelectOption = () => {
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
|
|
||||||
secondary: { variant: "body2" }
|
secondary: { variant: "body2" },
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,23 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Menu, type MenuProps } from "@mui/material";
|
import { Menu, type MenuProps } from "@mui/material";
|
||||||
|
|
||||||
const HoverMenu: React.ComponentType<MenuProps> = React.forwardRef(
|
const HoverMenu: React.ComponentType<MenuProps> = React.forwardRef(function HoverMenu(props: MenuProps, ref): any {
|
||||||
function HoverMenu(props: MenuProps, ref): any {
|
return (
|
||||||
return (
|
<Menu
|
||||||
<Menu
|
{...props}
|
||||||
{...props}
|
ref={ref}
|
||||||
ref={ref}
|
style={{ pointerEvents: "none", ...props.style }}
|
||||||
style={{ pointerEvents: "none", ...props.style }}
|
slotProps={{
|
||||||
slotProps={{
|
...props.slotProps,
|
||||||
...props.slotProps,
|
paper: {
|
||||||
paper: {
|
...props.slotProps?.paper,
|
||||||
...props.slotProps?.paper,
|
style: {
|
||||||
style: {
|
pointerEvents: "auto",
|
||||||
pointerEvents: "auto",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
},
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
export default HoverMenu;
|
export default HoverMenu;
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,7 @@ import { ViewersByID } from "../../../redux/siteConfigSlice.ts";
|
||||||
import { ListItemIcon, ListItemText } from "@mui/material";
|
import { ListItemIcon, ListItemText } from "@mui/material";
|
||||||
import { ViewerIcon } from "../Dialogs/OpenWith.tsx";
|
import { ViewerIcon } from "../Dialogs/OpenWith.tsx";
|
||||||
import { SquareMenuItem } from "./ContextMenu.tsx";
|
import { SquareMenuItem } from "./ContextMenu.tsx";
|
||||||
import {
|
import { CascadingContext, CascadingMenuItem, CascadingSubmenu } from "./CascadingMenu.tsx";
|
||||||
CascadingContext,
|
|
||||||
CascadingMenuItem,
|
|
||||||
CascadingSubmenu,
|
|
||||||
} from "./CascadingMenu.tsx";
|
|
||||||
import { NewFileTemplate, Viewer } from "../../../api/explorer.ts";
|
import { NewFileTemplate, Viewer } from "../../../api/explorer.ts";
|
||||||
import { createNew } from "../../../redux/thunks/file.ts";
|
import { createNew } from "../../../redux/thunks/file.ts";
|
||||||
import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
|
import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
|
||||||
|
|
@ -72,10 +68,7 @@ const NewFileTemplateMenuItems = (props: SubMenuItemsProps) => {
|
||||||
|
|
||||||
if (viewer.templates.length == 1) {
|
if (viewer.templates.length == 1) {
|
||||||
return (
|
return (
|
||||||
<SquareMenuItem
|
<SquareMenuItem key={viewer.id} onClick={onClick(viewer, viewer.templates[0])}>
|
||||||
key={viewer.id}
|
|
||||||
onClick={onClick(viewer, viewer.templates[0])}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<ViewerIcon size={20} viewer={viewer} py={0} />
|
<ViewerIcon size={20} viewer={viewer} py={0} />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,10 @@ import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
|
import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
|
||||||
import {
|
import { applyIconColor, dialogBasedMoveCopy } from "../../../redux/thunks/file.ts";
|
||||||
applyIconColor,
|
|
||||||
dialogBasedMoveCopy,
|
|
||||||
} from "../../../redux/thunks/file.ts";
|
|
||||||
import { ListItemIcon, ListItemText } from "@mui/material";
|
import { ListItemIcon, ListItemText } from "@mui/material";
|
||||||
import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx";
|
import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx";
|
||||||
import {
|
import { setChangeIconDialog, setPinFileDialog } from "../../../redux/globalStateSlice.ts";
|
||||||
setChangeIconDialog,
|
|
||||||
setPinFileDialog,
|
|
||||||
} from "../../../redux/globalStateSlice.ts";
|
|
||||||
import { getFileLinkedUri } from "../../../util";
|
import { getFileLinkedUri } from "../../../util";
|
||||||
import PinOutlined from "../../Icons/PinOutlined.tsx";
|
import PinOutlined from "../../Icons/PinOutlined.tsx";
|
||||||
import EmojiEdit from "../../Icons/EmojiEdit.tsx";
|
import EmojiEdit from "../../Icons/EmojiEdit.tsx";
|
||||||
|
|
@ -45,16 +39,11 @@ const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
|
||||||
[dispatch, targets],
|
[dispatch, targets],
|
||||||
);
|
);
|
||||||
const showDivider =
|
const showDivider =
|
||||||
(displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) &&
|
(displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) && displayOpt.showChangeFolderColor;
|
||||||
displayOpt.showChangeFolderColor;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{displayOpt.showMove && (
|
{displayOpt.showMove && (
|
||||||
<CascadingMenuItem
|
<CascadingMenuItem onClick={onClick(() => dispatch(dialogBasedMoveCopy(0, targets, false)))}>
|
||||||
onClick={onClick(() =>
|
|
||||||
dispatch(dialogBasedMoveCopy(0, targets, false)),
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<FolderArrowRightOutlined fontSize="small" />
|
<FolderArrowRightOutlined fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
@ -92,18 +81,14 @@ const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<EmojiEdit fontSize="small" />
|
<EmojiEdit fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>
|
<ListItemText>{t("application:fileManager.customizeIcon")}</ListItemText>
|
||||||
{t("application:fileManager.customizeIcon")}
|
|
||||||
</ListItemText>
|
|
||||||
</CascadingMenuItem>
|
</CascadingMenuItem>
|
||||||
)}
|
)}
|
||||||
{showDivider && <DenseDivider />}
|
{showDivider && <DenseDivider />}
|
||||||
{displayOpt.showChangeFolderColor && (
|
{displayOpt.showChangeFolderColor && (
|
||||||
<FolderColorQuickAction
|
<FolderColorQuickAction
|
||||||
file={targets[0]}
|
file={targets[0]}
|
||||||
onColorChange={(color) =>
|
onColorChange={(color) => onClick(() => dispatch(applyIconColor(0, targets, color, true)))()}
|
||||||
onClick(() => dispatch(applyIconColor(0, targets, color, true)))()
|
|
||||||
}
|
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "204px",
|
maxWidth: "204px",
|
||||||
margin: (theme) => `0 ${theme.spacing(0.5)}`,
|
margin: (theme) => `0 ${theme.spacing(0.5)}`,
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
|
||||||
import { setTagsDialog } from "../../../redux/globalStateSlice.ts";
|
import { setTagsDialog } from "../../../redux/globalStateSlice.ts";
|
||||||
import { ListItemIcon, ListItemText } from "@mui/material";
|
import { ListItemIcon, ListItemText } from "@mui/material";
|
||||||
import Tags from "../../Icons/Tags.tsx";
|
import Tags from "../../Icons/Tags.tsx";
|
||||||
import {
|
import { DenseDivider, SquareMenuItem, SubMenuItemsProps } from "./ContextMenu.tsx";
|
||||||
DenseDivider,
|
|
||||||
SquareMenuItem,
|
|
||||||
SubMenuItemsProps,
|
|
||||||
} from "./ContextMenu.tsx";
|
|
||||||
import SessionManager, { UserSettings } from "../../../session";
|
import SessionManager, { UserSettings } from "../../../session";
|
||||||
import { UsedTags } from "../../../session/utils.ts";
|
import { UsedTags } from "../../../session/utils.ts";
|
||||||
import Checkmark from "../../Icons/Checkmark.tsx";
|
import Checkmark from "../../Icons/Checkmark.tsx";
|
||||||
|
|
@ -111,33 +107,20 @@ const TagMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
|
||||||
</CascadingMenuItem>
|
</CascadingMenuItem>
|
||||||
{tags.length > 0 && <DenseDivider />}
|
{tags.length > 0 && <DenseDivider />}
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<SquareMenuItem
|
<SquareMenuItem key={tag.key} onClick={() => onTagChange(tag, !tag.selected)}>
|
||||||
key={tag.key}
|
|
||||||
onClick={() => onTagChange(tag, !tag.selected)}
|
|
||||||
>
|
|
||||||
{tag.selected && (
|
{tag.selected && (
|
||||||
<>
|
<>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Checkmark />
|
<Checkmark />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>
|
<ListItemText>
|
||||||
<FileTag
|
<FileTag disableClick spacing={1} label={tag.key} tagColor={tag.color} />
|
||||||
disableClick
|
|
||||||
spacing={1}
|
|
||||||
label={tag.key}
|
|
||||||
tagColor={tag.color}
|
|
||||||
/>
|
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!tag.selected && (
|
{!tag.selected && (
|
||||||
<ListItemText inset>
|
<ListItemText inset>
|
||||||
<FileTag
|
<FileTag disableClick spacing={1} label={tag.key} tagColor={tag.color} />
|
||||||
disableClick
|
|
||||||
spacing={1}
|
|
||||||
label={tag.key}
|
|
||||||
tagColor={tag.color}
|
|
||||||
/>
|
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
)}
|
)}
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { Box, Button, DialogContent, Skeleton, styled, Tab, Tabs, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
DialogContent,
|
|
||||||
Skeleton,
|
|
||||||
styled,
|
|
||||||
Tab,
|
|
||||||
Tabs,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||||
|
|
@ -73,18 +64,10 @@ const ChangeIcon = () => {
|
||||||
const [tabValue, setTabValue] = useState(0);
|
const [tabValue, setTabValue] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.globalState.changeIconDialogOpen);
|
||||||
(state) => state.globalState.changeIconDialogOpen,
|
const targets = useAppSelector((state) => state.globalState.changeIconDialogFile);
|
||||||
);
|
const emojiStr = useAppSelector((state) => state.siteConfig.emojis.config.emoji_preset);
|
||||||
const targets = useAppSelector(
|
const emojiStrLoaded = useAppSelector((state) => state.siteConfig.emojis.loaded);
|
||||||
(state) => state.globalState.changeIconDialogFile,
|
|
||||||
);
|
|
||||||
const emojiStr = useAppSelector(
|
|
||||||
(state) => state.siteConfig.emojis.config.emoji_preset,
|
|
||||||
);
|
|
||||||
const emojiStrLoaded = useAppSelector(
|
|
||||||
(state) => state.siteConfig.emojis.loaded,
|
|
||||||
);
|
|
||||||
|
|
||||||
const emojiSetting = useMemo((): EmojiSetting => {
|
const emojiSetting = useMemo((): EmojiSetting => {
|
||||||
if (!emojiStr) return {};
|
if (!emojiStr) return {};
|
||||||
|
|
@ -163,9 +146,7 @@ const ChangeIcon = () => {
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
{emojiStrLoaded ? (
|
{emojiStrLoaded ? (
|
||||||
Object.keys(emojiSetting).map((key) => (
|
Object.keys(emojiSetting).map((key) => <StyledTab label={key} key={key} />)
|
||||||
<StyledTab label={key} key={key} />
|
|
||||||
))
|
|
||||||
) : (
|
) : (
|
||||||
<StyledTab label={<Skeleton sx={{ minWidth: "20px" }} />} />
|
<StyledTab label={<Skeleton sx={{ minWidth: "20px" }} />} />
|
||||||
)}
|
)}
|
||||||
|
|
@ -177,9 +158,7 @@ const ChangeIcon = () => {
|
||||||
<CustomTabPanel value={tabValue} index={index}>
|
<CustomTabPanel value={tabValue} index={index}>
|
||||||
<SelectorBox>
|
<SelectorBox>
|
||||||
{emojiSetting[key].map((emoji) => (
|
{emojiSetting[key].map((emoji) => (
|
||||||
<EmojiButton onClick={onAccept(emoji)}>
|
<EmojiButton onClick={onAccept(emoji)}>{emoji}</EmojiButton>
|
||||||
{emoji}
|
|
||||||
</EmojiButton>
|
|
||||||
))}
|
))}
|
||||||
</SelectorBox>
|
</SelectorBox>
|
||||||
</CustomTabPanel>
|
</CustomTabPanel>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,7 @@ import { setRenameFileModalError } from "../../../redux/fileManagerSlice.ts";
|
||||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||||
import { createNewDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
import { createNewDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
||||||
import { FilledTextField } from "../../Common/StyledComponents.tsx";
|
import { FilledTextField } from "../../Common/StyledComponents.tsx";
|
||||||
import {
|
import { closeCreateNewDialog, CreateNewDialogType } from "../../../redux/globalStateSlice.ts";
|
||||||
closeCreateNewDialog,
|
|
||||||
CreateNewDialogType,
|
|
||||||
} from "../../../redux/globalStateSlice.ts";
|
|
||||||
import { submitCreateNew } from "../../../redux/thunks/file.ts";
|
import { submitCreateNew } from "../../../redux/thunks/file.ts";
|
||||||
import { FileType } from "../../../api/explorer.ts";
|
import { FileType } from "../../../api/explorer.ts";
|
||||||
|
|
||||||
|
|
@ -24,15 +21,10 @@ const CreateNew = () => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const open = useAppSelector((state) => state.globalState.createNewDialogOpen);
|
const open = useAppSelector((state) => state.globalState.createNewDialogOpen);
|
||||||
const promiseId = useAppSelector(
|
const promiseId = useAppSelector((state) => state.globalState.createNewPromiseId);
|
||||||
(state) => state.globalState.createNewPromiseId,
|
|
||||||
);
|
|
||||||
const type = useAppSelector((state) => state.globalState.createNewDialogType);
|
const type = useAppSelector((state) => state.globalState.createNewDialogType);
|
||||||
const defaultName = useAppSelector(
|
const defaultName = useAppSelector((state) => state.globalState.createNewDialogDefault);
|
||||||
(state) => state.globalState.createNewDialogDefault,
|
const fmIndex = useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0;
|
||||||
);
|
|
||||||
const fmIndex =
|
|
||||||
useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
|
@ -54,13 +46,7 @@ const CreateNew = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
dispatch(
|
dispatch(submitCreateNew(fmIndex, name, type == CreateNewDialogType.folder ? FileType.folder : FileType.file))
|
||||||
submitCreateNew(
|
|
||||||
fmIndex,
|
|
||||||
name,
|
|
||||||
type == CreateNewDialogType.folder ? FileType.folder : FileType.file,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then((f) => {
|
.then((f) => {
|
||||||
if (promiseId) {
|
if (promiseId) {
|
||||||
createNewDialogPromisePool[promiseId]?.resolve(f);
|
createNewDialogPromisePool[promiseId]?.resolve(f);
|
||||||
|
|
@ -86,12 +72,7 @@ const CreateNew = () => {
|
||||||
if (open) {
|
if (open) {
|
||||||
const lastDot = name.lastIndexOf(".");
|
const lastDot = name.lastIndexOf(".");
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() => inputRef.current && inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : name.length),
|
||||||
inputRef.current &&
|
|
||||||
inputRef.current.setSelectionRange(
|
|
||||||
0,
|
|
||||||
lastDot > 0 ? lastDot : name.length,
|
|
||||||
),
|
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -110,9 +91,7 @@ const CreateNew = () => {
|
||||||
return (
|
return (
|
||||||
<DraggableDialog
|
<DraggableDialog
|
||||||
title={t(
|
title={t(
|
||||||
type == CreateNewDialogType.folder
|
type == CreateNewDialogType.folder ? "application:fileManager.newFolder" : "application:fileManager.newFile",
|
||||||
? "application:fileManager.newFolder"
|
|
||||||
: "application:fileManager.newFile",
|
|
||||||
)}
|
)}
|
||||||
showActions
|
showActions
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|
@ -137,9 +116,7 @@ const CreateNew = () => {
|
||||||
helperText={error}
|
helperText={error}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label={t(
|
label={t(
|
||||||
type == CreateNewDialogType.folder
|
type == CreateNewDialogType.folder ? "application:modals.folderName" : "application:modals.fileName",
|
||||||
? "application:modals.folderName"
|
|
||||||
: "application:modals.fileName",
|
|
||||||
)}
|
)}
|
||||||
type="text"
|
type="text"
|
||||||
value={name}
|
value={name}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ const DirectLinks = () => {
|
||||||
|
|
||||||
const [showFileName, setShowFileName] = useState(false);
|
const [showFileName, setShowFileName] = useState(false);
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.globalState.directLinkDialogOpen);
|
||||||
(state) => state.globalState.directLinkDialogOpen,
|
|
||||||
);
|
|
||||||
const targets = useAppSelector((state) => state.globalState.directLinkRes);
|
const targets = useAppSelector((state) => state.globalState.directLinkRes);
|
||||||
|
|
||||||
const contents = useMemo(() => {
|
const contents = useMemo(() => {
|
||||||
|
|
@ -86,7 +84,7 @@ const DirectLinks = () => {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
slotProps={{
|
slotProps={{
|
||||||
htmlInput: { readonly: true }
|
htmlInput: { readonly: true },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,9 @@
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { DialogContent, Stack } from "@mui/material";
|
import { DialogContent, Stack } from "@mui/material";
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import {
|
import { ChangeEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
ChangeEvent,
|
import { closeRenameFileModal, setRenameFileModalError } from "../../../redux/fileManagerSlice.ts";
|
||||||
useCallback,
|
import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx";
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import {
|
|
||||||
closeRenameFileModal,
|
|
||||||
setRenameFileModalError,
|
|
||||||
} from "../../../redux/fileManagerSlice.ts";
|
|
||||||
import DraggableDialog, {
|
|
||||||
StyledDialogContentText,
|
|
||||||
} from "../../Dialogs/DraggableDialog.tsx";
|
|
||||||
import { renameDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
import { renameDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
||||||
import { validateFileName } from "../../../redux/thunks/file.ts";
|
import { validateFileName } from "../../../redux/thunks/file.ts";
|
||||||
import { FileType } from "../../../api/explorer.ts";
|
import { FileType } from "../../../api/explorer.ts";
|
||||||
|
|
@ -33,21 +21,11 @@ const Rename = () => {
|
||||||
|
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.fileManager[0].renameFileModalOpen);
|
||||||
(state) => state.fileManager[0].renameFileModalOpen,
|
const targets = useAppSelector((state) => state.fileManager[0].renameFileModalSelected);
|
||||||
);
|
const promiseId = useAppSelector((state) => state.fileManager[0].renameFileModalPromiseId);
|
||||||
const targets = useAppSelector(
|
const loading = useAppSelector((state) => state.fileManager[0].renameFileModalLoading);
|
||||||
(state) => state.fileManager[0].renameFileModalSelected,
|
const error = useAppSelector((state) => state.fileManager[0].renameFileModalError);
|
||||||
);
|
|
||||||
const promiseId = useAppSelector(
|
|
||||||
(state) => state.fileManager[0].renameFileModalPromiseId,
|
|
||||||
);
|
|
||||||
const loading = useAppSelector(
|
|
||||||
(state) => state.fileManager[0].renameFileModalLoading,
|
|
||||||
);
|
|
||||||
const error = useAppSelector(
|
|
||||||
(state) => state.fileManager[0].renameFileModalError,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
|
|
@ -67,13 +45,7 @@ const Rename = () => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
if (promiseId) {
|
if (promiseId) {
|
||||||
dispatch(
|
dispatch(validateFileName(0, renameDialogPromisePool[promiseId]?.resolve, name));
|
||||||
validateFileName(
|
|
||||||
0,
|
|
||||||
renameDialogPromisePool[promiseId]?.resolve,
|
|
||||||
name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[promiseId, name],
|
[promiseId, name],
|
||||||
|
|
@ -95,12 +67,8 @@ const Rename = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (targets && open && inputRef.current) {
|
if (targets && open && inputRef.current) {
|
||||||
const lastDot =
|
const lastDot = targets.type == FileType.folder ? 0 : targets.name.lastIndexOf(".");
|
||||||
targets.type == FileType.folder ? 0 : targets.name.lastIndexOf(".");
|
inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : targets.name.length);
|
||||||
inputRef.current.setSelectionRange(
|
|
||||||
0,
|
|
||||||
lastDot > 0 ? lastDot : targets.name.length,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [inputRef.current, open]);
|
}, [inputRef.current, open]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,8 @@ import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Button, DialogContent, Stack } from "@mui/material";
|
import { Button, DialogContent, Stack } from "@mui/material";
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import DraggableDialog, {
|
import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx";
|
||||||
StyledDialogContentText,
|
import { askSaveAs, staleVersionDialogPromisePool } from "../../../redux/thunks/dialog.ts";
|
||||||
} from "../../Dialogs/DraggableDialog.tsx";
|
|
||||||
import {
|
|
||||||
askSaveAs,
|
|
||||||
staleVersionDialogPromisePool,
|
|
||||||
} from "../../../redux/thunks/dialog.ts";
|
|
||||||
import { closeStaleVersionDialog } from "../../../redux/globalStateSlice.ts";
|
import { closeStaleVersionDialog } from "../../../redux/globalStateSlice.ts";
|
||||||
import CrUri from "../../../util/uri.ts";
|
import CrUri from "../../../util/uri.ts";
|
||||||
|
|
||||||
|
|
@ -16,13 +11,9 @@ const StaleVersionConfirm = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const open = useAppSelector(
|
const open = useAppSelector((state) => state.globalState.staleVersionDialogOpen);
|
||||||
(state) => state.globalState.staleVersionDialogOpen,
|
|
||||||
);
|
|
||||||
const uri = useAppSelector((state) => state.globalState.staleVersionUri);
|
const uri = useAppSelector((state) => state.globalState.staleVersionUri);
|
||||||
const promiseId = useAppSelector(
|
const promiseId = useAppSelector((state) => state.globalState.staleVersionPromiseId);
|
||||||
(state) => state.globalState.staleVersionPromiseId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(closeStaleVersionDialog());
|
dispatch(closeStaleVersionDialog());
|
||||||
|
|
@ -88,11 +79,7 @@ const StaleVersionConfirm = () => {
|
||||||
<StyledDialogContentText>
|
<StyledDialogContentText>
|
||||||
{t("modals.conflictDes1")}
|
{t("modals.conflictDes1")}
|
||||||
<ul>
|
<ul>
|
||||||
<Trans
|
<Trans i18nKey="modals.conflictDes2" ns={"application"} components={[<li key={0} />, <li key={1} />]} />
|
||||||
i18nKey="modals.conflictDes2"
|
|
||||||
ns={"application"}
|
|
||||||
components={[<li key={0} />, <li key={1} />]}
|
|
||||||
/>
|
|
||||||
</ul>
|
</ul>
|
||||||
</StyledDialogContentText>
|
</StyledDialogContentText>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,7 @@ const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => {
|
||||||
zIndex: 1610 - i - 1,
|
zIndex: 1610 - i - 1,
|
||||||
top: (i + 1) * 4,
|
top: (i + 1) * 4,
|
||||||
left: (i + 1) * 4,
|
left: (i + 1) * 4,
|
||||||
transition: (theme) =>
|
transition: (theme) => theme.transitions.create(["width", "height"]),
|
||||||
theme.transitions.create(["width", "height"]),
|
|
||||||
}}
|
}}
|
||||||
elevation={3}
|
elevation={3}
|
||||||
/>
|
/>
|
||||||
|
|
@ -68,14 +67,12 @@ const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => {
|
||||||
const DragLayer = () => {
|
const DragLayer = () => {
|
||||||
DisableDropDelay();
|
DisableDropDelay();
|
||||||
|
|
||||||
const { itemType, isDragging, item, pointerOffset } = useDragLayer(
|
const { itemType, isDragging, item, pointerOffset } = useDragLayer((monitor) => ({
|
||||||
(monitor) => ({
|
item: monitor.getItem(),
|
||||||
item: monitor.getItem(),
|
itemType: monitor.getItemType(),
|
||||||
itemType: monitor.getItemType(),
|
pointerOffset: monitor.getClientOffset(),
|
||||||
pointerOffset: monitor.getClientOffset(),
|
isDragging: monitor.isDragging(),
|
||||||
isDragging: monitor.isDragging(),
|
}));
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const selected = useAppSelector((state) => state.fileManager[0].selected);
|
const selected = useAppSelector((state) => state.fileManager[0].selected);
|
||||||
const draggingFiles = useMemo(() => {
|
const draggingFiles = useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ const threshold = 0.1;
|
||||||
|
|
||||||
const useDragScrolling = (containers: string[]) => {
|
const useDragScrolling = (containers: string[]) => {
|
||||||
const isScrolling = useRef(false);
|
const isScrolling = useRef(false);
|
||||||
const targets = containers.map(
|
const targets = containers.map((id) => document.querySelector(id) as HTMLElement);
|
||||||
(id) => document.querySelector(id) as HTMLElement,
|
|
||||||
);
|
|
||||||
const rects = useRef<DOMRect[]>([]);
|
const rects = useRef<DOMRect[]>([]);
|
||||||
|
|
||||||
const goDown = (target: HTMLElement) => {
|
const goDown = (target: HTMLElement) => {
|
||||||
|
|
@ -41,16 +39,10 @@ const useDragScrolling = (containers: string[]) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = rect.bottom - rect.top;
|
const height = rect.bottom - rect.top;
|
||||||
if (
|
if (event.clientY > rect.top && event.clientY < rect.top + threshold * height) {
|
||||||
event.clientY > rect.top &&
|
|
||||||
event.clientY < rect.top + threshold * height
|
|
||||||
) {
|
|
||||||
isScrolling.current = true;
|
isScrolling.current = true;
|
||||||
window.requestAnimationFrame(goUp(targets[index]));
|
window.requestAnimationFrame(goUp(targets[index]));
|
||||||
} else if (
|
} else if (event.clientY < rect.bottom && event.clientY > rect.bottom - threshold * height) {
|
||||||
event.clientY < rect.bottom &&
|
|
||||||
event.clientY > rect.bottom - threshold * height
|
|
||||||
) {
|
|
||||||
isScrolling.current = true;
|
isScrolling.current = true;
|
||||||
window.requestAnimationFrame(goDown(targets[index]));
|
window.requestAnimationFrame(goDown(targets[index]));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
import {
|
import { Chip, ChipProps, darken, styled, Tooltip, useTheme } from "@mui/material";
|
||||||
Chip,
|
|
||||||
ChipProps,
|
|
||||||
darken,
|
|
||||||
styled,
|
|
||||||
Tooltip,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useCallback, useContext } from "react";
|
import { useCallback, useContext } from "react";
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import { Metadata } from "../../../api/explorer.ts";
|
import { Metadata } from "../../../api/explorer.ts";
|
||||||
import { searchMetadata } from "../../../redux/thunks/filemanager.ts";
|
import { searchMetadata } from "../../../redux/thunks/filemanager.ts";
|
||||||
import { FmIndexContext } from "../FmIndexContext.tsx";
|
import { FmIndexContext } from "../FmIndexContext.tsx";
|
||||||
|
|
||||||
export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({
|
export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({ defaultStyle }) => {
|
||||||
defaultStyle,
|
|
||||||
}) => {
|
|
||||||
const base = {
|
const base = {
|
||||||
"& .MuiChip-deleteIcon": {},
|
"& .MuiChip-deleteIcon": {},
|
||||||
};
|
};
|
||||||
|
|
@ -30,16 +21,7 @@ export interface FileTagProps extends ChipProps {
|
||||||
disableClick?: boolean;
|
disableClick?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileTag = ({
|
const FileTag = ({ disableClick, tagColor, sx, label, defaultStyle, spacing, openInNewTab, ...rest }: FileTagProps) => {
|
||||||
disableClick,
|
|
||||||
tagColor,
|
|
||||||
sx,
|
|
||||||
label,
|
|
||||||
defaultStyle,
|
|
||||||
spacing,
|
|
||||||
openInNewTab,
|
|
||||||
...rest
|
|
||||||
}: FileTagProps) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
@ -56,23 +38,13 @@ const FileTag = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dispatch(
|
dispatch(searchMetadata(fmIndex, Metadata.tag_prefix + label, tagColor, openInNewTab));
|
||||||
searchMetadata(
|
|
||||||
fmIndex,
|
|
||||||
Metadata.tag_prefix + label,
|
|
||||||
tagColor,
|
|
||||||
openInNewTab,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[root, dispatch, fmIndex, disableClick],
|
[root, dispatch, fmIndex, disableClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hackColor =
|
const hackColor =
|
||||||
!!tagColor &&
|
!!tagColor && theme.palette.getContrastText(tagColor) != theme.palette.text.primary ? "error" : undefined;
|
||||||
theme.palette.getContrastText(tagColor) != theme.palette.text.primary
|
|
||||||
? "error"
|
|
||||||
: undefined;
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={label}>
|
<Tooltip title={label}>
|
||||||
<TagChip
|
<TagChip
|
||||||
|
|
|
||||||
|
|
@ -12,86 +12,80 @@ export interface FileTagSummaryProps {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileTagSummary = memo(
|
const FileTagSummary = memo(({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => {
|
||||||
({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => {
|
const theme = useTheme();
|
||||||
const theme = useTheme();
|
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
const popupState = usePopupState({
|
||||||
const popupState = usePopupState({
|
variant: "popover",
|
||||||
variant: "popover",
|
popupId: "demoMenu",
|
||||||
popupId: "demoMenu",
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const { open, ...rest } = bindPopover(popupState);
|
const { open, ...rest } = bindPopover(popupState);
|
||||||
const stopPropagation = useCallback((e: any) => e.stopPropagation(), []);
|
const stopPropagation = useCallback((e: any) => e.stopPropagation(), []);
|
||||||
const [shown, hidden] = useMemo(() => {
|
const [shown, hidden] = useMemo(() => {
|
||||||
if (tags.length <= max) {
|
if (tags.length <= max) {
|
||||||
return [tags, []];
|
return [tags, []];
|
||||||
}
|
}
|
||||||
return [tags.slice(0, max), tags.slice(max)];
|
return [tags.slice(0, max), tags.slice(max)];
|
||||||
}, [tags, max]);
|
}, [tags, max]);
|
||||||
|
|
||||||
const { onClick, ...triggerProps } = bindTrigger(popupState);
|
const { onClick, ...triggerProps } = bindTrigger(popupState);
|
||||||
const onMobileClick = (e: React.MouseEvent<HTMLElement>) => {
|
const onMobileClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onClick(e);
|
onClick(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PopoverComponent = isMobile ? Popover : HoverPopover;
|
const PopoverComponent = isMobile ? Popover : HoverPopover;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction={"row"} spacing={1} sx={{ ...sx }} {...restProps}>
|
<Stack direction={"row"} spacing={1} sx={{ ...sx }} {...restProps}>
|
||||||
{shown.map((tag) => (
|
{shown.map((tag) => (
|
||||||
<FileTag
|
<FileTag tagColor={tag.value == "" ? undefined : tag.value} label={tag.key} key={tag.key} />
|
||||||
tagColor={tag.value == "" ? undefined : tag.value}
|
))}
|
||||||
label={tag.key}
|
{hidden.length > 0 && (
|
||||||
key={tag.key}
|
<TagChip
|
||||||
/>
|
size="small"
|
||||||
))}
|
variant={"outlined"}
|
||||||
{hidden.length > 0 && (
|
label={`+${hidden.length}`}
|
||||||
<TagChip
|
{...(isMobile
|
||||||
size="small"
|
? {
|
||||||
variant={"outlined"}
|
onClick: onMobileClick,
|
||||||
label={`+${hidden.length}`}
|
...triggerProps,
|
||||||
{...(isMobile
|
}
|
||||||
? {
|
: bindHover(popupState))}
|
||||||
onClick: onMobileClick,
|
/>
|
||||||
...triggerProps,
|
)}
|
||||||
}
|
|
||||||
: bindHover(popupState))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{open && (
|
{open && (
|
||||||
<PopoverComponent
|
<PopoverComponent
|
||||||
onMouseDown={stopPropagation}
|
onMouseDown={stopPropagation}
|
||||||
onMouseUp={stopPropagation}
|
onMouseUp={stopPropagation}
|
||||||
onClick={stopPropagation}
|
onClick={stopPropagation}
|
||||||
open={open}
|
open={open}
|
||||||
{...rest}
|
{...rest}
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "bottom",
|
vertical: "bottom",
|
||||||
horizontal: "center",
|
horizontal: "center",
|
||||||
}}
|
}}
|
||||||
transformOrigin={{
|
transformOrigin={{
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "center",
|
horizontal: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ p: 1, maxWidth: "300px" }}>
|
<Box sx={{ p: 1, maxWidth: "300px" }}>
|
||||||
{hidden.map((tag, i) => (
|
{hidden.map((tag, i) => (
|
||||||
<FileTag
|
<FileTag
|
||||||
tagColor={tag.value == "" ? undefined : tag.value}
|
tagColor={tag.value == "" ? undefined : tag.value}
|
||||||
label={tag.key}
|
label={tag.key}
|
||||||
spacing={i === hidden.length - 1 ? undefined : 1}
|
spacing={i === hidden.length - 1 ? undefined : 1}
|
||||||
key={tag.key}
|
key={tag.key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</PopoverComponent>
|
</PopoverComponent>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default FileTagSummary;
|
export default FileTagSummary;
|
||||||
|
|
|
||||||
|
|
@ -44,21 +44,11 @@ export const loadingPlaceHolderNumb = 3;
|
||||||
const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
|
const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
|
||||||
const { t } = useTranslation("application");
|
const { t } = useTranslation("application");
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const files = useAppSelector(
|
const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
|
||||||
(state) => state.fileManager[fmIndex].list?.files,
|
const mixedType = useAppSelector((state) => state.fileManager[fmIndex].list?.mixed_type);
|
||||||
);
|
const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
|
||||||
const mixedType = useAppSelector(
|
const showThumb = useAppSelector((state) => state.fileManager[fmIndex].showThumb);
|
||||||
(state) => state.fileManager[fmIndex].list?.mixed_type,
|
const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params);
|
||||||
);
|
|
||||||
const pagination = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex].list?.pagination,
|
|
||||||
);
|
|
||||||
const showThumb = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex].showThumb,
|
|
||||||
);
|
|
||||||
const search_params = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex]?.search_params,
|
|
||||||
);
|
|
||||||
const list = useMemo(() => {
|
const list = useMemo(() => {
|
||||||
const list: listComponents = {
|
const list: listComponents = {
|
||||||
Files: [],
|
Files: [],
|
||||||
|
|
@ -115,9 +105,7 @@ const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => {
|
||||||
</GridItem>
|
</GridItem>
|
||||||
);
|
);
|
||||||
const _ =
|
const _ =
|
||||||
list.Files.length > 0
|
list.Files.length > 0 ? list.Files.push(loadingPlaceholder) : list.Folders?.push(loadingPlaceholder);
|
||||||
? list.Files.push(loadingPlaceholder)
|
|
||||||
: list.Folders?.push(loadingPlaceholder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,10 @@ export interface ListBodyProps {
|
||||||
|
|
||||||
const ListBody = ({ columns }: ListBodyProps) => {
|
const ListBody = ({ columns }: ListBodyProps) => {
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const files = useAppSelector(
|
const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
|
||||||
(state) => state.fileManager[fmIndex].list?.files,
|
const mixedType = useAppSelector((state) => state.fileManager[fmIndex].list?.mixed_type);
|
||||||
);
|
const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
|
||||||
const mixedType = useAppSelector(
|
const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params);
|
||||||
(state) => state.fileManager[fmIndex].list?.mixed_type,
|
|
||||||
);
|
|
||||||
const pagination = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex].list?.pagination,
|
|
||||||
);
|
|
||||||
const search_params = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex]?.search_params,
|
|
||||||
);
|
|
||||||
|
|
||||||
const list = useMemo(() => {
|
const list = useMemo(() => {
|
||||||
const list: FmFile[] = [];
|
const list: FmFile[] = [];
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,7 @@ export interface FileBadgeProps extends ButtonProps {
|
||||||
clickable?: boolean;
|
clickable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileBadge = ({
|
const FileBadge = ({ file, clickable, simplifiedFile, unknown, ...rest }: FileBadgeProps) => {
|
||||||
file,
|
|
||||||
clickable,
|
|
||||||
simplifiedFile,
|
|
||||||
unknown,
|
|
||||||
...rest
|
|
||||||
}: FileBadgeProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const popupState = usePopupState({
|
const popupState = usePopupState({
|
||||||
variant: "popover",
|
variant: "popover",
|
||||||
|
|
@ -131,9 +125,7 @@ const FileBadge = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<BadgeText variant={"body2"}>
|
<BadgeText variant={"body2"}>{name == "" ? displayName : name}</BadgeText>
|
||||||
{name == "" ? displayName : name}
|
|
||||||
</BadgeText>
|
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { Box, Button, Divider, Popover, styled, Tooltip, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Popover,
|
|
||||||
styled,
|
|
||||||
Tooltip,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { CSSProperties, useCallback, useState } from "react";
|
import { CSSProperties, useCallback, useState } from "react";
|
||||||
import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||||
|
|
@ -72,11 +64,7 @@ const ColorCircleBox = styled("div")(({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const ColorCircleBoxChild = styled("div")(({
|
const ColorCircleBoxChild = styled("div")(({ selected }: { selected: boolean }) => {
|
||||||
selected,
|
|
||||||
}: {
|
|
||||||
selected: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return {
|
return {
|
||||||
"--circle-point-background-color": theme.palette.background.default,
|
"--circle-point-background-color": theme.palette.background.default,
|
||||||
|
|
@ -90,14 +78,7 @@ const ColorCircleBoxChild = styled("div")(({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ColorCircle = ({
|
export const ColorCircle = ({ color, selected, isCustomization, onClick, size, noMb }: ColorCircleProps) => {
|
||||||
color,
|
|
||||||
selected,
|
|
||||||
isCustomization,
|
|
||||||
onClick,
|
|
||||||
size,
|
|
||||||
noMb,
|
|
||||||
}: ColorCircleProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const displayColor = isCustomization
|
const displayColor = isCustomization
|
||||||
? "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)"
|
? "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)"
|
||||||
|
|
@ -105,16 +86,8 @@ export const ColorCircle = ({
|
||||||
? "linear-gradient(45deg, rgba(217,217,217,1) 46%, rgba(217,217,217,1) 47%, rgba(128,128,128,1) 47%)"
|
? "linear-gradient(45deg, rgba(217,217,217,1) 46%, rgba(217,217,217,1) 47%, rgba(128,128,128,1) 47%)"
|
||||||
: color;
|
: color;
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip title={isCustomization ? t("application:fileManager.customizeColor") : ""}>
|
||||||
title={isCustomization ? t("application:fileManager.customizeColor") : ""}
|
<ColorCircleBox size={size} onClick={onClick} color={displayColor} selected={selected} noMb={noMb}>
|
||||||
>
|
|
||||||
<ColorCircleBox
|
|
||||||
size={size}
|
|
||||||
onClick={onClick}
|
|
||||||
color={displayColor}
|
|
||||||
selected={selected}
|
|
||||||
noMb={noMb}
|
|
||||||
>
|
|
||||||
<ColorCircleBoxChild selected={selected} />
|
<ColorCircleBoxChild selected={selected} />
|
||||||
</ColorCircleBox>
|
</ColorCircleBox>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -124,9 +97,7 @@ export const ColorCircle = ({
|
||||||
const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [customizeColor, setCustomizeColor] = useState<string>(
|
const [customizeColor, setCustomizeColor] = useState<string>(props.selectedColor);
|
||||||
props.selectedColor,
|
|
||||||
);
|
|
||||||
const popupState = usePopupState({
|
const popupState = usePopupState({
|
||||||
variant: "popover",
|
variant: "popover",
|
||||||
popupId: "color-picker",
|
popupId: "color-picker",
|
||||||
|
|
@ -152,13 +123,8 @@ const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
||||||
{props.colors.map((color) => (
|
{props.colors.map((color) => (
|
||||||
<ColorCircle
|
<ColorCircle
|
||||||
noMb={props.showColorValueInCustomization}
|
noMb={props.showColorValueInCustomization}
|
||||||
isCustomization={
|
isCustomization={color === customizeMagicColor && !props.showColorValueInCustomization}
|
||||||
color === customizeMagicColor &&
|
color={!props.showColorValueInCustomization ? color : props.selectedColor}
|
||||||
!props.showColorValueInCustomization
|
|
||||||
}
|
|
||||||
color={
|
|
||||||
!props.showColorValueInCustomization ? color : props.selectedColor
|
|
||||||
}
|
|
||||||
onClick={onClick(color)}
|
onClick={onClick(color)}
|
||||||
selected={color === props.selectedColor}
|
selected={color === props.selectedColor}
|
||||||
{...(color === customizeMagicColor && bindTrigger(popupState))}
|
{...(color === customizeMagicColor && bindTrigger(popupState))}
|
||||||
|
|
@ -195,12 +161,7 @@ const CircleColorSelector = (props: CircleColorSelectorProps) => {
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box sx={{ p: 1 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Button
|
<Button size={"small"} onClick={onApply} fullWidth variant={"contained"}>
|
||||||
size={"small"}
|
|
||||||
onClick={onApply}
|
|
||||||
fullWidth
|
|
||||||
variant={"contained"}
|
|
||||||
>
|
|
||||||
{t("application:fileManager.apply")}
|
{t("application:fileManager.apply")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
import {
|
import { Box, BoxProps, Stack, styled, Typography, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
BoxProps,
|
|
||||||
Stack,
|
|
||||||
styled,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { FileResponse, Metadata } from "../../../api/explorer.ts";
|
import { FileResponse, Metadata } from "../../../api/explorer.ts";
|
||||||
import CircleColorSelector, {
|
import CircleColorSelector, { customizeMagicColor } from "./ColorCircle/CircleColorSelector.tsx";
|
||||||
customizeMagicColor,
|
|
||||||
} from "./ColorCircle/CircleColorSelector.tsx";
|
|
||||||
import SessionManager, { UserSettings } from "../../../session";
|
import SessionManager, { UserSettings } from "../../../session";
|
||||||
import { defaultColors } from "../../../constants";
|
import { defaultColors } from "../../../constants";
|
||||||
|
|
||||||
|
|
@ -25,23 +16,16 @@ export interface FolderColorQuickActionProps extends BoxProps {
|
||||||
onColorChange: (color?: string) => void;
|
onColorChange: (color?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FolderColorQuickAction = ({
|
const FolderColorQuickAction = ({ file, onColorChange, ...rest }: FolderColorQuickActionProps) => {
|
||||||
file,
|
|
||||||
onColorChange,
|
|
||||||
...rest
|
|
||||||
}: FolderColorQuickActionProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [hex, setHex] = useState<string>(
|
const [hex, setHex] = useState<string>(
|
||||||
(file.metadata && file.metadata[Metadata.icon_color]) ??
|
(file.metadata && file.metadata[Metadata.icon_color]) ?? theme.palette.action.active,
|
||||||
theme.palette.action.active,
|
|
||||||
);
|
);
|
||||||
const presetColors = useMemo(() => {
|
const presetColors = useMemo(() => {
|
||||||
const colors = new Set(defaultColors);
|
const colors = new Set(defaultColors);
|
||||||
|
|
||||||
const recentColors = SessionManager.get(
|
const recentColors = SessionManager.get(UserSettings.UsedCustomizedIconColors) as string[] | undefined;
|
||||||
UserSettings.UsedCustomizedIconColors,
|
|
||||||
) as string[] | undefined;
|
|
||||||
|
|
||||||
if (recentColors) {
|
if (recentColors) {
|
||||||
recentColors.forEach((color) => {
|
recentColors.forEach((color) => {
|
||||||
|
|
@ -54,20 +38,12 @@ const FolderColorQuickAction = ({
|
||||||
return (
|
return (
|
||||||
<StyledBox {...rest}>
|
<StyledBox {...rest}>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Typography variant={"caption"}>
|
<Typography variant={"caption"}>{t("application:fileManager.folderColor")}</Typography>
|
||||||
{t("application:fileManager.folderColor")}
|
|
||||||
</Typography>
|
|
||||||
<CircleColorSelector
|
<CircleColorSelector
|
||||||
colors={[
|
colors={[theme.palette.action.active, ...presetColors, customizeMagicColor]}
|
||||||
theme.palette.action.active,
|
|
||||||
...presetColors,
|
|
||||||
customizeMagicColor,
|
|
||||||
]}
|
|
||||||
selectedColor={hex}
|
selectedColor={hex}
|
||||||
onChange={(color) => {
|
onChange={(color) => {
|
||||||
onColorChange(
|
onColorChange(color == theme.palette.action.active ? undefined : color);
|
||||||
color == theme.palette.action.active ? undefined : color,
|
|
||||||
);
|
|
||||||
setHex(color);
|
setHex(color);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
|
|
||||||
export const FmIndexContext = createContext(0);
|
export const FmIndexContext = createContext(0);
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ const NewButton = () => {
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton onClick={(e) => dispatch(openNewContextMenu(FileManagerIndex.main, e))}>
|
||||||
onClick={(e) => dispatch(openNewContextMenu(FileManagerIndex.main, e))}
|
|
||||||
>
|
|
||||||
<Add />
|
<Add />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
import { RadiusFrame } from "../../Frame/RadiusFrame.tsx";
|
import { RadiusFrame } from "../../Frame/RadiusFrame.tsx";
|
||||||
import {
|
import { Box, Pagination, Slide, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Pagination,
|
|
||||||
Slide,
|
|
||||||
styled,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { forwardRef, useContext } from "react";
|
import { forwardRef, useContext } from "react";
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import { MinPageSize } from "../TopBar/ViewOptionPopover.tsx";
|
import { MinPageSize } from "../TopBar/ViewOptionPopover.tsx";
|
||||||
|
|
@ -29,25 +22,20 @@ export interface PaginationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePaginationState = (fmIndex: number) => {
|
export const usePaginationState = (fmIndex: number) => {
|
||||||
const pagination = useAppSelector(
|
const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination);
|
||||||
(state) => state.fileManager[fmIndex].list?.pagination,
|
|
||||||
);
|
|
||||||
const totalItems = pagination?.total_items;
|
const totalItems = pagination?.total_items;
|
||||||
const page = pagination?.page;
|
const page = pagination?.page;
|
||||||
const pageSize = pagination?.page_size;
|
const pageSize = pagination?.page_size;
|
||||||
|
|
||||||
const currentPage = (page ?? 0) + 1;
|
const currentPage = (page ?? 0) + 1;
|
||||||
const totalPages = Math.ceil(
|
const totalPages = Math.ceil((totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize));
|
||||||
(totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize),
|
|
||||||
);
|
|
||||||
const usePagination = totalPages > 1;
|
const usePagination = totalPages > 1;
|
||||||
return {
|
return {
|
||||||
currentPage,
|
currentPage,
|
||||||
totalPages,
|
totalPages,
|
||||||
usePagination,
|
usePagination,
|
||||||
useEndlessLoading: !usePagination,
|
useEndlessLoading: !usePagination,
|
||||||
moreItems:
|
moreItems: pagination?.next_token || (usePagination && currentPage < totalPages),
|
||||||
pagination?.next_token || (usePagination && currentPage < totalPages),
|
|
||||||
nextToken: pagination?.next_token,
|
nextToken: pagination?.next_token,
|
||||||
} as PaginationState;
|
} as PaginationState;
|
||||||
};
|
};
|
||||||
|
|
@ -64,10 +52,7 @@ const PaginationFooter = forwardRef((_props, ref) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slide direction={"up"} unmountOnExit in={paginationState.usePagination}>
|
<Slide direction={"up"} unmountOnExit in={paginationState.usePagination}>
|
||||||
<Box
|
<Box ref={ref} sx={{ display: "flex", px: isMobile ? 1 : 0, pb: isMobile ? 1 : 0 }}>
|
||||||
ref={ref}
|
|
||||||
sx={{ display: "flex", px: isMobile ? 1 : 0, pb: isMobile ? 1 : 0 }}
|
|
||||||
>
|
|
||||||
<PaginationFrame withBorder>
|
<PaginationFrame withBorder>
|
||||||
<Pagination
|
<Pagination
|
||||||
renderItem={(item) => <PaginationItem {...item} />}
|
renderItem={(item) => <PaginationItem {...item} />}
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,15 @@ import { mergeRefs } from "../../../util";
|
||||||
|
|
||||||
let timeOut: ReturnType<typeof setTimeout> | undefined = undefined;
|
let timeOut: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||||
|
|
||||||
const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>(
|
const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({
|
||||||
({ theme, isDropOver }) => ({
|
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
||||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
transitionProperty: "background-color,opacity,box-shadow",
|
||||||
transitionProperty: "background-color,opacity,box-shadow",
|
boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
|
||||||
boxShadow: isDropOver
|
}));
|
||||||
? `inset 0 0 0 2px ${theme.palette.primary.light}`
|
|
||||||
: "none",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const CustomPaginationItem = (props: PaginationItemProps) => {
|
const CustomPaginationItem = (props: PaginationItemProps) => {
|
||||||
const [drag, drop, isOver, isDragging] = useFileDrag({
|
const [drag, drop, isOver, isDragging] = useFileDrag({
|
||||||
dropUri:
|
dropUri: props.type !== "start-ellipsis" && props.type !== "end-ellipsis" ? NoOpDropUri : undefined,
|
||||||
props.type !== "start-ellipsis" && props.type !== "end-ellipsis"
|
|
||||||
? NoOpDropUri
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
const buttonRef = useRef<HTMLElement>();
|
const buttonRef = useRef<HTMLElement>();
|
||||||
|
|
||||||
|
|
@ -47,9 +40,7 @@ const CustomPaginationItem = (props: PaginationItemProps) => {
|
||||||
[drop, buttonRef],
|
[drop, buttonRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <StyledPaginationItem isDropOver={isOver} ref={mergedRef} {...props} />;
|
||||||
<StyledPaginationItem isDropOver={isOver} ref={mergedRef} {...props} />
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomPaginationItem;
|
export default CustomPaginationItem;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,7 @@ import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { SecondaryButton } from "../../../Common/StyledComponents.tsx";
|
import { SecondaryButton } from "../../../Common/StyledComponents.tsx";
|
||||||
import Add from "../../../Icons/Add.tsx";
|
import Add from "../../../Icons/Add.tsx";
|
||||||
import {
|
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||||
bindMenu,
|
|
||||||
bindTrigger,
|
|
||||||
usePopupState,
|
|
||||||
} from "material-ui-popup-state/hooks";
|
|
||||||
import { ListItemIcon, ListItemText, Menu } from "@mui/material";
|
import { ListItemIcon, ListItemText, Menu } from "@mui/material";
|
||||||
import { Condition, ConditionType } from "./ConditionBox.tsx";
|
import { Condition, ConditionType } from "./ConditionBox.tsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
@ -154,20 +150,13 @@ const AddCondition = (props: AddConditionProps) => {
|
||||||
const onConditionAdd = (condition: Condition) => {
|
const onConditionAdd = (condition: Condition) => {
|
||||||
props.onConditionAdd({
|
props.onConditionAdd({
|
||||||
...condition,
|
...condition,
|
||||||
id:
|
id: condition.type == ConditionType.metadata && !condition.id ? Math.random().toString() : condition.id,
|
||||||
condition.type == ConditionType.metadata && !condition.id
|
|
||||||
? Math.random().toString()
|
|
||||||
: condition.id,
|
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SecondaryButton
|
<SecondaryButton {...bindTrigger(conditionPopupState)} startIcon={<Add />} sx={{ px: "15px" }}>
|
||||||
{...bindTrigger(conditionPopupState)}
|
|
||||||
startIcon={<Add />}
|
|
||||||
sx={{ px: "15px" }}
|
|
||||||
>
|
|
||||||
{t("navbar.addCondition")}
|
{t("navbar.addCondition")}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
<Menu
|
<Menu
|
||||||
|
|
@ -183,11 +172,7 @@ const AddCondition = (props: AddConditionProps) => {
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
>
|
>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<SquareMenuItem
|
<SquareMenuItem dense key={index} onClick={() => onConditionAdd(option.condition)}>
|
||||||
dense
|
|
||||||
key={index}
|
|
||||||
onClick={() => onConditionAdd(option.condition)}
|
|
||||||
>
|
|
||||||
<ListItemIcon>{option.icon}</ListItemIcon>
|
<ListItemIcon>{option.icon}</ListItemIcon>
|
||||||
{t(option.name)}
|
{t(option.name)}
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
|
|
@ -198,14 +183,12 @@ const AddCondition = (props: AddConditionProps) => {
|
||||||
title={t("application:fileManager.mediaInfo")}
|
title={t("application:fileManager.mediaInfo")}
|
||||||
>
|
>
|
||||||
{mediaMetaOptions.map((option, index) => (
|
{mediaMetaOptions.map((option, index) => (
|
||||||
<SquareMenuItem
|
<SquareMenuItem key={index} dense onClick={() => onConditionAdd(option.condition)}>
|
||||||
key={index}
|
<ListItemText
|
||||||
dense
|
slotProps={{
|
||||||
onClick={() => onConditionAdd(option.condition)}
|
primary: { variant: "body2" },
|
||||||
>
|
}}
|
||||||
<ListItemText slotProps={{
|
>
|
||||||
primary: { variant: "body2" }
|
|
||||||
}}>
|
|
||||||
{t(option.name)}
|
{t(option.name)}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,7 @@ import { defaultPath } from "../../../../hooks/useNavigation.tsx";
|
||||||
import { advancedSearch } from "../../../../redux/thunks/filemanager.ts";
|
import { advancedSearch } from "../../../../redux/thunks/filemanager.ts";
|
||||||
import { Metadata } from "../../../../api/explorer.ts";
|
import { Metadata } from "../../../../api/explorer.ts";
|
||||||
|
|
||||||
const searchParamToConditions = (
|
const searchParamToConditions = (search_params: SearchParam, base: string): Condition[] => {
|
||||||
search_params: SearchParam,
|
|
||||||
base: string,
|
|
||||||
): Condition[] => {
|
|
||||||
const applied: Condition[] = [
|
const applied: Condition[] = [
|
||||||
{
|
{
|
||||||
type: ConditionType.base,
|
type: ConditionType.base,
|
||||||
|
|
@ -41,10 +38,7 @@ const searchParamToConditions = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (search_params.size_gte != undefined || search_params.size_lte != undefined) {
|
||||||
search_params.size_gte != undefined ||
|
|
||||||
search_params.size_lte != undefined
|
|
||||||
) {
|
|
||||||
applied.push({
|
applied.push({
|
||||||
type: ConditionType.size,
|
type: ConditionType.size,
|
||||||
size_gte: search_params.size_gte,
|
size_gte: search_params.size_gte,
|
||||||
|
|
@ -52,10 +46,7 @@ const searchParamToConditions = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (search_params.created_at_gte != undefined || search_params.created_at_lte != undefined) {
|
||||||
search_params.created_at_gte != undefined ||
|
|
||||||
search_params.created_at_lte != undefined
|
|
||||||
) {
|
|
||||||
applied.push({
|
applied.push({
|
||||||
type: ConditionType.created,
|
type: ConditionType.created,
|
||||||
created_gte: search_params.created_at_gte,
|
created_gte: search_params.created_at_gte,
|
||||||
|
|
@ -63,10 +54,7 @@ const searchParamToConditions = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (search_params.updated_at_gte != undefined || search_params.updated_at_lte != undefined) {
|
||||||
search_params.updated_at_gte != undefined ||
|
|
||||||
search_params.updated_at_lte != undefined
|
|
||||||
) {
|
|
||||||
applied.push({
|
applied.push({
|
||||||
type: ConditionType.modified,
|
type: ConditionType.modified,
|
||||||
updated_gte: search_params.updated_at_gte,
|
updated_gte: search_params.updated_at_gte,
|
||||||
|
|
@ -106,18 +94,10 @@ const AdvanceSearch = () => {
|
||||||
|
|
||||||
const [conditions, setConditions] = useState<Condition[]>([]);
|
const [conditions, setConditions] = useState<Condition[]>([]);
|
||||||
const open = useAppSelector((state) => state.globalState.advanceSearchOpen);
|
const open = useAppSelector((state) => state.globalState.advanceSearchOpen);
|
||||||
const base = useAppSelector(
|
const base = useAppSelector((state) => state.globalState.advanceSearchBasePath);
|
||||||
(state) => state.globalState.advanceSearchBasePath,
|
const initialNames = useAppSelector((state) => state.globalState.advanceSearchInitialNameCondition);
|
||||||
);
|
const search_params = useAppSelector((state) => state.fileManager[FileManagerIndex.main].search_params);
|
||||||
const initialNames = useAppSelector(
|
const current_base = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path);
|
||||||
(state) => state.globalState.advanceSearchInitialNameCondition,
|
|
||||||
);
|
|
||||||
const search_params = useAppSelector(
|
|
||||||
(state) => state.fileManager[FileManagerIndex.main].search_params,
|
|
||||||
);
|
|
||||||
const current_base = useAppSelector(
|
|
||||||
(state) => state.fileManager[FileManagerIndex.main].pure_path,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(closeAdvanceSearch());
|
dispatch(closeAdvanceSearch());
|
||||||
|
|
@ -141,10 +121,7 @@ const AdvanceSearch = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search_params) {
|
if (search_params) {
|
||||||
const existedConditions = searchParamToConditions(
|
const existedConditions = searchParamToConditions(search_params, current_base ?? defaultPath);
|
||||||
search_params,
|
|
||||||
current_base ?? defaultPath,
|
|
||||||
);
|
|
||||||
if (existedConditions.length > 0) {
|
if (existedConditions.length > 0) {
|
||||||
setConditions(existedConditions);
|
setConditions(existedConditions);
|
||||||
}
|
}
|
||||||
|
|
@ -157,9 +134,7 @@ const AdvanceSearch = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onConditionAdd = (condition: Condition) => {
|
const onConditionAdd = (condition: Condition) => {
|
||||||
if (
|
if (conditions.find((c) => c.type === condition.type && c.id === condition.id)) {
|
||||||
conditions.find((c) => c.type === condition.type && c.id === condition.id)
|
|
||||||
) {
|
|
||||||
enqueueSnackbar(t("application:navbar.conditionDuplicate"), {
|
enqueueSnackbar(t("application:navbar.conditionDuplicate"), {
|
||||||
variant: "warning",
|
variant: "warning",
|
||||||
action: DefaultCloseAction,
|
action: DefaultCloseAction,
|
||||||
|
|
@ -194,11 +169,7 @@ const AdvanceSearch = () => {
|
||||||
<Collapse key={`${condition.type} ${condition.id}`}>
|
<Collapse key={`${condition.type} ${condition.id}`}>
|
||||||
<ConditionBox
|
<ConditionBox
|
||||||
index={index}
|
index={index}
|
||||||
onRemove={
|
onRemove={conditions.length > 2 && condition.type != ConditionType.base ? onConditionRemove : undefined}
|
||||||
conditions.length > 2 && condition.type != ConditionType.base
|
|
||||||
? onConditionRemove
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
condition={condition}
|
condition={condition}
|
||||||
onChange={(condition) => {
|
onChange={(condition) => {
|
||||||
const new_conditions = [...conditions];
|
const new_conditions = [...conditions];
|
||||||
|
|
|
||||||
|
|
@ -137,49 +137,25 @@ const ConditionBox = forwardRef((props: ConditionProps, ref) => {
|
||||||
<Icon sx={{ width: "20px", height: "20px" }} />
|
<Icon sx={{ width: "20px", height: "20px" }} />
|
||||||
<Box sx={{ flexGrow: 1 }}>{title}</Box>
|
<Box sx={{ flexGrow: 1 }}>{title}</Box>
|
||||||
<Grow in={hovered && !!onRemove}>
|
<Grow in={hovered && !!onRemove}>
|
||||||
<IconButton
|
<IconButton onClick={onRemove ? () => onRemove(condition) : undefined}>
|
||||||
onClick={onRemove ? () => onRemove(condition) : undefined}
|
|
||||||
>
|
|
||||||
<Dismiss fontSize={"small"} />
|
<Dismiss fontSize={"small"} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grow>
|
</Grow>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box>
|
<Box>
|
||||||
{condition.type == ConditionType.name && (
|
{condition.type == ConditionType.name && (
|
||||||
<FileNameCondition
|
<FileNameCondition condition={condition} onChange={onChange} onNameConditionAdded={onNameConditionAdded} />
|
||||||
condition={condition}
|
|
||||||
onChange={onChange}
|
|
||||||
onNameConditionAdded={onNameConditionAdded}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{condition.type == ConditionType.type && (
|
|
||||||
<FileTypeCondition condition={condition} onChange={onChange} />
|
|
||||||
)}
|
|
||||||
{condition.type == ConditionType.base && (
|
|
||||||
<SearchBaseCondition condition={condition} onChange={onChange} />
|
|
||||||
)}
|
|
||||||
{condition.type == ConditionType.tag && (
|
|
||||||
<TagCondition onChange={onChange} condition={condition} />
|
|
||||||
)}
|
|
||||||
{condition.type == ConditionType.metadata && (
|
|
||||||
<MetadataCondition onChange={onChange} condition={condition} />
|
|
||||||
)}
|
|
||||||
{condition.type == ConditionType.size && (
|
|
||||||
<SizeCondition condition={condition} onChange={onChange} />
|
|
||||||
)}
|
)}
|
||||||
|
{condition.type == ConditionType.type && <FileTypeCondition condition={condition} onChange={onChange} />}
|
||||||
|
{condition.type == ConditionType.base && <SearchBaseCondition condition={condition} onChange={onChange} />}
|
||||||
|
{condition.type == ConditionType.tag && <TagCondition onChange={onChange} condition={condition} />}
|
||||||
|
{condition.type == ConditionType.metadata && <MetadataCondition onChange={onChange} condition={condition} />}
|
||||||
|
{condition.type == ConditionType.size && <SizeCondition condition={condition} onChange={onChange} />}
|
||||||
{condition.type == ConditionType.created && (
|
{condition.type == ConditionType.created && (
|
||||||
<DateTimeCondition
|
<DateTimeCondition condition={condition} onChange={onChange} field={"created"} />
|
||||||
condition={condition}
|
|
||||||
onChange={onChange}
|
|
||||||
field={"created"}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{condition.type == ConditionType.modified && (
|
{condition.type == ConditionType.modified && (
|
||||||
<DateTimeCondition
|
<DateTimeCondition condition={condition} onChange={onChange} field={"updated"} />
|
||||||
condition={condition}
|
|
||||||
onChange={onChange}
|
|
||||||
field={"updated"}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</StyledBox>
|
</StyledBox>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
import {
|
import { Autocomplete, Box, Chip, FormControlLabel, styled } from "@mui/material";
|
||||||
Autocomplete,
|
|
||||||
Box,
|
|
||||||
Chip,
|
|
||||||
FormControlLabel,
|
|
||||||
styled,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { FilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx";
|
||||||
FilledTextField,
|
|
||||||
StyledCheckbox,
|
|
||||||
} from "../../../Common/StyledComponents.tsx";
|
|
||||||
import { Condition } from "./ConditionBox.tsx";
|
import { Condition } from "./ConditionBox.tsx";
|
||||||
|
|
||||||
export const StyledBox = styled(Box)(({ theme }) => ({
|
export const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
|
@ -41,9 +32,7 @@ export const FileNameCondition = ({
|
||||||
renderTags={(value: readonly string[], getTagProps) =>
|
renderTags={(value: readonly string[], getTagProps) =>
|
||||||
value.map((option: string, index: number) => {
|
value.map((option: string, index: number) => {
|
||||||
const { key, ...tagProps } = getTagProps({ index });
|
const { key, ...tagProps } = getTagProps({ index });
|
||||||
return (
|
return <Chip variant="outlined" label={option} key={key} {...tagProps} />;
|
||||||
<Chip variant="outlined" label={option} key={key} {...tagProps} />
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,11 @@ export const FileTypeCondition = ({
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Document fontSize="small" />
|
<Document fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("application:fileManager.file")}
|
{t("application:fileManager.file")}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
|
|
@ -41,9 +43,11 @@ export const FileTypeCondition = ({
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Folder fontSize="small" />
|
<Folder fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText slotProps={{
|
<ListItemText
|
||||||
primary: { variant: "body2" }
|
slotProps={{
|
||||||
}}>
|
primary: { variant: "body2" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("application:fileManager.folder")}
|
{t("application:fileManager.folder")}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ export const MetadataCondition = ({
|
||||||
variant="filled"
|
variant="filled"
|
||||||
label={t("application:fileManager.metadataKey")}
|
label={t("application:fileManager.metadataKey")}
|
||||||
value={condition.metadata_key ?? ""}
|
value={condition.metadata_key ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) => onChange({ ...condition, metadata_key: e.target.value })}
|
||||||
onChange({ ...condition, metadata_key: e.target.value })
|
|
||||||
}
|
|
||||||
disabled={condition.metadata_key_readonly}
|
disabled={condition.metadata_key_readonly}
|
||||||
type="text"
|
type="text"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
@ -33,9 +31,7 @@ export const MetadataCondition = ({
|
||||||
variant="filled"
|
variant="filled"
|
||||||
label={t("application:fileManager.metadataValue")}
|
label={t("application:fileManager.metadataValue")}
|
||||||
value={condition.metadata_value ?? ""}
|
value={condition.metadata_value ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) => onChange({ ...condition, metadata_value: e.target.value })}
|
||||||
onChange({ ...condition, metadata_value: e.target.value })
|
|
||||||
}
|
|
||||||
type="text"
|
type="text"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ export const TagCondition = ({
|
||||||
renderTags={(value: readonly string[], getTagProps) =>
|
renderTags={(value: readonly string[], getTagProps) =>
|
||||||
value.map((option: string, index: number) => {
|
value.map((option: string, index: number) => {
|
||||||
const { key, ...tagProps } = getTagProps({ index });
|
const { key, ...tagProps } = getTagProps({ index });
|
||||||
return (
|
return <Chip variant="outlined" label={option} key={key} {...tagProps} />;
|
||||||
<Chip variant="outlined" label={option} key={key} {...tagProps} />
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { Box, List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
|
||||||
Box,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemText,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -27,8 +20,7 @@ const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(base: string) => () =>
|
(base: string) => () => dispatch(quickSearch(FileManagerIndex.main, base, keyword)),
|
||||||
dispatch(quickSearch(FileManagerIndex.main, base, keyword)),
|
|
||||||
[keyword, dispatch],
|
[keyword, dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -56,15 +48,13 @@ const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => {
|
||||||
values={{
|
values={{
|
||||||
keywords: keyword,
|
keywords: keyword,
|
||||||
}}
|
}}
|
||||||
components={[
|
components={[<Box component={"span"} sx={{ fontWeight: 600 }} />]}
|
||||||
<Box component={"span"} sx={{ fontWeight: 600 }} />,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
primary: {
|
primary: {
|
||||||
variant: "body2",
|
variant: "body2",
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FileBadge
|
<FileBadge
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts";
|
import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts";
|
||||||
import {
|
import { List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemText,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { sizeToString } from "../../../util";
|
import { sizeToString } from "../../../util";
|
||||||
import FileIcon from "../Explorer/FileIcon.tsx";
|
import FileIcon from "../Explorer/FileIcon.tsx";
|
||||||
|
|
@ -49,15 +43,7 @@ const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => {
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
sx={{ py: 0 }}
|
sx={{ py: 0 }}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
dispatch(
|
dispatch(openFileContextMenu(FileManagerIndex.main, file, true, e, ContextMenuTypes.searchResult))
|
||||||
openFileContextMenu(
|
|
||||||
FileManagerIndex.main,
|
|
||||||
file,
|
|
||||||
true,
|
|
||||||
e,
|
|
||||||
ContextMenuTypes.searchResult,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemAvatar sx={{ minWidth: 48 }}>
|
<ListItemAvatar sx={{ minWidth: 48 }}>
|
||||||
|
|
@ -91,8 +77,9 @@ const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => {
|
||||||
|
|
||||||
secondary: {
|
secondary: {
|
||||||
variant: "body2",
|
variant: "body2",
|
||||||
}
|
},
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
<FileBadge
|
<FileBadge
|
||||||
clickable
|
clickable
|
||||||
variant={"outlined"}
|
variant={"outlined"}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,10 @@ import { useTranslation } from "react-i18next";
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import { useContext, useMemo } from "react";
|
import { useContext, useMemo } from "react";
|
||||||
import { FmIndexContext } from "../FmIndexContext.tsx";
|
import { FmIndexContext } from "../FmIndexContext.tsx";
|
||||||
import {
|
import { alpha, Button, ButtonGroup, Grow, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||||
alpha,
|
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
Grow,
|
|
||||||
styled,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import Search from "../../Icons/Search.tsx";
|
import Search from "../../Icons/Search.tsx";
|
||||||
import Dismiss from "../../Icons/Dismiss.tsx";
|
import Dismiss from "../../Icons/Dismiss.tsx";
|
||||||
import {
|
import { clearSearch, openAdvancedSearch } from "../../../redux/thunks/filemanager.ts";
|
||||||
clearSearch,
|
|
||||||
openAdvancedSearch,
|
|
||||||
} from "../../../redux/thunks/filemanager.ts";
|
|
||||||
import { FileManagerIndex } from "../FileManager.tsx";
|
import { FileManagerIndex } from "../FileManager.tsx";
|
||||||
|
|
||||||
export const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({
|
export const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({
|
||||||
|
|
@ -44,9 +33,7 @@ export const SearchIndicator = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
|
|
||||||
const search_params = useAppSelector(
|
const search_params = useAppSelector((state) => state.fileManager[fmIndex].search_params);
|
||||||
(state) => state.fileManager[fmIndex].search_params,
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchConditionsCount = useMemo(() => {
|
const searchConditionsCount = useMemo(() => {
|
||||||
if (!search_params) {
|
if (!search_params) {
|
||||||
|
|
@ -90,10 +77,7 @@ export const SearchIndicator = () => {
|
||||||
num: searchConditionsCount,
|
num: searchConditionsCount,
|
||||||
})}
|
})}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
<StyledButton
|
<StyledButton size={"small"} onClick={() => dispatch(clearSearch(fmIndex))}>
|
||||||
size={"small"}
|
|
||||||
onClick={() => dispatch(clearSearch(fmIndex))}
|
|
||||||
>
|
|
||||||
<Dismiss fontSize={"small"} sx={{ width: 16, height: 16 }} />
|
<Dismiss fontSize={"small"} sx={{ width: 16, height: 16 }} />
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</StyledButtonGroup>
|
</StyledButtonGroup>
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,7 @@ import SessionManager from "../../../session";
|
||||||
import { defaultPath } from "../../../hooks/useNavigation.tsx";
|
import { defaultPath } from "../../../hooks/useNavigation.tsx";
|
||||||
import FullSearchOption from "./FullSearchOptions.tsx";
|
import FullSearchOption from "./FullSearchOptions.tsx";
|
||||||
import { TransitionProps } from "@mui/material/transitions";
|
import { TransitionProps } from "@mui/material/transitions";
|
||||||
import {
|
import { openAdvancedSearch, quickSearch } from "../../../redux/thunks/filemanager.ts";
|
||||||
openAdvancedSearch,
|
|
||||||
quickSearch,
|
|
||||||
} from "../../../redux/thunks/filemanager.ts";
|
|
||||||
import Options from "../../Icons/Options.tsx";
|
import Options from "../../Icons/Options.tsx";
|
||||||
|
|
||||||
const StyledDialog = styled(Dialog)<{
|
const StyledDialog = styled(Dialog)<{
|
||||||
|
|
@ -66,9 +63,7 @@ const SearchPopup = () => {
|
||||||
|
|
||||||
const [keywords, setKeywords] = useState("");
|
const [keywords, setKeywords] = useState("");
|
||||||
const [searchedKeyword, setSearchedKeyword] = useState("");
|
const [searchedKeyword, setSearchedKeyword] = useState("");
|
||||||
const [treeSearchResults, setTreeSearchResults] = useState<FileResponse[]>(
|
const [treeSearchResults, setTreeSearchResults] = useState<FileResponse[]>([]);
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dispatch(setSearchPopup(false));
|
dispatch(setSearchPopup(false));
|
||||||
|
|
@ -77,48 +72,36 @@ const SearchPopup = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const open = useAppSelector((state) => state.globalState.searchPopupOpen);
|
const open = useAppSelector((state) => state.globalState.searchPopupOpen);
|
||||||
const tree = useAppSelector(
|
const tree = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.tree);
|
||||||
(state) => state.fileManager[FileManagerIndex.main]?.tree,
|
const path = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.path);
|
||||||
);
|
const single_file_view = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.list?.single_file_view);
|
||||||
const path = useAppSelector(
|
|
||||||
(state) => state.fileManager[FileManagerIndex.main]?.path,
|
|
||||||
);
|
|
||||||
const single_file_view = useAppSelector(
|
|
||||||
(state) => state.fileManager[FileManagerIndex.main]?.list?.single_file_view,
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchTree = useMemo(
|
const searchTree = useMemo(
|
||||||
() =>
|
() =>
|
||||||
debounce(
|
debounce((request: { input: string }, callback: (results?: FileResponse[]) => void) => {
|
||||||
(
|
const options = {
|
||||||
request: { input: string },
|
includeScore: true,
|
||||||
callback: (results?: FileResponse[]) => void,
|
// Search in `author` and in `tags` array
|
||||||
) => {
|
keys: ["file.name"],
|
||||||
const options = {
|
};
|
||||||
includeScore: true,
|
const fuse = new Fuse(Object.values(tree), options);
|
||||||
// Search in `author` and in `tags` array
|
const result = fuse.search(
|
||||||
keys: ["file.name"],
|
request.input
|
||||||
};
|
.split(" ")
|
||||||
const fuse = new Fuse(Object.values(tree), options);
|
.filter((k) => k != "")
|
||||||
const result = fuse.search(
|
.join(" "),
|
||||||
request.input
|
{ limit: 50 },
|
||||||
.split(" ")
|
);
|
||||||
.filter((k) => k != "")
|
const res: FileResponse[] = [];
|
||||||
.join(" "),
|
result
|
||||||
{ limit: 50 },
|
.filter((r) => r.item.file != undefined)
|
||||||
);
|
.forEach((r) => {
|
||||||
const res: FileResponse[] = [];
|
if (r.item.file) {
|
||||||
result
|
res.push(r.item.file);
|
||||||
.filter((r) => r.item.file != undefined)
|
}
|
||||||
.forEach((r) => {
|
});
|
||||||
if (r.item.file) {
|
callback(res);
|
||||||
res.push(r.item.file);
|
}, 400),
|
||||||
}
|
|
||||||
});
|
|
||||||
callback(res);
|
|
||||||
},
|
|
||||||
400,
|
|
||||||
),
|
|
||||||
[tree],
|
[tree],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -158,10 +141,7 @@ const SearchPopup = () => {
|
||||||
res.push(current.base());
|
res.push(current.base());
|
||||||
}
|
}
|
||||||
// my files - user login and not my fs
|
// my files - user login and not my fs
|
||||||
if (
|
if (SessionManager.currentLoginOrNull() && !(current.fs() == Filesystem.my)) {
|
||||||
SessionManager.currentLoginOrNull() &&
|
|
||||||
!(current.fs() == Filesystem.my)
|
|
||||||
) {
|
|
||||||
res.push(defaultPath);
|
res.push(defaultPath);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -173,9 +153,7 @@ const SearchPopup = () => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (fullSearchOptions.length > 0) {
|
if (fullSearchOptions.length > 0) {
|
||||||
dispatch(
|
dispatch(quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords));
|
||||||
quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -214,9 +192,7 @@ const SearchPopup = () => {
|
||||||
height: 40,
|
height: 40,
|
||||||
mr: 1.5,
|
mr: 1.5,
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
onClick={() => dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))}
|
||||||
dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Options />
|
<Options />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
@ -244,10 +220,7 @@ const SearchPopup = () => {
|
||||||
>
|
>
|
||||||
{t("navbar.searchFilesTitle")}
|
{t("navbar.searchFilesTitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<FullSearchOption
|
<FullSearchOption keyword={keywords} options={fullSearchOptions} />
|
||||||
keyword={keywords}
|
|
||||||
options={fullSearchOptions}
|
|
||||||
/>
|
|
||||||
{treeSearchResults.length > 0 && <Divider />}
|
{treeSearchResults.length > 0 && <Divider />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -264,10 +237,7 @@ const SearchPopup = () => {
|
||||||
>
|
>
|
||||||
{t("navbar.recentlyViewed")}
|
{t("navbar.recentlyViewed")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<FuzzySearchResult
|
<FuzzySearchResult keyword={searchedKeyword} files={treeSearchResults} />
|
||||||
keyword={searchedKeyword}
|
|
||||||
files={treeSearchResults}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AutoHeight>
|
</AutoHeight>
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,7 @@ export interface DetailsProps {
|
||||||
target: FileResponse;
|
target: FileResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InfoBlock = ({
|
const InfoBlock = ({ title, children }: { title: string; children: React.ReactNode }) => {
|
||||||
title,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant={"body2"}>{title}</Typography>
|
<Typography variant={"body2"}>{title}</Typography>
|
||||||
|
|
@ -35,11 +29,7 @@ const Details = ({ target, inPhotoViewer }: DetailsProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [thumbSrc, setThumbSrc] = useState<string | null>(null);
|
const [thumbSrc, setThumbSrc] = useState<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (target.type == FileType.file && (!target.metadata || target.metadata[Metadata.thumbDisabled] === undefined)) {
|
||||||
target.type == FileType.file &&
|
|
||||||
(!target.metadata ||
|
|
||||||
target.metadata[Metadata.thumbDisabled] === undefined)
|
|
||||||
) {
|
|
||||||
dispatch(loadFileThumb(FileManagerIndex.main, target)).then((src) => {
|
dispatch(loadFileThumb(FileManagerIndex.main, target)).then((src) => {
|
||||||
setThumbSrc(src);
|
setThumbSrc(src);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,10 @@ const Header = ({ target }: HeaderProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", p: 2 }}>
|
<Box sx={{ display: "flex", p: 2 }}>
|
||||||
{target !== null && (
|
{target !== null && <FileIcon sx={{ p: 0 }} loading={target == undefined} file={target} type={target?.type} />}
|
||||||
<FileIcon
|
|
||||||
sx={{ p: 0 }}
|
|
||||||
loading={target == undefined}
|
|
||||||
file={target}
|
|
||||||
type={target?.type}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{target !== null && (
|
{target !== null && (
|
||||||
<Box sx={{ flexGrow: 1, ml: 1.5 }}>
|
<Box sx={{ flexGrow: 1, ml: 1.5 }}>
|
||||||
<Typography
|
<Typography color="textPrimary" sx={{ wordBreak: "break-all" }} variant={"subtitle2"}>
|
||||||
color="textPrimary"
|
|
||||||
sx={{ wordBreak: "break-all" }}
|
|
||||||
variant={"subtitle2"}
|
|
||||||
>
|
|
||||||
{target && target.name}
|
{target && target.name}
|
||||||
{!target && <Skeleton variant={"text"} width={75} />}
|
{!target && <Skeleton variant={"text"} width={75} />}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
|
||||||
|
|
@ -12,23 +12,11 @@ export interface MapLoaderProps extends BoxProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MapLoader = (props: MapLoaderProps) => {
|
const MapLoader = (props: MapLoaderProps) => {
|
||||||
const mapProvider = useAppSelector(
|
const mapProvider = useAppSelector((state) => state.siteConfig.explorer.config.map_provider);
|
||||||
(state) => state.siteConfig.explorer.config.map_provider,
|
const googleTileType = useAppSelector((state) => state.siteConfig.explorer.config.google_map_tile_type);
|
||||||
);
|
|
||||||
const googleTileType = useAppSelector(
|
|
||||||
(state) => state.siteConfig.explorer.config.google_map_tile_type,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense fallback={<Skeleton variant="rounded" width={"100%"} height={props.height} />}>
|
||||||
fallback={
|
<MapBox mapProvider={mapProvider} googleTileType={googleTileType} {...props} />
|
||||||
<Skeleton variant="rounded" width={"100%"} height={props.height} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MapBox
|
|
||||||
mapProvider={mapProvider}
|
|
||||||
googleTileType={googleTileType}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,8 @@ export interface MediaMetaCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledButtonBase = styled(Box)(({ theme }) => {
|
const StyledButtonBase = styled(Box)(({ theme }) => {
|
||||||
let bgColor =
|
let bgColor = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
|
||||||
theme.palette.mode === "light"
|
let bgColorHover = theme.palette.mode === "light" ? theme.palette.grey[300] : theme.palette.grey[700];
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900];
|
|
||||||
let bgColorHover =
|
|
||||||
theme.palette.mode === "light"
|
|
||||||
? theme.palette.grey[300]
|
|
||||||
: theme.palette.grey[700];
|
|
||||||
return {
|
return {
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
backgroundColor: bgColor,
|
backgroundColor: bgColor,
|
||||||
|
|
@ -69,21 +63,12 @@ export interface MediaMetaElementsProps extends LinkProps {
|
||||||
element: MediaMetaElements;
|
element: MediaMetaElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaMetaElements = ({
|
export const MediaMetaElements = ({ element, ...rest }: MediaMetaElementsProps) => {
|
||||||
element,
|
|
||||||
...rest
|
|
||||||
}: MediaMetaElementsProps) => {
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
dispatch(
|
dispatch(searchMetadata(FileManagerIndex.main, element.searchKey, element.searchValue));
|
||||||
searchMetadata(
|
|
||||||
FileManagerIndex.main,
|
|
||||||
element.searchKey,
|
|
||||||
element.searchValue,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -113,18 +98,10 @@ export const MediaMetaElements = ({
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Clipboard fontSize="small" />
|
<Clipboard fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>
|
<ListItemText>{t("application:fileManager.copyToClipboard")}</ListItemText>
|
||||||
{t("application:fileManager.copyToClipboard")}
|
|
||||||
</ListItemText>
|
|
||||||
</SquareMenuItem>
|
</SquareMenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Link
|
<Link color="inherit" href={"#"} onClick={(e) => setAnchorEl(e.currentTarget)} underline="hover" {...rest}>
|
||||||
color="inherit"
|
|
||||||
href={"#"}
|
|
||||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
|
||||||
underline="hover"
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{element.display}
|
{element.display}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
|
@ -147,26 +124,14 @@ const MediaMetaCard = ({ contents, icon }: MediaMetaCardProps) => {
|
||||||
>
|
>
|
||||||
{contents.map(({ title, content }) => (
|
{contents.map(({ title, content }) => (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography variant={"body2"} color="textPrimary" fontWeight={500}>
|
||||||
variant={"body2"}
|
|
||||||
color="textPrimary"
|
|
||||||
fontWeight={500}
|
|
||||||
>
|
|
||||||
{title.map((element) =>
|
{title.map((element) =>
|
||||||
typeof element === "string" ? (
|
typeof element === "string" ? element : <MediaMetaElements element={element} />,
|
||||||
element
|
|
||||||
) : (
|
|
||||||
<MediaMetaElements element={element} />
|
|
||||||
),
|
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant={"body2"} color={"text.secondary"}>
|
<Typography variant={"body2"} color={"text.secondary"}>
|
||||||
{content.map((element) =>
|
{content.map((element) =>
|
||||||
typeof element === "string" ? (
|
typeof element === "string" ? element : <MediaMetaElements element={element} />,
|
||||||
element
|
|
||||||
) : (
|
|
||||||
<MediaMetaElements element={element} />
|
|
||||||
),
|
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,9 @@ export interface SideBarProps {
|
||||||
const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
|
const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
|
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
|
||||||
const sidebarTarget = useAppSelector(
|
const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget);
|
||||||
(state) => state.globalState.sidebarTarget,
|
|
||||||
);
|
|
||||||
// null: not valid, undefined: not loaded, FileResponse: loaded
|
// null: not valid, undefined: not loaded, FileResponse: loaded
|
||||||
const [target, setTarget] = useState<FileResponse | undefined | null>(
|
const [target, setTarget] = useState<FileResponse | undefined | null>(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadExtendedInfo = useCallback(
|
const loadExtendedInfo = useCallback(
|
||||||
(path: string) => {
|
(path: string) => {
|
||||||
|
|
@ -72,8 +68,7 @@ const Sidebar = ({ inPhotoViewer }: SideBarProps) => {
|
||||||
width: "300px",
|
width: "300px",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
ml: 1,
|
ml: 1,
|
||||||
borderRadius: (theme) =>
|
borderRadius: (theme) => (inPhotoViewer ? 0 : theme.shape.borderRadius / 8),
|
||||||
inPhotoViewer ? 0 : theme.shape.borderRadius / 8,
|
|
||||||
}}
|
}}
|
||||||
withBorder={!inPhotoViewer}
|
withBorder={!inPhotoViewer}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,9 @@ const Transition = forwardRef(function Transition(
|
||||||
const SidebarDialog = ({ inPhotoViewer }: SideBarProps) => {
|
const SidebarDialog = ({ inPhotoViewer }: SideBarProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
|
const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen);
|
||||||
const sidebarTarget = useAppSelector(
|
const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget);
|
||||||
(state) => state.globalState.sidebarTarget,
|
|
||||||
);
|
|
||||||
// null: not valid, undefined: not loaded, FileResponse: loaded
|
// null: not valid, undefined: not loaded, FileResponse: loaded
|
||||||
const [target, setTarget] = useState<FileResponse | undefined | null>(
|
const [target, setTarget] = useState<FileResponse | undefined | null>(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadExtendedInfo = useCallback(
|
const loadExtendedInfo = useCallback(
|
||||||
(path: string) => {
|
(path: string) => {
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,7 @@ const Tags = ({ target }: TagsProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography
|
<Typography sx={{ pt: 1 }} color="textPrimary" fontWeight={500} variant={"subtitle1"}>
|
||||||
sx={{ pt: 1 }}
|
|
||||||
color="textPrimary"
|
|
||||||
fontWeight={500}
|
|
||||||
variant={"subtitle1"}
|
|
||||||
>
|
|
||||||
{t("application:fileManager.tags")}
|
{t("application:fileManager.tags")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box>
|
<Box>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,5 @@
|
||||||
import {
|
import { BreadcrumbButtonProps, useBreadcrumbButtons } from "./BreadcrumbButton.tsx";
|
||||||
BreadcrumbButtonProps,
|
import { ListItemIcon, ListItemText, MenuItem, Skeleton, styled } from "@mui/material";
|
||||||
useBreadcrumbButtons,
|
|
||||||
} from "./BreadcrumbButton.tsx";
|
|
||||||
import {
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
MenuItem,
|
|
||||||
Skeleton,
|
|
||||||
styled,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useContext, useMemo } from "react";
|
import { useContext, useMemo } from "react";
|
||||||
import { useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import FileIcon from "../Explorer/FileIcon.tsx";
|
import FileIcon from "../Explorer/FileIcon.tsx";
|
||||||
|
|
@ -20,24 +11,13 @@ export interface BreadcrumbHiddenItem extends BreadcrumbButtonProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>(
|
export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({
|
||||||
({ theme, isDropOver }) => ({
|
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
||||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important",
|
transitionProperty: "background-color,opacity,box-shadow",
|
||||||
transitionProperty: "background-color,opacity,box-shadow",
|
boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
|
||||||
boxShadow: isDropOver
|
}));
|
||||||
? `inset 0 0 0 2px ${theme.palette.primary.light}`
|
|
||||||
: "none",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const BreadcrumbHiddenItem = ({
|
const BreadcrumbHiddenItem = ({ name, is_root, is_latest, path, onClose, ...rest }: BreadcrumbHiddenItem) => {
|
||||||
name,
|
|
||||||
is_root,
|
|
||||||
is_latest,
|
|
||||||
path,
|
|
||||||
onClose,
|
|
||||||
...rest
|
|
||||||
}: BreadcrumbHiddenItem) => {
|
|
||||||
const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({
|
const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({
|
||||||
name,
|
name,
|
||||||
is_latest,
|
is_latest,
|
||||||
|
|
@ -70,12 +50,7 @@ const BreadcrumbHiddenItem = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMenuItem
|
<StyledMenuItem isDropOver={isOver} ref={drop} {...rest} onClick={onItemClick}>
|
||||||
isDropOver={isOver}
|
|
||||||
ref={drop}
|
|
||||||
{...rest}
|
|
||||||
onClick={onItemClick}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
{StartIcon ? (
|
{StartIcon ? (
|
||||||
StartIcon
|
StartIcon
|
||||||
|
|
@ -91,9 +66,7 @@ const BreadcrumbHiddenItem = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>
|
<ListItemText>{loading ? <Skeleton variant={"text"} width={75} /> : displayName}</ListItemText>
|
||||||
{loading ? <Skeleton variant={"text"} width={75} /> : displayName}
|
|
||||||
</ListItemText>
|
|
||||||
</StyledMenuItem>
|
</StyledMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
import {
|
import { Button, ButtonGroup, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
styled,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { bindPopover } from "material-ui-popup-state";
|
import { bindPopover } from "material-ui-popup-state";
|
||||||
import {
|
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||||
bindMenu,
|
|
||||||
bindTrigger,
|
|
||||||
usePopupState,
|
|
||||||
} from "material-ui-popup-state/hooks";
|
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppSelector } from "../../../redux/hooks.ts";
|
||||||
|
|
@ -42,12 +32,8 @@ const TopActions = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const sortOptions = useAppSelector(
|
const sortOptions = useAppSelector((state) => state.fileManager[fmIndex].list?.props.order_by_options);
|
||||||
(state) => state.fileManager[fmIndex].list?.props.order_by_options,
|
const isSingleFileView = useAppSelector((state) => state.fileManager[fmIndex].list?.single_file_view);
|
||||||
);
|
|
||||||
const isSingleFileView = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex].list?.single_file_view,
|
|
||||||
);
|
|
||||||
const viewPopupState = usePopupState({
|
const viewPopupState = usePopupState({
|
||||||
variant: "popover",
|
variant: "popover",
|
||||||
popupId: "viewOption",
|
popupId: "viewOption",
|
||||||
|
|
@ -68,11 +54,7 @@ const TopActions = () => {
|
||||||
{...bindTrigger(viewPopupState)}
|
{...bindTrigger(viewPopupState)}
|
||||||
startIcon={!isMobile && <TableSettingsOutlined />}
|
startIcon={!isMobile && <TableSettingsOutlined />}
|
||||||
>
|
>
|
||||||
{isMobile ? (
|
{isMobile ? <TableSettingsOutlined fontSize={"small"} /> : t("application:fileManager.view")}
|
||||||
<TableSettingsOutlined fontSize={"small"} />
|
|
||||||
) : (
|
|
||||||
t("application:fileManager.view")
|
|
||||||
)}
|
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{(!(!sortOptions || isSingleFileView) || !isMobile) && (
|
{(!(!sortOptions || isSingleFileView) || !isMobile) && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
@ -80,11 +62,7 @@ const TopActions = () => {
|
||||||
startIcon={!isMobile && <ArrowSort />}
|
startIcon={!isMobile && <ArrowSort />}
|
||||||
{...bindTrigger(sortPopupState)}
|
{...bindTrigger(sortPopupState)}
|
||||||
>
|
>
|
||||||
{isMobile ? (
|
{isMobile ? <ArrowSort fontSize={"small"} /> : t("application:fileManager.sortMethod")}
|
||||||
<ArrowSort fontSize={"small"} />
|
|
||||||
) : (
|
|
||||||
t("application:fileManager.sortMethod")
|
|
||||||
)}
|
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)}
|
)}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,8 @@ interface PinnedItem {
|
||||||
|
|
||||||
export const usePinned = () => {
|
export const usePinned = () => {
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const generation = useAppSelector(
|
const generation = useAppSelector((state) => state.globalState.pinedGeneration);
|
||||||
(state) => state.globalState.pinedGeneration,
|
const path_root = useAppSelector((state) => state.fileManager[fmIndex].path_root);
|
||||||
);
|
|
||||||
const path_root = useAppSelector(
|
|
||||||
(state) => state.fileManager[fmIndex].path_root,
|
|
||||||
);
|
|
||||||
const pined = useMemo(() => {
|
const pined = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
return SessionManager.currentLogin().user.pined?.map((p): PinnedItem => {
|
return SessionManager.currentLogin().user.pined?.map((p): PinnedItem => {
|
||||||
|
|
@ -38,9 +34,7 @@ export const usePinned = () => {
|
||||||
|
|
||||||
const Pinned = memo(() => {
|
const Pinned = memo(() => {
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const elements = useAppSelector(
|
const elements = useAppSelector((state) => state.fileManager[fmIndex].path_elements);
|
||||||
(state) => state.fileManager[fmIndex].path_elements,
|
|
||||||
);
|
|
||||||
const pined = usePinned();
|
const pined = usePinned();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -50,14 +44,9 @@ const Pinned = memo(() => {
|
||||||
sx={{ mt: index == 0 ? 2 : 0 }}
|
sx={{ mt: index == 0 ? 2 : 0 }}
|
||||||
flatten={
|
flatten={
|
||||||
p.crUri.fs() != Filesystem.share ||
|
p.crUri.fs() != Filesystem.share ||
|
||||||
(p.crUri.fs() == Filesystem.share &&
|
(p.crUri.fs() == Filesystem.share && (!p.crUri.is_root() || p.crUri.is_search()))
|
||||||
(!p.crUri.is_root() || p.crUri.is_search()))
|
|
||||||
}
|
|
||||||
canDrop={
|
|
||||||
(p.crUri.fs() == Filesystem.share ||
|
|
||||||
p.crUri.fs() == Filesystem.my) &&
|
|
||||||
!p.crUri.is_search()
|
|
||||||
}
|
}
|
||||||
|
canDrop={(p.crUri.fs() == Filesystem.share || p.crUri.fs() == Filesystem.my) && !p.crUri.is_search()}
|
||||||
level={0}
|
level={0}
|
||||||
labelOverwrite={p.name}
|
labelOverwrite={p.name}
|
||||||
path={p.uri}
|
path={p.uri}
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,11 @@ import { SideNavItemBase } from "../../Frame/NavBar/SideNavItem.tsx";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import FileIcon from "../Explorer/FileIcon.tsx";
|
import FileIcon from "../Explorer/FileIcon.tsx";
|
||||||
import { FileResponse } from "../../../api/explorer.ts";
|
import { FileResponse } from "../../../api/explorer.ts";
|
||||||
import {
|
import { TreeItem, treeItemClasses, TreeItemContentProps, TreeItemProps, useTreeItem } from "@mui/x-tree-view";
|
||||||
TreeItem,
|
|
||||||
treeItemClasses,
|
|
||||||
TreeItemContentProps,
|
|
||||||
TreeItemProps,
|
|
||||||
useTreeItem,
|
|
||||||
} from "@mui/x-tree-view";
|
|
||||||
import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx";
|
import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx";
|
||||||
import { mergeRefs } from "../../../util";
|
import { mergeRefs } from "../../../util";
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import {
|
import { loadChild, navigateToPath } from "../../../redux/thunks/filemanager.ts";
|
||||||
loadChild,
|
|
||||||
navigateToPath,
|
|
||||||
} from "../../../redux/thunks/filemanager.ts";
|
|
||||||
import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
|
import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
|
||||||
import CaretDown from "../../Icons/CaretDown.tsx";
|
import CaretDown from "../../Icons/CaretDown.tsx";
|
||||||
import { StartIcon } from "../TopBar/BreadcrumbButton.tsx";
|
import { StartIcon } from "../TopBar/BreadcrumbButton.tsx";
|
||||||
|
|
@ -41,9 +32,7 @@ const CustomContentRoot = styled(SideNavItemBase)<{
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms",
|
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms",
|
||||||
transitionProperty: "opacity,box-shadow,background-color",
|
transitionProperty: "opacity,box-shadow,background-color",
|
||||||
boxShadow: isDropOver
|
boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none",
|
||||||
? `inset 0 0 0 2px ${theme.palette.primary.light}`
|
|
||||||
: "none",
|
|
||||||
height: "32px",
|
height: "32px",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -56,16 +45,14 @@ const StyledTreeItemRoot = styled(TreeItem)(() => ({
|
||||||
},
|
},
|
||||||
})) as unknown as typeof TreeItem;
|
})) as unknown as typeof TreeItem;
|
||||||
|
|
||||||
export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>(
|
export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>(({ theme, expanded }) => ({
|
||||||
({ theme, expanded }) => ({
|
fontSize: "12px!important",
|
||||||
fontSize: "12px!important",
|
transform: `rotate(${expanded ? 0 : -90}deg)`,
|
||||||
transform: `rotate(${expanded ? 0 : -90}deg)`,
|
transition: theme.transitions.create("transform", {
|
||||||
transition: theme.transitions.create("transform", {
|
duration: theme.transitions.duration.shortest,
|
||||||
duration: theme.transitions.duration.shortest,
|
easing: theme.transitions.easing.easeInOut,
|
||||||
easing: theme.transitions.easing.easeInOut,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
);
|
}));
|
||||||
|
|
||||||
export interface CustomContentProps extends TreeItemContentProps {
|
export interface CustomContentProps extends TreeItemContentProps {
|
||||||
parent?: string;
|
parent?: string;
|
||||||
|
|
@ -117,12 +104,7 @@ const UnpinButton = (props: UnpinButton) => {
|
||||||
return (
|
return (
|
||||||
<Fade in={props.show} unmountOnExit>
|
<Fade in={props.show} unmountOnExit>
|
||||||
<Tooltip title={t("application:fileManager.unpin")}>
|
<Tooltip title={t("application:fileManager.unpin")}>
|
||||||
<SmallIconButton
|
<SmallIconButton disabled={loading} onMouseDown={(e) => e.stopPropagation()} onClick={onClick} size="small">
|
||||||
disabled={loading}
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
onClick={onClick}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<Dismiss fontSize={"inherit"} />
|
<Dismiss fontSize={"inherit"} />
|
||||||
</SmallIconButton>
|
</SmallIconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -136,27 +118,10 @@ const CustomContent = React.memo(
|
||||||
const fmIndex = useContext(FmIndexContext);
|
const fmIndex = useContext(FmIndexContext);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
const {
|
const { classes, className, label, nodeId, icon: iconProp, expansionIcon, displayIcon, file, fileIcon } = props;
|
||||||
classes,
|
|
||||||
className,
|
|
||||||
label,
|
|
||||||
nodeId,
|
|
||||||
icon: iconProp,
|
|
||||||
expansionIcon,
|
|
||||||
displayIcon,
|
|
||||||
file,
|
|
||||||
fileIcon,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const {
|
const { disabled, expanded, selected, focused, handleExpansion, handleSelection, preventSelection } =
|
||||||
disabled,
|
useTreeItem(nodeId);
|
||||||
expanded,
|
|
||||||
selected,
|
|
||||||
focused,
|
|
||||||
handleExpansion,
|
|
||||||
handleSelection,
|
|
||||||
preventSelection,
|
|
||||||
} = useTreeItem(nodeId);
|
|
||||||
|
|
||||||
const uri = useMemo(() => {
|
const uri = useMemo(() => {
|
||||||
// Trim 'pinedPrefix' if exist in prefix
|
// Trim 'pinedPrefix' if exist in prefix
|
||||||
|
|
@ -169,9 +134,7 @@ const CustomContent = React.memo(
|
||||||
|
|
||||||
const icon = iconProp || expansionIcon || displayIcon;
|
const icon = iconProp || expansionIcon || displayIcon;
|
||||||
|
|
||||||
const handleMouseDown = (
|
const handleMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
||||||
) => {
|
|
||||||
preventSelection(event);
|
preventSelection(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -182,13 +145,7 @@ const CustomContent = React.memo(
|
||||||
handleExpansion(event);
|
handleExpansion(event);
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
try {
|
try {
|
||||||
await dispatch(
|
await dispatch(loadChild(fmIndex, uri, () => (timeOutID = setTimeout(() => setLoading(true), 300))));
|
||||||
loadChild(
|
|
||||||
fmIndex,
|
|
||||||
uri,
|
|
||||||
() => (timeOutID = setTimeout(() => setLoading(true), 300)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (timeOutID) {
|
if (timeOutID) {
|
||||||
clearTimeout(timeOutID);
|
clearTimeout(timeOutID);
|
||||||
|
|
@ -211,14 +168,7 @@ const CustomContent = React.memo(
|
||||||
|
|
||||||
const FileItemIcon = useMemo(() => {
|
const FileItemIcon = useMemo(() => {
|
||||||
if (props.loading) {
|
if (props.loading) {
|
||||||
return (
|
return <Skeleton sx={{ mr: "14px" }} variant={"rounded"} width={20} height={20} />;
|
||||||
<Skeleton
|
|
||||||
sx={{ mr: "14px" }}
|
|
||||||
variant={"rounded"}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (fileIcon && fileIcon.Icons) {
|
if (fileIcon && fileIcon.Icons) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -320,9 +270,7 @@ const CustomContent = React.memo(
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||||
<div onClick={handleExpansionClick} className={classes.iconContainer}>
|
<div onClick={handleExpansionClick} className={classes.iconContainer}>
|
||||||
{icon && !loading && <CaretDownIcon expanded={expanded} />}
|
{icon && !loading && <CaretDownIcon expanded={expanded} />}
|
||||||
{icon && loading && (
|
{icon && loading && <FacebookCircularProgress size={15} sx={{ pt: 0.5 }} />}
|
||||||
<FacebookCircularProgress size={15} sx={{ pt: 0.5 }} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{FileItemIcon}
|
{FileItemIcon}
|
||||||
{fileName}
|
{fileName}
|
||||||
|
|
@ -333,23 +281,11 @@ const CustomContent = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const TreeFile = React.memo(
|
const TreeFile = React.memo(
|
||||||
React.forwardRef(function CustomTreeItem(
|
React.forwardRef(function CustomTreeItem(props: TreeFileProps, ref: React.Ref<HTMLLIElement>) {
|
||||||
props: TreeFileProps,
|
|
||||||
ref: React.Ref<HTMLLIElement>,
|
|
||||||
) {
|
|
||||||
const contentProps = useMemo(() => {
|
const contentProps = useMemo(() => {
|
||||||
const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } =
|
const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } = props;
|
||||||
props;
|
|
||||||
return { level, file, notLoaded, fileIcon, loading, pinned, canDrop };
|
return { level, file, notLoaded, fileIcon, loading, pinned, canDrop };
|
||||||
}, [
|
}, [props.level, props.file, props.notLoaded, props.fileIcon, props.loading, props.canDrop, props.pinned]);
|
||||||
props.level,
|
|
||||||
props.file,
|
|
||||||
props.notLoaded,
|
|
||||||
props.fileIcon,
|
|
||||||
props.loading,
|
|
||||||
props.canDrop,
|
|
||||||
props.pinned,
|
|
||||||
]);
|
|
||||||
return (
|
return (
|
||||||
<StyledTreeItemRoot
|
<StyledTreeItemRoot
|
||||||
ContentComponent={CustomContent}
|
ContentComponent={CustomContent}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,7 @@ const Loading = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const HeadlessFrame = () => {
|
const HeadlessFrame = () => {
|
||||||
const loading = useAppSelector(
|
const loading = useAppSelector((state) => state.globalState.loading.headlessFrame);
|
||||||
(state) => state.globalState.loading.headlessFrame,
|
|
||||||
);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
let navigation = useNavigation();
|
let navigation = useNavigation();
|
||||||
|
|
||||||
|
|
@ -31,9 +29,7 @@ const HeadlessFrame = () => {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: (theme) =>
|
backgroundColor: (theme) =>
|
||||||
theme.palette.mode === "light"
|
theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900],
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900],
|
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
height: "100vh",
|
height: "100vh",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
|
|
@ -51,8 +47,7 @@ const HeadlessFrame = () => {
|
||||||
<Box sx={{ width: "100%", py: 2 }}>
|
<Box sx={{ width: "100%", py: 2 }}>
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
padding: (theme) =>
|
padding: (theme) => `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
|
||||||
`${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo
|
<Logo
|
||||||
|
|
@ -66,10 +61,7 @@ const HeadlessFrame = () => {
|
||||||
<div>
|
<div>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display:
|
display: loading || navigation.state !== "idle" ? "none" : "block",
|
||||||
loading || navigation.state !== "idle"
|
|
||||||
? "none"
|
|
||||||
: "block",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { Box, Drawer, Popover, PopoverProps, Stack, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Drawer,
|
|
||||||
Popover,
|
|
||||||
PopoverProps,
|
|
||||||
Stack,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import DrawerHeader from "./DrawerHeader.tsx";
|
import DrawerHeader from "./DrawerHeader.tsx";
|
||||||
import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx";
|
import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx";
|
||||||
|
|
@ -68,10 +60,7 @@ const AppDrawer = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const open = useAppSelector((state) => state.globalState.drawerOpen);
|
const open = useAppSelector((state) => state.globalState.drawerOpen);
|
||||||
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
|
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
|
||||||
const appBarBg =
|
const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
|
||||||
theme.palette.mode === "light"
|
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { IconButton, Popover, ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material";
|
||||||
IconButton,
|
|
||||||
Popover,
|
|
||||||
ToggleButton,
|
|
||||||
ToggleButtonGroup,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mui/material";
|
|
||||||
import DarkTheme from "../../Icons/DarkTheme.tsx";
|
import DarkTheme from "../../Icons/DarkTheme.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
@ -21,11 +15,7 @@ interface SwitchPopoverProps {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SwitchPopover = ({
|
export const SwitchPopover = ({ open, anchorEl, onClose }: SwitchPopoverProps) => {
|
||||||
open,
|
|
||||||
anchorEl,
|
|
||||||
onClose,
|
|
||||||
}: SwitchPopoverProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
@ -36,10 +26,7 @@ export const SwitchPopover = ({
|
||||||
}
|
}
|
||||||
return darkMode ? "dark" : "light";
|
return darkMode ? "dark" : "light";
|
||||||
}, [darkMode]);
|
}, [darkMode]);
|
||||||
const handleChange = (
|
const handleChange = (_event: React.MouseEvent<HTMLElement>, newMode: string) => {
|
||||||
_event: React.MouseEvent<HTMLElement>,
|
|
||||||
newMode: string,
|
|
||||||
) => {
|
|
||||||
let newSetting: boolean | undefined;
|
let newSetting: boolean | undefined;
|
||||||
if (newMode === "light") {
|
if (newMode === "light") {
|
||||||
newSetting = false;
|
newSetting = false;
|
||||||
|
|
@ -107,11 +94,7 @@ const DarkThemeSwitcher = () => {
|
||||||
<DarkTheme />
|
<DarkTheme />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<SwitchPopover
|
<SwitchPopover open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={() => setAnchorEl(null)} />
|
||||||
open={Boolean(anchorEl)}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
onClose={() => setAnchorEl(null)}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { Box, Fade, IconButton, styled, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Fade,
|
|
||||||
IconButton,
|
|
||||||
styled,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { setDrawerOpen } from "../../../redux/globalStateSlice.ts";
|
import { setDrawerOpen } from "../../../redux/globalStateSlice.ts";
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import { ChevronLeft } from "@mui/icons-material";
|
import { ChevronLeft } from "@mui/icons-material";
|
||||||
|
|
@ -29,10 +22,7 @@ const DrawerHeader = () => {
|
||||||
const [showCollapse, setShowCollapse] = useState(false);
|
const [showCollapse, setShowCollapse] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerHeaderContainer
|
<DrawerHeaderContainer onMouseEnter={() => setShowCollapse(true)} onMouseLeave={() => setShowCollapse(false)}>
|
||||||
onMouseEnter={() => setShowCollapse(true)}
|
|
||||||
onMouseLeave={() => setShowCollapse(false)}
|
|
||||||
>
|
|
||||||
<Box sx={{ width: "100%", pl: 2 }}>
|
<Box sx={{ width: "100%", pl: 2 }}>
|
||||||
<Logo
|
<Logo
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ import FileSelectedActions from "./FileSelectedActions.tsx";
|
||||||
import { FileManagerIndex } from "../../FileManager/FileManager.tsx";
|
import { FileManagerIndex } from "../../FileManager/FileManager.tsx";
|
||||||
|
|
||||||
const NavBarMainActions = () => {
|
const NavBarMainActions = () => {
|
||||||
const selected = useAppSelector(
|
const selected = useAppSelector((state) => state.fileManager[FileManagerIndex.main].selected);
|
||||||
(state) => state.fileManager[FileManagerIndex.main].selected,
|
|
||||||
);
|
|
||||||
const targets = useMemo(() => {
|
const targets = useMemo(() => {
|
||||||
return Object.keys(selected).map((key) => selected[key]);
|
return Object.keys(selected).map((key) => selected[key]);
|
||||||
}, [selected]);
|
}, [selected]);
|
||||||
|
|
@ -17,9 +15,7 @@ const NavBarMainActions = () => {
|
||||||
<>
|
<>
|
||||||
<SwitchTransition>
|
<SwitchTransition>
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
addEndListener={(node, done) =>
|
addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
|
||||||
node.addEventListener("transitionend", done, false)
|
|
||||||
}
|
|
||||||
classNames="fade"
|
classNames="fade"
|
||||||
key={`${targets.length > 0}`}
|
key={`${targets.length > 0}`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,7 @@ export interface NavIconTransitionProps {
|
||||||
iconProps?: SvgIconProps;
|
iconProps?: SvgIconProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavIconTransition = ({
|
const NavIconTransition = ({ fileIcon, active, iconProps, ...rest }: NavIconTransitionProps) => {
|
||||||
fileIcon,
|
|
||||||
active,
|
|
||||||
iconProps,
|
|
||||||
...rest
|
|
||||||
}: NavIconTransitionProps) => {
|
|
||||||
const [Active, InActive] = fileIcon;
|
const [Active, InActive] = fileIcon;
|
||||||
return (
|
return (
|
||||||
<Box {...rest}>
|
<Box {...rest}>
|
||||||
|
|
@ -30,11 +25,7 @@ const NavIconTransition = ({
|
||||||
{!active && (
|
{!active && (
|
||||||
<Fade key={"inactive"}>
|
<Fade key={"inactive"}>
|
||||||
<span>
|
<span>
|
||||||
<InActive
|
<InActive sx={{ position: "absolute" }} key={"inactive"} {...iconProps} />
|
||||||
sx={{ position: "absolute" }}
|
|
||||||
key={"inactive"}
|
|
||||||
{...iconProps}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</Fade>
|
</Fade>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { alpha, Button, IconButton, styled, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
alpha,
|
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
styled,
|
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { setSearchPopup } from "../../../redux/globalStateSlice.ts";
|
import { setSearchPopup } from "../../../redux/globalStateSlice.ts";
|
||||||
|
|
@ -14,10 +6,7 @@ import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import Search from "../../Icons/Search.tsx";
|
import Search from "../../Icons/Search.tsx";
|
||||||
|
|
||||||
export const KeyIndicator = styled("code")(({ theme }) => ({
|
export const KeyIndicator = styled("code")(({ theme }) => ({
|
||||||
backgroundColor:
|
backgroundColor: theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900],
|
||||||
theme.palette.mode === "light"
|
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900],
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
boxShadow:
|
boxShadow:
|
||||||
theme.palette.mode === "light"
|
theme.palette.mode === "light"
|
||||||
|
|
@ -36,7 +25,7 @@ const SearchButton = styled(Button)(({ theme }) => ({
|
||||||
" :hover": {
|
" :hover": {
|
||||||
border: `1px solid ${theme.palette.primary.main}`,
|
border: `1px solid ${theme.palette.primary.main}`,
|
||||||
backgroundColor: alpha(theme.palette.primary.main, 0.04),
|
backgroundColor: alpha(theme.palette.primary.main, 0.04),
|
||||||
}
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@ const SplitHandle = (_props: SplitHandleProps) => {
|
||||||
function onMouseMove(e: MouseEvent) {
|
function onMouseMove(e: MouseEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newWidth = e.clientX - document.body.offsetLeft;
|
const newWidth = e.clientX - document.body.offsetLeft;
|
||||||
const cappedWidth = Math.max(
|
const cappedWidth = Math.max(Math.min(newWidth, window.innerWidth / 2), minDrawerWidth);
|
||||||
Math.min(newWidth, window.innerWidth / 2),
|
|
||||||
minDrawerWidth,
|
|
||||||
);
|
|
||||||
setCursor(cappedWidth);
|
setCursor(cappedWidth);
|
||||||
finalWidth.current = cappedWidth;
|
finalWidth.current = cappedWidth;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,8 @@
|
||||||
import { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
import { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
||||||
import {
|
import { AppBar, Box, Collapse, IconButton, Stack, Toolbar, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||||
AppBar,
|
|
||||||
Box,
|
|
||||||
Collapse,
|
|
||||||
IconButton,
|
|
||||||
Stack,
|
|
||||||
Toolbar,
|
|
||||||
Tooltip,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { Menu } from "@mui/icons-material";
|
import { Menu } from "@mui/icons-material";
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||||
import {
|
import { setDrawerOpen, setMobileDrawerOpen } from "../../../redux/globalStateSlice.ts";
|
||||||
setDrawerOpen,
|
|
||||||
setMobileDrawerOpen,
|
|
||||||
} from "../../../redux/globalStateSlice.ts";
|
|
||||||
import NewButton from "../../FileManager/NewButton.tsx";
|
import NewButton from "../../FileManager/NewButton.tsx";
|
||||||
import UserAction from "./UserAction.tsx";
|
import UserAction from "./UserAction.tsx";
|
||||||
import Setting from "../../Icons/Setting.tsx";
|
import Setting from "../../Icons/Setting.tsx";
|
||||||
|
|
@ -43,19 +30,12 @@ const TopAppBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const open = useAppSelector((state) => state.globalState.drawerOpen);
|
const open = useAppSelector((state) => state.globalState.drawerOpen);
|
||||||
const mobileDrawerOpen = useAppSelector(
|
const mobileDrawerOpen = useAppSelector((state) => state.globalState.mobileDrawerOpen);
|
||||||
(state) => state.globalState.mobileDrawerOpen,
|
|
||||||
);
|
|
||||||
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
|
const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth);
|
||||||
const musicPlayer = useAppSelector((state) => state.globalState.musicPlayer);
|
const musicPlayer = useAppSelector((state) => state.globalState.musicPlayer);
|
||||||
const [mobileMenuAnchor, setMobileMenuAnchor] = useState<null | HTMLElement>(
|
const [mobileMenuAnchor, setMobileMenuAnchor] = useState<null | HTMLElement>(null);
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const appBarBg =
|
const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900];
|
||||||
theme.palette.mode === "light"
|
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900];
|
|
||||||
const isLogin = !!SessionManager.currentLoginOrNull();
|
const isLogin = !!SessionManager.currentLoginOrNull();
|
||||||
|
|
||||||
const onMobileMenuClicked = (e: React.MouseEvent<HTMLElement>) => {
|
const onMobileMenuClicked = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
|
@ -94,10 +74,7 @@ const TopAppBar = () => {
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
"&.MuiToolbar-root.MuiToolbar-gutters": {
|
"&.MuiToolbar-root.MuiToolbar-gutters": {
|
||||||
paddingLeft:
|
paddingLeft: open && !isMobile ? theme.spacing(0) : theme.spacing(isMobile ? 2 : 3),
|
||||||
open && !isMobile
|
|
||||||
? theme.spacing(0)
|
|
||||||
: theme.spacing(isMobile ? 2 : 3),
|
|
||||||
transition: theme.transitions.create("padding", {
|
transition: theme.transitions.create("padding", {
|
||||||
easing: theme.transitions.easing.easeInOut,
|
easing: theme.transitions.easing.easeInOut,
|
||||||
duration: theme.transitions.duration.standard,
|
duration: theme.transitions.duration.standard,
|
||||||
|
|
@ -110,11 +87,7 @@ const TopAppBar = () => {
|
||||||
color="inherit"
|
color="inherit"
|
||||||
aria-label="open drawer"
|
aria-label="open drawer"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
onClick={
|
onClick={isMobile ? onMobileMenuClicked : () => dispatch(setDrawerOpen(true))}
|
||||||
isMobile
|
|
||||||
? onMobileMenuClicked
|
|
||||||
: () => dispatch(setDrawerOpen(true))
|
|
||||||
}
|
|
||||||
edge="start"
|
edge="start"
|
||||||
sx={{
|
sx={{
|
||||||
mr: isMobile ? 1 : 2,
|
mr: isMobile ? 1 : 2,
|
||||||
|
|
@ -126,11 +99,7 @@ const TopAppBar = () => {
|
||||||
</Collapse>
|
</Collapse>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<>
|
<>
|
||||||
<DrawerPopover
|
<DrawerPopover open={!!mobileDrawerOpen} anchorEl={mobileMenuAnchor} onClose={onCloseMobileMenu} />
|
||||||
open={!!mobileDrawerOpen}
|
|
||||||
anchorEl={mobileMenuAnchor}
|
|
||||||
onClose={onCloseMobileMenu}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isMobile && isMainPage && (
|
{!isMobile && isMainPage && (
|
||||||
|
|
@ -154,10 +123,7 @@ const TopAppBar = () => {
|
||||||
<DarkThemeSwitcher />
|
<DarkThemeSwitcher />
|
||||||
{isLogin && (
|
{isLogin && (
|
||||||
<Tooltip title={t("navbar.setting")}>
|
<Tooltip title={t("navbar.setting")}>
|
||||||
<IconButton
|
<IconButton size="large" onClick={() => navigate("/settings")}>
|
||||||
size="large"
|
|
||||||
onClick={() => navigate("/settings")}
|
|
||||||
>
|
|
||||||
<Setting />
|
<Setting />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,10 @@ export default function Border(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props}>
|
<SvgIcon {...props}>
|
||||||
<g fill="none">
|
<g fill="none">
|
||||||
<path
|
<path d="M14 3.75a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h2.5a.75.75 0 0 1 .75.75z" fill="currentColor" />
|
||||||
d="M14 3.75a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h2.5a.75.75 0 0 1 .75.75z"
|
<path d="M4.5 10.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 0 1.5 0v-2.5z" fill="currentColor" />
|
||||||
fill="currentColor"
|
<path d="M19.5 10.75a.75.75 0 0 1 1.5 0v2.5a.75.75 0 0 1-1.5 0v-2.5z" fill="currentColor" />
|
||||||
/>
|
<path d="M13.25 21a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 0 0 1.5h2.5z" fill="currentColor" />
|
||||||
<path
|
|
||||||
d="M4.5 10.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 0 1.5 0v-2.5z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M19.5 10.75a.75.75 0 0 1 1.5 0v2.5a.75.75 0 0 1-1.5 0v-2.5z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M13.25 21a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 0 0 1.5h2.5z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
<path
|
||||||
d="M7 3.75A.75.75 0 0 0 6.25 3h-.5A2.75 2.75 0 0 0 3 5.75v.5a.75.75 0 0 0 1.5 0v-.5c0-.69.56-1.25 1.25-1.25h.5A.75.75 0 0 0 7 3.75z"
|
d="M7 3.75A.75.75 0 0 0 6.25 3h-.5A2.75 2.75 0 0 0 3 5.75v.5a.75.75 0 0 0 1.5 0v-.5c0-.69.56-1.25 1.25-1.25h.5A.75.75 0 0 0 7 3.75z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { DialogContent, DialogProps, FilledInput, IconButton, InputAdornment, InputLabel, Stack } from "@mui/material";
|
||||||
DialogContent,
|
|
||||||
DialogProps,
|
|
||||||
FilledInput,
|
|
||||||
IconButton,
|
|
||||||
InputAdornment,
|
|
||||||
InputLabel,
|
|
||||||
Stack,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||||
import { DavAccount } from "../../../api/setting.ts";
|
import { DavAccount } from "../../../api/setting.ts";
|
||||||
|
|
@ -47,11 +39,7 @@ const InfoTextField = ({ label, value }: { label: string; value: string }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConnectionInfoDialog = ({
|
const ConnectionInfoDialog = ({ onClose, account, ...rest }: ConnectionInfoDialogProps) => {
|
||||||
onClose,
|
|
||||||
account,
|
|
||||||
...rest
|
|
||||||
}: ConnectionInfoDialogProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
@ -70,18 +58,12 @@ const ConnectionInfoDialog = ({
|
||||||
>
|
>
|
||||||
<DialogContent sx={{ pt: 1 }}>
|
<DialogContent sx={{ pt: 1 }}>
|
||||||
<Stack spacing={2} direction={"column"}>
|
<Stack spacing={2} direction={"column"}>
|
||||||
<InfoTextField
|
<InfoTextField label={t("application:setting.webdavServer")} value={window.location.origin + "/dav"} />
|
||||||
label={t("application:setting.webdavServer")}
|
|
||||||
value={window.location.origin + "/dav"}
|
|
||||||
/>
|
|
||||||
<InfoTextField
|
<InfoTextField
|
||||||
label={t("application:setting.userName")}
|
label={t("application:setting.userName")}
|
||||||
value={SessionManager.currentLoginOrNull()?.user.email ?? ""}
|
value={SessionManager.currentLoginOrNull()?.user.email ?? ""}
|
||||||
/>
|
/>
|
||||||
<InfoTextField
|
<InfoTextField label={t("application:login.password")} value={account?.password ?? ""} />
|
||||||
label={t("application:login.password")}
|
|
||||||
value={account?.password ?? ""}
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</DraggableDialog>
|
</DraggableDialog>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { Checkbox, DialogContent, DialogProps, FormGroup, Stack, Typography, useTheme } from "@mui/material";
|
||||||
Checkbox,
|
|
||||||
DialogContent,
|
|
||||||
DialogProps,
|
|
||||||
FormGroup,
|
|
||||||
Stack,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
|
|
@ -22,10 +14,7 @@ import Boolset from "../../../util/boolset.ts";
|
||||||
import { GroupPermission } from "../../../api/user.ts";
|
import { GroupPermission } from "../../../api/user.ts";
|
||||||
import SessionManager from "../../../session";
|
import SessionManager from "../../../session";
|
||||||
import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx";
|
import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx";
|
||||||
import {
|
import { sendCreateDavAccounts, sendUpdateDavAccounts } from "../../../api/api.ts";
|
||||||
sendCreateDavAccounts,
|
|
||||||
sendUpdateDavAccounts,
|
|
||||||
} from "../../../api/api.ts";
|
|
||||||
import { DavAccount, DavAccountOption } from "../../../api/setting.ts";
|
import { DavAccount, DavAccountOption } from "../../../api/setting.ts";
|
||||||
|
|
||||||
export interface CreateDAVAccountDialogProps extends DialogProps {
|
export interface CreateDAVAccountDialogProps extends DialogProps {
|
||||||
|
|
@ -55,9 +44,7 @@ const CreateDAVAccountDialog = ({
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const groupProxyEnabled = useMemo(() => {
|
const groupProxyEnabled = useMemo(() => {
|
||||||
const perm = new Boolset(
|
const perm = new Boolset(SessionManager.currentLoginOrNull()?.user.group?.permission);
|
||||||
SessionManager.currentLoginOrNull()?.user.group?.permission,
|
|
||||||
);
|
|
||||||
return perm.enabled(GroupPermission.webdav_proxy);
|
return perm.enabled(GroupPermission.webdav_proxy);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -83,11 +70,7 @@ const CreateDAVAccountDialog = ({
|
||||||
proxy,
|
proxy,
|
||||||
readonly,
|
readonly,
|
||||||
};
|
};
|
||||||
dispatch(
|
dispatch(editTarget ? sendUpdateDavAccounts(editTarget.id, req) : sendCreateDavAccounts(req))
|
||||||
editTarget
|
|
||||||
? sendUpdateDavAccounts(editTarget.id, req)
|
|
||||||
: sendCreateDavAccounts(req),
|
|
||||||
)
|
|
||||||
.then((account) => {
|
.then((account) => {
|
||||||
onClose && onClose({}, "escapeKeyDown");
|
onClose && onClose({}, "escapeKeyDown");
|
||||||
!editTarget && onAccountAdded && onAccountAdded(account);
|
!editTarget && onAccountAdded && onAccountAdded(account);
|
||||||
|
|
@ -146,39 +129,19 @@ const CreateDAVAccountDialog = ({
|
||||||
>
|
>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<SmallFormControlLabel
|
<SmallFormControlLabel
|
||||||
control={
|
control={<Checkbox size="small" onChange={(e) => setReadonly(e.target.checked)} checked={readonly} />}
|
||||||
<Checkbox
|
|
||||||
size="small"
|
|
||||||
onChange={(e) => setReadonly(e.target.checked)}
|
|
||||||
checked={readonly}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={t("application:setting.readonlyOn")}
|
label={t("application:setting.readonlyOn")}
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography sx={{ pl: "27px" }} variant={"caption"} color={"text.secondary"}>
|
||||||
sx={{ pl: "27px" }}
|
|
||||||
variant={"caption"}
|
|
||||||
color={"text.secondary"}
|
|
||||||
>
|
|
||||||
{t("application:setting.readonlyTooltip")}
|
{t("application:setting.readonlyTooltip")}
|
||||||
</Typography>
|
</Typography>
|
||||||
{groupProxyEnabled && (
|
{groupProxyEnabled && (
|
||||||
<>
|
<>
|
||||||
<SmallFormControlLabel
|
<SmallFormControlLabel
|
||||||
control={
|
control={<Checkbox size="small" onChange={(e) => setProxy(e.target.checked)} checked={proxy} />}
|
||||||
<Checkbox
|
|
||||||
size="small"
|
|
||||||
onChange={(e) => setProxy(e.target.checked)}
|
|
||||||
checked={proxy}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={t("application:setting.proxy")}
|
label={t("application:setting.proxy")}
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography sx={{ pl: "27px" }} variant={"caption"} color={"text.secondary"}>
|
||||||
sx={{ pl: "27px" }}
|
|
||||||
variant={"caption"}
|
|
||||||
color={"text.secondary"}
|
|
||||||
>
|
|
||||||
{t("application:setting.proxyTooltip")}
|
{t("application:setting.proxyTooltip")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import {
|
import { Box, Table, TableBody, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
|
||||||
Box,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableContainer,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { getDavAccounts } from "../../../api/api.ts";
|
import { getDavAccounts } from "../../../api/api.ts";
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
|
|
@ -26,19 +18,14 @@ export interface DavAccountListProps {
|
||||||
setCreateAccountDialog: (value: boolean) => void;
|
setCreateAccountDialog: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DavAccountList = ({
|
const DavAccountList = ({ creatAccountDialog, setCreateAccountDialog }: DavAccountListProps) => {
|
||||||
creatAccountDialog,
|
|
||||||
setCreateAccountDialog,
|
|
||||||
}: DavAccountListProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [nextPageToken, setNextPageToken] = useState<string | undefined>("");
|
const [nextPageToken, setNextPageToken] = useState<string | undefined>("");
|
||||||
const [accounts, setAccounts] = useState<DavAccount[]>([]);
|
const [accounts, setAccounts] = useState<DavAccount[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [accountInfoTarget, setAccountInfoTarget] = useState<
|
const [accountInfoTarget, setAccountInfoTarget] = useState<DavAccount | undefined>();
|
||||||
DavAccount | undefined
|
|
||||||
>();
|
|
||||||
const [editTarget, setEditTarget] = useState<DavAccount | undefined>();
|
const [editTarget, setEditTarget] = useState<DavAccount | undefined>();
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -75,9 +62,7 @@ const DavAccountList = ({
|
||||||
|
|
||||||
const onAccountDeleted = useCallback(
|
const onAccountDeleted = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
setAccounts((accounts) =>
|
setAccounts((accounts) => accounts.filter((account) => account.id !== id));
|
||||||
accounts.filter((account) => account.id !== id),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[setAccounts],
|
[setAccounts],
|
||||||
);
|
);
|
||||||
|
|
@ -103,9 +88,7 @@ const DavAccountList = ({
|
||||||
open={editOpen}
|
open={editOpen}
|
||||||
editTarget={editTarget}
|
editTarget={editTarget}
|
||||||
onClose={() => setEditOpen(false)}
|
onClose={() => setEditOpen(false)}
|
||||||
onAccountUpdated={(account) =>
|
onAccountUpdated={(account) => setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))}
|
||||||
setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<ConnectionInfoDialog
|
<ConnectionInfoDialog
|
||||||
open={accountInfoTarget != undefined}
|
open={accountInfoTarget != undefined}
|
||||||
|
|
@ -116,26 +99,14 @@ const DavAccountList = ({
|
||||||
<Table sx={{ width: "100%", tableLayout: "fixed" }} size="small">
|
<Table sx={{ width: "100%", tableLayout: "fixed" }} size="small">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<NoWrapTableCell width={200}>{t("setting.annotation")}</NoWrapTableCell>
|
||||||
<NoWrapTableCell width={200}>
|
<NoWrapTableCell width={200}>
|
||||||
{t("setting.annotation")}
|
<CellHeaderWithPadding>{t("setting.rootFolder")}</CellHeaderWithPadding>
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={200}>
|
|
||||||
<CellHeaderWithPadding>
|
|
||||||
{t("setting.rootFolder")}
|
|
||||||
</CellHeaderWithPadding>
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={150}>
|
|
||||||
{t("fileManager.permissions")}
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={150}>
|
|
||||||
{t("setting.proxy")}
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={200}>
|
|
||||||
{t("fileManager.createdAt")}
|
|
||||||
</NoWrapTableCell>
|
|
||||||
<NoWrapTableCell width={100}>
|
|
||||||
{t("fileManager.actions")}
|
|
||||||
</NoWrapTableCell>
|
</NoWrapTableCell>
|
||||||
|
<NoWrapTableCell width={150}>{t("fileManager.permissions")}</NoWrapTableCell>
|
||||||
|
<NoWrapTableCell width={150}>{t("setting.proxy")}</NoWrapTableCell>
|
||||||
|
<NoWrapTableCell width={200}>{t("fileManager.createdAt")}</NoWrapTableCell>
|
||||||
|
<NoWrapTableCell width={100}>{t("fileManager.actions")}</NoWrapTableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
@ -152,9 +123,7 @@ const DavAccountList = ({
|
||||||
<>
|
<>
|
||||||
{[...Array(4)].map((_, i) => (
|
{[...Array(4)].map((_, i) => (
|
||||||
<DavAccountRow
|
<DavAccountRow
|
||||||
onLoad={
|
onLoad={i == 0 ? loadNextPage(accounts, nextPageToken) : undefined}
|
||||||
i == 0 ? loadNextPage(accounts, nextPageToken) : undefined
|
|
||||||
}
|
|
||||||
loading={true}
|
loading={true}
|
||||||
key={i}
|
key={i}
|
||||||
onAccountDeleted={onAccountDeleted}
|
onAccountDeleted={onAccountDeleted}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import {
|
import { IconButton, ListItemText, Menu, Skeleton, TableRow } from "@mui/material";
|
||||||
IconButton,
|
|
||||||
ListItemText,
|
|
||||||
Menu,
|
|
||||||
Skeleton,
|
|
||||||
TableRow,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||||
import { NoWrapCell, SquareChip } from "../../Common/StyledComponents.tsx";
|
import { NoWrapCell, SquareChip } from "../../Common/StyledComponents.tsx";
|
||||||
|
|
@ -20,11 +14,7 @@ import { useInView } from "react-intersection-observer";
|
||||||
import Eye from "../../Icons/Eye.tsx";
|
import Eye from "../../Icons/Eye.tsx";
|
||||||
import { Edit } from "@mui/icons-material";
|
import { Edit } from "@mui/icons-material";
|
||||||
import CloudFilled from "../../Icons/CloudFilled.tsx";
|
import CloudFilled from "../../Icons/CloudFilled.tsx";
|
||||||
import {
|
import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
|
||||||
bindMenu,
|
|
||||||
bindTrigger,
|
|
||||||
usePopupState,
|
|
||||||
} from "material-ui-popup-state/hooks";
|
|
||||||
import MoreVertical from "../../Icons/MoreVertical.tsx";
|
import MoreVertical from "../../Icons/MoreVertical.tsx";
|
||||||
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
|
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
|
||||||
import { sendDeleteDavAccount } from "../../../api/api.ts";
|
import { sendDeleteDavAccount } from "../../../api/api.ts";
|
||||||
|
|
@ -38,14 +28,7 @@ export interface DavAccountRowProps {
|
||||||
onEditClicked?: (account: DavAccount) => void;
|
onEditClicked?: (account: DavAccount) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DavAccountRow = ({
|
const DavAccountRow = ({ account, onAccountDeleted, onLoad, loading, onClick, onEditClicked }: DavAccountRowProps) => {
|
||||||
account,
|
|
||||||
onAccountDeleted,
|
|
||||||
onLoad,
|
|
||||||
loading,
|
|
||||||
onClick,
|
|
||||||
onEditClicked,
|
|
||||||
}: DavAccountRowProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
@ -73,10 +56,7 @@ const DavAccountRow = ({
|
||||||
const [readOnly, proxy] = useMemo(() => {
|
const [readOnly, proxy] = useMemo(() => {
|
||||||
if (!account?.options) return [false, false] as const;
|
if (!account?.options) return [false, false] as const;
|
||||||
const bs = new Boolset(account.options);
|
const bs = new Boolset(account.options);
|
||||||
return [
|
return [bs.enabled(DavAccountOption.readonly), bs.enabled(DavAccountOption.proxy)] as const;
|
||||||
bs.enabled(DavAccountOption.readonly),
|
|
||||||
bs.enabled(DavAccountOption.proxy),
|
|
||||||
] as const;
|
|
||||||
}, [account?.options]);
|
}, [account?.options]);
|
||||||
|
|
||||||
const { onClose, ...rest } = bindMenu(actionMenuState);
|
const { onClose, ...rest } = bindMenu(actionMenuState);
|
||||||
|
|
@ -101,11 +81,7 @@ const DavAccountRow = ({
|
||||||
return (
|
return (
|
||||||
<TableRow ref={ref} hover sx={{ cursor: "pointer" }} onClick={onClick}>
|
<TableRow ref={ref} hover sx={{ cursor: "pointer" }} onClick={onClick}>
|
||||||
<NoWrapCell component="th" scope="row">
|
<NoWrapCell component="th" scope="row">
|
||||||
{loading ? (
|
{loading ? <Skeleton variant={"text"} width={200} sx={{ my: "6px" }} /> : account?.name}
|
||||||
<Skeleton variant={"text"} width={200} sx={{ my: "6px" }} />
|
|
||||||
) : (
|
|
||||||
account?.name
|
|
||||||
)}
|
|
||||||
</NoWrapCell>
|
</NoWrapCell>
|
||||||
<NoWrapCell>
|
<NoWrapCell>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|
@ -126,17 +102,9 @@ const DavAccountRow = ({
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton variant={"text"} width={100} />
|
<Skeleton variant={"text"} width={100} />
|
||||||
) : readOnly ? (
|
) : readOnly ? (
|
||||||
<SquareChip
|
<SquareChip icon={<Eye />} size={"small"} label={t("setting.readonlyOn")} />
|
||||||
icon={<Eye />}
|
|
||||||
size={"small"}
|
|
||||||
label={t("setting.readonlyOn")}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<SquareChip
|
<SquareChip icon={<Edit />} size={"small"} label={t("setting.readonlyOff")} />
|
||||||
icon={<Edit />}
|
|
||||||
size={"small"}
|
|
||||||
label={t("setting.readonlyOff")}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</NoWrapCell>
|
</NoWrapCell>
|
||||||
<NoWrapCell>
|
<NoWrapCell>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue