@player-tools/devtools-client 0.2.1 → 0.2.2--canary.17.363

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.
@@ -1,70 +1,7 @@
1
- import {
2
- type Message,
3
- clearStore,
4
- playerFlowStartAction,
5
- playerInitAction,
6
- playerRemoveAction,
7
- playerTimelineAction,
8
- playerViewUpdateAction,
9
- selectedPlayerAction,
10
- } from '@player-tools/devtools-common';
11
- import type { Store } from 'redux';
12
- import { GET_DATA_BINDING_DETAILS } from './aliases';
13
-
14
1
  export * from './actions';
15
2
  export * from './aliases';
3
+ export * from './listeners';
16
4
  export * from './reducers';
17
-
18
- export function handleMessage(store: Store, message: Message) {
19
- switch (message.type) {
20
- case 'runtime-init':
21
- store.dispatch(clearStore());
22
- break;
23
- case 'player-init':
24
- store.dispatch(playerInitAction(message));
25
- store.dispatch(selectedPlayerAction());
26
- break;
27
- case 'player-removed':
28
- store.dispatch(playerRemoveAction(message.playerID));
29
- store.dispatch(selectedPlayerAction());
30
- break;
31
- case 'player-flow-start':
32
- store.dispatch(playerFlowStartAction(message));
33
- store.dispatch(playerTimelineAction(message));
34
- store.dispatch({
35
- type: GET_DATA_BINDING_DETAILS,
36
- payload: { playerID: message.playerID, binding: '' },
37
- });
38
- break;
39
- case 'player-log-event':
40
- store.dispatch(playerTimelineAction(message));
41
- break;
42
- case 'player-view-update-event':
43
- store.dispatch(playerViewUpdateAction(message));
44
- break;
45
- case 'player-data-change-event': {
46
- const { players } = store.getState();
47
-
48
- if (
49
- players.activePlayers[message.playerID] &&
50
- players.activePlayers[message.playerID].dataState.selectedBinding
51
- ) {
52
- store.dispatch({
53
- type: GET_DATA_BINDING_DETAILS,
54
- payload: message,
55
- });
56
- }
57
-
58
- store.dispatch({
59
- type: GET_DATA_BINDING_DETAILS,
60
- payload: { playerID: message.playerID, binding: '' },
61
- });
62
- store.dispatch(playerTimelineAction(message));
63
- break;
64
- }
65
-
66
- default:
67
- console.warn(`Unhandled event: ${JSON.stringify(message)}`);
68
- break;
69
- }
70
- }
5
+ export * from './selectors';
6
+ export * from './state';
7
+ export * from './store';
@@ -0,0 +1,10 @@
1
+ import { Events, Message } from "@player-tools/devtools-common";
2
+ import { Events as EventActions } from "./actions";
3
+
4
+ export function handleMessage(message: Message) {
5
+ // propagate message to default event handlers
6
+ const { type } = message;
7
+ if (type in Events.EventTypes) {
8
+ EventActions.actions[type as Events.EventTypes](message as any)
9
+ }
10
+ }
@@ -0,0 +1,63 @@
1
+ import { StoreState } from "@player-tools/devtools-client";
2
+ import { createListenerMiddleware, isAnyOf } from "@reduxjs/toolkit";
3
+ import { Actions, Events } from "./actions";
4
+ import { GET_DATA_BINDING_DETAILS } from "./aliases";
5
+
6
+ export const listenerMiddleware = createListenerMiddleware();
7
+
8
+ listenerMiddleware.startListening({
9
+ matcher: isAnyOf(
10
+ Events.actions['player-data-change-event'],
11
+ Events.actions['player-log-event'],
12
+ Events.actions['player-flow-start'],
13
+ // TODO: I don't actually think this _was_ included
14
+ Events.actions['player-view-update-event']
15
+ ),
16
+ effect: (action, api) => {
17
+ api.dispatch(Actions['player-timeline-event'](action.payload));
18
+ },
19
+ });
20
+
21
+ listenerMiddleware.startListening({
22
+ actionCreator: Events.actions['runtime-init'],
23
+ effect: (_, api) => {
24
+ api.dispatch(Actions["clear-store"]())
25
+ }
26
+ })
27
+
28
+ listenerMiddleware.startListening({
29
+ matcher: isAnyOf(
30
+ Events.actions["player-init"],
31
+ Events.actions["player-removed"],
32
+ ),
33
+ effect: (_, api) => {
34
+ api.dispatch(Actions["selected-player"]())
35
+ }
36
+ });
37
+
38
+ listenerMiddleware.startListening({
39
+ matcher: isAnyOf(
40
+ Events.actions["player-flow-start"],
41
+ Events.actions["player-data-change-event"],
42
+ ),
43
+ effect: (action, api) => {
44
+ // TODO: Just appropriately type the middleware
45
+ const { players } = api.getState() as StoreState;
46
+ const { playerID } = action.payload;
47
+
48
+ if (
49
+ players.activePlayers[playerID] &&
50
+ players.activePlayers[playerID].dataState.selectedBinding
51
+ ) {
52
+ api.dispatch({
53
+ type: GET_DATA_BINDING_DETAILS,
54
+ payload: { playerID, binding: players.activePlayers[playerID].dataState.selectedBinding },
55
+ });
56
+ }
57
+
58
+ api.dispatch({
59
+ type: GET_DATA_BINDING_DETAILS,
60
+ payload: { playerID, binding: '' },
61
+ })
62
+ }
63
+ })
@@ -1,118 +1,248 @@
1
- import type { ActionReducerMapBuilder } from '@reduxjs/toolkit';
2
- import type { PlayersState } from '@player-tools/devtools-common';
3
- import type { AsyncRPCActions } from './actions';
4
-
5
- /**
6
- * Callback function that adds cases for async actions for the player.
7
- * @param playerReducerCallback
8
- * @returns
9
- */
10
- export const buildPlayerReducerCallback =
11
- (actions: AsyncRPCActions) =>
12
- (builder: ActionReducerMapBuilder<PlayersState>) => {
13
- builder.addCase(
14
- actions['player-runtime-info-request'].fulfilled,
15
- (state, action) => {
16
- const { activePlayers, selectedPlayerId } = state;
17
-
18
- if (!selectedPlayerId) {
19
- return;
20
- }
21
-
22
- const data =
23
- action.payload && Object.keys(action.payload).length > 0
24
- ? action.payload
25
- : null;
26
- activePlayers[selectedPlayerId].flowInfo = data;
1
+ import { ActionReducerMapBuilder, createReducer } from '@reduxjs/toolkit';
2
+ import type { PlayersState } from './state';
3
+ import { Actions, Events, Methods } from './actions';
4
+
5
+ const initialState = {
6
+ selectedPlayerId: null,
7
+ activePlayers: {},
8
+ } as PlayersState;
9
+
10
+ // TODO: It'd be nice if methodThunks didn't have to be passed in - but it is kinda client dependent
11
+ export const methodsReducer = (methods: Methods.MethodThunks) => (builder: ActionReducerMapBuilder<PlayersState>) => {
12
+ builder.addCase(
13
+ methods['player-runtime-info-request'].fulfilled,
14
+ (state, action) => {
15
+ const { activePlayers, selectedPlayerId } = state;
16
+
17
+ if (!selectedPlayerId) {
18
+ return;
27
19
  }
28
- );
29
20
 
30
- builder.addCase(
31
- actions['player-config-request'].fulfilled,
32
- (state, action) => {
33
- const { activePlayers, selectedPlayerId } = state;
34
-
35
- if (!selectedPlayerId) {
36
- return;
37
- }
38
-
39
- activePlayers[selectedPlayerId].configState = action.payload;
21
+ const data =
22
+ action.payload && Object.keys(action.payload).length > 0
23
+ ? action.payload
24
+ : null;
25
+ activePlayers[selectedPlayerId].flowInfo = data;
26
+ }
27
+ );
28
+
29
+ builder.addCase(
30
+ methods['player-config-request'].fulfilled,
31
+ (state, action) => {
32
+ const { activePlayers, selectedPlayerId } = state;
33
+
34
+ if (!selectedPlayerId) {
35
+ return;
40
36
  }
41
- );
42
37
 
43
- builder.addCase(
44
- actions['player-view-details-request'].fulfilled,
45
- (state, action) => {
46
- const { activePlayers, selectedPlayerId } = state;
38
+ activePlayers[selectedPlayerId].configState = action.payload;
39
+ }
40
+ );
47
41
 
48
- if (!selectedPlayerId) {
49
- return;
50
- }
42
+ builder.addCase(
43
+ methods['player-view-details-request'].fulfilled,
44
+ (state, action) => {
45
+ const { activePlayers, selectedPlayerId } = state;
51
46
 
52
- activePlayers[selectedPlayerId].view = action.payload?.lastViewUpdate;
53
- }
54
- );
55
-
56
- builder.addCase(
57
- actions['player-data-binding-details'].fulfilled,
58
- (state, action) => {
59
- const {
60
- meta: {
61
- arg: { binding, playerID },
62
- },
63
- payload,
64
- } = action;
65
- const { activePlayers } = state;
66
-
67
- if (!playerID || !activePlayers[playerID]) {
68
- return;
69
- }
70
-
71
- if (binding === '') {
72
- activePlayers[playerID].dataState.allBindings = payload;
73
- return;
74
- }
75
-
76
- activePlayers[playerID].dataState.selectedBinding = payload;
77
- }
78
- );
79
-
80
- builder.addCase(
81
- actions['player-execute-expression'].fulfilled,
82
- (state, action) => {
83
- const { activePlayers, selectedPlayerId } = state;
84
-
85
- if (!selectedPlayerId) {
86
- return;
87
- }
88
-
89
- activePlayers[selectedPlayerId].consoleState?.history?.push({
90
- id: action.meta.requestId,
91
- result: action.payload,
92
- expression: action.payload?.exp ?? '',
93
- });
47
+ if (!selectedPlayerId) {
48
+ return;
94
49
  }
95
- );
96
50
 
97
- builder.addCase(
98
- actions['player-start-profiler-request'].fulfilled,
99
- (state, action) => {
100
- const { activePlayers, selectedPlayerId } = state;
101
-
102
- if (!selectedPlayerId) return;
51
+ activePlayers[selectedPlayerId].view = action.payload?.lastViewUpdate;
52
+ }
53
+ );
54
+
55
+ builder.addCase(
56
+ methods['player-data-binding-details'].fulfilled,
57
+ (state, action) => {
58
+ const {
59
+ meta: {
60
+ arg: { params: { binding, playerID } },
61
+ },
62
+ payload,
63
+ } = action;
64
+ const { activePlayers } = state;
65
+
66
+ if (!playerID || !activePlayers[playerID]) {
67
+ return;
68
+ }
103
69
 
104
- activePlayers[selectedPlayerId].profilerInfo = action.payload?.data;
70
+ if (binding === '') {
71
+ activePlayers[playerID].dataState.allBindings = payload;
72
+ return;
105
73
  }
106
- );
107
74
 
108
- builder.addCase(
109
- actions['player-stop-profiler-request'].fulfilled,
110
- (state, action) => {
111
- const { activePlayers, selectedPlayerId } = state;
75
+ activePlayers[playerID].dataState.selectedBinding = payload;
76
+ }
77
+ );
112
78
 
113
- if (!selectedPlayerId) return;
79
+ builder.addCase(
80
+ methods['player-execute-expression'].fulfilled,
81
+ (state, action) => {
82
+ const { activePlayers, selectedPlayerId } = state;
114
83
 
115
- activePlayers[selectedPlayerId].profilerInfo = action.payload?.data;
84
+ if (!selectedPlayerId) {
85
+ return;
116
86
  }
117
- );
118
- };
87
+
88
+ activePlayers[selectedPlayerId].consoleState?.history?.push({
89
+ id: action.meta.requestId,
90
+ result: action.payload,
91
+ expression: action.payload?.exp ?? '',
92
+ });
93
+ }
94
+ );
95
+
96
+ builder.addCase(
97
+ methods['player-start-profiler-request'].fulfilled,
98
+ (state, action) => {
99
+ const { activePlayers, selectedPlayerId } = state;
100
+
101
+ if (!selectedPlayerId) return;
102
+
103
+ activePlayers[selectedPlayerId].profilerInfo = action.payload?.data;
104
+ }
105
+ );
106
+
107
+ builder.addCase(
108
+ methods['player-stop-profiler-request'].fulfilled,
109
+ (state, action) => {
110
+ const { activePlayers, selectedPlayerId } = state;
111
+
112
+ if (!selectedPlayerId) return;
113
+
114
+ activePlayers[selectedPlayerId].profilerInfo = action.payload?.data;
115
+ }
116
+ );
117
+ };
118
+
119
+ export const eventsReducer = (builder: ActionReducerMapBuilder<PlayersState>) => {
120
+ builder.addCase(Events.actions['player-init'], (state, action) => {
121
+ const {
122
+ payload: { version, playerID },
123
+ } = action;
124
+ state.activePlayers[playerID] = {
125
+ timelineEvents: [],
126
+ dataState: {},
127
+ consoleState: { history: [] },
128
+ };
129
+ state.version = version;
130
+ });
131
+
132
+ builder.addCase(Events.actions['player-removed'], (state, action) => {
133
+ delete state.activePlayers[action.payload.playerID];
134
+ });
135
+
136
+ builder.addCase(Events.actions['player-flow-start'], (state, action) => {
137
+ const {
138
+ payload: { flow, playerID },
139
+ } = action;
140
+
141
+ if (!state.activePlayers[playerID]) {
142
+ state.activePlayers[playerID] = {
143
+ flowInfo: { currentFlow: flow },
144
+ timelineEvents: [],
145
+ dataState: {},
146
+ consoleState: { history: [] },
147
+ };
148
+ state.selectedPlayerId = playerID;
149
+ return;
150
+ }
151
+
152
+ state.activePlayers[playerID].flowInfo = {
153
+ ...state.activePlayers[playerID].flowInfo,
154
+ currentFlow: flow,
155
+ };
156
+ });
157
+
158
+ builder.addCase(Events.actions['player-view-update-event'], (state, action) => {
159
+ const {
160
+ payload: { playerID, update },
161
+ } = action;
162
+
163
+ if (!state.activePlayers[playerID]) {
164
+ state.activePlayers[playerID] = {
165
+ view: update,
166
+ timelineEvents: [],
167
+ dataState: {},
168
+ consoleState: { history: [] },
169
+ };
170
+ state.selectedPlayerId = playerID;
171
+ return;
172
+ }
173
+
174
+ state.activePlayers[playerID].view = update;
175
+ });
176
+ };
177
+
178
+ export const actionsReducer = (builder: ActionReducerMapBuilder<PlayersState>) => {
179
+ builder.addCase(Actions['selected-player'], (state, action) => {
180
+ if (action.payload) {
181
+ state.selectedPlayerId = action.payload;
182
+ return;
183
+ }
184
+
185
+ state.selectedPlayerId = Object.keys(state.activePlayers)[0] || null;
186
+ });
187
+
188
+ builder.addCase(Actions['player-timeline-event'], (state, action) => {
189
+ const {
190
+ payload: { playerID },
191
+ } = action;
192
+
193
+ if (!state.activePlayers[playerID]) {
194
+ state.activePlayers[playerID] = {
195
+ timelineEvents: [action.payload],
196
+ dataState: {},
197
+ consoleState: { history: [] },
198
+ };
199
+ state.selectedPlayerId = playerID;
200
+ return;
201
+ }
202
+
203
+ state.activePlayers[playerID].timelineEvents.push(action.payload);
204
+ });
205
+
206
+ builder.addCase(Actions['clear-selected-data-details'], (state) => {
207
+ const { activePlayers, selectedPlayerId } = state;
208
+
209
+ if (!selectedPlayerId || !activePlayers[selectedPlayerId]) {
210
+ return;
211
+ }
212
+
213
+ activePlayers[selectedPlayerId].dataState.selectedBinding = undefined;
214
+ });
215
+
216
+ builder.addCase(Actions['clear-console'], (state) => {
217
+ const { activePlayers, selectedPlayerId } = state;
218
+
219
+ if (!selectedPlayerId) {
220
+ return;
221
+ }
222
+
223
+ activePlayers[selectedPlayerId].consoleState = {
224
+ history: [],
225
+ };
226
+ });
227
+
228
+ builder.addCase(Actions['clear-logs'], (state) => {
229
+ const { activePlayers, selectedPlayerId } = state;
230
+
231
+ if (!selectedPlayerId) {
232
+ return;
233
+ }
234
+
235
+ activePlayers[selectedPlayerId].timelineEvents = [];
236
+ });
237
+
238
+ builder.addCase(Actions['clear-store'], () => {
239
+ return initialState;
240
+ });
241
+ };
242
+
243
+ export const playersReducer = (methods: Methods.MethodThunks) =>
244
+ createReducer<PlayersState>(initialState, (builder) => {
245
+ actionsReducer(builder)
246
+ eventsReducer(builder)
247
+ methodsReducer(methods)(builder)
248
+ });
@@ -0,0 +1,133 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+ import type { StoreState, PlayersState } from './state';
3
+
4
+ /**
5
+ * Selects the player state
6
+ * @param state
7
+ * @returns
8
+ */
9
+ const selectPlayers = (state: StoreState) => {
10
+ return state.players;
11
+ };
12
+
13
+ /**
14
+ * Selects all the active players.
15
+ */
16
+ const selectActivePlayers = createSelector(
17
+ selectPlayers,
18
+ (players: PlayersState) => players.activePlayers
19
+ );
20
+
21
+ export const selectPlayerVersion = createSelector(
22
+ selectPlayers,
23
+ (players: PlayersState) => players.version
24
+ );
25
+
26
+ export const selectPlayerIds = createSelector(
27
+ selectActivePlayers,
28
+ (activePlayers) => Object.keys(activePlayers) || []
29
+ );
30
+
31
+ /**
32
+ * Selects the selected/currently active player Id.
33
+ */
34
+ export const selectSelectedPlayerId = createSelector(
35
+ selectPlayers,
36
+ (players: PlayersState) => players.selectedPlayerId
37
+ );
38
+
39
+ export const selectCurrentPlayer = createSelector(
40
+ selectActivePlayers,
41
+ selectSelectedPlayerId,
42
+ (activePlayers, selectedPlayerId) => {
43
+ if (!selectedPlayerId) {
44
+ return null;
45
+ }
46
+
47
+ return activePlayers[selectedPlayerId];
48
+ }
49
+ );
50
+
51
+ export const selectConfig = createSelector(
52
+ selectCurrentPlayer,
53
+ (currentPlayer) => {
54
+ return currentPlayer?.configState ?? null;
55
+ }
56
+ );
57
+
58
+ const selectData = createSelector(selectCurrentPlayer, (currentPlayer) => {
59
+ return currentPlayer?.dataState;
60
+ });
61
+
62
+ export const selectFlowInfo = createSelector(
63
+ selectCurrentPlayer,
64
+ (currentPlayer) => {
65
+ if (!currentPlayer) {
66
+ return null;
67
+ }
68
+
69
+ return currentPlayer?.flowInfo;
70
+ }
71
+ );
72
+
73
+ export const selectCurrentFlow = createSelector(
74
+ selectFlowInfo,
75
+ (flowInfo) => {
76
+ return flowInfo?.currentFlow;
77
+ }
78
+ );
79
+
80
+ export const selectCurrentTopic = createSelector(
81
+ selectCurrentFlow,
82
+ (currentFlow) => {
83
+ return currentFlow?.topic;
84
+ }
85
+ );
86
+
87
+ export const selectEvents = createSelector(
88
+ selectCurrentPlayer,
89
+ (currentPlayer) => {
90
+ if (!currentPlayer) {
91
+ return [];
92
+ }
93
+
94
+ return currentPlayer?.timelineEvents;
95
+ }
96
+ );
97
+
98
+ export const selectView = createSelector(
99
+ selectCurrentPlayer,
100
+ (currentPlayer) => {
101
+ if (!currentPlayer) {
102
+ return null;
103
+ }
104
+
105
+ return currentPlayer?.view;
106
+ }
107
+ );
108
+
109
+ export const selectAllBindings = createSelector(selectData, (data) => {
110
+ return data?.allBindings;
111
+ });
112
+
113
+ export const selectSelectedBinding = createSelector(selectData, (data) => {
114
+ return data?.selectedBinding;
115
+ });
116
+
117
+ export const selectConsole = createSelector(
118
+ selectCurrentPlayer,
119
+ (currentPlayer) => {
120
+ if (!currentPlayer) {
121
+ return { history: [] };
122
+ }
123
+
124
+ return currentPlayer.consoleState;
125
+ }
126
+ );
127
+
128
+ export const selectProfiler = createSelector(
129
+ selectCurrentPlayer,
130
+ (currentPlayer) => {
131
+ return currentPlayer?.profilerInfo;
132
+ }
133
+ );