This commit is contained in:
2024-02-25 08:27:01 +08:00
commit 20c1fa08dc
279 changed files with 78489 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
import React, { useCallback } from "react";
import { IconButton, makeStyles } from "@material-ui/core";
import DayIcon from "@material-ui/icons/Brightness7";
import NightIcon from "@material-ui/icons/Brightness4";
import { useDispatch, useSelector } from "react-redux";
import Tooltip from "@material-ui/core/Tooltip";
import Auth from "../../middleware/Auth";
import classNames from "classnames";
import { toggleDaylightMode } from "../../redux/explorer";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles(() => ({
icon: {
color: "rgb(255, 255, 255)",
opacity: "0.54",
},
}));
const DarkModeSwitcher = ({ position }) => {
const { t } = useTranslation();
const ThemeType = useSelector(
(state) => state.siteConfig.theme.palette.type
);
const dispatch = useDispatch();
const ToggleThemeMode = useCallback(() => dispatch(toggleDaylightMode()), [
dispatch,
]);
const isDayLight = (ThemeType && ThemeType === "light") || !ThemeType;
const isDark = ThemeType && ThemeType === "dark";
const toggleMode = () => {
Auth.SetPreference("theme_mode", isDayLight ? "dark" : "light");
ToggleThemeMode();
};
const classes = useStyles();
return (
<Tooltip
title={
isDayLight ? t("navbar.toDarkMode") : t("navbar.toLightMode")
}
placement="bottom"
>
<IconButton
className={classNames({
[classes.icon]: "left" === position,
})}
onClick={toggleMode}
color="inherit"
>
{isDayLight && <NightIcon />}
{isDark && <DayIcon />}
</IconButton>
</Tooltip>
);
};
export default DarkModeSwitcher;

View File

