diff --git a/src/editor/OpenButton.tsx b/src/editor/OpenButton.tsx deleted file mode 100644 index 66be3c46..00000000 --- a/src/editor/OpenButton.tsx +++ /dev/null @@ -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; - -const OpenButton: React.FunctionComponent = (props) => { - const { editor } = useContext(EditorContext); - const dispatch = useDispatch(); - - return ( - dispatch(editorActions.open(data))} - onReject={(file) => - dispatch( - notificationActions.add( - 'error', - `'${file.name}' is not a valid python file.`, - ), - ) - } - {...props} - /> - ); -}; - -export default OpenButton; diff --git a/src/editor/SaveAsButton.tsx b/src/editor/SaveAsButton.tsx deleted file mode 100644 index 31a22eb8..00000000 --- a/src/editor/SaveAsButton.tsx +++ /dev/null @@ -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 & - Pick; - -const SaveAsButton: React.FunctionComponent = (props) => { - const { editor } = useContext(EditorContext); - - const dispatch = useDispatch(); - - return ( - dispatch(editorActions.saveAs())} - {...props} - /> - ); -}; - -export default SaveAsButton; diff --git a/src/editor/actions.ts b/src/editor/actions.ts deleted file mode 100644 index aa78a258..00000000 --- a/src/editor/actions.ts +++ /dev/null @@ -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, -})); diff --git a/src/editor/open.svg b/src/editor/open.svg deleted file mode 100644 index 06b4c5bc..00000000 --- a/src/editor/open.svg +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/editor/sagas.test.ts b/src/editor/sagas.test.ts deleted file mode 100644 index ff580551..00000000 --- a/src/editor/sagas.test.ts +++ /dev/null @@ -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(); - 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(); - 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(); - const originalShowSaveFilePicker = window.showSaveFilePicker; - window.showSaveFilePicker = jest.fn().mockResolvedValue( - mock({ - 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(); - 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({ - 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(); - 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(); - 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(); - }); -}); diff --git a/src/editor/sagas.ts b/src/editor/sagas.ts deleted file mode 100644 index 793de7c7..00000000 --- a/src/editor/sagas.ts +++ /dev/null @@ -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): Generator { - const editor = yield* getContext('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('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); -} diff --git a/src/editor/save.svg b/src/editor/save.svg deleted file mode 100644 index 0468d81f..00000000 --- a/src/editor/save.svg +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/src/notifications/sagas.test.ts b/src/notifications/sagas.test.ts index ab53456d..a2f153b6 100644 --- a/src/notifications/sagas.test.ts +++ b/src/notifications/sagas.test.ts @@ -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'), diff --git a/src/notifications/sagas.ts b/src/notifications/sagas.ts index 8fc40f1e..7e6be560 100644 --- a/src/notifications/sagas.ts +++ b/src/notifications/sagas.ts @@ -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, -): Generator { - if (action.err.name === 'AbortError') { - // user clicked cancel button - not an error - return; - } - - yield* showUnexpectedError(MessageId.EditorFailedToSaveFile, action.err); -} - function* showFlashFirmwareError( action: ReturnType, ): 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); diff --git a/src/sagas.ts b/src/sagas.ts index a9fe4e96..4a5e7e21 100644 --- a/src/sagas.ts +++ b/src/sagas.ts @@ -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; diff --git a/src/toolbar/Toolbar.tsx b/src/toolbar/Toolbar.tsx index bf35ab3e..20e47108 100644 --- a/src/toolbar/Toolbar.tsx +++ b/src/toolbar/Toolbar.tsx @@ -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" > - - - - diff --git a/src/toolbar/i18n.en.json b/src/toolbar/i18n.en.json index b820fd37..616c7878 100644 --- a/src/toolbar/i18n.en.json +++ b/src/toolbar/i18n.en.json @@ -1,6 +1,4 @@ { - "open": { "tooltip": "Open file" }, - "saveAs": { "tooltip": "Download file" }, "stop": { "tooltip": "Stop everything" }, "run": { "action": { "tooltip": "Download and run this program" }, diff --git a/src/toolbar/i18n.ts b/src/toolbar/i18n.ts index 2f1ce117..1221aa09 100644 --- a/src/toolbar/i18n.ts +++ b/src/toolbar/i18n.ts @@ -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',