Skip to content
Arno van der Vegt edited this page Oct 7, 2020 · 19 revisions

This page describes the structure of the IDE.

Dispatcher and emitters

To decouple different components, a global dispatcher is used. For example, when an error occurs in the compiler, then a message is dispatched. The compiler has does not know anything about the console and the console does not know anything about the compiler. The console listens to messages from the dispatcher and when a for the console a relevant message comes in then it's handled there.

The following code shows how a component starts listening to messages:

dispatcher.on('Console.Log', this, this.onLog);

The dispatcher.on function returns a function which can be called to remove the listener.

When a log message should be shown then a message can be dispatched:

dispatcher.dispatch('Console.Log', {type: 'Info', message: 'This is an information message...'});

The dispatcher is a special kind of emitter. There are also a lot of classes which are emitters, for example the SettingsState class. This class emits a message if a settings is changed.

Dialogs

All screen components live in a layer. The screen layer is the first layer, where uiId equals 1. All components which can have a tab focus have a uiId.

If a popup is shown then a different uiId is activated. A uiId is pushed to a stack, the highest value on the stack is the active component layer. All components which don't have the same uiId as the popup are disabled. When the popup is closed then the last uiId popped from the stack and is activated again. All components with that uiId can receive tab focus again. This ensures that components which are not on the active dialog can not receive tab focus.

An instance from the UIState class is passed to every component. The component listens to an event which is triggered if the uiId changes:

this._onGlobalUIId  = ui.addEventListener('Global.UIId', this, this.onGlobalUIId);

Frontend/backend communication

When the frontend needs information or data from the backend for example to connect to a device or retrieve a file then this is done through a dataProvider. To get a dataProvider, the getDataProvider function is used. The dataProvider is a dependency which has different implementations for electron, the browser and the demo website.

All dataproviders have a getData function:

getData: function(method, uri, params, callback) {}

The method can be post or get and the uri is a path string. The params parameter is an object with keys/values. The callback will be called when the data is received and has a single data parameter:

onData: function(data) {}

Devices

The frontend

For both the EV3 and Powered Up devices the state of the device is represented in a sub class of the BasicDeviceState class which is located in the (js/frontend/vm/BasicDeviceState.js) file.

The EV3State class (js/frontend/vm/ev3/EV3State.js) and the Powered Up class (js/frontend/vm/poweredup/PoweredUpState.js) communicate to the backend through a data provider. These classes do not communicate directly with the devices. This allows the IDE to run in electron as well as in a browser.

The communication is done through an abstraction layer, a data provider.

The demo version in the browser uses the web version of the Powered Up library. The NodeJs version uses de same library but it's running in a server and the data provides communicates through HTTP. The electron uses the library but makes direct calls.

Separate devices are represented as layers with the device state class. The base class for a layer is BasicLayerState located in the file js/frontend/vm/BasicLayerState.js.

For EV3 devices, each layer represents either the primary device or for layers higher than one it represents a daisy chained device. The maximum number of EV3 devices is four.

For Powered Up devices, each layer represents a specific device which also has to be connected separately. The number of Powered Up devices which can be connected is currently eight.