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, Avatar, Toolbar, Tooltip } from "@material-ui/core"; import { styles } from "./PageLayout.styles"; import { setUserDefaultBool, getUserDefaultBool, getUserDefaultTheme, setUserDefaultTheme, getUserDefaultVisibility, setUserDefaultVisibility, getConfig } from "../utilities/settings"; import { canSendNotifications, browserSupportsNotificationRequests, getNotificationRequestPermission } from "../utilities/notifications"; import { themes, defaultTheme } from "../types/HyperspaceTheme"; import ThemePreview from "../components/ThemePreview"; import { setHyperspaceTheme, getHyperspaceTheme } from "../utilities/themes"; import { Visibility } from "../types/Visibility"; import { LinkableIconButton } from "../interfaces/overrides"; 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 DomainDisabledIcon from "@material-ui/icons/DomainDisabled"; import AccountSettingsIcon from "mdi-material-ui/AccountSettings"; import AlphabeticalVariantOffIcon from "mdi-material-ui/AlphabeticalVariantOff"; import DashboardIcon from "@material-ui/icons/Dashboard"; import InfiniteIcon from "@material-ui/icons/AllInclusive"; import { Config } from "../types/Config"; import { Account } from "../types/Account"; import Mastodon from "megalodon"; import { withSnackbar } from "notistack"; 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; currentUser?: Account; imposeCharacterLimit: boolean; masonryLayout?: boolean; infiniteScroll?: boolean; } class SettingsPage extends Component { client: Mastodon; constructor(props: any) { super(props); this.client = new Mastodon( localStorage.getItem("access_token") as string, (localStorage.getItem("baseurl") as string) + "/api/v1" ); 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, imposeCharacterLimit: getUserDefaultBool("imposeCharacterLimit"), masonryLayout: getUserDefaultBool("isMasonryLayout"), infiniteScroll: getUserDefaultBool("isInfiniteScroll") }; 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.toggleMasonryLayout = this.toggleMasonryLayout.bind(this); this.toggleInfiniteScroll = this.toggleInfiniteScroll.bind(this); this.changeThemeName = this.changeThemeName.bind(this); this.changeTheme = this.changeTheme.bind(this); this.setVisibility = this.setVisibility.bind(this); } componentWillReceiveProps() { const path = window.location.hash.split("#"); const lastPath = document.getElementById(path[path.length - 1]); if (lastPath !== null) { lastPath.scrollIntoView(); window.scrollBy(0, -64); } } componentDidMount() { getConfig() .then((config: any) => { this.setState({ brandName: config.branding.name }); }) .catch((err: Error) => { console.error(err.message); }); this.getFederatedStatus(); this.client .get("/accounts/verify_credentials") .then((resp: any) => { let data: Account = resp.data; this.setState({ currentUser: data }); }) .catch((err: Error) => { let acct = localStorage.getItem("account"); if (acct) { this.setState({ currentUser: JSON.parse(acct) }); } else { this.props.enqueueSnackbar( "Couldn't find profile info: " + err.name ); console.error(err.message); } }); const path = window.location.hash.split("#"); const lastPath = document.getElementById(path[path.length - 1]); if (lastPath !== null) { lastPath.scrollIntoView(); window.scrollBy(0, -64); } } getFederatedStatus() { getConfig().then((result: any) => { if (result !== undefined) { let config: Config = result; // console.log(!config.federation.allowPublicPosts); 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(); } /** * Toggle the setting for enabling/disabling push notifications. * * If the notification permission wasn't set yet (i.e., `Notification.permission`) * is in `"default"` state, get the permission request first. */ togglePushNotifications() { if (!browserSupportsNotificationRequests()) { return; } if (Notification.permission === "default") { getNotificationRequestPermission() .then(permission => { if (permission === "granted") { setUserDefaultBool( "enablePushNotifications", !this.state.pushNotificationsEnabled ); this.setState({ pushNotificationsEnabled: !this.state .pushNotificationsEnabled }); } else if (permission === "denied") { this.props.enqueueSnackbar( "Permission request was denied.", { variant: "error" } ); } }) .catch(reason => this.props.enqueueSnackbar(reason, { variant: "error" }) ); } else { setUserDefaultBool( "enablePushNotifications", !this.state.pushNotificationsEnabled ); this.setState({ pushNotificationsEnabled: !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 }); } toggleCharacterLimit() { this.setState({ imposeCharacterLimit: !this.state.imposeCharacterLimit }); setUserDefaultBool( "imposeCharacterLimit", !this.state.imposeCharacterLimit ); } toggleResetDialog() { this.setState({ resetHyperspaceDialog: !this.state.resetHyperspaceDialog }); } toggleResetSettingsDialog() { this.setState({ resetSettingsDialog: !this.state.resetSettingsDialog }); } toggleMasonryLayout() { this.setState({ masonryLayout: !this.state.masonryLayout }); setUserDefaultBool("isMasonryLayout", !this.state.masonryLayout); } toggleInfiniteScroll() { this.setState({ infiniteScroll: !this.state.infiniteScroll }); setUserDefaultBool("isInfiniteScroll", !this.state.infiniteScroll); } 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(); } settingsList = () => { const { classes } = this.props; return ( <> Appearance {!this.state.systemDecidesDarkMode ? ( ) : null}
Composer this.toggleCharacterLimit()} />
Notifications
Advanced ); }; showThemeDialog() { const { classes } = this.props; return ( Choose a theme this.changeThemeName(value) } > {themes.map(theme => ( } label={theme.name} /> ))} Theme preview ); } showVisibilityDialog() { return ( Set your default visibility this.changeVisibility(value as Visibility) } > } label={`Public ${ this.state.federated ? "" : "(disabled by provider)" }`} disabled={!this.state.federated} /> } label={"Unlisted"} /> } label={"Private (followers only)"} /> } label={"Direct"} /> ); } showResetSettingsDialog() { return ( this.toggleResetSettingsDialog()} > Are you sure you want to refresh settings? ); } showResetDialog() { return ( this.toggleResetDialog()} > Reset {this.state.brandName}? Are you sure you want to reset {this.state.brandName}? You'll need to re-authorize {this.state.brandName}{" "} access again. ); } render() { const { classes } = this.props; return (
{this.state.currentUser ? (

{this.state.currentUser.display_name || this.state.currentUser.username} @{this.state.currentUser.acct}
) : (

{"Loading..."} @{"..."}
)}
{this.settingsList()} {this.showThemeDialog()} {this.showVisibilityDialog()} {this.showResetDialog()} {this.showResetSettingsDialog()}
); } } export default withStyles(styles)(withSnackbar(SettingsPage));