mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
add basic about dialog
This commit is contained in:
+23
-1
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"about": {
|
||||
"description": "A simple web app for programming LEGO® Powered Up smart hubs using Pybricks MicroPython.",
|
||||
"websiteButton": { "label": "Pybricks Website" }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -15,3 +15,7 @@
|
||||
.pb-settings .bp3-form-helper-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pb-settings a {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
+16
-4
@@ -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 });
|
||||
|
||||
@@ -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.';
|
||||
|
||||
Reference in New Issue
Block a user