diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2021-09-13 13:33:54 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2021-09-13 13:33:54 +0200 |
| commit | 343fd4ff64150920f14de07c66385f5bb3fff9ed (patch) | |
| tree | 00cf12cabe0545acec7d9f457d8cdd30ffd2888f /gui/src/renderer | |
| parent | 8c7653b4f16d48d39154b149f9c49a0be5209caa (diff) | |
| parent | 6ab3a737364bfa5e593581d968081c5b86bdb372 (diff) | |
| download | mullvadvpn-343fd4ff64150920f14de07c66385f5bb3fff9ed.tar.xz mullvadvpn-343fd4ff64150920f14de07c66385f5bb3fff9ed.zip | |
Merge branch 'detect-macos-scrollbar-visibility'
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/app.tsx | 16 | ||||
| -rw-r--r-- | gui/src/renderer/components/CustomScrollbars.tsx | 42 | ||||
| -rw-r--r-- | gui/src/renderer/components/MacOsScrollbarDetection.tsx | 41 | ||||
| -rw-r--r-- | gui/src/renderer/components/NavigationBar.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/SelectLanguage.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/SelectLocation.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/SplitTunnelingSettings.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/redux/userinterface/actions.ts | 19 | ||||
| -rw-r--r-- | gui/src/renderer/redux/userinterface/reducers.ts | 6 |
9 files changed, 118 insertions, 24 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 3f65002340..cf7a354694 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -4,6 +4,7 @@ import { Router } from 'react-router'; import { bindActionCreators } from 'redux'; import AppRouter from './components/AppRouter'; +import MacOsScrollbarDetection from './components/MacOsScrollbarDetection'; import ErrorBoundary from './components/ErrorBoundary'; import { AppContext } from './context'; @@ -132,7 +133,7 @@ export default class AppRenderer { log.addOutput(new ConsoleOutput(LogLevel.debug)); log.addOutput(new IpcOutput(LogLevel.debug)); - IpcRendererEventChannel.windowShape.listen((windowShapeParams) => { + IpcRendererEventChannel.window.listenShape((windowShapeParams) => { if (typeof windowShapeParams.arrowPosition === 'number') { this.reduxActions.userInterface.updateWindowArrowPosition(windowShapeParams.arrowPosition); } @@ -199,10 +200,14 @@ export default class AppRenderer { this.reduxActions.settings.setSplitTunnelingApplications(applications); }); - IpcRendererEventChannel.windowFocus.listen((focus: boolean) => { + IpcRendererEventChannel.window.listenFocus((focus: boolean) => { this.reduxActions.userInterface.setWindowFocused(focus); }); + IpcRendererEventChannel.window.listenMacOsScrollbarVisibility((visibility) => { + this.reduxActions.userInterface.setMacOsScrollbarVisibility(visibility); + }); + IpcRendererEventChannel.navigation.listenReset(() => this.history.dismiss(true)); // Request the initial state from the main process @@ -237,6 +242,12 @@ export default class AppRenderer { this.storeAutoStart(initialState.autoStart); this.setWireguardPublicKey(initialState.wireguardPublicKey); + if (initialState.macOsScrollbarVisibility !== undefined) { + this.reduxActions.userInterface.setMacOsScrollbarVisibility( + initialState.macOsScrollbarVisibility, + ); + } + if (initialState.isConnected) { void this.onDaemonConnected(); } @@ -265,6 +276,7 @@ export default class AppRenderer { <Router history={this.history.asHistory}> <ErrorBoundary> <AppRouter /> + {window.env.platform === 'darwin' && <MacOsScrollbarDetection />} </ErrorBoundary> </Router> </Provider> diff --git a/gui/src/renderer/components/CustomScrollbars.tsx b/gui/src/renderer/components/CustomScrollbars.tsx index 3dacd26626..139c4381de 100644 --- a/gui/src/renderer/components/CustomScrollbars.tsx +++ b/gui/src/renderer/components/CustomScrollbars.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import styled from 'styled-components'; +import { MacOsScrollbarVisibility } from '../../shared/ipc-schema'; import { Scheduler } from '../../shared/scheduler'; +import { useSelector } from '../redux/store'; const ScrollableContent = styled.div({ display: 'flex', @@ -12,8 +14,8 @@ const ScrollableContent = styled.div({ const AUTOHIDE_TIMEOUT = 1000; interface IProps { - autoHide: boolean; - trackPadding: { x: number; y: number }; + autoHide?: boolean; + trackPadding?: { x: number; y: number }; onScroll?: (value: IScrollEvent) => void; className?: string; fillContainer?: boolean; @@ -44,10 +46,26 @@ interface IScrollbarUpdateContext { position: boolean; } -export default class CustomScrollbars extends React.Component<IProps, IState> { - public static defaultProps: IProps = { - // auto-hide on macOS by default - autoHide: window.env.platform === 'darwin', +export default React.forwardRef(function CustomScrollbarsContainer( + props: IProps, + forwardRef: React.Ref<CustomScrollbars>, +) { + const macOsScrollbarVisibility = useSelector( + (state) => state.userInterface.macOsScrollbarVisibility, + ); + const autoHide = + props.autoHide ?? + (window.env.platform === 'darwin' && + (macOsScrollbarVisibility === undefined || + macOsScrollbarVisibility === MacOsScrollbarVisibility.whenScrolling)); + + return <CustomScrollbars {...props} autoHide={autoHide} ref={forwardRef} />; +}); + +export type CustomScrollbarsRef = CustomScrollbars; + +class CustomScrollbars extends React.Component<IProps, IState> { + public static defaultProps: Partial<IProps> = { trackPadding: { x: 2, y: 2 }, }; @@ -161,8 +179,8 @@ export default class CustomScrollbars extends React.Component<IProps, IState> { return ( prevProps.children !== nextProps.children || prevProps.autoHide !== nextProps.autoHide || - prevProps.trackPadding.x !== nextProps.trackPadding.x || - prevProps.trackPadding.y !== nextProps.trackPadding.y || + prevProps.trackPadding?.x !== nextProps.trackPadding?.x || + prevProps.trackPadding?.y !== nextProps.trackPadding?.y || prevState.canScroll !== nextState.canScroll || prevState.showScrollIndicators !== nextState.showScrollIndicators || prevState.showTrack !== nextState.showTrack || @@ -350,7 +368,7 @@ export default class CustomScrollbars extends React.Component<IProps, IState> { // a thumb at the lowest point matches the bottom of scrollable view const thumbBoundary = this.computeTrackLength(scrollable) - thumb.clientHeight; const thumbTop = - pointInScrollContainer.y - this.state.dragStart.y - this.props.trackPadding.y; + pointInScrollContainer.y - this.state.dragStart.y - (this.props.trackPadding?.y ?? 0); const newScrollTop = (thumbTop / thumbBoundary) * maxScrollTop; scrollable.scrollTop = newScrollTop; @@ -410,7 +428,7 @@ export default class CustomScrollbars extends React.Component<IProps, IState> { } private computeTrackLength(scrollable: HTMLElement) { - return scrollable.offsetHeight - this.props.trackPadding.y * 2; + return scrollable.offsetHeight - (this.props.trackPadding?.y ?? 0) * 2; } // Computes the position of child element within scrollable container @@ -471,10 +489,10 @@ export default class CustomScrollbars extends React.Component<IProps, IState> { // calculate thumb position based on scroll progress and thumb boundary // adding vertical inset to adjust the thumb's appearance - const thumbPosition = thumbBoundary * scrollPosition + this.props.trackPadding.y; + const thumbPosition = thumbBoundary * scrollPosition + (this.props.trackPadding?.y ?? 0); return { - x: -this.props.trackPadding.x, + x: -(this.props.trackPadding?.x ?? 0), y: thumbPosition, }; } diff --git a/gui/src/renderer/components/MacOsScrollbarDetection.tsx b/gui/src/renderer/components/MacOsScrollbarDetection.tsx new file mode 100644 index 0000000000..69de63cc6a --- /dev/null +++ b/gui/src/renderer/components/MacOsScrollbarDetection.tsx @@ -0,0 +1,41 @@ +import React, { useEffect, useRef } from 'react'; +import styled from 'styled-components'; +import useActions from '../lib/actionsHook'; +import userInterface from '../redux/userinterface/actions'; +import { useSelector } from '../redux/store'; +import { MacOsScrollbarVisibility } from '../../shared/ipc-schema'; + +const StyledContainer = styled.div({ + position: 'absolute', + visibility: 'hidden', + overflowY: 'scroll', + overflowX: 'hidden', + width: '1px', + height: '0px', +}); + +// This component is used to determine wheter scrollbars should be always visible or only visible +// while scrolling when the system setting for this is set to "Automatic". This is detected by +// testing if any space is taken by a scrollbar. +export default function MacOsScrollbarDetection() { + const visibility = useSelector((state) => state.userInterface.macOsScrollbarVisibility); + const { setMacOsScrollbarVisibility } = useActions(userInterface); + const ref = useRef() as React.RefObject<HTMLDivElement>; + + useEffect(() => { + if (visibility === MacOsScrollbarVisibility.automatic) { + // If the width is 0 then the 1 px width of the parent has been used by the scrollbar. + const newVisibility = + ref.current?.offsetWidth === 0 + ? MacOsScrollbarVisibility.always + : MacOsScrollbarVisibility.whenScrolling; + setMacOsScrollbarVisibility(newVisibility); + } + }, [visibility]); + + return ( + <StyledContainer> + <div ref={ref} /> + </StyledContainer> + ); +} diff --git a/gui/src/renderer/components/NavigationBar.tsx b/gui/src/renderer/components/NavigationBar.tsx index 9af5a4b615..e02ae99387 100644 --- a/gui/src/renderer/components/NavigationBar.tsx +++ b/gui/src/renderer/components/NavigationBar.tsx @@ -6,7 +6,7 @@ import { useHistory } from '../lib/history'; import { useCombinedRefs } from '../lib/utilityHooks'; import { useSelector } from '../redux/store'; import userInterface from '../redux/userinterface/actions'; -import CustomScrollbars, { IScrollEvent } from './CustomScrollbars'; +import CustomScrollbars, { CustomScrollbarsRef, IScrollEvent } from './CustomScrollbars'; import { StyledBackBarItemButton, StyledBackBarItemIcon, @@ -112,12 +112,12 @@ interface INavigationScrollbarsProps { export const NavigationScrollbars = React.forwardRef(function NavigationScrollbarsT( props: INavigationScrollbarsProps, - forwardedRef?: React.Ref<CustomScrollbars>, + forwardedRef?: React.Ref<CustomScrollbarsRef>, ) { const history = useHistory(); const { onScroll } = useContext(NavigationScrollContext); - const ref = useRef<CustomScrollbars>(); + const ref = useRef<CustomScrollbarsRef>(); const combinedRefs = useCombinedRefs(forwardedRef, ref); const { addScrollPosition, removeScrollPosition } = useActions(userInterface); diff --git a/gui/src/renderer/components/SelectLanguage.tsx b/gui/src/renderer/components/SelectLanguage.tsx index 7c67927182..38d20b09e3 100644 --- a/gui/src/renderer/components/SelectLanguage.tsx +++ b/gui/src/renderer/components/SelectLanguage.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; import { AriaInputGroup } from './AriaGroup'; -import CustomScrollbars from './CustomScrollbars'; +import { CustomScrollbarsRef } from './CustomScrollbars'; import { Container, Layout } from './Layout'; import { BackBarItem, @@ -40,7 +40,7 @@ const StyledSelector = (styled(Selector)({ }) as unknown) as new <T>() => Selector<T>; export default class SelectLanguage extends React.Component<IProps, IState> { - private scrollView = React.createRef<CustomScrollbars>(); + private scrollView = React.createRef<CustomScrollbarsRef>(); private selectedCellRef = React.createRef<HTMLButtonElement>(); constructor(props: IProps) { diff --git a/gui/src/renderer/components/SelectLocation.tsx b/gui/src/renderer/components/SelectLocation.tsx index 2867ddaf45..528e8129ef 100644 --- a/gui/src/renderer/components/SelectLocation.tsx +++ b/gui/src/renderer/components/SelectLocation.tsx @@ -6,7 +6,7 @@ import { messages } from '../../shared/gettext'; import { IRelayLocationRedux } from '../redux/settings/reducers'; import { LocationScope } from '../redux/userinterface/reducers'; import BridgeLocations, { SpecialBridgeLocationType } from './BridgeLocations'; -import CustomScrollbars from './CustomScrollbars'; +import { CustomScrollbarsRef } from './CustomScrollbars'; import ExitLocations from './ExitLocations'; import ImageView from './ImageView'; import { Layout } from './Layout'; @@ -66,7 +66,7 @@ interface ISelectLocationSnapshot { export default class SelectLocation extends React.Component<IProps, IState> { public state = { showFilterMenu: false, headingHeight: 0 }; - private scrollView = React.createRef<CustomScrollbars>(); + private scrollView = React.createRef<CustomScrollbarsRef>(); private spacePreAllocationViewRef = React.createRef<SpacePreAllocationView>(); private selectedExitLocationRef = React.createRef<React.ReactInstance>(); private selectedBridgeLocationRef = React.createRef<React.ReactInstance>(); diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx index 64faf178f2..97a4e5189c 100644 --- a/gui/src/renderer/components/SplitTunnelingSettings.tsx +++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx @@ -11,7 +11,7 @@ import { IReduxState } from '../redux/store'; import Accordion from './Accordion'; import * as AppButton from './AppButton'; import * as Cell from './cell'; -import CustomScrollbars from './CustomScrollbars'; +import { CustomScrollbarsRef } from './CustomScrollbars'; import ImageView from './ImageView'; import { Layout } from './Layout'; import { ModalContainer, ModalAlert, ModalAlertType } from './Modal'; @@ -50,7 +50,7 @@ import { export default function SplitTunneling() { const { pop } = useHistory(); const [browsing, setBrowsing] = useState(false); - const scrollbarsRef = useRef() as React.RefObject<CustomScrollbars>; + const scrollbarsRef = useRef() as React.RefObject<CustomScrollbarsRef>; const scrollToTop = useCallback(() => scrollbarsRef.current?.scrollToTop(true), [scrollbarsRef]); diff --git a/gui/src/renderer/redux/userinterface/actions.ts b/gui/src/renderer/redux/userinterface/actions.ts index d0d1378a88..fd08dccd54 100644 --- a/gui/src/renderer/redux/userinterface/actions.ts +++ b/gui/src/renderer/redux/userinterface/actions.ts @@ -1,3 +1,4 @@ +import { MacOsScrollbarVisibility } from '../../../shared/ipc-schema'; import { LocationScope } from './reducers'; export interface IUpdateLocaleAction { @@ -35,6 +36,11 @@ export interface IRemoveScrollPosition { path: string; } +export interface ISetMacOsScrollbarVisibility { + type: 'SET_MACOS_SCROLLBAR_VISIBILITY'; + visibility: MacOsScrollbarVisibility; +} + export type UserInterfaceAction = | IUpdateLocaleAction | IUpdateWindowArrowPositionAction @@ -42,7 +48,8 @@ export type UserInterfaceAction = | ISetLocationScopeAction | ISetWindowFocusedAction | IAddScrollPosition - | IRemoveScrollPosition; + | IRemoveScrollPosition + | ISetMacOsScrollbarVisibility; function updateLocale(locale: string): IUpdateLocaleAction { return { @@ -93,6 +100,15 @@ function removeScrollPosition(path: string): IRemoveScrollPosition { }; } +function setMacOsScrollbarVisibility( + visibility: MacOsScrollbarVisibility, +): ISetMacOsScrollbarVisibility { + return { + type: 'SET_MACOS_SCROLLBAR_VISIBILITY', + visibility, + }; +} + export default { updateLocale, updateWindowArrowPosition, @@ -101,4 +117,5 @@ export default { setWindowFocused, addScrollPosition, removeScrollPosition, + setMacOsScrollbarVisibility, }; diff --git a/gui/src/renderer/redux/userinterface/reducers.ts b/gui/src/renderer/redux/userinterface/reducers.ts index 05e091797e..846007f20a 100644 --- a/gui/src/renderer/redux/userinterface/reducers.ts +++ b/gui/src/renderer/redux/userinterface/reducers.ts @@ -1,3 +1,4 @@ +import { MacOsScrollbarVisibility } from '../../../shared/ipc-schema'; import { ReduxAction } from '../store'; export enum LocationScope { @@ -12,6 +13,7 @@ export interface IUserInterfaceReduxState { locationScope: LocationScope; windowFocused: boolean; scrollPosition: Record<string, [number, number]>; + macOsScrollbarVisibility?: MacOsScrollbarVisibility; } const initialState: IUserInterfaceReduxState = { @@ -20,6 +22,7 @@ const initialState: IUserInterfaceReduxState = { locationScope: LocationScope.relay, windowFocused: false, scrollPosition: {}, + macOsScrollbarVisibility: undefined, }; export default function ( @@ -54,6 +57,9 @@ export default function ( return { ...state, scrollPosition }; } + case 'SET_MACOS_SCROLLBAR_VISIBILITY': + return { ...state, macOsScrollbarVisibility: action.visibility }; + default: return state; } |
