@pihanga2/core 0.3.0 → 0.3.2

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/src/card.tsx CHANGED
@@ -1,194 +1,234 @@
1
- import React from 'react'
2
- import { useDispatch, useSelector } from "react-redux"
3
- import equal from "deep-equal"
1
+ import React, { useEffect, useId } from "react";
2
+ import { useDispatch, useSelector, useStore } from "react-redux";
3
+ import equal from "deep-equal";
4
4
 
5
- import { getLogger } from "./logger"
5
+ import { getLogger } from "./logger";
6
6
  import {
7
7
  CSSModuleClasses,
8
8
  CardProp,
9
9
  PiCardDef,
10
+ PiReducer,
10
11
  PiRegisterComponent,
11
12
  PiRegisterReducerF,
13
+ ReduceF,
14
+ ReduxAction,
12
15
  ReduxState,
13
16
  StateMapper,
14
17
  StateMapperContext,
15
- } from "./types"
16
- import { Action, AnyAction, Dispatch } from "@reduxjs/toolkit"
17
- import { _createCardMapping, _updateCardMapping, _registerCard, cardMappings, cardTypes, dispatch2registerReducer, framework, Mapping } from './register_cards'
18
-
19
- const logger = getLogger("card")
18
+ } from "./types";
19
+ import { Action, AnyAction, Dispatch } from "@reduxjs/toolkit";
20
+ import {
21
+ _createCardMapping,
22
+ _updateCardMapping,
23
+ _registerCard,
24
+ cardMappings,
25
+ cardTypes,
26
+ dispatch2registerReducer,
27
+ framework,
28
+ Mapping,
29
+ } from "./register_cards";
30
+
31
+ const logger = getLogger("card");
20
32
 
21
33
  // export type CardProp = {
22
34
  // cardName: PiCardRef
23
35
  // } & { [k: string]: any }
24
36
 
25
- type CompProps = { [k: string]: any }
37
+ type CompProps = { [k: string]: any };
26
38
  type CardInfo = {
27
- mapping: Mapping
28
- cardType: PiRegisterComponent
29
- }
39
+ mapping: Mapping;
40
+ cardType: PiRegisterComponent;
41
+ };
30
42
 
31
43
  export function Card(props: CardProp): JSX.Element {
32
- let cardName: string
44
+ let cardName: string;
33
45
 
34
- const [id, _] = React.useState<number>(Math.floor(Math.random() * 10000))
35
- const dispatch = useDispatch() // never change the order of hooks called
46
+ const [id, _] = React.useState<number>(Math.floor(Math.random() * 10000));
47
+ const dispatch = useDispatch(); // never change the order of hooks called
36
48
 
37
49
  if (typeof props.cardName === "string") {
38
- cardName = props.cardName
50
+ cardName = props.cardName;
39
51
  } else {
40
52
  // lets fix it
41
- cardName = checkForAnonymousCard(props, id, dispatch)
53
+ cardName = checkForAnonymousCard(props, id, dispatch);
42
54
  }
43
55
  if (cardName === "") {
44
- logger.error("card name is not of type string", props.cardName)
45
- return ErrorCard(<div>Unknown type of cardName '{`${props.cardName}`}'</div>)
56
+ logger.error("card name is not of type string", props.cardName);
57
+ return ErrorCard(
58
+ <div>Unknown type of cardName '{`${props.cardName}`}'</div>
59
+ );
46
60
  }
47
61
 
48
- const [info, errCard] = getCardInfo(cardName)
62
+ const [info, errCard] = getCardInfo(cardName);
49
63
  if (errCard) {
50
- return ErrorCard(errCard)
64
+ return ErrorCard(errCard);
51
65
  }
52
66
  if (!info) {
53
- throw new Error("info is empty, should never happen")
67
+ throw new Error("info is empty, should never happen");
54
68
  }
55
- return GenericCard(cardName, props, info, id)
69
+ return GenericCard(cardName, props, info, id);
70
+ }
71
+
72
+ export function usePiReducer<S extends ReduxState, A extends ReduxAction>(
73
+ eventType: string,
74
+ mapper: ReduceF<S, A> // (state: S, action: A, dispatch: DispatchF) => S,
75
+ ) {
76
+ const store = useStore();
77
+ const id = useId();
78
+ useEffect(() => {
79
+ const r = (store as any).piReducer as PiReducer;
80
+ return r.register(eventType, mapper, 0, id);
81
+ });
56
82
  }
