Skip to content

Commit

Permalink
[doc] Add ADR for custom widgets support
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
  • Loading branch information
pcdavid committed May 15, 2023
1 parent dbabc68 commit 240c1b8
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

- [ADR-098] Use the editing context to compute the metamodels
- [ADR-099] Filter tree based representations
- [ADR-100] Add support for custom widgets

=== Breaking changes

Expand Down Expand Up @@ -49,7 +50,7 @@ In the _styleDescription_, the definition of a color are now a select list of al
These natures can be used later to enable or not some capabilities on a project.
This work will start by adding the ability to filter the project's domains.
A large set of features will have to be updated in order to stop considering the list of metamodels available in an editing context as a certainty.
- https://github.com/eclipse-sirius/sirius-components/issues/1946[1#946] Enabled child extenders in the View DSL implementation.
- https://github.com/eclipse-sirius/sirius-components/issues/1946[#1946] Enabled child extenders in the View DSL implementation.
This allows downstream projects and applications to provide their own sub-types of the DSL types (e.g. new WidgetDescriptions).
In addition to registering the extension metadmodel itself, users must provide a `ChildExtenderProvider` bean for their extensions to be properly integrated.
- https://github.com/eclipse-sirius/sirius-components/issues/1918[#1918] [tree] Its is now possible to filter tree items in trees.
Expand Down
133 changes: 133 additions & 0 deletions doc/adrs/100_add_support_for_custom_widgets.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
= ADR-100 - Add support for custom widgets

== Context

The _Form_ representation currently supports a fixed set of generic widgets.
Even if we enrich this set, it will never account for all the specific needs of concrete applications.
The Sirius Components framework should be open for custom applications to provide their own widgets.

== Decision

We will make the _Form_ and _Form Description Editor_ representations (including the View-based Form definitions) extensible to allow for applications to contribute and use their own widgets beyond the fixed set supported by Sirius Components.

We will provide a simple "Slider" widget as part of Sirius Web Sample Application (not in Sirius Components) to illustrate how an application can contribute its own widgets.

=== Form Representation Core

A new interface named `IWidgetDescriptor` will be introduced to allow applications to provide the required information to "plug" their new widgets into the Form rendering:

```java
public interface IWidgetDescriptor {
String getWidgetType();

Class<? extends IComponent> getComponentClass();

Class<? extends IProps> getInstancePropsClass();

Class<? extends IProps> getComponentPropsClass();

Optional<Object> instanciate(IProps elementProps, List<Object> children);

Optional<Element> createElement(VariableManager variableManager, AbstractWidgetDescription widgetDescription);
}
```

The `FormRenderer` will take a list of such widget descriptors as argument, and pass it to its helpers:

```java
public FormRenderer(List<IWidgetDescriptor> widgetDescriptors) {
this.baseRenderer = new BaseRenderer(new FormInstancePropsValidator(widgetDescriptors), new FormComponentPropsValidator(widgetDescriptors), new FormElementFactory(widgetDescriptors));
}
```

This list of widget descriptors will be passed by `FormEventProcessor`, which will itself get it from `FormEventProcessorFactory`.
`FormEventProcessorFactory` itself will get these using usual Spring dependency mechanisms.

If the custom widget supports edition operations, its backend implementation will also need to provide the appropriate `IFormEventHandler` implementations and supporting types (custom `IFormInput`s and `IPayload`s).

For the example "Slider" widget, the only edition operation supported will be `editSlider(input: EditSliderInput!): EditSliderPayload!`, which will require:

