summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/renderer/components')
-rw-r--r--gui/src/renderer/components/KeyboardNavigation.tsx41
-rw-r--r--gui/src/renderer/components/NavigationBar.tsx11
-rw-r--r--gui/src/renderer/components/NotificationArea.tsx97
-rw-r--r--gui/src/renderer/components/NotificationBanner.tsx37
-rw-r--r--gui/src/renderer/components/Settings.tsx2
-rw-r--r--gui/src/renderer/components/select-location/SelectLocation.tsx2
6 files changed, 141 insertions, 49 deletions
diff --git a/gui/src/renderer/components/KeyboardNavigation.tsx b/gui/src/renderer/components/KeyboardNavigation.tsx
index 8e8bae5faf..b98f57b4ce 100644
--- a/gui/src/renderer/components/KeyboardNavigation.tsx
+++ b/gui/src/renderer/components/KeyboardNavigation.tsx
@@ -11,7 +11,7 @@ interface IKeyboardNavigationProps {
// Listens for and handles keyboard shortcuts
export default function KeyboardNavigation(props: IKeyboardNavigationProps) {
const history = useHistory();
- const [backAction, setBackAction] = useState<IBackActionConfiguration>();
+ const [backAction, setBackAction] = useState<BackActionFn>();
const location = useLocation();
const handleKeyDown = useCallback(
@@ -22,7 +22,7 @@ export default function KeyboardNavigation(props: IKeyboardNavigationProps) {
if (event.shiftKey) {
history.pop(true);
} else {
- backAction?.action();
+ backAction?.();
}
}
}
@@ -38,18 +38,12 @@ export default function KeyboardNavigation(props: IKeyboardNavigationProps) {
return <BackActionTracker registerBackAction={setBackAction}>{props.children}</BackActionTracker>;
}
-type BackActionIcon = 'back' | 'close';
type BackActionFn = () => void;
-interface IBackActionConfiguration {
- icon: BackActionIcon;
- action: BackActionFn;
-}
-
interface IBackActionContext {
- parentBackAction?: IBackActionConfiguration;
- registerBackAction: (backAction: IBackActionConfiguration) => void;
- removeBackAction: (backAction: IBackActionConfiguration) => void;
+ parentBackAction?: BackActionFn;
+ registerBackAction: (backAction: BackActionFn) => void;
+ removeBackAction: (backAction: BackActionFn) => void;
}
export const BackActionContext = React.createContext<IBackActionContext>({
@@ -63,7 +57,6 @@ export const BackActionContext = React.createContext<IBackActionContext>({
interface IBackActionProps {
disabled?: boolean;
- icon?: BackActionIcon;
action: BackActionFn;
children: React.ReactNode;
}
@@ -72,13 +65,9 @@ interface IBackActionProps {
// either by pressing the back button in the navigation bar or by pressing escape.
export function BackAction(props: IBackActionProps) {
const backActionContext = useContext(BackActionContext);
- const [childrenBackAction, setChildrenBackAction] = useState<IBackActionConfiguration>();
+ const [childrenBackAction, setChildrenBackAction] = useState<BackActionFn>();
- const parentBackAction = useMemo<IBackActionConfiguration>(
- () => ({ icon: props.icon ?? 'back', action: props.action }),
- [props.icon, props.action],
- );
- const backActionConfiguration = childrenBackAction ?? parentBackAction;
+ const backActionConfiguration = childrenBackAction ?? props.action;
// Every time the action or the disabled property changes the action needs to be reregistered.
useEffect((): (() => void) | void => {
@@ -91,29 +80,27 @@ export function BackAction(props: IBackActionProps) {
// Every back action keeps track of the back actions in its subtree. This makes it possible to
// always use the action furthest down in the tree.
return (
- <BackActionTracker
- registerBackAction={setChildrenBackAction}
- parentBackAction={parentBackAction}>
+ <BackActionTracker registerBackAction={setChildrenBackAction} parentBackAction={props.action}>
{props.children}
</BackActionTracker>
);
}
interface IBackActionTracker {
- parentBackAction?: IBackActionConfiguration;
- registerBackAction: (backAction: IBackActionConfiguration | undefined) => void;
+ parentBackAction?: BackActionFn;
+ registerBackAction: (backAction: BackActionFn | undefined) => void;
children: React.ReactNode;
}
// This component keeps track of all registered back actions in it's subtree and reports one of them
// to it's parent.
function BackActionTracker(props: IBackActionTracker) {
- const [backActions, setBackActions] = useState<Array<IBackActionConfiguration>>([]);
+ const [backActions, setBackActions] = useState<Array<BackActionFn>>([]);
- const registerBackAction = useCallback((backAction: IBackActionConfiguration) => {
+ const registerBackAction = useCallback((backAction: BackActionFn) => {
setBackActions((backActions) => [...backActions, backAction]);
}, []);
- const removeBackAction = useCallback((backAction: IBackActionConfiguration) => {
+ const removeBackAction = useCallback((backAction: BackActionFn) => {
setBackActions((backActions) => backActions.filter((action) => action !== backAction));
}, []);
const backActionContext = useMemo(
@@ -121,7 +108,7 @@ function BackActionTracker(props: IBackActionTracker) {
[backActions],
);
- useEffect(() => props.registerBackAction(backActions.at(0)), [backActions]);
+ useEffect(() => props.registerBackAction(() => backActions.at(0)), [backActions]);
return (
<BackActionContext.Provider value={backActionContext}>
diff --git a/gui/src/renderer/components/NavigationBar.tsx b/gui/src/renderer/components/NavigationBar.tsx
index 317a9bd26e..56c3d2b8a3 100644
--- a/gui/src/renderer/components/NavigationBar.tsx
+++ b/gui/src/renderer/components/NavigationBar.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef } from 'react';
+import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import { colors } from '../../config.json';
import { messages } from '../../shared/gettext';
@@ -184,13 +184,14 @@ export const TitleBarItem = React.memo(function TitleBarItemT(props: ITitleBarIt
});
export function BackBarItem() {
+ const history = useHistory();
+ const backIcon = useMemo(() => history.length > 2, []);
const { parentBackAction } = useContext(BackActionContext);
- const iconSource = parentBackAction?.icon === 'back' ? 'icon-back' : 'icon-close-down';
- const ariaLabel =
- parentBackAction?.icon === 'back' ? messages.gettext('Back') : messages.gettext('Close');
+ const iconSource = backIcon ? 'icon-back' : 'icon-close-down';
+ const ariaLabel = backIcon ? messages.gettext('Back') : messages.gettext('Close');
return (
- <StyledBackBarItemButton aria-label={ariaLabel} onClick={parentBackAction?.action}>
+ <StyledBackBarItemButton aria-label={ariaLabel} onClick={parentBackAction}>
<StyledBackBarItemIcon source={iconSource} tintColor={colors.white40} width={24} />
</StyledBackBarItemButton>
);
diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx
index 317cc1e923..4923bacc7c 100644
--- a/gui/src/renderer/components/NotificationArea.tsx
+++ b/gui/src/renderer/components/NotificationArea.tsx
@@ -1,21 +1,29 @@
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
+import styled from 'styled-components';
+import { colors } from '../../config.json';
+import { messages } from '../../shared/gettext';
import log from '../../shared/logging';
import {
BlockWhenDisconnectedNotificationProvider,
CloseToAccountExpiryNotificationProvider,
ConnectingNotificationProvider,
ErrorNotificationProvider,
+ InAppNotificationAction,
InAppNotificationProvider,
+ InAppNotificationTroubleshootInfo,
InconsistentVersionNotificationProvider,
- NotificationAction,
ReconnectingNotificationProvider,
UnsupportedVersionNotificationProvider,
UpdateAvailableNotificationProvider,
} from '../../shared/notifications/notification';
import { useAppContext } from '../context';
+import { transitions, useHistory } from '../lib/history';
+import { RoutePath } from '../lib/routes';
import { IReduxState } from '../redux/store';
+import * as AppButton from './AppButton';
+import { ModalAlert, ModalAlertType, ModalMessage } from './Modal';
import {
NotificationActions,
NotificationBanner,
@@ -24,6 +32,7 @@ import {
NotificationOpenLinkAction,
NotificationSubtitle,
NotificationTitle,
+ NotificationTroubleshootDialogAction,
} from './NotificationBanner';
interface IProps {
@@ -92,24 +101,92 @@ export default function NotificationArea(props: IProps) {
return <NotificationBanner className={props.className} aria-hidden={true} />;
}
+const TroubleshootList = styled.ul({
+ listStyle: 'disc outside',
+ paddingLeft: '20px',
+ color: colors.white80,
+});
+
interface INotificationActionWrapperProps {
- action: NotificationAction;
+ action: InAppNotificationAction;
}
function NotificationActionWrapper(props: INotificationActionWrapperProps) {
+ const history = useHistory();
const { openLinkWithAuth, openUrl } = useAppContext();
+ const [troubleshootInfo, setTroubleshootInfo] = useState<InAppNotificationTroubleshootInfo>();
const handleClick = useCallback(() => {
- if (props.action.withAuth) {
- return openLinkWithAuth(props.action.url);
- } else {
- return openUrl(props.action.url);
+ if (props.action) {
+ switch (props.action.type) {
+ case 'open-url':
+ if (props.action.withAuth) {
+ return openLinkWithAuth(props.action.url);
+ } else {
+ return openUrl(props.action.url);
+ }
+ case 'troubleshoot-dialog':
+ setTroubleshootInfo(props.action.troubleshoot);
+ break;
+ }
}
+
+ return Promise.resolve();
+ }, [props.action]);
+
+ const goToProblemReport = useCallback(() => {
+ setTroubleshootInfo(undefined);
+ history.push(RoutePath.problemReport, { transition: transitions.show });
}, []);
+ const closeTroubleshootInfo = useCallback(() => setTroubleshootInfo(undefined), []);
+
+ let actionComponent: React.ReactElement | undefined;
+ if (props.action) {
+ switch (props.action.type) {
+ case 'open-url':
+ actionComponent = <NotificationOpenLinkAction onClick={handleClick} />;
+ break;
+ case 'troubleshoot-dialog':
+ actionComponent = (
+ <>
+ <NotificationTroubleshootDialogAction onClick={handleClick} />
+ </>
+ );
+ break;
+ }
+ }
+
return (
- <NotificationActions>
- <NotificationOpenLinkAction onClick={handleClick} />
- </NotificationActions>
+ <>
+ <NotificationActions>{actionComponent}</NotificationActions>
+ <ModalAlert
+ isOpen={troubleshootInfo !== undefined}
+ type={ModalAlertType.info}
+ buttons={[
+ <AppButton.GreenButton key="problem-report" onClick={goToProblemReport}>
+ {messages.pgettext('in-app-notifications', 'Send problem report')}
+ </AppButton.GreenButton>,
+ <AppButton.BlueButton key="back" onClick={closeTroubleshootInfo}>
+ {messages.gettext('Back')}
+ </AppButton.BlueButton>,
+ ]}
+ close={closeTroubleshootInfo}>
+ <ModalMessage>{troubleshootInfo?.details}</ModalMessage>
+ <ModalMessage>
+ <TroubleshootList>
+ {troubleshootInfo?.steps.map((step) => (
+ <li key={step}>{step}</li>
+ ))}
+ </TroubleshootList>
+ </ModalMessage>
+ <ModalMessage>
+ {messages.pgettext(
+ 'troubleshoot',
+ 'If these steps do not work please send a problem report.',
+ )}
+ </ModalMessage>
+ </ModalAlert>
+ </>
);
}
diff --git a/gui/src/renderer/components/NotificationBanner.tsx b/gui/src/renderer/components/NotificationBanner.tsx
index 9f8c63e4b2..4f20d315c8 100644
--- a/gui/src/renderer/components/NotificationBanner.tsx
+++ b/gui/src/renderer/components/NotificationBanner.tsx
@@ -26,7 +26,7 @@ export function NotificationSubtitle(props: INotificationSubtitleProps) {
return React.Children.count(props.children) > 0 ? <NotificationSubtitleText {...props} /> : null;
}
-export const NotificationOpenLinkActionButton = styled(AppButton.SimpleButton)({
+export const NotificationActionButton = styled(AppButton.SimpleButton)({
flex: 1,
justifyContent: 'center',
cursor: 'default',
@@ -36,20 +36,25 @@ export const NotificationOpenLinkActionButton = styled(AppButton.SimpleButton)({
});
export const NotificationOpenLinkActionIcon = styled(ImageView)({
- [NotificationOpenLinkActionButton + ':hover &']: {
+ [NotificationActionButton + ':hover &']: {
+ backgroundColor: colors.white80,
+ },
+});
+
+export const NotificationTroubleshootDialogActionIcon = styled(ImageView)({
+ [NotificationActionButton + ':hover &']: {
backgroundColor: colors.white80,
},
});
interface INotifcationOpenLinkActionProps {
onClick: () => Promise<void>;
- children?: React.ReactNode;
}
export function NotificationOpenLinkAction(props: INotifcationOpenLinkActionProps) {
return (
<AppButton.BlockingButton onClick={props.onClick}>
- <NotificationOpenLinkActionButton
+ <NotificationActionButton
aria-describedby={NOTIFICATION_AREA_ID}
aria-label={messages.gettext('Open URL')}>
<NotificationOpenLinkActionIcon
@@ -58,11 +63,33 @@ export function NotificationOpenLinkAction(props: INotifcationOpenLinkActionProp
tintColor={colors.white60}
source="icon-extLink"
/>
- </NotificationOpenLinkActionButton>
+ </NotificationActionButton>
</AppButton.BlockingButton>
);
}
+interface INotifcationTroubleshootDialogActionProps {
+ onClick: () => Promise<void>;
+}
+
+export function NotificationTroubleshootDialogAction(
+ props: INotifcationTroubleshootDialogActionProps,
+) {
+ return (
+ <NotificationActionButton
+ aria-describedby={NOTIFICATION_AREA_ID}
+ aria-label={messages.gettext('Troubleshoot')}
+ onClick={props.onClick}>
+ <NotificationOpenLinkActionIcon
+ height={12}
+ width={12}
+ tintColor={colors.white60}
+ source="icon-info"
+ />
+ </NotificationActionButton>
+ );
+}
+
export const NotificationContent = styled.div.attrs({ id: NOTIFICATION_AREA_ID })({
display: 'flex',
flexDirection: 'column',
diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx
index 76b4d6b4fd..1a316f81bd 100644
--- a/gui/src/renderer/components/Settings.tsx
+++ b/gui/src/renderer/components/Settings.tsx
@@ -38,7 +38,7 @@ export default function Support() {
const showSubSettings = loginState.type === 'ok' && connectedToDaemon;
return (
- <BackAction icon="close" action={history.pop}>
+ <BackAction action={history.pop}>
<Layout>
<SettingsContainer>
<NavigationContainer>
diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx
index 9e9c60b7a5..bc397506a8 100644
--- a/gui/src/renderer/components/select-location/SelectLocation.tsx
+++ b/gui/src/renderer/components/select-location/SelectLocation.tsx
@@ -117,7 +117,7 @@ export default function SelectLocation() {
const showProvidersFilter = providers.length > 0;
const showFilters = showOwnershipFilter || showProvidersFilter;
return (
- <BackAction icon="close" action={onClose}>
+ <BackAction action={onClose}>
<Layout>
<SettingsContainer>
<NavigationContainer>