57
83
 
58
- function checkForAnonymousCard(props: any, id: number, dispatch: Dispatch<AnyAction>): string {
59
- const cardType = props.cardName?.cardType
84
+ function checkForAnonymousCard(
85
+ props: any,
86
+ id: number,
87
+ dispatch: Dispatch<AnyAction>
88
+ ): string {
89
+ const cardType = props.cardName?.cardType;
60
90
  if (!cardType) {
61
- return "" // not sure what that is
91
+ return ""; // not sure what that is
62
92
  }
63
93
  // looks like a potentially unregistered card
64
- let cardName: string
94
+ let cardName: string;
65
95
  if (props.parentCard) {
66
- cardName = `${props.parentCard}/${cardType.split('/').pop()}`
96
+ cardName = `${props.parentCard}/${cardType.split("/").pop()}`;
67
97
  } else {
68
- cardName = cardType
98
+ cardName = cardType;
69
99
  }
70
100
  if (props.cardKey) {
71
- cardName = `${cardName}#${props.cardKey}-${id}`
101
+ cardName = `${cardName}#${props.cardKey}-${id}`;
72
102
  } else {
73
- cardName = `${cardName}#${id}`
103
+ cardName = `${cardName}#${id}`;
74
104
  }
75
105
 
76
- const mapping = cardMappings[cardName]
77
- const parameters = props.cardName as PiCardDef
78
- const el = dispatch2registerReducer.find(([d, _]) => d === dispatch)
106
+ const mapping = cardMappings[cardName];
107
+ const parameters = props.cardName as PiCardDef;
108
+ const el = dispatch2registerReducer.find(([d, _]) => d === dispatch);
79
109
  if (!el) {
80
- logger.warn("unexpected missing mapping between dispatcher and reducerF")
81
- return ""
110
+ logger.warn("unexpected missing mapping between dispatcher and reducerF");
111
+ return "";
82
112
  }
83
- const regRed = el[1]
113
+ const regRed = el[1];
84
114
  if (mapping) {
85
115
  // looks like we already processed it
86
116
  // do update props as they may have changed
87
- _updateCardMapping(cardName, parameters, regRed, mapping)
117
+ _updateCardMapping(cardName, parameters, regRed, mapping);
88
118
  } else {
89
- _registerCard(cardName, parameters, regRed)
119
+ _registerCard(cardName, parameters, regRed);
90
120
  }
91
- return cardName
121
+ return cardName;
92
122
  }
93
123
 
94
- function GenericCard(cardName: string, props: CardProp, info: CardInfo, id: number) {
124
+ function GenericCard(
125
+ cardName: string,
126
+ props: CardProp,
127
+ info: CardInfo,
128
+ id: number
129
+ ) {
95
130
  const cardProps = useSelector<ReduxState, CompProps>(
96
131
  (s) => getCardProps(cardName, s, props),
97
- propEq,
98
- )
99
- const dispatch = useDispatch()
100
-
101
- const extCardProps = appendEventHandlers(info, cardProps, cardName, dispatch)
102
- extCardProps._cls = cls_f(cardName, info.mapping.cardType)
103
- return React.createElement(info.cardType.component, extCardProps, props.children)
132
+ propEq
133
+ );
134
+ const dispatch = useDispatch();
135
+
136
+ const extCardProps = appendEventHandlers(info, cardProps, cardName, dispatch);
137
+ extCardProps._cls = cls_f(cardName, info.mapping.cardType);
138
+ return React.createElement(
139
+ info.cardType.component,
140
+ extCardProps,
141
+ props.children
142
+ );
104
143
  }
105
144
 
106
- const EmptyCompProps = {} as CompProps
145
+ const EmptyCompProps = {} as CompProps;
107
146
 
108
147
  function ErrorCard(el: JSX.Element) {
109
148
  // Note: call the EXACT same hooks as 'GenericCard'
110
- useSelector<ReduxState, CompProps>((s) => EmptyCompProps, (a, b) => false)
111
- useDispatch()
112
- return el
149
+ useSelector<ReduxState, CompProps>(
150
+ (s) => EmptyCompProps,
151
+ (a, b) => false
152
+ );
153
+ useDispatch();
154
+ return el;
113
155
  }
114
156
 
115
-
116
157
  function getCardInfo(cardName: string): [CardInfo?, JSX.Element?] {
117
- const mapping = cardMappings[cardName]
158
+ const mapping = cardMappings[cardName];
118
159
  if (!mapping) {
119
- return [undefined, renderUnknownCard(cardName)]
160
+ return [undefined, renderUnknownCard(cardName)];
120
161
  }
121
- let cardType = cardTypes[mapping.cardType]
162
+ let cardType = cardTypes[mapping.cardType];
122
163
  if (!cardType) {
123
164
  if (framework) {
124
- cardType = cardTypes[`${framework}/${mapping.cardType}`]
165
+ cardType = cardTypes[`${framework}/${mapping.cardType}`];
125
166
  }
126
167
  if (!cardType) {
127
- return [undefined, renderUnknownCardType(mapping.cardType)]
168
+ return [undefined, renderUnknownCardType(mapping.cardType)];
128
169
  }
129
170
  }
130
- const info = { mapping, cardType }
131
- return [info, undefined]
171
+ const info = { mapping, cardType };
172
+ return [info, undefined];
132
173
  }
133
174
 
134
-
135
175
  function getCardProps(
136
176
  cardName: string,
137
177
  state: ReduxState,
138
- props: CardProp,
178
+ props: CardProp
139
179
  ): CompProps {
140
- const mapping = cardMappings[cardName]
180
+ const mapping = cardMappings[cardName];
141
181
  const ctxt: StateMapperContext<unknown> = {
142
182
  cardName,
143
183
  cardKey: props.cardKey,
144
184
  ctxtProps: props,
145
- }
185
+ };
146
186
  const init: CompProps = {
147
187
  cardName,
148
188
  cardKey: props.cardKey,
149
- }
189
+ };
150
190
  const cprops = Object.entries(mapping.props).reduce((p, [key, vf]) => {
151
- let v = vf
191
+ let v = vf;
152
192
  if (typeof vf === "function") {
153
- const f = vf as StateMapper<unknown, ReduxState, any>
193
+ const f = vf as StateMapper<unknown, ReduxState, any>;
154
194
  try {
155
- v = f(state, ctxt)
195
+ v = f(state, ctxt);
156
196
  } catch (ex) {
157
- logger.error(`while resolving property '${key}'`, ex)
197
+ logger.error(`while resolving property '${key}'`, ex);
158
198
  }
159
199
  } else if (key in props) {
160
- v = props[key]
200
+ v = props[key];
161
201
  }
162
- p[key] = v
163
- return p
164
- }, init)
165
- return cprops
202
+ p[key] = v;
203
+ return p;
204
+ }, init);
205
+ return cprops;
166
206
  }
