The base architecture of Sprotty revolves around an unidirectional cyclic flow of information between three major components: 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
ActionHandlers. All operations on the diagram must be passed through the
ActionDispatcher, so the
CommandStack and the
Viewer must never be invoked directly.
flowchart LR; ModelSource ActionDispatcher ModelSource -->|Action| ActionDispatcher ActionDispatcher -->|Action| ModelSource
Actions 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
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.
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.
Commands describe the behavior of their corresponding
Action. They have the typical methods
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
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
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
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.
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
Decorators. The received events should be converted to
Actions and transferred to the
flowchart LR; Viewer ViewRegistry Views VirtualDOM DOM Viewer --> ViewRegistry ViewRegistry --> Views Views -->|render| VirtualDOM VirtualDOM -->|patch| DOM DOM -->|event| Viewer
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.