@@ -0,0 +1,407 @@
import React, { Suspense, useCallback, useState } from "react";
import {
Divider,
List,
ListItemIcon,
ListItemText,
makeStyles,
withStyles,
} from "@material-ui/core";
import { Clear, KeyboardArrowRight } from "@material-ui/icons";
import classNames from "classnames";
import FolderShared from "@material-ui/icons/FolderShared";
import UploadIcon from "@material-ui/icons/CloudUpload";
import VideoIcon from "@material-ui/icons/VideoLibraryOutlined";
import ImageIcon from "@material-ui/icons/CollectionsOutlined";
import MusicIcon from "@material-ui/icons/LibraryMusicOutlined";
import DocIcon from "@material-ui/icons/FileCopyOutlined";
import { useHistory, useLocation } from "react-router";
import pathHelper from "../../utils/page";
import MuiExpansionPanel from "@material-ui/core/ExpansionPanel";
import MuiExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
import MuiExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
import MuiListItem from "@material-ui/core/ListItem";
import { useDispatch } from "react-redux";
import Auth from "../../middleware/Auth";
import {
Circle,
CircleOutline,
FolderHeartOutline,
Heart,
HeartOutline,
Hexagon,
HexagonOutline,
Hexagram,
HexagramOutline,
Rhombus,
RhombusOutline,
Square,
SquareOutline,
TagPlus,
Triangle,
TriangleOutline,
} from "mdi-material-ui";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import IconButton from "@material-ui/core/IconButton";
import API from "../../middleware/Api";
import { navigateTo, searchMyFile, toggleSnackbar } from "../../redux/explorer";
import { useTranslation } from "react-i18next";
const ListItem = withStyles((theme) => ({
root: {
borderRadius:theme.shape.borderRadius,
},
}))(MuiListItem);
const ExpansionPanel = withStyles({
root: {
maxWidth: "100%",
boxShadow: "none",
"&:not(:last-child)": {
borderBottom: 0,
},
"&:before": {
display: "none",
},
"&$expanded": { margin: 0 },
},
expanded: {},
})(MuiExpansionPanel);
const ExpansionPanelSummary = withStyles((theme) =>({
root: {
minHeight: 0,
padding: 0,
"&$expanded": {
minHeight: 0,
},
},
content: {
maxWidth: "100%",
margin: 0,
display: "block",
"&$expanded": {
margin: "0",
},
},
expanded: {},
}))(MuiExpansionPanelSummary);
const ExpansionPanelDetails = withStyles((theme) => ({
root: {
display: "block",
padding: theme.spacing(0),
},
}))(MuiExpansionPanelDetails);
const useStyles = makeStyles((theme) => ({
expand: {
display: "none",
transition: ".15s all ease-in-out",
},
expanded: {
display: "block",
transform: "rotate(90deg)",
},
iconFix: {
marginLeft: "16px",
},
hiddenButton: {
display: "none",
},
subMenu: {
marginLeft: theme.spacing(2),
},
overFlow: {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
},
paddingList:{
padding:theme.spacing(1),
},
paddingSummary:{
paddingLeft:theme.spacing(1),
paddingRight:theme.spacing(1),
}
}));
const icons = {
Circle: Circle,
CircleOutline: CircleOutline,
Heart: Heart,
HeartOutline: HeartOutline,
Hexagon: Hexagon,
HexagonOutline: HexagonOutline,
Hexagram: Hexagram,
HexagramOutline: HexagramOutline,
Rhombus: Rhombus,
RhombusOutline: RhombusOutline,
Square: Square,
SquareOutline: SquareOutline,
Triangle: Triangle,
TriangleOutline: TriangleOutline,
FolderHeartOutline: FolderHeartOutline,
};
const AddTag = React.lazy(() => import("../Modals/AddTag"));
export default function FileTag() {
const classes = useStyles();
const { t } = useTranslation();
const location = useLocation();
const history = useHistory();
const isHomePage = pathHelper.isHomePage(location.pathname);
const [tagOpen, setTagOpen] = useState(true);
const [addTagModal, setAddTagModal] = useState(false);
const [tagHover, setTagHover] = useState(null);
const [tags, setTags] = useState(
Auth.GetUser().tags ? Auth.GetUser().tags : []
);
const dispatch = useDispatch();
const SearchMyFile = useCallback((k, p) => dispatch(searchMyFile(k, p)), [
dispatch,
]);
const NavigateTo = useCallback((k) => dispatch(navigateTo(k)), [dispatch]);
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const getIcon = (icon, color) => {
if (icons[icon]) {
const IconComponent = icons[icon];
return (
<IconComponent
className={[classes.iconFix]}
style={
color
? {
color: color,
}
: {}
}
/>
);
}
return <Circle className={[classes.iconFix]} />;
};
const submitSuccess = (tag) => {
const newTags = [...tags, tag];
setTags(newTags);
const user = Auth.GetUser();
user.tags = newTags;
Auth.SetUser(user);
};
const submitDelete = (id) => {
API.delete("/tag/" + id)
.then(() => {
const newTags = tags.filter((v) => {
return v.id !== id;
});
setTags(newTags);
const user = Auth.GetUser();
user.tags = newTags;
Auth.SetUser(user);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
};
return (
<>
<Suspense fallback={""}>
<AddTag
onSuccess={submitSuccess}
open={addTagModal}
onClose={() => setAddTagModal(false)}
/>
</Suspense>
<ExpansionPanel
square
expanded={tagOpen && isHomePage}
onChange={() => isHomePage && setTagOpen(!tagOpen)}
>
<ExpansionPanelSummary
aria-controls="panel1d-content"
id="panel1d-header"
>
<div className={classes.paddingSummary}>
<ListItem
button
key="我的文件"
onClick={() =>
!isHomePage && history.push("/home?path=%2F")
}
>
<ListItemIcon>
<KeyboardArrowRight
className={classNames(
{
[classes.expanded]:
tagOpen && isHomePage,
[classes.iconFix]: true,
},
classes.expand
)}
/>
{!(tagOpen && isHomePage) && (
<FolderShared className={classes.iconFix} />
)}
</ListItemIcon>
<ListItemText primary={t("navbar.myFiles")} />
</ListItem>
</div>
<Divider />
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<List className={classes.paddingList} onMouseLeave={() => setTagHover(null)}>
<ListItem
button
id="pickfiles"
className={classes.hiddenButton}
>
<ListItemIcon>
<UploadIcon />
</ListItemIcon>
<ListItemText />
</ListItem>
<ListItem
button
id="pickfolder"
className={classes.hiddenButton}
>
<ListItemIcon>
<UploadIcon />
</ListItemIcon>
<ListItemText />
</ListItem>
{[
{
key: t("navbar.videos"),
id: "video",
icon: (
<VideoIcon
className={[
classes.iconFix,
classes.iconVideo,
]}
/>
),
},
{
key: t("navbar.photos"),
id: "image",
icon: (
<ImageIcon
className={[
classes.iconFix,
classes.iconImg,
]}
/>
),
},
{
key: t("navbar.music"),
id: "audio",
icon: (
<MusicIcon
className={[
classes.iconFix,
classes.iconAudio,
]}
/>
),
},
{
key: t("navbar.documents"),
id: "doc",
icon: (
<DocIcon
className={[
classes.iconFix,
classes.iconDoc,
]}
/>
),
},
].map((v) => (
<ListItem
button
key={v.key}
onClick={() =>
SearchMyFile(v.id + "/internal", "")
}
>
<ListItemIcon className={classes.subMenu}>
{v.icon}
</ListItemIcon>
<ListItemText primary={v.key} />
</ListItem>
))}
{tags.map((v) => (
<ListItem
button
key={v.id}
onMouseEnter={() => setTagHover(v.id)}
onClick={() => {
if (v.type === 0) {
SearchMyFile("tag/" + v.id, "");
} else {
NavigateTo(v.expression);
}
}}
>
<ListItemIcon className={classes.subMenu}>
{getIcon(
v.type === 0
? v.icon
: "FolderHeartOutline",
v.type === 0 ? v.color : null
)}
</ListItemIcon>
<ListItemText
className={classes.overFlow}
primary={v.name}
/>
{tagHover === v.id && (
<ListItemSecondaryAction
onClick={() => submitDelete(v.id)}
>
<IconButton
size={"small"}
edge="end"
aria-label="delete"
>
<Clear />
</IconButton>
</ListItemSecondaryAction>
)}
</ListItem>
))}
<ListItem button onClick={() => setAddTagModal(true)}>
<ListItemIcon className={classes.subMenu}>
<TagPlus className={classes.iconFix} />
</ListItemIcon>
<ListItemText primary={t("navbar.addATag")} />
</ListItem>
</List>{" "}
<Divider />
</ExpansionPanelDetails>
</ExpansionPanel>
</>
);
}

View File

@@ -0,0 +1,984 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { connect } from "react-redux";
import ShareIcon from "@material-ui/icons/Share";
import MusicNote from "@material-ui/icons/MusicNote";
import BackIcon from "@material-ui/icons/ArrowBack";
import SdStorage from "@material-ui/icons/SdStorage";
import OpenIcon from "@material-ui/icons/OpenInNew";
import DownloadIcon from "@material-ui/icons/CloudDownload";
import RenameIcon from "@material-ui/icons/BorderColor";
import MoveIcon from "@material-ui/icons/Input";
import DeleteIcon from "@material-ui/icons/Delete";
import MenuIcon from "@material-ui/icons/Menu";
import { isPreviewable } from "../../config";
import { changeThemeColor, sizeToString, vhCheck } from "../../utils";
import Uploader from "../Uploader/Uploader.js";
import pathHelper from "../../utils/page";
import SezrchBar from "./SearchBar";
import StorageBar from "./StorageBar";
import UserAvatar from "./UserAvatar";
import UserInfo from "./UserInfo";
import {
FolderDownload,
AccountArrowRight,
AccountPlus,
LogoutVariant,
} from "mdi-material-ui";
import { withRouter } from "react-router-dom";
import {
AppBar,
Drawer,
Grow,
Hidden,
IconButton,
List,
ListItemIcon,
ListItemText,
SwipeableDrawer,
Toolbar,
Tooltip,
Typography,
withStyles,
withTheme
} from "@material-ui/core";
import Auth from "../../middleware/Auth";
import API from "../../middleware/Api";
import FileTag from "./FileTags";
import { Assignment, Devices, MoreHoriz, Settings } from "@material-ui/icons";
import Divider from "@material-ui/core/Divider";
import SubActions from "../FileManager/Navigator/SubActions";
import {
audioPreviewSetIsOpen,
changeContextMenu,
drawerToggleAction,
navigateTo,
openCreateFolderDialog,
openLoadingDialog,
openMoveDialog,
openMusicDialog,
openPreview,
openRemoveDialog,
openRenameDialog,
openShareDialog,
saveFile,
setSelectedTarget,
setSessionStatus,
showImgPreivew,
toggleSnackbar,
} from "../../redux/explorer";
import {
startBatchDownload,
startDirectoryDownload,
startDownload,
} from "../../redux/explorer/action";
import PolicySwitcher from "./PolicySwitcher";
import { withTranslation } from "react-i18next";
import MuiListItem from "@material-ui/core/ListItem";
vhCheck();
const drawerWidth = 240;
const drawerWidthMobile = 270;
const ListItem = withStyles((theme) => ({
root: {
borderRadius:theme.shape.borderRadius,
},
}))(MuiListItem);
const mapStateToProps = (state) => {
return {
desktopOpen: state.viewUpdate.open,
selected: state.explorer.selected,
isMultiple: state.explorer.selectProps.isMultiple,
withFolder: state.explorer.selectProps.withFolder,
withFile: state.explorer.selectProps.withFile,
path: state.navigator.path,
title: state.siteConfig.title,
subTitle: state.viewUpdate.subTitle,
loadUploader: state.viewUpdate.loadUploader,
isLogin: state.viewUpdate.isLogin,
shareInfo: state.viewUpdate.shareInfo,
registerEnabled: state.siteConfig.registerEnabled,
audioPreviewPlayingName: state.explorer.audioPreview.playingName,
audioPreviewIsOpen: state.explorer.audioPreview.isOpen,
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleDesktopToggle: (open) => {
dispatch(drawerToggleAction(open));
},
setSelectedTarget: (targets) => {
dispatch(setSelectedTarget(targets));
},
navigateTo: (path) => {
dispatch(navigateTo(path));
},
openCreateFolderDialog: () => {
dispatch(openCreateFolderDialog());
},
changeContextMenu: (type, open) => {
dispatch(changeContextMenu(type, open));
},
saveFile: () => {
dispatch(saveFile());
},
openMusicDialog: () => {
dispatch(openMusicDialog());
},
showImgPreivew: (first) => {
dispatch(showImgPreivew(first));
},
toggleSnackbar: (vertical, horizontal, msg, color) => {
dispatch(toggleSnackbar(vertical, horizontal, msg, color));
},
openRenameDialog: () => {
dispatch(openRenameDialog());
},
openMoveDialog: () => {
dispatch(openMoveDialog());
},
openRemoveDialog: () => {
dispatch(openRemoveDialog());
},
openShareDialog: () => {
dispatch(openShareDialog());
},
openLoadingDialog: (text) => {
dispatch(openLoadingDialog(text));
},
setSessionStatus: () => {
dispatch(setSessionStatus());
},
openPreview: (share) => {
dispatch(openPreview(share));
},
audioPreviewOpen: () => {
dispatch(audioPreviewSetIsOpen(true));
},
startBatchDownload: (share) => {
dispatch(startBatchDownload(share));
},
startDirectoryDownload: (share) => {
dispatch(startDirectoryDownload(share));
},
startDownload: (share, file) => {
dispatch(startDownload(share, file));
},
};
};
const styles = (theme) => ({
appBar: {
marginLeft: drawerWidth,
[theme.breakpoints.down("xs")]: {
marginLeft: drawerWidthMobile,
},
zIndex: theme.zIndex.drawer + 1,
transition: " background-color 250ms",
},
drawer: {
width: 0,
flexShrink: 0,
},
drawerDesktop: {
width: drawerWidth,
flexShrink: 0,
},
icon: {
marginRight: theme.spacing(2),
},
menuButton: {
marginRight: 20,
[theme.breakpoints.up("sm")]: {
display: "none",
},
},
menuButtonDesktop: {
marginRight: 20,
[theme.breakpoints.down("xs")]: {
display: "none",
},
},
menuIcon: {
marginRight: 20,
},
toolbar: theme.mixins.toolbar,
drawerPaper: {
width: drawerWidthMobile,
},
drawerPaperDesktop: {
width: drawerWidth,
},
upDrawer: {
overflowX: "hidden",
[theme.breakpoints.up("sm")]: {
display: "flex",
flexDirection: "column",
height: "100%",
justifyContent: "space-between",
},
},
drawerOpen: {
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerClose: {
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: "hidden",
width: 0,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
grow: {
flexGrow: 1,
},
badge: {
top: 1,
right: -15,
},
nested: {
paddingLeft: theme.spacing(4),
},
sectionForFile: {
display: "flex",
},
extendedIcon: {
marginRight: theme.spacing(1),
},
addButton: {
marginLeft: "40px",
marginTop: "25px",
marginBottom: "15px",
},
fabButton: {
borderRadius: "100px",
},
badgeFix: {
right: "10px",
},
iconFix: {
marginLeft: "16px",
},
dividerFix: {
marginTop: "8px",
},
folderShareIcon: {
verticalAlign: "sub",
marginRight: "5px",
},
shareInfoContainer: {
display: "flex",
marginTop: "15px",
marginBottom: "20px",
marginLeft: "28px",
textDecoration: "none",
},
shareAvatar: {
width: "40px",
height: "40px",
},
stickFooter: {
bottom: "0px",
position: "absolute",
backgroundColor: theme.palette.background.paper,
width: "100%",
},
ownerInfo: {
marginLeft: "10px",
width: "150px",
},
minStickDrawer: {
overflowY: "auto",
},
paddingList:{
padding:theme.spacing(1),
}
});
class NavbarCompoment extends Component {
constructor(props) {
super(props);
this.state = {
mobileOpen: false,
};
this.UploaderRef = React.createRef();
}
UNSAFE_componentWillMount() {
this.unlisten = this.props.history.listen(() => {
this.setState(() => ({ mobileOpen: false }));
});
}
componentWillUnmount() {
this.unlisten();
}
componentDidMount = () => {
changeThemeColor(
this.props.selected.length <= 1 &&
!(!this.props.isMultiple && this.props.withFile)
? this.props.theme.palette.primary.main
: this.props.theme.palette.background.default
);
};
UNSAFE_componentWillReceiveProps = (nextProps) => {
if (
(this.props.selected.length === 0) !==
(nextProps.selected.length === 0)
) {
changeThemeColor(
!(this.props.selected.length === 0)
? this.props.theme.palette.type === "dark"
? this.props.theme.palette.background.default
: this.props.theme.palette.primary.main
: this.props.theme.palette.background.default
);
}
};
handleDrawerToggle = () => {
this.setState((state) => ({ mobileOpen: !state.mobileOpen }));
};
openDownload = () => {
this.props.startDownload(this.props.shareInfo, this.props.selected[0]);
};
openDirectoryDownload = (e) => {
this.props.startDirectoryDownload(this.props.shareInfo);
};
archiveDownload = (e) => {
this.props.startBatchDownload(this.props.shareInfo);
};
signOut = () => {
API.delete("/user/session/")
.then(() => {
this.props.toggleSnackbar(
"top",
"right",
this.props.t("login.loggedOut"),
"success"
);
Auth.signout();
window.location.reload();
this.props.setSessionStatus(false);
})
.catch((error) => {
this.props.toggleSnackbar(
"top",
"right",
error.message,
"warning"
);
})
.finally(() => {
this.handleClose();
});
};
render() {
const { classes, t } = this.props;
const user = Auth.GetUser(this.props.isLogin);
const isHomePage = pathHelper.isHomePage(this.props.location.pathname);
const isSharePage = pathHelper.isSharePage(
this.props.location.pathname
);
const drawer = (
<div id="container" className={classes.upDrawer}>
{pathHelper.isMobile() && <UserInfo />}
{Auth.Check(this.props.isLogin) && (
<>
<div className={classes.minStickDrawer}>
<FileTag />
<List className={classes.paddingList}>
<ListItem
button
key="我的分享"
onClick={() =>
this.props.history.push("/shares?")
}
>
<ListItemIcon>
<ShareIcon
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText
primary={t("navbar.myShare")}
/>
</ListItem>
{user.group.allowRemoteDownload && (
<ListItem
button
key="离线下载"
onClick={() =>
this.props.history.push("/aria2?")
}
>
<ListItemIcon>
<DownloadIcon
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText
primary={t("navbar.remoteDownload")}
/>
</ListItem>
)}
<ListItem
button
key="容量配额"
onClick={() =>
this.props.history.push("/quota?")
}
>
<ListItemIcon>
<SdStorage
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText primary={t("vas.quota")} />
</ListItem>
<ListItem
button
key="WebDAV"
onClick={() =>
this.props.history.push("/connect?")
}
>
<ListItemIcon>
<Devices className={classes.iconFix} />
</ListItemIcon>
<ListItemText
primary={t("navbar.connect")}
/>
</ListItem>
<ListItem
button
key="任务队列"
onClick={() =>
this.props.history.push("/tasks?")
}
>
<ListItemIcon>
<Assignment
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText
primary={t("navbar.taskQueue")}
/>
</ListItem>
{pathHelper.isMobile() && (
<>
<Divider />
<ListItem
button
key="个人设置"
onClick={() =>
this.props.history.push(
"/setting?"
)
}
>
<ListItemIcon>
<Settings
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText
primary={t("navbar.setting")}
/>
</ListItem>
<ListItem
button
key="退出登录"
onClick={this.signOut}
>
<ListItemIcon>
<LogoutVariant
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText
primary={t("login.logout")}
/>
</ListItem>
</>
)}
</List>
</div>
<div>
<StorageBar></StorageBar>
</div>
</>
)}
{!Auth.Check(this.props.isLogin) && (
<div>
<ListItem
button
key="登录"
onClick={() => this.props.history.push("/login")}
>
<ListItemIcon>
<AccountArrowRight
className={classes.iconFix}
/>
</ListItemIcon>
<ListItemText primary={t("login.signIn")} />
</ListItem>
{this.props.registerEnabled && (
<ListItem
button
key="注册"
onClick={() =>
this.props.history.push("/signup")
}
>
<ListItemIcon>
<AccountPlus className={classes.iconFix} />
</ListItemIcon>
<ListItemText primary={t("login.signUp")} />
</ListItem>
)}
</div>
)}
</div>
);
const iOS =
process.browser && /iPad|iPhone|iPod/.test(navigator.userAgent);
return (
<div>
<AppBar
position="fixed"
className={classes.appBar}
color={
this.props.theme.palette.type !== "dark" &&
this.props.selected.length === 0
? "primary"
: "default"
}
>
<Toolbar>
{this.props.selected.length === 0 && (
<IconButton
color="inherit"
aria-label="Open drawer"
onClick={this.handleDrawerToggle}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
)}
{this.props.selected.length === 0 && (
<IconButton
color="inherit"
aria-label="Open drawer"
onClick={() =>
this.props.handleDesktopToggle(
!this.props.desktopOpen
)
}
className={classes.menuButtonDesktop}
>
<MenuIcon />
</IconButton>
)}
{this.props.selected.length > 0 &&
(isHomePage ||
pathHelper.isSharePage(
this.props.location.pathname
)) && (
<Grow in={this.props.selected.length > 0}>
<IconButton
color="inherit"
className={classes.menuIcon}
onClick={() =>
this.props.setSelectedTarget([])
}
>
<BackIcon />
</IconButton>
</Grow>
)}
{this.props.selected.length === 0 && (
<Typography
variant="h6"
color="inherit"
style={{
cursor: "pointer",
}}
noWrap
onClick={() => {
this.props.history.push("/");
}}
>
{this.props.subTitle
? this.props.subTitle
: this.props.title}
</Typography>
)}
{!this.props.isMultiple &&
(this.props.withFile || this.props.withFolder) &&
!pathHelper.isMobile() && (
<Typography variant="h6" color="inherit" noWrap>
{this.props.selected[0].name}{" "}
{this.props.withFile &&
(isHomePage ||
pathHelper.isSharePage(
this.props.location.pathname
)) &&
"(" +
sizeToString(
this.props.selected[0].size
) +
")"}
</Typography>
)}
{this.props.selected.length > 1 &&
!pathHelper.isMobile() && (
<Typography variant="h6" color="inherit" noWrap>
{t("navbar.objectsSelected", {
num: this.props.selected.length,
})}
</Typography>
)}
{this.props.selected.length === 0 && <SezrchBar />}
<div className={classes.grow} />
{this.props.selected.length > 0 &&
(isHomePage || isSharePage) && (
<div className={classes.sectionForFile}>
{!this.props.isMultiple &&
this.props.withFile &&
isPreviewable(
this.props.selected[0].name
) && (
<Grow
in={
!this.props.isMultiple &&
this.props.withFile &&
isPreviewable(
this.props.selected[0]
.name
)
}
>
<Tooltip
title={t(
"fileManager.open"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.props.openPreview(
this.props
.shareInfo
)
}
>
<OpenIcon />
</IconButton>
</Tooltip>
</Grow>
)}
{!this.props.isMultiple &&
this.props.withFile && (
<Grow
in={
!this.props.isMultiple &&
this.props.withFile
}
>
<Tooltip
title={t(
"fileManager.download"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.openDownload()
}
>
<DownloadIcon />
</IconButton>
</Tooltip>
</Grow>
)}
{(this.props.isMultiple ||
this.props.withFolder) &&
window.showDirectoryPicker &&
window.isSecureContext && (
<Grow
in={
(this.props.isMultiple ||
this.props
.withFolder) &&
window.showDirectoryPicker &&
window.isSecureContext
}
>
<Tooltip
title={t(
"fileManager.download"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.openDirectoryDownload()
}
>
<FolderDownload />
</IconButton>
</Tooltip>
</Grow>
)}
{(this.props.isMultiple ||
this.props.withFolder) && (
<Grow
in={
this.props.isMultiple ||
this.props.withFolder
}
>
<Tooltip
title={t(
"fileManager.batchDownload"
)}
>
<IconButton
color="inherit"
disableClickAway
onClick={() =>
this.archiveDownload()
}
>
<DownloadIcon />
</IconButton>
</Tooltip>
</Grow>
)}
{!this.props.isMultiple &&
!pathHelper.isMobile() &&
!isSharePage && (
<Grow in={!this.props.isMultiple}>
<Tooltip
title={t(
"fileManager.share"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.props.openShareDialog()
}
>
<ShareIcon />
</IconButton>
</Tooltip>
</Grow>
)}
{!this.props.isMultiple && !isSharePage && (
<Grow in={!this.props.isMultiple}>
<Tooltip
title={t("fileManager.rename")}
>
<IconButton
color="inherit"
onClick={() =>
this.props.openRenameDialog()
}
>
<RenameIcon />
</IconButton>
</Tooltip>
</Grow>
)}
{!isSharePage && (
<div style={{ display: "flex" }}>
{!pathHelper.isMobile() && (
<Grow
in={
this.props.selected
.length !== 0 &&
!pathHelper.isMobile()
}
>
<Tooltip
title={t(
"fileManager.move"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.props.openMoveDialog()
}
>
<MoveIcon />
</IconButton>
</Tooltip>
</Grow>
)}
<Grow
in={
this.props.selected
.length !== 0
}
>
<Tooltip
title={t(
"fileManager.delete"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.props.openRemoveDialog()
}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</Grow>
{pathHelper.isMobile() && (
<Grow
in={
this.props.selected
.length !== 0 &&
pathHelper.isMobile()
}
>
<Tooltip
title={t(
"fileManager.moreActions"
)}
>
<IconButton
color="inherit"
onClick={() =>
this.props.changeContextMenu(
"file",
true
)
}
>
<MoreHoriz />
</IconButton>
</Tooltip>
</Grow>
)}
</div>
)}
</div>
)}
{this.props.selected.length <= 1 &&
!(!this.props.isMultiple && this.props.withFile) &&
this.props.audioPreviewPlayingName != null && (
<IconButton
title={t("navbar.music")}
className={classes.sideButton}
onClick={this.props.audioPreviewOpen}
color={"inherit"}
>
<MusicNote fontSize={"default"} />
</IconButton>
)}
{this.props.selected.length === 0 && <UserAvatar />}
{this.props.selected.length === 0 &&
pathHelper.isMobile() && (
<>
{isHomePage && <PolicySwitcher />}
{(isHomePage || this.props.shareInfo) && (
<SubActions inherit />
)}
</>
)}
</Toolbar>
</AppBar>
<Uploader />
<Hidden smUp implementation="css">
<SwipeableDrawer
container={this.props.container}
variant="temporary"
classes={{
paper: classes.drawerPaper,
}}
anchor="left"
open={this.state.mobileOpen}
onClose={this.handleDrawerToggle}
onOpen={() =>
this.setState(() => ({ mobileOpen: true }))
}
disableDiscovery={iOS}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</SwipeableDrawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer
classes={{
paper: classes.drawerPaperDesktop,
}}
className={classNames(classes.drawer, {
[classes.drawerOpen]: this.props.desktopOpen,
[classes.drawerClose]: !this.props.desktopOpen,
})}
variant="persistent"
anchor="left"
open={this.props.desktopOpen}
>
<div className={classes.toolbar} />
{drawer}
</Drawer>
</Hidden>
</div>
);
}
}
NavbarCompoment.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
const Navbar = connect(
mapStateToProps,
mapDispatchToProps
)(
withTheme(
withStyles(styles)(withRouter(withTranslation()(NavbarCompoment)))
)
);
export default Navbar;

View File

@@ -0,0 +1,202 @@
import React, { useCallback } from "react";
import {
Avatar,
CircularProgress,
IconButton,
ListItem,
ListItemAvatar,
ListItemText,
makeStyles,
} from "@material-ui/core";
import Tooltip from "@material-ui/core/Tooltip";
import { Nas } from "mdi-material-ui";
import Popover from "@material-ui/core/Popover";
import API from "../../middleware/Api";
import { useDispatch, useSelector } from "react-redux";
import { Backup, Check } from "@material-ui/icons";
import { blue, green } from "@material-ui/core/colors";
import List from "@material-ui/core/List";
import { refreshFileList, toggleSnackbar } from "../../redux/explorer";
import Divider from "@material-ui/core/Divider";
import Box from "@material-ui/core/Box";
import Link from "@material-ui/core/Link";
import { Link as RouterLink } from "react-router-dom";
import pathHelper from "../../utils/page";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
uploadFromFile: {
backgroundColor: blue[100],
color: blue[600],
},
policySelected: {
backgroundColor: green[100],
color: green[800],
},
header: {
padding: "8px 16px",
fontSize: 14,
},
list: {
minWidth: 300,
maxHeight: 600,
overflow: "auto",
},
}));
const PolicySwitcher = () => {
const { t } = useTranslation();
const [anchorEl, setAnchorEl] = React.useState(null);
const [policies, setPolicies] = React.useState([]);
const [loading, setLoading] = React.useState(null);
const policy = useSelector((state) => state.explorer.currentPolicy);
const path = useSelector((state) => state.navigator.path);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const RefreshFileList = useCallback(() => dispatch(refreshFileList()), [
dispatch,
]);
const search = useSelector((state) => state.explorer.search);
const handleClick = (event) => {
if (policies.length === 0) {
API.get("/user/setting/policies", {})
.then((response) => {
setPolicies(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
}
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const switchTo = (id) => {
if (id === policy.id) {
handleClose();
return;
}
setLoading(id);
API.post("/webdav/mount", {
path: path,
policy: id,
})
.then(() => {
ToggleSnackbar(
"top",
"right",
t("vas.folderPolicySwitched"),
"success"
);
RefreshFileList();
setLoading(null);
handleClose();
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
setLoading(null);
handleClose();
});
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const classes = useStyles();
return (
<>
{pathHelper.isHomePage(location.pathname) && !search && (
<Tooltip title={t("vas.switchFolderPolicy")} placement="bottom">
<IconButton onClick={handleClick} color="inherit">
<Nas />
</IconButton>
</Tooltip>
)}
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
<div className={classes.header}>
<Box color={"text.secondary"}>
{t("vas.setPolicyForFolder")}
</Box>
</div>
<Divider />
<List className={classes.list}>
{policies.map((value, index) => (
<ListItem
button
component="label"
key={index}
onClick={() => switchTo(value.id)}
>
<ListItemAvatar>
{value.id === loading && (
<CircularProgress
size={35}
color="secondary"
/>
)}
{value.id !== loading && (
<>
{value.id === policy.id && (
<Avatar
className={
classes.policySelected
}
>
<Check />
</Avatar>
)}
{value.id !== policy.id && (
<Avatar
className={
classes.uploadFromFile
}
>
<Backup />
</Avatar>
)}
</>
)}
</ListItemAvatar>
<ListItemText primary={value.name} />
</ListItem>
))}
</List>
<Divider />
<div className={classes.header}>
<Link
onClick={() => handleClose()}
component={RouterLink}
to={"/connect?tab=1"}
color={"secondary"}
>
{t("vas.manageMount")}
</Link>
</div>
</Popover>
</>
);
};
export default PolicySwitcher;

View File

@@ -0,0 +1,283 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import SearchIcon from "@material-ui/icons/Search";
import { fade } from "@material-ui/core/styles/colorManipulator";
import FileIcon from "@material-ui/icons/InsertDriveFile";
import ShareIcon from "@material-ui/icons/Share";
import { connect } from "react-redux";
import {
Fade,
InputBase,
ListItemIcon,
ListItemText,
MenuItem,
Paper,
Popper,
Typography,
withStyles,
} from "@material-ui/core";
import { withRouter } from "react-router";
import pathHelper from "../../utils/page";
import { configure, HotKeys } from "react-hotkeys";
import { searchMyFile } from "../../redux/explorer";
import FolderIcon from "@material-ui/icons/Folder";
import { Trans, withTranslation } from "react-i18next";
configure({
ignoreTags: [],
});
const mapStateToProps = (state) => {
return {
path: state.navigator.path,
search: state.explorer.search,
};
};
const mapDispatchToProps = (dispatch) => {
return {
searchMyFile: (keywords, path) => {
dispatch(searchMyFile(keywords, path));
},
};
};
const styles = (theme) => ({
search: {
[theme.breakpoints.down("sm")]: {
display: "none",
},
position: "relative",
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.common.white, 0.15),
"&:hover": {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
marginRight: theme.spacing(2),
marginLeft: 0,
width: "100%",
[theme.breakpoints.up("sm")]: {
marginLeft: theme.spacing(7.2),
width: "auto",
},
},
searchIcon: {
width: theme.spacing(9),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
inputRoot: {
color: "inherit",
width: "100%",
},
inputInput: {
paddingTop: theme.spacing(1),
paddingRight: theme.spacing(1),
paddingBottom: theme.spacing(1),
paddingLeft: theme.spacing(7),
transition: theme.transitions.create("width"),
width: "100%",
[theme.breakpoints.up("md")]: {
width: 200,
"&:focus": {
width: 300,
},
},
},
suggestBox: {
zIndex: "9999",
width: 364,
},
});
const keyMap = {
SEARCH: "enter",
};
class SearchBarCompoment extends Component {
constructor(props) {
super(props);
this.state = {
anchorEl: null,
input: "",
};
}
handlers = {
SEARCH: (e) => {
if (pathHelper.isHomePage(this.props.location.pathname)) {
this.searchMyFile("")();
} else {
this.searchShare();
}
e.target.blur();
},
};
handleChange = (event) => {
const { currentTarget } = event;
this.input = event.target.value;
this.setState({
anchorEl: currentTarget,
input: event.target.value,
});
};
cancelSuggest = () => {
this.setState({
input: "",
});
};
searchMyFile = (path) => () => {
this.props.searchMyFile("keywords/" + this.input, path);
};
searchShare = () => {
this.props.history.push(
"/search?keywords=" + encodeURIComponent(this.input)
);
};
render() {
const { classes, t } = this.props;
const { anchorEl } = this.state;
const id = this.state.input !== "" ? "simple-popper" : null;
const isHomePage = pathHelper.isHomePage(this.props.location.pathname);
return (
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<HotKeys keyMap={keyMap} handlers={this.handlers}>
<InputBase
placeholder={t("navbar.searchPlaceholder")}
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={this.handleChange}
onBlur={this.cancelSuggest}
value={this.state.input}
/>
</HotKeys>
<Popper
id={id}
open={this.state.input !== ""}
anchorEl={anchorEl}
className={classes.suggestBox}
transition
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper square={true}>
{isHomePage && (
<MenuItem onClick={this.searchMyFile("")}>
<ListItemIcon className={classes.icon}>
<FileIcon />
</ListItemIcon>
<ListItemText
classes={{
primary: classes.primary,
}}
primary={
<Typography noWrap>
<Trans
i18nKey="navbar.searchInFiles"
values={{
name: this.state
.input,
}}
components={[
<strong key={0} />,
]}
/>
</Typography>
}
/>
</MenuItem>
)}
{isHomePage &&
this.props.path !== "/" &&
!this.props.search && (
<MenuItem
onClick={this.searchMyFile(
this.props.path
)}
>
<ListItemIcon
className={classes.icon}
>
<FolderIcon />
</ListItemIcon>
<ListItemText
classes={{
primary: classes.primary,
}}
primary={
<Typography noWrap>
<Trans
i18nKey="navbar.searchInFolders"
values={{
name: this.state
.input,
}}
components={[
<strong
key={0}
/>,
]}
/>
</Typography>
}
/>
</MenuItem>
)}
<MenuItem onClick={this.searchShare}>
<ListItemIcon className={classes.icon}>
<ShareIcon />
</ListItemIcon>
<ListItemText
classes={{ primary: classes.primary }}
primary={
<Typography noWrap>
<Trans
i18nKey="navbar.searchInShares"
values={{
name: this.state.input,
}}
components={[
<strong key={0} />,
]}
/>
</Typography>
}
/>
</MenuItem>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
}
SearchBarCompoment.propTypes = {
classes: PropTypes.object.isRequired,
};
const SearchBar = connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(withRouter(withTranslation()(SearchBarCompoment))));
export default SearchBar;

View File

@@ -0,0 +1,208 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import StorageIcon from "@material-ui/icons/Storage";
import { connect } from "react-redux";
import API from "../../middleware/Api";
import { sizeToString } from "../../utils";
import {
Divider,
LinearProgress,
Tooltip,
Typography,
withStyles,
} from "@material-ui/core";
import ButtonBase from "@material-ui/core/ButtonBase";
import Link from "@material-ui/core/Link";
import { withRouter } from "react-router";
import { toggleSnackbar } from "../../redux/explorer";
import { Link as RouterLink } from "react-router-dom";
import { withTranslation } from "react-i18next";
const mapStateToProps = (state) => {
return {
refresh: state.viewUpdate.storageRefresh,
isLogin: state.viewUpdate.isLogin,
};
};
const mapDispatchToProps = (dispatch) => {
return {
toggleSnackbar: (vertical, horizontal, msg, color) => {
dispatch(toggleSnackbar(vertical, horizontal, msg, color));
},
};
};
const styles = (theme) => ({
iconFix: {
marginLeft: "32px",
marginRight: "17px",
color: theme.palette.text.secondary,
marginTop: "2px",
},
textFix: {
padding: " 0 0 0 16px",
},
storageContainer: {
display: "flex",
marginTop: "15px",
textAlign: "left",
marginBottom: "11px",
},
detail: {
width: "100%",
marginRight: "35px",
},
info: {
width: "131px",
overflow: "hidden",
textOverflow: "ellipsis",
[theme.breakpoints.down("xs")]: {
width: "162px",
},
marginTop: "5px",
},
bar: {
marginTop: "5px",
},
stickFooter: {
backgroundColor: theme.palette.background.paper,
},
});
// TODO 使用 hooks 重构
class StorageBarCompoment extends Component {
state = {
percent: 0,
used: null,
total: null,
showExpand: false,
};
firstLoad = true;
componentDidMount = () => {
if (this.firstLoad && this.props.isLogin) {
this.firstLoad = !this.firstLoad;
this.updateStatus();
}
};
componentWillUnmount() {
this.firstLoad = false;
}
UNSAFE_componentWillReceiveProps = (nextProps) => {
if (
(this.props.isLogin && this.props.refresh !== nextProps.refresh) ||
(this.props.isLogin !== nextProps.isLogin && nextProps.isLogin)
) {
this.updateStatus();
}
};
updateStatus = () => {
let percent = 0;
API.get("/user/storage")
.then((response) => {
if (response.data.used / response.data.total >= 1) {
percent = 100;
this.props.toggleSnackbar(
"top",
"right",
this.props.t("vas.exceedQuota"),
"warning"
);
} else {
percent = (response.data.used / response.data.total) * 100;
}
this.setState({
percent: percent,
used: sizeToString(response.data.used),
total: sizeToString(response.data.total),
});
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
.catch(() => {});
};
render() {
const { classes, t } = this.props;
return (
<div
onMouseEnter={() => this.setState({ showExpand: true })}
onMouseLeave={() => this.setState({ showExpand: false })}
className={classes.stickFooter}
>
<Divider />
<ButtonBase onClick={() => this.props.history.push("/quota")}>
<div className={classes.storageContainer}>
<StorageIcon className={classes.iconFix} />
<div className={classes.detail}>
<Typography variant={"subtitle2"}>
{t("navbar.storage") + " "}
{this.state.showExpand && (
<Link
component={RouterLink}
color={"secondary"}
to={"/buy"}
>
{t("vas.extendStorage")}
</Link>
)}
</Typography>
<LinearProgress
className={classes.bar}
color="secondary"
variant="determinate"
value={this.state.percent}
/>
<div className={classes.info}>
<Tooltip
title={t("navbar.storageDetail", {
used:
this.state.used === null
? " -- "
: this.state.used,
total:
this.state.total === null
? " -- "
: this.state.total,
})}
placement="top"
>
<Typography
variant="caption"
noWrap
color="textSecondary"
>
{this.state.used === null
? " -- "
: this.state.used}
{" / "}
{this.state.total === null
? " -- "
: this.state.total}
</Typography>
</Tooltip>
</div>
</div>
</div>
</ButtonBase>
</div>
);
}
}
StorageBarCompoment.propTypes = {
classes: PropTypes.object.isRequired,
};
const StorageBar = connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(withRouter(withTranslation()(StorageBarCompoment))));
export default StorageBar;

View File

@@ -0,0 +1,178 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import SettingIcon from "@material-ui/icons/Settings";
import UserAvatarPopover from "./UserAvatarPopover";
import { AccountCircle } from "mdi-material-ui";
import Auth from "../../middleware/Auth";
import {
Avatar,
Grow,
IconButton,
Tooltip,
withStyles,
} from "@material-ui/core";
import { withRouter } from "react-router-dom";
import pathHelper from "../../utils/page";
import DarkModeSwitcher from "./DarkModeSwitcher";
import PolicySwitcher from "./PolicySwitcher";
import { Home } from "@material-ui/icons";
import { setUserPopover } from "../../redux/explorer";
import { withTranslation } from "react-i18next";
const mapStateToProps = (state) => {
return {
selected: state.explorer.selected,
isMultiple: state.explorer.selectProps.isMultiple,
withFolder: state.explorer.selectProps.withFolder,
withFile: state.explorer.selectProps.withFile,
isLogin: state.viewUpdate.isLogin,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setUserPopover: (anchor) => {
dispatch(setUserPopover(anchor));
},
};
};
const styles = (theme) => ({
mobileHidden: {
[theme.breakpoints.down("xs")]: {
display: "none",
},
whiteSpace: "nowrap",
},
avatar: {
width: "30px",
height: "30px",
},
header: {
display: "flex",
padding: "20px 20px 20px 20px",
},
largeAvatar: {
height: "90px",
width: "90px",
},
info: {
marginLeft: "10px",
width: "139px",
},
badge: {
marginTop: "10px",
},
visitorMenu: {
width: 200,
},
});
class UserAvatarCompoment extends Component {
state = {
anchorEl: null,
};
showUserInfo = (e) => {
this.props.setUserPopover(e.currentTarget);
};
handleClose = () => {
this.setState({
anchorEl: null,
});
};
openURL = (url) => {
window.location.href = url;
};
returnHome = () => {
window.location.href = "/home";
};
render() {
const { classes, t } = this.props;
const loginCheck = Auth.Check(this.props.isLogin);
const user = Auth.GetUser(this.props.isLogin);
const isAdminPage = pathHelper.isAdminPage(
this.props.location.pathname
);
return (
<div className={classes.mobileHidden}>
<Grow
in={
this.props.selected.length <= 1 &&
!(!this.props.isMultiple && this.props.withFile)
}
>
<div>
{!isAdminPage && (
<>
<DarkModeSwitcher position="top" />
{loginCheck && (
<>
<PolicySwitcher />
<Tooltip
title={t("navbar.setting")}
placement="bottom"
>
<IconButton
onClick={() =>
this.props.history.push(
"/setting?"
)
}
color="inherit"
>
<SettingIcon />
</IconButton>
</Tooltip>
</>
)}
</>
)}
{isAdminPage && (
<Tooltip
title={t("navbar.backToHomepage")}
placement="bottom"
>
<IconButton
color="inherit"
onClick={this.returnHome}
>
<Home />
</IconButton>
</Tooltip>
)}
<IconButton color="inherit" onClick={this.showUserInfo}>
{!loginCheck && <AccountCircle />}
{loginCheck && (
<Avatar
src={
"/api/v3/user/avatar/" + user.id + "/s"
}
className={classes.avatar}
/>
)}
</IconButton>{" "}
</div>
</Grow>
<UserAvatarPopover />
</div>
);
}
}
UserAvatarCompoment.propTypes = {
classes: PropTypes.object.isRequired,
};
const UserAvatar = connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(withRouter(withTranslation()(UserAvatarCompoment))));
export default UserAvatar;

View File

@@ -0,0 +1,260 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
AccountArrowRight,
AccountPlus,
DesktopMacDashboard,
HomeAccount,
LogoutVariant,
} from "mdi-material-ui";
import { withRouter } from "react-router-dom";
import Auth from "../../middleware/Auth";
import {
Avatar,
Chip,
Divider,
ListItemIcon,
MenuItem,
Popover,
Typography,
withStyles,
} from "@material-ui/core";
import API from "../../middleware/Api";
import pathHelper from "../../utils/page";
import {
setSessionStatus,
setUserPopover,
toggleSnackbar,
} from "../../redux/explorer";
import { withTranslation } from "react-i18next";
const mapStateToProps = (state) => {
return {
anchorEl: state.viewUpdate.userPopoverAnchorEl,
registerEnabled: state.siteConfig.registerEnabled,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setUserPopover: (anchor) => {
dispatch(setUserPopover(anchor));
},
toggleSnackbar: (vertical, horizontal, msg, color) => {
dispatch(toggleSnackbar(vertical, horizontal, msg, color));
},
setSessionStatus: (status) => {
dispatch(setSessionStatus(status));
},
};
};
const styles = () => ({
avatar: {
width: "30px",
height: "30px",
},
header: {
display: "flex",
padding: "20px 20px 20px 20px",
},
largeAvatar: {
height: "90px",
width: "90px",
},
info: {
marginLeft: "10px",
width: "139px",
},
badge: {
marginTop: "10px",
},
visitorMenu: {
width: 200,
},
});
class UserAvatarPopoverCompoment extends Component {
handleClose = () => {
this.props.setUserPopover(null);
};
openURL = (url) => {
window.location.href = url;
};
sigOut = () => {
API.delete("/user/session/")
.then(() => {
this.props.toggleSnackbar(
"top",
"right",
this.props.t("login.loggedOut"),
"success"
);
Auth.signout();
window.location.reload();
this.props.setSessionStatus(false);
})
.catch((error) => {
this.props.toggleSnackbar(
"top",
"right",
error.message,
"warning"
);
})
.then(() => {
this.handleClose();
});
};
render() {
const { classes, t } = this.props;
const user = Auth.GetUser();
const isAdminPage = pathHelper.isAdminPage(
this.props.location.pathname
);
return (
<Popover
open={this.props.anchorEl !== null}
onClose={this.handleClose}
anchorEl={this.props.anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
>
{!Auth.Check() && (
<div className={classes.visitorMenu}>
<Divider />
<MenuItem
onClick={() => this.props.history.push("/login")}
>
<ListItemIcon>
<AccountArrowRight />
</ListItemIcon>
{t("login.signIn")}
</MenuItem>
{this.props.registerEnabled && (
<MenuItem
onClick={() =>
this.props.history.push("/signup")
}
>
<ListItemIcon>
<AccountPlus />
</ListItemIcon>
{t("login.signUp")}
</MenuItem>
)}
</div>
)}
{Auth.Check() && (
<div>
<div className={classes.header}>
<div className={classes.largeAvatarContainer}>
<Avatar
className={classes.largeAvatar}
src={
"/api/v3/user/avatar/" + user.id + "/l"
}
/>
</div>
<div className={classes.info}>
<Typography noWrap>{user.nickname}</Typography>
<Typography
color="textSecondary"
style={{
fontSize: "0.875rem",
}}
noWrap
>
{user.user_name}
</Typography>
<Chip
className={classes.badge}
color={
user.group.id === 1
? "secondary"
: "default"
}
label={user.group.name}
/>
</div>
</div>
<div>
<Divider />
{!isAdminPage && (
<MenuItem
style={{
padding: " 11px 16px 11px 16px",
}}
onClick={() => {
this.handleClose();
this.props.history.push(
"/profile/" + user.id
);
}}
>
<ListItemIcon>
<HomeAccount />
</ListItemIcon>
{t("navbar.myProfile")}
</MenuItem>
)}
{user.group.id === 1 && (
<MenuItem
style={{
padding: " 11px 16px 11px 16px",
}}
onClick={() => {
this.handleClose();
this.props.history.push("/admin/home");
}}
>
<ListItemIcon>
<DesktopMacDashboard />
</ListItemIcon>
{t("navbar.dashboard")}
</MenuItem>
)}
<MenuItem
style={{
padding: " 11px 16px 11px 16px",
}}
onClick={this.sigOut}
>
<ListItemIcon>
<LogoutVariant />
</ListItemIcon>
{t("login.logout")}
</MenuItem>
</div>
</div>
)}
</Popover>
);
}
}
UserAvatarPopoverCompoment.propTypes = {
classes: PropTypes.object.isRequired,
};
const UserAvatarPopover = connect(
mapStateToProps,
mapDispatchToProps
)(
withStyles(styles)(
withRouter(withTranslation()(UserAvatarPopoverCompoment))
)
);
export default UserAvatarPopover;

View File

@@ -0,0 +1,152 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Typography, withStyles } from "@material-ui/core";
import Auth from "../../middleware/Auth";
import DarkModeSwitcher from "./DarkModeSwitcher";
import Avatar from "@material-ui/core/Avatar";
import { setUserPopover } from "../../redux/explorer";
import { withTranslation } from "react-i18next";
const mapStateToProps = (state) => {
return {
isLogin: state.viewUpdate.isLogin,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setUserPopover: (anchor) => {
dispatch(setUserPopover(anchor));
},
};
};
const styles = (theme) => ({
userNav: {
height: "170px",
backgroundColor: theme.palette.primary.main,
padding: "20px 20px 2em",
backgroundImage:
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1600 900'%3E%3Cpolygon fill='" +
theme.palette.primary.light.replace("#", "%23") +
"' points='957 450 539 900 1396 900'/%3E%3Cpolygon fill='" +
theme.palette.primary.dark.replace("#", "%23") +
"' points='957 450 872.9 900 1396 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.main.replace("#", "%23") +
"' points='-60 900 398 662 816 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.dark.replace("#", "%23") +
"' points='337 900 398 662 816 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.light.replace("#", "%23") +
"' points='1203 546 1552 900 876 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.main.replace("#", "%23") +
"' points='1203 546 1552 900 1162 900'/%3E%3Cpolygon fill='" +
theme.palette.primary.dark.replace("#", "%23") +
"' points='641 695 886 900 367 900'/%3E%3Cpolygon fill='" +
theme.palette.primary.main.replace("#", "%23") +
"' points='587 900 641 695 886 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.light.replace("#", "%23") +
"' points='1710 900 1401 632 1096 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.dark.replace("#", "%23") +
"' points='1710 900 1401 632 1365 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.main.replace("#", "%23") +
"' points='1210 900 971 687 725 900'/%3E%3Cpolygon fill='" +
theme.palette.secondary.dark.replace("#", "%23") +
"' points='943 900 1210 900 971 687'/%3E%3C/svg%3E\")",
backgroundSize: "cover",
},
avatar: {
display: "block",
width: "70px",
height: "70px",
border: " 2px solid #fff",
borderRadius: "50%",
overflow: "hidden",
boxShadow:
"0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12)",
},
avatarImg: {
width: "66px",
height: "66px",
},
nickName: {
color: "#fff",
marginTop: "15px",
fontSize: "17px",
},
flexAvatar: {
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
},
groupName: {
color: "#ffffff",
opacity: "0.54",
},
storageCircle: {
width: "200px",
},
});
class UserInfoCompoment extends Component {
showUserInfo = (e) => {
this.props.setUserPopover(e.currentTarget);
};
render() {
const { classes, t } = this.props;
const isLogin = Auth.Check(this.props.isLogin);
const user = Auth.GetUser(this.props.isLogin);
return (
<div className={classes.userNav}>
<div className={classes.flexAvatar}>
{/* eslint-disable-next-line */}
<a onClick={this.showUserInfo} className={classes.avatar}>
{isLogin && (
<Avatar
src={"/api/v3/user/avatar/" + user.id + "/l"}
className={classes.avatarImg}
/>
)}
{!isLogin && (
<Avatar
src={"/api/v3/user/avatar/0/l"}
className={classes.avatarImg}
/>
)}
</a>
<DarkModeSwitcher position="left" />
</div>
<div className={classes.storageCircle}>
<Typography
className={classes.nickName}
component="h2"
noWrap
>
{isLogin ? user.nickname : t("navbar.notLoginIn")}
</Typography>
<Typography
className={classes.groupName}
component="h2"
color="textSecondary"
noWrap
>
{isLogin ? user.group.name : t("navbar.visitor")}
</Typography>
</div>
</div>
);
}
}
UserInfoCompoment.propTypes = {
classes: PropTypes.object.isRequired,
};
const UserInfo = connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(withTranslation()(UserInfoCompoment)));
export default UserInfo;