@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/reducer.ts CHANGED
@@ -2,6 +2,7 @@ import { Action, Reducer } from "@reduxjs/toolkit"
2
2
  import {
3
3
  DispatchF,
4
4
  PiReducer,
5
+ PiReducerCancelF,
5
6
  PiRegisterOneShotReducerF,
6
7
  PiRegisterReducerF,
7
8
  ReduceF,
@@ -69,8 +70,8 @@ export function createReducer(
69
70
  mapper: ReduceF<S, A>,
70
71
  priority: number = 0,
71
72
  key: string | undefined = undefined
72
- ): void => {
73
- addReducer(eventType, { mapperMany: mapper, priority, key })
73
+ ): PiReducerCancelF => {
74
+ return addReducer(eventType, { mapperMany: mapper, priority, key })
74
75
  }
75
76
 
76
77
  const registerOneShot: PiRegisterOneShotReducerF = <
@@ -81,20 +82,19 @@ export function createReducer(
81
82
  mapper: (state: S, action: A, dispatch: DispatchF) => boolean,
82
83
  priority: number = 0,
83
84
  key: string | undefined = undefined
84
- ): void => {
85
- addReducer(eventType, { mapperOnce: mapper, priority, key })
85
+ ): PiReducerCancelF => {
86
+ return addReducer(eventType, { mapperOnce: mapper, priority, key })
86
87
  }
87
88
 
89
+ const nonCancelF = () => {}
90
+
88
91
  function addReducer<S extends ReduxState, A extends ReduxAction>(
89
92
  eventType: string,
90
93
  reducerDef: ReducerDef<S, A>,
91
- // mappings: { [k: string]: ReducerDef<ReduxState, Action>[] }
92
- ) {
94
+ ): PiReducerCancelF {
93
95
  let m = mappings[eventType] || []
94
- if (reducerDef.key) {
95
- // remove reducer with same key - if there is one
96
- m = m.filter((r) => r.key !== reducerDef.key)
97
- }
96
+ const key = reducerDef.key
97
+ m = removeReducer(key, m)
98
98
  m.push(reducerDef as any as ReducerDef<ReduxState, Action<any>>) // keep typing happy
99
99
  m.sort((a, b) => (b.priority || 0) - (a.priority || 0))
100
100
  mappings[eventType] = m
@@ -109,6 +109,10 @@ export function createReducer(
109
109
  logger.warn(err.message)
110
110
  }
111
111
  StackTrace.get().then(callback).catch(errback)
112
+ return key ? () => {
113
+ let m = mappings[eventType] || []
114
+ mappings[eventType] = removeReducer(key, m)
115
+ } : nonCancelF
112
116
  }
113
117
 
114
118
  const piReducer: PiReducer = {
@@ -121,6 +125,17 @@ export function createReducer(
121
125
  return [reducer, piReducer]
122
126
  }
123
127
 
128
+ function removeReducer(
129
+ key: string | undefined,
130
+ m: ReducerDef<ReduxState, Action>[]
131
+ ) {
132
+ if (key) {
133
+ return m.filter((r) => r.key !== key)
134
+ } else {
135
+ return m
136
+ }
137
+ }
138
+
124
139
  function _reduce(
125
140
  ra: ReducerDef<ReduxState, Action>[],
126
141
  draft: ReduxState,
@@ -1,5 +1,5 @@
1
- import equal from "deep-equal"
2
- import { getLogger } from "./logger"
1
+ import equal from "deep-equal";
2
+ import { getLogger } from "./logger";
3
3
  import {
4
4
  CSSModuleClasses,
5
5
  CardAction,
@@ -17,200 +17,221 @@ import {
17
17
  RegisterCardF,
18
18
  StateMapper,
19
19
  StateMapperContext,
20
- } from "./types"
21
- import { Action, AnyAction, Dispatch } from "@reduxjs/toolkit"
20
+ } from "./types";
21
+ import { Action, AnyAction, Dispatch } from "@reduxjs/toolkit";
22
22
 
23
- const logger = getLogger("card-register")
23
+ const logger = getLogger("card-register");
24
24
 
25
25
  export function isCardRef(p: any): boolean {
26
- return (typeof p === "object" && p.cardType !== undefined)
26
+ return typeof p === "object" && p.cardType !== undefined;
27
27
  }
28
28
 
29
29
  export type Mapping = {
30
- cardType: string
31
- props: { [k: string]: unknown }
32
- eventMappers: { [k: string]: (ev: Action) => Action | null }
33
- cardEvents: { [key: string]: string }
34
- parameters: PiCardDef // original
35
- }
30
+ cardType: string;
31
+ props: { [k: string]: unknown };
32
+ eventMappers: { [k: string]: (ev: Action) => Action | null };
33
+ cardEvents: { [key: string]: string };
34
+ parameters: PiCardDef; // original
35
+ };
36
36
 
37
37
  export type MetaCard = {
38
- type: string
39
- registerCard: RegisterCardF
40
- mapper: MetaCardMapperF
41
- events?: { [key: string]: string }
42
- }
38
+ type: string;
39
+ registerCard: RegisterCardF;
40
+ mapper: MetaCardMapperF;
41
+ events?: { [key: string]: string };
42
+ };
43
43
 
44
- export const cardTypes: { [k: string]: PiRegisterComponent } = {}
45
- export const metacardTypes: { [k: string]: MetaCard } = {}
46
-
47
- export let framework: string // name of active UI framework
48
- export const cardMappings: { [k: string]: Mapping } = {}
49
- export const dispatch2registerReducer: [React.Dispatch<any>, PiRegisterReducerF][] = []
44
+ export const cardTypes: { [k: string]: PiRegisterComponent } = {};
45
+ export const metacardTypes: { [k: string]: MetaCard } = {};
50
46
 
47
+ export let framework: string; // name of active UI framework
48
+ export const cardMappings: { [k: string]: Mapping } = {};
49
+ export const dispatch2registerReducer: [
50
+ React.Dispatch<any>,
51
+ PiRegisterReducerF
52
+ ][] = [];
51
53
 
52
54
  export function registerCardComponent(card: PiRegisterComponent): void {
53
55
  if (cardTypes[card.name]) {
54
- logger.warn(`Overwriting definition for card type "${card.name}"`)
56
+ logger.warn(`Overwriting definition for card type "${card.name}"`);
55
57
  }
56
- logger.info(`Register card type "${card.name}"`)
58
+ logger.info(`Register card type "${card.name}"`);
57
59
  if (!framework) {
58
60
  // set default framework
59
- const na = card.name.split("/")
61
+ const na = card.name.split("/");
60
62
  if (na.length >= 2) {
61
- framework = na[0]
62
- logger.info(`Setting UI framework to "${framework}"`)
63
+ framework = na[0];
64
+ logger.info(`Setting UI framework to "${framework}"`);
63
65
  }
64
66
  }
65
- cardTypes[card.name] = card
67
+ cardTypes[card.name] = card;
66
68
  }
67
69
 
68
70
  export function registerMetacard(registerCard: RegisterCardF) {
69
71
  function f<C>(declaration: PiRegisterMetaCard) {
70
- const { type, mapper, events } = declaration
72
+ const { type, mapper, events } = declaration;
71
73
  if (metacardTypes[type]) {
72
- logger.warn(`Overwriting definition for meta card type "${type}"`)
74
+ logger.warn(`Overwriting definition for meta card type "${type}"`);
73
75
  }
74
- logger.info(`Register meta card type "${type}"`)
75
- metacardTypes[type] = { type, registerCard, mapper, events }
76
+ logger.info(`Register meta card type "${type}"`);
77
+ metacardTypes[type] = { type, registerCard, mapper, events };
76
78
  }
77
- return f
79
+ return f;
78
80
  }
79
81
 
80
-
81
82
  export function registerCard(
82
83
  registerReducer: PiRegisterReducerF,
83
- dispatchF: React.Dispatch<any>,
84
+ dispatchF: React.Dispatch<any>
84
85
  ) {
85
86
  // to be used by dynamically registered cards
86
- dispatch2registerReducer.push([dispatchF, registerReducer])
87
+ dispatch2registerReducer.push([dispatchF, registerReducer]);
87
88
  return (name: string, parameters: PiCardDef): PiCardRef => {
88
- return _registerCard(name, parameters, registerReducer)
89
- }
89
+ return _registerCard(name, parameters, registerReducer);
90
+ };
90
91
  }