167
207
 
168
208
  function cls_f(
169
209
  cardName: string,
170
210
  cardComp: string,
171
- prefix: string = "pi",
172
- ): (nodeName: string | string[], styles?: CSSModuleClasses) => string {
173
- const cn = cardName.replaceAll(/[/:]/g, "_")
174
- const cp = cardComp.replaceAll(/[/:]/g, "_")
175
- return (nodeName: string | string[], styles?: CSSModuleClasses): string => {
176
- const na: string[] = typeof nodeName === "string" ? [nodeName] : nodeName
177
- const ca = [] as string[]
211
+ prefix: string = "pi"
212
+ ): (nodeName: string | string[], className?: string) => string {
213
+ const cn = cardName.replaceAll(/[/:]/g, "_");
214
+ const cp = cardComp.replaceAll(/[/:]/g, "_");
215
+ return (nodeName: string | string[], className?: string): string => {
216
+ const na: string[] = typeof nodeName === "string" ? [nodeName] : nodeName;
217
+ const ca = [] as string[];
218
+ if (className) {
219
+ ca.push(className);
220
+ }
178
221
  na.forEach((n) => {
179
- const s = styles?.[n]
180
- if (s) ca.push(s)
181
-
182
- const nn = n.replaceAll(/[/:]/g, "_")
183
- ca.push(`${prefix}-${cn}-${nn}`)
184
- ca.push(`${prefix}-${cp}-${nn}`)
185
- })
186
- return ca.join(" ")
187
- }
222
+ const nn = n.replaceAll(/[/:]/g, "_");
223
+ ca.push(`${prefix}-${cn}-${nn}`);
224
+ ca.push(`${prefix}-${cp}-${nn}`);
225
+ });
226
+ return ca.join(" ");
227
+ };
188
228
  }
