@player-devtools/client 0.0.2-next.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/devtools/client/src/panel/index.tsx","../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/devtools/client/src/constants/index.ts","../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/devtools/client/src/plugins/index.ts","../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/devtools/client/src/state/index.ts","../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/devtools/client/src/state/reducer.ts","../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/devtools/client/src/helpers/flowDiff.ts"],"sourcesContent":["import React, { useRef } from \"react\";\nimport type {\n MessengerOptions,\n ExtensionSupportedEvents,\n} from \"@player-devtools/types\";\nimport { DataController, Flow, useReactPlayer } from \"@player-ui/react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"react-error-boundary\";\nimport {\n Card,\n CardBody,\n CardHeader,\n ChakraProvider,\n Container,\n extendTheme,\n Flex,\n FormControl,\n FormLabel,\n Heading,\n Select,\n Text,\n type ThemeConfig,\n} from \"@chakra-ui/react\";\n\nimport { ThemeProvider, useDarkMode } from \"@devtools-ds/themes\";\n\nimport { INITIAL_FLOW } from \"../constants\";\nimport { PLAYER_PLUGINS, PUBSUB_PLUGIN } from \"../plugins\";\nimport { useExtensionState } from \"../state\";\nimport { flowDiff } from \"../helpers/flowDiff\";\n\nconst fallbackRender: ErrorBoundary[\"props\"][\"fallbackRender\"] = ({\n error,\n}) => {\n return (\n <Container centerContent>\n <Card>\n <CardHeader>\n <Heading>Ops, something went wrong.</Heading>\n </CardHeader>\n <CardBody>\n <Text as=\"pre\">{error.message}</Text>\n </CardBody>\n </Card>\n </Container>\n );\n};\n\n/**\n * Panel Component\n *\n * This component serves as the main container for the devtools plugin content defined by plugin authors using Player-UI DSL.\n *\n * Props:\n * - `communicationLayer`: An object that allows communication between the devtools and the Player-UI plugins,\n * enabling the exchange of data and events.\n *\n * Features:\n * - Error Handling: Utilizes the `ErrorBoundary` component from `react-error-boundary` to gracefully handle and display errors\n * that may occur during the rendering of the plugin's content.\n * - State Management: Integrates with custom hooks such as `useExtensionState` to manage the state of the plugin and its components.\n * - Player Integration: Uses the `useReactPlayer` hook from `player-ui/react` to render interactive player components based on the\n * DSL defined by the plugin authors.\n *\n * Example Usage:\n * ```tsx\n * <Panel communicationLayer={myCommunicationLayer} />\n * ```\n *\n * Note: The `communicationLayer` prop is essential for the proper functioning of the `Panel` component, as it enables the necessary\n * communication and data exchange with the player-ui/react library.\n */\nexport const Panel: React.FC<{\n /** the communication layer to use for the extension */\n readonly communicationLayer: Pick<\n MessengerOptions<ExtensionSupportedEvents>,\n \"sendMessage\" | \"addListener\" | \"removeListener\"\n >;\n baseTheme?: Record<string, unknown>;\n}> = ({ communicationLayer, baseTheme }) => {\n const { state, selectPlayer, selectPlugin, handleInteraction } =\n useExtensionState({\n communicationLayer,\n });\n\n const { reactPlayer, playerState } = useReactPlayer({\n plugins: PLAYER_PLUGINS,\n });\n\n const dataController = useRef<WeakRef<DataController> | null>(null);\n\n const currentFlow = useRef<Flow | null>(null);\n\n useEffect(() => {\n reactPlayer.player.hooks.dataController.tap(\"devtools-panel\", (d) => {\n dataController.current = new WeakRef(d);\n });\n }, [reactPlayer]);\n\n useEffect(() => {\n // we subscribe to all messages from the devtools plugin\n // so the plugin author can define their own events\n PUBSUB_PLUGIN.subscribe(\"*\", (type: string, payload: string) => {\n handleInteraction({\n type,\n payload,\n });\n });\n }, []);\n\n useEffect(() => {\n const { player, plugin } = state.current;\n\n const flow =\n player && plugin\n ? state.players[player]?.plugins?.[plugin]?.flow || INITIAL_FLOW\n : INITIAL_FLOW;\n\n if (!currentFlow.current) {\n currentFlow.current = flow;\n reactPlayer.start(flow);\n return;\n }\n\n const diff = flowDiff({\n curr: currentFlow.current as Flow,\n next: flow,\n });\n\n if (diff) {\n const { change, value } = diff;\n\n if (change === \"flow\") {\n currentFlow.current = value;\n reactPlayer.start(value);\n } else if (change === \"data\") {\n if (dataController.current) {\n dataController.current.deref()?.set(value as Record<string, unknown>);\n } else {\n reactPlayer.start(flow);\n }\n }\n }\n }, [reactPlayer, state]);\n\n const isDarkMode = useDarkMode();\n const colorMode = isDarkMode ? \"dark\" : \"light\";\n\n const config: ThemeConfig = {\n initialColorMode: colorMode,\n useSystemColorMode: true,\n };\n const theme = extendTheme({ config, ...baseTheme });\n\n const Component = reactPlayer.Component as React.FC;\n\n return (\n <ChakraProvider resetCSS={false} theme={theme}>\n <ThemeProvider theme=\"chrome\" colorScheme={colorMode}>\n <ErrorBoundary fallbackRender={fallbackRender}>\n <Flex\n direction=\"column\"\n h=\"100vh\"\n alignItems={\"normal\"}\n overflow=\"scroll\"\n >\n {state.current.player ? (\n <Flex direction=\"column\" gap=\"20px\">\n <Flex gap=\"16px\">\n <FormControl>\n <FormLabel>Player</FormLabel>\n <Select\n id=\"player\"\n value={state.current.player || \"\"}\n onChange={(event) => selectPlayer(event.target.value)}\n >\n {Object.keys(state.players).map((playerID) => (\n <option key={playerID} value={playerID}>\n {playerID}\n </option>\n ))}\n </Select>\n </FormControl>\n <FormControl>\n <FormLabel>Plugin</FormLabel>\n <Select\n id=\"plugin\"\n value={state.current.plugin || \"\"}\n onChange={(event) => selectPlugin(event.target.value)}\n >\n {Object.keys(\n state.players[state.current.player]?.plugins ?? [],\n ).map((pluginID) => (\n <option key={pluginID} value={pluginID}>\n {pluginID}\n </option>\n ))}\n </Select>\n </FormControl>\n </Flex>\n <Flex width=\"100%\">\n {playerState.status === \"error\" ? (\n fallbackRender({\n error: playerState.error,\n resetErrorBoundary: () => {},\n })\n ) : (\n <Component />\n )}\n </Flex>\n <details>\n <summary>Debug</summary>\n <pre style={{ maxHeight: \"30vh\", overflow: \"scroll\" }}>\n {JSON.stringify(state, null, 2)}\n </pre>\n </details>\n </Flex>\n ) : (\n <Flex justifyContent=\"center\" padding=\"6\">\n <Text>\n No Player-UI instance or devtools plugin detected. Visit{\" \"}\n <a href=\"https://player-ui.github.io/\">\n https://player-ui.github.io/\n </a>{\" \"}\n for more info.\n </Text>\n </Flex>\n )}\n </Flex>\n </ErrorBoundary>\n </ThemeProvider>\n </ChakraProvider>\n );\n};\n","import type { ExtensionState } from \"@player-devtools/types\";\nimport type { Flow } from \"@player-ui/player\";\n\nexport const INITIAL_FLOW: Flow = {\n id: \"initial-flow\",\n views: [\n {\n id: \"view-1\",\n type: \"text\",\n value: \"connecting...\",\n },\n ],\n navigation: {\n BEGIN: \"FLOW_1\",\n FLOW_1: {\n startState: \"VIEW_1\",\n VIEW_1: {\n state_type: \"VIEW\",\n ref: \"view-1\",\n transitions: {},\n },\n },\n },\n};\n\nexport const INITIAL_EXTENSION_STATE: ExtensionState = {\n current: {\n player: null,\n plugin: null,\n },\n players: {},\n};\n","import DevtoolsUIAssetsPlugin from \"@devtools-ui/plugin\";\nimport { PubSubPlugin } from \"@player-ui/pubsub-plugin\";\nimport { CommonExpressionsPlugin } from \"@player-ui/common-expressions-plugin\";\nimport { CommonTypesPlugin } from \"@player-ui/common-types-plugin\";\nimport { DataChangeListenerPlugin } from \"@player-ui/data-change-listener-plugin\";\nimport {\n ConsoleLogger,\n Player,\n PlayerPlugin,\n ReactPlayer,\n type ReactPlayerPlugin,\n} from \"@player-ui/react\";\n\nclass LogForwarder implements PlayerPlugin, ReactPlayerPlugin {\n name = \"Log\";\n apply(player: Player) {\n player.logger.addHandler(new ConsoleLogger(\"trace\", console));\n }\n\n applyReact(reactPlayer: ReactPlayer) {\n this.apply(reactPlayer.player);\n }\n}\n\nexport const PUBSUB_PLUGIN = new PubSubPlugin();\n\nexport const PLAYER_PLUGINS: ReactPlayerPlugin[] = [\n new CommonTypesPlugin(),\n new CommonExpressionsPlugin(),\n new DataChangeListenerPlugin(),\n new DevtoolsUIAssetsPlugin(),\n new LogForwarder(),\n PUBSUB_PLUGIN,\n];\n","import { Messenger } from \"@player-devtools/messenger\";\nimport type {\n ExtensionSupportedEvents,\n MessengerOptions,\n} from \"@player-devtools/types\";\nimport { useCallback, useEffect, useMemo, useReducer } from \"react\";\n\nimport { INITIAL_EXTENSION_STATE } from \"../constants\";\nimport { reducer } from \"./reducer\";\n\nconst NOOP_ID = -1;\n\n/**\n * Custom React hook for managing the state of the devtools extension.\n *\n * This hook initializes the extension's state and sets up a communication layer\n * using the `Messenger` class. It provides methods to select a player or plugin,\n * and handle interactions, which dispatch actions to update the state accordingly.\n *\n */\nexport const useExtensionState = ({\n communicationLayer,\n}: {\n /** the communication layer to use for the extension */\n communicationLayer: Pick<\n MessengerOptions<ExtensionSupportedEvents>,\n \"sendMessage\" | \"addListener\" | \"removeListener\"\n >;\n}) => {\n const [state, dispatch] = useReducer(reducer, INITIAL_EXTENSION_STATE);\n\n const messengerOptions = useMemo<MessengerOptions<ExtensionSupportedEvents>>(\n () => ({\n context: \"devtools\",\n target: \"player\",\n messageCallback: (message) => {\n dispatch(message);\n },\n ...communicationLayer,\n logger: console,\n }),\n [dispatch, communicationLayer],\n );\n\n const messenger = useMemo(\n () => new Messenger(messengerOptions),\n [messengerOptions],\n );\n\n useEffect(() => {\n return () => {\n messenger.destroy();\n };\n }, []);\n\n const selectPlayer = useCallback(\n (playerID: string) => {\n dispatch({\n id: NOOP_ID,\n sender: \"internal\",\n context: \"devtools\",\n _messenger_: false,\n timestamp: Date.now(),\n type: \"PLAYER_DEVTOOLS_PLAYER_SELECTED\",\n payload: {\n playerID,\n },\n });\n\n messenger.sendMessage({\n type: \"PLAYER_DEVTOOLS_PLUGIN_INTERACTION\",\n payload: {\n type: \"player-selected\",\n payload: playerID,\n },\n });\n },\n [dispatch],\n );\n\n const selectPlugin = useCallback(\n (pluginID: string) => {\n dispatch({\n id: NOOP_ID,\n sender: \"internal\",\n context: \"devtools\",\n _messenger_: false,\n timestamp: Date.now(),\n type: \"PLAYER_DEVTOOLS_PLUGIN_SELECTED\",\n payload: {\n pluginID,\n },\n });\n },\n [dispatch],\n );\n\n /**\n * Plugin authors can add interactive elements to the Player-UI content by leveraging\n * the pub-sub plugin and having the handle interaction proxy the message to the inspected\n * Player-UI instance.\n */\n const handleInteraction = useCallback(\n ({\n type,\n payload,\n }: {\n /** interaction type */\n type: string;\n /** interaction payload */\n payload?: string;\n }) => {\n messenger.sendMessage({\n type: \"PLAYER_DEVTOOLS_PLUGIN_INTERACTION\",\n payload: {\n type,\n payload,\n },\n ...(state.current.player ? { target: state.current.player } : {}),\n });\n },\n [messenger],\n );\n\n return { state, selectPlayer, selectPlugin, handleInteraction };\n};\n","import type {\n ExtensionState,\n ExtensionSupportedEvents,\n Transaction,\n} from \"@player-devtools/types\";\nimport { dsetAssign } from \"@player-devtools/utils\";\nimport { produce } from \"immer\";\n\n/** Extension state reducer */\nexport const reducer = (\n state: ExtensionState,\n transaction: Transaction<ExtensionSupportedEvents>,\n): ExtensionState => {\n switch (transaction.type) {\n case \"PLAYER_DEVTOOLS_PLAYER_INIT\":\n return produce(state, (draft) => {\n const {\n sender,\n payload: { plugins },\n } = transaction;\n\n const [plugin] = Object.values(plugins);\n if (!plugin) return;\n\n draft.current.player = sender;\n draft.current.plugin = draft.current.plugin || plugin.id;\n\n // TODO: Verify this works with multiple plugins\n dsetAssign(draft, [\"players\", sender, \"plugins\"], plugins, true);\n dsetAssign(draft.players, [sender, \"active\"], true);\n });\n case \"PLAYER_DEVTOOLS_PLUGIN_FLOW_CHANGE\":\n return produce(state, (draft) => {\n const {\n sender,\n payload: { flow, pluginID },\n } = transaction;\n\n dsetAssign(\n draft,\n [\"players\", sender, \"plugins\", pluginID, \"flow\"],\n flow,\n );\n });\n case \"PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE\":\n return produce(state, (draft) => {\n const {\n sender,\n payload: { data, pluginID },\n } = transaction;\n dsetAssign(\n draft,\n [\"players\", sender, \"plugins\", pluginID, \"flow\", \"data\"],\n data,\n );\n });\n case \"MESSENGER_EVENT_BATCH\":\n return transaction.payload.events.reduce(reducer, state);\n case \"PLAYER_DEVTOOLS_PLAYER_STOPPED\":\n return produce(state, (draft) => {\n const { sender } = transaction;\n dsetAssign(draft, [\"players\", sender, \"active\"], false);\n });\n case \"PLAYER_DEVTOOLS_PLAYER_SELECTED\":\n return produce(state, (draft) => {\n const { playerID } = transaction.payload;\n draft.current.player = playerID;\n });\n case \"PLAYER_DEVTOOLS_PLUGIN_SELECTED\":\n return produce(state, (draft) => {\n const { pluginID } = transaction.payload;\n draft.current.plugin = pluginID;\n });\n default:\n return state;\n }\n};\n","import type { Flow } from \"@player-ui/react\";\nimport { dequal } from \"dequal\";\n\n/**\n * Compares two Flow objects and identifies if there's a change in their structure or data.\n *\n * This function takes two Flow objects as input, `curr` (current) and `next` (next), and compares them\n * to determine if there's a change in the flow's structure or its data. If there's a change in the flow's\n * structure (excluding the `data` property), it returns an object indicating a \"flow\" change along with the\n * new flow. If there's a change in the `data` property, it returns an object indicating a \"data\" change along\n * with the new data. If there are no changes, it returns null.\n */\nexport const flowDiff = ({\n curr,\n next,\n}: {\n curr: Flow;\n next: Flow;\n}):\n | { change: \"data\"; value: Flow[\"data\"] }\n | { change: \"flow\"; value: Flow }\n | null => {\n // compare flows except for the `data` property\n const currCopy = { ...curr, data: null };\n const nextCopy = { ...next, data: null };\n\n const baseFlowIsEqual = dequal(currCopy, nextCopy);\n\n if (!baseFlowIsEqual) {\n return { change: \"flow\", value: next };\n }\n\n // compare data\n const currData = curr.data;\n const nextData = next.data;\n\n const dataIsEqual = dequal(currData, nextData);\n\n if (!dataIsEqual) {\n return { change: \"data\", value: nextData };\n }\n\n return null;\n};\n"],"mappings":"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","names":["useEffect","useEffect"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "sideEffects": false,
3
+ "files": [
4
+ "dist",
5
+ "src",
6
+ "types"
7
+ ],
8
+ "name": "@player-devtools/client",
9
+ "version": "0.0.2-next.0",
10
+ "main": "dist/cjs/index.cjs",
11
+ "dependencies": {
12
+ "@player-devtools/messenger": "0.0.2-next.0",
13
+ "@player-devtools/types": "0.0.2-next.0",
14
+ "@player-devtools/utils": "0.0.2-next.0",
15
+ "@chakra-ui/react": "2.8.2",
16
+ "@player-ui/pubsub-plugin": "0.12.0-next.1",
17
+ "@player-ui/player": "0.12.0-next.1",
18
+ "@player-ui/react": "0.12.0-next.1",
19
+ "@player-ui/common-expressions-plugin": "0.12.0-next.1",
20
+ "@player-ui/common-types-plugin": "0.12.0-next.1",
21
+ "@player-ui/data-change-listener-plugin": "0.12.0-next.1",
22
+ "@devtools-ui/plugin": "0.4.0",
23
+ "@types/react": "^18.2.51",
24
+ "immer": "^10.0.3",
25
+ "react": "^18.2.0",
26
+ "react-error-boundary": "^4.0.12",
27
+ "dequal": "^2.0.2",
28
+ "@devtools-ds/themes": "^1.2.1",
29
+ "tslib": "^2.6.2"
30
+ },
31
+ "module": "dist/index.legacy-esm.js",
32
+ "types": "types/index.d.ts",
33
+ "exports": {
34
+ "./package.json": "./package.json",
35
+ "./dist/index.css": "./dist/index.css",
36
+ ".": {
37
+ "types": "./types/index.d.ts",
38
+ "import": "./dist/index.mjs",
39
+ "default": "./dist/cjs/index.cjs"
40
+ }
41
+ },
42
+ "peerDependencies": {}
43
+ }
@@ -0,0 +1,32 @@
1
+ import type { ExtensionState } from "@player-devtools/types";
2
+ import type { Flow } from "@player-ui/player";
3
+
4
+ export const INITIAL_FLOW: Flow = {
5
+ id: "initial-flow",
6
+ views: [
7
+ {
8
+ id: "view-1",
9
+ type: "text",
10
+ value: "connecting...",
11
+ },
12
+ ],
13
+ navigation: {
14
+ BEGIN: "FLOW_1",
15
+ FLOW_1: {
16
+ startState: "VIEW_1",
17
+ VIEW_1: {
18
+ state_type: "VIEW",
19
+ ref: "view-1",
20
+ transitions: {},
21
+ },
22
+ },
23
+ },
24
+ };
25
+
26
+ export const INITIAL_EXTENSION_STATE: ExtensionState = {
27
+ current: {
28
+ player: null,
29
+ plugin: null,
30
+ },
31
+ players: {},
32
+ };
@@ -0,0 +1,72 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import type { Flow } from "@player-ui/react";
3
+ import { flowDiff } from "../flowDiff";
4
+
5
+ const mockFlow1: Flow = {
6
+ id: "flow",
7
+ views: [
8
+ {
9
+ id: "view",
10
+ type: "info",
11
+ },
12
+ ],
13
+ data: {
14
+ foo: "bar",
15
+ },
16
+ navigation: {
17
+ BEGIN: "BEGIN",
18
+ },
19
+ };
20
+
21
+ const mockFlow2: Flow = {
22
+ id: "flow",
23
+ views: [
24
+ {
25
+ id: "view",
26
+ type: "info",
27
+ },
28
+ ],
29
+ data: {
30
+ foo: "bar",
31
+ something: "else",
32
+ },
33
+ navigation: {
34
+ BEGIN: "BEGIN",
35
+ },
36
+ };
37
+
38
+ const mockFlow3: Flow = {
39
+ id: "another_flow",
40
+ views: [
41
+ {
42
+ id: "view",
43
+ type: "info",
44
+ },
45
+ ],
46
+ data: {
47
+ foo: "bar",
48
+ },
49
+ navigation: {
50
+ BEGIN: "BEGIN",
51
+ },
52
+ };
53
+
54
+ describe("flowDiff", () => {
55
+ test("returns null if no changes", () => {
56
+ const result = flowDiff({ curr: mockFlow1, next: mockFlow1 });
57
+
58
+ expect(result).toBeNull();
59
+ });
60
+
61
+ test("returns flow change if base flow is different", () => {
62
+ const result = flowDiff({ curr: mockFlow1, next: mockFlow3 });
63
+
64
+ expect(result).toEqual({ change: "flow", value: mockFlow3 });
65
+ });
66
+
67
+ test("returns data change if data is different", () => {
68
+ const result = flowDiff({ curr: mockFlow1, next: mockFlow2 });
69
+
70
+ expect(result).toEqual({ change: "data", value: mockFlow2.data });
71
+ });
72
+ });
@@ -0,0 +1,44 @@
1
+ import type { Flow } from "@player-ui/react";
2
+ import { dequal } from "dequal";
3
+
4
+ /**
5
+ * Compares two Flow objects and identifies if there's a change in their structure or data.
6
+ *
7
+ * This function takes two Flow objects as input, `curr` (current) and `next` (next), and compares them
8
+ * to determine if there's a change in the flow's structure or its data. If there's a change in the flow's
9
+ * structure (excluding the `data` property), it returns an object indicating a "flow" change along with the
10
+ * new flow. If there's a change in the `data` property, it returns an object indicating a "data" change along
11
+ * with the new data. If there are no changes, it returns null.
12
+ */
13
+ export const flowDiff = ({
14
+ curr,
15
+ next,
16
+ }: {
17
+ curr: Flow;
18
+ next: Flow;
19
+ }):
20
+ | { change: "data"; value: Flow["data"] }
21
+ | { change: "flow"; value: Flow }
22
+ | null => {
23
+ // compare flows except for the `data` property
24
+ const currCopy = { ...curr, data: null };
25
+ const nextCopy = { ...next, data: null };
26
+
27
+ const baseFlowIsEqual = dequal(currCopy, nextCopy);
28
+
29
+ if (!baseFlowIsEqual) {
30
+ return { change: "flow", value: next };
31
+ }
32
+
33
+ // compare data
34
+ const currData = curr.data;
35
+ const nextData = next.data;
36
+
37
+ const dataIsEqual = dequal(currData, nextData);
38
+
39
+ if (!dataIsEqual) {
40
+ return { change: "data", value: nextData };
41
+ }
42
+
43
+ return null;
44
+ };
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { Panel } from "./panel";
@@ -0,0 +1,234 @@
1
+ import React, { useRef } from "react";
2
+ import type {
3
+ MessengerOptions,
4
+ ExtensionSupportedEvents,
5
+ } from "@player-devtools/types";
6
+ import { DataController, Flow, useReactPlayer } from "@player-ui/react";
7
+ import { useEffect } from "react";
8
+ import { ErrorBoundary } from "react-error-boundary";
9
+ import {
10
+ Card,
11
+ CardBody,
12
+ CardHeader,
13
+ ChakraProvider,
14
+ Container,
15
+ extendTheme,
16
+ Flex,
17
+ FormControl,
18
+ FormLabel,
19
+ Heading,
20
+ Select,
21
+ Text,
22
+ type ThemeConfig,
23
+ } from "@chakra-ui/react";
24
+
25
+ import { ThemeProvider, useDarkMode } from "@devtools-ds/themes";
26
+
27
+ import { INITIAL_FLOW } from "../constants";
28
+ import { PLAYER_PLUGINS, PUBSUB_PLUGIN } from "../plugins";
29
+ import { useExtensionState } from "../state";
30
+ import { flowDiff } from "../helpers/flowDiff";
31
+
32
+ const fallbackRender: ErrorBoundary["props"]["fallbackRender"] = ({
33
+ error,
34
+ }) => {
35
+ return (
36
+ <Container centerContent>
37
+ <Card>
38
+ <CardHeader>
39
+ <Heading>Ops, something went wrong.</Heading>
40
+ </CardHeader>
41
+ <CardBody>
42
+ <Text as="pre">{error.message}</Text>
43
+ </CardBody>
44
+ </Card>
45
+ </Container>
46
+ );
47
+ };
48
+
49
+ /**
50
+ * Panel Component
51
+ *
52
+ * This component serves as the main container for the devtools plugin content defined by plugin authors using Player-UI DSL.
53
+ *
54
+ * Props:
55
+ * - `communicationLayer`: An object that allows communication between the devtools and the Player-UI plugins,
56
+ * enabling the exchange of data and events.
57
+ *
58
+ * Features:
59
+ * - Error Handling: Utilizes the `ErrorBoundary` component from `react-error-boundary` to gracefully handle and display errors
60
+ * that may occur during the rendering of the plugin's content.
61
+ * - State Management: Integrates with custom hooks such as `useExtensionState` to manage the state of the plugin and its components.
62
+ * - Player Integration: Uses the `useReactPlayer` hook from `player-ui/react` to render interactive player components based on the
63
+ * DSL defined by the plugin authors.
64
+ *
65
+ * Example Usage:
66
+ * ```tsx
67
+ * <Panel communicationLayer={myCommunicationLayer} />
68
+ * ```
69
+ *
70
+ * Note: The `communicationLayer` prop is essential for the proper functioning of the `Panel` component, as it enables the necessary
71
+ * communication and data exchange with the player-ui/react library.
72
+ */
73
+ export const Panel: React.FC<{
74
+ /** the communication layer to use for the extension */
75
+ readonly communicationLayer: Pick<
76
+ MessengerOptions<ExtensionSupportedEvents>,
77
+ "sendMessage" | "addListener" | "removeListener"
78
+ >;
79
+ baseTheme?: Record<string, unknown>;
80
+ }> = ({ communicationLayer, baseTheme }) => {
81
+ const { state, selectPlayer, selectPlugin, handleInteraction } =
82
+ useExtensionState({
83
+ communicationLayer,
84
+ });
85
+
86
+ const { reactPlayer, playerState } = useReactPlayer({
87
+ plugins: PLAYER_PLUGINS,
88
+ });
89
+
90
+ const dataController = useRef<WeakRef<DataController> | null>(null);
91
+
92
+ const currentFlow = useRef<Flow | null>(null);
93
+
94
+ useEffect(() => {
95
+ reactPlayer.player.hooks.dataController.tap("devtools-panel", (d) => {
96
+ dataController.current = new WeakRef(d);
97
+ });
98
+ }, [reactPlayer]);
99
+
100
+ useEffect(() => {
101
+ // we subscribe to all messages from the devtools plugin
102
+ // so the plugin author can define their own events
103
+ PUBSUB_PLUGIN.subscribe("*", (type: string, payload: string) => {
104
+ handleInteraction({
105
+ type,
106
+ payload,
107
+ });
108
+ });
109
+ }, []);
110
+
111
+ useEffect(() => {
112
+ const { player, plugin } = state.current;
113
+
114
+ const flow =
115
+ player && plugin
116
+ ? state.players[player]?.plugins?.[plugin]?.flow || INITIAL_FLOW
117
+ : INITIAL_FLOW;
118
+
119
+ if (!currentFlow.current) {
120
+ currentFlow.current = flow;
121
+ reactPlayer.start(flow);
122
+ return;
123
+ }
124
+
125
+ const diff = flowDiff({
126
+ curr: currentFlow.current as Flow,
127
+ next: flow,
128
+ });
129
+
130
+ if (diff) {
131
+ const { change, value } = diff;
132
+
133
+ if (change === "flow") {
134
+ currentFlow.current = value;
135
+ reactPlayer.start(value);
136
+ } else if (change === "data") {
137
+ if (dataController.current) {
138
+ dataController.current.deref()?.set(value as Record<string, unknown>);
139
+ } else {
140
+ reactPlayer.start(flow);
141
+ }
142
+ }
143
+ }
144
+ }, [reactPlayer, state]);
145
+
146
+ const isDarkMode = useDarkMode();
147
+ const colorMode = isDarkMode ? "dark" : "light";
148
+
149
+ const config: ThemeConfig = {
150
+ initialColorMode: colorMode,
151
+ useSystemColorMode: true,
152
+ };
153
+ const theme = extendTheme({ config, ...baseTheme });
154
+
155
+ const Component = reactPlayer.Component as React.FC;
156
+
157
+ return (
158
+ <ChakraProvider resetCSS={false} theme={theme}>
159
+ <ThemeProvider theme="chrome" colorScheme={colorMode}>
160
+ <ErrorBoundary fallbackRender={fallbackRender}>
161
+ <Flex
162
+ direction="column"
163
+ h="100vh"
164
+ alignItems={"normal"}
165
+ overflow="scroll"
166
+ >
167
+ {state.current.player ? (
168
+ <Flex direction="column" gap="20px">
169
+ <Flex gap="16px">
170
+ <FormControl>
171
+ <FormLabel>Player</FormLabel>
172
+ <Select
173
+ id="player"
174
+ value={state.current.player || ""}
175
+ onChange={(event) => selectPlayer(event.target.value)}
176
+ >
177
+ {Object.keys(state.players).map((playerID) => (
178
+ <option key={playerID} value={playerID}>
179
+ {playerID}
180
+ </option>
181
+ ))}
182
+ </Select>
183
+ </FormControl>
184
+ <FormControl>
185
+ <FormLabel>Plugin</FormLabel>
186
+ <Select
187
+ id="plugin"
188
+ value={state.current.plugin || ""}
189
+ onChange={(event) => selectPlugin(event.target.value)}
190
+ >
191
+ {Object.keys(
192
+ state.players[state.current.player]?.plugins ?? [],
193
+ ).map((pluginID) => (
194
+ <option key={pluginID} value={pluginID}>
195
+ {pluginID}
196
+ </option>
197
+ ))}
198
+ </Select>
199
+ </FormControl>
200
+ </Flex>
201
+ <Flex width="100%">
202
+ {playerState.status === "error" ? (
203
+ fallbackRender({
204
+ error: playerState.error,
205
+ resetErrorBoundary: () => {},
206
+ })
207
+ ) : (
208
+ <Component />
209
+ )}
210
+ </Flex>
211
+ <details>
212
+ <summary>Debug</summary>
213
+ <pre style={{ maxHeight: "30vh", overflow: "scroll" }}>
214
+ {JSON.stringify(state, null, 2)}
215
+ </pre>
216
+ </details>
217
+ </Flex>
218
+ ) : (
219
+ <Flex justifyContent="center" padding="6">
220
+ <Text>
221
+ No Player-UI instance or devtools plugin detected. Visit{" "}
222
+ <a href="https://player-ui.github.io/">
223
+ https://player-ui.github.io/
224
+ </a>{" "}
225
+ for more info.
226
+ </Text>
227
+ </Flex>
228
+ )}
229
+ </Flex>
230
+ </ErrorBoundary>
231
+ </ThemeProvider>
232
+ </ChakraProvider>
233
+ );
234
+ };
@@ -0,0 +1,34 @@
1
+ import DevtoolsUIAssetsPlugin from "@devtools-ui/plugin";
2
+ import { PubSubPlugin } from "@player-ui/pubsub-plugin";
3
+ import { CommonExpressionsPlugin } from "@player-ui/common-expressions-plugin";
4
+ import { CommonTypesPlugin } from "@player-ui/common-types-plugin";
5
+ import { DataChangeListenerPlugin } from "@player-ui/data-change-listener-plugin";
6
+ import {
7
+ ConsoleLogger,
8
+ Player,
9
+ PlayerPlugin,
10
+ ReactPlayer,
11
+ type ReactPlayerPlugin,
12
+ } from "@player-ui/react";
13
+
14
+ class LogForwarder implements PlayerPlugin, ReactPlayerPlugin {
15
+ name = "Log";
16
+ apply(player: Player) {
17
+ player.logger.addHandler(new ConsoleLogger("trace", console));
18
+ }
19
+
20
+ applyReact(reactPlayer: ReactPlayer) {
21
+ this.apply(reactPlayer.player);
22
+ }
23
+ }
24
+
25
+ export const PUBSUB_PLUGIN = new PubSubPlugin();
26
+
27
+ export const PLAYER_PLUGINS: ReactPlayerPlugin[] = [
28
+ new CommonTypesPlugin(),
29
+ new CommonExpressionsPlugin(),
30
+ new DataChangeListenerPlugin(),
31
+ new DevtoolsUIAssetsPlugin(),
32
+ new LogForwarder(),
33
+ PUBSUB_PLUGIN,
34
+ ];