91
92
 
92
93
  export function updateOrRegisterCard(
93
94
  registerReducer: PiRegisterReducerF,
94
- dispatchF: React.Dispatch<any>,
95
+ dispatchF: React.Dispatch<any>
95
96
  ) {
96
97
  // to be used by dynamically registered cards
97
- dispatch2registerReducer.push([dispatchF, registerReducer])
98
- return (name: string, parameters: { [key: string]: GenericCardParameterT }): PiCardRef => {
99
- return _updateCard(name, parameters, registerReducer)
100
- }
98
+ dispatch2registerReducer.push([dispatchF, registerReducer]);
99
+ return (
100
+ name: string,
101
+ parameters: { [key: string]: GenericCardParameterT }
102
+ ): PiCardRef => {
103
+ return _updateCard(name, parameters, registerReducer);
104
+ };
101
105
  }
102
106
 
103
107
  export function _registerCard(
104
108
  name: string,
105
109
  parameters: PiCardDef,
106
110
  registerReducer: PiRegisterReducerF,
107
- overrideEvents?: { [key: string]: string },
111
+ overrideEvents?: { [key: string]: string }
108
112
  ): PiCardRef {
109
113
  if (cardMappings[name]) {
110
- logger.warn(`Overwriting definition for card "${name}"`)
114
+ logger.warn(`Overwriting definition for card "${name}"`);
111
115
  }
112
- let cardType = cardTypes[parameters.cardType]
116
+ let cardType = cardTypes[parameters.cardType];
113
117
  if (!cardType) {
114
118
  if (framework) {
115
- cardType = cardTypes[`${framework}/${parameters.cardType}`]
119
+ cardType = cardTypes[`${framework}/${parameters.cardType}`];
116
120
  }
117
121
  if (!cardType) {
118
122
  // maybe it's a metadata card
119
123
  if (_registerMetadataCard(name, parameters, registerReducer)) {
120
- return name
124
+ return name;
121
125
  }
122
- logger.warn("unknown card type", parameters.cardType)
123
- return name
126
+ logger.warn("unknown card type", parameters.cardType);
127
+ return name;
124
128
  }
125
129
  }
126
130
 
127
- const events = overrideEvents || cardType.events || {}
128
- _createCardMapping(name, parameters, registerReducer, events)
129
- return name
131
+ const events = overrideEvents || cardType.events || {};
132
+ _createCardMapping(name, parameters, registerReducer, events);
133
+ return name;
130
134
  }