189
229
 
190
230
  function propEq(oldP: CompProps, newP: CompProps): boolean {
191
- let isUnchanged = equal(oldP, newP)
231
+ let isUnchanged = equal(oldP, newP);
192
232
  // for (const [k, v] of Object.entries(newP)) {
193
233
  // const ov = oldP[k]
194
234
  // if (ov !== v) {
@@ -199,179 +239,190 @@ function propEq(oldP: CompProps, newP: CompProps): boolean {
199
239
  // }
200
240
  // }
201
241
  // }
202
- RegisterCardState.changed(newP.cardName, isUnchanged, newP)
203
- return isUnchanged
242
+ RegisterCardState.changed(newP.cardName, isUnchanged, newP);
243
+ return isUnchanged;
204
244
  }
205
245
 
206
246
  function appendEventHandlers(
207
247
  info: CardInfo,
208
248
  cardProps: CompProps,
209
249
  cardName: string,
210
- dispatch: Dispatch<Action>,
250
+ dispatch: Dispatch<Action>
211
251
  ): CompProps {
212
- RegisterCardState.props(cardName, cardProps, dispatch)
252
+ RegisterCardState.props(cardName, cardProps, dispatch);
213
253
  const cp: CompProps = {
214
254
  ...cardProps,
215
- _dispatch: (a: AnyAction) => dispatch(a)
216
- }
255
+ _dispatch: (a: AnyAction) => dispatch(a),
256
+ };
217
257
 
218
- const events = info.cardType?.events
258
+ const events = info.cardType?.events;
219
259
  if (!events) {
220
- return cp
260
+ return cp;
221
261
  }
222
- const eventMappers = info.mapping.eventMappers
262
+ const eventMappers = info.mapping.eventMappers;
223
263
  Object.entries(events).forEach(([name, actionType]) => {
224
- const m = eventMappers[name]
264
+ const m = eventMappers[name];
225
265
  if (m) {
226
- logger.debug("setup mapper", cardName)
266
+ logger.debug("setup mapper", cardName);
227
267
  cp[name] = (a: AnyAction) => {
228
- a.cardID = cardName
229
- const a2 = m(a)
230
- if (a2) dispatch(a2)
231
- }
268
+ a.cardID = cardName;
269
+ const a2 = m(a);
270
+ if (a2) dispatch(a2);
271
+ };
232
272
  } else {
233
273
  cp[name] = (a: AnyAction) => {
234
- a.type = actionType
235
- a.cardID = cardName
236
- dispatch(a)
237
- }
274
+ a.type = actionType;
275
+ a.cardID = cardName;
276
+ dispatch(a);
277
+ };
238
278
  }
239
- })
240
- return cp
279
+ });
280
+ return cp;
241
281
  }
242
282
 
243
283
  function renderUnknownCard(cardName: string): JSX.Element {
244
- return <div>Unknown card '{cardName}'</div>
284
+ return <div>Unknown card '{cardName}'</div>;
245
285
  }
246
286
 
247
287
  function renderUnknownCardType(cardType: string): JSX.Element {
248
- return <div>Unknown card type '{cardType}'</div>
288
+ return <div>Unknown card type '{cardType}'</div>;
249
289
  }
250
290
 
251
291
  // Adding card state to redux state for debugging
252
292
 
253
- export const UPDATE_STATE_ACTION = "pi/card/update_state"
293
+ export const UPDATE_STATE_ACTION = "pi/card/update_state";
254
294
 
