mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
editor: add editorGetValue() saga
This is another step towards removing the 'editor' context.
This commit is contained in:
@@ -7,3 +7,23 @@ import { createAction } from '../actions';
|
||||
export const editorDidCreate = createAction(() => ({
|
||||
type: 'editor.action.didCreate',
|
||||
}));
|
||||
|
||||
/**
|
||||
* Action that requests getting the current contents of the editor.
|
||||
* @param id A unique identifier for this request.
|
||||
*/
|
||||
export const editorGetValueRequest = createAction((id: number) => ({
|
||||
type: 'editor,action.getCurrentScriptRequest',
|
||||
id,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Action that responds to {@link editorGetCurrentScriptRequest}.
|
||||
* @param id The id that matches {@link editorGetCurrentScriptRequest}.
|
||||
* @param value The current editor contents.
|
||||
*/
|
||||
export const editorGetValueResponse = createAction((id: number, value: string) => ({
|
||||
type: 'editor,action.getCurrentScriptResponse',
|
||||
id,
|
||||
value,
|
||||
}));
|
||||
|
||||
+45
-3
@@ -3,7 +3,16 @@
|
||||
|
||||
import { monaco } from 'react-monaco-editor';
|
||||
import { eventChannel } from 'redux-saga';
|
||||
import { fork, put, race, select, take, takeEvery } from 'typed-redux-saga/macro';
|
||||
import {
|
||||
SagaGenerator,
|
||||
fork,
|
||||
getContext,
|
||||
put,
|
||||
race,
|
||||
select,
|
||||
take,
|
||||
takeEvery,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import {
|
||||
fileStorageDidFailToReadFile,
|
||||
fileStorageDidInitialize,
|
||||
@@ -11,7 +20,40 @@ import {
|
||||
fileStorageReadFile,
|
||||
} from '../fileStorage/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { editorDidCreate } from './actions';
|
||||
import {
|
||||
editorDidCreate,
|
||||
editorGetValueRequest,
|
||||
editorGetValueResponse,
|
||||
} from './actions';
|
||||
|
||||
/**
|
||||
* Saga that gets the current value from the editor.
|
||||
* @returns The value.
|
||||
* @throws Error if editor.isReady state is false.
|
||||
*/
|
||||
export function* editorGetValue(): SagaGenerator<string> {
|
||||
const nextMessageId = yield* getContext<() => number>('nextMessageId');
|
||||
|
||||
const isReady = yield* select((s: RootState) => s.editor.isReady);
|
||||
|
||||
if (!isReady) {
|
||||
throw new Error('editorGetValue() called before editor.isReady');
|
||||
}
|
||||
|
||||
const request = yield* put(editorGetValueRequest(nextMessageId()));
|
||||
const response = yield* take(
|
||||
editorGetValueResponse.when((a) => a.id === request.id),
|
||||
);
|
||||
|
||||
return response.value;
|
||||
}
|
||||
|
||||
function* handleEditorGetValueRequest(
|
||||
editor: monaco.editor.ICodeEditor,
|
||||
action: ReturnType<typeof editorGetValueRequest>,
|
||||
): Generator {
|
||||
yield* put(editorGetValueResponse(action.id, editor.getValue()));
|
||||
}
|
||||
|
||||
function* handleDidCreateEditor(editor: monaco.editor.ICodeEditor): Generator {
|
||||
// first, we need to be sure that file storage is ready
|
||||
@@ -42,7 +84,7 @@ function* handleDidCreateEditor(editor: monaco.editor.ICodeEditor): Generator {
|
||||
editor.setValue(succeeded.fileContents);
|
||||
}
|
||||
|
||||
// TODO: subscribe to actions that act on the editor
|
||||
yield* takeEvery(editorGetValueRequest, handleEditorGetValueRequest, editor);
|
||||
|
||||
yield* put(editorDidCreate());
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import JSZip from 'jszip';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import { EditorType } from '../editor/Editor';
|
||||
import {
|
||||
BootloaderConnectionFailureReason,
|
||||
checksumRequest,
|
||||
@@ -1730,12 +1729,7 @@ describe('flashFirmware', () => {
|
||||
new Response(await zip.generateAsync({ type: 'blob' })),
|
||||
);
|
||||
|
||||
const editor = mock<EditorType>({
|
||||
getValue: () => 'print("test")',
|
||||
});
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
editor,
|
||||
nextMessageId: createCountFunc(),
|
||||
});
|
||||
|
||||
|
||||
+2
-10
@@ -25,7 +25,7 @@ import {
|
||||
take,
|
||||
takeEvery,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import { EditorType } from '../editor/Editor';
|
||||
import { editorGetValue } from '../editor/sagas';
|
||||
import {
|
||||
checksumRequest,
|
||||
checksumResponse,
|
||||
@@ -286,15 +286,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
let program: string | undefined = undefined;
|
||||
|
||||
if (action.flashCurrentProgram) {
|
||||
const editor = yield* getContext<EditorType>('editor');
|
||||
|
||||
// istanbul ignore if: it is a bug to dispatch this action with no current editor
|
||||
if (editor === null) {
|
||||
console.error('flashFirmware: No current editor');
|
||||
return;
|
||||
}
|
||||
|
||||
program = editor.getValue();
|
||||
program = yield* editorGetValue();
|
||||
}
|
||||
|
||||
if (action.data !== null) {
|
||||
|
||||
+17
-15
@@ -1,14 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { monaco } from 'react-monaco-editor';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import { didWrite, write } from '../ble-nordic-uart-service/actions';
|
||||
import {
|
||||
didSendCommand,
|
||||
sendStopUserProgramCommand,
|
||||
} from '../ble-pybricks-service/actions';
|
||||
import { editorGetValueRequest, editorGetValueResponse } from '../editor/actions';
|
||||
import { compile, didCompile } from '../mpy/actions';
|
||||
import { createCountFunc } from '../utils/iter';
|
||||
import {
|
||||
@@ -26,17 +25,23 @@ jest.mock('react-monaco-editor');
|
||||
|
||||
describe('downloadAndRun', () => {
|
||||
test('no errors', async () => {
|
||||
const mockEditor = mock<monaco.editor.ICodeEditor>();
|
||||
const saga = new AsyncSaga(hub, {
|
||||
editor: mockEditor,
|
||||
nextMessageId: createCountFunc(),
|
||||
});
|
||||
|
||||
saga.updateState({ editor: { isReady: true } });
|
||||
|
||||
saga.put(downloadAndRun());
|
||||
|
||||
// first, it tries to compile the program in the current editor
|
||||
// first, it gets the value from the current editor
|
||||
const editorValueAction = await saga.take();
|
||||
expect(editorValueAction).toEqual(editorGetValueRequest(0));
|
||||
|
||||
saga.put(editorGetValueResponse(0, ''));
|
||||
|
||||
// then it tries to compile the program in the current editor
|
||||
const compileAction = await saga.take();
|
||||
expect(compile.matches(compileAction)).toBeTruthy();
|
||||
expect(compileAction).toEqual(compile('', ['-mno-unicode']));
|
||||
saga.put(didCompile(new Uint8Array(30)));
|
||||
|
||||
// then it notifies that loading has begun
|
||||
@@ -45,9 +50,8 @@ describe('downloadAndRun', () => {
|
||||
|
||||
// first message is the length
|
||||
const writeAction = await saga.take();
|
||||
expect(writeAction).toBeTruthy();
|
||||
expect((writeAction as ReturnType<typeof write>).value.length).toBe(4);
|
||||
saga.put(didWrite(0));
|
||||
expect(writeAction).toEqual(write(1, new Uint8Array([30, 0, 0, 0])));
|
||||
saga.put(didWrite(1));
|
||||
saga.put(checksum(30));
|
||||
|
||||
// then progress is updated
|
||||
@@ -56,9 +60,8 @@ describe('downloadAndRun', () => {
|
||||
|
||||
// then the first chunk of 20 bytes
|
||||
const writeAction2 = await saga.take();
|
||||
expect(write.matches(writeAction2)).toBeTruthy();
|
||||
expect((writeAction2 as ReturnType<typeof write>).value.length).toBe(20);
|
||||
saga.put(didWrite(1));
|
||||
expect(writeAction2).toEqual(write(2, new Uint8Array(20)));
|
||||
saga.put(didWrite(2));
|
||||
saga.put(checksum(0));
|
||||
|
||||
// then progress is updated
|
||||
@@ -67,9 +70,8 @@ describe('downloadAndRun', () => {
|
||||
|
||||
// then last chunk
|
||||
const writeAction3 = await saga.take();
|
||||
expect(write.matches(writeAction3)).toBeTruthy();
|
||||
expect((writeAction3 as ReturnType<typeof write>).value.length).toBe(10);
|
||||
saga.put(didWrite(2));
|
||||
expect(writeAction3).toEqual(write(3, new Uint8Array(10)));
|
||||
saga.put(didWrite(3));
|
||||
saga.put(checksum(0));
|
||||
|
||||
// Then a status message saying that we are done
|
||||
|
||||
+3
-9
@@ -19,7 +19,7 @@ import {
|
||||
sendStopUserProgramCommand,
|
||||
} from '../ble-pybricks-service/actions';
|
||||
import { didConnect } from '../ble/actions';
|
||||
import { EditorType } from '../editor/Editor';
|
||||
import { editorGetValue } from '../editor/sagas';
|
||||
import { compile, didCompile, didFailToCompile } from '../mpy/actions';
|
||||
import { defined } from '../utils';
|
||||
import { xor8 } from '../utils/math';
|
||||
@@ -47,16 +47,10 @@ function* waitForWrite(id: number): SagaGenerator<{
|
||||
}
|
||||
|
||||
function* handleDownloadAndRun(): Generator {
|
||||
const editor = yield* getContext<EditorType>('editor');
|
||||
const script = yield* editorGetValue();
|
||||
|
||||
// istanbul ignore next: it is a bug to dispatch this action with no current editor
|
||||
if (editor === null) {
|
||||
console.error('downloadAndRun: No current editor');
|
||||
return;
|
||||
}
|
||||
|
||||
const script = editor.getValue();
|
||||
yield* put(compile(script, ['-mno-unicode']));
|
||||
|
||||
const { mpy, mpyFail } = yield* race({
|
||||
mpy: take(didCompile),
|
||||
mpyFail: take(didFailToCompile),
|
||||
|
||||
Reference in New Issue
Block a user