131
135
 
132
136
  export function _updateCard(
133
137
  name: string,
134
138
  parameters: { [key: string]: GenericCardParameterT },
135
139
  registerReducer: PiRegisterReducerF,
136
- overrideEvents?: { [key: string]: string },
140
+ overrideEvents?: { [key: string]: string }
137
141
  ): PiCardRef {
138
- const mappings = cardMappings[name]
142
+ const mappings = cardMappings[name];
139
143
  if (!mappings) {
140
144
  // first time
141
145
  if (!parameters.cardType) {
142
- logger.warn("missing 'cardType'", name)
143
- return name
146
+ logger.warn("missing 'cardType'", name);
147
+ return name;
144
148
  }
145
- const p: any = parameters
146
- return _registerCard(name, p, registerReducer, overrideEvents)
149
+ const p: any = parameters;
150
+ return _registerCard(name, p, registerReducer, overrideEvents);
147
151
  }
148
152
 
149
- const p = { ...mappings.parameters, ...parameters }
150
- _createCardMapping(name, p, registerReducer, mappings.cardEvents)
151
- return name
152
-
153
+ const p = { ...mappings.parameters, ...parameters };
154
+ _createCardMapping(name, p, registerReducer, mappings.cardEvents);
155
+ return name;
153
156
  }
154
157
 
155
158
  export function _createCardMapping(
156
159
  name: string,
157
160
  parameters: PiCardDef,
158
161
  registerReducer: PiRegisterReducerF,
159
- cardEvents: { [key: string]: string },
162
+ cardEvents: { [key: string]: string }
160
163
  ) {
161
- const props = {} as { [k: string]: unknown }
162
- const eventMappers = {} as { [k: string]: (ev: Action) => Action }
164
+ const props = {} as { [k: string]: unknown };
165
+ const eventMappers = {} as { [k: string]: (ev: Action) => Action };
163
166
 
164
167
  Object.entries(parameters).forEach(([k, v]) => {
165
- if (k === "cardType") return
168
+ if (k === "cardType") return;
166
169
  if (typeof v === "object") {
167
- const cd = v as PiCardDef // speculative
170
+ const cd = v as PiCardDef; // speculative
168
171
  if (cd.cardType) {
169
- const cardName = `${name}/${k}`
170
- v = _registerCard(cardName, cd, registerReducer)
172
+ const cardName = `${name}/${k}`;
173
+ v = _registerCard(cardName, cd, registerReducer);
171
174
  }
172
175
  }
173
176
  if (
174
177
  k.startsWith("on") &&
175
- processEventParameter(k, v, cardEvents, eventMappers, registerReducer, name)
178
+ processEventParameter(
179
+ k,
180
+ v,
181
+ cardEvents,
182
+ eventMappers,
183
+ registerReducer,
184
+ name
185
+ )
176
186
  ) {
177
- return
187
+ return;
178
188
  }
179
- props[k] = v
180
- })
181
- cardMappings[name] = { cardType: parameters.cardType, props, eventMappers, cardEvents, parameters }
189
+ props[k] = v;
190
+ });
191
+ cardMappings[name] = {
192
+ cardType: parameters.cardType,
193
+ props,
194
+ eventMappers,
195
+ cardEvents,
196
+ parameters,
197
+ };
182
198
  }
