toolbar/buttons: consolidate buttons
@@ -10,11 +10,9 @@ import {
|
||||
useHotkeys,
|
||||
} from '@blueprintjs/core';
|
||||
import { Tooltip2 } from '@blueprintjs/popover2';
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { tooltipDelay } from '../app/constants';
|
||||
import { pointerEventsNone } from '../utils/react';
|
||||
import { I18nId } from './i18n';
|
||||
|
||||
const smallScreenThreshold = 700;
|
||||
|
||||
@@ -24,9 +22,7 @@ export interface ActionButtonProps {
|
||||
/** Keyboard shortcut. */
|
||||
readonly keyboardShortcut?: string;
|
||||
/** Tooltip text that appears when hovering over the button. */
|
||||
readonly tooltip: I18nId;
|
||||
/** Tooltip text that appears when hovering over the button and @showProgress is true. */
|
||||
readonly progressTooltip?: I18nId;
|
||||
readonly tooltip: string;
|
||||
/** Icon shown on the button. */
|
||||
readonly icon: string;
|
||||
/** When true or undefined, the button is enabled. */
|
||||
@@ -43,15 +39,12 @@ const ActionButton: React.VoidFunctionComponent<ActionButtonProps> = ({
|
||||
label,
|
||||
keyboardShortcut,
|
||||
tooltip,
|
||||
progressTooltip,
|
||||
icon,
|
||||
enabled,
|
||||
showProgress,
|
||||
progress,
|
||||
onAction,
|
||||
}) => {
|
||||
const [i18n] = useI18n();
|
||||
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(
|
||||
window.innerWidth <= smallScreenThreshold,
|
||||
);
|
||||
@@ -66,15 +59,6 @@ const ActionButton: React.VoidFunctionComponent<ActionButtonProps> = ({
|
||||
|
||||
const buttonSize = isSmallScreen ? SpinnerSize.SMALL : SpinnerSize.STANDARD;
|
||||
|
||||
const tooltipText =
|
||||
showProgress && progressTooltip
|
||||
? i18n.translate(progressTooltip, {
|
||||
percent:
|
||||
progress === undefined ? '' : i18n.formatPercentage(progress),
|
||||
})
|
||||
: i18n.translate(tooltip) +
|
||||
(keyboardShortcut ? ` (${keyboardShortcut})` : '');
|
||||
|
||||
const hotkeys = useMemo(() => {
|
||||
if (!keyboardShortcut) {
|
||||
return [];
|
||||
@@ -86,7 +70,7 @@ const ActionButton: React.VoidFunctionComponent<ActionButtonProps> = ({
|
||||
allowInInput: true,
|
||||
preventDefault: true,
|
||||
combo: keyboardShortcut.replaceAll('-', '+'),
|
||||
label: i18n.translate(tooltip),
|
||||
label,
|
||||
onKeyDown: () => {
|
||||
if (enabled) {
|
||||
onAction();
|
||||
@@ -94,13 +78,13 @@ const ActionButton: React.VoidFunctionComponent<ActionButtonProps> = ({
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [keyboardShortcut, tooltip, enabled, onAction, i18n]);
|
||||
}, [keyboardShortcut, tooltip, enabled, label, onAction]);
|
||||
|
||||
useHotkeys(hotkeys);
|
||||
|
||||
return (
|
||||
<Tooltip2
|
||||
content={tooltipText}
|
||||
content={tooltip}
|
||||
placement="bottom"
|
||||
hoverOpenDelay={tooltipDelay}
|
||||
renderTarget={({
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
|
||||
import { Button, IRef, Intent, Spinner, SpinnerSize } from '@blueprintjs/core';
|
||||
import { Tooltip2 } from '@blueprintjs/popover2';
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { tooltipDelay } from '../app/constants';
|
||||
import { pointerEventsNone } from '../utils/react';
|
||||
import { I18nId } from './i18n';
|
||||
|
||||
const smallScreenThreshold = 700;
|
||||
export interface OpenFileButtonProps {
|
||||
@@ -17,7 +15,7 @@ export interface OpenFileButtonProps {
|
||||
/** The accepted file extension */
|
||||
readonly fileExtension: string;
|
||||
/** Tooltip text that appears when hovering over the button. */
|
||||
readonly tooltip: I18nId;
|
||||
readonly tooltip: string;
|
||||
/** Icon shown on the button. */
|
||||
readonly icon: string;
|
||||
/** When true or undefined, the button is enabled. */
|
||||
@@ -49,8 +47,6 @@ const OpenFileButton: React.VoidFunctionComponent<OpenFileButtonProps> = ({
|
||||
onReject,
|
||||
onClick,
|
||||
}) => {
|
||||
const [i18n] = useI18n();
|
||||
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(
|
||||
window.innerWidth <= smallScreenThreshold,
|
||||
);
|
||||
@@ -101,17 +97,7 @@ const OpenFileButton: React.VoidFunctionComponent<OpenFileButtonProps> = ({
|
||||
|
||||
return (
|
||||
<Tooltip2
|
||||
content={i18n.translate(
|
||||
tooltip,
|
||||
tooltip === I18nId.FlashProgress
|
||||
? {
|
||||
percent:
|
||||
progress === undefined
|
||||
? ''
|
||||
: i18n.formatPercentage(progress),
|
||||
}
|
||||
: undefined,
|
||||
)}
|
||||
content={tooltip}
|
||||
placement="bottom"
|
||||
hoverOpenDelay={tooltipDelay}
|
||||
renderTarget={({
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
import { ButtonGroup } from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
import FlashButton from '../firmware/FlashButton';
|
||||
import BluetoothButton from '../hub/BluetoothButton';
|
||||
import ReplButton from '../hub/ReplButton';
|
||||
import RunButton from '../hub/RunButton';
|
||||
import StopButton from '../hub/StopButton';
|
||||
import SettingsButton from '../settings/SettingsButton';
|
||||
import SettingsDrawer from '../settings/SettingsDrawer';
|
||||
import { preventBrowserNativeContextMenu } from '../utils/react';
|
||||
import BluetoothButton from './buttons/bluetooth/BluetoothButton';
|
||||
import FlashButton from './buttons/flash/FlashButton';
|
||||
import ReplButton from './buttons/repl/ReplButton';
|
||||
import RunButton from './buttons/run/RunButton';
|
||||
import SettingsButton from './buttons/settings/SettingsButton';
|
||||
import StopButton from './buttons/stop/StopButton';
|
||||
|
||||
import './toolbar.scss';
|
||||
|
||||
@@ -24,19 +24,16 @@ const Toolbar: React.VFC = (_props) => {
|
||||
className="pb-toolbar"
|
||||
>
|
||||
<ButtonGroup className="pb-toolbar-group pb-align-left">
|
||||
<FlashButton label="Flash" />
|
||||
<BluetoothButton label="Bluetooth" />
|
||||
<FlashButton />
|
||||
<BluetoothButton />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="pb-toolbar-group pb-align-left">
|
||||
<RunButton label="Run" keyboardShortcut="F5" />
|
||||
<StopButton label="Stop" keyboardShortcut="F6" />
|
||||
<ReplButton label="REPL" />
|
||||
<RunButton />
|
||||
<StopButton />
|
||||
<ReplButton />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="pb-toolbar-group pb-align-right">
|
||||
<SettingsButton
|
||||
label="Settings"
|
||||
onAction={() => setIsSettingsDrawerOpen(true)}
|
||||
/>
|
||||
<SettingsButton onAction={() => setIsSettingsDrawerOpen(true)} />
|
||||
<SettingsDrawer
|
||||
isOpen={isSettingsDrawerOpen}
|
||||
onClose={() => setIsSettingsDrawerOpen(false)}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../../../test';
|
||||
import { toggleBluetooth } from '../../../ble/actions';
|
||||
import BluetoothButton from './BluetoothButton';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should dispatch action when clicked', () => {
|
||||
const [button, dispatch] = testRender(<BluetoothButton />);
|
||||
|
||||
button.getByRole('button', { name: 'Bluetooth' }).click();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(toggleBluetooth());
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { toggleBluetooth } from '../../../ble/actions';
|
||||
import { BleConnectionState } from '../../../ble/reducers';
|
||||
import { BootloaderConnectionState } from '../../../lwp3-bootloader/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton from '../../ActionButton';
|
||||
import connectedIcon from './connected.svg';
|
||||
import disconnectedIcon from './disconnected.svg';
|
||||
import { I18nId } from './i18n';
|
||||
|
||||
const BluetoothButton: React.VFC = () => {
|
||||
const bootloaderConnection = useSelector((s) => s.bootloader.connection);
|
||||
const bleConnection = useSelector((s) => s.ble.connection);
|
||||
|
||||
const isDisconnected =
|
||||
bootloaderConnection === BootloaderConnectionState.Disconnected &&
|
||||
bleConnection === BleConnectionState.Disconnected;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [i18n] = useI18n();
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
tooltip={i18n.translate(
|
||||
isDisconnected ? I18nId.TooltipConnect : I18nId.TooltipDisconnect,
|
||||
)}
|
||||
icon={isDisconnected ? disconnectedIcon : connectedIcon}
|
||||
enabled={isDisconnected || bleConnection === BleConnectionState.Connected}
|
||||
showProgress={bleConnection === BleConnectionState.Connecting}
|
||||
onAction={() => dispatch(toggleBluetooth())}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BluetoothButton;
|
||||
@@ -0,0 +1,177 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
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:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="bt-connected.svg"
|
||||
inkscape:export-filename="button.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="light-circle-fill"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(1.6201655,0,0,1.6201655,-208.26055,-377.58061)">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="icon-contents"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.78378378,0,0,0.78378378,-217.21909,-864.32335)">
|
||||
<stop
|
||||
style="stop-color:#3880b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5179" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-circle-edge"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04571075,0,0,0.26284445,-63.602396,-483.67016)">
|
||||
<stop
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5173" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(1195.9167,-717.54999)">
|
||||
<stop
|
||||
style="stop-color:#333333;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1439" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(4643.556,-4.2349475e-6)">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1389" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-313.01005,-269.76128)">
|
||||
<stop
|
||||
style="stop-color:#4e4e4e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1184" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-78.252472)">
|
||||
<stop
|
||||
style="stop-color:#eeeeee;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1172" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#light-circle-fill"
|
||||
id="linearGradient5199"
|
||||
x1="76.890312"
|
||||
y1="96.730286"
|
||||
x2="84.212921"
|
||||
y2="96.730286"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.2728572,0,0,1.2728572,-22.909817,-26.355555)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="21.730624"
|
||||
inkscape:cy="15.927965"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g1254"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1853"
|
||||
inkscape:window-height="1145"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
units="px"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:snap-global="false"
|
||||
showguides="true"
|
||||
inkscape:pagecheckerboard="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<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>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<g
|
||||
transform="matrix(1.5712682,0,0,1.5712682,-114.96952,131.35809)"
|
||||
id="g1254">
|
||||
<path
|
||||
style="fill:none;stroke:url(#linearGradient5199);stroke-width:1.34710717;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 75.46292,99.915389 7.760721,-6.421198 -3.784681,-3.146812 v 12.778601 l 3.805941,-3.380689 -7.856399,-6.463723"
|
||||
id="path1061"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:1.34710717;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1063"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="82.457184"
|
||||
sodipodi:cy="96.529022"
|
||||
sodipodi:rx="2.976866"
|
||||
sodipodi:ry="2.976866"
|
||||
sodipodi:start="5.5902437"
|
||||
sodipodi:end="0.68451919"
|
||||
d="m 84.747496,94.627391 a 2.976866,2.976866 0 0 1 0.01593,3.783905 l -2.306247,-1.882274 z"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:1.34710717;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 86.772857,92.884335 -1.068213,0.886672 c 1.320158,1.59269 1.328977,3.896842 0.02105,5.499591 -0.0025,0.0015 -0.0062,-3.05e-4 1.077354,0.884102 1.729557,-2.118935 1.716876,-5.165832 -0.03026,-7.270296 z"
|
||||
id="path1065"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
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:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="bt-disconnected.svg"
|
||||
inkscape:export-filename="button.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="light-circle-fill"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(1.6201655,0,0,1.6201655,-208.26055,-377.58061)">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="icon-contents"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.78378378,0,0,0.78378378,-217.21909,-864.32335)">
|
||||
<stop
|
||||
style="stop-color:#3880b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5179" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-circle-edge"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04571075,0,0,0.26284445,-63.602396,-483.67016)">
|
||||
<stop
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5173" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(1195.9167,-717.54999)">
|
||||
<stop
|
||||
style="stop-color:#333333;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1439" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(4643.556,-4.2349475e-6)">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1389" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-313.01005,-269.76128)">
|
||||
<stop
|
||||
style="stop-color:#4e4e4e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1184" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-78.252472)">
|
||||
<stop
|
||||
style="stop-color:#eeeeee;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1172" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#light-circle-fill"
|
||||
id="linearGradient5199"
|
||||
x1="76.890312"
|
||||
y1="96.730286"
|
||||
x2="84.212921"
|
||||
y2="96.730286"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.2728572,0,0,1.2728572,-22.909817,-26.355555)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="30.590127"
|
||||
inkscape:cy="29.036914"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g1254"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1853"
|
||||
inkscape:window-height="1145"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
units="px"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:snap-global="false"
|
||||
showguides="true"
|
||||
inkscape:pagecheckerboard="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<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>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<g
|
||||
transform="matrix(1.5712682,0,0,1.5712682,-114.96952,131.35809)"
|
||||
id="g1254">
|
||||
<path
|
||||
style="fill:none;stroke:url(#linearGradient5199);stroke-width:1.34710717;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 75.46292,99.915389 7.760721,-6.421198 -3.784681,-3.146812 v 12.778601 l 3.805941,-3.380689 -7.856399,-6.463723"
|
||||
id="path1061"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
TooltipConnect = 'tooltip.connect',
|
||||
TooltipDisconnect = 'tooltip.disconnect',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Bluetooth",
|
||||
"tooltip": {
|
||||
"connect": "Connect using Bluetooth",
|
||||
"disconnect": "Disconnect Bluetooth"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../../../test';
|
||||
import { flashFirmware } from '../../../firmware/actions';
|
||||
import FlashButton from './FlashButton';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should dispatch action when clicked', () => {
|
||||
const [button, dispatch] = testRender(<FlashButton />);
|
||||
|
||||
button.getByRole('button', { name: 'Flash' }).click();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(flashFirmware(null, false, ''));
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { BleConnectionState } from '../../../ble/reducers';
|
||||
import { flashFirmware } from '../../../firmware/actions';
|
||||
import { BootloaderConnectionState } from '../../../lwp3-bootloader/reducers';
|
||||
import * as notificationActions from '../../../notifications/actions';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import {
|
||||
useSettingFlashCurrentProgram,
|
||||
useSettingHubName,
|
||||
} from '../../../settings/hooks';
|
||||
import OpenFileButton from '../../../toolbar/OpenFileButton';
|
||||
import { I18nId } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
const FlashButton: React.VFC = () => {
|
||||
const bootloaderConnection = useSelector((s) => s.bootloader.connection);
|
||||
const bleConnection = useSelector((s) => s.ble.connection);
|
||||
const flashing = useSelector((s) => s.firmware.flashing);
|
||||
const progress = useSelector((s) => s.firmware.progress);
|
||||
const [isSettingFlashCurrentProgramEnabled] = useSettingFlashCurrentProgram();
|
||||
const { hubName } = useSettingHubName();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [i18n] = useI18n();
|
||||
|
||||
return (
|
||||
<OpenFileButton
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
fileExtension=".zip"
|
||||
icon={icon}
|
||||
tooltip={
|
||||
progress
|
||||
? i18n.translate(I18nId.TooltipProgress, {
|
||||
percent: i18n.formatPercentage(progress),
|
||||
})
|
||||
: i18n.translate(I18nId.TooltipAction)
|
||||
}
|
||||
enabled={
|
||||
bootloaderConnection === BootloaderConnectionState.Disconnected &&
|
||||
bleConnection === BleConnectionState.Disconnected
|
||||
}
|
||||
showProgress={flashing}
|
||||
progress={progress === null ? undefined : progress}
|
||||
onFile={(data) =>
|
||||
dispatch(
|
||||
flashFirmware(data, isSettingFlashCurrentProgramEnabled, hubName),
|
||||
)
|
||||
}
|
||||
onReject={(file) =>
|
||||
dispatch(
|
||||
notificationActions.add(
|
||||
'error',
|
||||
`'${file.name}' is not a valid firmware file.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
flashFirmware(null, isSettingFlashCurrentProgramEnabled, hubName),
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlashButton;
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
TooltipAction = 'tooltip.action',
|
||||
TooltipProgress = 'tooltip.progress',
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
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:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="firmware.svg"
|
||||
inkscape:export-filename="button.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="light-circle-fill"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04149181,0,0,0.04149181,1.7417234,549.61437)">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="icon-contents"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.78378378,0,0,0.78378378,-217.21909,-864.32335)">
|
||||
<stop
|
||||
style="stop-color:#3880b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5179" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-circle-edge"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04571075,0,0,0.26284445,-63.602396,-483.67016)">
|
||||
<stop
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5173" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(1195.9167,-717.54999)">
|
||||
<stop
|
||||
style="stop-color:#333333;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1439" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(4643.556,-4.2349475e-6)">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1389" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-313.01005,-269.76128)">
|
||||
<stop
|
||||
style="stop-color:#4e4e4e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1184" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-78.252472)">
|
||||
<stop
|
||||
style="stop-color:#eeeeee;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1172" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#light-circle-fill"
|
||||
id="linearGradient5187"
|
||||
x1="170.49026"
|
||||
y1="91.974777"
|
||||
x2="171.5486"
|
||||
y2="91.974777"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.2770973,0,0,2.2770973,-375.89057,64.428671)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="33.439167"
|
||||
inkscape:cy="33.623378"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1853"
|
||||
inkscape:window-height="1145"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
units="px"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:snap-global="false"
|
||||
showguides="true"
|
||||
inkscape:pagecheckerboard="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<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>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<path
|
||||
transform="matrix(0,2.2770972,-2.2770972,0,242.26153,191.39198)"
|
||||
sodipodi:type="star"
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:0.92954594;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path924"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="37.758598"
|
||||
sodipodi:cy="100.44551"
|
||||
sodipodi:r1="1.9985147"
|
||||
sodipodi:r2="0.99925733"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 39.757113,100.44551 -1.498886,0.86538 -1.498886,0.86539 v -1.73077 -1.730764 l 1.498886,0.865383 z"
|
||||
inkscape:transform-center-y="0.56883747"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:transform-center-x="-5.2936222e-06" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path926"
|
||||
d="m 13.537299,271.92763 v 3.8731"
|
||||
style="fill:#4e4e4e;fill-opacity:1;stroke:url(#linearGradient5187);stroke-width:2.11666656;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<rect
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:0.59935653;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect940"
|
||||
width="24.01683"
|
||||
height="10.585944"
|
||||
x="1.2748805"
|
||||
y="284.17142"
|
||||
rx="0"
|
||||
ry="0"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<rect
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="283.18323"
|
||||
x="3.4314961"
|
||||
height="10.585944"
|
||||
width="2.7715306"
|
||||
id="rect942"
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:0.20360442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<rect
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:0.20360442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect944"
|
||||
width="2.7715306"
|
||||
height="10.585944"
|
||||
x="9.0755405"
|
||||
y="283.18323"
|
||||
rx="0"
|
||||
ry="0"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<rect
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="283.18323"
|
||||
x="14.719461"
|
||||
height="10.585944"
|
||||
width="2.7715306"
|
||||
id="rect947"
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:0.20360442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<rect
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:0.20360442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect949"
|
||||
width="2.7715306"
|
||||
height="10.585944"
|
||||
x="20.363565"
|
||||
y="283.18323"
|
||||
rx="0"
|
||||
ry="0"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Flash",
|
||||
"tooltip": {
|
||||
"action": "Install Pybricks firmware",
|
||||
"progress": "Flashing… {percent}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../../../test';
|
||||
import { repl } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import ReplButton from './ReplButton';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should dispatch action when clicked', () => {
|
||||
const [button, dispatch] = testRender(<ReplButton />, {
|
||||
hub: { runtime: HubRuntimeState.Idle },
|
||||
});
|
||||
|
||||
button.getByRole('button', { name: 'REPL' }).click();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(repl());
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { repl } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton from '../../ActionButton';
|
||||
import { I18nId } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
const ReplButton: React.VFC = () => {
|
||||
const enabled = useSelector((s) => s.hub.runtime === HubRuntimeState.Idle);
|
||||
const [i18n] = useI18n();
|
||||
const dispatch = useDispatch();
|
||||
const action = useCallback(() => dispatch(repl()), [dispatch]);
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
tooltip={i18n.translate(I18nId.Tooltip)}
|
||||
icon={icon}
|
||||
enabled={enabled}
|
||||
onAction={action}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplButton;
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
Tooltip = 'tooltip',
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
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:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="repl.svg"
|
||||
inkscape:export-filename="button.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="light-circle-fill"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-111.65281,-194.6407)">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="icon-contents"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.78378378,0,0,0.78378378,-217.21909,-864.32335)">
|
||||
<stop
|
||||
style="stop-color:#3880b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5179" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-circle-edge"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04571075,0,0,0.26284445,-63.602396,-483.67016)">
|
||||
<stop
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5173" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(1195.9167,-717.54999)">
|
||||
<stop
|
||||
style="stop-color:#333333;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1439" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(4643.556,-4.2349475e-6)">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1389" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-313.01005,-269.76128)">
|
||||
<stop
|
||||
style="stop-color:#4e4e4e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1184" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-78.252472)">
|
||||
<stop
|
||||
style="stop-color:#eeeeee;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1172" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#light-circle-fill"
|
||||
id="linearGradient5189"
|
||||
x1="151.17233"
|
||||
y1="96.594086"
|
||||
x2="155.53439"
|
||||
y2="96.594086"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.4800054,0,0,2.5237898,-359.04545,39.652624)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#light-circle-fill"
|
||||
id="linearGradient5191"
|
||||
x1="147.6288"
|
||||
y1="96.594086"
|
||||
x2="151.99086"
|
||||
y2="96.594086"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.4800054,0,0,2.5237898,-358.13592,39.652624)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#light-circle-fill"
|
||||
id="linearGradient5193"
|
||||
x1="144.08528"
|
||||
y1="96.594086"
|
||||
x2="148.44734"
|
||||
y2="96.594086"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.4800054,0,0,2.5237898,-357.22644,39.652624)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="26.727876"
|
||||
inkscape:cy="22.121513"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1853"
|
||||
inkscape:window-height="1145"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
units="px"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:snap-global="false"
|
||||
showguides="true"
|
||||
inkscape:pagecheckerboard="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<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>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<path
|
||||
style="fill:none;stroke:url(#linearGradient5193);stroke-width:2.11666656;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 0.93974812,276.85042 7.92259998,6.63441 -7.7594,6.53898"
|
||||
id="path1115"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1135"
|
||||
d="m 8.8182101,276.85042 7.9225999,6.63441 -7.7593799,6.53898"
|
||||
style="fill:none;stroke:url(#linearGradient5191);stroke-width:2.11666656;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="fill:none;stroke:url(#linearGradient5189);stroke-width:2.11666656;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 16.696658,276.85042 7.92262,6.63441 -7.7594,6.53898"
|
||||
id="path1137"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "REPL",
|
||||
"tooltip": "Start REPL in terminal"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../../../test';
|
||||
import { downloadAndRun } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import RunButton from './RunButton';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should dispatch action when clicked', () => {
|
||||
const [button, dispatch] = testRender(<RunButton />, {
|
||||
app: { hasEditor: true },
|
||||
hub: { runtime: HubRuntimeState.Idle },
|
||||
});
|
||||
|
||||
button.getByRole('button', { name: 'Run' }).click();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(downloadAndRun());
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { downloadAndRun } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton from '../../ActionButton';
|
||||
import { I18nId } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
const RunButton: React.VFC = () => {
|
||||
const downloadProgress = useSelector((s) => s.hub.downloadProgress);
|
||||
const runtime = useSelector((s) => s.hub.runtime);
|
||||
const hasEditor = useSelector((s) => s.app.hasEditor);
|
||||
const keyboardShortcut = 'F5';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [i18n] = useI18n();
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
keyboardShortcut={keyboardShortcut}
|
||||
tooltip={
|
||||
downloadProgress
|
||||
? i18n.translate(I18nId.TooltipProgress, {
|
||||
percent: i18n.formatPercentage(downloadProgress),
|
||||
})
|
||||
: i18n.translate(I18nId.TooltipAction, { key: keyboardShortcut })
|
||||
}
|
||||
icon={icon}
|
||||
enabled={hasEditor && runtime === HubRuntimeState.Idle}
|
||||
showProgress={runtime === HubRuntimeState.Loading}
|
||||
progress={downloadProgress === null ? undefined : downloadProgress}
|
||||
onAction={() => dispatch(downloadAndRun())}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default RunButton;
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
TooltipAction = 'tooltip.action',
|
||||
TooltipProgress = 'tooltip.progress',
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
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"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="run.svg"
|
||||
inkscape:export-filename="button.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="light-circle-fill"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.52916667,0,0,0.52916667,-54.066878,170.25889)">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="icon-contents"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.78378378,0,0,0.78378378,-217.21909,-864.32335)">
|
||||
<stop
|
||||
style="stop-color:#3880b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5179" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-circle-edge"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04571075,0,0,0.26284445,-63.602396,-483.67016)">
|
||||
<stop
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5173" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(1195.9167,-717.54999)">
|
||||
<stop
|
||||
style="stop-color:#333333;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1439" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(4643.556,-4.2349475e-6)">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1389" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-313.01005,-269.76128)">
|
||||
<stop
|
||||
style="stop-color:#4e4e4e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1184" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-78.252472)">
|
||||
<stop
|
||||
style="stop-color:#eeeeee;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1172" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6568542"
|
||||
inkscape:cx="32.44435"
|
||||
inkscape:cy="20.159859"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1853"
|
||||
inkscape:window-height="1145"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
units="px"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:snap-global="false"
|
||||
showguides="true"
|
||||
inkscape:pagecheckerboard="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<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>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1017"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="10.839368"
|
||||
sodipodi:cy="283.3425"
|
||||
sodipodi:r1="11.6466"
|
||||
sodipodi:r2="5.8232999"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 22.485968,283.3425 -8.73495,5.04312 -8.73495,5.04313 0,-10.08625 0,-10.08625 8.73495,5.04312 z"
|
||||
inkscape:transform-center-x="-1.4558184"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Run",
|
||||
"tooltip": {
|
||||
"action": "Download and run this program ({key})",
|
||||
"progress": "Downloading… {percent}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../../../test';
|
||||
import SettingsButton from './SettingsButton';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should invoke callback when clicked', () => {
|
||||
const handleAction = jest.fn();
|
||||
|
||||
const [button] = testRender(<SettingsButton onAction={handleAction} />);
|
||||
|
||||
button.getByRole('button', { name: 'Settings' }).click();
|
||||
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton';
|
||||
import { I18nId } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
type SettingsButtonProps = Pick<ActionButtonProps, 'onAction'>;
|
||||
|
||||
const SettingsButton: React.VoidFunctionComponent<SettingsButtonProps> = ({
|
||||
onAction,
|
||||
}) => {
|
||||
const [i18n] = useI18n();
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
tooltip={i18n.translate(I18nId.Tooltip)}
|
||||
icon={icon}
|
||||
onAction={onAction}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsButton;
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
Tooltip = 'tooltip',
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.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="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
enable-background="new 0 0 20 20"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="settings.svg"
|
||||
width="50"
|
||||
height="50"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
|
||||
id="metadata12"><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="defs10" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1480"
|
||||
inkscape:window-height="1046"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="11.8"
|
||||
inkscape:cx="24.88532"
|
||||
inkscape:cy="16.779661"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="cog_2_"
|
||||
transform="matrix(2.25,0,0,2.25,2.5,2.5)"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<g
|
||||
id="g4"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="M 19,8 H 16.69 C 16.55,7.54 16.36,7.11 16.13,6.7 L 17.83,5 c 0.39,-0.39 0.39,-1.02 0,-1.41 L 16.42,2.18 c -0.39,-0.39 -1.02,-0.39 -1.41,0 l -1.7,1.7 C 12.9,3.66 12.47,3.47 12.01,3.33 V 1 c 0,-0.55 -0.45,-1 -1,-1 H 9 C 8.45,0 8,0.45 8,1 V 3.33 C 7.52,3.47 7.06,3.67 6.63,3.91 L 5,2.28 C 4.63,1.91 4.02,1.91 3.64,2.28 L 2.28,3.64 C 1.91,4.02 1.91,4.63 2.28,5 L 3.9,6.62 C 3.66,7.06 3.46,7.51 3.31,8 H 1 C 0.45,8 0,8.45 0,9 v 2 c 0,0.55 0.45,1 1,1 h 2.31 c 0.14,0.46 0.33,0.89 0.56,1.3 L 2.17,15 c -0.39,0.39 -0.39,1.02 0,1.41 l 1.41,1.41 c 0.39,0.39 1.02,0.39 1.41,0 l 1.7,-1.7 c 0.41,0.22 0.84,0.41 1.3,0.55 V 19 c 0,0.55 0.45,1 1,1 h 2 c 0.55,0 1,-0.45 1,-1 v -2.33 c 0.48,-0.14 0.94,-0.35 1.37,-0.59 L 15,17.72 c 0.37,0.37 0.98,0.37 1.36,0 l 1.36,-1.36 c 0.37,-0.37 0.37,-0.98 0,-1.36 L 16.1,13.38 C 16.34,12.95 16.55,12.49 16.7,12 H 19 c 0.55,0 1,-0.45 1,-1 V 9 C 20,8.45 19.55,8 19,8 Z m -9,6 C 7.79,14 6,12.21 6,10 6,7.79 7.79,6 10,6 c 2.21,0 4,1.79 4,4 0,2.21 -1.79,4 -4,4 z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;fill:#ffffff;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Settings",
|
||||
"tooltip": "Open settings and help"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../../../test';
|
||||
import { stop } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import StopButton from './StopButton';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('should dispatch action when clicked', () => {
|
||||
const [button, dispatch] = testRender(<StopButton />, {
|
||||
hub: { runtime: HubRuntimeState.Running },
|
||||
});
|
||||
|
||||
button.getByRole('button', { name: 'Stop' }).click();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(stop());
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { stop } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton from '../../ActionButton';
|
||||
import { I18nId } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
const StopButton: React.VFC = () => {
|
||||
const runtime = useSelector((s) => s.hub.runtime);
|
||||
const keyboardShortcut = 'F6';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [i18n] = useI18n();
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
keyboardShortcut={keyboardShortcut}
|
||||
tooltip={i18n.translate(I18nId.Tooltip, { key: keyboardShortcut })}
|
||||
icon={icon}
|
||||
enabled={runtime === HubRuntimeState.Running}
|
||||
onAction={() => dispatch(stop())}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StopButton;
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
Tooltip = 'tooltip',
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
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"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="stop.svg"
|
||||
inkscape:export-filename="button.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="light-circle-fill"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.52916667,0,0,0.52916667,-53.400128,172.5108)">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="icon-contents"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.78378378,0,0,0.78378378,-217.21909,-864.32335)">
|
||||
<stop
|
||||
style="stop-color:#3880b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5179" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-circle-edge"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(0.04571075,0,0,0.26284445,-63.602396,-483.67016)">
|
||||
<stop
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5173" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(1195.9167,-717.54999)">
|
||||
<stop
|
||||
style="stop-color:#333333;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1439" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="dark-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(4643.556,-4.2349475e-6)">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1389" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-front"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-313.01005,-269.76128)">
|
||||
<stop
|
||||
style="stop-color:#4e4e4e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1184" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="light-back"
|
||||
osb:paint="solid"
|
||||
gradientTransform="translate(-78.252472)">
|
||||
<stop
|
||||
style="stop-color:#eeeeee;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1172" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="19.480688"
|
||||
inkscape:cy="15.933037"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1853"
|
||||
inkscape:window-height="1145"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
units="px"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:snap-global="false"
|
||||
showguides="true"
|
||||
inkscape:pagecheckerboard="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<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>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<rect
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:1.58749974;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5272"
|
||||
width="15.200954"
|
||||
height="15.200954"
|
||||
x="5.6828184"
|
||||
y="275.50818"
|
||||
inkscape:export-filename="out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Stop",
|
||||
"tooltip": "Stop everything ({key})"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
//
|
||||
// Toolbar button translation keys.
|
||||
|
||||
export enum I18nId {
|
||||
Run = 'run.action.tooltip',
|
||||
RunProgress = 'run.progress.tooltip',
|
||||
Stop = 'stop.tooltip',
|
||||
Repl = 'repl.tooltip',
|
||||
Flash = 'flash.action.tooltip',
|
||||
FlashProgress = 'flash.progress.tooltip',
|
||||
BluetoothConnect = 'bluetooth.connect.tooltip',
|
||||
BluetoothDisconnect = 'bluetooth.disconnect.tooltip',
|
||||
Settings = 'settings.tooltip',
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"stop": { "tooltip": "Stop everything" },
|
||||
"run": {
|
||||
"action": { "tooltip": "Download and run this program" },
|
||||
"progress": { "tooltip": "Downloading… {percent}" }
|
||||
},
|
||||
"repl": { "tooltip": "Start REPL in terminal" },
|
||||
"bluetooth": {
|
||||
"connect": { "tooltip": "Connect using Bluetooth" },
|
||||
"disconnect": { "tooltip": "Disconnect Bluetooth" }
|
||||
},
|
||||
"flash": {
|
||||
"action": { "tooltip": "Install Pybricks firmware" },
|
||||
"progress": { "tooltip": "Flashing… {percent}" }
|
||||
},
|
||||
"settings": { "tooltip": "Settings" }
|
||||
}
|
||||