@player-ui/react 0.0.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +543 -0
- package/dist/index.d.ts +330 -0
- package/dist/index.esm.js +526 -0
- package/package.json +25 -0
- package/src/app.tsx +23 -0
- package/src/hooks.tsx +47 -0
- package/src/index.tsx +6 -0
- package/src/manager/managed-player.tsx +319 -0
- package/src/manager/request-time.tsx +63 -0
- package/src/manager/types.ts +162 -0
- package/src/player.tsx +245 -0
- package/src/plugins/onupdate-plugin.ts +47 -0
- package/src/plugins/tapstate-plugin.ts +20 -0
- package/src/utils/desc.d.ts +2 -0
package/src/player.tsx
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/* eslint-disable react/no-this-in-sfc */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type {
|
|
4
|
+
CompletedState,
|
|
5
|
+
PlayerPlugin,
|
|
6
|
+
Flow,
|
|
7
|
+
View,
|
|
8
|
+
} from '@player-ui/player';
|
|
9
|
+
import { Player } from '@player-ui/player';
|
|
10
|
+
import type { AssetRegistryType } from '@player-ui/react-asset';
|
|
11
|
+
import { AssetContext } from '@player-ui/react-asset';
|
|
12
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
13
|
+
import { PlayerContext } from '@player-ui/react-utils';
|
|
14
|
+
import { SyncWaterfallHook, AsyncParallelHook } from 'tapable';
|
|
15
|
+
import { Subscribe, useSubscribedState } from '@player-ui/react-subscribe';
|
|
16
|
+
import { Registry } from '@player-ui/partial-match-registry';
|
|
17
|
+
|
|
18
|
+
import type { WebPlayerProps } from './app';
|
|
19
|
+
import PlayerComp from './app';
|
|
20
|
+
import OnUpdatePlugin from './plugins/onupdate-plugin';
|
|
21
|
+
|
|
22
|
+
const WEB_PLAYER_VERSION = '!!STABLE_VERSION!!';
|
|
23
|
+
const COMMIT = '!!STABLE_GIT_COMMIT!!';
|
|
24
|
+
|
|
25
|
+
export interface DevtoolsGlobals {
|
|
26
|
+
/** A global for a plugin to load to Player for devtools */
|
|
27
|
+
__PLAYER_DEVTOOLS_PLUGIN?: {
|
|
28
|
+
new (): WebPlayerPlugin;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type DevtoolsWindow = typeof window & DevtoolsGlobals;
|
|
33
|
+
|
|
34
|
+
const _window: DevtoolsWindow | undefined =
|
|
35
|
+
typeof window === 'undefined' ? undefined : window;
|
|
36
|
+
|
|
37
|
+
export interface WebPlayerInfo {
|
|
38
|
+
/** Version of the running player */
|
|
39
|
+
playerVersion: string;
|
|
40
|
+
|
|
41
|
+
/** Version of the running webplayer */
|
|
42
|
+
webplayerVersion: string;
|
|
43
|
+
|
|
44
|
+
/** Hash of the HEAD commit used to build the current player version */
|
|
45
|
+
playerCommit: string;
|
|
46
|
+
|
|
47
|
+
/** Hash of the HEAD commit used to build the current webplayer version */
|
|
48
|
+
webplayerCommit: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WebPlayerPlugin extends Partial<PlayerPlugin> {
|
|
52
|
+
/** The name of this plugin */
|
|
53
|
+
name: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Attach listeners to the web-player instance
|
|
57
|
+
*/
|
|
58
|
+
applyWeb?: (webPlayer: WebPlayer) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WebPlayerOptions {
|
|
62
|
+
/** A headless player instance to use */
|
|
63
|
+
player?: Player;
|
|
64
|
+
|
|
65
|
+
/** A set of plugins to apply to this player */
|
|
66
|
+
plugins?: Array<WebPlayerPlugin>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* If the underlying webPlayer.Component should use `React.Suspense` to trigger a loading state while waiting for content or content updates.
|
|
70
|
+
* It requires that a `React.Suspense` component handler be somewhere in the `webPlayer.Component` hierarchy.
|
|
71
|
+
*/
|
|
72
|
+
suspend?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type WebPlayerComponentProps = Record<string, unknown>;
|
|
76
|
+
|
|
77
|
+
/** The React webplayer */
|
|
78
|
+
export class WebPlayer {
|
|
79
|
+
public readonly options: WebPlayerOptions;
|
|
80
|
+
public readonly player: Player;
|
|
81
|
+
public readonly assetRegistry: AssetRegistryType = new Registry();
|
|
82
|
+
public readonly Component: React.ComponentType<WebPlayerComponentProps>;
|
|
83
|
+
public readonly hooks = {
|
|
84
|
+
/**
|
|
85
|
+
* A hook to create a React Component to be used for Player, regardless of the current flow state
|
|
86
|
+
*/
|
|
87
|
+
webComponent: new SyncWaterfallHook<React.ComponentType>(['webComponent']),
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A hook to create a React Component that's used to render a specific view.
|
|
91
|
+
* It will be called for each view update from the core player.
|
|
92
|
+
* Typically this will just be `Asset`
|
|
93
|
+
*/
|
|
94
|
+
playerComponent: new SyncWaterfallHook<React.ComponentType<WebPlayerProps>>(
|
|
95
|
+
['playerComponent']
|
|
96
|
+
),
|
|
97
|
+
/**
|
|
98
|
+
* A hook to execute async tasks before the view resets to undefined
|
|
99
|
+
*/
|
|
100
|
+
onBeforeViewReset: new AsyncParallelHook(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
private viewUpdateSubscription = new Subscribe<View>();
|
|
104
|
+
private webplayerInfo: WebPlayerInfo;
|
|
105
|
+
|
|
106
|
+
constructor(options?: WebPlayerOptions) {
|
|
107
|
+
this.options = options ?? {};
|
|
108
|
+
|
|
109
|
+
// Default the suspend option to `true` unless explicitly unset
|
|
110
|
+
// Remove the suspend option in the next major
|
|
111
|
+
if (!('suspend' in this.options)) {
|
|
112
|
+
this.options.suspend = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const Devtools = _window?.__PLAYER_DEVTOOLS_PLUGIN;
|
|
116
|
+
const onUpdatePlugin = new OnUpdatePlugin(
|
|
117
|
+
this.viewUpdateSubscription.publish
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const plugins = options?.plugins ?? [];
|
|
121
|
+
|
|
122
|
+
if (Devtools) {
|
|
123
|
+
plugins.push(new Devtools());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const playerPlugins = plugins.filter((p) =>
|
|
127
|
+
Boolean(p.apply)
|
|
128
|
+
) as PlayerPlugin[];
|
|
129
|
+
|
|
130
|
+
this.player = options?.player ?? new Player({ plugins: playerPlugins });
|
|
131
|
+
|
|
132
|
+
plugins.forEach((plugin) => {
|
|
133
|
+
if (plugin.applyWeb) {
|
|
134
|
+
plugin.applyWeb(this);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
onUpdatePlugin.apply(this.player);
|
|
139
|
+
|
|
140
|
+
this.Component = this.hooks.webComponent.call(this.createReactComp());
|
|
141
|
+
this.webplayerInfo = {
|
|
142
|
+
playerVersion: this.player.getVersion(),
|
|
143
|
+
playerCommit: this.player.getCommit(),
|
|
144
|
+
webplayerVersion: WEB_PLAYER_VERSION,
|
|
145
|
+
webplayerCommit: COMMIT,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Returns the current version of the underlying core Player */
|
|
150
|
+
public getPlayerVersion(): string {
|
|
151
|
+
return this.webplayerInfo.playerVersion;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Returns the git commit used to build this core Player version */
|
|
155
|
+
public getPlayerCommit(): string {
|
|
156
|
+
return this.webplayerInfo.playerCommit;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Find instance of [Plugin] that has been registered to the web player */
|
|
160
|
+
public findPlugin<Plugin extends WebPlayerPlugin>(
|
|
161
|
+
symbol: symbol
|
|
162
|
+
): Plugin | undefined {
|
|
163
|
+
return this.options.plugins?.find((el) => el.symbol === symbol) as Plugin;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Register and apply [Plugin] if one with the same symbol is not already registered. */
|
|
167
|
+
public registerPlugin(plugin: WebPlayerPlugin): void {
|
|
168
|
+
if (!plugin.applyWeb) return;
|
|
169
|
+
|
|
170
|
+
plugin.applyWeb(this);
|
|
171
|
+
this.options.plugins?.push(plugin);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Returns the current version of the running React Player */
|
|
175
|
+
public getWebPlayerVersion(): string {
|
|
176
|
+
return this.webplayerInfo.webplayerVersion;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Returns the git commit used to build the React Player version */
|
|
180
|
+
public getWebPlayerCommit(): string {
|
|
181
|
+
return this.webplayerInfo.webplayerCommit;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private createReactComp(): React.ComponentType<WebPlayerComponentProps> {
|
|
185
|
+
const ActualPlayerComp = this.hooks.playerComponent.call(PlayerComp);
|
|
186
|
+
|
|
187
|
+
/** the component to use to render Player */
|
|
188
|
+
const WebPlayerComponent = () => {
|
|
189
|
+
const view = useSubscribedState<View>(this.viewUpdateSubscription);
|
|
190
|
+
|
|
191
|
+
if (this.options.suspend) {
|
|
192
|
+
this.viewUpdateSubscription.suspend();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<ErrorBoundary
|
|
197
|
+
fallbackRender={() => null}
|
|
198
|
+
onError={(err) => {
|
|
199
|
+
const playerState = this.player.getState();
|
|
200
|
+
|
|
201
|
+
if (playerState.status === 'in-progress') {
|
|
202
|
+
playerState.fail(err);
|
|
203
|
+
}
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
<PlayerContext.Provider value={{ player: this.player }}>
|
|
207
|
+
<AssetContext.Provider
|
|
208
|
+
value={{
|
|
209
|
+
registry: this.assetRegistry,
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{view && <ActualPlayerComp view={view} />}
|
|
213
|
+
</AssetContext.Provider>
|
|
214
|
+
</PlayerContext.Provider>
|
|
215
|
+
</ErrorBoundary>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return WebPlayerComponent;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Call this method to force the WebPlayer to wait for the next view-update before performing the next render.
|
|
224
|
+
* If the `suspense` option is set, this will suspend while an update is pending, otherwise nothing will be rendered.
|
|
225
|
+
*/
|
|
226
|
+
public setWaitForNextViewUpdate() {
|
|
227
|
+
// If the `suspend` option isn't set, then we need to reset immediately otherwise we risk flashing the old view while the new one is processing
|
|
228
|
+
const shouldCallResetHook =
|
|
229
|
+
this.options.suspend && this.hooks.onBeforeViewReset.isUsed();
|
|
230
|
+
|
|
231
|
+
return this.viewUpdateSubscription.reset(
|
|
232
|
+
shouldCallResetHook ? this.hooks.onBeforeViewReset.promise() : undefined
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public start(flow: Flow): Promise<CompletedState> {
|
|
237
|
+
this.setWaitForNextViewUpdate();
|
|
238
|
+
|
|
239
|
+
return this.player.start(flow).finally(async () => {
|
|
240
|
+
if (this.options?.suspend) {
|
|
241
|
+
await this.setWaitForNextViewUpdate();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Player, PlayerPlugin, ViewInstance } from '@player-ui/player';
|
|
2
|
+
|
|
3
|
+
export type OnUpdateCallback = (update: any) => void;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A plugin that listens for view updates and publishes an event for when a view is updated
|
|
7
|
+
*/
|
|
8
|
+
export default class OnUpdatePlugin implements PlayerPlugin {
|
|
9
|
+
name = 'view-update';
|
|
10
|
+
|
|
11
|
+
private readonly onUpdateCallback: OnUpdateCallback;
|
|
12
|
+
|
|
13
|
+
constructor(onUpdate: OnUpdateCallback) {
|
|
14
|
+
this.onUpdateCallback = onUpdate;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply(player: Player) {
|
|
18
|
+
/** Trigger the callback for the view update */
|
|
19
|
+
const updateTap = (updatedView: any) => {
|
|
20
|
+
this.onUpdateCallback(updatedView);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Trigger the callback for the view creation */
|
|
24
|
+
const viewTap = (view: ViewInstance) => {
|
|
25
|
+
view.hooks.onUpdate.tap(this.name, updateTap);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Attach hooks for any new vc that gets created
|
|
29
|
+
player.hooks.view.tap(this.name, viewTap);
|
|
30
|
+
|
|
31
|
+
// Attach listeners and publish an update event for a view already in progress
|
|
32
|
+
const currentPlayerState = player.getState();
|
|
33
|
+
|
|
34
|
+
if (currentPlayerState.status === 'in-progress') {
|
|
35
|
+
const { currentView } = currentPlayerState.controllers.view;
|
|
36
|
+
|
|
37
|
+
if (currentView) {
|
|
38
|
+
viewTap(currentView);
|
|
39
|
+
const { lastUpdate } = currentView;
|
|
40
|
+
|
|
41
|
+
if (lastUpdate) {
|
|
42
|
+
this.onUpdateCallback(lastUpdate);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PlayerFlowState, Player } from '@player-ui/player';
|
|
2
|
+
import type { WebPlayerPlugin } from '../player';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A plugin to tap into state transition changes and call an arbitrary update function
|
|
6
|
+
*/
|
|
7
|
+
export class StateTapPlugin implements WebPlayerPlugin {
|
|
8
|
+
name = 'statetap';
|
|
9
|
+
private callbackFunction: (state: PlayerFlowState) => void;
|
|
10
|
+
|
|
11
|
+
constructor(callback: (state: PlayerFlowState) => void) {
|
|
12
|
+
this.callbackFunction = callback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
apply(player: Player) {
|
|
16
|
+
player.hooks.state.tap('usePlayer', (newPlayerState: PlayerFlowState) => {
|
|
17
|
+
this.callbackFunction(newPlayerState);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|