hyperspace-desktop-client-w.../src/pages/Settings.tsx

660 lines
25 KiB
TypeScript

import React, { Component } from "react";
import {
List,
ListItem,
ListItemAvatar,
ListItemText,
ListSubheader,
ListItemSecondaryAction,
Paper,
IconButton,
withStyles,
Button,
Switch,
Dialog,
DialogTitle,
DialogContent,
RadioGroup,
FormControlLabel,
Radio,
DialogActions,
DialogContentText,
Grid,
Theme,
Typography
} from "@material-ui/core";
import { styles } from "./PageLayout.styles";
import {
setUserDefaultBool,
getUserDefaultBool,
getUserDefaultTheme,
setUserDefaultTheme,
getUserDefaultVisibility,
setUserDefaultVisibility,
getConfig
} from "../utilities/settings";
import {
canSendNotifications,
browserSupportsNotificationRequests
} from "../utilities/notifications";
import { themes, defaultTheme } from "../types/HyperspaceTheme";
import ThemePreview from "../components/ThemePreview";
import {
setHyperspaceTheme,
getHyperspaceTheme,
getDarkModeFromSystem
} from "../utilities/themes";
import { Visibility } from "../types/Visibility";
import { LinkableButton } from "../interfaces/overrides";
import { Config } from "../types/Config";
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
import DevicesIcon from "@material-ui/icons/Devices";
import Brightness3Icon from "@material-ui/icons/Brightness3";
import PaletteIcon from "@material-ui/icons/Palette";
import AccountEditIcon from "mdi-material-ui/AccountEdit";
import MastodonIcon from "mdi-material-ui/Mastodon";
import VisibilityIcon from "@material-ui/icons/Visibility";
import NotificationsIcon from "@material-ui/icons/Notifications";
import BellAlertIcon from "mdi-material-ui/BellAlert";
import RefreshIcon from "@material-ui/icons/Refresh";
import UndoIcon from "@material-ui/icons/Undo";
import DomainDisablbedIcon from "@material-ui/icons/DomainDisabled";
interface ISettingsState {
darkModeEnabled: boolean;
systemDecidesDarkMode: boolean;
pushNotificationsEnabled: boolean;
badgeDisplaysAllNotifs: boolean;
selectThemeName: string;
themeDialogOpen: boolean;
visibilityDialogOpen: boolean;
resetHyperspaceDialog: boolean;
resetSettingsDialog: boolean;
previewTheme: Theme;
defaultVisibility: Visibility;
brandName: string;
federated: boolean;
}
class SettingsPage extends Component<any, ISettingsState> {
constructor(props: any) {
super(props);
this.state = {
darkModeEnabled: getUserDefaultBool("darkModeEnabled"),
systemDecidesDarkMode: getUserDefaultBool("systemDecidesDarkMode"),
pushNotificationsEnabled: canSendNotifications(),
badgeDisplaysAllNotifs: getUserDefaultBool(
"displayAllOnNotificationBadge"
),
selectThemeName: getUserDefaultTheme().key,
themeDialogOpen: false,
visibilityDialogOpen: false,
resetHyperspaceDialog: false,
resetSettingsDialog: false,
previewTheme:
setHyperspaceTheme(getUserDefaultTheme()) ||
setHyperspaceTheme(defaultTheme),
defaultVisibility: getUserDefaultVisibility() || "public",
brandName: "Hyperspace",
federated: true
};
this.toggleDarkMode = this.toggleDarkMode.bind(this);
this.toggleSystemDarkMode = this.toggleSystemDarkMode.bind(this);
this.togglePushNotifications = this.togglePushNotifications.bind(this);
this.toggleBadgeCount = this.toggleBadgeCount.bind(this);
this.toggleThemeDialog = this.toggleThemeDialog.bind(this);
this.toggleVisibilityDialog = this.toggleVisibilityDialog.bind(this);
this.changeThemeName = this.changeThemeName.bind(this);
this.changeTheme = this.changeTheme.bind(this);
this.setVisibility = this.setVisibility.bind(this);
}
componentDidMount() {
getConfig()
.then((config: any) => {
this.setState({
brandName: config.branding.name
});
})
.catch((err: Error) => {
console.error(err.message);
});
this.getFederatedStatus();
console.log(getDarkModeFromSystem());
}
getFederatedStatus() {
getConfig().then((result: any) => {
if (result !== undefined) {
let config: Config = result;
console.log(config.federation.allowPublicPosts === false);
this.setState({
federated: config.federation.allowPublicPosts
});
}
});
}
toggleDarkMode() {
this.setState({ darkModeEnabled: !this.state.darkModeEnabled });
setUserDefaultBool("darkModeEnabled", !this.state.darkModeEnabled);
window.location.reload();
}
toggleSystemDarkMode() {
this.setState({
systemDecidesDarkMode: !this.state.systemDecidesDarkMode
});
setUserDefaultBool(
"systemDecidesDarkMode",
!this.state.systemDecidesDarkMode
);
window.location.reload();
}
togglePushNotifications() {
this.setState({
pushNotificationsEnabled: !this.state.pushNotificationsEnabled
});
setUserDefaultBool(
"enablePushNotifications",
!this.state.pushNotificationsEnabled
);
}
toggleBadgeCount() {
this.setState({
badgeDisplaysAllNotifs: !this.state.badgeDisplaysAllNotifs
});
setUserDefaultBool(
"displayAllOnNotificationBadge",
!this.state.badgeDisplaysAllNotifs
);
}
toggleThemeDialog() {
this.setState({ themeDialogOpen: !this.state.themeDialogOpen });
}
toggleVisibilityDialog() {
this.setState({
visibilityDialogOpen: !this.state.visibilityDialogOpen
});
}
toggleResetDialog() {
this.setState({
resetHyperspaceDialog: !this.state.resetHyperspaceDialog
});
}
toggleResetSettingsDialog() {
this.setState({ resetSettingsDialog: !this.state.resetSettingsDialog });
}
changeTheme() {
setUserDefaultTheme(this.state.selectThemeName);
window.location.reload();
}
changeThemeName(theme: string) {
let previewTheme = setHyperspaceTheme(getHyperspaceTheme(theme));
this.setState({ selectThemeName: theme, previewTheme });
}
changeVisibility(to: Visibility) {
this.setState({ defaultVisibility: to });
}
setVisibility() {
setUserDefaultVisibility(this.state.defaultVisibility);
this.toggleVisibilityDialog();
}
reset() {
localStorage.clear();
window.location.reload();
}
refresh() {
let settings = [
"darkModeEnabled",
"enablePushNotifications",
"clearNotificationsOnRead",
"theme",
"displayAllOnNotificationBadge",
"defaultVisibility"
];
settings.forEach(setting => {
localStorage.removeItem(setting);
});
window.location.reload();
}
showThemeDialog() {
const { classes } = this.props;
return (
<Dialog
open={this.state.themeDialogOpen}
disableBackdropClick
disableEscapeKeyDown
maxWidth="md"
fullWidth={true}
aria-labelledby="confirmation-dialog-title"
>
<DialogTitle id="confirmation-dialog-title">
Choose a theme
</DialogTitle>
<DialogContent>
<Grid container spacing={16}>
<Grid item xs={12} md={6}>
<RadioGroup
aria-label="Theme"
name="colorScheme"
value={this.state.selectThemeName}
onChange={(e, value) =>
this.changeThemeName(value)
}
>
{themes.map(theme => (
<FormControlLabel
value={theme.key}
key={theme.key}
control={<Radio />}
label={theme.name}
/>
))}
))}
</RadioGroup>
</Grid>
<Grid
item
xs={12}
md={6}
className={classes.desktopOnly}
>
<Typography variant="h6" component="p">
Theme preview
</Typography>
<ThemePreview theme={this.state.previewTheme} />
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={this.toggleThemeDialog} color="default">
Cancel
</Button>
<Button onClick={this.changeTheme} color="secondary">
Set theme
</Button>
</DialogActions>
</Dialog>
);
}
showVisibilityDialog() {
return (
<Dialog
open={this.state.visibilityDialogOpen}
disableBackdropClick
disableEscapeKeyDown
maxWidth="xs"
fullWidth={true}
aria-labelledby="confirmation-dialog-title"
>
<DialogTitle id="confirmation-dialog-title">
Set your default visibility
</DialogTitle>
<DialogContent>
<RadioGroup
aria-label="Visibility"
name="visibility"
value={this.state.defaultVisibility}
onChange={(e, value) =>
this.changeVisibility(value as Visibility)
}
>
<FormControlLabel
value={"public"}
key={"public"}
control={<Radio />}
label={`Public ${
this.state.federated
? ""
: "(disabled by provider)"
}`}
disabled={!this.state.federated}
/>
<FormControlLabel
value={"unlisted"}
key={"unlisted"}
control={<Radio />}
label={"Unlisted"}
/>
<FormControlLabel
value={"private"}
key={"private"}
control={<Radio />}
label={"Private (followers only)"}
/>
<FormControlLabel
value={"direct"}
key={"direct"}
control={<Radio />}
label={"Direct"}
/>
</RadioGroup>
</DialogContent>
<DialogActions>
<Button
onClick={this.toggleVisibilityDialog}
color="default"
>
Cancel
</Button>
<Button onClick={this.setVisibility} color="secondary">
Set default
</Button>
</DialogActions>
</Dialog>
);
}
showResetSettingsDialog() {
return (
<Dialog
open={this.state.resetSettingsDialog}
onClose={() => this.toggleResetSettingsDialog()}
>
<DialogTitle id="alert-dialog-title">
Are you sure you want to refresh settings?
</DialogTitle>
<DialogActions>
<Button
onClick={() => this.toggleResetSettingsDialog()}
color="primary"
autoFocus
>
Cancel
</Button>
<Button
onClick={() => {
this.refresh();
}}
color="primary"
>
Refresh
</Button>
</DialogActions>
</Dialog>
);
}
showResetDialog() {
return (
<Dialog
open={this.state.resetHyperspaceDialog}
onClose={() => this.toggleResetDialog()}
>
<DialogTitle id="alert-dialog-title">
Reset {this.state.brandName}?
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure you want to reset {this.state.brandName}?
You'll need to re-authorize {this.state.brandName}{" "}
access again.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => this.toggleResetDialog()}
color="primary"
autoFocus
>
Cancel
</Button>
<Button
onClick={() => {
this.reset();
}}
color="primary"
>
Reset
</Button>
</DialogActions>
</Dialog>
);
}
render() {
const { classes } = this.props;
return (
<div className={classes.pageLayoutConstraints}>
<ListSubheader>Appearance</ListSubheader>
<Paper className={classes.pageListConstraints}>
<List>
<ListItem>
<ListItemAvatar>
<DevicesIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Match system appearance"
secondary="Obey light/dark theme from your system"
/>
<ListItemSecondaryAction>
<Switch
checked={this.state.systemDecidesDarkMode}
onChange={this.toggleSystemDarkMode}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<Brightness3Icon color="action" />
</ListItemAvatar>
<ListItemText
primary="Dark mode"
secondary="Toggles light or dark theme"
/>
<ListItemSecondaryAction>
<Switch
disabled={this.state.systemDecidesDarkMode}
checked={this.state.darkModeEnabled}
onChange={this.toggleDarkMode}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<PaletteIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Interface theme"
secondary="The color palette used for the interface"
/>
<ListItemSecondaryAction>
<Button onClick={this.toggleThemeDialog}>
Set theme
</Button>
</ListItemSecondaryAction>
</ListItem>
</List>
</Paper>
<br />
<ListSubheader>Your Account</ListSubheader>
<Paper className={classes.pageListConstraints}>
<List>
<ListItem>
<ListItemAvatar>
<AccountEditIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Edit your profile"
secondary="Change your bio, display name, and images"
/>
<ListItemSecondaryAction>
<LinkableButton to="/you">Edit</LinkableButton>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<DomainDisablbedIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Manage blocked servers"
secondary="View and manage servers that you've blocked"
/>
<ListItemSecondaryAction>
<LinkableButton to="/blocked">
Manage
</LinkableButton>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<MastodonIcon color="action" />
</ListItemAvatar>
<ListItemText primary="Configure on Mastodon" />
<ListItemSecondaryAction>
<IconButton
href={
(localStorage.getItem(
"baseurl"
) as string) + "/settings/preferences"
}
target="_blank"
rel="noreferrer"
>
<OpenInNewIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
</List>
</Paper>
<br />
<ListSubheader>Composer</ListSubheader>
<Paper className={classes.pageListConstraints}>
<List>
<ListItem>
<ListItemAvatar>
<VisibilityIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Default visibility"
secondary="New posts in composer will use this visiblity"
/>
<ListItemSecondaryAction>
<Button onClick={this.toggleVisibilityDialog}>
Change
</Button>
</ListItemSecondaryAction>
</ListItem>
</List>
</Paper>
<br />
<ListSubheader>Notifications</ListSubheader>
<Paper className={classes.pageListConstraints}>
<List>
<ListItem>
<ListItemAvatar>
<NotificationsIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Enable push notifications"
secondary={
getUserDefaultBool("userDeniedNotification")
? "Check your browser's notification permissions."
: browserSupportsNotificationRequests()
? "Send a push notification when not focused."
: "Notifications aren't supported."
}
/>
<ListItemSecondaryAction>
<Switch
checked={
this.state.pushNotificationsEnabled
}
onChange={this.togglePushNotifications}
disabled={
!browserSupportsNotificationRequests() ||
getUserDefaultBool(
"userDeniedNotification"
)
}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<BellAlertIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Notification badge counts all notifications"
secondary={
"Counts all notifications, read or unread."
}
/>
<ListItemSecondaryAction>
<Switch
checked={this.state.badgeDisplaysAllNotifs}
onChange={this.toggleBadgeCount}
/>
</ListItemSecondaryAction>
</ListItem>
</List>
</Paper>
<br />
<ListSubheader>Advanced</ListSubheader>
<Paper className={classes.pageListConstraints}>
<List>
<ListItem>
<ListItemAvatar>
<RefreshIcon color="action" />
</ListItemAvatar>
<ListItemText
primary="Refresh settings"
secondary="Reset the settings to defaults."
/>
<ListItemSecondaryAction>
<Button
onClick={() =>
this.toggleResetSettingsDialog()
}
>
Refresh
</Button>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<UndoIcon color="action" />
</ListItemAvatar>
<ListItemText
primary={`Reset ${this.state.brandName}`}
secondary="Deletes all data and resets the app"
/>
<ListItemSecondaryAction>
<Button
onClick={() => this.toggleResetDialog()}
>
Reset
</Button>
</ListItemSecondaryAction>
</ListItem>
</List>
</Paper>
{this.showThemeDialog()}
{this.showVisibilityDialog()}
{this.showResetDialog()}
{this.showResetSettingsDialog()}
</div>
);
}
}
export default withStyles(styles)(SettingsPage);