Skip to content

Commit

Permalink
Merge pull request #288 from player-ui/docs/dsl-docs
Browse files Browse the repository at this point in the history
Refactor existing DSL docs.
  • Loading branch information
KetanReddy authored Feb 10, 2024
2 parents 1ba1afa + 935fe28 commit 4b551b3
Show file tree
Hide file tree
Showing 14 changed files with 1,123 additions and 974 deletions.
33 changes: 25 additions & 8 deletions docs/site/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const navigation: Navigation = {
title: 'Overview',
path: '/content',
},
{
title: 'Navigation',
path: '/content/navigation',
},
{
title: 'Assets & Views',
path: '/content/assets-views',
Expand All @@ -78,12 +82,25 @@ const navigation: Navigation = {
path: '/content/data-expressions',
},
{
title: 'Navigation',
path: '/content/navigation',
title: 'Schema',
path: '/content/schema',
},
],
},
{
title: 'Authoring',
routes: [
{
title: 'Templates',
path: '/content/templates',
title: 'Overview',
path: '/dsl',
},
{
title: 'Views',
path: '/dsl/views',
},
{
title: 'Schema',
path: '/dsl/schema',
},
],
},
Expand All @@ -106,6 +123,10 @@ const navigation: Navigation = {
title: 'Custom Assets',
path: '/assets/custom',
},
{
title: 'DSL Components',
path: '/assets/dsl',
},
],
},
{
Expand All @@ -115,10 +136,6 @@ const navigation: Navigation = {
title: 'Storybook',
path: '/tools/storybook',
},
{
title: 'TSX Content Authoring',
path: '/tools/dsl',
},
{
title: 'CLI',
path: '/tools/cli',
Expand Down
207 changes: 207 additions & 0 deletions docs/site/pages/assets/dsl.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
title: Writing DSL Components
---

# Creating TSX 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](https://github.com/intuit/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.

```ts
import type { Asset, AssetWrapper, Expression } from '@player-ui/player';

export interface ActionAsset<AnyTextAsset extends Asset = Asset> extends Asset<'action'> {
/** The transition value of the action in the state machine */
value?: string;

/** A text-like asset for the action's label */
label?: AssetWrapper<AnyTextAsset>;

/** An optional expression to execute before transitioning */
exp?: Expression;
}
```

_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:

```tsx
import { Asset, AssetPropsWithChildren } from '@player-tools/dsl';

export const Action = (props: AssetPropsWithChildren<ActionAsset>) => {
return <Asset type="action" {...props} />;
}
```

This would allow users to import the `Action` component, and _render_ it to JSON:

```tsx
const myView = <Action value="next" />
```

which when compiled would look like

```json
{
"id": "root",
"type": "action",
"value": "next"
}
```

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.


```tsx
import React from 'react';
import { Asset, AssetPropsWithChildren, createSlot } from '@player-tools/dsl';

export const Action = (props: AssetPropsWithChildren<ActionAsset>) => {
return <Asset type="action" {...props} />;
}

Action.Label = createSlot({
name: 'label',
wrapInAsset: true,
TextComp: SomeTextComponent
CollectionComp: SomeCollectionComponent
})
```

This adds component (`Action.Label`) that will automatically place any nested children under the `label` property of the parent asset:

```tsx
const myView = (
<Action value="next">
<Action.Label>
<Text value="Continue" />
</Action.Label>
</Action>
);
```


```tsx
import React from 'react';

const myView = (
<Action value="next">
<Action.Label>Continue</Action.Label>
</Action>
);
```

which when compiled would look like (note the auto injection of the `Text` asset and corresponding Asset Wrapper):

```json
{
"id": "root",
"type": "action",
"value": "next",
"label": {
"asset": {
"id": "root-label-text",
"type": "text",
"value": "Continue"
}
}
}
```

And if we wanted to have the `label` property to have to text assets we could write the following DSL

```tsx
const myView = (
<Action value="next">
<Action.Label>
<Text>Some</Text>
<Text>Text</Text>
</Action.Label>
</Action>
);
```

which when compiled would look like the following (note the automatic insertion of the `Collection` Asset):

```json
{
"id": "root",
"type": "action",
"value": "next",
"label": {
"asset": {
"id": "root-collection",
"type": "text",
"values": [
{
"asset": {
"id": "root-collection-1-text",
"type": "text",
"value": "Some"
}
},{
"asset": {
"id": "root-collection-2-text",
"type": "text",
"value": "Text"
}
}
]
}
}
}
```

## 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.

```tsx
import React from 'react';

export const Action = (
props: Omit<AssetPropsWithChildren<ActionAsset>, 'exp'> & {
/** An optional expression to execute before transitioning */
exp?: ExpressionTemplateInstance;
}
) => {
const { exp, children, ...rest } = props;

return (
<Asset type="action" {...rest}>
<property name="exp">{exp?.toValue()}</property>
{children}
</Asset>
);
};
```

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.


Loading

0 comments on commit 4b551b3

Please sign in to comment.