change support button to settings button

and add new empty settings drawer
This commit is contained in:
David Lechner
2021-01-11 23:36:48 -06:00
parent fedef66931
commit 41a1cee092
16 changed files with 302 additions and 34 deletions
+26 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
// Copyright (c) 2020-2021 The Pybricks Authors
// File: actions/app.ts
// Actions for the app in general.
@@ -9,6 +9,10 @@ import { Action } from 'redux';
export enum AppActionType {
/** The app has just ben started. */
Startup = 'app.action.startup',
/** Open settings dialog. */
OpenSettings = 'app.action.openSettings',
/** Close settings dialog. */
CloseSettings = 'app.action.closeSettings',
/** Toggle documentation visibility. */
ToggleDocs = 'app.action.toggleDocs',
}
@@ -21,6 +25,22 @@ export function startup(): AppStartupAction {
return { type: AppActionType.Startup };
}
/** Action to open the settings dialog. */
export type AppOpenSettingsAction = Action<AppActionType.OpenSettings>;
/** Creates an action to open the settings dialog. */
export function openSettings(): AppOpenSettingsAction {
return { type: AppActionType.OpenSettings };
}
/** Action to close the settings dialog. */
export type AppCloseSettingsAction = Action<AppActionType.CloseSettings>;
/** Creates an action to close the settings dialog. */
export function closeSettings(): AppCloseSettingsAction {
return { type: AppActionType.CloseSettings };
}
/** Action to toggle documentation visibility. */
export type AppToggleDocsAction = Action<AppActionType.ToggleDocs>;
@@ -30,4 +50,8 @@ export function toggleDocs(): AppToggleDocsAction {
}
/** common type for all app actions. */
export type AppAction = AppStartupAction | AppToggleDocsAction;
export type AppAction =
| AppStartupAction
| AppOpenSettingsAction
| AppCloseSettingsAction
| AppToggleDocsAction;
+2
View File
@@ -6,6 +6,7 @@ import { useSelector } from 'react-redux';
import SplitterLayout from 'react-splitter-layout';
import { RootState } from '../reducers';
import Editor from './Editor';
import SettingsDrawer from './SettingsDrawer';
import StatusBar from './StatusBar';
import Terminal from './Terminal';
import Toolbar from './Toolbar';
@@ -61,6 +62,7 @@ function App(): JSX.Element {
)}
</SplitterLayout>
<StatusBar />
<SettingsDrawer />
</div>
);
}
+30
View File
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { connect } from 'react-redux';
import { Action, Dispatch } from '../actions';
import { openSettings as openSettings } from '../actions/app';
import ActionButton, { ActionButtonProps } from './ActionButton';
import { TooltipId } from './button-i18n';
import settingsIcon from './images/settings.svg';
type StateProps = undefined;
type DispatchProps = Pick<ActionButtonProps, 'onAction'>;
type OwnProps = Pick<ActionButtonProps, 'id'>;
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
onAction: (): Action => dispatch(openSettings()),
});
const mergeProps = (
_stateProps: StateProps,
dispatchProps: DispatchProps,
ownProps: OwnProps,
): ActionButtonProps => ({
tooltip: TooltipId.Settings,
icon: settingsIcon,
...dispatchProps,
...ownProps,
});
export default connect(undefined, mapDispatchToProps, mergeProps)(ActionButton);
+55
View File
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { Drawer } from '@blueprintjs/core';
import { WithI18nProps, withI18n } from '@shopify/react-i18n';
import React from 'react';
import { connect } from 'react-redux';
import { Action, Dispatch } from '../actions';
import { closeSettings } from '../actions/app';
import { RootState } from '../reducers';
import { SettingsStringId } from './settings-i18n';
import en from './settings-i18n.en.json';
type StateProps = {
open: boolean;
};
type DispatchProps = {
onClose: () => void;
};
type SettingsProps = StateProps & DispatchProps & WithI18nProps;
class SettingsDrawer extends React.PureComponent<SettingsProps> {
render(): JSX.Element {
const { i18n, open, onClose } = this.props;
return (
<Drawer
isOpen={open}
icon="cog"
title={i18n.translate(SettingsStringId.Title)}
onClose={() => onClose()}
></Drawer>
);
}
}
const mapStateToProps = (state: RootState): StateProps => ({
open: state.app.showSettings,
});
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
onClose: (): Action => dispatch(closeSettings()),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
withI18n({
id: 'settings',
fallback: en,
translations: { en },
})(SettingsDrawer),
);
-24
View File
@@ -1,24 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
import { connect } from 'react-redux';
import LinkButton, { LinkButtonProps } from './LinkButton';
import { TooltipId } from './button-i18n';
import supportIcon from './images/support.svg';
type StateProps = undefined;
type DispatchProps = undefined;
type OwnProps = Pick<LinkButtonProps, 'id'>;
const mergeProps = (
_stateProps: StateProps,
_dispatchProps: DispatchProps,
ownProps: OwnProps,
): LinkButtonProps => ({
url: 'https://github.com/pybricks/support/issues',
tooltip: TooltipId.Support,
icon: supportIcon,
...ownProps,
});
export default connect(undefined, undefined, mergeProps)(LinkButton);
+2 -2
View File
@@ -10,8 +10,8 @@ import OpenButton from './OpenButton';
import ReplButton from './ReplButton';
import RunButton from './RunButton';
import SaveAsButton from './SaveAsButton';
import SettingsButton from './SettingsButton';
import StopButton from './StopButton';
import SupportButton from './SupportButton';
class Toolbar extends React.Component {
render(): JSX.Element {
@@ -39,7 +39,7 @@ class Toolbar extends React.Component {
</Navbar.Group>
<Navbar.Group align={Alignment.RIGHT}>
<ButtonGroup>
<SupportButton id="support" />
<SettingsButton id="settings" />
<DocsButton id="docs" />
</ButtonGroup>
</Navbar.Group>
+1 -1
View File
@@ -10,5 +10,5 @@
},
"flash": { "tooltip": "Flash hub firmware" },
"docs": { "tooltip": "Show/hide documentation" },
"support": { "tooltip": "Open Pybricks Support web site" }
"settings": { "tooltip": "Open setting" }
}
+1 -1
View File
@@ -13,5 +13,5 @@ export enum TooltipId {
BluetoothConnect = 'bluetooth.connect.tooltip',
BluetoothDisconnect = 'bluetooth.disconnect.tooltip',
Docs = 'docs.tooltip',
Support = 'support.tooltip',
Settings = 'settings.tooltip',
}
+131
View File
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 50 50"
xml:space="preserve"
sodipodi:docname="settings.svg"
width="50"
height="50"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
id="metadata41"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs39" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2089"
inkscape:window-height="1160"
id="namedview37"
showgrid="false"
inkscape:pagecheckerboard="true"
inkscape:zoom="15.418475"
inkscape:cx="21.7408"
inkscape:cy="28.83975"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="Capa_1" />
<g
id="g4"
transform="matrix(0.08166572,0,0,0.08166572,4.9999835,4.9999835)"
style="fill:#ffffff;fill-opacity:0.90707964">
<path
d="m 20.701,281.901 32.1,0.2 c 4.8,24.7 14.3,48.7 28.7,70.5 l -22.8,22.6 c -8.2,8.1 -8.2,21.2 -0.2,29.4 l 24.6,24.9 c 8.1,8.2 21.2,8.2 29.4,0.2 l 22.8,-22.6 c 21.6,14.6 45.5,24.5 70.2,29.5 l -0.2,32.1 c -0.1,11.5 9.2,20.8 20.7,20.9 l 35,0.2 c 11.5,0.1 20.8,-9.2 20.9,-20.7 l 0.2,-32.1 c 24.7,-4.8 48.7,-14.3 70.5,-28.7 l 22.6,22.8 c 8.1,8.2 21.2,8.2 29.4,0.2 l 24.9,-24.6 c 8.2,-8.1 8.2,-21.2 0.2,-29.4 l -22.6,-22.8 c 14.6,-21.6 24.5,-45.5 29.5,-70.2 l 32.1,0.2 c 11.5,0.1 20.8,-9.2 20.9,-20.7 l 0.2,-35 c 0.1,-11.5 -9.2,-20.8 -20.7,-20.9 l -32.1,-0.2 c -4.8,-24.7 -14.3,-48.7 -28.7,-70.5 l 22.8,-22.6 c 8.2,-8.1 8.2,-21.2 0.2,-29.4 l -24.6,-24.9 c -8.1,-8.2 -21.2,-8.2 -29.4,-0.2 l -22.8,22.6 c -21.6,-14.6 -45.5,-24.5 -70.2,-29.5 l 0.2,-32.1 c 0.1,-11.5 -9.2,-20.8 -20.7,-20.9 l -35,-0.2 c -11.5,-0.1 -20.8,9.2 -20.9,20.7 l -0.3,32.1 c -24.8,4.8 -48.8,14.3 -70.5,28.7 l -22.6,-22.8 c -8.1,-8.2 -21.2,-8.2 -29.4,-0.2 l -24.8,24.6 c -8.2,8.1 -8.2,21.2 -0.2,29.4 l 22.6,22.8 c -14.6,21.6 -24.5,45.5 -29.5,70.2 l -32.1,-0.2 c -11.5,-0.1 -20.8,9.2 -20.9,20.7 l -0.2,35 c -0.1,11.4 9.2,20.8 20.7,20.9 z m 158.6,-103.3 c 36.6,-36.2 95.5,-35.9 131.7,0.7 36.2,36.6 35.9,95.5 -0.7,131.7 -36.6,36.2 -95.5,35.9 -131.7,-0.7 -36.2,-36.6 -35.9,-95.5 0.7,-131.7 z"
id="path2"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:0.90707964" />
</g>
<g
id="g6"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g8"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g10"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g12"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g14"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g16"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g18"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g20"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g22"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g24"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g26"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g28"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g30"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g32"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
<g
id="g34"
transform="matrix(0.08166572,0,0,0.08166572,224.90098,-214.90102)"
style="fill:#ffffff;fill-opacity:0.90707964">
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

+5
View File
@@ -0,0 +1,5 @@
{
"settings": {
"title": "Settings"
}
}
+12
View File
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { lookup } from '../../test';
import { SettingsStringId } from './settings-i18n';
import en from './settings-i18n.en.json';
describe('Ensure .json file has matches for SettingsStringIds', () => {
test.each(Object.values(SettingsStringId))('%s', (id) => {
expect(lookup(en, id)).toBeDefined();
});
});
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
// File: components/settings-i18n.ts
// Settings translation keys.
export enum SettingsStringId {
Title = 'settings.title',
}
+14 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
// Copyright (c) 2020-2021 The Pybricks Authors
// File: reducers/app.ts
// Manages state the app in general.
@@ -7,6 +7,17 @@ import { Reducer, combineReducers } from 'redux';
import { Action } from '../actions';
import { AppActionType } from '../actions/app';
const showSettings: Reducer<boolean, Action> = (state = false, action) => {
switch (action.type) {
case AppActionType.OpenSettings:
return true;
case AppActionType.CloseSettings:
return false;
default:
return state;
}
};
const showDocs: Reducer<boolean, Action> = (state = false, action) => {
switch (action.type) {
case AppActionType.ToggleDocs:
@@ -17,7 +28,8 @@ const showDocs: Reducer<boolean, Action> = (state = false, action) => {
};
export interface AppState {
readonly showSettings: boolean;
readonly showDocs: boolean;
}
export default combineReducers({ showDocs });
export default combineReducers({ showSettings, showDocs });
+3
View File
@@ -8,6 +8,7 @@ import bootloader, { BootloaderState } from './bootloader';
import editor, { EditorState } from './editor';
import hub, { HubState } from './hub';
import notification, { NotificationState } from './notification';
import settings, { SettingsState } from './settings';
import status, { StatusState } from './status';
import terminal, { TerminalState } from './terminal';
@@ -21,6 +22,7 @@ export interface RootState {
readonly editor: EditorState;
readonly hub: HubState;
readonly notification: NotificationState;
readonly settings: SettingsState;
readonly status: StatusState;
readonly terminal: TerminalState;
}
@@ -32,6 +34,7 @@ export default combineReducers({
editor,
hub,
notification,
settings,
status,
terminal,
});
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { combineReducers } from 'redux';
export interface SettingsState {
readonly visible: boolean;
}
export default combineReducers({});
+2 -2
View File
@@ -88,7 +88,7 @@ describe('storeDocsState', () => {
const mockSetItem = jest
.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem')
.mockImplementation((_key, value) => expect(value).toBe('true'));
saga.setState({ app: { showDocs: true } });
saga.setState({ app: { showSettings: false, showDocs: true } });
saga.put(toggleDocs());
expect(mockSetItem).toHaveBeenCalled();
@@ -101,7 +101,7 @@ describe('storeDocsState', () => {
const mockSetItem = jest
.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem')
.mockImplementation((_key, value) => expect(value).toBe('false'));
saga.setState({ app: { showDocs: false } });
saga.setState({ app: { showSettings: false, showDocs: false } });
saga.put(toggleDocs());
expect(mockSetItem).toHaveBeenCalled();