Overview
The base architecture of Sprotty revolves around an unidirectional cyclic flow of information between three major components: the ActionDispatcher
, the CommandStack
, and the Viewer
. This leads to a clear and easily testable flow of data which prevents feedback loops.
flowchart TD; ActionDispatcher CommandStack Viewer ActionDispatcher -->|Command| CommandStack CommandStack -->|SModel| Viewer Viewer -->|Action| ActionDispatcher
The main role of the ActionDispatcher
is to receive an Action
and produce a corresponding command to be transmitted to the CommandStack
using ActionHandlers
. All operations on the diagram must be passed through the ActionDispatcher
, so the CommandStack
and the Viewer
must never be invoked directly.
The ActionDispatcher
also communicates with the ModelSource
through Action
s in a bidirectional manner, for example to inject external data into the loop or apply edits to the ModelSource
.
flowchart LR; ModelSource ActionDispatcher ModelSource -->|Action| ActionDispatcher ActionDispatcher -->|Action| ModelSource
Action
s are objects without behavior, JSON structures that describe what should happen but not how it should happen. As such, they can be serialized and serve as protocol messages that are exchanged between the client and the server. In actions, model elements are referred to by their IDs.
There are two different ModelSource
s: the LocalModelSource
offers an API to control the model directly in the client, while the DiagramServer
delegates to a remote source, e.g. through a WebSocket or a VSCode extension.
The CommandStack
executes the commands it receives from the ActionDispatcher
. It chains the promises returned by the execution methods and keeps an undo and a redo stack. It merges the current commands with the last one, e.g. to only keep the start and end point of a move from a drag operation. It is responsible for producing a graph model (namely SModel
) and forwards it to the Viewer
to be rendered.
Command
s describe the behavior of their corresponding Action
. They have the typical methods execute()
, undo()
and redo()
, each of which take the current model and a command execution context as parameter, and return the new model or a promise for it. The latter serves to chain asynchronous commands such as animations.
The diagram is stored in an internal model called SModel
. The root of the diagram is always an instance of SModelRootImpl
and holds an index of the model to allow fast lookup of elements by ID. All elements of a diagram inherit SModelElementImpl
which has a unique ID and a mandatory type referring to its View
. The model elements are organized in a tree derived from the children
and parent
properties of each model element. It can be useful to introduce domain-specific information into the SModel
. This can be achieved via creating new element classes that inherit from any related SModelElementImpl
.
flowchart BT; SModelElementImpl SShapeElementImpl SEdgeImpl SNodeImpl SPortImpl SLabelImpl CustomEdge CustomNode CustomPort CustomLabel SShapeElementImpl --> SModelElementImpl SEdgeImpl --> SModelElementImpl CustomEdge -.-> SEdgeImpl SNodeImpl --> SShapeElementImpl CustomNode -.-> SNodeImpl SPortImpl --> SShapeElementImpl CustomPort -.-> SPortImpl SLabelImpl --> SShapeElementImpl CustomLabel -.-> SLabelImpl
The Viewer
is responsible for turning the internal model into its representation in the DOM. The conversion from an SModel
to its representation in the DOM is not direct. Instead, Sprotty first creates a VirtualDOM
and uses it to patch the actual DOM. This approach saves on expensive modification of the DOM by applying only the minimum amount of modification to it.
The Viewer
receives an SModel
from the CommandStack
and traverses it to apply a corresponding View
to every element.
The viewer is also responsible to add event listeners and animations using its Decorator
s. The received events should be converted to Action
s and transferred to the ActionDispatcher
.
flowchart LR; Viewer ViewRegistry Views VirtualDOM DOM Viewer --> ViewRegistry ViewRegistry --> Views Views -->|render| VirtualDOM VirtualDOM -->|patch| DOM DOM -->|event| Viewer
The Viewer
uses the ViewRegistry
to look up the View
for a graph model element using its ID.
A View
knows how to turn a graph model element and its children into a virtual DOM node. It uses JSX technology and contains a render
method producing one or a group of SVG elements.