183
199
 
184
200
  export function _updateCardMapping(
185
201
  name: string,
186
202
  parameters: PiCardDef,
187
203
  registerReducer: PiRegisterReducerF,
188
- mappings: Mapping,
204
+ mappings: Mapping
189
205
  ) {
190
- return _createCardMapping(name, parameters, registerReducer, mappings.cardEvents)
206
+ return _createCardMapping(
207
+ name,
208
+ parameters,
209
+ registerReducer,
210
+ mappings.cardEvents
211
+ );
191
212
  }
192
213
 
193
214
  function _registerMetadataCard(
194
215
  metaName: string,
195
216
  parameters: PiCardDef,
196
- registerReducer: PiRegisterReducerF,
217
+ registerReducer: PiRegisterReducerF
197
218
  ): boolean {
198
- let mc = metacardTypes[parameters.cardType]
219
+ let mc = metacardTypes[parameters.cardType];
199
220
  if (!mc) {
200
221
  if (framework) {
201
- mc = metacardTypes[`${framework}/${parameters.cardType}`]
222
+ mc = metacardTypes[`${framework}/${parameters.cardType}`];
202
223
  }
203
224
  if (!mc) {
204
- return false
225
+ return false;
205
226
  }
206
227
  }
207
228
  function registerCard(name: string, parameters: PiCardDef): PiCardRef {
208
- const n = `${metaName}/${name}`
209
- return mc.registerCard(n, parameters)
229
+ const n = `${metaName}/${name}`;
230
+ return mc.registerCard(n, parameters);
210
231
  }
211
- const top = mc.mapper(metaName, parameters, registerCard)
212
- _registerCard(metaName, top, registerReducer, mc.events)
213
- return true
232
+ const top = mc.mapper(metaName, parameters, registerCard);
233
+ _registerCard(metaName, top, registerReducer, mc.events);
234
+ return true;
214
235
  }
215
236
 
216
237
  // NOT IMPLEMENTED YET
@@ -238,12 +259,12 @@ function _registerMetadataCard(
238
259
  // }
239
260
 
240
261
  export function createCardDeclaration<Props = {}, Events = {}>(
241
- cardType: string,
262
+ cardType: string
242
263
  ): <S extends ReduxState>(p: PiMapProps<Props, S, Events>) => PiCardDef {
243
264
  return (p) => ({
244
265
  ...p,
245
266
  cardType,
246
- })
267
+ });
247
268
  }
248
269
 
249
270
  function processEventParameter(
@@ -252,57 +273,96 @@ function processEventParameter(
252
273
  events: { [key: string]: string },
253
274
  eventMappers: { [k: string]: (ev: Action) => Action },
254
275
  registerReducer: PiRegisterReducerF,
255
- cardName: string,
276
+ cardName: string
256
277
  ): boolean {
257
278
  const eva = Object.entries(events).find(([n, _]) => {
258
- return propName === n || propName === `${n}Mapper`
259
- })
279
+ return propName === n || propName === `${n}Mapper`;
280
+ });
260
281
  if (!eva) {
261
282
  logger.warn(
262
- `encountered property '${propName}' for card '${cardName}' which looks like an even but is not defined`,
263
- )
264
- return false
283
+ `encountered property '${propName}' for card '${cardName}' which looks like an even but is not defined`
284
+ );
285
+ return false;
265
286
  }
266
287
 