```java
public record EditSliderInput(UUID id, String editingContextId, String representationId, String sliderId, int newValue) implements IFormInput {
}

@MutationDataFetcher(type = "Mutation", field = "editSlider")
public class MutationEditSliderDataFetcher implements IDataFetcherWithFieldCoordinates<CompletableFuture<IPayload>> {
// Code omitted
}
````

=== GraphQL Schema

On the backend, since we switched to declaring our GraphQL Schema using `.graphqls` files, extending the GraphQL Schema with the definition of a new widgets is simply a matter of contributing the corresponding `.graqphls` file.
The file in question can declare both the new widget type, any needed styles or additional types, and any new mutation needed to trigger the new widget's behaviors.

For example:

```graphql
type Slider implements Widget {
id: ID!
diagnostics: [Diagnostic!]!
label: String!
iconURL: String
minValue: Int!
maxValue: Int!
currentValue: Int!
}

extend type Mutation {
editSlider(input: EditSliderInput!): EditSliderPayload!
}
```

On the frontend, things are a little more involved. The shape of the GraphQL Schema for forms impacts:

- `FormEventFragments.types.ts` defines all the TypeScript types, but is easily extensible; new TypeScript types can be added by the new widget's implementation code.
- `FormEventFragments.ts` is more complex and hard-codes the set of supported widgets and all their fields in the query's structure. It will need to be parameterized (it is already computed) and use some form of "custom widgets schema" metadata supplied by the application in a React context.

[#frontend]
=== Frontend (property sections)

Assuming the new widgets have been rendered by the backend, and received with all their custom fields by the frontend through its GraphQL Subscription, it must then be actually displayed.

This is handled by `PropertySection.tsx` but, like other parts the set of supported widgets (e.g. `ButtonPropertySection`) is currently hard-coded.
We will use the same kind of technique as already used to make the `Workench` component independant of the concrete representations supported:

* Extract a generic super-type shared by all widgets, similar to `RepresentationComponentProps` but for widgets;
* Setup a React context (similar to `RepresentationContext`) to register new widget types;
* At the top-level of the application, register the custom widget types it wants to use and put that information into the context;
* Modify `PropertySection` so that in addition to the hard-coded widgets, it also looks into the context to find the React component to instanciate for a widget it does not know about.

=== View DSL Support

To support the definition of custom widgets through the View DSL, we will first need to _make the View metamodel extensible_.
In its current state, the generated implementation of `view.ecore` does not support new types (e.g. new `WidgetDescription` subtypes) which are not directly defined in `view.ecore`.
This is possible in EMF with the "child creation extender" GenModel feature, but needs to be enabled explictly.
This mechanism is normally used in the context of an Eclipse runtime and relies on EMF extension points, so some adaptation will be needed to support it in our context.

Once it is possible to create View models which use custom widgets definitions, these models must be converted into the actual API-based widget description (see <<#core, the section above>>) to be rendered correctly (and later displayed by <<#frontend,the frontend>>).
This transformation is handled by `ViewFormDescriptionConverter`, but like the rest it will need to be made extensible.
It already uses and EMF-based "switch class" to handle the different kinds of core widgets, so we will extend this to also consider EMF switches which know about any custom widgets.

Finally, we will create a `slider.ecore` metamodel which defines the `SliderDescription` type as a custom `WidgetDescrption` subtype and provide the corresponding converter switch.

=== Form Description Editor Support

Support for custom widgets in the visual _Form Description Editor_ will be minimal.
Custom widgets will appear using the palette with their name and a custom icon, but when displayed inside the WYSIWYG editor they will all appear using the same generic icon (to be determined).
There will be no style preview.
It should still be possible to create, move, and delete these widgets using the editor.

The kinds of widgets supported by the editor is currently hard-coded in several places in the frontend, which will need to be made extensible and use the same context/registry information as in <<#frontend,the property section support>>.
This includes at least:

- In `WidgetOperations.tsx`: the `isKind` function.
- In `FormDescriptionEditorRepresentation.types.ts`: the `Kind` type.
- In `FormDescriptionEditorRepresentation.tsx`: the hard-coded definition of the palette elements.

On the backend side, `FormDescriptionEditorGroupComponent` will be made extensible to support `ViewFormDescriptionEditorConverterSwitch`.
== Status

Accepted

== Consequences

0 comments on commit 240c1b8

Please sign in to comment.