add basic about dialog

This commit is contained in:
David Lechner
2021-01-15 12:51:52 -06:00
parent efb474b727
commit dcb432583c
12 changed files with 172 additions and 7 deletions
+23 -1
View File
@@ -13,6 +13,10 @@ export enum AppActionType {
OpenSettings = 'app.action.openSettings',
/** Close settings dialog. */
CloseSettings = 'app.action.closeSettings',
/** Open about dialog. */
OpenAboutDialog = 'app.action.openAboutDialog',
/** Close about dialog. */
CloseAboutDialog = 'app.action.closeAboutDialog',
}
/** Action that indicates the app has just started. */
@@ -39,8 +43,26 @@ export function closeSettings(): AppCloseSettingsAction {
return { type: AppActionType.CloseSettings };
}
/** Action to open the about dialog. */
export type AppOpenAboutDialogAction = Action<AppActionType.OpenAboutDialog>;
/** Creates an action to open the about dialog. */
export function openAboutDialog(): AppOpenAboutDialogAction {
return { type: AppActionType.OpenAboutDialog };
}
/** Action to close the about dialog. */
export type AppCloseAboutDialogAction = Action<AppActionType.CloseAboutDialog>;
/** Creates an action to close the about dialog. */
export function closeAboutDialog(): AppCloseAboutDialogAction {
return { type: AppActionType.CloseAboutDialog };
}
/** common type for all app actions. */
export type AppAction =
| AppStartupAction
| AppOpenSettingsAction
| AppCloseSettingsAction;
| AppCloseSettingsAction
| AppOpenAboutDialogAction
| AppCloseAboutDialogAction;
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
// The about dialog
import { AnchorButton, Classes, Dialog } 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 { closeAboutDialog } from '../actions/app';
import { RootState } from '../reducers';
import {
appName,
legoDisclaimer,
pybricksCopyright,
pybricksWebsiteUrl,
} from '../settings/ui';
import { AboutStringId } from './about-i18n';
import en from './about-i18n.en.json';
type StateProps = { showAboutDialog: boolean };
type DispatchProps = { onClose: () => void };
type AboutDialogProps = StateProps & DispatchProps & WithI18nProps;
class AboutDialog extends React.Component<AboutDialogProps> {
render(): JSX.Element {
const { i18n, showAboutDialog, onClose } = this.props;
return (
<Dialog title={appName} isOpen={showAboutDialog} onClose={() => onClose()}>
<div className={Classes.DIALOG_BODY}>
<p>
<strong>{i18n.translate(AboutStringId.Description)}</strong>
</p>
<p>{pybricksCopyright}</p>
</div>
<div className={Classes.DIALOG_FOOTER}>
<p>{legoDisclaimer}</p>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<AnchorButton href={pybricksWebsiteUrl} target="blank_">
{i18n.translate(AboutStringId.WebsiteButtonLabel)}
</AnchorButton>
</div>
</div>
</Dialog>
);
}
}
const mapStateToProps = (state: RootState): StateProps => ({
showAboutDialog: state.app.showAboutDialog,
});
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
onClose: (): Action => dispatch(closeAboutDialog()),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withI18n({ id: 'about', fallback: en, translations: { en } })(AboutDialog));
+2
View File
@@ -5,6 +5,7 @@ import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import SplitterLayout from 'react-splitter-layout';
import { RootState } from '../reducers';
import AboutDialog from './AboutDialog';
import Editor from './Editor';
import SettingsDrawer from './SettingsDrawer';
import StatusBar from './StatusBar';
@@ -64,6 +65,7 @@ function App(): JSX.Element {
</SplitterLayout>
<StatusBar />
<SettingsDrawer />
<AboutDialog key="about" />
</div>
);
}
+14 -1
View File
@@ -6,7 +6,7 @@ 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 { closeSettings, openAboutDialog } from '../actions/app';
import { setBoolean } from '../actions/settings';
import { RootState } from '../reducers';
import { tooltipDelay } from '../settings/ui';
@@ -29,6 +29,7 @@ type DispatchProps = {
onShowDocsChanged: (checked: boolean) => void;
onDarkModeChanged: (checked: boolean) => void;
onFlashCurrentProgramChanged: (checked: boolean) => void;
onAbout: () => void;
};
type SettingsProps = StateProps & DispatchProps & WithI18nProps;
@@ -45,6 +46,7 @@ class SettingsDrawer extends React.PureComponent<SettingsProps> {
onDarkModeChanged,
flashCurrentProgram,
onFlashCurrentProgramChanged,
onAbout,
} = this.props;
return (
<Drawer
@@ -131,6 +133,16 @@ class SettingsDrawer extends React.PureComponent<SettingsProps> {
/>
</Tooltip>
</FormGroup>
<FormGroup label={i18n.translate(SettingsStringId.HelpTitle)}>
<a
onClick={() => {
onAbout();
return true;
}}
>
{i18n.translate(SettingsStringId.HelpAboutLabel)}
</a>
</FormGroup>
</div>
</Drawer>
);
@@ -152,6 +164,7 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
dispatch(setBoolean(SettingId.DarkMode, checked)),
onFlashCurrentProgramChanged: (checked): Action =>
dispatch(setBoolean(SettingId.FlashCurrentProgram, checked)),
onAbout: (): Action => dispatch(openAboutDialog()),
});
export default connect(
+6
View File
@@ -0,0 +1,6 @@
{
"about": {
"description": "A simple web app for programming LEGO® Powered Up smart hubs using Pybricks MicroPython.",
"websiteButton": { "label": "Pybricks Website" }
}
}
+12
View File
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { lookup } from '../../test';
import { AboutStringId } from './about-i18n';
import en from './about-i18n.en.json';
describe('Ensure .json file has matches for AboutStringId', () => {
test.each(Object.values(AboutStringId))('%s', (id) => {
expect(lookup(en, id)).toBeDefined();
});
});
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
// About dialog translation keys.
export enum AboutStringId {
Description = 'about.description',
WebsiteButtonLabel = 'about.websiteButton.label',
}
+7 -1
View File
@@ -1,6 +1,6 @@
{
"settings": {
"title": "Settings",
"title": "Settings & Help",
"appearance": {
"title": "Appearance",
"documentation": {
@@ -21,6 +21,12 @@
"label": "Include current program",
"tooltip": "Select to include your program when installing the firmware."
}
},
"help": {
"title": "Help",
"about": {
"label": "About"
}
}
}
}
+2
View File
@@ -14,4 +14,6 @@ export enum SettingsStringId {
FirmwareTitle = 'settings.firmware.title',
FirmwareCurrentProgramLabel = 'settings.firmware.flash-current-program.label',
FirmwareCurrentProgramTooltip = 'settings.firmware.flash-current-program.tooltip',
HelpTitle = 'settings.help.title',
HelpAboutLabel = 'settings.help.about.label',
}
+4
View File
@@ -15,3 +15,7 @@
.pb-settings .bp3-form-helper-text {
font-size: 14px;
}
.pb-settings a {
font-size: 16px;
}
+16 -4
View File
@@ -7,6 +7,11 @@ import { Reducer, combineReducers } from 'redux';
import { Action } from '../actions';
import { AppActionType } from '../actions/app';
export interface AppState {
readonly showSettings: boolean;
readonly showAboutDialog: boolean;
}
const showSettings: Reducer<boolean, Action> = (state = false, action) => {
switch (action.type) {
case AppActionType.OpenSettings:
@@ -18,8 +23,15 @@ const showSettings: Reducer<boolean, Action> = (state = false, action) => {
}
};
export interface AppState {
readonly showSettings: boolean;
}
const showAboutDialog: Reducer<boolean, Action> = (state = false, action) => {
switch (action.type) {
case AppActionType.OpenAboutDialog:
return true;
case AppActionType.CloseAboutDialog:
return false;
default:
return state;
}
};
export default combineReducers({ showSettings });
export default combineReducers({ showSettings, showAboutDialog });
+14
View File
@@ -5,3 +5,17 @@
/** Time in milliseconds before showing tooltip popover. */
export const tooltipDelay = 1000;
/** Official name of the app. */
export const appName = 'Pybricks Code';
/** URL to main Pybricks website. */
export const pybricksWebsiteUrl = 'https://pybricks.com';
/** Pybricks copyright statement. */
export const pybricksCopyright = 'Copyright (c) 2020-2021 The Pybricks Authors';
// https://www.lego.com/en-us/legal/notices-and-policies/fair-play/
/** LEGO "fair play" disclaimer */
export const legoDisclaimer =
'LEGO® is a trademark of the LEGO Group of companies which does not sponsor, authorize or endorse this site.';