Writing DSL Components
In order to take advantage of the auto-completion and validation of TypeScript types, asset libraries can export a component library for content authoring. Creating components isn’t much different than writing a React component for the web. The primative elements uses the react-json-reconciler to create the JSON content tree, with utilities to make it quick and painless to create new asset-components.
Creating a Basic Component
The Asset
component from the @player-tools/dsl
package is the quickest way to create a new component. The Asset
component will take all the Asset’s properties and convert them to their equivalent JSON representation when serialized.
In the examples below, we’ll be creating a TSX component for the action
asset in our reference set.
The action
asset has a label
slot (which is typically used as a text
asset), a value
(for flow transitions), and an exp
for evaluating expressions.
For this example we’ll use a resemblance of this type, but in practice types should be imported directly from their asset rather than duplicating them.
Note: The Asset
type we’re importing here from the @player-ui/player
package is different than the Asset
component from the @player-tools/dsl
package. The former is the basic TypeScript definition for what an Asset in Player is while the latter is a helper function for allowing DSL components to be created. Fundamentally they share a name to reinforce the abstraction of foundational capabilities to core libraries
To turn this interface into a usable component, create a new React component that renders an Asset:
This would allow users to import the Action
component, and render it to JSON:
which when compiled would look like
The AssetPropsWithChildren
type is a utility type to help convert the Asset
type (which has a required id
and type
properties) to a type more suited for components. It changes the id
to be optional, and adds a applicability
property automatically.
Slots
Continuing the example fo the ActionAsset
, we need a way for users to users to specify the nested label
property, which itself is another asset. This can be accomplished using the createSlot
utility function. The createSlot
function also accept components to enable automatically creating text
and collection
assets when they aren’t specified where needed. If these components aren’t passed into the slot when used, the resulting content may be invalid. Let’s add a Label
slot to our Action
component to allow it to be easily authored. Lets assume we already have a Text
and Collection
component.
This adds component (Action.Label
) that will automatically place any nested children under the label
property of the parent asset:
which can also be written as the following because the slot has a TextComp
passed in for it which allows the automatic creation of a text asset for any regular text that is passed in the slot
When compiled, both examples would look like (note the auto injection of the Text
asset and corresponding Asset Wrapper):
And if we wanted to have the label
property to have to text assets we could write the following DSL
which when compiled would look like the following (note the automatic insertion of the Collection
Asset):
Creating a Complex Component
While a majority of Assets can be described simply via the base Action
Component, there are certain cases where DSL components need to contain a bit more logic. This section aims to describe further tools that are offered in the @player-tools/dsl
package.
Components with Specially Handled Properties
In the previous example, we covered how to create a DSL Component for our reference Action
Asset. Our actual Action Asset however looks a little bit different.
Crucially, the difference is in how the exp
property is handled. As the exp
property is an Expression
, if we just allowed the Action
component to process this property, we would end up with an ExpressionTemplate
instance not an Expression
instance. While technically they are equivalent, there is no need to wrap the final string in the Expression Template tags (@[]@
) since we know the string will be an Expression
and it will just lead to additonal procssing at runtime. Therefore, we need to do a few things to properly construct this DSL component.
The first is to modify the type for the commponent. In the above code snippit we are using the Omit
type to remove the base exp
property from the source type and replacing it with an exp
property that expects a ExpressionTemplateInstance
which allows an DSL expression to be passed in.
The second is to extract out the exp
property from the props and use a property
component to manually control how that property will get serialized. This component is exposed by the underlying react-json-reconciler
library which also supplies an array
, obj
and value
component to allow full control over more complicated data structures. The @player-tools/dsl
package also exposes the toJsonProperties
function to process whole non-Asset objects.
View Components
For Assets that are intended to be Views, a View
component is exported from the @player-tools/dsl
package. Its usage is exactly the same as the Asset
component, however it correctly handles the serialization of any Crossfield Validations that exist on the View.