Add Flipper logs to leftrail
Summary: This adds support for flipper logs in Sandy, including some theme adjustments. Did remove storage and showing of debug messages, as I noticed it tends to crash Flipper after a while since there are so many. Also added a fixed limit of only remembering last 1000 Also converted Toolbar and button with dropdown items to Sandy. Reviewed By: cekkaewnumchai Differential Revision: D23824528 fbshipit-source-id: b89d1182d4f14682251dbb482d93c2c009ddc7a4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
191df465b7
commit
aea04dd0cf
@@ -12,8 +12,13 @@ import {Button, Toolbar, ButtonGroup, Layout} from '../ui';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Console, Hook} from 'console-feed';
|
import {Console, Hook} from 'console-feed';
|
||||||
import type {Methods} from 'console-feed/lib/definitions/Methods';
|
import type {Methods} from 'console-feed/lib/definitions/Methods';
|
||||||
|
import type {Styles} from 'console-feed/lib/definitions/Styles';
|
||||||
import {createState, useValue} from 'flipper-plugin';
|
import {createState, useValue} from 'flipper-plugin';
|
||||||
import {useLocalStorage} from '../utils/useLocalStorage';
|
import {useLocalStorage} from '../utils/useLocalStorage';
|
||||||
|
import {theme, useIsDarkMode} from '../sandy-chrome/theme';
|
||||||
|
import {useIsSandy} from '../sandy-chrome/SandyContext';
|
||||||
|
|
||||||
|
const MAX_LOG_ITEMS = 1000;
|
||||||
|
|
||||||
const logsAtom = createState<any[]>([]);
|
const logsAtom = createState<any[]>([]);
|
||||||
export const errorCounterAtom = createState(0);
|
export const errorCounterAtom = createState(0);
|
||||||
@@ -23,7 +28,12 @@ export function enableConsoleHook() {
|
|||||||
Hook(
|
Hook(
|
||||||
window.console,
|
window.console,
|
||||||
(log) => {
|
(log) => {
|
||||||
logsAtom.set([...logsAtom.get(), log]);
|
if (log.method === 'debug') {
|
||||||
|
return; // See below, skip debug messages which are generated very aggressively by Flipper
|
||||||
|
}
|
||||||
|
const newLogs = logsAtom.get().slice(-MAX_LOG_ITEMS);
|
||||||
|
newLogs.push(log);
|
||||||
|
logsAtom.set(newLogs);
|
||||||
if (log.method === 'error' || log.method === 'assert') {
|
if (log.method === 'error' || log.method === 'assert') {
|
||||||
errorCounterAtom.set(errorCounterAtom.get() + 1);
|
errorCounterAtom.set(errorCounterAtom.get() + 1);
|
||||||
}
|
}
|
||||||
@@ -39,7 +49,8 @@ function clearLogs() {
|
|||||||
|
|
||||||
const allLogLevels: Methods[] = [
|
const allLogLevels: Methods[] = [
|
||||||
'log',
|
'log',
|
||||||
'debug',
|
// 'debug', We typically don't want to allow users to enable the debug logs, as they are used very intensively by flipper itself,
|
||||||
|
// making Flipper / console-feed. For debug level logging, use the Chrome devtools.
|
||||||
'info',
|
'info',
|
||||||
'warn',
|
'warn',
|
||||||
'error',
|
'error',
|
||||||
@@ -54,6 +65,8 @@ const allLogLevels: Methods[] = [
|
|||||||
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
|
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
|
||||||
|
|
||||||
export function ConsoleLogs() {
|
export function ConsoleLogs() {
|
||||||
|
const isSandy = useIsSandy();
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
const logs = useValue(logsAtom);
|
const logs = useValue(logsAtom);
|
||||||
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
|
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
|
||||||
'console-logs-loglevels',
|
'console-logs-loglevels',
|
||||||
@@ -77,6 +90,8 @@ export function ConsoleLogs() {
|
|||||||
);
|
);
|
||||||
}, [logLevels, setLogLevels]);
|
}, [logLevels, setLogLevels]);
|
||||||
|
|
||||||
|
const styles = useMemo(() => buildTheme(isSandy), [isSandy]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Top scrollable>
|
<Layout.Top scrollable>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
@@ -87,7 +102,46 @@ export function ConsoleLogs() {
|
|||||||
<Button dropdown={dropdown}>Log Levels</Button>
|
<Button dropdown={dropdown}>Log Levels</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Console logs={logs} filter={logLevels} variant="light" />
|
<Console
|
||||||
|
logs={logs}
|
||||||
|
filter={logLevels}
|
||||||
|
variant={isDarkMode || !isSandy ? 'dark' : 'light'}
|
||||||
|
styles={styles}
|
||||||
|
/>
|
||||||
</Layout.Top>
|
</Layout.Top>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildTheme(isSandy: boolean): Styles {
|
||||||
|
if (!isSandy) {
|
||||||
|
const bg = '#333';
|
||||||
|
return {
|
||||||
|
BASE_BACKGROUND_COLOR: bg,
|
||||||
|
BASE_COLOR: 'white',
|
||||||
|
LOG_BACKGROUND: bg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
// See: https://github.com/samdenty/console-feed/blob/master/src/definitions/Styles.d.ts
|
||||||
|
BASE_BACKGROUND_COLOR: 'transparent',
|
||||||
|
BASE_COLOR: theme.textColorPrimary,
|
||||||
|
LOG_COLOR: theme.textColorPrimary,
|
||||||
|
LOG_BACKGROUND: 'transparent',
|
||||||
|
LOG_INFO_BACKGROUND: 'transparent',
|
||||||
|
LOG_COMMAND_BACKGROUND: 'transparent',
|
||||||
|
LOG_RESULT_BACKGROUND: 'transparent',
|
||||||
|
LOG_WARN_BACKGROUND: theme.warningColor,
|
||||||
|
LOG_ERROR_BACKGROUND: theme.errorColor,
|
||||||
|
LOG_INFO_COLOR: theme.textColorPrimary,
|
||||||
|
LOG_COMMAND_COLOR: theme.textColorSecondary,
|
||||||
|
LOG_RESULT_COLOR: theme.textColorSecondary,
|
||||||
|
LOG_WARN_COLOR: 'white',
|
||||||
|
LOG_ERROR_COLOR: 'white',
|
||||||
|
LOG_INFO_BORDER: theme.dividerColor,
|
||||||
|
LOG_COMMAND_BORDER: theme.dividerColor,
|
||||||
|
LOG_RESULT_BORDER: theme.dividerColor,
|
||||||
|
LOG_WARN_BORDER: theme.dividerColor,
|
||||||
|
LOG_ERROR_BORDER: theme.dividerColor,
|
||||||
|
LOG_BORDER: theme.dividerColor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
|
|||||||
TestDevicePlugin
|
TestDevicePlugin
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
checked={false}
|
checked={false}
|
||||||
@@ -84,7 +84,7 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
|
|||||||
TestPlugin
|
TestPlugin
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
checked={false}
|
checked={false}
|
||||||
@@ -109,7 +109,7 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
|
|||||||
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="css-12n892b"
|
className="css-12n892b"
|
||||||
@@ -188,7 +188,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
|||||||
TestDevicePlugin
|
TestDevicePlugin
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
checked={false}
|
checked={false}
|
||||||
@@ -229,7 +229,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
|||||||
TestPlugin
|
TestPlugin
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
checked={true}
|
checked={true}
|
||||||
@@ -254,7 +254,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
|||||||
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="css-12n892b"
|
className="css-12n892b"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
|
|||||||
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="css-xing9h-StyledButton enfqd40"
|
className="css-xing9h-StyledButton enfqd40"
|
||||||
@@ -77,7 +77,7 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
|
|||||||
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
className="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="css-xing9h-StyledButton enfqd40"
|
className="css-xing9h-StyledButton enfqd40"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
class="css-13jp8bd-View-FlexBox-FlexColumn"
|
class="css-13jp8bd-View-FlexBox-FlexColumn"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80"
|
class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-awcbnc-View-FlexBox-SearchBox e271nro1"
|
class="css-awcbnc-View-FlexBox-SearchBox e271nro1"
|
||||||
@@ -137,7 +137,7 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
World?
|
World?
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
class="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||||
@@ -204,7 +204,7 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
Hello?
|
Hello?
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
class="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||||
@@ -235,7 +235,7 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80"
|
class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541"
|
class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541"
|
||||||
@@ -301,7 +301,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
class="css-13jp8bd-View-FlexBox-FlexColumn"
|
class="css-13jp8bd-View-FlexBox-FlexColumn"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80"
|
class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-awcbnc-View-FlexBox-SearchBox e271nro1"
|
class="css-awcbnc-View-FlexBox-SearchBox e271nro1"
|
||||||
@@ -432,7 +432,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
World?
|
World?
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
class="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||||
@@ -498,7 +498,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
Hello?
|
Hello?
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="css-te359u-View-FlexBox-Spacer e13mj6h81"
|
class="css-te359u-View-FlexBox-Spacer e13mj6h82"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||||
@@ -529,7 +529,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80"
|
class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541"
|
class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541"
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ import {toggleLeftSidebarVisible} from '../reducers/application';
|
|||||||
import {theme} from './theme';
|
import {theme} from './theme';
|
||||||
import SettingsSheet from '../chrome/SettingsSheet';
|
import SettingsSheet from '../chrome/SettingsSheet';
|
||||||
import WelcomeScreen from './WelcomeScreen';
|
import WelcomeScreen from './WelcomeScreen';
|
||||||
|
import {isStaticViewActive} from '../chrome/mainsidebar/sidebarUtils';
|
||||||
|
import {ConsoleLogs, errorCounterAtom} from '../chrome/ConsoleLogs';
|
||||||
|
import {setStaticView} from '../reducers/connections';
|
||||||
|
import {useValue} from 'flipper-plugin';
|
||||||
|
|
||||||
const LeftRailContainer = styled(FlexColumn)({
|
const LeftRailContainer = styled(FlexColumn)({
|
||||||
background: theme.backgroundDefault,
|
background: theme.backgroundDefault,
|
||||||
@@ -109,10 +113,7 @@ export function LeftRail() {
|
|||||||
title="Notifications"
|
title="Notifications"
|
||||||
/>
|
/>
|
||||||
<LeftRailDivider />
|
<LeftRailDivider />
|
||||||
<LeftRailButton
|
<DebugLogsButton />
|
||||||
icon={<FileExclamationOutlined />}
|
|
||||||
title="Flipper Logs"
|
|
||||||
/>
|
|
||||||
</LeftRailSection>
|
</LeftRailSection>
|
||||||
<LeftRailSection>
|
<LeftRailSection>
|
||||||
<LeftRailButton
|
<LeftRailButton
|
||||||
@@ -159,6 +160,25 @@ function LeftSidebarToggleButton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DebugLogsButton() {
|
||||||
|
const staticView = useStore((state) => state.connections.staticView);
|
||||||
|
const active = isStaticViewActive(staticView, ConsoleLogs);
|
||||||
|
const errorCount = useValue(errorCounterAtom);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LeftRailButton
|
||||||
|
icon={<FileExclamationOutlined />}
|
||||||
|
title="Flipper Logs"
|
||||||
|
selected={active}
|
||||||
|
count={errorCount}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setStaticView(ConsoleLogs));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ShowSettingsButton() {
|
function ShowSettingsButton() {
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const onClose = useCallback(() => setShowSettings(false), []);
|
const onClose = useCallback(() => setShowSettings(false), []);
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ export function SandyApp({logger}: {logger: Logger}) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const mainMenuVisible = useStore(
|
const mainMenuVisible = useStore(
|
||||||
(state) => state.application.leftSidebarVisible,
|
(state) =>
|
||||||
|
state.application.leftSidebarVisible && !state.connections.staticView,
|
||||||
);
|
);
|
||||||
|
const staticView = useStore((state) => state.connections.staticView);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SandyContext.Provider value={true}>
|
<SandyContext.Provider value={true}>
|
||||||
@@ -51,7 +53,13 @@ export function SandyApp({logger}: {logger: Logger}) {
|
|||||||
<Layout.Right initialSize={300} minSize={200}>
|
<Layout.Right initialSize={300} minSize={200}>
|
||||||
<MainContentWrapper>
|
<MainContentWrapper>
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
|
{staticView ? (
|
||||||
|
React.createElement(staticView, {
|
||||||
|
logger: logger,
|
||||||
|
})
|
||||||
|
) : (
|
||||||
<TemporarilyContent />
|
<TemporarilyContent />
|
||||||
|
)}
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
</MainContentWrapper>
|
</MainContentWrapper>
|
||||||
<MainContentWrapper>
|
<MainContentWrapper>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {useStore} from '../utils/useStore';
|
||||||
|
|
||||||
// Exposes all the variables defined in themes/base.less:
|
// Exposes all the variables defined in themes/base.less:
|
||||||
|
|
||||||
export const theme = {
|
export const theme = {
|
||||||
@@ -33,3 +35,14 @@ export const theme = {
|
|||||||
smallBody: '12px',
|
smallBody: '12px',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This hook returns whether dark mode is currently being used.
|
||||||
|
* Generally should be avoided in favor of using the above theme object,
|
||||||
|
* which will provide colors that reflect the theme
|
||||||
|
*/
|
||||||
|
export function useIsDarkMode(): boolean {
|
||||||
|
return useStore(
|
||||||
|
(state) => state.settingsState.enableSandy && state.settingsState.darkMode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useContext, useState, useRef, useCallback} from 'react';
|
import React, {useContext, useState, useRef, useCallback, useMemo} from 'react';
|
||||||
import electron, {MenuItemConstructorOptions} from 'electron';
|
import electron, {MenuItemConstructorOptions} from 'electron';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {findDOMNode} from 'react-dom';
|
import {findDOMNode} from 'react-dom';
|
||||||
import {keyframes} from 'emotion';
|
import {keyframes} from 'emotion';
|
||||||
import {Button as AntdButton} from 'antd';
|
import {Button as AntdButton, Dropdown, Menu} from 'antd';
|
||||||
|
|
||||||
import {colors} from './colors';
|
import {colors} from './colors';
|
||||||
import Glyph, {IconSize} from './Glyph';
|
import Glyph, {IconSize} from './Glyph';
|
||||||
@@ -20,6 +20,8 @@ import {ButtonGroupContext} from './ButtonGroup';
|
|||||||
import {useStore} from '../../utils/useStore';
|
import {useStore} from '../../utils/useStore';
|
||||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||||
import type {ButtonProps} from 'antd/lib/button';
|
import type {ButtonProps} from 'antd/lib/button';
|
||||||
|
import {DownOutlined, CheckOutlined} from '@ant-design/icons';
|
||||||
|
import {theme} from '../../sandy-chrome/theme';
|
||||||
|
|
||||||
type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
|
type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
|
||||||
|
|
||||||
@@ -217,7 +219,7 @@ type Props = {
|
|||||||
/**
|
/**
|
||||||
* Type of button.
|
* Type of button.
|
||||||
*/
|
*/
|
||||||
type?: ButtonType;
|
type?: ButtonType; // TODO: normalize to Sandy
|
||||||
/**
|
/**
|
||||||
* Children.
|
* Children.
|
||||||
*/
|
*/
|
||||||
@@ -264,11 +266,15 @@ type Props = {
|
|||||||
* A simple button, used in many parts of the application.
|
* A simple button, used in many parts of the application.
|
||||||
*/
|
*/
|
||||||
export default function Button(props: Props) {
|
export default function Button(props: Props) {
|
||||||
|
const isSandy = useIsSandy();
|
||||||
|
return isSandy ? <SandyButton {...props} /> : <ClassicButton {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ClassicButton(props: Props) {
|
||||||
const windowIsFocused = useStore(
|
const windowIsFocused = useStore(
|
||||||
(state) => state.application.windowIsFocused,
|
(state) => state.application.windowIsFocused,
|
||||||
);
|
);
|
||||||
const inButtonGroup = useContext(ButtonGroupContext);
|
const inButtonGroup = useContext(ButtonGroupContext);
|
||||||
const isSandy = useIsSandy();
|
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
const [wasClosed, setWasClosed] = useState(false);
|
const [wasClosed, setWasClosed] = useState(false);
|
||||||
|
|
||||||
@@ -359,19 +365,7 @@ export default function Button(props: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSandy ? (
|
return (
|
||||||
<AntdButton
|
|
||||||
{...restProps}
|
|
||||||
type={props.type === 'primary' ? 'primary' : 'default'}
|
|
||||||
danger={props.type === 'danger'}
|
|
||||||
ref={_ref}
|
|
||||||
onClick={onClick}
|
|
||||||
onMouseDown={onMouseDown}
|
|
||||||
onMouseUp={onMouseUp}
|
|
||||||
icon={iconComponent}>
|
|
||||||
{children}
|
|
||||||
</AntdButton>
|
|
||||||
) : (
|
|
||||||
<StyledButton
|
<StyledButton
|
||||||
{...restProps}
|
{...restProps}
|
||||||
ref={_ref as any}
|
ref={_ref as any}
|
||||||
@@ -385,3 +379,108 @@ export default function Button(props: Props) {
|
|||||||
</StyledButton>
|
</StyledButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple button, used in many parts of the application.
|
||||||
|
*/
|
||||||
|
export function SandyButton({
|
||||||
|
compact,
|
||||||
|
disabled,
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
iconSize,
|
||||||
|
iconVariant,
|
||||||
|
dropdown,
|
||||||
|
type,
|
||||||
|
onClick,
|
||||||
|
href,
|
||||||
|
...restProps
|
||||||
|
}: Props) {
|
||||||
|
const [dropdownVisible, setDropdownVible] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
if (disabled === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onClick?.(e);
|
||||||
|
if (href != null) {
|
||||||
|
electron.shell.openExternal(href); // TODO: decouple from Electron
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[disabled, onClick, href],
|
||||||
|
);
|
||||||
|
const handleVisibleChange = useCallback((flag: boolean) => {
|
||||||
|
setDropdownVible(flag);
|
||||||
|
}, []);
|
||||||
|
let iconComponent;
|
||||||
|
if (icon != null) {
|
||||||
|
iconComponent = (
|
||||||
|
<Icon
|
||||||
|
name={icon}
|
||||||
|
size={iconSize || (compact === true ? 12 : 16)}
|
||||||
|
color={theme.textColorPrimary}
|
||||||
|
variant={iconVariant || 'filled'}
|
||||||
|
hasText={Boolean(children)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropdownItems = useMemo(
|
||||||
|
() =>
|
||||||
|
dropdown && (
|
||||||
|
<Menu>
|
||||||
|
{dropdown!.map((item, idx) => (
|
||||||
|
<Menu.Item
|
||||||
|
onClick={(e) => {
|
||||||
|
// @ts-ignore this event args are bound to electron, remove in the future
|
||||||
|
item.click();
|
||||||
|
if (item.checked !== undefined) {
|
||||||
|
// keep the menu item for check lists
|
||||||
|
e.domEvent.stopPropagation();
|
||||||
|
e.domEvent.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={item.enabled === false}
|
||||||
|
icon={
|
||||||
|
item.checked !== undefined && (
|
||||||
|
<CheckOutlined
|
||||||
|
style={{visibility: item.checked ? 'visible' : 'hidden'}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
key={idx}>
|
||||||
|
{item.label}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
),
|
||||||
|
[dropdown],
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = (
|
||||||
|
<AntdButton
|
||||||
|
/* Probably more properties need passing on, but lets be explicit about it */
|
||||||
|
style={restProps.style}
|
||||||
|
disabled={disabled}
|
||||||
|
type={type === 'primary' ? 'primary' : 'default'}
|
||||||
|
danger={type === 'danger'}
|
||||||
|
onClick={handleClick}
|
||||||
|
icon={iconComponent}>
|
||||||
|
{children}
|
||||||
|
{dropdown ? <DownOutlined /> : null}
|
||||||
|
</AntdButton>
|
||||||
|
);
|
||||||
|
if (dropdown) {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
overlay={dropdownItems!}
|
||||||
|
visible={dropdownVisible}
|
||||||
|
onVisibleChange={handleVisibleChange}>
|
||||||
|
{button}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import React, {createContext} from 'react';
|
import React, {createContext} from 'react';
|
||||||
|
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||||
|
import {Space} from 'antd';
|
||||||
|
|
||||||
const ButtonGroupContainer = styled.div({
|
const ButtonGroupContainer = styled.div({
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
@@ -33,7 +35,12 @@ export const ButtonGroupContext = createContext(false);
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export default function ButtonGroup({children}: {children: React.ReactNode}) {
|
export default function ButtonGroup({children}: {children: React.ReactNode}) {
|
||||||
return (
|
const isSandy = useIsSandy(); // according to Ant design guides buttons should only be grouped if they are radios
|
||||||
|
return isSandy ? (
|
||||||
|
<ButtonGroupContext.Provider value={true}>
|
||||||
|
<Space>{children}</Space>
|
||||||
|
</ButtonGroupContext.Provider>
|
||||||
|
) : (
|
||||||
<ButtonGroupContext.Provider value={true}>
|
<ButtonGroupContext.Provider value={true}>
|
||||||
<ButtonGroupContainer>{children}</ButtonGroupContainer>
|
<ButtonGroupContainer>{children}</ButtonGroupContainer>
|
||||||
</ButtonGroupContext.Provider>
|
</ButtonGroupContext.Provider>
|
||||||
|
|||||||
@@ -7,15 +7,19 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import {colors} from './colors';
|
import {colors} from './colors';
|
||||||
import FlexRow from './FlexRow';
|
import FlexRow from './FlexRow';
|
||||||
import FlexBox from './FlexBox';
|
import FlexBox from './FlexBox';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import {Space} from 'antd';
|
||||||
|
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||||
|
import {theme} from '../../sandy-chrome/theme';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A toolbar.
|
* A toolbar.
|
||||||
*/
|
*/
|
||||||
const Toolbar = styled(FlexRow)<{
|
const ToolbarContainer = styled(FlexRow)<{
|
||||||
position?: 'bottom' | 'top';
|
position?: 'bottom' | 'top';
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}>((props) => ({
|
}>((props) => ({
|
||||||
@@ -36,11 +40,35 @@ const Toolbar = styled(FlexRow)<{
|
|||||||
padding: 6,
|
padding: 6,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}));
|
}));
|
||||||
Toolbar.displayName = 'Toolbar';
|
ToolbarContainer.displayName = 'ToolbarContainer';
|
||||||
|
|
||||||
|
const SandyToolbarContainer = styled(Space)({
|
||||||
|
width: '100%',
|
||||||
|
padding: theme.space.small,
|
||||||
|
boxShadow: `inset 0px -1px 0px ${theme.dividerColor}`,
|
||||||
|
});
|
||||||
|
|
||||||
export const Spacer = styled(FlexBox)({
|
export const Spacer = styled(FlexBox)({
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
});
|
});
|
||||||
Spacer.displayName = 'Spacer';
|
Spacer.displayName = 'Spacer';
|
||||||
|
|
||||||
export default Toolbar;
|
export default function Toolbar({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
position?: 'bottom' | 'top';
|
||||||
|
compact?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}) {
|
||||||
|
const isSandy = useIsSandy();
|
||||||
|
return isSandy ? (
|
||||||
|
<SandyToolbarContainer style={style}>{children}</SandyToolbarContainer>
|
||||||
|
) : (
|
||||||
|
<ToolbarContainer style={style} {...rest}>
|
||||||
|
{children}
|
||||||
|
</ToolbarContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user