-
Notifications
You must be signed in to change notification settings - Fork 1
IDE
This page describes the structure of the IDE.
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.
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);
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 data provider. To get a data provider, the getDataProvider function is used. The data provider 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) {}
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.
The backend is accessed through a data provider. A data provider is acquired by a call to the getDataProvider function declared in the file js/frontend/lib/dataprovider/dataProvider.js.
When the electron version is active then an instance of ElectronDataProvider (js/frontend/lib/dataprovider/ElectronDataProvider.js) is returned. If the NodeJs or browser version is active then an instance of the HttpDataProvider (js/frontend/lib/dataprovider/HttpDataProvider.js) class is returned.
In the data provider there are routes which are either accessed through HTTP or called immediately.
Since the HTTP calls are asynchronous the direct calls are also made asynchronous. This is done to prevent a leaky abstraction. By making the direct calls asynchronous it is ensured that the all implementations of the data provider behave the same way.
Because the demo version does not have an actual server or electron backend it uses a virtual file system which is extended by local storage for custom files.