// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-License-Identifier: AGPL-3.0-only import * as React from "react"; import { List as ImmutableList } from "immutable"; import { connect } from "react-redux"; import { withRouter } from "react-router"; import { BrowserRouter } from "react-router-dom"; import { MuiThemeProvider as ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; // v1.x import amber from "@material-ui/core/colors/amber"; import lightBlue from "@material-ui/core/colors/lightBlue"; import AppBar from "@material-ui/core/AppBar"; import Toolbar from "@material-ui/core/Toolbar"; import Drawer from "@material-ui/core/Drawer"; import IconButton from "@material-ui/core/IconButton"; import Badge from "@material-ui/core/Badge"; import NavigationMenu from "@material-ui/icons/Menu"; import NavigationBack from "@material-ui/icons/ArrowBack"; import NavigationRefresh from "@material-ui/icons/Refresh"; import ErrorsIcon from "@material-ui/icons/Error"; import "react-virtualized/styles.css"; // only needs to be imported once import "./App.css"; import ConfirmationDialog from "./widgets/ConfirmationDialog"; import PrettyError from "./widgets/PrettyError"; import { List, ListItem } from "./widgets/List"; import withSpin from "./widgets/withSpin"; import ErrorBoundary from "./components/ErrorBoundary"; import SideMenu from "./SideMenu"; import LoginGate from "./LoginGate"; import { RouteResolver } from "./routes"; import * as store from "./store"; import * as actions from "./store/actions"; import { credentialsSelector } from "./login"; import { History } from "history"; export const routeResolver = new RouteResolver({ home: "", pim: { contacts: { _id: { _base: ":itemUid", edit: "edit", log: "log", }, new: "new", }, events: { _id: { _base: ":itemUid", edit: "edit", duplicate: "duplicate", log: "log", }, new: "new", }, tasks: { _id: { _base: ":itemUid", edit: "edit", log: "log", }, new: "new", }, }, journals: { _id: { _base: ":journalUid", edit: "edit", items: { _id: { _base: ":itemUid", }, }, entries: { _id: { _base: ":entryUid", }, }, members: { }, }, new: "new", import: "import", }, settings: { }, debug: { }, }); const AppBarWitHistory = withRouter( class extends React.PureComponent { public props: { title: string; toggleDrawerIcon: any; history?: History; staticContext?: any; iconElementRight: any; }; constructor(props: any) { super(props); this.goBack = this.goBack.bind(this); this.canGoBack = this.canGoBack.bind(this); } public render() { const { staticContext, toggleDrawerIcon, history, iconElementRight, ...props } = this.props; return ( {!this.canGoBack() ? toggleDrawerIcon : } {iconElementRight} ); } private canGoBack() { return ( (this.props.history!.length > 1) && (this.props.history!.location.pathname !== routeResolver.getRoute("pim")) && (this.props.history!.location.pathname !== routeResolver.getRoute("home")) ); } private goBack() { this.props.history!.goBack(); } } ); const IconRefreshWithSpin = withSpin(NavigationRefresh); class App extends React.PureComponent { public state: { drawerOpen: boolean; errorsDialog: boolean; }; public props: { credentials: store.CredentialsData; entries: store.EntriesData; fetchCount: number; darkMode: boolean; errors: ImmutableList; }; constructor(props: any) { super(props); this.state = { drawerOpen: false, errorsDialog: false }; this.toggleDrawer = this.toggleDrawer.bind(this); this.closeDrawer = this.closeDrawer.bind(this); this.refresh = this.refresh.bind(this); this.autoRefresh = this.autoRefresh.bind(this); } public render() { const credentials = this.props.credentials ?? null; const { darkMode } = this.props; const errors = this.props.errors; const fetching = this.props.fetchCount > 0; const muiTheme = createMuiTheme({ palette: { type: darkMode ? "dark" : undefined, primary: amber, secondary: { light: lightBlue.A200, main: lightBlue.A400, dark: lightBlue.A700, contrastText: "#fff", }, }, }); const styles = { main: { backgroundColor: muiTheme.palette.background.default, color: muiTheme.palette.text.primary, flexGrow: 1, }, }; return ( } iconElementRight={ <> {(errors.size > 0) && ( this.setState({ errorsDialog: true })} title="Parse Errors"> )} > } /> this.setState({ errorsDialog: false })} onOk={() => this.setState({ errorsDialog: false })} > This should not happen, please contact developers! {errors.map((error, index) => ( (window as any).navigator.clipboard.writeText(`${error.message}\n\n${error.stack}`)} > ))} ); } private toggleDrawer() { this.setState({ drawerOpen: !this.state.drawerOpen }); } private closeDrawer() { this.setState({ drawerOpen: false }); } private refresh() { store.store.dispatch(actions.fetchAll(this.props.credentials, this.props.entries)); } private autoRefresh() { if (navigator.onLine && this.props.credentials && !(window.location.pathname.match(/.*\/(new|edit|duplicate)$/))) { this.refresh(); } } componentDidMount() { const interval = 60 * 1000; setInterval(this.autoRefresh, interval); } } const mapStateToProps = (state: store.StoreState) => { return { credentials: credentialsSelector(state), entries: state.cache.entries, fetchCount: state.fetchCount, darkMode: !!state.settings.darkMode, errors: state.errors, }; }; export default connect( mapStateToProps )(App as any);