255
295
  type CardState = {
256
296
  props: (
257
297
  cardName: string,
258
298
  cardProps: CompProps,
259
- dispatch: Dispatch<Action>,
260
- ) => void
261
- changed: (cardName: string, isUnchanged: boolean, props: CompProps) => void
262
- reducer: (state: ReduxState, action: Action) => ReduxState
263
- }
299
+ dispatch: Dispatch<Action>
300
+ ) => void;
301
+ changed: (cardName: string, isUnchanged: boolean, props: CompProps) => void;
302
+ reducer: (state: ReduxState, action: Action) => ReduxState;
303
+ };
264
304
 
265
- export const RegisterCardState = createCardState()
305
+ export const RegisterCardState = createCardState();
266
306
 
267
307
  function createCardState(): CardState {
268
308
  type S = {
269
- cardProps?: CompProps
270
- changedAt: number
271
- reportedAt: number
272
- }
273
- const s: { [name: string]: S } = {}
274
- let dispatch: Dispatch<Action>
275
- let timer: number
276
- let lastReport = 0
309
+ cardProps?: CompProps;
310
+ changedAt: number;
311
+ reportedAt: number;
312
+ };
313
+ const s: { [name: string]: S } = {};
314
+ let dispatch: Dispatch<Action>;
315
+ let timer: number;
316
+ let lastReport = 0;
277
317
 
278
318
  // const timer
279
319
  const getS = (cardName: string, props: CompProps): S => {
280
- const name = cardName
281
- let e = s[name]
320
+ const name = cardName;
321
+ let e = s[name];
282
322
  if (!e) {
283
- const ts = Date.now()
323
+ const ts = Date.now();
284
324
  e = {
285
325
  changedAt: ts,
286
326
  reportedAt: ts,
287
- } as S
288
- s[name] = e
289
- resetTimer()
327
+ } as S;
328
+ s[name] = e;
329
+ resetTimer();
290
330
  }
291
- return e
292
- }
331
+ return e;
332
+ };
293
333
  const resetTimer = () => {
294
334
  if (timer) {
295
- clearTimeout(timer)
335
+ clearTimeout(timer);
296
336
  }
297
337
  timer = window.setTimeout(() => {
298
338
  //logger.debug("... timer went off") // , s, dispatch)
299
339
  if (dispatch) {
300
- const changed = Object.values(s).filter((s) => s.changedAt > lastReport)
340
+ const changed = Object.values(s).filter(
341
+ (s) => s.changedAt > lastReport
342
+ );
301
343
  if (changed.length > 0) {
302
- clearTimeout(timer) // just in case
303
- dispatch({ type: UPDATE_STATE_ACTION })
344
+ clearTimeout(timer); // just in case
345
+ dispatch({ type: UPDATE_STATE_ACTION });
304
346
  }
305
347
  }
306
- }, 1000)
307
- }
348
+ }, 1000);
349
+ };
308
350
  const props = (
309
351
  cardName: string,
310
352
  cardProps: CompProps,
311
- _dispatch: Dispatch<Action>,
353
+ _dispatch: Dispatch<Action>
312
354
  ) => {
313
- const e = getS(cardName, cardProps)
314
- e.cardProps = cardProps
315
- dispatch = _dispatch
316
- }
317
- const changed = (cardName: string, isUnchanged: boolean, props: CompProps) => {
318
- const e = getS(cardName, props)
319
- e.reportedAt = Date.now()
355
+ const e = getS(cardName, cardProps);
356
+ e.cardProps = cardProps;
357
+ dispatch = _dispatch;
358
+ };
359
+ const changed = (
360
+ cardName: string,
361
+ isUnchanged: boolean,
362
+ props: CompProps
363
+ ) => {
364
+ const e = getS(cardName, props);
365
+ e.reportedAt = Date.now();
320
366
  if (!isUnchanged) {
321
- logger.debug("card has changed:", cardName)
322
- e.changedAt = Date.now()
323
- resetTimer()
367
+ logger.debug("card has changed:", cardName);
368
+ e.changedAt = Date.now();
369
+ resetTimer();
324
370
  }
325
- }
371
+ };
326
372
  const reducer = (state: ReduxState): ReduxState => {
327
373
  const pi = Object.values(s)
328
374
  .filter((s) => s.reportedAt > lastReport)
329
375
  .reduce((p, s) => {
330
- const cname = s.cardProps?.cardName
376
+ const cname = s.cardProps?.cardName;
331
377
  if (!cname) {
332
- logger.warn("Unexpected missing card name", s)
333
- return p
378
+ logger.warn("Unexpected missing card name", s);
379
+ return p;
334
380
  }
335
- const name = cname
336
- const props = copySafeProps(s.cardProps || {})
337
- delete props.cardName
338
- delete props._cls
339
- p[name] = props
340
- return p
341
- }, {} as { [k: string]: any })
342
- state.pihanga = pi
343
- lastReport = Date.now()
344
- return state
345
- }
346
- return { props, changed, reducer }
381
+ const name = cname;
382
+ const props = copySafeProps(s.cardProps || {});
383
+ delete props.cardName;
384
+ delete props._cls;
385
+ p[name] = props;
386
+ return p;
387
+ }, {} as { [k: string]: any });
388
+ state.pihanga = pi;
389
+ lastReport = Date.now();
390
+ return state;
391
+ };
392
+ return { props, changed, reducer };
347
393
  }
