@player-ui/storybook 0.0.1-next.10
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 +659 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.esm.js +643 -0
- package/package.json +76 -0
- package/register.js +1 -0
- package/src/addons/appetize/index.tsx +72 -0
- package/src/addons/constants.ts +10 -0
- package/src/addons/editor/index.tsx +85 -0
- package/src/addons/events/events.css +7 -0
- package/src/addons/events/index.tsx +100 -0
- package/src/addons/index.tsx +46 -0
- package/src/addons/refresh/index.tsx +28 -0
- package/src/decorator/index.tsx +46 -0
- package/src/index.ts +3 -0
- package/src/player/Appetize.tsx +121 -0
- package/src/player/PlayerFlowSummary.tsx +36 -0
- package/src/player/PlayerStory.tsx +198 -0
- package/src/player/hooks.ts +29 -0
- package/src/player/index.ts +1 -0
- package/src/player/storybookWebPlayerPlugin.ts +113 -0
- package/src/state/events.ts +62 -0
- package/src/state/hooks.ts +152 -0
- package/src/state/index.ts +2 -0
- package/src/types.ts +41 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DecoratorFn } from '@storybook/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { WebPlayerPlugin, Flow } from '@player-ui/react';
|
|
4
|
+
|
|
5
|
+
/** register all the storybook addons */
|
|
6
|
+
declare function register(): void;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A story decorator for rendering player content
|
|
10
|
+
*/
|
|
11
|
+
declare const PlayerDecorator: DecoratorFn;
|
|
12
|
+
|
|
13
|
+
interface AppetizeVersions {
|
|
14
|
+
/** The iOS version to load */
|
|
15
|
+
ios: string;
|
|
16
|
+
/** The Android version to load */
|
|
17
|
+
android: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare type AsyncImportFactory<T> = () => Promise<{
|
|
21
|
+
/** default export of the module */
|
|
22
|
+
default: T;
|
|
23
|
+
}>;
|
|
24
|
+
declare type RenderTarget = {
|
|
25
|
+
/** platform to render on */
|
|
26
|
+
platform: 'ios' | 'android' | 'web';
|
|
27
|
+
/** the token to use if applicable */
|
|
28
|
+
token?: string;
|
|
29
|
+
/** A different base URL for appetize if necessary */
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
/** The OS Versions to use for appetize */
|
|
32
|
+
appetizeVersions?: AppetizeVersions;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
declare const WebPlayerPluginContext: React.Context<{
|
|
36
|
+
/** Plugins to use for the player */
|
|
37
|
+
plugins?: WebPlayerPlugin[] | undefined;
|
|
38
|
+
}>;
|
|
39
|
+
declare const PlayerRenderContext: React.Context<RenderTarget>;
|
|
40
|
+
declare const StorybookControlsContext: React.Context<{
|
|
41
|
+
/** any storybook controls to include */
|
|
42
|
+
controls?: Flow['data'];
|
|
43
|
+
}>;
|
|
44
|
+
declare type Mock = Record<string, unknown>;
|
|
45
|
+
declare type MockFactory = () => Mock;
|
|
46
|
+
declare type MockFactoryOrPromise = MockFactory | Mock;
|
|
47
|
+
interface PlayerStoryProps {
|
|
48
|
+
/** The mock to load */
|
|
49
|
+
flow: AsyncImportFactory<MockFactoryOrPromise> | MockFactoryOrPromise;
|
|
50
|
+
/** Custom data to use in the flow */
|
|
51
|
+
data?: Flow['data'];
|
|
52
|
+
/** props from storybook controls */
|
|
53
|
+
storybookControls?: Flow['data'];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Takes an initial flow and renders it as a story in storybook.
|
|
57
|
+
* This handles all of the wiring of the mock into the flow editor, events, etc
|
|
58
|
+
*/
|
|
59
|
+
declare const PlayerStory: (props: PlayerStoryProps) => JSX.Element;
|
|
60
|
+
|
|
61
|
+
export { PlayerDecorator, PlayerRenderContext, PlayerStory, PlayerStoryProps, StorybookControlsContext, WebPlayerPluginContext, register };
|
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import addons, { types } from '@storybook/addons';
|
|
3
|
+
import { useDarkMode } from 'storybook-dark-mode';
|
|
4
|
+
import { dequal } from 'dequal';
|
|
5
|
+
import Editor from '@monaco-editor/react';
|
|
6
|
+
import { v4 } from 'uuid';
|
|
7
|
+
import { Table, Head, Row, HeadCell, Body, Cell } from '@devtools-ds/table';
|
|
8
|
+
import makeClass from 'clsx';
|
|
9
|
+
import { Separator, IconButton, Icons, WithTooltip, TooltipLinkList } from '@storybook/components';
|
|
10
|
+
import { useParameter } from '@storybook/api';
|
|
11
|
+
import { STORY_CHANGED } from '@storybook/core-events';
|
|
12
|
+
import { WebPlayer } from '@player-ui/react';
|
|
13
|
+
import { VStack, Heading, Code, Text, Button, ChakraProvider, Spinner } from '@chakra-ui/react';
|
|
14
|
+
import { makeFlow } from '@player-ui/make-flow';
|
|
15
|
+
import { DocsContext } from '@storybook/addon-docs';
|
|
16
|
+
import { compressToEncodedURIComponent } from 'lz-string';
|
|
17
|
+
import { MetricsPlugin } from '@player-ui/metrics-plugin-react';
|
|
18
|
+
|
|
19
|
+
const ADDON_ID = "web-player/addon";
|
|
20
|
+
const FLOW_PANEL_ID = `${ADDON_ID}/flow-editor-panel`;
|
|
21
|
+
const EVENT_PANEL_ID = `${ADDON_ID}/events-panel`;
|
|
22
|
+
const FLOW_REFRESH_TOOL_ID = `${ADDON_ID}/flow-refresh-tool`;
|
|
23
|
+
const RENDER_SELECT_TOOL_ID = `${ADDON_ID}/render-select-tool`;
|
|
24
|
+
|
|
25
|
+
var __defProp$2 = Object.defineProperty;
|
|
26
|
+
var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols;
|
|
27
|
+
var __hasOwnProp$2 = Object.prototype.hasOwnProperty;
|
|
28
|
+
var __propIsEnum$2 = Object.prototype.propertyIsEnumerable;
|
|
29
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
30
|
+
var __spreadValues$2 = (a, b) => {
|
|
31
|
+
for (var prop in b || (b = {}))
|
|
32
|
+
if (__hasOwnProp$2.call(b, prop))
|
|
33
|
+
__defNormalProp$2(a, prop, b[prop]);
|
|
34
|
+
if (__getOwnPropSymbols$2)
|
|
35
|
+
for (var prop of __getOwnPropSymbols$2(b)) {
|
|
36
|
+
if (__propIsEnum$2.call(b, prop))
|
|
37
|
+
__defNormalProp$2(a, prop, b[prop]);
|
|
38
|
+
}
|
|
39
|
+
return a;
|
|
40
|
+
};
|
|
41
|
+
function createEvent(base) {
|
|
42
|
+
return __spreadValues$2({
|
|
43
|
+
id: v4(),
|
|
44
|
+
time: Date.now()
|
|
45
|
+
}, base);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function subscribe(chan, eventName, callback) {
|
|
49
|
+
const handler = (payload) => {
|
|
50
|
+
callback(JSON.parse(payload));
|
|
51
|
+
};
|
|
52
|
+
chan.addListener(eventName, handler);
|
|
53
|
+
return () => {
|
|
54
|
+
chan.removeListener(eventName, handler);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function publish(chan, event) {
|
|
58
|
+
chan.emit(event.type, JSON.stringify(event));
|
|
59
|
+
}
|
|
60
|
+
function useStateActions(chan) {
|
|
61
|
+
return React.useMemo(() => ({
|
|
62
|
+
addEvents: (events) => {
|
|
63
|
+
publish(chan, {
|
|
64
|
+
type: "@@player/event/add",
|
|
65
|
+
events
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
setMetrics: (metrics) => {
|
|
69
|
+
publish(chan, {
|
|
70
|
+
type: "@@player/metrics/set",
|
|
71
|
+
metrics
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
clearEvents: () => {
|
|
75
|
+
publish(chan, {
|
|
76
|
+
type: "@@player/event/clear"
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
setFlow: (flow) => {
|
|
80
|
+
publish(chan, {
|
|
81
|
+
type: "@@player/flow/set",
|
|
82
|
+
flow
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
resetFlow: () => {
|
|
86
|
+
publish(chan, { type: "@@player/flow/reset" });
|
|
87
|
+
},
|
|
88
|
+
setPlatform: (platform) => {
|
|
89
|
+
publish(chan, { type: "@@player/platform/set", platform });
|
|
90
|
+
}
|
|
91
|
+
}), [chan]);
|
|
92
|
+
}
|
|
93
|
+
function useEventState(chan) {
|
|
94
|
+
const [events, setEvents] = React.useState([]);
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
const unsubAdd = subscribe(chan, "@@player/event/add", (evt) => setEvents((old) => [...old, ...evt.events]));
|
|
97
|
+
const unsubClear = subscribe(chan, "@@player/event/clear", () => {
|
|
98
|
+
setEvents([]);
|
|
99
|
+
});
|
|
100
|
+
return () => {
|
|
101
|
+
unsubAdd();
|
|
102
|
+
unsubClear();
|
|
103
|
+
};
|
|
104
|
+
}, [chan]);
|
|
105
|
+
return events;
|
|
106
|
+
}
|
|
107
|
+
function useFlowState(chan) {
|
|
108
|
+
const [flow, setFlow] = React.useState();
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
return subscribe(chan, "@@player/flow/set", (evt) => {
|
|
111
|
+
setFlow(evt.flow);
|
|
112
|
+
});
|
|
113
|
+
}, [chan]);
|
|
114
|
+
return flow;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const EditorPanel = (props) => {
|
|
118
|
+
const { active } = props;
|
|
119
|
+
const darkMode = useDarkMode();
|
|
120
|
+
const flow = useFlowState(props.api.getChannel());
|
|
121
|
+
const actions = useStateActions(props.api.getChannel());
|
|
122
|
+
const [editorValue, setEditorValue] = React.useState(flow ? JSON.stringify(flow, null, 2) : "{}");
|
|
123
|
+
const updateTimerRef = React.useRef(void 0);
|
|
124
|
+
function clearPending() {
|
|
125
|
+
if (updateTimerRef.current) {
|
|
126
|
+
clearTimeout(updateTimerRef.current);
|
|
127
|
+
updateTimerRef.current = void 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
if (!active) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
if (editorValue) {
|
|
136
|
+
const parsed = JSON.parse(editorValue);
|
|
137
|
+
if (dequal(flow, parsed)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {
|
|
142
|
+
}
|
|
143
|
+
setEditorValue(JSON.stringify(flow, null, 2));
|
|
144
|
+
}, [flow, active]);
|
|
145
|
+
if (!active) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const onChange = (val) => {
|
|
149
|
+
clearPending();
|
|
150
|
+
setEditorValue(val != null ? val : "");
|
|
151
|
+
try {
|
|
152
|
+
if (val) {
|
|
153
|
+
const parsed = JSON.parse(val);
|
|
154
|
+
if (!dequal(parsed, flow)) {
|
|
155
|
+
updateTimerRef.current = setTimeout(() => {
|
|
156
|
+
if (active) {
|
|
157
|
+
actions.setFlow(parsed);
|
|
158
|
+
}
|
|
159
|
+
}, 1e3);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Editor, {
|
|
166
|
+
theme: darkMode ? "vs-dark" : "light",
|
|
167
|
+
value: editorValue,
|
|
168
|
+
language: "json",
|
|
169
|
+
onChange
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
|
|
174
|
+
|
|
175
|
+
var css = "\n.events_header__1bcc1085 td {\n width: 200px;\n}\n\n.events_body__1bcc1085 td {\n}\n";
|
|
176
|
+
var modules_2563542d = {"header":"events_header__1bcc1085","body":"events_body__1bcc1085"};
|
|
177
|
+
n(css,{});
|
|
178
|
+
|
|
179
|
+
var __defProp$1 = Object.defineProperty;
|
|
180
|
+
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
|
|
181
|
+
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
|
|
182
|
+
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
|
|
183
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
184
|
+
var __spreadValues$1 = (a, b) => {
|
|
185
|
+
for (var prop in b || (b = {}))
|
|
186
|
+
if (__hasOwnProp$1.call(b, prop))
|
|
187
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
188
|
+
if (__getOwnPropSymbols$1)
|
|
189
|
+
for (var prop of __getOwnPropSymbols$1(b)) {
|
|
190
|
+
if (__propIsEnum$1.call(b, prop))
|
|
191
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
192
|
+
}
|
|
193
|
+
return a;
|
|
194
|
+
};
|
|
195
|
+
const ExtraCells = (event) => {
|
|
196
|
+
var _a, _b;
|
|
197
|
+
if (event.type === "log") {
|
|
198
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("td", null, event.severity), /* @__PURE__ */ React.createElement("td", null, event.message.map((a) => JSON.stringify(a)).join(" ")));
|
|
199
|
+
}
|
|
200
|
+
if (event.type === "dataChange") {
|
|
201
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("td", null, event.binding), /* @__PURE__ */ React.createElement("td", null, `${JSON.stringify(event.from)} \u279C ${JSON.stringify(event.to)}`));
|
|
202
|
+
}
|
|
203
|
+
if (event.type === "stateChange") {
|
|
204
|
+
let name = event.state;
|
|
205
|
+
if (event.state === "completed") {
|
|
206
|
+
name = `${name} (${event.error ? "error" : "success"})`;
|
|
207
|
+
}
|
|
208
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("td", null, name), /* @__PURE__ */ React.createElement("td", null, (_b = (_a = event.outcome) != null ? _a : event.error) != null ? _b : ""));
|
|
209
|
+
}
|
|
210
|
+
if (event.type === "metric") {
|
|
211
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("td", null, event.metricType), /* @__PURE__ */ React.createElement("td", null, event.message));
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
};
|
|
215
|
+
const EventsPanel = (props) => {
|
|
216
|
+
const events = useEventState(props.api.getChannel());
|
|
217
|
+
const darkMode = useDarkMode();
|
|
218
|
+
if (!props.active) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
222
|
+
className: makeClass(modules_2563542d.wrapper, {
|
|
223
|
+
[modules_2563542d.dark]: darkMode
|
|
224
|
+
})
|
|
225
|
+
}, /* @__PURE__ */ React.createElement(Table, {
|
|
226
|
+
colorScheme: darkMode ? "dark" : "light"
|
|
227
|
+
}, /* @__PURE__ */ React.createElement(Head, {
|
|
228
|
+
className: modules_2563542d.header
|
|
229
|
+
}, /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(HeadCell, null, "Time"), /* @__PURE__ */ React.createElement(HeadCell, null, "Type"), /* @__PURE__ */ React.createElement(HeadCell, null), /* @__PURE__ */ React.createElement(HeadCell, null))), /* @__PURE__ */ React.createElement(Body, {
|
|
230
|
+
className: modules_2563542d.body
|
|
231
|
+
}, events.map((evt) => /* @__PURE__ */ React.createElement(Row, {
|
|
232
|
+
key: evt.id
|
|
233
|
+
}, /* @__PURE__ */ React.createElement(Cell, null, evt.time), /* @__PURE__ */ React.createElement(Cell, null, evt.type), /* @__PURE__ */ React.createElement(ExtraCells, __spreadValues$1({}, evt)))))));
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const FlowRefresh = ({ api }) => {
|
|
237
|
+
const actions = useStateActions(api.getChannel());
|
|
238
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Separator, null), /* @__PURE__ */ React.createElement(IconButton, {
|
|
239
|
+
title: "Reset the current flow",
|
|
240
|
+
onClick: () => {
|
|
241
|
+
actions.resetFlow();
|
|
242
|
+
}
|
|
243
|
+
}, /* @__PURE__ */ React.createElement(Icons, {
|
|
244
|
+
icon: "sync"
|
|
245
|
+
})));
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const RenderSelection = ({ api }) => {
|
|
249
|
+
const params = useParameter("appetizeTokens", {});
|
|
250
|
+
const actions = useStateActions(api.getChannel());
|
|
251
|
+
const [selectedPlatform, setPlatform] = React.useState("web");
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
const listener = () => {
|
|
254
|
+
setPlatform("web");
|
|
255
|
+
};
|
|
256
|
+
api.getChannel().addListener(STORY_CHANGED, listener);
|
|
257
|
+
return () => {
|
|
258
|
+
api.getChannel().removeListener(STORY_CHANGED, listener);
|
|
259
|
+
};
|
|
260
|
+
}, [api]);
|
|
261
|
+
const mobilePlatforms = Object.keys(params);
|
|
262
|
+
if (mobilePlatforms.length === 0) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
return /* @__PURE__ */ React.createElement(WithTooltip, {
|
|
266
|
+
closeOnClick: true,
|
|
267
|
+
placement: "top",
|
|
268
|
+
trigger: "click",
|
|
269
|
+
tooltip: ({ onHide }) => /* @__PURE__ */ React.createElement(TooltipLinkList, {
|
|
270
|
+
links: ["web", ...mobilePlatforms].map((platform) => ({
|
|
271
|
+
id: platform,
|
|
272
|
+
title: platform,
|
|
273
|
+
onClick: () => {
|
|
274
|
+
setPlatform(platform);
|
|
275
|
+
actions.setPlatform(platform);
|
|
276
|
+
onHide();
|
|
277
|
+
},
|
|
278
|
+
value: platform,
|
|
279
|
+
active: platform === selectedPlatform
|
|
280
|
+
}))
|
|
281
|
+
})
|
|
282
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
283
|
+
title: "Change the render target"
|
|
284
|
+
}, /* @__PURE__ */ React.createElement(Icons, {
|
|
285
|
+
icon: selectedPlatform === "web" ? "browser" : "mobile"
|
|
286
|
+
})));
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
function register() {
|
|
290
|
+
addons.register(ADDON_ID, (api) => {
|
|
291
|
+
addons.addPanel(EVENT_PANEL_ID, {
|
|
292
|
+
title: "Events",
|
|
293
|
+
render: ({ active, key }) => /* @__PURE__ */ React.createElement(EventsPanel, {
|
|
294
|
+
key,
|
|
295
|
+
api,
|
|
296
|
+
active: Boolean(active)
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
addons.addPanel(FLOW_PANEL_ID, {
|
|
300
|
+
title: "Flow",
|
|
301
|
+
render: ({ active, key }) => /* @__PURE__ */ React.createElement(EditorPanel, {
|
|
302
|
+
key,
|
|
303
|
+
api,
|
|
304
|
+
active: Boolean(active)
|
|
305
|
+
})
|
|
306
|
+
});
|
|
307
|
+
addons.add(FLOW_REFRESH_TOOL_ID, {
|
|
308
|
+
title: "Refresh Flow",
|
|
309
|
+
type: types.TOOL,
|
|
310
|
+
render: () => /* @__PURE__ */ React.createElement(FlowRefresh, {
|
|
311
|
+
api
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
addons.add(RENDER_SELECT_TOOL_ID, {
|
|
315
|
+
title: "Render Selection",
|
|
316
|
+
type: types.TOOL,
|
|
317
|
+
render: () => /* @__PURE__ */ React.createElement(RenderSelection, {
|
|
318
|
+
api
|
|
319
|
+
})
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function useEditorFlow(initialFlow) {
|
|
325
|
+
const stateActions = useStateActions(addons.getChannel());
|
|
326
|
+
const flow = useFlowState(addons.getChannel());
|
|
327
|
+
const docsContext = React.useContext(DocsContext);
|
|
328
|
+
React.useEffect(() => {
|
|
329
|
+
stateActions.setFlow(initialFlow);
|
|
330
|
+
}, [initialFlow]);
|
|
331
|
+
React.useEffect(() => {
|
|
332
|
+
if (!flow) {
|
|
333
|
+
stateActions.setFlow(initialFlow);
|
|
334
|
+
}
|
|
335
|
+
}, [flow]);
|
|
336
|
+
if (docsContext.id) {
|
|
337
|
+
return initialFlow;
|
|
338
|
+
}
|
|
339
|
+
return flow != null ? flow : initialFlow;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const DEVICE_HEIGHT = {
|
|
343
|
+
iphonexs: 845
|
|
344
|
+
};
|
|
345
|
+
const toAppetizeUrl = (baseUrl, key, params) => `https://${baseUrl}/embed/${key}?${Object.entries(params).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&")}`;
|
|
346
|
+
const Appetize = (props) => {
|
|
347
|
+
const device = "iphonexs";
|
|
348
|
+
const height = DEVICE_HEIGHT[device];
|
|
349
|
+
const defaultVersions = {
|
|
350
|
+
ios: "13.7",
|
|
351
|
+
android: "8.1"
|
|
352
|
+
};
|
|
353
|
+
const {
|
|
354
|
+
baseUrl = "appetize.io",
|
|
355
|
+
token,
|
|
356
|
+
flow,
|
|
357
|
+
osVersions = defaultVersions,
|
|
358
|
+
platform
|
|
359
|
+
} = props;
|
|
360
|
+
const osVersion = osVersions[platform];
|
|
361
|
+
return /* @__PURE__ */ React.createElement("iframe", {
|
|
362
|
+
title: "native app",
|
|
363
|
+
style: { height: `${height}px`, border: "none", width: "100%" },
|
|
364
|
+
src: toAppetizeUrl(baseUrl, token, {
|
|
365
|
+
autoplay: true,
|
|
366
|
+
device: "iphonexs",
|
|
367
|
+
deviceColor: "black",
|
|
368
|
+
scale: 100,
|
|
369
|
+
osVersion,
|
|
370
|
+
params: encodeURIComponent(JSON.stringify({
|
|
371
|
+
json: compressToEncodedURIComponent(JSON.stringify(flow))
|
|
372
|
+
}))
|
|
373
|
+
})
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
class StorybookPlayerPlugin {
|
|
378
|
+
constructor(actions) {
|
|
379
|
+
this.name = "Storybook";
|
|
380
|
+
this.actions = actions;
|
|
381
|
+
this.metricsPlugin = new MetricsPlugin({
|
|
382
|
+
onUpdate: (metrics) => {
|
|
383
|
+
actions.setMetrics(metrics);
|
|
384
|
+
},
|
|
385
|
+
onRenderEnd: (timing) => {
|
|
386
|
+
this.onMetricChange(timing, "render");
|
|
387
|
+
},
|
|
388
|
+
onUpdateEnd: (timing) => {
|
|
389
|
+
this.onMetricChange(timing, "update");
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
apply(player) {
|
|
394
|
+
player.registerPlugin(this.metricsPlugin);
|
|
395
|
+
}
|
|
396
|
+
applyWeb(wp) {
|
|
397
|
+
wp.registerPlugin(this.metricsPlugin);
|
|
398
|
+
wp.player.hooks.dataController.tap(this.name, (dc) => {
|
|
399
|
+
this.actions.clearEvents();
|
|
400
|
+
dc.hooks.onUpdate.tap(this.name, (dataUpdates) => {
|
|
401
|
+
const events = dataUpdates.map((dataUpdate) => createEvent({
|
|
402
|
+
type: "dataChange",
|
|
403
|
+
binding: dataUpdate.binding.asString(),
|
|
404
|
+
from: dataUpdate.oldValue,
|
|
405
|
+
to: dataUpdate.newValue
|
|
406
|
+
}));
|
|
407
|
+
this.actions.addEvents(events);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
wp.player.logger.hooks.log.tap(this.name, (severity, data) => {
|
|
411
|
+
this.actions.addEvents([
|
|
412
|
+
createEvent({
|
|
413
|
+
type: "log",
|
|
414
|
+
message: data,
|
|
415
|
+
severity
|
|
416
|
+
})
|
|
417
|
+
]);
|
|
418
|
+
});
|
|
419
|
+
wp.player.hooks.state.tap(this.name, (newState) => {
|
|
420
|
+
if ("error" in newState) {
|
|
421
|
+
this.actions.addEvents([
|
|
422
|
+
createEvent({
|
|
423
|
+
type: "stateChange",
|
|
424
|
+
state: newState.status,
|
|
425
|
+
error: newState.error.message
|
|
426
|
+
})
|
|
427
|
+
]);
|
|
428
|
+
} else if (newState.status === "completed") {
|
|
429
|
+
this.actions.addEvents([
|
|
430
|
+
createEvent({
|
|
431
|
+
type: "stateChange",
|
|
432
|
+
state: newState.status,
|
|
433
|
+
outcome: newState.endState.outcome
|
|
434
|
+
})
|
|
435
|
+
]);
|
|
436
|
+
} else {
|
|
437
|
+
this.actions.addEvents([
|
|
438
|
+
createEvent({
|
|
439
|
+
type: "stateChange",
|
|
440
|
+
state: newState.status
|
|
441
|
+
})
|
|
442
|
+
]);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
onMetricChange(timing, metricType) {
|
|
447
|
+
if (!timing.completed) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
this.actions.addEvents([
|
|
451
|
+
createEvent({
|
|
452
|
+
type: "metric",
|
|
453
|
+
metricType,
|
|
454
|
+
message: `Duration: ${timing.duration.toFixed(0)} ms`
|
|
455
|
+
})
|
|
456
|
+
]);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const PlayerFlowSummary = (props) => {
|
|
461
|
+
var _a;
|
|
462
|
+
return /* @__PURE__ */ React.createElement(VStack, {
|
|
463
|
+
gap: "10"
|
|
464
|
+
}, /* @__PURE__ */ React.createElement(Heading, null, "Flow Completed ", props.error ? "with Error" : ""), props.outcome && /* @__PURE__ */ React.createElement(Code, null, "Outcome: ", /* @__PURE__ */ React.createElement(Text, {
|
|
465
|
+
as: "strong"
|
|
466
|
+
}, props.outcome)), props.error && /* @__PURE__ */ React.createElement(Code, {
|
|
467
|
+
colorScheme: "red"
|
|
468
|
+
}, /* @__PURE__ */ React.createElement("pre", null, (_a = props.error) == null ? void 0 : _a.message)), /* @__PURE__ */ React.createElement(Button, {
|
|
469
|
+
variant: "solid",
|
|
470
|
+
onClick: props.reset
|
|
471
|
+
}, "Reset"));
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
var __defProp = Object.defineProperty;
|
|
475
|
+
var __defProps = Object.defineProperties;
|
|
476
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
477
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
478
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
479
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
480
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
481
|
+
var __spreadValues = (a, b) => {
|
|
482
|
+
for (var prop in b || (b = {}))
|
|
483
|
+
if (__hasOwnProp.call(b, prop))
|
|
484
|
+
__defNormalProp(a, prop, b[prop]);
|
|
485
|
+
if (__getOwnPropSymbols)
|
|
486
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
487
|
+
if (__propIsEnum.call(b, prop))
|
|
488
|
+
__defNormalProp(a, prop, b[prop]);
|
|
489
|
+
}
|
|
490
|
+
return a;
|
|
491
|
+
};
|
|
492
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
493
|
+
var __objRest = (source, exclude) => {
|
|
494
|
+
var target = {};
|
|
495
|
+
for (var prop in source)
|
|
496
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
497
|
+
target[prop] = source[prop];
|
|
498
|
+
if (source != null && __getOwnPropSymbols)
|
|
499
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
500
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
501
|
+
target[prop] = source[prop];
|
|
502
|
+
}
|
|
503
|
+
return target;
|
|
504
|
+
};
|
|
505
|
+
var __async = (__this, __arguments, generator) => {
|
|
506
|
+
return new Promise((resolve, reject) => {
|
|
507
|
+
var fulfilled = (value) => {
|
|
508
|
+
try {
|
|
509
|
+
step(generator.next(value));
|
|
510
|
+
} catch (e) {
|
|
511
|
+
reject(e);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
var rejected = (value) => {
|
|
515
|
+
try {
|
|
516
|
+
step(generator.throw(value));
|
|
517
|
+
} catch (e) {
|
|
518
|
+
reject(e);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
522
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
523
|
+
});
|
|
524
|
+
};
|
|
525
|
+
const WebPlayerPluginContext = React.createContext({ plugins: [] });
|
|
526
|
+
const PlayerRenderContext = React.createContext({
|
|
527
|
+
platform: "web"
|
|
528
|
+
});
|
|
529
|
+
const StorybookControlsContext = React.createContext({
|
|
530
|
+
controls: {}
|
|
531
|
+
});
|
|
532
|
+
const LocalPlayerStory = (props) => {
|
|
533
|
+
var _a, _b;
|
|
534
|
+
let flow = useEditorFlow(props.flow);
|
|
535
|
+
const renderContext = React.useContext(PlayerRenderContext);
|
|
536
|
+
const pluginContext = React.useContext(WebPlayerPluginContext);
|
|
537
|
+
const controlsContext = React.useContext(StorybookControlsContext);
|
|
538
|
+
const stateActions = useStateActions(addons.getChannel());
|
|
539
|
+
const plugins = (_b = (_a = props.webPlugins) != null ? _a : pluginContext == null ? void 0 : pluginContext.plugins) != null ? _b : [];
|
|
540
|
+
const [playerState, setPlayerState] = React.useState("not-started");
|
|
541
|
+
const wp = React.useMemo(() => {
|
|
542
|
+
return new WebPlayer({
|
|
543
|
+
plugins: [new StorybookPlayerPlugin(stateActions), ...plugins]
|
|
544
|
+
});
|
|
545
|
+
}, [plugins]);
|
|
546
|
+
const startFlow = () => {
|
|
547
|
+
setPlayerState("in-progress");
|
|
548
|
+
wp.start(flow).then(() => {
|
|
549
|
+
setPlayerState("completed");
|
|
550
|
+
}).catch((e) => {
|
|
551
|
+
console.error("Error starting flow", e);
|
|
552
|
+
setPlayerState("error");
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
React.useEffect(() => {
|
|
556
|
+
startFlow();
|
|
557
|
+
}, [wp, flow]);
|
|
558
|
+
React.useEffect(() => {
|
|
559
|
+
var _a2;
|
|
560
|
+
if (controlsContext) {
|
|
561
|
+
flow = __spreadProps(__spreadValues({}, flow), {
|
|
562
|
+
data: __spreadValues(__spreadValues({}, (_a2 = flow.data) != null ? _a2 : {}), controlsContext)
|
|
563
|
+
});
|
|
564
|
+
stateActions.setFlow(flow);
|
|
565
|
+
}
|
|
566
|
+
}, [controlsContext]);
|
|
567
|
+
React.useEffect(() => {
|
|
568
|
+
return subscribe(addons.getChannel(), "@@player/flow/reset", () => {
|
|
569
|
+
startFlow();
|
|
570
|
+
});
|
|
571
|
+
}, [wp, flow]);
|
|
572
|
+
if (renderContext.platform !== "web" && renderContext.token) {
|
|
573
|
+
return /* @__PURE__ */ React.createElement(Appetize, {
|
|
574
|
+
flow,
|
|
575
|
+
platform: renderContext.platform,
|
|
576
|
+
token: renderContext.token,
|
|
577
|
+
baseUrl: renderContext.baseUrl,
|
|
578
|
+
osVersions: renderContext.appetizeVersions
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
const currentState = wp.player.getState();
|
|
582
|
+
if (playerState === "completed" && currentState.status === "completed") {
|
|
583
|
+
return /* @__PURE__ */ React.createElement(PlayerFlowSummary, {
|
|
584
|
+
reset: startFlow,
|
|
585
|
+
outcome: currentState.endState.outcome
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (playerState === "error" && currentState.status === "error") {
|
|
589
|
+
return /* @__PURE__ */ React.createElement(PlayerFlowSummary, {
|
|
590
|
+
reset: startFlow,
|
|
591
|
+
error: currentState.error
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
return /* @__PURE__ */ React.createElement(wp.Component, null);
|
|
595
|
+
};
|
|
596
|
+
function wrapInLazy(Component, flowFactory, other) {
|
|
597
|
+
const asPlayer = () => __async(this, null, function* () {
|
|
598
|
+
const mock = typeof flowFactory === "function" ? yield flowFactory() : flowFactory;
|
|
599
|
+
const Comp = () => {
|
|
600
|
+
const flow = __spreadValues(__spreadValues({}, makeFlow("default" in mock ? mock.default : mock)), other != null ? other : {});
|
|
601
|
+
return /* @__PURE__ */ React.createElement(Component, {
|
|
602
|
+
flow
|
|
603
|
+
});
|
|
604
|
+
};
|
|
605
|
+
return {
|
|
606
|
+
default: Comp
|
|
607
|
+
};
|
|
608
|
+
});
|
|
609
|
+
return React.lazy(asPlayer);
|
|
610
|
+
}
|
|
611
|
+
const PlayerStory = (props) => {
|
|
612
|
+
const _a = props, { flow, storybookControls } = _a, other = __objRest(_a, ["flow", "storybookControls"]);
|
|
613
|
+
const MockComp = React.useMemo(() => wrapInLazy(LocalPlayerStory, flow, other), []);
|
|
614
|
+
return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ChakraProvider, null, /* @__PURE__ */ React.createElement(React.Suspense, {
|
|
615
|
+
fallback: /* @__PURE__ */ React.createElement(Spinner, null)
|
|
616
|
+
}, /* @__PURE__ */ React.createElement(StorybookControlsContext.Provider, {
|
|
617
|
+
value: __spreadValues({}, storybookControls)
|
|
618
|
+
}, /* @__PURE__ */ React.createElement(MockComp, null)))));
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const PlayerDecorator = (story, ctx) => {
|
|
622
|
+
var _a;
|
|
623
|
+
const playerParams = ctx.parameters;
|
|
624
|
+
const [selectedPlatform, setPlatform] = React.useState("web");
|
|
625
|
+
React.useEffect(() => {
|
|
626
|
+
return subscribe(addons.getChannel(), "@@player/platform/set", (evt) => {
|
|
627
|
+
setPlatform(evt.platform);
|
|
628
|
+
});
|
|
629
|
+
}, []);
|
|
630
|
+
return /* @__PURE__ */ React.createElement(PlayerRenderContext.Provider, {
|
|
631
|
+
value: {
|
|
632
|
+
platform: selectedPlatform,
|
|
633
|
+
token: selectedPlatform === "web" ? void 0 : (_a = playerParams == null ? void 0 : playerParams.appetizeTokens) == null ? void 0 : _a[selectedPlatform],
|
|
634
|
+
baseUrl: playerParams.appetizeBaseUrl,
|
|
635
|
+
appetizeVersions: playerParams.appetizeVersions
|
|
636
|
+
}
|
|
637
|
+
}, /* @__PURE__ */ React.createElement(WebPlayerPluginContext.Provider, {
|
|
638
|
+
value: { plugins: playerParams.webplayerPlugins }
|
|
639
|
+
}, story()));
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
export { PlayerDecorator, PlayerRenderContext, PlayerStory, StorybookControlsContext, WebPlayerPluginContext, register };
|
|
643
|
+
//# sourceMappingURL=index.esm.js.map
|