267
- const [evName, actionType] = eva
288
+ const [evName, actionType] = eva;
268
289
  if (propName === evName) {
269
- const r = value as (state: ReduxState, action: CardAction, dispatch: DispatchF) => ReduxState
270
- registerReducer(actionType, (s, a, d) => {
271
- const ca = a as CardAction
272
- if (ca.cardID === cardName) {
273
- s = r(s, ca, d)
274
- }
275
- return s
276
- }, 0, `${cardName}|${propName}`)
290
+ const r = value as (
291
+ state: ReduxState,
292
+ action: CardAction,
293
+ dispatch: DispatchF
294
+ ) => ReduxState;
295
+ registerReducer(
296
+ actionType,
297
+ (s, a, d) => {
298
+ const ca = a as CardAction;
299
+ if (ca.cardID === cardName) {
300
+ s = r(s, ca, d);
301
+ }
302
+ return s;
303
+ },
304
+ 0,
305
+ `${cardName}|${propName}`
306
+ );
277
307
  }
278
308
  if (propName === `${evName}Mapper`) {
279
- logger.debug("processEventParameter", cardName)
309
+ logger.debug("processEventParameter", cardName);
280
310
 
281
- const m = value as (ev: Action) => Action
282
- eventMappers[evName] = m
311
+ const m = value as (ev: Action) => Action;
312
+ eventMappers[evName] = m;
283
313
  }
284
- return true
314
+ return true;
285
315
  }
286
316
 
287
- export function memo<P, T, S extends ReduxState, C>(
317
+ /**
318
+ * Memorises a calculation as long as a certain "part"
319
+ * of the ReduxState is not changing. The `filterF` function
320
+ * is always called with the current ReduxState.
321
+ *
322
+ * If `memo` has been called previously, the return value of
323
+ * `filterF` is compared with the last previous call. If it has
324
+ * changed, `mapF` is called. Both return values are internally
325
+ * stored and the most recent result of `mapF` is returned.
326
+ *
327
+ * If `filterF` is returning the same result as in the most recent
328
+ * call, `mapF` will NOT be called, but the result of the most recent
329
+ * `mapF` is returned.
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * options: memo<CatalogItemtT[], SelectOptionT[], AppState>(
334
+ * (s) => s.catalog,
335
+ * (items) => items.map(...),
336
+ * ),
337
+ * ```
338
+ *
339
+ * @param filterF Function to return the part [P] of the ReduxState of interests.
340
+ * @param mapF Function to map the result of `mapF` to the return value of type T
341
+ * @typeparam P The type of a section of the ReduxState S.
342
+ * @typeparam T The return type of this function call.
343
+ * @typeparam S The type of the ReduxState which is being passed to `filterF`.
344
+ * @typeparam C They type of specific context this card is being used StateMapperContext<C> (Primarily relevant for tables)
345
+ * @returns The result of `mapF` if the result of `filterF` has changed, otherwise returns a previous result of `mapF`
346
+ */
347
+ export function memo<P, T, S extends ReduxState, C = any>(
288
348
  filterF: (state: S, context: StateMapperContext<C>) => P,
289
- mapperF: (partial: P, context: StateMapperContext<C>, state: S) => T,
349
+ mapperF: (partial: P, context: StateMapperContext<C>, state: S) => T
290
350
  ): (state: S, context: StateMapperContext<C>) => T {
291
- const lastFilter: { [k: string]: P } = {}
292
- const lastValue: { [k: string]: T } = {}
293
- const isNotFirst: { [k: string]: boolean } = {}
351
+ const lastFilter: { [k: string]: P } = {};
352
+ const lastValue: { [k: string]: T } = {};
353
+ const isNotFirst: { [k: string]: boolean } = {};
294
354
 
295
355
  return (state: S, context: StateMapperContext<C>): T => {
296
- const k = context.cardKey || "-"
297
- const fv = filterF(state, context)
356
+ const k = context.cardKey || "-";
357
+ const fv = filterF(state, context);
298
358
  if (isNotFirst[k] && equal(fv, lastFilter[k])) {
299
359
  // nothing changed
300
- return lastValue[k]
360
+ return lastValue[k];
301
361
  }
302
- lastFilter[k] = fv
303
- const v = mapperF(fv, context, state)
304
- lastValue[k] = v
305
- isNotFirst[k] = true
306
- return v
307
- }
308
- }
362
+ lastFilter[k] = fv;
363
+ const v = mapperF(fv, context, state);
364
+ lastValue[k] = v;
365
+ isNotFirst[k] = true;
366
+ return v;
367
+ };
368
+ }