348
394
 
349
395
  function copySafeProps(props: CompProps): CompProps {
350
396
  return Object.entries(props).reduce((p, [k, v]) => {
351
397
  // const ok = (typeof v === 'undefined' || typeof v === 'string' || typeof v === 'boolean' || typeof v === 'number' || Array.isArray(v));
352
- const sv = makeSafe(v)
353
- p[k] = sv
354
- return p
355
- }, {} as CompProps)
398
+ const sv = makeSafe(v);
399
+ p[k] = sv;
400
+ return p;
401
+ }, {} as CompProps);
356
402
  }
357
403
 
358
404
  function makeSafe(v: any): any {
359
- const t = typeof v
360
- if (t === 'undefined' || t === 'string' || t === 'boolean' || t === 'number') {
361
- return v
405
+ const t = typeof v;
406
+ if (
407
+ t === "undefined" ||
408
+ t === "string" ||
409
+ t === "boolean" ||
410
+ t === "number"
411
+ ) {
412
+ return v;
362
413
  }
363
- if (t === 'function') {
364
- return "f(...)"
414
+ if (t === "function") {
415
+ return "f(...)";
365
416
  }
366
417
  if (Array.isArray(v)) {
367
- return v.map(makeSafe)
418
+ return v.map(makeSafe);
368
419
  }
369
- if (t === 'object') {
420
+ if (t === "object") {
370
421
  return Object.entries(v).reduce((p, [k, v]) => {
371
- p[k] = makeSafe(v)
372
- return p
373
- }, {} as { [k: string]: any })
422
+ p[k] = makeSafe(v);
423
+ return p;
424
+ }, {} as { [k: string]: any });
374
425
  }
375
- logger.warn(">>> reject", v, typeof v)
376
- return "..."
426
+ logger.warn(">>> reject", v, typeof v);
427
+ return "...";
377
428
  }
package/src/index.ts CHANGED
@@ -52,7 +52,7 @@ export type {
52
52
  WindowProps,
53
53
  } from "./types"
54
54
  export { registerActions, actionTypesToEvents, createOnAction } from "./redux"
55
- export { Card } from "./card"
55
+ export { Card, usePiReducer } from "./card"
56
56
  export { memo, createCardDeclaration, isCardRef } from "./register_cards"
57
57
  export { getLogger } from "./logger"
58
58
  export type { PiCardProps, PiCardRef } from "./types"
@@ -166,6 +166,10 @@ export function start<S extends Partial<ReduxState>>(
166
166
  }
167
167
  })
168
168
  })
169
+ // make pihanga's reducer interface available to cards
170
+ const anyStore: any = store
171
+ anyStore.piReducer = piReducer
172
+
169
173
  dispatchF = store.dispatch
170
174
 
171
175
  const card = registerCard(piReducer.register, dispatchF)