Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] plugin and cli updates, links fix #294

Merged
merged 25 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f3e449a
plugin core doc addition
lexfm Jan 29, 2024
dd531f4
Merge branch 'main' into plugin-core-documentation
lexfm Jan 29, 2024
99acda1
some more cleanup and information
lexfm Jan 30, 2024
9eb267e
Merge branch 'plugin-core-documentation' of https://github.com/lexfm/…
lexfm Jan 30, 2024
38f5845
Merge branch 'main' into plugin-core-documentation
lexfm Jan 30, 2024
85c0b42
Added dsl validate command
lexfm Jan 30, 2024
8f65ad3
clean up first set of observation wording
lexfm Jan 30, 2024
47ee1f0
Updated descriptions to include TSC compiling
lexfm Jan 31, 2024
9f25410
Plugin implementation doc v1
lexfm Jan 31, 2024
f02fa87
plugin docs addl cleanup
lexfm Jan 31, 2024
835cc51
Merge branch 'main' into plugin-core-documentation
lexfm Jan 31, 2024
34da9dc
Overview plugin links fix
lexfm Feb 1, 2024
17e344c
Merge branch 'main' into plugin-core-documentation
lexfm Feb 1, 2024
26c28f1
Fixing invalid character on plugin implementation doc
lexfm Feb 2, 2024
1c8dd31
Merge branch 'main' into plugin-core-documentation
lexfm Feb 2, 2024
b039bcb
Added snippets reference
lexfm Feb 2, 2024
e52fa8a
plugin hook implementation wording cleanup
lexfm Feb 2, 2024
7ba52cd
Asset reference link fixed
lexfm Feb 5, 2024
37856a7
Merge branch 'plugins-links-fix-2' into afimbres/docs-plugin-cli
lexfm Feb 5, 2024
a1e6430
Merge branch 'plugin-core-documentation' into afimbres/docs-plugin-cli
lexfm Feb 5, 2024
e7b60c5
Merge branch 'cli-documentation-update' into afimbres/docs-plugin-cli
lexfm Feb 5, 2024
b6fe347
Doc: Plugin react wording correction.
lexfm Feb 6, 2024
12e9390
Update docs/site/pages/plugin-implementation.mdx
lexfm Feb 6, 2024
d957434
docs: plugin implementation 1st observations
lexfm Feb 6, 2024
1630e25
Docs: plugin implementation obervations pt2
lexfm Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/site/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const navigation: Navigation = {
title: 'Writing Plugins',
path: '/writing-plugins',
},
{
title: 'Plugin Implementation',
path: '/plugin-implementation',
},
{
title: 'Multi-Flow Experiences',
path: '/guides/multi-flow-experiences',
Expand Down
2 changes: 1 addition & 1 deletion docs/site/pages/guides/designing-semantic-assets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Designing Semantic Assets

While not a _hard_ requirement by Player, the API design for assets plays an important role in it's adoption, especially if the intent is to re-use content across platforms. In many cases, Player content is written, and edited many more times than assets are created, and thus it's schema plays an important role in it's effective adoption.

Player ships with a set of [Reference Assets](assets/reference) to get started, but intentionally doesn't include anything beyond some basics. We believe it's up to each consumer to define their own semantics (if at all), that best suites their applications.
Player ships with a set of [Reference Assets](../assets/reference) to get started, but intentionally doesn't include anything beyond some basics. We believe it's up to each consumer to define their own semantics (if at all), that best suites their applications.

## Intent Based Schema

Expand Down
92 changes: 92 additions & 0 deletions docs/site/pages/plugin-implementation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: Plugin Implementation
---

# Plugin Implementation

Plugins main capabilities are to extend or add new functionality by tapping into player's pipeline of components via hooks . In this section we'll go over the steps to implement a plugin.
lexfm marked this conversation as resolved.
Show resolved Hide resolved

We'll use the [stage-revert-data](./plugins/stage-revert-data) plugin as example. After creating our plugin class, in the `apply` method we'll add the following code:
lexfm marked this conversation as resolved.
Show resolved Hide resolved

```typescript
lexfm marked this conversation as resolved.
Show resolved Hide resolved
export default class StageRevertDataPlugin implements PlayerPlugin {
name = 'stage-revert-data-plugin';

apply(player: Player) {
let dataController: DataController;
let stageData: String;
let commitTransitions: String[];
let commitShadowModel: Boolean = false;

const GatedDataMiddleware = new ValidationMiddleware(
() =>
commitShadowModel
? undefined
: {
message: 'staging data',
severity: 'error',
},
{ shouldIncludeInvalid: () => true }
);
```
We defined variables that will be used in the plugin:
- `dataController`: The data orchestrator of our Player instance
- `stageData`: State attribute that comes from the view attribute content to enabling the staging of data
- `commitTransitions`: The list of view names which the shadow model should be committed if transitioned to. Comes from the view state attribute.
- `commitShadowModel`: Flag that enables committing shadow model into data model after the transition, only if `stageData` is set to true and the next view name is included in `commitTransitions`.
- `GatedDataMiddleware` Data Middleware that will be used to intercept the data pipeline if `stageData` is set to true.
lexfm marked this conversation as resolved.
Show resolved Hide resolved

The next step is to tap into the necessary Player hooks. First we tap into the `viewController` which we can then intercept the `resolveView` hook. By defining our own property method `call` we can intercept the current view state and read the `stageData` and `commitTransitions` properties.
lexfm marked this conversation as resolved.
Show resolved Hide resolved

```javascript
lexfm marked this conversation as resolved.
Show resolved Hide resolved
player.hooks.viewController.tap(this.name, (vc) => {
vc.hooks.resolveView.intercept({
call: (view, id, state) => {
stageData = state?.attributes?.stageData;
commitTransitions = state?.attributes?.commitTransitions;
},
});
});
```
`Note`: notice how each time we tap into a hook, we use the `this.name` property as the name of the plugin. This is important to avoid conflicts with other plugins.

Next we tap into the `dataController`, so we can scope the data controller instance for future use. Then we tap into the `resolveDataStages` plugin in this data controller instance. If the `stage` property is set to true, we add our `GatedDataMiddleware` to the data pipeline. If not, we return the data pipeline as is.

```javascript
lexfm marked this conversation as resolved.
Show resolved Hide resolved
player.hooks.dataController.tap(this.name, (dc: DataController) => {
dataController = dc;

dc.hooks.resolveDataStages.tap(this.name, (dataPipeline) => {
return stageData
? [...dataPipeline, GatedDataMiddleware]
: [...dataPipeline];
});
});
```
Finally, we tap into the `flowController` so we can intercept the `flow` hook. We then tap into the `transition` hook, which is called every time the player transitions from one view to another. If the `commitTransitions` includes the next view name, we set the `commitShadowModel` flag to true, and commit the data stored in the shadow model through `GatedDataMiddleware` into the data model. Whether the data was committed from the shadow model or not, we clear the shadow model paths in the `GatedDataMiddleware` instance and set the `commitShadowModel` flag to false as final steps.

```javascript
lexfm marked this conversation as resolved.
Show resolved Hide resolved
player.hooks.flowController.tap(this.name, (flowController) => {
flowController.hooks.flow.tap(this.name, (flow) => {
flow.hooks.transition.tap(this.name, (from, to) => {
if (from) {
if (commitTransitions.includes(to.name)) {
commitShadowModel = true;
player.logger.debug(
'Shadow Model Data to be committed %s',
GatedDataMiddleware.shadowModelPaths
);
dataController.set(GatedDataMiddleware.shadowModelPaths);
}

commitShadowModel = false;
GatedDataMiddleware.shadowModelPaths.clear();
}
});
});
});
```

And this is how we implement a plugin that manages the staging of data based on the view state attributes.

Code Snippets Reference: [StageRevertDataPlugin](https://github.com/player-ui/player/blob/main/plugins/stage-revert-data/core/src/index.ts)
2 changes: 1 addition & 1 deletion docs/site/pages/plugins/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Internally they allow access to many of the core sub-systems, which can add feat

![Plugins Overview Diagram](/plugin_overview.png?darkModeInvert)

The scope of what a plugin is capable of is pretty broad, but are typically broken down into smaller reusable modules. Some are more end-user focused ([Common Expression Plugin](./common-expression) and [Common Types Plugin](./common-types)) while others are more relavant for other plugin developers ([Expression Plugin](./expression) and [Types Provider Plugin](./types-provider))
The scope of what a plugin is capable of is pretty broad, but are typically broken down into smaller reusable modules. Some are more end-user focused ([Common Expression Plugin](../plugins/common-expressions) and [Common Types Plugin](../plugins/common-types)) while others are more relavant for other plugin developers ([Expression Plugin](../plugins/expression) and [Types Provider Plugin](../plugins/types-provider))
2 changes: 1 addition & 1 deletion docs/site/pages/plugins/stage-revert-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Stage Revert Data
platform: core,ios
---

# Stage Rvert Data
# Stage Revert Data Plugin

This plugin enables users to temporarily stage data changes before committing to the actual data model

Expand Down
20 changes: 18 additions & 2 deletions docs/site/pages/tools/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Plugins are the way to change runtime behavior of the CLI actions. This includes
# Commands

- [`player dsl compile`](#player-dsl-compile)
Compile Player DSL files into JSON
Compile Player DSL files into JSON after running TSC compiler against Typescript files

```
USAGE
Expand All @@ -60,7 +60,23 @@ FLAGS
--skip-validation Option to skip validating the generated JSON

DESCRIPTION
Compile Player DSL files into JSON
Compile Player DSL files into JSON after running TSC compiler against Typescript files
```

- [`player dsl validate`](#player-dsl-validate)
Runs isolated TSC compiler on authored Player DSL Typescript files.

```
USAGE
$ player dsl validate [-f <value>] [-c <value>]

FLAGS
-c, --config=<value> Path to a specific config file to load.
By default, will automatically search for an rc or config file to load
-f, --files=<value>... A list of files or globs to validate

DESCRIPTION
Runs isolated TSC compiler on authored Player DSL Typescript files.
```

- [`player json validate`](#player-json-validate)
Expand Down
23 changes: 22 additions & 1 deletion docs/site/pages/writing-plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ platform: core,react

Core plugins are the easiest way to extend Player functionality regardless of what platform you are using Player on. To make writing core plugins easy `@player-ui/player` exposes an interface `PlayerPlugin` that denotes everything needed. The two mandatory features are a `name` property which is lets Player know how to refer to the plugin and an implemented `apply` function that takes a `player` object. Optionally a `symbol` property can be used to provide a unique identifier that can be used to retrieve the plugin from Player.

The first step for creating a plugin is making our plugin class, making sure it implements the `PlayerPlugin` interface from `@player-ui/player`. By convention, a name attribute with the dash-cased name of your plugin should be defined.

```typescript
export default class ExamplePlayerPlugin implements PlayerPlugin {
name = 'example-player-plugin';

{/* A constructor can go here */}

apply(player: Player) {
{/* Your logic here */}
}

{/* Helper methods can go here */}
}
```

The `apply` function is where the actual logic of the plugin lives. By tapping the hooks exposed via `player.hooks` you gain access to the internal pipeline of components that comprise Player and can inject your functionality into their exposed hooks. For example if you want to do something any time Player's state changes you could do the following:

```javascript
Expand All @@ -36,12 +52,14 @@ apply(player: Player) {

It is not uncommon for core plugins to have constructors for cases where the plugin needs to take some configuration. In cases where plugin configs are more complicated than basic feature flags, it is recommended to make an interface to represent the config object. As an added benefit it also makes it easier to down stream consumers to use your plugin.

For a more comprehensive guide on plugins, check out this [Plugin Implementation](./plugin-implementation) example.

_Note: For the React Player you can import and load the plugin the same way you would a React Player Plugin but for the iOS and Android Players you will need to wrap the javascript bundle in a iOS/Android plugin to ensure it is available on your platform._

</core>
<react>

React Player Plugins are very similar to core plugins in both their composition and use. The `@player-ui/react` package exposes an interface `ReactPlayerPlugin` that, much like the `PlayerPlugin` interface provides the necessary attributes that are required for a React Player plugin. Again a `name` attribute is required and a function `applyReact` is required that takes a `ReactPlayer` instance. Similarly to core plugins in the `applyReact` function you have access to the React Player object and access to the three exposed hooks:
React Player Plugins are very similar to core plugins in both their composition and use. The `@player-ui/react` package exposes an interface `ReactPlayerPlugin` that, much like the `PlayerPlugin` interface provides the necessary attributes that are required for a React Player plugin. Again a dash-cased `name` attribute should be used by convention, and a function `applyReact` is required that takes a `ReactPlayer` instance. Similarly to core plugins in the `applyReact` function you have access to the React Player object and access to the three exposed hooks:

- The `webComponent` hook allows you to modify a React component that is stored in the React Player for use when it renders content. This happens during the initialization phase and ise useful if you want to wrap components in various content providers.
- The `playerComponent` hook allows you to modify a component or execute functionality when the React Player is rendering a component after the view has been reconciled in Player. This is useful if you want to inject additional props to components or collect data on which component was rendered.
Expand Down Expand Up @@ -78,6 +96,9 @@ export class FunctionPlugin implements ReactPlayerPlugin {
);
});
}

{/* Helper methods can go here */}
}
```

Lastly React plugins can also act as a core plugin in cases where core functionality needs to be extended for the React plugin to work. Since both the `PlayerPlugin` and `ReactPlayerPlugin` are typescript interfaces a plugin can implement both and be considered a valid plugin.
Expand Down