diff --git a/ide/CHANGELOG.md b/ide/CHANGELOG.md
index a9766a542880..3432943eb820 100644
--- a/ide/CHANGELOG.md
+++ b/ide/CHANGELOG.md
@@ -14,6 +14,11 @@
unnecessary library imports when selecting hints from node searcher. This
makes the generated textual code easier to read and reduces likelihood of
accidental name collision.
+- [Visualizations can define context for preprocessor evaluation][1291]. Users
+ can now decide what module's context should be used for visualization
+ preprocessor. This allows providing visualization with standard library
+ functionalities or defining utilities that are shared between multiple
+ visualizations.
#### EnsoGL (rendering engine)
@@ -24,6 +29,7 @@ you can find their release notes
[here](https://github.com/enso-org/enso/blob/main/RELEASES.md).
[1209]: https://github.com/enso-org/ide/pull/1209
+[1291]: https://github.com/enso-org/ide/pull/1291
diff --git a/ide/docs/product/visualizations.md b/ide/docs/product/visualizations.md
index 1f2f5d8b5a7f..32c36907a7bd 100644
--- a/ide/docs/product/visualizations.md
+++ b/ide/docs/product/visualizations.md
@@ -8,6 +8,7 @@ tags: [product]
# Visualization Workflow
## Purpose of visualizations
+
Visualizations have two main purposes:
- **Display results of nodes**
@@ -27,14 +28,14 @@ Visualizations have two main purposes:
used to interactively generate a table of numbers. Image visualizations can
behave like an image editor, etc.
-
## Visualization Display Forms
+
Visualizations can be displayed in the following ways:
-- **Attached to nodes**
- In this mode, visualizations display the most recent result of the node. They
- behave like an integrated part of the node. Whenever you move the node, the
- visualization moves as well. This mode can be toggled by tapping the spacebar.
+- **Attached to nodes** In this mode, visualizations display the most recent
+ result of the node. They behave like an integrated part of the node. Whenever
+ you move the node, the visualization moves as well. This mode can be toggled
+ by tapping the spacebar.
- **Fullscreen**
Visualization attached to node can grow (animate) to ocupy full IDE visual
@@ -47,9 +48,9 @@ Visualizations can be displayed in the following ways:
- **Detached**
Visualizations attached to nodes can be detached, scaled, and placed freely
across the visual canvas (we might introduce a special place where you can put
- such visualizations). This is useful when defining dashboards or reports.
- We also plan to provide a notebook-like experience where you can write text
- mixed with visualizations (including widgets for an interactive experience).
+ such visualizations). This is useful when defining dashboards or reports. We
+ also plan to provide a notebook-like experience where you can write text mixed
+ with visualizations (including widgets for an interactive experience).
- **Widgets**
In this mode visualizations behave like nodes but do not display expressions.
@@ -65,8 +66,8 @@ Visualizations can be displayed in the following ways:
user clicks the map to define locations, the data could be a string literal
containing locations encoded in JSON.
-
### Choosing a Visualization Type.
+
When a new data is provided to a visualization, the visualization registry
searches for all visualizations that match it (see visualization registry to
learn more). For example, when a data of type `[Int]` (list of ints) is
@@ -78,6 +79,7 @@ visualization has a drop-down menu allowinh the user switching to another
visualization type.
### Active Visualizations
+
When visualizations are displayed on the stage, they are not active by default,
which means, they do not capture keyboard shortcuts. Visualization becomes
active when user clicks it. Visualizations are deactivated by clicking in the
@@ -87,8 +89,8 @@ border (to be defined). Active visualizations capture all keyboard shortcuts,
but the space bar presses. Fullscreen visualizations are considered active by
default.
-
## HTML and Native Visualizations
+
There are two main types of visualizations - Html and Native. The later uses the
BaseGL shape API to draw on the screen. We prefer the later as it integrates
tightly with our framework and allows for much better performance. However,
@@ -105,33 +107,37 @@ and bottom HTML layer. The HTML visualizations are created and displayed on the
bottom layer by default. Whenever an HTML visualization gets active, it should
be moved to the top layer.
-
## Visualization Registry
+
Visualizations are user-defined. Enso ships with a set of predefined
visualizations, but they are in no way different than user-defined, they are
just defined for you. Visualizations can be defined either as HTML or native
-visualization and can be defined in JS or WASM (or any language that compiles
-to one of these). Visualizations are stored on disk on the server-side and are
+visualization and can be defined in JS or WASM (or any language that compiles to
+one of these). Visualizations are stored on disk on the server-side and are
provided to the GUI by the server. Users can upload their custom visualizations
as well. Each visualization is registered in the visualization map. The map maps
an Enso type to a set of visualizations defined for that type. The type might be
very generic, like `[a]` (which in Enso terms means list of any elements).
-### Defining a Visualization
-Visualizations is planned to be defined both with Enso and JavaScript but, for
-now, only JavaScript visualizations are supported.
+## Defining a Visualization
+
+Currently only JavaScript visualizations can be defined. Support for native
+visualizations is planned.
+
+### Defining a JavaScript Visualization
-Because IDE lacks support for editing any other file besides `Main.enso`, the
-user has to create it outside of IDE in the `visualization` folder of the Enso
-project, as demonstrated bellow.
+JavaScript visualizations are defined by placing `*.js` files in the
+`visualization` subfolder in the project's root directory. As IDE currently
+allows only editing `Main.enso` file, users have to create `.js` file manually,
+editing it outside IDE.
-#### Custom Visualization Example
+## Custom JavaScript Visualization Example
Every visualization must reside in the `visualization` folder of the user's
project. For instance:
```
-└─ ProjectName
+└─ Project_Name
├─ src
│ └─ Main.enso
└─ visualization
@@ -139,48 +145,48 @@ project. For instance:
```
Visualizations can be defined as a JavaScript function which returns a class of
-a shape specified below. Consider the following definition:
+a shape specified below. Consider the following sample definition:
```javascript
-console.log("Hi, this definition is being registered now!")
+console.log("Hi, this definition is being registered now!");
return class BubbleVisualization extends Visualization {
- static inputType = "Any"
-
- onDataReceived(data) {
- const xmlns = "http://www.w3.org/2000/svg";
- while (this.dom.firstChild) {
- this.dom.removeChild(this.dom.lastChild);
- }
- const width = this.dom.getAttributeNS(null, "width");
- const height = this.dom.getAttributeNS(null, "height");
- const svgElem = document.createElementNS(xmlns, "svg");
- svgElem.setAttributeNS(null, "id" , "vis-svg");
- svgElem.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height);
- svgElem.setAttributeNS(null, "width" , "100%");
- svgElem.setAttributeNS(null, "height" , "100%");
- this.dom.appendChild(svgElem);
- data.forEach(data => {
- const bubble = document.createElementNS(xmlns,"circle");
- bubble.setAttributeNS(null,"stroke", "black");
- bubble.setAttributeNS(null,"fill" , "red");
- bubble.setAttributeNS(null,"r" , data[2]);
- bubble.setAttributeNS(null,"cx" , data[0]);
- bubble.setAttributeNS(null,"cy" , data[1]);
- svgElem.appendChild(bubble);
- });
- }
+ static inputType = "Any";
- setSize(size) {
- this.dom.setAttributeNS(null, "width", size[0]);
- this.dom.setAttributeNS(null, "height", size[1]);
+ onDataReceived(data) {
+ const xmlns = "http://www.w3.org/2000/svg";
+ while (this.dom.firstChild) {
+ this.dom.removeChild(this.dom.lastChild);
}
-}
+ const width = this.dom.getAttributeNS(null, "width");
+ const height = this.dom.getAttributeNS(null, "height");
+ const svgElem = document.createElementNS(xmlns, "svg");
+ svgElem.setAttributeNS(null, "id", "vis-svg");
+ svgElem.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height);
+ svgElem.setAttributeNS(null, "width", "100%");
+ svgElem.setAttributeNS(null, "height", "100%");
+ this.dom.appendChild(svgElem);
+ data.forEach((data) => {
+ const bubble = document.createElementNS(xmlns, "circle");
+ bubble.setAttributeNS(null, "stroke", "black");
+ bubble.setAttributeNS(null, "fill", "red");
+ bubble.setAttributeNS(null, "r", data[2]);
+ bubble.setAttributeNS(null, "cx", data[0]);
+ bubble.setAttributeNS(null, "cy", data[1]);
+ svgElem.appendChild(bubble);
+ });
+ }
+
+ setSize(size) {
+ this.dom.setAttributeNS(null, "width", size[0]);
+ this.dom.setAttributeNS(null, "height", size[1]);
+ }
+};
```
In particular:
-- [Required] **Source code**
+- ### [Required] Source code
Visualization definition has to be a valid body of JavaScript function which
returns a class definition. Instances of that class will be considered
@@ -188,20 +194,37 @@ In particular:
state across visualizations of the same type, but you are highly advised not
to do so.
-- [Required] **`Visualization` superclass**
-
+- ### [Required] `Visualization` superclass
+
The class returned by the definition function should extend the predefined
`Visualization` class. Classes which do not extend it, will not be registered
as visualizations. The superclass defines a default constructor and a set of
utilities:
- - The `setPreprocessor(code)` a method allowing setting an Enso code
- which will be evaluated on the server-side before sending data to
- visualization. See also [Lazy visualizations](#lazy-visualizations) section.
- - The `dom` field, which will be initialized in the constructor to the DOM
- symbol used to host the visualization content. You are free to modify the
- DOM element, including adding other elements as its children.
-- [Optional] **Field `label`**
+ - #### Method `setPreprocessorCode(code)`
+ Set an Enso code which will be evaluated on the server-side before sending
+ data to visualization. If not called, a default unspecified code is used
+ that will provide some JSON representation of the value. See
+ [Lazy visualizations](#lazy-visualizations) section for details.
+ - #### Method `setPreprocessorModule(module)`
+ Define in which module's context the preprocessor code should be evaluated.
+ If not called, the `Main` module of the project that defines visualization
+ will be used. See [Lazy visualizations](#lazy-visualizations) section for
+ details.
+ - #### Method `setPreprocessor(code,mode)`
+ Set both code and its module context at once. If both need to be updated,
+ using this method can save an update processing and needless evaluation.
+ Note that using both `setPreprocessorCode` and `setPreprocessorModule` from
+ the visualization's custom constructor will not cause any unnecessary
+ updates, as the preprocessor is applied only after visualization is fully
+ constructed. See [Lazy visualizations](#lazy-visualizations) section for
+ details.
+ - #### Field `dom`
+ It is initialized in the constructor to the DOM symbol used to host the
+ visualization content. Users are free to modify the DOM element, including
+ adding other elements as its children.
+
+- ### [Optional] Field `label`
The static field `label` is an user-facing name used to identify the
visualization. You are not allowed to define several visualizations of the
@@ -209,16 +232,16 @@ In particular:
will be inferred from the class name by splitting the camel-case name into
chunks and converting them to lowercase string.
-- [Optional] **Field `inputType`**
+- ### [Optional] Field `inputType`
The static field `inputType` is used to determine which Enso data types this
visualization can be used for. Its value should be a valid Enso type, like
- "String | Int". In case the field is an empty string or it is missing, it will
- default to "Any", which is a type containing all other types. It is a rare
- case when you want to define a visualization which is able to work with just
- any data type, so you are highly advised to provide the type definition.
+ `"String | Int"`. In case the field is an empty string or it is missing, it
+ will default to `"Any"`, which is a type containing all other types. It is a
+ rare case when you want to define a visualization which is able to work with
+ just any data type, so you are highly advised to provide the type definition.
-- [Optional] **Field `inputFormat`**
+- ### [Optional] Field `inputFormat`
The static field `inputFormat` is used to determine what format the data
should be provided to the `onDataReceived` function. Currently, the only valid
@@ -226,29 +249,30 @@ In particular:
In the later case, it is up to the visualization author to manage the binary
stream received from the server.
-- [Optional] **Constructor**
+- ### [Optional] Constructor
The visualization will be instantiated by providing the constructor with a
configuration object. The shape of the configuration object is not part of the
public API and can change between releases of this library. You have to pass
it unchanged to the superclass constructor.
-- [Optional] **Function `onDataReceived`**
+- ### [Optional] Function `onDataReceived`
The `onDataReceived(data)` method is called on every new data chunk received
from the server. Note that the visualization will receive the "full data" if
you are not using the `setPreprocessor` method.
-- [Optional] **Function `setSize`**
+- ### [Optional] Function `setSize`
The `setSize(size)` method is called on every size change of the
visualization. You should not draw outside of the provided area, however, if
you do so, it will be clipped to the provided area automatically. The `size`
parameter contains two fields `width` and `height` expressed in pixels.
-### Sending Data to Visualizations
+## Sending Data to Visualizations
+
+### Lazy Visualizations
-#### Lazy Visualizations
Very important information is how visualization architecture works to make them
interactive and fast. Whenever new data is computed by the compiler and
visualization is attached to it, it is sent to GUI to be displayed. However,
@@ -258,28 +282,27 @@ This code is part of the visualization definition and is stored server-side.
Visualizations are allowed to change the code at runtime (in JavaScript
visualization you may use the `setPreprocessor` method). This code defines an
Enso function, which will be run by the compiler on data the visualization is
-attached to. Only the results of this code will be sent to the GUI. In the case
-of the JSON input format, the result of the call should be a valid JSON string.
-The code will be evaluated in the context of the `Main` module in the
-project where visualization is defined - you may use any symbol defined or
-imported in that module.
-
-For example, imagine you want to display a heatmap of 10 million points on
-a map, and these points change rapidly. Sending such an amount of information
-via WebSocket could be too much, and you (as the visualization author) might
-decide that the visualization image should be generated on the server, and your
+attached to. Only the results of this code will be sent to the GUI. In the case
+of the JSON input format, the result of the call should be a valid JSON string.
+The code will be evaluated in the context of the `Main` module in the project
+where visualization is defined - you may use any symbol defined or imported in
+that module.
+
+For example, imagine you want to display a heatmap of 10 million points on a
+map, and these points change rapidly. Sending such an amount of information via
+WebSocket could be too much, and you (as the visualization author) might decide
+that the visualization image should be generated on the server, and your
visualization is meant only to display the resulting image. In such a scenario,
-you can define in your visualization an Enso function which will compute the
-the image on the server!
-
+you can define in your visualization an Enso function which will compute the the
+image on the server!
+### Binary and Text (JSON) Formats
-#### Binary and Text (JSON) Formats
Each visualization can choose whether it supports either binary or JSON input.
The input format defaults to JSON. The data from the server is always sent to
GUI in a binary channel, however, when JSON format is selected, it is first
-converted to JSON representation on the server side. We can assume that all
-Enso data types have defined conversion to JSON by default. If the visualization
+converted to JSON representation on the server side. We can assume that all Enso
+data types have defined conversion to JSON by default. If the visualization
input is defined as JSON input, the binary stream will be converted to JSON by
the GUI engine before passing to visualization. It is up to the visualization
-author to handle the textual or binary form.
+author to handle the textual or binary form.
diff --git a/ide/src/rust/Cargo.lock b/ide/src/rust/Cargo.lock
index cd3891b18736..c7777f7f5389 100644
--- a/ide/src/rust/Cargo.lock
+++ b/ide/src/rust/Cargo.lock
@@ -470,7 +470,7 @@ dependencies = [
name = "enso-args"
version = "0.1.0"
dependencies = [
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"ensogl 0.1.0",
]
@@ -507,7 +507,7 @@ dependencies = [
"Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-callback 0.1.0",
"enso-generics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"ensogl-system-web 0.1.0",
"keyboard-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -529,7 +529,7 @@ dependencies = [
[[package]]
name = "enso-logger"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -592,7 +592,7 @@ dependencies = [
"bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-data 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ensogl-build-utilities 0.1.0",
@@ -647,7 +647,7 @@ version = "0.1.0"
dependencies = [
"enso-automata 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"ensogl-system-web 0.1.0",
"js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -664,7 +664,7 @@ version = "0.1.0"
dependencies = [
"enso-automata 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-shortcuts 0.1.0",
"ensogl-system-web 0.1.0",
@@ -681,7 +681,7 @@ name = "enso-span-tree-example"
version = "0.1.0"
dependencies = [
"ast 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"ensogl-system-web 0.1.0",
"span-tree 0.1.0",
@@ -728,7 +728,7 @@ dependencies = [
"enso-data 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-frp 0.1.0",
"enso-generics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-optics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -761,7 +761,7 @@ name = "ensogl-examples"
version = "0.1.0"
dependencies = [
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ensogl-core 0.1.0",
@@ -782,7 +782,7 @@ name = "ensogl-gui-components"
version = "0.1.0"
dependencies = [
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-types 0.1.0",
@@ -799,7 +799,7 @@ dependencies = [
"async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-data 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"gloo-timers 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1265,7 +1265,7 @@ dependencies = [
"enso-callback 0.1.0",
"enso-data 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-protocol 0.1.0",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1281,7 +1281,7 @@ dependencies = [
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"fuzzly 0.1.0",
"ide-view 0.1.0",
- "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itertools 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
"json-rpc 0.1.0",
"mockall 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1306,7 +1306,7 @@ version = "0.1.0"
dependencies = [
"ast 0.1.0",
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-protocol 0.1.0",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1335,7 +1335,7 @@ dependencies = [
"ast 0.1.0",
"enso-args 0.1.0",
"enso-frp 0.1.0",
- "enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enso-protocol 0.1.0",
"enso-shapely 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1344,6 +1344,7 @@ dependencies = [
"ensogl-text 0.1.0",
"ensogl-text-msdf-sys 0.1.0",
"ensogl-theme 0.1.0",
+ "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
"nalgebra 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3223,7 +3224,7 @@ dependencies = [
"checksum enso-automata 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f8d432048ea9d4c4b5f84fd246303b74c8a52d9e5878f593fef540a67faff24"
"checksum enso-data 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5baf4b3bd2a814441265d47d1d5ffb1d38bd194043165be0b68c2ad4ad7e73de"
"checksum enso-generics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf6f66c10edfaa8b55292053192112d0796c12b2aeab0e2f51dae5eec729d3b"
-"checksum enso-logger 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "115416c9661c7b35b29fb3385c9b40e55fabc008e74c9bf0eb350e551297c481"
+"checksum enso-logger 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a287f9ae80537a9ee4847b1a12e16e7e2dc94d576505ae3c13c6dcb9437bcbb3"
"checksum enso-macro-utils 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "616e099985be92ef3de01f4c062868b46ab06868e1fc9cd077050709ccc2d9ae"
"checksum enso-optics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f403efb0c27b67eb8cfec664ed7456224d564b3095268d856023648848d4139"
"checksum enso-prelude 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bd9bdcf47dc157a85cbad266966ba2587b67088e5cf49ba3621c94ab01e2ab"
diff --git a/ide/src/rust/ide/Cargo.toml b/ide/src/rust/ide/Cargo.toml
index 8da1bac8c96d..dc02b56fa67b 100644
--- a/ide/src/rust/ide/Cargo.toml
+++ b/ide/src/rust/ide/Cargo.toml
@@ -13,7 +13,7 @@ enso-args = { path = "lib/args" }
enso-callback = { path = "../lib/callback" }
enso-data = { version = "0.1.4" }
enso-frp = { path = "../lib/frp" }
-enso-logger = { version = "0.2.2" }
+enso-logger = { version = "0.2.3" }
enso-prelude = { version = "0.1.10" }
enso-shapely = { version = "0.1.4" }
ensogl = { path = "../ensogl" }
@@ -36,7 +36,7 @@ console_error_panic_hook = { version = "0.1.6" }
failure = { version = "0.1.6" }
flo_stream = { version = "0.4.0" }
futures = { version = "0.3.1" }
-itertools = { version = "0.8.1" }
+itertools = { version = "0.10.0" }
js-sys = { version = "0.3.28" }
mockall = { version = "0.7.1", features = ["nightly"] }
nalgebra = { version = "0.21.1", features = ["serde-serialize"] }
diff --git a/ide/src/rust/ide/src/controller/graph/executed.rs b/ide/src/rust/ide/src/controller/graph/executed.rs
index 308f177f490a..ab58207b46da 100644
--- a/ide/src/rust/ide/src/controller/graph/executed.rs
+++ b/ide/src/rust/ide/src/controller/graph/executed.rs
@@ -141,8 +141,8 @@ impl Handle {
/// Modify preprocessor code in visualization. See also
/// [`model::ExecutionContext::modify_visualization`].
pub async fn set_visualization_preprocessor
- (&self, id:VisualizationId, code:String) -> FallibleResult {
- self.execution_ctx.modify_visualization(id,Some(code),None).await
+ (&self, id:VisualizationId, code:String, module:model::module::QualifiedName) -> FallibleResult {
+ self.execution_ctx.modify_visualization(id,Some(code),Some(module)).await
}
/// Subscribe to updates about changes in this executed graph.
diff --git a/ide/src/rust/ide/src/controller/visualization.rs b/ide/src/rust/ide/src/controller/visualization.rs
index 5d3f133f9bed..b4c97a4a02ad 100644
--- a/ide/src/rust/ide/src/controller/visualization.rs
+++ b/ide/src/rust/ide/src/controller/visualization.rs
@@ -8,7 +8,6 @@ use crate::prelude::*;
use crate::constants::VISUALIZATION_DIRECTORY;
use enso_protocol::language_server;
-use ide_view::graph_editor::data::enso;
use ide_view::graph_editor::component::visualization::definition;
use ide_view::graph_editor::component::visualization;
use std::rc::Rc;
@@ -22,14 +21,30 @@ use std::rc::Rc;
/// Enumeration of errors used in `Visualization Controller`.
#[derive(Debug,Fail)]
#[allow(missing_docs)]
-pub enum VisualizationError {
+pub enum Error {
#[fail(display = "Visualization \"{}\" not found.", identifier)]
NotFound {
- identifier : VisualizationPath
+ identifier:VisualizationPath
+ },
+ #[fail(display = "JavaScript visualization \"{}\" failed to be prepared.", identifier)]
+ Preparation {
+ identifier:VisualizationPath,
+ #[cause]
+ cause:failure::Error,
},
#[fail(display = "JavaScript visualization \"{}\" failed to be instantiated.", identifier)]
- InstantiationError {
- identifier : VisualizationPath
+ Instantiation {
+ identifier:VisualizationPath
+ },
+}
+
+impl Error {
+ /// Construct a new error regarding visualization preparation.
+ pub fn js_preparation_error
+ (identifier:VisualizationPath, error:visualization::foreign::java_script::definition::Error)
+ -> Self {
+ let cause = failure::format_err!("{}",error);
+ Self::Preparation {identifier,cause}
}
}
@@ -130,21 +145,18 @@ impl Handle {
let embedded_visualizations = self.embedded_visualizations.borrow();
let result = embedded_visualizations.get(identifier);
let identifier = visualization.clone();
- let error = || VisualizationError::NotFound{identifier}.into();
+ let error = || Error::NotFound{identifier}.into();
result.cloned().ok_or_else(error)
},
VisualizationPath::File(path) => {
+ let project = visualization::path::Project::CurrentProject;
let js_code = self.language_server_rpc.read_file(&path).await?.contents;
- let identifier = visualization.clone();
- let error = |_| VisualizationError::InstantiationError {identifier}.into();
- // FIXME: provide real library name. The name set here is discarded anyway in
- // [`ide::integration::Model::prepare_visualization`] and set the Main of the
- // current project. All those should be fixed in
- // https://github.com/enso-org/ide/issues/1167
- let module = enso::builtin_library();
- // TODO: this is wrong. This is translated to InstantiationError and it is preparation error :
- let js_class = visualization::java_script::Definition::new(module,&js_code).map_err(error);
- js_class.map(|t| t.into())
+ let wrap_error = |err| {
+ Error::js_preparation_error(visualization.clone(),err).into()
+ };
+ visualization::java_script::Definition::new(project,&js_code)
+ .map(Into::into)
+ .map_err(wrap_error)
}
}
}
@@ -164,6 +176,7 @@ mod tests {
use enso_protocol::language_server::Path;
use ide_view::graph_editor::builtin;
use ide_view::graph_editor::component::visualization;
+ use ide_view::graph_editor::component::visualization::java_script as js_vis;
use json_rpc::expect_call;
use wasm_bindgen_test::wasm_bindgen_test_configure;
@@ -173,6 +186,7 @@ mod tests {
#[wasm_bindgen_test(async)]
async fn list_and_load() {
+
let mock_client = language_server::MockClient::default();
let root_id = uuid::Uuid::default();
@@ -226,8 +240,9 @@ mod tests {
assert_eq!(visualizations[2], VisualizationPath::File(path1));
assert_eq!(visualizations.len(),3);
- let javascript_vis0 = visualization::java_script::Definition::new("builtin", &file_content0);
- let javascript_vis1 = visualization::java_script::Definition::new("builtin", &file_content1);
+ let owner = visualization::Project::CurrentProject;
+ let javascript_vis0 = js_vis::Definition::new(owner.clone_ref(),&file_content0);
+ let javascript_vis1 = js_vis::Definition::new(owner,&file_content1);
let javascript_vis0 = javascript_vis0.expect("Couldn't create visualization class.");
let javascript_vis1 = javascript_vis1.expect("Couldn't create visualization class.");
let javascript_vis0:visualization::Definition = javascript_vis0.into();
diff --git a/ide/src/rust/ide/src/double_representation/identifier.rs b/ide/src/rust/ide/src/double_representation/identifier.rs
index 832b8d820382..69afcfb0c8eb 100644
--- a/ide/src/rust/ide/src/double_representation/identifier.rs
+++ b/ide/src/rust/ide/src/double_representation/identifier.rs
@@ -239,6 +239,20 @@ impl AsRef for ReferentName {
}
}
+impl TryFrom<&str> for ReferentName {
+ type Error = NotReferentName;
+ fn try_from(value:&str) -> Result {
+ Self::new(value)
+ }
+}
+
+impl TryFrom for ReferentName {
+ type Error = NotReferentName;
+ fn try_from(value:String) -> Result {
+ Self::new(value)
+ }
+}
+
impl From for String {
fn from(name:ReferentName) -> Self {
name.0
diff --git a/ide/src/rust/ide/src/double_representation/module.rs b/ide/src/rust/ide/src/double_representation/module.rs
index 5b954fbb2902..367431fb796b 100644
--- a/ide/src/rust/ide/src/double_representation/module.rs
+++ b/ide/src/rust/ide/src/double_representation/module.rs
@@ -153,10 +153,18 @@ impl QualifiedName {
QualifiedName {project_name,id}
}
+ /// Create a qualified name for the project's main module.
+ ///
+ /// It is special, as its name consists only from the project name, unlike other modules'
+ /// qualified names.
+ pub fn new_main(project_name:ReferentName) -> QualifiedName {
+ Self::new(project_name, Id::new(std::iter::empty()))
+ }
+
/// Constructs a qualified name from its text representation.
///
/// Fails, if the text is not a valid module's qualified name.
- pub fn from_text(text:impl Str) -> FallibleResult {
+ pub fn from_text(text:impl AsRef) -> FallibleResult {
use ast::opr::predefined::ACCESS;
let text = text.as_ref();
diff --git a/ide/src/rust/ide/src/ide/integration.rs b/ide/src/rust/ide/src/ide/integration.rs
index dc8ded1907c2..022ea1dc272a 100644
--- a/ide/src/rust/ide/src/ide/integration.rs
+++ b/ide/src/rust/ide/src/ide/integration.rs
@@ -246,8 +246,8 @@ impl Integration {
// === Setting Visualization Preprocessor ===
frp::extend! { network
- eval editor_outs.visualization_preprocessor_changed ([model]((node_id,code)) {
- if let Err(err) = model.visualization_preprocessor_changed(*node_id,code) {
+ eval editor_outs.visualization_preprocessor_changed ([model]((node_id,preprocessor)) {
+ if let Err(err) = model.visualization_preprocessor_changed(*node_id,preprocessor) {
error!(model.logger, "Error when handling request for setting new \
visualization's preprocessor code: {err}");
}
@@ -675,15 +675,16 @@ impl Model {
/// Mark node as erroneous if given payload contains an error.
fn set_error
- (&self, node_id:graph_editor::NodeId, error:Option<&ExpressionUpdatePayload>) -> FallibleResult {
+ (&self, node_id:graph_editor::NodeId, error:Option<&ExpressionUpdatePayload>)
+ -> FallibleResult {
let error = self.convert_payload_to_error_view(error,node_id);
self.view.graph().set_node_error_status(node_id,error.clone());
if error.is_some() && !self.error_visualizations.contains_key(&node_id) {
- let endpoint = self.view.graph().frp.set_error_visualization_data.clone_ref();
- let preprocessor = graph_editor::builtin::visualization::native::error::PREPROCESSOR;
- let preprocessor = graph_editor::data::enso::Code::new(preprocessor);
- let metadata = visualization::Metadata {preprocessor};
- self.attach_visualization(node_id,&metadata,endpoint,self.error_visualizations.clone_ref())?;
+ use graph_editor::builtin::visualization::native::error;
+ let endpoint = self.view.graph().frp.set_error_visualization_data.clone_ref();
+ let metadata = error::metadata();
+ let vis_map = self.error_visualizations.clone_ref();
+ self.attach_visualization(node_id,&metadata,endpoint,vis_map)?;
} else if error.is_none() && self.error_visualizations.contains_key(&node_id) {
self.detach_visualization(node_id,self.error_visualizations.clone_ref())?;
}
@@ -1143,14 +1144,28 @@ impl Model {
});
}
- fn visualization_preprocessor_changed(&self, node_id:graph_editor::NodeId, code:&graph_editor::data::enso::Code)
- -> FallibleResult {
+ fn resolve_visualization_context
+ (&self, context:&visualization::instance::ContextModule)
+ -> FallibleResult {
+ use visualization::instance::ContextModule::*;
+ match context {
+ ProjectMain => self.project.main_module(),
+ Specific(module_name) => model::module::QualifiedName::from_text(module_name),
+ }
+ }
+
+ fn visualization_preprocessor_changed
+ ( &self
+ , node_id : graph_editor::NodeId
+ , preprocessor : &visualization::instance::PreprocessorConfiguration
+ ) -> FallibleResult {
if let Some(visualization) = self.visualizations.get_copied(&node_id) {
let logger = self.logger.clone_ref();
let controller = self.graph.clone_ref();
- let code_string = AsRef::::as_ref(code).to_string();
+ let code = preprocessor.code.deref().into();
+ let module = self.resolve_visualization_context(&preprocessor.module)?;
executor::global::spawn(async move {
- let result = controller.set_visualization_preprocessor(visualization,code_string);
+ let result = controller.set_visualization_preprocessor(visualization,code,module);
if let Err(err) = result.await {
error!(logger, "Error when setting visualization preprocessor: {err}");
}
@@ -1274,21 +1289,10 @@ impl Model {
fn prepare_visualization
(&self, node_id:graph_editor::NodeId, metadata:&visualization::Metadata)
-> FallibleResult {
- use crate::model::module::QualifiedName;
-
- // TODO [mwu]
- // Currently it is not possible to:
- // * enter other module than the initial (namely, "Main")
- // * describe that visualization's expression wishes to be evaluated in any other
- // context.
- // Because of that for now we will just hardcode the `visualization_module` using
- // fixed defaults. In future this will be changed, then the editor will also get access
- // to the customised values.
- let project_name:String = self.project.name().into();
- let module_name = crate::ide::INITIAL_MODULE_NAME;
- let visualisation_module = QualifiedName::from_segments(project_name,&[module_name])?;
+ let module_designation = &metadata.preprocessor.module;
+ let visualisation_module = self.resolve_visualization_context(module_designation)?;
let id = VisualizationId::new_v4();
- let expression = metadata.preprocessor.to_string();
+ let expression = metadata.preprocessor.code.to_string();
let ast_id = self.get_controller_node_id(node_id)?;
Ok(Visualization{ast_id,expression,id,visualisation_module})
}
diff --git a/ide/src/rust/ide/src/model/project.rs b/ide/src/rust/ide/src/model/project.rs
index fbdeea596909..8962935ddd83 100644
--- a/ide/src/rust/ide/src/model/project.rs
+++ b/ide/src/rust/ide/src/model/project.rs
@@ -24,6 +24,7 @@ use uuid::Uuid;
#[automock]
pub trait API:Debug {
/// Project's name
+ // TODO [mwu] This should return Rc.
fn name(&self) -> ImString;
/// Get Language Server JSON-RPC Connection for this project.
@@ -67,6 +68,23 @@ pub trait API:Debug {
(&self, path:&model::module::Path) -> crate::model::module::QualifiedName {
path.qualified_module_name(self.name().deref())
}
+
+ /// Get qualified name of the project's `Main` module.
+ ///
+ /// This module is special, as it needs to be referred by the project name itself.
+ fn main_module(&self) -> FallibleResult {
+ let main = std::iter::once(crate::ide::INITIAL_MODULE_NAME);
+ model::module::QualifiedName::from_segments(self.name(),main)
+
+ // TODO [mwu] The code below likely should be preferred but does not work
+ // because language server does not support using project name
+ // for project's main module in some contexts.
+ // This is tracked by: https://github.com/enso-org/enso/issues/1543
+ // use model::module::QualifiedName;
+ // ReferentName::try_from(self.name().as_str())
+ // .map(QualifiedName::new_main)
+ // .map_err(Into::into)
+ }
}
impl Debug for MockAPI {
diff --git a/ide/src/rust/ide/src/model/project/synchronized.rs b/ide/src/rust/ide/src/model/project/synchronized.rs
index 0c5a551edf2b..9b8ea6d62232 100644
--- a/ide/src/rust/ide/src/model/project/synchronized.rs
+++ b/ide/src/rust/ide/src/model/project/synchronized.rs
@@ -209,6 +209,7 @@ impl Project {
use enso_protocol::binary::Notification;
match event {
Event::Notification(Notification::VisualizationUpdate {context,data}) => {
+ debug!(logger, "Visualization binary data: {String::from_utf8_lossy(data.as_ref())}");
let data = VisualizationUpdateData::new(data);
if let Some(execution_contexts) = weak_execution_contexts.upgrade() {
let result = execution_contexts.dispatch_visualization_update(context,data);
diff --git a/ide/src/rust/ide/view/graph-editor/Cargo.toml b/ide/src/rust/ide/view/graph-editor/Cargo.toml
index 3ecabf50c2df..e77fee6523f4 100644
--- a/ide/src/rust/ide/view/graph-editor/Cargo.toml
+++ b/ide/src/rust/ide/view/graph-editor/Cargo.toml
@@ -21,6 +21,7 @@ ensogl-gui-components = { version = "0.1.0", path = "../../../ensogl/lib/compone
ensogl-text = { version = "0.1.0", path = "../../../ensogl/lib/text" }
ensogl-text-msdf-sys = { version = "0.1.0", path = "../../../ensogl/lib/text/msdf-sys" }
ensogl-theme = { version = "0.1.0", path = "../../../ensogl/lib/theme" }
+failure = { version = "0.1.8" }
span-tree = { version = "0.1.0", path = "../../lib/span-tree" }
js-sys = { version = "0.3.28" }
nalgebra = { version = "0.21.1", features = ["serde-serialize"] }
diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script.rs b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script.rs
index 9b46da378b17..93a4a92fba92 100644
--- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script.rs
@@ -2,7 +2,6 @@
// TODO remove once we have proper visualizations or replace with a nice d3 example.
// These implementations are neither efficient nor pretty, but get the idea across.
-use crate::data::enso;
use crate::component::visualization;
@@ -18,7 +17,7 @@ pub fn table_visualization() -> visualization::java_script::FallibleDefinition {
let source = include_str!("java_script/table.js");
let source = format!("{}{}{}",loading_scripts,scrollable,source);
- visualization::java_script::Definition::new(enso::builtin_library(),source)
+ visualization::java_script::Definition::new_builtin(source)
}
/// Return a `JavaScript` Scatter plot visualization.
@@ -27,7 +26,7 @@ pub fn scatter_plot_visualization() -> visualization::java_script::FallibleDefin
let source = include_str!("java_script/scatterPlot.js");
let source = format!("{}{}",loading_scripts,source);
- visualization::java_script::Definition::new(enso::builtin_library(),source)
+ visualization::java_script::Definition::new_builtin(source)
}
/// Return a `JavaScript` Histogram visualization.
@@ -36,7 +35,7 @@ pub fn histogram_visualization() -> visualization::java_script::FallibleDefiniti
let source = include_str!("java_script/histogram.js");
let source = format!("{}{}",loading_scripts,source);
- visualization::java_script::Definition::new(enso::builtin_library(),source)
+ visualization::java_script::Definition::new_builtin(source)
}
/// Return a `JavaScript` Map visualization.
@@ -45,14 +44,14 @@ pub fn geo_map_visualization() -> visualization::java_script::FallibleDefinition
let source = include_str!("java_script/geoMap.js");
let source = format!("{}{}",loading_scripts,source);
- visualization::java_script::Definition::new(enso::builtin_library(),source)
+ visualization::java_script::Definition::new_builtin(source)
}
/// Return a `JavaScript` Bubble visualization. This should not be used as it is a demo visualization.
pub fn bubble_visualization() -> visualization::java_script::FallibleDefinition {
let source = include_str!("java_script/bubbleVisualization.js");
- visualization::java_script::Definition::new(enso::builtin_library(),source)
+ visualization::java_script::Definition::new_builtin(source)
}
/// Return an empty minimal `JavaScript` visualization. This should not be used except for testing.
@@ -62,5 +61,5 @@ pub fn empty_visualization() -> visualization::java_script::FallibleDefinition {
return EmptyVisualization;
"#;
- visualization::java_script::Definition::new(enso::builtin_library(),source)
+ visualization::java_script::Definition::new_builtin(source)
}
diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js
index 6f185a83c0b7..5a6ab7574308 100644
--- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js
+++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js
@@ -102,6 +102,15 @@ class GeoMapVisualization extends Visualization {
this.initMapElement()
this.initStyle()
this.dataPoints = []
+ this.setPreprocessorModule('Table.Main')
+ this.setPreprocessorCode(`
+ df -> case df of
+ Table.Table _ ->
+ columns = df.select ['label', 'latitude', 'longitude'] . columns
+ serialized = columns.map (c -> ['df_' + c.name, c.to_vector])
+ Json.from_pairs serialized . to_text
+ _ -> df . to_json . to_text
+ `)
}
initMapElement() {
@@ -136,37 +145,6 @@ class GeoMapVisualization extends Visualization {
}
onDataReceived(data) {
- if (!this.isInit) {
- // FIXME: This should be simplified when issue [#1167]
- // (https://github.com/enso-org/ide/issues/1167) has been implemented.
- // Use the previous version again:
- // 'df -> case df of\n' +
- // ' Table.Table _ ->\n' +
- // " columns = df.select ['label', 'latitude', 'longitude'] . columns\n" +
- // " serialized = columns.map (c -> ['df_' + c.name, c.to_vector])\n" +
- // ' Json.from_pairs serialized . to_text\n' +
- // ' _ -> df . to_json . to_text'
- this.setPreprocessor(
- 'df -> \n' +
- ' get_cons_name : Any -> Text | Nothing\n' +
- ' get_cons_name val =\n' +
- ' meta_val = Meta.meta val\n' +
- ' case meta_val of\n' +
- ' Meta.Atom _ ->\n' +
- ' cons = meta_val.constructor\n' +
- ' Meta.Constructor cons . name \n' +
- ' _ -> Nothing\n' +
- " if (((get_cons_name df)) == 'Table').not then (df . to_json . to_text) else\n" +
- " columns = df.select ['label', 'latitude', 'longitude'] . columns\n" +
- " serialized = columns.map (c -> ['df_' + c.name, c.to_vector])\n" +
- ' Json.from_pairs serialized . to_text'
- )
- this.isInit = true
- // We discard this data the first time. We will get another update with
- // the correct data that has been transformed by the preprocessor.
- return
- }
-
let parsedData = data
if (typeof data === 'string') {
parsedData = JSON.parse(data)
diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js
index 34040d3e31c8..e9a3aca03d32 100644
--- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js
+++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js
@@ -18,12 +18,12 @@ const buttonsHeight = 25
/**
* A d3.js ScatterPlot visualization.
*
- * To zoom use scrollwheel
- * To select click and swipe with LMB
- * To deselect click outside of selection with LMB
- * To pan click and swipe with RMB
- * To zoom out click "Fit all" or use key combination "ctrl/cmd+a"
- * To zoom into selection click appropriate button or use key combination "ctrl/cmd+z"
+ * To zoom use scroll wheel.
+ * To select click and swipe with LMB.
+ * To deselect click outside of selection with LMB.
+ * To pan click and swipe with RMB.
+ * To zoom out click "Fit all" or use key combination "ctrl/cmd+a".
+ * To zoom into selection click appropriate button or use key combination "ctrl/cmd+z".
*
* Data format (json):
* {
diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js
index 0ba675a20acd..724a16a722cd 100644
--- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js
+++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js
@@ -14,14 +14,21 @@ class TableVisualization extends Visualization {
static inputType = 'Any'
static label = 'Table'
- onDataReceived(data) {
- if (!this.isInit) {
- this.setPreprocessor(
- 'x -> (Json.from_pairs [["header", x.columns.map .name], ["data", x.columns.map .to_vector . map (x -> x.take_start 2000) ]]).to_text '
- )
- this.isInit = true
- }
+ constructor(data) {
+ super(data)
+ this.setPreprocessorModule('Table.Main')
+ this.setPreprocessorCode(`
+ x -> case x of
+ Table.Table _ ->
+ header = ["header", x.columns.map .name]
+ data = ["data", x.columns.map .to_vector . map (x -> x.take_start 2000) ]
+ pairs = [header,data]
+ Json.from_pairs pairs . to_text
+ _ -> x . to_json . to_text
+ `)
+ }
+ onDataReceived(data) {
function tableOf(content, level) {
let open = '
'
return open + content + '
'
diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/bubble_chart.rs b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/bubble_chart.rs
index ea2c0020ead7..a9d7c03fb738 100644
--- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/bubble_chart.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/bubble_chart.rs
@@ -53,13 +53,13 @@ impl BubbleChartModel {
fn receive_data(&self, data:&Data) -> Result<(),DataError> {
let data_inner = match data {
Data::Json {content} => content,
- _ => todo!() // FIXME
+ _ => return Err(DataError::BinaryNotSupported),
};
let data_inner:&serde_json::Value = data_inner;
let data_inner: Rc>> = if let Ok(result) = serde_json::from_value(data_inner.clone()) {
result
} else {
- return Err(DataError::InvalidDataType)
+ return Err(DataError::InvalidJsonText)
};
// Avoid re-creating views, if we have already created some before.
diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs
index 4921fc7c008e..353e69e5c995 100644
--- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs
@@ -2,9 +2,11 @@
use crate::prelude::*;
+pub use crate::component::node::error::Kind;
+
use crate::component::visualization::*;
use crate::component::visualization;
-use crate::data::enso;
+use crate::SharedHashMap;
use enso_frp as frp;
use ensogl::data::color;
@@ -28,23 +30,33 @@ use serde::Serialize;
const PADDING_TEXT:f32 = 10.0;
/// The Error Visualization preprocessor. See also _Lazy Visualization_ section
/// [here](http://dev.enso.org/docs/ide/product/visualizations.html).
-pub const PREPROCESSOR:&str = r#"x ->
- result = Ref.new "{ message: \"\"}"
+pub const PREPROCESSOR_CODE:&str = r#"
+x ->
+ result = Builtins.Ref.new "{ message: \"\"}"
x.catch err->
message = err.to_display_text
- Ref.put result ("{ \"kind\": \"Dataflow\", \"message\": " + message.to_json.to_text + "}")
- Ref.get result
+ Builtins.Ref.put result ("{ \"kind\": \"Dataflow\", \"message\": " + message.to_json.to_text + "}")
+ Builtins.Ref.get result
"#;
+/// The context module for the `PREPROCESSOR_CODE`. See there.
+pub const PREPROCESSOR_MODULE:&str = "Base.Main";
+/// Get preprocessor configuration for error visualization.
+pub fn preprocessor() -> instance::PreprocessorConfiguration {
+ instance::PreprocessorConfiguration::new(PREPROCESSOR_CODE,PREPROCESSOR_MODULE)
+}
+
+/// Get metadata description for error visualization.
+pub fn metadata() -> Metadata {
+ let preprocessor = preprocessor();
+ Metadata {preprocessor}
+}
// =============
// === Input ===
// =============
-pub use crate::component::node::error::Kind;
-use crate::SharedHashMap;
-
/// The input for Error Visualization.
#[allow(missing_docs)]
#[derive(Clone,Debug,Deserialize,Serialize)]
@@ -108,8 +120,7 @@ impl Error {
});
}
- frp.preprocessor_change.emit(enso::Code::from(PREPROCESSOR));
-
+ frp.preprocessor_change.emit(preprocessor());
self
}
@@ -183,14 +194,14 @@ impl Model {
}
fn receive_data(&self, data:&Data) -> Result<(),DataError> {
- iprintln!("Receive data {data:?}");
- if let Data::Json {content} = data {
- let input_result = serde_json::from_value(content.deref().clone());
- let input:Input = input_result.map_err(|_| DataError::InvalidDataType)?;
- self.set_data(&input);
- Ok(())
- } else {
- Err(DataError::InvalidDataType)
+ match data {
+ Data::Json {content} => {
+ let input_result = serde_json::from_value(content.deref().clone());
+ let input:Input = input_result.map_err(|_| DataError::InvalidDataType)?;
+ self.set_data(&input);
+ Ok(())
+ }
+ Data::Binary => Err(DataError::BinaryNotSupported)
}
}
diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs
index c3cef9d7f003..d782c60e0781 100644
--- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs
@@ -13,8 +13,8 @@ mod visualization_chooser;
use crate::prelude::*;
-use crate::data::enso;
use crate::visualization;
+use crate::component::visualization::instance::PreprocessorConfiguration;
use action_bar::ActionBar;
use enso_frp as frp;
@@ -33,7 +33,6 @@ use ensogl_theme as theme;
-
// =================
// === Constants ===
// =================
@@ -166,7 +165,7 @@ ensogl::define_endpoints! {
}
Output {
- preprocessor (enso::Code),
+ preprocessor (PreprocessorConfiguration),
visualisation (Option),
size (Vector2),
is_selected (bool),
@@ -399,14 +398,14 @@ impl ContainerModel {
}
fn set_visualization
- (&self, visualization:visualization::Instance, preprocessor:&frp::Any) {
+ (&self, visualization:visualization::Instance, preprocessor:&frp::Any) {
let size = self.size.get();
visualization.set_size.emit(size);
frp::new_network! { vis_frp_connection
// We need an additional "copy" node here. We create a new network to manage lifetime of
// connection between `visualization.on_preprocessor_change` and `preprocessor`.
// However, doing simple `preprocessor <+ visualization.on_preprocessor_change` will not
- // create any node in this network, so in fact ot won't manage the connection.
+ // create any node in this network, so in fact it won't manage the connection.
vis_preprocessor_change <- visualization.on_preprocessor_change.map(|x| x.clone());
preprocessor <+ vis_preprocessor_change;
}
@@ -546,7 +545,7 @@ impl Container {
action_bar.set_selected_visualization.emit(path);
},
Err(err) => {
- warning!(logger,"Failed to instantiate visualisation: {err:?}");
+ warning!(logger,"Failed to instantiate visualization: {err:?}");
},
};
}
diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/data.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/data.rs
index 83d5d8268fdf..e2330f8d6f8c 100644
--- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/data.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/data.rs
@@ -105,8 +105,12 @@ impl From for Data {
/// violates some other assumption of the visualization.
#[derive(Copy,Clone,Debug)]
pub enum DataError {
- /// Indicates that that the provided data type does not match the expected data type.
+ /// Visualization received a binary data package, which is currently not supported.
+ BinaryNotSupported,
+ /// Indicates that that the provided data type does not match the expected data format.
InvalidDataType,
+ /// Received text data is not valid JSON while it is required.
+ InvalidJsonText,
/// The data caused an error in the computation of the visualization.
InternalComputationError,
}
diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs
index 0677146f68e4..7a91b2af8154 100644
--- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs
@@ -2,7 +2,8 @@
use crate::prelude::*;
-use super::PreprocessorCallback;
+use crate::component::visualization::foreign::java_script::PreprocessorCallback;
+use crate::component::visualization::instance::PreprocessorConfiguration;
use ensogl::display::DomSymbol;
use fmt::Formatter;
@@ -30,6 +31,7 @@ extern "C" {
fn __Visualization__() -> JsValue;
#[allow(unsafe_code)]
+ #[wasm_bindgen(extends = js_sys::Object)]
pub type Visualization;
#[allow(unsafe_code)]
@@ -37,8 +39,8 @@ extern "C" {
fn new(init:JsConsArgs) -> Visualization;
#[allow(unsafe_code)]
- #[wasm_bindgen(method)]
- fn setPreprocessor(this:&Visualization, code:String);
+ #[wasm_bindgen(catch, js_name = __emitPreprocessorChange__, method)]
+ pub fn emitPreprocessorChange(this:&Visualization) -> Result<(),JsValue>;
}
/// Provides reference to the visualizations JavaScript base class.
@@ -85,8 +87,9 @@ impl JsConsArgs {
}
/// Helper method to emit an preprocessor change event from the visualisation.
- pub fn emit_preprocessor_change(&self, code:String){
- let closure = &self.set_preprocessor;
- (*closure)(code);
+ pub fn emit_preprocessor_change(&self, code:Option, module:Option){
+ let closure = &self.set_preprocessor;
+ let preprocessor_config = PreprocessorConfiguration::from_options(code,module);
+ (*closure)(preprocessor_config);
}
}
diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs
index 0e8a492422c0..d583d2ecdf21 100644
--- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs
@@ -1,100 +1,8 @@
//! Definition of visualization JavaScript API.
//!
-//! Visualizations can be defined as a JavaScript function which returns a class of a shape
-//! specified below. Consider the following definition:
-//!
-//! ```javascript
-//! console.log("Hi, this definition is being registered now!")
-//!
-//! return class BubbleVisualization extends Visualization {
-//! static inputType = "Any"
-//!
-//! onDataReceived(data) {
-//! const xmlns = "http://www.w3.org/2000/svg";
-//! while (this.dom.firstChild) {
-//! this.dom.removeChild(this.dom.lastChild);
-//! }
-//! const width = this.dom.getAttributeNS(null, "width");
-//! const height = this.dom.getAttributeNS(null, "height");
-//! const svgElem = document.createElementNS(xmlns, "svg");
-//! svgElem.setAttributeNS(null, "id" , "vis-svg");
-//! svgElem.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height);
-//! svgElem.setAttributeNS(null, "width" , "100%");
-//! svgElem.setAttributeNS(null, "height" , "100%");
-//! this.dom.appendChild(svgElem);
-//! data.forEach(data => {
-//! const bubble = document.createElementNS(xmlns,"circle");
-//! bubble.setAttributeNS(null,"stroke", "black");
-//! bubble.setAttributeNS(null,"fill" , "red");
-//! bubble.setAttributeNS(null,"r" , data[2]);
-//! bubble.setAttributeNS(null,"cx" , data[0]);
-//! bubble.setAttributeNS(null,"cy" , data[1]);
-//! svgElem.appendChild(bubble);
-//! });
-//! }
-//!
-//! setSize(size) {
-//! this.dom.setAttributeNS(null, "width", size[0]);
-//! this.dom.setAttributeNS(null, "height", size[1]);
-//! }
-//! }
-//! ```
-//!
-//! In particular:
-//!
-//! - [Required] **Source code**
-//! Visualization definition has to be a valid body of JavaScript function which returns a class
-//! definition. Instances of that class will be considered separate visualizations. You are
-//! allowed to use global variables / global state across visualizations of the same type, but you
-//! are highly advised not to do so.
-//!
-//! - [Required] **`Visualization` superclass**
-//! The class returned by the definition function should extend the predefined `Visualization`
-//! class. Classes which do not extend it, will not be registered as visualizations. The
-//! superclass defines a default constructor and a set of utils:
-//! - The `setPreprocessor(code)` method allowing setting an Enso code which will be evaluated on
-//! server-side before sending data to visualization.
-//! - The `dom` field, which will be initialized in the constructor to the DOM symbol used to host
-//! the visualization content. You are free to modify the DOM element, including adding other
-//! elements as its children.
-//!
-//! - [Optional] **Field `label`**
-//! The static field `label` is an user-facing name used to identify the visualization. You are
-//! not allowed to define several visualizations of the same name in the same Enso library. In
-//! case the field is missing, the name will be inferred from the class name by splitting the
-//! camel-case name into chunks and converting them to lowercase string.
-//!
-//! - [Optional] **Field `inputType`**
-//! The static field `inputType` is used to determine which Enso data types this visualization
-//! can be used for. Its value should be a valid Enso type, like "String | Int". In case the field
-//! is an empty string or it is missing, it will default to "Any", which is a type containing all
-//! other types. It is a rare case when you want to define a visualization which is able to work
-//! with just any data type, so you are highly advised to provide the type definition.
-//!
-//! - [Optional] **Field `inputFormat`**
-//! The static field `inputFormat` is used to determine what format the data should be provided
-//! to the `onDataReceived` function. Currently, the only valid option is "json", but it will be
-//! possible to set it to "binary" in the future. In the later case, it is up to the visualization
-//! author to manage the binary stream received from the server.
-//!
-//! - [Optional] **Constructor**
-//! The visualization will be instantiated by providing the constructor with a configuration
-//! object. The shape of the configuration object is not part of the public API and can change
-//! between releases of this library. You have to pass it unchanged to the superclass constructor.
-//!
-//! - [Optional] **Function `onDataReceived`**
-//! The `onDataReceived(data)` method is called on every new data chunk received from the server.
-//! Note that the visualization will receive the "full data" if you are not using the
-//! `setPreprocessor` method.
-//!
-//! - [Optional] **Function `setSize`**
-//! The `setSize(size)` method is called on every size change of the visualization. You should not
-//! draw outside of the provided area, however, if you do so, it will be clipped to the provided
-//! area automatically. The `size` parameter contains two fields `width` and `height` expressed in
-//! pixels.
-
-// TODO: Connect the `setPreprocessor` method on Rust side.
-
+//! For details of the API please see:
+//! * docstrings in the `visualization.js` file;
+//! * [visualization documentation](https://dev.enso.org/docs/ide/product/visualizations.html).
// FIXME: Can we simplify the above definition so its more minimal, yet functional?
@@ -103,17 +11,17 @@ use crate::prelude::*;
use crate::component::visualization::InstantiationError;
use crate::component::visualization::InstantiationResult;
use crate::component::visualization;
-use crate::data::enso;
use super::binding;
use super::instance::Instance;
use ensogl::display::Scene;
use ensogl::system::web::JsValue;
+use fmt::Formatter;
use js_sys::JsString;
use js_sys;
-use wasm_bindgen::JsCast;
-use fmt::Formatter;
use std::str::FromStr;
+use wasm_bindgen::JsCast;
+
// =================
@@ -150,7 +58,7 @@ pub struct Definition {
impl Definition {
/// Create a visualization source from piece of JS source code. Signature needs to be inferred.
- pub fn new (library:impl Into, source:impl AsRef) -> Result {
+ pub fn new(project:visualization::path::Project, source:impl AsRef) -> Result {
let source = source.as_ref();
let source = source;
let context = JsValue::NULL;
@@ -158,19 +66,23 @@ impl Definition {
let js_class = binding::js_class();
let class = function.call1(&context,&js_class).map_err(Error::InvalidFunction)?;
- let library = library.into();
let input_type = try_str_field(&class,field::INPUT_TYPE).unwrap_or_default();
let input_format = try_str_field(&class,field::INPUT_FORMAT).unwrap_or_default();
let input_format = visualization::data::Format::from_str(&input_format).unwrap_or_default();
let label = label(&class)?;
- let path = visualization::Path::new(library,label);
+ let path = visualization::Path::new(project,label);
let signature = visualization::Signature::new(path,input_type,input_format);
Ok(Self{class,signature})
}
+ /// Create a definition of visualization that is built into the IDE.
+ pub fn new_builtin(source:impl AsRef) -> Result {
+ Self::new(visualization::path::Project::Builtin,source)
+ }
+
fn new_instance(&self, scene:&Scene) -> InstantiationResult {
let instance = Instance::new(&self.class, scene)
.map_err(InstantiationError::ConstructorError)?;
diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs
index 0c64ce8cff7f..7a5214e3edb4 100644
--- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs
+++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs
@@ -10,6 +10,7 @@
use crate::prelude::*;
use crate::component::visualization::*;
+use crate::component::visualization::instance::PreprocessorConfiguration;
use crate::component::visualization::java_script::binding::JsConsArgs;
use crate::component::visualization::java_script::method;
use crate::component::visualization;
@@ -76,7 +77,7 @@ pub type Result = result::Result;
// =====================
/// Helper type for the callback used to set the preprocessor code.
-pub trait PreprocessorCallback = Fn(String);
+pub trait PreprocessorCallback = Fn(PreprocessorConfiguration);
/// Internal helper type to store the preprocessor callback.
type PreprocessorCallbackCell = Rc>>>;
@@ -91,7 +92,8 @@ pub struct InstanceModel {
pub logger : Logger,
on_data_received : Rc