add license dialog

This commit is contained in:
David Lechner
2021-01-17 10:39:38 -06:00
parent 4b63a80f80
commit 1626533cca
21 changed files with 454 additions and 12 deletions
+10 -4
View File
@@ -3,12 +3,12 @@
// The about dialog
import { AnchorButton, Classes, Dialog } from '@blueprintjs/core';
import { AnchorButton, Button, 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 { closeAboutDialog, openLicenseDialog } from '../actions/app';
import { RootState } from '../reducers';
import {
appName,
@@ -17,6 +17,7 @@ import {
pybricksWebsiteUrl,
} from '../settings/ui';
import ExternalLinkIcon from './ExternalLinkIcon';
import LicenseDialog from './LicenseDialog';
import { AboutStringId } from './about-i18n';
import en from './about-i18n.en.json';
@@ -24,13 +25,13 @@ import './about.scss';
type StateProps = { showAboutDialog: boolean };
type DispatchProps = { onClose: () => void };
type DispatchProps = { onClose: () => void; onLicenseButtonClick: () => void };
type AboutDialogProps = StateProps & DispatchProps & WithI18nProps;
class AboutDialog extends React.Component<AboutDialogProps> {
render(): JSX.Element {
const { i18n, showAboutDialog, onClose } = this.props;
const { i18n, showAboutDialog, onClose, onLicenseButtonClick } = this.props;
return (
<Dialog title={appName} isOpen={showAboutDialog} onClose={() => onClose()}>
<div className={Classes.DIALOG_BODY}>
@@ -47,12 +48,16 @@ class AboutDialog extends React.Component<AboutDialogProps> {
<small>{legoDisclaimer}</small>
</p>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={() => onLicenseButtonClick()}>
{i18n.translate(AboutStringId.LicenseButtonLabel)}
</Button>
<AnchorButton href={pybricksWebsiteUrl} target="blank_">
{i18n.translate(AboutStringId.WebsiteButtonLabel)}&nbsp;
<ExternalLinkIcon />
</AnchorButton>
</div>
</div>
<LicenseDialog />
</Dialog>
);
}
@@ -64,6 +69,7 @@ const mapStateToProps = (state: RootState): StateProps => ({
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
onClose: (): Action => dispatch(closeAboutDialog()),
onLicenseButtonClick: (): Action => dispatch(openLicenseDialog()),
});
export default connect(
-2
View File
@@ -5,7 +5,6 @@ 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';
@@ -65,7 +64,6 @@ function App(): JSX.Element {
</SplitterLayout>
<StatusBar />
<SettingsDrawer />
<AboutDialog key="about" />
</div>
);
}
+163
View File
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
// The license dialog
import {
Button,
ButtonGroup,
Callout,
Card,
Classes,
Dialog,
NonIdealState,
} 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 { closeLicenseDialog } from '../actions/app';
import { select } from '../actions/license';
import { RootState } from '../reducers';
import { LicenseInfo, LicenseList } from '../reducers/license';
import { appName } from '../settings/ui';
import { LicenseStringId } from './license-i18n';
import en from './license-i18n.en.json';
import './license.scss';
type StateProps = {
showLicenseDialog: boolean;
licenseList: LicenseList | null;
licenseInfo: LicenseInfo | null;
};
type DispatchProps = {
onClose: () => void;
onSelectPackage: (info: LicenseInfo) => void;
};
type LicenseDialogProps = StateProps & DispatchProps & WithI18nProps;
class LicenseDialog extends React.Component<LicenseDialogProps> {
render(): JSX.Element {
const infoDiv = React.createRef<HTMLDivElement>();
const {
i18n,
showLicenseDialog,
onClose,
licenseList,
licenseInfo,
onSelectPackage,
} = this.props;
return (
<Dialog
title={i18n.translate(LicenseStringId.Title)}
isOpen={showLicenseDialog}
onClose={() => onClose()}
className="pb-license-dialog"
>
<div className={Classes.DIALOG_BODY}>
<Callout className={Classes.INTENT_PRIMARY} icon="info-sign">
{i18n.translate(LicenseStringId.Description, {
name: appName,
})}
</Callout>
<Callout className="pb-license-browser">
<div className="pb-license-list">
{licenseList === null ? (
// TODO: this should be translated and hooked to
// state indicating if download is in progress
// or there was an actual failure.
<NonIdealState>
Failed to load license data.
</NonIdealState>
) : (
<ButtonGroup
minimal={true}
vertical={true}
alignText="left"
>
{licenseList.map((info) => (
<Button
key={`${info.name}@${info.version}`}
onClick={() => {
infoDiv.current?.scrollTo(0, 0);
onSelectPackage(info);
}}
>
{info.name}
</Button>
))}
</ButtonGroup>
)}
</div>
<div className="pb-license-info" ref={infoDiv}>
{licenseInfo == null ? (
<NonIdealState>
{i18n.translate(LicenseStringId.SelectPackageHelp)}
</NonIdealState>
) : (
<div>
<Card>
<p>
<strong>
{i18n.translate(
LicenseStringId.PackageLabel,
)}
</strong>{' '}
{licenseInfo.name}{' '}
<span className={Classes.TEXT_MUTED}>
v{licenseInfo.version}
</span>
</p>
{licenseInfo.author && (
<p>
<strong>
{' '}
{i18n.translate(
LicenseStringId.AuthorLabel,
)}
</strong>{' '}
{licenseInfo.author}
</p>
)}
<p>
<strong>
{' '}
{i18n.translate(
LicenseStringId.LicenseLabel,
)}
</strong>{' '}
{licenseInfo.license}
</p>
</Card>
<div className="pb-license-text">
<pre>{licenseInfo.licenseText}</pre>
</div>
</div>
)}
</div>
</Callout>
</div>
</Dialog>
);
}
}
const mapStateToProps = (state: RootState): StateProps => ({
showLicenseDialog: state.app.showLicenseDialog,
licenseList: state.license.list,
licenseInfo: state.license.selected,
});
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
onClose: (): Action => dispatch(closeLicenseDialog()),
onSelectPackage: (info): Action => dispatch(select(info)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withI18n({ id: 'license', fallback: en, translations: { en } })(LicenseDialog));
+2
View File
@@ -27,6 +27,7 @@ import {
} from '../settings/ui';
import { SettingId } from '../settings/user';
import { isMacOS } from '../utils/os';
import AboutDialog from './AboutDialog';
import ExternalLinkIcon from './ExternalLinkIcon';
import { SettingsStringId } from './settings-i18n';
import en from './settings-i18n.en.json';
@@ -192,6 +193,7 @@ class SettingsDrawer extends React.PureComponent<SettingsProps> {
>
{i18n.translate(SettingsStringId.HelpAboutLabel)}
</Button>
<AboutDialog />
</ButtonGroup>
</FormGroup>
</div>
+1
View File
@@ -1,6 +1,7 @@
{
"about": {
"description": "A simple web app for programming LEGO® Powered Up smart hubs using Pybricks MicroPython.",
"licenseButton": { "label": "Open Source Licenses" },
"websiteButton": { "label": "Pybricks Website" }
}
}
+1
View File
@@ -5,5 +5,6 @@
export enum AboutStringId {
Description = 'about.description',
LicenseButtonLabel = 'about.licenseButton.label',
WebsiteButtonLabel = 'about.websiteButton.label',
}
+12
View File
@@ -0,0 +1,12 @@
{
"license": {
"title": "Open Source Licenses",
"description": "{name} is built on open source software. By using {name} you are agreeing to the terms and conditions of all of the included software licenses.",
"packageLabel": "Package:",
"authorLabel": "Author:",
"licenseLabel": "License:",
"help": {
"selectPackage": "Select a package to view the license."
}
}
}
+12
View File
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { lookup } from '../../test';
import { LicenseStringId } from './license-i18n';
import en from './license-i18n.en.json';
describe('Ensure .json file has matches for LicenseStringId', () => {
test.each(Object.values(LicenseStringId))('%s', (id) => {
expect(lookup(en, id)).toBeDefined();
});
});
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
// License dialog translation keys.
export enum LicenseStringId {
Title = 'license.title',
Description = 'license.description',
PackageLabel = 'license.packageLabel',
AuthorLabel = 'license.authorLabel',
LicenseLabel = 'license.licenseLabel',
SelectPackageHelp = 'license.help.selectPackage',
}
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2021 The Pybricks Authors
// Custom styling for the LicenseDialog control.
@import '../variables.scss';
.pb-license-dialog {
width: 1200px;
}
.pb-license-browser {
margin-top: 16px;
display: flex;
height: 600px;
}
.pb-license-list {
width: 25%;
flex-flow: column;
overflow: auto;
}
.pb-license-info {
width: 75%;
flex-flow: column;
padding: 0px 16px;
overflow: auto;
}
.pb-license-text {
padding: 0px 16px;
}