Move title bar functionality to rail (#1816)

Summary:
Pull Request resolved: https://github.com/facebook/flipper/pull/1816

Now that Sandy is the default in OSS builds as well, we can remove the temporarily title bar and switch to small topbar windows in Electron.

This diff removes any remaining elements in the titlebar

- version number went into the title bar
- the update check warning is shown on top of the bottom section of the left rail (orange triangle)
- the development only perf graphs are moved to the left bar as well and are now aligned vertically.

Reviewed By: jknoxville

Differential Revision: D25805957

fbshipit-source-id: fba4b60c246b8f5d99a93087af31af9ac55defe8
This commit is contained in:
Michel Weststrate
2021-01-18 06:45:17 -08:00
committed by Facebook GitHub Bot
parent bd6c5c0f71
commit ffeb47ed75
10 changed files with 84 additions and 117 deletions

View File

@@ -10,20 +10,15 @@
import React, {useEffect, useRef} from 'react'; import React, {useEffect, useRef} from 'react';
import {fpsEmitter} from '../dispatcher/tracking'; import {fpsEmitter} from '../dispatcher/tracking';
export default function FpsGraph({ const width = 36;
width, const height = 36;
height, const graphHeight = 20;
sampleRate = 200,
}: { export default function FpsGraph({sampleRate = 200}: {sampleRate?: number}) {
width: number;
height: number;
sampleRate?: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => { useEffect(() => {
const graphWidth = width - 20; const fps: number[] = new Array<number>(width).fill(0, 0, width);
const fps: number[] = new Array<number>(graphWidth).fill(0, 0, graphWidth);
let lastFps = 0; let lastFps = 0;
let lastDraw = Date.now(); let lastDraw = Date.now();
@@ -35,7 +30,7 @@ export default function FpsGraph({
const interval = setInterval(() => { const interval = setInterval(() => {
const ctx = canvasRef.current!.getContext('2d')!; const ctx = canvasRef.current!.getContext('2d')!;
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = '#ccc'; ctx.strokeStyle = '#ddd';
const now = Date.now(); const now = Date.now();
let missedFrames = 0; let missedFrames = 0;
@@ -51,24 +46,26 @@ export default function FpsGraph({
fps.push(lastFps); fps.push(lastFps);
fps.shift(); fps.shift();
ctx.font = 'lighter 10px arial';
ctx.strokeText( ctx.strokeText(
'' + '' +
(missedFrames (missedFrames
? // if we were chocked, show FPS based on frames missed ? // if we were chocked, show FPS based on frames missed
Math.floor((1000 / sampleRate) * missedFrames) Math.floor((1000 / sampleRate) * missedFrames)
: lastFps), : lastFps) +
width - 15, ' fps',
5 + height / 2, 0,
height - 4,
); );
ctx.beginPath();
ctx.moveTo(0, height); ctx.moveTo(0, height);
ctx.beginPath();
ctx.lineWidth = 1; ctx.lineWidth = 1;
fps.forEach((num, idx) => { fps.forEach((num, idx) => {
ctx.lineTo(idx, height - (Math.min(60, num) / 60) * height); ctx.lineTo(idx, graphHeight - (Math.min(60, num) / 60) * graphHeight);
}); });
ctx.strokeStyle = missedFrames ? '#ff0000' : '#ccc'; ctx.strokeStyle = missedFrames ? '#ff0000' : '#ddd';
ctx.stroke(); ctx.stroke();
lastFps = 60; lastFps = 60;

View File

@@ -10,13 +10,10 @@
import React, {useEffect, useRef, useState} from 'react'; import React, {useEffect, useRef, useState} from 'react';
import {onBytesReceived} from '../dispatcher/tracking'; import {onBytesReceived} from '../dispatcher/tracking';
export default function NetworkGraph({ const height = 16;
width, const width = 36;
height,
}: { export default function NetworkGraph() {
width: number;
height: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const lastTime = useRef(performance.now()); const lastTime = useRef(performance.now());
const lastBytes = useRef(0); const lastBytes = useRef(0);
@@ -46,9 +43,9 @@ export default function NetworkGraph({
const ctx = canvasRef.current!.getContext('2d')!; const ctx = canvasRef.current!.getContext('2d')!;
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = kiloBytesPerSecond >= 1000 ? '#f00' : '#ccc'; ctx.strokeStyle = kiloBytesPerSecond >= 1000 ? '#f00' : '#ddd';
ctx.textAlign = 'end'; ctx.font = 'lighter 10px arial';
ctx.strokeText(`${kiloBytesPerSecond} kB/s`, width - 5, 5 + height / 2); ctx.strokeText(`${kiloBytesPerSecond} kB/s`, 0, height - 4);
setHoverText( setHoverText(
'Total data traffic per plugin:\n\n' + 'Total data traffic per plugin:\n\n' +

View File

@@ -35,15 +35,13 @@ import {LocationsButton} from './LocationsButton';
import ScreenCaptureButtons from './ScreenCaptureButtons'; import ScreenCaptureButtons from './ScreenCaptureButtons';
import UpdateIndicator from './UpdateIndicator'; import UpdateIndicator from './UpdateIndicator';
import config from '../fb-stubs/config'; import config from '../fb-stubs/config';
import isProduction from '../utils/isProduction';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
import React from 'react'; import React from 'react';
import {State} from '../reducers'; import {State} from '../reducers';
import {reportUsage} from '../utils/metrics'; import {reportUsage} from '../utils/metrics';
import FpsGraph from './FpsGraph';
import NetworkGraph from './NetworkGraph';
import MetroButton from './MetroButton'; import MetroButton from './MetroButton';
import {navPluginStateSelector} from './LocationsButton'; import {navPluginStateSelector} from './LocationsButton';
import {getVersionString} from '../utils/versionString';
const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({ const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({
userSelect: 'none', userSelect: 'none',
@@ -168,16 +166,10 @@ class TitleBar extends React.Component<Props, StateFromProps> {
)} )}
<Spacer /> <Spacer />
{!isProduction() && <NetworkGraph height={20} width={60} />}
{!isProduction() && <FpsGraph height={20} width={60} />}
{config.showFlipperRating ? <RatingButton /> : null} {config.showFlipperRating ? <RatingButton /> : null}
<Version>{this.props.version + (isProduction() ? '' : '-dev')}</Version> <Version>{getVersionString()}</Version>
<UpdateIndicator <UpdateIndicator />
launcherMsg={this.props.launcherMsg}
version={this.props.version}
/>
<Button <Button
icon="settings" icon="settings"

View File

@@ -8,8 +8,8 @@
*/ */
import {LauncherMsg} from '../reducers/application'; import {LauncherMsg} from '../reducers/application';
import {colors, FlexRow, Glyph, styled} from '../ui'; import {FlexRow, Glyph, styled} from '../ui';
import Tooltip from '../ui/components/Tooltip'; import {Tooltip} from 'antd';
import isProduction from '../utils/isProduction'; import isProduction from '../utils/isProduction';
import { import {
checkForUpdate, checkForUpdate,
@@ -20,10 +20,13 @@ import React from 'react';
import {shell} from 'electron'; import {shell} from 'electron';
import config from '../utils/processConfig'; import config from '../utils/processConfig';
import fbConfig from '../fb-stubs/config'; import fbConfig from '../fb-stubs/config';
import {useStore} from '../utils/useStore';
import {remote} from 'electron';
import {theme} from 'flipper-plugin';
const version = remote.app.getVersion();
const Container = styled(FlexRow)({ const Container = styled(FlexRow)({
alignItems: 'center', alignItems: 'center',
marginLeft: 4,
}); });
type Props = { type Props = {
@@ -38,13 +41,13 @@ type State = {
function getSeverityColor(severity: 'warning' | 'error'): string { function getSeverityColor(severity: 'warning' | 'error'): string {
switch (severity) { switch (severity) {
case 'warning': case 'warning':
return colors.light30; return theme.warningColor;
case 'error': case 'error':
return colors.cherry; return theme.errorColor;
} }
} }
export default class UpdateIndicator extends React.PureComponent<Props, State> { class UpdateIndicatorImpl extends React.PureComponent<Props, State> {
state = { state = {
versionCheckResult: {kind: 'up-to-date'} as VersionCheckResult, versionCheckResult: {kind: 'up-to-date'} as VersionCheckResult,
}; };
@@ -87,7 +90,7 @@ export default class UpdateIndicator extends React.PureComponent<Props, State> {
); );
return ( return (
<Tooltip <Tooltip
options={{position: 'toLeft'}} placement="right"
title={`Update to Flipper v${result.version} available. Click to download.`} title={`Update to Flipper v${result.version} available. Click to download.`}
children={container} children={container}
/> />
@@ -119,3 +122,8 @@ export default class UpdateIndicator extends React.PureComponent<Props, State> {
); );
} }
} }
export default function UpdateIndicator() {
const launcherMsg = useStore((state) => state.application.launcherMsg);
return <UpdateIndicatorImpl launcherMsg={launcherMsg} version={version} />;
}

View File

@@ -55,6 +55,10 @@ import {getUser} from '../fb-stubs/user';
import {SandyRatingButton} from '../chrome/RatingButton'; import {SandyRatingButton} from '../chrome/RatingButton';
import {filterNotifications} from './notification/notificationUtils'; import {filterNotifications} from './notification/notificationUtils';
import {useMemoize} from '../utils/useMemoize'; import {useMemoize} from '../utils/useMemoize';
import isProduction from '../utils/isProduction';
import NetworkGraph from '../chrome/NetworkGraph';
import FpsGraph from '../chrome/FpsGraph';
import UpdateIndicator from '../chrome/UpdateIndicator';
const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({ const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({
width: kind === 'small' ? 32 : 36, width: kind === 'small' ? 32 : 36,
@@ -154,6 +158,13 @@ export const LeftRail = withTrackingScope(function LeftRail({
/> />
</Layout.Container> </Layout.Container>
<Layout.Container center gap={10} padh={6}> <Layout.Container center gap={10} padh={6}>
{!isProduction() && (
<div>
<FpsGraph />
<NetworkGraph />
</div>
)}
<UpdateIndicator />
<SandyRatingButton /> <SandyRatingButton />
<LaunchEmulatorButton /> <LaunchEmulatorButton />
<SetupDoctorButton /> <SetupDoctorButton />

View File

@@ -14,7 +14,6 @@ import {Layout, Sidebar} from '../ui';
import {theme} from 'flipper-plugin'; import {theme} from 'flipper-plugin';
import {LeftRail} from './LeftRail'; import {LeftRail} from './LeftRail';
import {TemporarilyTitlebar} from './TemporarilyTitlebar';
import {registerStartupTime} from '../App'; import {registerStartupTime} from '../App';
import {useStore, useDispatch} from '../utils/useStore'; import {useStore, useDispatch} from '../utils/useStore';
import {SandyContext} from './SandyContext'; import {SandyContext} from './SandyContext';
@@ -32,6 +31,8 @@ import {Notification} from './notification/Notification';
import {SheetRenderer} from '../chrome/SheetRenderer'; import {SheetRenderer} from '../chrome/SheetRenderer';
import {hasNewChangesToShow} from '../chrome/ChangelogSheet'; import {hasNewChangesToShow} from '../chrome/ChangelogSheet';
import {SandyWelcomScreen} from './SandyWelcomeScreen'; import {SandyWelcomScreen} from './SandyWelcomeScreen';
import {getVersionString} from '../utils/versionString';
import config from '../fb-stubs/config';
export type ToplevelNavItem = export type ToplevelNavItem =
| 'appinspect' | 'appinspect'
@@ -85,6 +86,10 @@ export function SandyApp() {
); );
useEffect(() => { useEffect(() => {
document.title = `Flipper (${getVersionString()}${
config.isFBBuild ? '@FB' : ''
})`;
registerStartupTime(logger); registerStartupTime(logger);
if (hasNewChangesToShow(window.localStorage)) { if (hasNewChangesToShow(window.localStorage)) {
dispatch(setActiveSheet(ACTIVE_SHEET_CHANGELOG_RECENT_ONLY)); dispatch(setActiveSheet(ACTIVE_SHEET_CHANGELOG_RECENT_ONLY));
@@ -104,7 +109,6 @@ export function SandyApp() {
<SandyContext.Provider value={true}> <SandyContext.Provider value={true}>
<Layout.Top> <Layout.Top>
<> <>
<TemporarilyTitlebar />
<SheetRenderer logger={logger} /> <SheetRenderer logger={logger} />
<SandyWelcomScreen /> <SandyWelcomScreen />
</> </>

View File

@@ -1,67 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React from 'react';
import {styled, colors} from '../ui';
import FpsGraph from '../chrome/FpsGraph';
import NetworkGraph from '../chrome/NetworkGraph';
import isProduction from '../utils/isProduction';
import UpdateIndicator from '../chrome/UpdateIndicator';
import {Version} from '../chrome/TitleBar';
import {useStore} from '../utils/useStore';
import {remote} from 'electron';
import config from '../fb-stubs/config';
import ReleaseChannel from '../ReleaseChannel';
const version = remote.app.getVersion();
const TemporarilyTitlebarContainer = styled('div')<{focused?: boolean}>(
({focused}) => ({
textAlign: 'center',
userSelect: 'none',
height: '38px',
lineHeight: '38px',
fontSize: '10pt',
color: colors.macOSTitleBarIcon,
background: true
? `linear-gradient(to bottom, ${colors.macOSTitleBarBackgroundTop} 0%, ${colors.macOSTitleBarBackgroundBottom} 100%)`
: colors.macOSTitleBarBackgroundBlur,
borderBottom: `1px solid ${
focused ? colors.macOSTitleBarBorder : colors.macOSTitleBarBorderBlur
}`,
WebkitAppRegion: 'drag',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}),
);
// This component should be dropped, and insetTitlebar should be removed from Electron startup once Sandy is the default
// But: figure out where to put the graphs, version numbers, flipper rating ets :)
export function TemporarilyTitlebar() {
const launcherMsg = useStore((state) => state.application.launcherMsg);
const isFocused = useStore((state) => state.application.windowIsFocused);
return (
<TemporarilyTitlebarContainer focused={isFocused}>
[Sandy] Flipper{' '}
{!isProduction() && <NetworkGraph height={20} width={60} />}
{!isProduction() && <FpsGraph height={20} width={60} />}
<Version>
{version +
(isProduction() ? '' : '-dev') +
(config.getReleaseChannel() !== ReleaseChannel.STABLE
? `-${config.getReleaseChannel()}`
: '')}
</Version>
<UpdateIndicator launcherMsg={launcherMsg} version={version} />
</TemporarilyTitlebarContainer>
);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import isProduction from '../utils/isProduction';
import {remote} from 'electron';
import config from '../fb-stubs/config';
import ReleaseChannel from '../ReleaseChannel';
const version = remote.app.getVersion();
export function getVersionString() {
return (
version +
(isProduction() ? '' : '-dev') +
(config.getReleaseChannel() !== ReleaseChannel.STABLE
? `-${config.getReleaseChannel()}`
: '')
);
}

View File

@@ -120,6 +120,7 @@
16 16
], ],
"data-table": [ "data-table": [
12,
16 16
], ],
"desktop": [ "desktop": [
@@ -561,6 +562,7 @@
20 20
], ],
"dog": [ "dog": [
12,
16 16
], ],
"hub": [ "hub": [

View File

@@ -268,8 +268,6 @@ function createWindow() {
minWidth: 800, minWidth: 800,
minHeight: 600, minHeight: 600,
center: true, center: true,
titleBarStyle: 'hiddenInset',
vibrancy: 'sidebar',
webPreferences: { webPreferences: {
enableRemoteModule: true, enableRemoteModule: true,
backgroundThrottling: false, backgroundThrottling: false,