From 93f135e93b6e8154961505187c7e73c314a427f5 Mon Sep 17 00:00:00 2001 From: Marquis Kurt Date: Wed, 11 Dec 2019 12:27:42 -0500 Subject: [PATCH 1/4] Create basic back button (HD-28) --- src/components/AppLayout/AppLayout.styles.tsx | 4 ++++ src/components/AppLayout/AppLayout.tsx | 13 +++++++++++++ src/utilities/appbar.tsx | 17 +++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/components/AppLayout/AppLayout.styles.tsx b/src/components/AppLayout/AppLayout.styles.tsx index 8c9824d..7f8116c 100644 --- a/src/components/AppLayout/AppLayout.styles.tsx +++ b/src/components/AppLayout/AppLayout.styles.tsx @@ -57,6 +57,10 @@ export const styles = (theme: Theme) => display: "none" } }, + appBarBackButton: { + marginLeft: -12, + marginRight: 20 + }, appBarTitle: { display: "none", [theme.breakpoints.up("md")]: { diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index a3f89d5..ef4ef34 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -44,6 +44,7 @@ import SupervisedUserCircleIcon from "@material-ui/icons/SupervisedUserCircle"; import ExitToAppIcon from "@material-ui/icons/ExitToApp"; import TrendingUpIcon from "@material-ui/icons/TrendingUp"; import BuildIcon from "@material-ui/icons/Build"; +import ArrowBackIcon from "@material-ui/icons/ArrowBack"; import { styles } from "./AppLayout.styles"; import { MultiAccount, UAccount } from "../../types/Account"; @@ -67,6 +68,7 @@ import { getAccountRegistry, removeAccountFromRegistry } from "../../utilities/accounts"; +import { isChildView } from "../../utilities/appbar"; interface IAppLayoutState { acctMenuOpen: boolean; @@ -484,6 +486,17 @@ export class AppLayout extends Component { {this.titlebar()} + {isChildView(window.location.hash) ? ( + window.history.back()} + > + + + ) : null} + { + if (hash.includes(childViewProtocol)) { + console.log(childViewProtocol); + foundProtocol = true; + } + }); + + return foundProtocol; +} From e761dc4f97abb55a62e4297520e0c80fed1b9580 Mon Sep 17 00:00:00 2001 From: Marquis Kurt Date: Wed, 11 Dec 2019 12:55:38 -0500 Subject: [PATCH 2/4] Rely on just window history --- src/components/AppLayout/AppLayout.tsx | 7 +++++-- src/utilities/appbar.tsx | 17 ----------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index ef4ef34..3660ae8 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -68,7 +68,6 @@ import { getAccountRegistry, removeAccountFromRegistry } from "../../utilities/accounts"; -import { isChildView } from "../../utilities/appbar"; interface IAppLayoutState { acctMenuOpen: boolean; @@ -223,6 +222,10 @@ export class AppLayout extends Component { }); } + canGoBack(): boolean { + return window.history.length > 1; + } + toggleAcctMenu() { this.setState({ acctMenuOpen: !this.state.acctMenuOpen }); } @@ -486,7 +489,7 @@ export class AppLayout extends Component { {this.titlebar()} - {isChildView(window.location.hash) ? ( + {isDesktopApp() && this.canGoBack() ? ( { - if (hash.includes(childViewProtocol)) { - console.log(childViewProtocol); - foundProtocol = true; - } - }); - - return foundProtocol; -} From 2ffa744e528a5bd8e8d69e3bf48949172e61fcb5 Mon Sep 17 00:00:00 2001 From: Marquis Kurt Date: Wed, 11 Dec 2019 13:21:28 -0500 Subject: [PATCH 3/4] Re-instate original idea --- src/components/AppLayout/AppLayout.tsx | 3 ++- src/utilities/appbar.tsx | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 3660ae8..a142bb3 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -68,6 +68,7 @@ import { getAccountRegistry, removeAccountFromRegistry } from "../../utilities/accounts"; +import { isChildView } from "../../utilities/appbar"; interface IAppLayoutState { acctMenuOpen: boolean; @@ -489,7 +490,7 @@ export class AppLayout extends Component { {this.titlebar()} - {isDesktopApp() && this.canGoBack() ? ( + {isDesktopApp() && isChildView() ? ( { + if (path.startsWith(childViewProtocol)) { + protocolMatched = true; + } + }); + return protocolMatched; +} From 774e2eb817fe9246647c946312504eca63f41847 Mon Sep 17 00:00:00 2001 From: Marquis Kurt Date: Wed, 11 Dec 2019 13:37:42 -0500 Subject: [PATCH 4/4] Document AppLayout and pass in window location hash --- src/components/AppLayout/AppLayout.tsx | 110 +++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index a142bb3..3b82fa7 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -70,29 +70,79 @@ import { } from "../../utilities/accounts"; import { isChildView } from "../../utilities/appbar"; +/** + * The pre-define state interface for the app layout. + */ interface IAppLayoutState { + /** + * Whether the account menu is open or not. + */ acctMenuOpen: boolean; + + /** + * Whether the drawer is open (mobile-only). + */ drawerOpenOnMobile: boolean; + + /** + * The current user signed in. + */ currentUser?: UAccount; + + /** + * The number of notifications received. + */ notificationCount: number; + + /** + * Whether the log out dialog is open. + */ logOutOpen: boolean; + + /** + * Whether federation has been enabled in the config. + */ enableFederation?: boolean; + + /** + * The brand name of the app, if not "Hyperspace". + */ brandName?: string; + + /** + * Whether the app is in development mode. + */ developerMode?: boolean; } +/** + * The base app layout class. Responsible for the search bar, navigation menus, etc. + */ export class AppLayout extends Component { + /** + * The Mastodon client to operate with. + */ client: Mastodon; + + /** + * A stream listener to listen for new streaming events from Mastodon. + */ streamListener: any; + /** + * Construct the app layout. + * @param props The properties to pass in. + */ constructor(props: any) { super(props); + // Create the Mastodon client this.client = new Mastodon( localStorage.getItem("access_token") as string, (localStorage.getItem("baseurl") as string) + "/api/v1" ); + // Initialize the state this.state = { drawerOpenOnMobile: false, acctMenuOpen: false, @@ -100,14 +150,20 @@ export class AppLayout extends Component { logOutOpen: false }; + // Bind functions as properties to this class for reference this.toggleDrawerOnMobile = this.toggleDrawerOnMobile.bind(this); this.toggleAcctMenu = this.toggleAcctMenu.bind(this); this.clearBadge = this.clearBadge.bind(this); } + /** + * Run post-mount tasks such as getting account data and refreshing the config file. + */ componentDidMount() { + // Get the account data. this.getAccountData(); + // Read the config file and then update the state. getConfig().then((result: any) => { if (result !== undefined) { let config: Config = result; @@ -121,18 +177,25 @@ export class AppLayout extends Component { } }); + // Listen for notifications. this.streamNotifications(); } + /** + * Get updated credentials from Mastodon or pull information from local storage. + */ getAccountData() { + // Try to get updated credentials from Mastodon. this.client .get("/accounts/verify_credentials") .then((resp: any) => { + // Update the account if possible. let data: UAccount = resp.data; this.setState({ currentUser: data }); sessionStorage.setItem("id", data.id); }) .catch((err: Error) => { + // Otherwise, pull from local storage. this.props.enqueueSnackbar( "Couldn't find profile info: " + err.name ); @@ -142,9 +205,14 @@ export class AppLayout extends Component { }); } + /** + * Set up a stream listener and listen for notifications. + */ streamNotifications() { + // Set up the stream listener. this.streamListener = this.client.stream("/streaming/user"); + // Set the count if the user asked to display the total count. if (getUserDefaultBool("displayAllOnNotificationBadge")) { this.client.get("/notifications").then((resp: any) => { let notifArray = resp.data; @@ -152,14 +220,17 @@ export class AppLayout extends Component { }); } + // Listen for notifications. this.streamListener.on("notification", (notif: Notification) => { const notificationCount = this.state.notificationCount + 1; this.setState({ notificationCount }); + // Update the badge on the desktop. if (isDesktopApp()) { getElectronApp().setBadgeCount(notificationCount); } + // Set up a push notification if the window isn't in focus. if (!document.hasFocus()) { let primaryMessage = ""; let secondaryMessage = ""; @@ -218,29 +289,39 @@ export class AppLayout extends Component { break; } + // Respectfully send the notification request. sendNotificationRequest(primaryMessage, secondaryMessage); } }); } - canGoBack(): boolean { - return window.history.length > 1; - } - + /** + * Toggle the account menu. + */ toggleAcctMenu() { this.setState({ acctMenuOpen: !this.state.acctMenuOpen }); } + /** + * Toggle the app drawer, if on mobile. + */ toggleDrawerOnMobile() { this.setState({ drawerOpenOnMobile: !this.state.drawerOpenOnMobile }); } + /** + * Toggle the logout dialog. + */ toggleLogOutDialog() { this.setState({ logOutOpen: !this.state.logOutOpen }); } + /** + * Perform a search and redirect to the search page. + * @param what The query input from the search box + */ searchForQuery(what: string) { what = what.replace(/^#/g, "tag:"); console.log(what); @@ -249,9 +330,13 @@ export class AppLayout extends Component { : "/#/search?query=" + what; } + /** + * Clear login information, remove the account from the registry, and reload the web page. + */ logOutAndRestart() { let loginData = localStorage.getItem("login"); if (loginData) { + // Remove account from the registry. let registry = getAccountRegistry(); registry.forEach((registryItem: MultiAccount, index: number) => { @@ -263,15 +348,20 @@ export class AppLayout extends Component { } }); + // Clear some of the local storage fields. let items = ["login", "account", "baseurl", "access_token"]; items.forEach(entry => { localStorage.removeItem(entry); }); + // Finally, reload. window.location.reload(); } } + /** + * Clear the notifications badge. + */ clearBadge() { if (!getUserDefaultBool("displayAllOnNotificationBadge")) { this.setState({ notificationCount: 0 }); @@ -282,6 +372,9 @@ export class AppLayout extends Component { } } + /** + * Render the title bar. + */ titlebar() { const { classes } = this.props; if (isDarwinApp()) { @@ -313,6 +406,9 @@ export class AppLayout extends Component { } } + /** + * Render the app drawer. On the desktop, this appears as a sidebar in larger layouts. + */ appDrawer() { const { classes } = this.props; return ( @@ -482,6 +578,9 @@ export class AppLayout extends Component { ); } + /** + * Render the entire layout. + */ render() { const { classes } = this.props; return ( @@ -490,7 +589,8 @@ export class AppLayout extends Component { {this.titlebar()} - {isDesktopApp() && isChildView() ? ( + {isDesktopApp() && + isChildView(window.location.hash) ? (