mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
toolbar: drop open and saveAs buttons
These have been replace by the explorer.
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import * as notificationActions from '../notifications/actions';
|
||||
import { pythonFileExtension } from '../pybricksMicropython/lib';
|
||||
import OpenFileButton, { OpenFileButtonProps } from '../toolbar/OpenFileButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import { EditorContext } from './Editor';
|
||||
import * as editorActions from './actions';
|
||||
import openIcon from './open.svg';
|
||||
|
||||
type OpenButtonProps = Pick<OpenFileButtonProps, 'id'>;
|
||||
|
||||
const OpenButton: React.FunctionComponent<OpenButtonProps> = (props) => {
|
||||
const { editor } = useContext(EditorContext);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<OpenFileButton
|
||||
fileExtension={pythonFileExtension}
|
||||
tooltip={TooltipId.Open}
|
||||
icon={openIcon}
|
||||
enabled={editor !== null}
|
||||
onFile={(data) => dispatch(editorActions.open(data))}
|
||||
onReject={(file) =>
|
||||
dispatch(
|
||||
notificationActions.add(
|
||||
'error',
|
||||
`'${file.name}' is not a valid python file.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OpenButton;
|
||||
@@ -1,31 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import * as editorActions from '../editor/actions';
|
||||
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import { EditorContext } from './Editor';
|
||||
import downloadIcon from './save.svg';
|
||||
|
||||
type SaveAsButtonProps = Pick<ActionButtonProps, 'id'> &
|
||||
Pick<ActionButtonProps, 'keyboardShortcut'>;
|
||||
|
||||
const SaveAsButton: React.FunctionComponent<SaveAsButtonProps> = (props) => {
|
||||
const { editor } = useContext(EditorContext);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
tooltip={TooltipId.SaveAs}
|
||||
icon={downloadIcon}
|
||||
enabled={editor !== null}
|
||||
onAction={() => dispatch(editorActions.saveAs())}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveAsButton;
|
||||
@@ -1,31 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { createAction } from '../actions';
|
||||
|
||||
/**
|
||||
* Creates an action to save the current file
|
||||
*/
|
||||
export const saveAs = createAction(() => ({
|
||||
type: 'editor.action.saveAs',
|
||||
}));
|
||||
|
||||
/** Action that indicates saving a file succeeded. */
|
||||
export const didSaveAs = createAction(() => ({
|
||||
type: 'editor.action.didSaveAs',
|
||||
}));
|
||||
|
||||
/** Action that indicates saving a file failed. */
|
||||
export const didFailToSaveAs = createAction((err: Error) => ({
|
||||
type: 'editor.action.didFailToSaveAs',
|
||||
err,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Creates an action to save a file
|
||||
* @param data The file data
|
||||
*/
|
||||
export const open = createAction((data: ArrayBuffer) => ({
|
||||
type: 'editor.action.open',
|
||||
data,
|
||||
}));
|
||||
@@ -1,146 +0,0 @@
|
||||
<?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="open.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
|
||||
id="light-circle-fill-3"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(1.4086066e-4,-8.1325944e-5,8.8105755e-5,1.5260364e-4,2250.622,3765.5708)">
|
||||
<stop
|
||||
style="stop-color:#0088ce;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207-6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="14.566286"
|
||||
inkscape:cy="-4.9193288"
|
||||
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:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.83952707;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 0,283.0985 v -10.61134 h 4.6381475 4.638146 l 1.2359985,1.23674 1.235998,1.23674 h 3.842999 5.132843 v 3.04238 3.04239 h 2.770299 c 1.523663,0 2.770297,0.0169 2.770297,0.0373 0,0.0205 -1.130907,2.86443 -2.513131,6.31974 l -2.513134,6.28238 -9.974309,0.0125 L 0,293.70973 Z m 21.446718,4.37796 c 0.908972,-2.27189 1.665144,-4.16968 1.680384,-4.21729 0.02631,-0.0822 0.885319,-0.0866 -7.288433,-0.0866 H 7.2326845 l -1.6812488,4.20244 c -0.9246856,2.31136 -1.668944,4.21476 -1.6539081,4.2298 0.01504,0.015 3.8881179,0.0217 8.6068474,0.0148 l 7.289666,-0.0125 z m -8.649134,-6.43105 H 18.498 v -1.95406 -1.95404 h -3.199894 -4.489738 l -1.2359985,-1.23675 -1.235998,-1.23673 H 5.2812523 2.226133 l 0.00232,7.65542 0.00232,7.65541 1.7882473,-4.46462 1.7882472,-4.46463 h 6.9902555 z"
|
||||
id="path857"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccsssccccccscccccccccccccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
@@ -1,125 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { monaco } from 'react-monaco-editor';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import { didFailToSaveAs, didSaveAs, open, saveAs } from './actions';
|
||||
import editor from './sagas';
|
||||
|
||||
jest.mock('react-monaco-editor');
|
||||
jest.mock('file-saver');
|
||||
|
||||
test('open', async () => {
|
||||
const mockEditor = mock<monaco.editor.ICodeEditor>();
|
||||
const saga = new AsyncSaga(editor, { editor: mockEditor });
|
||||
|
||||
const data = new Uint8Array().buffer;
|
||||
saga.put(open(data));
|
||||
|
||||
expect(mockEditor.setValue).toBeCalled();
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
describe('saveAs', () => {
|
||||
test('web file system api can succeed', async () => {
|
||||
const mockEditor = mock<monaco.editor.ICodeEditor>();
|
||||
const saga = new AsyncSaga(editor, { editor: mockEditor });
|
||||
|
||||
// window.showSaveFilePicker is not defined in the test environment
|
||||
// so we can't use spyOn().
|
||||
const mockWriteable = mock<FileSystemWritableFileStream>();
|
||||
const originalShowSaveFilePicker = window.showSaveFilePicker;
|
||||
window.showSaveFilePicker = jest.fn().mockResolvedValue(
|
||||
mock<FileSystemFileHandle>({
|
||||
createWritable: jest.fn().mockResolvedValue(mockWriteable),
|
||||
}),
|
||||
);
|
||||
|
||||
saga.put(saveAs());
|
||||
|
||||
expect(mockEditor.getValue).toHaveBeenCalled();
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toEqual(didSaveAs());
|
||||
expect(window.showSaveFilePicker).toHaveBeenCalled();
|
||||
expect(mockWriteable.write).toHaveBeenCalled();
|
||||
expect(mockWriteable.close).toHaveBeenCalled();
|
||||
|
||||
await saga.end();
|
||||
|
||||
window.showSaveFilePicker = originalShowSaveFilePicker;
|
||||
});
|
||||
|
||||
test('web file system api can fail', async () => {
|
||||
const mockEditor = mock<monaco.editor.ICodeEditor>();
|
||||
const saga = new AsyncSaga(editor, { editor: mockEditor });
|
||||
|
||||
// window.showSaveFilePicker is not defined in the test environment
|
||||
// so we can't use spyOn().
|
||||
const testError = new Error('test error');
|
||||
const originalShowSaveFilePicker = window.showSaveFilePicker;
|
||||
window.showSaveFilePicker = jest.fn().mockResolvedValue(
|
||||
mock<FileSystemFileHandle>({
|
||||
createWritable: jest.fn().mockRejectedValue(testError),
|
||||
}),
|
||||
);
|
||||
|
||||
saga.put(saveAs());
|
||||
|
||||
expect(mockEditor.getValue).toHaveBeenCalled();
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toEqual(didFailToSaveAs(testError));
|
||||
expect(window.showSaveFilePicker).toHaveBeenCalled();
|
||||
|
||||
await saga.end();
|
||||
|
||||
window.showSaveFilePicker = originalShowSaveFilePicker;
|
||||
});
|
||||
|
||||
test('fallback can succeed', async () => {
|
||||
const mockEditor = mock<monaco.editor.ICodeEditor>();
|
||||
const saga = new AsyncSaga(editor, { editor: mockEditor });
|
||||
|
||||
const mockFileSaverSaveAs = jest.spyOn(FileSaver, 'saveAs');
|
||||
|
||||
saga.put(saveAs());
|
||||
|
||||
expect(mockEditor.getValue).toHaveBeenCalled();
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toEqual(didSaveAs());
|
||||
expect(mockFileSaverSaveAs).toHaveBeenCalled();
|
||||
|
||||
await saga.end();
|
||||
|
||||
mockFileSaverSaveAs.mockRestore();
|
||||
});
|
||||
|
||||
test('fallback can fail', async () => {
|
||||
const mockEditor = mock<monaco.editor.ICodeEditor>();
|
||||
const saga = new AsyncSaga(editor, { editor: mockEditor });
|
||||
|
||||
const testError = new Error('test error');
|
||||
const mockFileSaverSaveAs = jest
|
||||
.spyOn(FileSaver, 'saveAs')
|
||||
.mockImplementation(() => {
|
||||
throw testError;
|
||||
});
|
||||
|
||||
saga.put(saveAs());
|
||||
|
||||
expect(mockEditor.getValue).toHaveBeenCalled();
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toEqual(didFailToSaveAs(testError));
|
||||
expect(mockFileSaverSaveAs).toHaveBeenCalled();
|
||||
|
||||
await saga.end();
|
||||
|
||||
mockFileSaverSaveAs.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import { call, getContext, put, takeEvery } from 'typed-redux-saga/macro';
|
||||
import { pythonFileExtension, pythonFileMimeType } from '../pybricksMicropython/lib';
|
||||
import { ensureError } from '../utils';
|
||||
import { EditorType } from './Editor';
|
||||
import { didFailToSaveAs, didSaveAs, open, saveAs } from './actions';
|
||||
|
||||
/**
|
||||
* Partial saga context type for context used in the editor sagas.
|
||||
*/
|
||||
export type EditorSagaContext = { editor: EditorType };
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
function* handleOpen(action: ReturnType<typeof open>): Generator {
|
||||
const editor = yield* getContext<EditorType>('editor');
|
||||
|
||||
// istanbul ignore next: it is a bug to dispatch this action with no current editor
|
||||
if (editor === null) {
|
||||
console.error('open: No current editor');
|
||||
return;
|
||||
}
|
||||
|
||||
const text = decoder.decode(action.data);
|
||||
editor.setValue(text);
|
||||
}
|
||||
|
||||
function* handleSaveAs(): Generator {
|
||||
const editor = yield* getContext<EditorType>('editor');
|
||||
|
||||
// istanbul ignore next: it is a bug to dispatch this action with no current editor
|
||||
if (editor === null) {
|
||||
console.error('saveAs: No current editor');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = editor.getValue();
|
||||
const blob = new Blob([data], { type: `${pythonFileMimeType};charset=utf-8` });
|
||||
|
||||
if (window.showSaveFilePicker) {
|
||||
// This uses https://wicg.github.io/file-system-access which is not
|
||||
// available in all browsers
|
||||
try {
|
||||
const handle = yield* call(() =>
|
||||
window.showSaveFilePicker({
|
||||
suggestedName: 'main.py',
|
||||
types: [
|
||||
{
|
||||
accept: { [pythonFileMimeType]: pythonFileExtension },
|
||||
// TODO: translate description
|
||||
description: 'Python Files',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const writeable = yield* call(() => handle.createWritable());
|
||||
yield* call(() => writeable.write(blob));
|
||||
yield* call(() => writeable.close());
|
||||
} catch (err) {
|
||||
yield* put(didFailToSaveAs(ensureError(err)));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// this is a fallback to use the standard browser download mechanism
|
||||
try {
|
||||
FileSaver.saveAs(blob, 'main.py');
|
||||
} catch (err) {
|
||||
yield* put(didFailToSaveAs(ensureError(err)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield* put(didSaveAs());
|
||||
}
|
||||
|
||||
export default function* (): Generator {
|
||||
yield* takeEvery(open, handleOpen);
|
||||
yield* takeEvery(saveAs, handleSaveAs);
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
<?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="download.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.28001736,0,0,0.23860295,-25.635992,341.90119)">
|
||||
<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="linearGradient5201"
|
||||
x1="48.865211"
|
||||
y1="96.215172"
|
||||
x2="57.67136"
|
||||
y2="96.215172"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.4926212,0,0,2.4926212,-119.48235,43.562502)" />
|
||||
<linearGradient
|
||||
id="light-circle-fill-6"
|
||||
osb:paint="solid"
|
||||
gradientTransform="matrix(1.4086066e-4,-8.1325943e-5,8.8105753e-5,1.5260364e-4,2183.2035,3758.7078)">
|
||||
<stop
|
||||
style="stop-color:#0088ce;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5207-2" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="23.424389"
|
||||
inkscape:cy="35.145846"
|
||||
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 />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path919"
|
||||
d="m 3.6391222,283.39048 v -9.65619 h 6.9396798 8.87582 c 1.811001,1.81136 1.68586,1.68657 3.496881,3.49792 v 8.87531 6.93915 H 13.295302 3.6391222 Z"
|
||||
style="fill:none;fill-opacity:0.50267382;stroke:url(#linearGradient5201);stroke-width:2.11666679;stroke-linecap:round;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
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="287.20477"
|
||||
x="8.4382458"
|
||||
height="6.0376749"
|
||||
width="9.2444725"
|
||||
id="rect915"
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:1.39999986;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
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-filename="out.png"
|
||||
style="fill:url(#light-circle-fill);fill-opacity:1;stroke:none;stroke-width:1.39999986;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect838"
|
||||
width="9.2444725"
|
||||
height="5.1447062"
|
||||
x="8.4382458"
|
||||
y="277.1506"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -16,7 +16,6 @@ import {
|
||||
BleDeviceFailToConnectReasonType,
|
||||
didFailToConnect as bleDidFailToConnect,
|
||||
} from '../ble/actions';
|
||||
import { didFailToSaveAs } from '../editor/actions';
|
||||
import { explorerDeleteFile, explorerDidFailToImportFiles } from '../explorer/actions';
|
||||
import {
|
||||
fileStorageDeleteFile,
|
||||
@@ -111,7 +110,6 @@ test.each([
|
||||
didFailToFinish(FailToFinishReasonType.Unknown, new Error('test error')),
|
||||
appDidCheckForUpdate(false),
|
||||
bleDIServiceDidReceiveFirmwareRevision('3.0.0'),
|
||||
didFailToSaveAs(new DOMException('test message', 'NotAllowedError')),
|
||||
fileStorageDidFailToInitialize(new Error('test error')),
|
||||
fileStorageDidFailToReadFile('test.file', new Error('test error')),
|
||||
fileStorageDidFailToWriteFile('test.file', new Error('test error')),
|
||||
@@ -138,7 +136,6 @@ test.each([
|
||||
serviceWorkerDidSucceed(),
|
||||
appDidCheckForUpdate(true),
|
||||
bleDIServiceDidReceiveFirmwareRevision(firmwareVersion),
|
||||
didFailToSaveAs(new DOMException('test message', 'AbortError')),
|
||||
fileStorageDidFailToExportFile(
|
||||
'test.file',
|
||||
new DOMException('test message', 'AbortError'),
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
BleDeviceFailToConnectReasonType,
|
||||
didFailToConnect as bleDeviceDidFailToConnect,
|
||||
} from '../ble/actions';
|
||||
import { didFailToSaveAs } from '../editor/actions';
|
||||
import { explorerDeleteFile, explorerDidFailToImportFiles } from '../explorer/actions';
|
||||
import {
|
||||
fileStorageDeleteFile,
|
||||
@@ -239,17 +238,6 @@ function* showBootloaderDidFailToConnectError(
|
||||
}
|
||||
}
|
||||
|
||||
function* showEditorFailToSaveFile(
|
||||
action: ReturnType<typeof didFailToSaveAs>,
|
||||
): Generator {
|
||||
if (action.err.name === 'AbortError') {
|
||||
// user clicked cancel button - not an error
|
||||
return;
|
||||
}
|
||||
|
||||
yield* showUnexpectedError(MessageId.EditorFailedToSaveFile, action.err);
|
||||
}
|
||||
|
||||
function* showFlashFirmwareError(
|
||||
action: ReturnType<typeof didFailToFinish>,
|
||||
): Generator {
|
||||
@@ -494,7 +482,6 @@ function* showExplorerFailToImportFiles(
|
||||
export default function* (): Generator {
|
||||
yield* takeEvery(bleDeviceDidFailToConnect, showBleDeviceDidFailToConnectError);
|
||||
yield* takeEvery(bootloaderDidFailToConnect, showBootloaderDidFailToConnectError);
|
||||
yield* takeEvery(didFailToSaveAs, showEditorFailToSaveFile);
|
||||
yield* takeEvery(didFailToFinish, showFlashFirmwareError);
|
||||
yield* takeEvery(didCompile, dismissCompilerError);
|
||||
yield* takeEvery(didFailToCompile, showCompilerError);
|
||||
|
||||
+2
-4
@@ -6,7 +6,7 @@ import { didStart } from './app/actions';
|
||||
import app from './app/sagas';
|
||||
import blePybricksService from './ble-pybricks-service/sagas';
|
||||
import ble from './ble/sagas';
|
||||
import editor, { EditorSagaContext } from './editor/sagas';
|
||||
import { EditorType } from './editor/Editor';
|
||||
import errorLog from './error-log/sagas';
|
||||
import explorer from './explorer/sagas';
|
||||
import fileStorage from './fileStorage/sagas';
|
||||
@@ -29,7 +29,6 @@ export default function* (): Generator {
|
||||
fileStorage(),
|
||||
lwp3BootloaderBle(),
|
||||
lwp3BootloaderProtocol(),
|
||||
editor(),
|
||||
errorLog(),
|
||||
explorer(),
|
||||
flashFirmware(),
|
||||
@@ -46,7 +45,6 @@ export default function* (): Generator {
|
||||
/**
|
||||
* Combined type for all saga contexts.
|
||||
*/
|
||||
export type RootSagaContext = EditorSagaContext &
|
||||
FirmwareSagaContext &
|
||||
export type RootSagaContext = { editor: EditorType } & FirmwareSagaContext &
|
||||
NotificationSagaContext &
|
||||
TerminalSagaContext;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
import { ButtonGroup } from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
import OpenButton from '../editor/OpenButton';
|
||||
import SaveAsButton from '../editor/SaveAsButton';
|
||||
import FlashButton from '../firmware/FlashButton';
|
||||
import BluetoothButton from '../hub/BluetoothButton';
|
||||
import ReplButton from '../hub/ReplButton';
|
||||
@@ -25,10 +23,6 @@ const Toolbar: React.VFC = (_props) => {
|
||||
onContextMenu={preventBrowserNativeContextMenu}
|
||||
className="pb-toolbar"
|
||||
>
|
||||
<ButtonGroup className="pb-toolbar-group pb-align-left">
|
||||
<OpenButton id="open" />
|
||||
<SaveAsButton id="saveAs" />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="pb-toolbar-group pb-align-left">
|
||||
<RunButton id="run" keyboardShortcut="F5" />
|
||||
<StopButton id="stop" keyboardShortcut="F6" />
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"open": { "tooltip": "Open file" },
|
||||
"saveAs": { "tooltip": "Download file" },
|
||||
"stop": { "tooltip": "Stop everything" },
|
||||
"run": {
|
||||
"action": { "tooltip": "Download and run this program" },
|
||||
|
||||
+2
-4
@@ -1,11 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
//
|
||||
// Button translation keys.
|
||||
// Toolbar button translation keys.
|
||||
|
||||
export enum TooltipId {
|
||||
Open = 'open.tooltip',
|
||||
SaveAs = 'saveAs.tooltip',
|
||||
Run = 'run.action.tooltip',
|
||||
RunProgress = 'run.progress.tooltip',
|
||||
Stop = 'stop.tooltip',
|
||||
|
||||
Reference in New Issue
Block a user