@pihanga2/core 0.2.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.
- package/dist/card.d.ts +22 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +493 -0
- package/dist/card.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +12 -0
- package/dist/logger.js.map +1 -0
- package/dist/reducer.d.ts +5 -0
- package/dist/reducer.d.ts.map +1 -0
- package/dist/reducer.js +77 -0
- package/dist/reducer.js.map +1 -0
- package/dist/redux.d.ts +31 -0
- package/dist/redux.d.ts.map +1 -0
- package/dist/redux.js +50 -0
- package/dist/redux.js.map +1 -0
- package/dist/rest/delete.d.ts +4 -0
- package/dist/rest/delete.d.ts.map +1 -0
- package/dist/rest/delete.js +23 -0
- package/dist/rest/delete.js.map +1 -0
- package/dist/rest/enums.d.ts +6 -0
- package/dist/rest/enums.d.ts.map +1 -0
- package/dist/rest/enums.js +9 -0
- package/dist/rest/enums.js.map +1 -0
- package/dist/rest/get.d.ts +10 -0
- package/dist/rest/get.d.ts.map +1 -0
- package/dist/rest/get.js +31 -0
- package/dist/rest/get.js.map +1 -0
- package/dist/rest/index.d.ts +6 -0
- package/dist/rest/index.d.ts.map +1 -0
- package/dist/rest/index.js +6 -0
- package/dist/rest/index.js.map +1 -0
- package/dist/rest/postPutPatch.d.ts +6 -0
- package/dist/rest/postPutPatch.d.ts.map +1 -0
- package/dist/rest/postPutPatch.js +72 -0
- package/dist/rest/postPutPatch.js.map +1 -0
- package/dist/rest/types.d.ts +93 -0
- package/dist/rest/types.d.ts.map +1 -0
- package/dist/rest/types.js +38 -0
- package/dist/rest/types.js.map +1 -0
- package/dist/rest/utils.d.ts +8 -0
- package/dist/rest/utils.d.ts.map +1 -0
- package/dist/rest/utils.js +212 -0
- package/dist/rest/utils.js.map +1 -0
- package/dist/router.d.ts +25 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +136 -0
- package/dist/router.js.map +1 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +18 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +71 -0
- package/src/card.tsx +628 -0
- package/src/index.tsx +193 -0
- package/src/logger.ts +13 -0
- package/src/reducer.ts +127 -0
- package/src/redux.ts +64 -0
- package/src/rest/delete.ts +41 -0
- package/src/rest/enums.ts +8 -0
- package/src/rest/get.ts +50 -0
- package/src/rest/index.ts +7 -0
- package/src/rest/postPutPatch.ts +118 -0
- package/src/rest/types.ts +135 -0
- package/src/rest/utils.ts +265 -0
- package/src/router.ts +171 -0
- package/src/store.ts +19 -0
- package/src/types.ts +146 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pihanga2/core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "max.ott@data61.csiro.au",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pihanga",
|
|
15
|
+
"module",
|
|
16
|
+
"library"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"docs": "yarn typedoc",
|
|
20
|
+
"build": "tsc --build && typedoc",
|
|
21
|
+
"build:watch": "tsc --build --watch && typedoc",
|
|
22
|
+
"prettier": "prettier --write .",
|
|
23
|
+
"test:coverage": "vitest run --coverage",
|
|
24
|
+
"test:run": "vitest run",
|
|
25
|
+
"test:watch": "vitest"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"history": "^5.3.0",
|
|
29
|
+
"deep-equal": "^2.2.3"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^18.2.0",
|
|
33
|
+
"react-dom": "^18.2.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@microsoft/tsdoc": "^0.14.1",
|
|
37
|
+
"@reduxjs/toolkit": "^1.8.1",
|
|
38
|
+
"@rollup/plugin-typescript": "^11.1.5",
|
|
39
|
+
"@testing-library/dom": "^9.2.0",
|
|
40
|
+
"@testing-library/jest-dom": "^5.11.4",
|
|
41
|
+
"@testing-library/react": "^14.0.0",
|
|
42
|
+
"@testing-library/user-event": "^14.2.5",
|
|
43
|
+
"@types/luxon": "^2.3.2",
|
|
44
|
+
"@types/node": "^17.0.35",
|
|
45
|
+
"@types/react": "^18.0.15",
|
|
46
|
+
"@types/react-dom": "^18.0.6",
|
|
47
|
+
"@types/stacktrace-js": "^2.0.3",
|
|
48
|
+
"@types/testing-library__jest-dom": "^5.14.5",
|
|
49
|
+
"@types/deep-equal": "^1.0.4",
|
|
50
|
+
"@vitest/coverage-v8": "^1.0.1",
|
|
51
|
+
"c8": "^7.11.3",
|
|
52
|
+
"eslint": "^8.0.0",
|
|
53
|
+
"eslint-config-react-app": "^7.0.1",
|
|
54
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
55
|
+
"jsdom": "^21.1.0",
|
|
56
|
+
"prettier": "^2.7.1",
|
|
57
|
+
"prettier-config-nick": "^1.0.2",
|
|
58
|
+
"react-redux": "^8.0.1",
|
|
59
|
+
"rollup-plugin-typescript-paths": "^1.4.0",
|
|
60
|
+
"rollup-plugin-visualizer": "^5.10.0",
|
|
61
|
+
"stacktrace-js": "^2.0.2",
|
|
62
|
+
"tslib": "^2.6.2",
|
|
63
|
+
"tslog": "^4.9.2",
|
|
64
|
+
"typedoc": "^0.25.4",
|
|
65
|
+
"typescript": "^5.0.2",
|
|
66
|
+
"vite": "^5.0.4",
|
|
67
|
+
"vite-plugin-dts": "^3.6.4",
|
|
68
|
+
"vite-plugin-progress": "^0.0.7",
|
|
69
|
+
"vitest": "^0.34.6"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/card.tsx
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useDispatch, useSelector } from "react-redux"
|
|
3
|
+
import equal from "deep-equal"
|
|
4
|
+
|
|
5
|
+
import { getLogger } from "./logger"
|
|
6
|
+
import {
|
|
7
|
+
CSSModuleClasses,
|
|
8
|
+
CardAction,
|
|
9
|
+
CardProp,
|
|
10
|
+
DispatchF,
|
|
11
|
+
MetaCardMapperF,
|
|
12
|
+
PiCardDef,
|
|
13
|
+
PiCardRef,
|
|
14
|
+
PiMapProps,
|
|
15
|
+
PiRegisterComponent,
|
|
16
|
+
PiRegisterMetaCard,
|
|
17
|
+
PiRegisterReducerF,
|
|
18
|
+
ReduxState,
|
|
19
|
+
RegisterCardF,
|
|
20
|
+
StateMapper,
|
|
21
|
+
StateMapperContext,
|
|
22
|
+
} from "./types"
|
|
23
|
+
import { Action, AnyAction, Dispatch } from "@reduxjs/toolkit"
|
|
24
|
+
|
|
25
|
+
export function isCardRef(p: any): boolean {
|
|
26
|
+
return (typeof p === "object" && p.cardType !== undefined)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Mapping = {
|
|
30
|
+
cardType: string
|
|
31
|
+
props: { [k: string]: unknown }
|
|
32
|
+
eventMappers: { [k: string]: (ev: Action) => Action | null }
|
|
33
|
+
cardEvents: { [key: string]: string }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type MetaCard = {
|
|
37
|
+
type: string
|
|
38
|
+
registerCard: RegisterCardF
|
|
39
|
+
mapper: MetaCardMapperF
|
|
40
|
+
events?: { [key: string]: string }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cardTypes: { [k: string]: PiRegisterComponent } = {}
|
|
44
|
+
const metacardTypes: { [k: string]: MetaCard } = {}
|
|
45
|
+
|
|
46
|
+
let framework: string // name of active UI framework
|
|
47
|
+
const cardMappings: { [k: string]: Mapping } = {}
|
|
48
|
+
const dispatch2registerReducer: [React.Dispatch<any>, PiRegisterReducerF][] = []
|
|
49
|
+
const logger = getLogger("card")
|
|
50
|
+
|
|
51
|
+
export function registerCardComponent(card: PiRegisterComponent): void {
|
|
52
|
+
if (cardTypes[card.name]) {
|
|
53
|
+
logger.warn(`Overwriting definition for card type "${card.name}"`)
|
|
54
|
+
}
|
|
55
|
+
logger.info(`Register card type "${card.name}"`)
|
|
56
|
+
if (!framework) {
|
|
57
|
+
// set default framework
|
|
58
|
+
const na = card.name.split("/")
|
|
59
|
+
if (na.length >= 2) {
|
|
60
|
+
framework = na[0]
|
|
61
|
+
logger.info(`Setting UI framework to "${framework}"`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
cardTypes[card.name] = card
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function registerMetacard(registerCard: RegisterCardF) {
|
|
68
|
+
function f<C>(declaration: PiRegisterMetaCard) {
|
|
69
|
+
const { type, mapper, events } = declaration
|
|
70
|
+
if (metacardTypes[type]) {
|
|
71
|
+
logger.warn(`Overwriting definition for meta card type "${type}"`)
|
|
72
|
+
}
|
|
73
|
+
logger.info(`Register meta card type "${type}"`)
|
|
74
|
+
metacardTypes[type] = { type, registerCard, mapper, events }
|
|
75
|
+
}
|
|
76
|
+
return f
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function registerCard(
|
|
80
|
+
registerReducer: PiRegisterReducerF,
|
|
81
|
+
dispatchF: React.Dispatch<any>,
|
|
82
|
+
) {
|
|
83
|
+
// to be used by dynamically registered cards
|
|
84
|
+
dispatch2registerReducer.push([dispatchF, registerReducer])
|
|
85
|
+
return (name: string, parameters: PiCardDef): PiCardRef => {
|
|
86
|
+
return _registerCard(name, parameters, registerReducer)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function _registerCard(
|
|
91
|
+
name: string,
|
|
92
|
+
parameters: PiCardDef,
|
|
93
|
+
registerReducer: PiRegisterReducerF,
|
|
94
|
+
overrideEvents?: { [key: string]: string },
|
|
95
|
+
): PiCardRef {
|
|
96
|
+
if (cardMappings[name]) {
|
|
97
|
+
logger.warn(`Overwriting definition for card "${name}"`)
|
|
98
|
+
}
|
|
99
|
+
let cardType = cardTypes[parameters.cardType]
|
|
100
|
+
if (!cardType) {
|
|
101
|
+
if (framework) {
|
|
102
|
+
cardType = cardTypes[`${framework}/${parameters.cardType}`]
|
|
103
|
+
}
|
|
104
|
+
if (!cardType) {
|
|
105
|
+
// maybe it's a metadata card
|
|
106
|
+
if (_registerMetadataCard(name, parameters, registerReducer)) {
|
|
107
|
+
return name
|
|
108
|
+
}
|
|
109
|
+
logger.warn("unknown card type", parameters.cardType)
|
|
110
|
+
return name
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const events = overrideEvents || cardType.events || {}
|
|
115
|
+
_createCardMapping(name, parameters, registerReducer, events)
|
|
116
|
+
return name
|
|
117
|
+
// const props = {} as { [k: string]: unknown }
|
|
118
|
+
// const eventMappers = {} as { [k: string]: (ev: Action) => Action }
|
|
119
|
+
|
|
120
|
+
// const events = overrideEvents || cardType.events || {}
|
|
121
|
+
// Object.entries(parameters).forEach(([k, v]) => {
|
|
122
|
+
// if (k === "cardType") return
|
|
123
|
+
// if (typeof v === "object") {
|
|
124
|
+
// const cd = v as PiCardDef // speculative
|
|
125
|
+
// if (cd.cardType) {
|
|
126
|
+
// const cardName = `${name}/${k}`
|
|
127
|
+
// v = _registerCard(cardName, cd, registerReducer)
|
|
128
|
+
// }
|
|
129
|
+
// }
|
|
130
|
+
// if (
|
|
131
|
+
// k.startsWith("on") &&
|
|
132
|
+
// processEventParameter(k, v, events, eventMappers, registerReducer, name)
|
|
133
|
+
// ) {
|
|
134
|
+
// return
|
|
135
|
+
// }
|
|
136
|
+
// props[k] = v
|
|
137
|
+
// })
|
|
138
|
+
// cardMappings[name] = { cardType: parameters.cardType, props, eventMappers }
|
|
139
|
+
// return name
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _createCardMapping(
|
|
143
|
+
name: string,
|
|
144
|
+
parameters: PiCardDef,
|
|
145
|
+
registerReducer: PiRegisterReducerF,
|
|
146
|
+
cardEvents: { [key: string]: string },
|
|
147
|
+
) {
|
|
148
|
+
const props = {} as { [k: string]: unknown }
|
|
149
|
+
const eventMappers = {} as { [k: string]: (ev: Action) => Action }
|
|
150
|
+
|
|
151
|
+
Object.entries(parameters).forEach(([k, v]) => {
|
|
152
|
+
if (k === "cardType") return
|
|
153
|
+
if (typeof v === "object") {
|
|
154
|
+
const cd = v as PiCardDef // speculative
|
|
155
|
+
if (cd.cardType) {
|
|
156
|
+
const cardName = `${name}/${k}`
|
|
157
|
+
v = _registerCard(cardName, cd, registerReducer)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (
|
|
161
|
+
k.startsWith("on") &&
|
|
162
|
+
processEventParameter(k, v, cardEvents, eventMappers, registerReducer, name)
|
|
163
|
+
) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
props[k] = v
|
|
167
|
+
})
|
|
168
|
+
cardMappings[name] = { cardType: parameters.cardType, props, eventMappers, cardEvents }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function _registerMetadataCard(
|
|
172
|
+
metaName: string,
|
|
173
|
+
parameters: PiCardDef,
|
|
174
|
+
registerReducer: PiRegisterReducerF,
|
|
175
|
+
): boolean {
|
|
176
|
+
let mc = metacardTypes[parameters.cardType]
|
|
177
|
+
if (!mc) {
|
|
178
|
+
if (framework) {
|
|
179
|
+
mc = metacardTypes[`${framework}/${parameters.cardType}`]
|
|
180
|
+
}
|
|
181
|
+
if (!mc) {
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function registerCard(name: string, parameters: PiCardDef): PiCardRef {
|
|
186
|
+
const n = `${metaName}/${name}`
|
|
187
|
+
return mc.registerCard(n, parameters)
|
|
188
|
+
}
|
|
189
|
+
const top = mc.mapper(metaName, parameters, registerCard)
|
|
190
|
+
_registerCard(metaName, top, registerReducer, mc.events)
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function createCardDeclaration<Props = {}, Events = {}>(
|
|
195
|
+
cardType: string,
|
|
196
|
+
): <S extends ReduxState>(p: PiMapProps<Props, S, Events>) => PiCardDef {
|
|
197
|
+
return (p) => ({
|
|
198
|
+
...p,
|
|
199
|
+
cardType,
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function processEventParameter(
|
|
204
|
+
propName: string,
|
|
205
|
+
value: unknown,
|
|
206
|
+
events: { [key: string]: string },
|
|
207
|
+
eventMappers: { [k: string]: (ev: Action) => Action },
|
|
208
|
+
registerReducer: PiRegisterReducerF,
|
|
209
|
+
cardName: string,
|
|
210
|
+
): boolean {
|
|
211
|
+
const eva = Object.entries(events).filter((e) => propName.startsWith(e[0]))
|
|
212
|
+
if (eva.length === 0) {
|
|
213
|
+
logger.warn(
|
|
214
|
+
`encountered property '${propName}' for card '${cardName}' which looks like an even but is not defined`,
|
|
215
|
+
)
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
// eva could actually return more than one hit if we have similar named events
|
|
219
|
+
let handled = false
|
|
220
|
+
for (const [evName, actionType] of eva) {
|
|
221
|
+
if (propName === evName) {
|
|
222
|
+
const r = value as (state: ReduxState, action: CardAction, dispatch: DispatchF) => ReduxState
|
|
223
|
+
registerReducer(actionType, (s, a, d) => {
|
|
224
|
+
const ca = a as CardAction
|
|
225
|
+
if (ca.cardID === cardName) {
|
|
226
|
+
s = r(s, ca, d)
|
|
227
|
+
}
|
|
228
|
+
return s
|
|
229
|
+
})
|
|
230
|
+
handled = true
|
|
231
|
+
break
|
|
232
|
+
}
|
|
233
|
+
if (propName === `${evName}Mapper`) {
|
|
234
|
+
logger.debug("processEventParameter", cardName)
|
|
235
|
+
|
|
236
|
+
const m = value as (ev: Action) => Action
|
|
237
|
+
eventMappers[evName] = m
|
|
238
|
+
handled = true
|
|
239
|
+
break
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return handled
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function memo<P, T, S extends ReduxState, C>(
|
|
246
|
+
filterF: (state: S, context: StateMapperContext<C>) => P,
|
|
247
|
+
mapperF: (partial: P, context: StateMapperContext<C>, state: S) => T,
|
|
248
|
+
): (state: S, context: StateMapperContext<C>) => T {
|
|
249
|
+
const lastFilter: { [k: string]: P } = {}
|
|
250
|
+
const lastValue: { [k: string]: T } = {}
|
|
251
|
+
const isNotFirst: { [k: string]: boolean } = {}
|
|
252
|
+
|
|
253
|
+
return (state: S, context: StateMapperContext<C>): T => {
|
|
254
|
+
const k = context.cardKey || "-"
|
|
255
|
+
const fv = filterF(state, context)
|
|
256
|
+
if (isNotFirst[k] && equal(fv, lastFilter[k])) {
|
|
257
|
+
// nothing changed
|
|
258
|
+
return lastValue[k]
|
|
259
|
+
}
|
|
260
|
+
lastFilter[k] = fv
|
|
261
|
+
const v = mapperF(fv, context, state)
|
|
262
|
+
lastValue[k] = v
|
|
263
|
+
isNotFirst[k] = true
|
|
264
|
+
return v
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// export type CardProp = {
|
|
269
|
+
// cardName: PiCardRef
|
|
270
|
+
// } & { [k: string]: any }
|
|
271
|
+
|
|
272
|
+
type CompProps = { [k: string]: any }
|
|
273
|
+
type CardInfo = {
|
|
274
|
+
mapping: Mapping
|
|
275
|
+
cardType: PiRegisterComponent
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function Card(props: CardProp): JSX.Element {
|
|
279
|
+
let cardName: string
|
|
280
|
+
|
|
281
|
+
const [id, _] = React.useState<number>(Math.floor(Math.random() * 10000))
|
|
282
|
+
const dispatch = useDispatch() // never change the order of hooks called
|
|
283
|
+
|
|
284
|
+
if (typeof props.cardName === "string") {
|
|
285
|
+
cardName = props.cardName
|
|
286
|
+
} else {
|
|
287
|
+
// lets fix it
|
|
288
|
+
cardName = checkForAnonymousCard(props, id, dispatch)
|
|
289
|
+
}
|
|
290
|
+
if (cardName === "") {
|
|
291
|
+
logger.error("card name is not of type string", props.cardName)
|
|
292
|
+
return ErrorCard(<div>Unknown type of cardName '{`${props.cardName}`}'</div>)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const [info, errCard] = getCardInfo(cardName)
|
|
296
|
+
if (errCard) {
|
|
297
|
+
return ErrorCard(errCard)
|
|
298
|
+
}
|
|
299
|
+
if (!info) {
|
|
300
|
+
throw new Error("info is empty, should never happen")
|
|
301
|
+
}
|
|
302
|
+
return GenericCard(cardName, props, info, id)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function checkForAnonymousCard(props: any, id: number, dispatch: Dispatch<AnyAction>): string {
|
|
306
|
+
// const [id, _] = React.useState<number>(Math.floor(Math.random() * 10000))
|
|
307
|
+
// const dispatch = useDispatch() // never change the order of hooks called
|
|
308
|
+
|
|
309
|
+
const cardType = props.cardName?.cardType
|
|
310
|
+
if (!cardType) {
|
|
311
|
+
return "" // not sure what that is
|
|
312
|
+
}
|
|
313
|
+
// looks like a potentially unregistered card
|
|
314
|
+
let cardName: string
|
|
315
|
+
if (props.parentCard) {
|
|
316
|
+
cardName = `${props.parentCard}/${cardType.split('/').pop()}`
|
|
317
|
+
} else {
|
|
318
|
+
cardName = cardType
|
|
319
|
+
}
|
|
320
|
+
if (props.cardKey) {
|
|
321
|
+
cardName = `${cardName}#${props.cardKey}-${id}`
|
|
322
|
+
} else {
|
|
323
|
+
cardName = `${cardName}#${id}`
|
|
324
|
+
}
|
|
325
|
+
// logger.debug("anonymous", cardName)
|
|
326
|
+
|
|
327
|
+
const mapping = cardMappings[cardName]
|
|
328
|
+
const parameters = props.cardName as PiCardDef
|
|
329
|
+
const el = dispatch2registerReducer.find(([d, _]) => d === dispatch)
|
|
330
|
+
if (!el) {
|
|
331
|
+
logger.warn("unexpected missing mapping between dispatcher and reducerF")
|
|
332
|
+
return ""
|
|
333
|
+
}
|
|
334
|
+
const regRed = el[1]
|
|
335
|
+
if (mapping) {
|
|
336
|
+
// looks like we already processed it
|
|
337
|
+
// do update props as they may have changed
|
|
338
|
+
_createCardMapping(cardName, parameters, regRed, mapping.cardEvents)
|
|
339
|
+
} else {
|
|
340
|
+
_registerCard(cardName, parameters, regRed)
|
|
341
|
+
}
|
|
342
|
+
return cardName
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function GenericCard(cardName: string, props: CardProp, info: CardInfo, id: number) {
|
|
346
|
+
const cardProps = useSelector<ReduxState, CompProps>(
|
|
347
|
+
(s) => getCardProps(cardName, s, props, info?.mapping),
|
|
348
|
+
propEq,
|
|
349
|
+
)
|
|
350
|
+
const dispatch = useDispatch()
|
|
351
|
+
|
|
352
|
+
const extCardProps = appendEventHandlers(info, cardProps, cardName, dispatch)
|
|
353
|
+
extCardProps._cls = cls_f(cardName, info.mapping.cardType)
|
|
354
|
+
return React.createElement(info.cardType.component, extCardProps, props.children)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const EmptyCompProps = {} as CompProps
|
|
358
|
+
|
|
359
|
+
function ErrorCard(el: JSX.Element) {
|
|
360
|
+
// Note: call the EXACT same hooks as 'GenericCard'
|
|
361
|
+
useSelector<ReduxState, CompProps>((s) => EmptyCompProps, (a, b) => false)
|
|
362
|
+
useDispatch()
|
|
363
|
+
return el
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
function getCardInfo(cardName: string): [CardInfo?, JSX.Element?] {
|
|
368
|
+
const mapping = cardMappings[cardName]
|
|
369
|
+
if (!mapping) {
|
|
370
|
+
return [undefined, renderUnknownCard(cardName)]
|
|
371
|
+
}
|
|
372
|
+
let cardType = cardTypes[mapping.cardType]
|
|
373
|
+
if (!cardType) {
|
|
374
|
+
if (framework) {
|
|
375
|
+
cardType = cardTypes[`${framework}/${mapping.cardType}`]
|
|
376
|
+
}
|
|
377
|
+
if (!cardType) {
|
|
378
|
+
return [undefined, renderUnknownCardType(mapping.cardType)]
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const info = { mapping, cardType }
|
|
382
|
+
return [info, undefined]
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
function getCardProps(
|
|
387
|
+
cardName: string,
|
|
388
|
+
state: ReduxState,
|
|
389
|
+
props: CardProp,
|
|
390
|
+
mapping: Mapping,
|
|
391
|
+
): CompProps {
|
|
392
|
+
const ctxt: StateMapperContext<unknown> = {
|
|
393
|
+
cardName,
|
|
394
|
+
cardKey: props.cardKey,
|
|
395
|
+
ctxtProps: props,
|
|
396
|
+
}
|
|
397
|
+
const init: CompProps = {
|
|
398
|
+
cardName,
|
|
399
|
+
cardKey: props.cardKey,
|
|
400
|
+
}
|
|
401
|
+
const cprops = Object.entries(mapping.props).reduce((p, [key, vf]) => {
|
|
402
|
+
let v = vf
|
|
403
|
+
if (typeof vf === "function") {
|
|
404
|
+
const f = vf as StateMapper<unknown, ReduxState, any>
|
|
405
|
+
try {
|
|
406
|
+
v = f(state, ctxt)
|
|
407
|
+
} catch (ex) {
|
|
408
|
+
logger.error(`while resolving property '${key}'`, ex)
|
|
409
|
+
}
|
|
410
|
+
} else if (key in props) {
|
|
411
|
+
v = props[key]
|
|
412
|
+
}
|
|
413
|
+
p[key] = v
|
|
414
|
+
return p
|
|
415
|
+
}, init)
|
|
416
|
+
return cprops
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function cls_f(
|
|
420
|
+
cardName: string,
|
|
421
|
+
cardComp: string,
|
|
422
|
+
prefix: string = "pi",
|
|
423
|
+
): (nodeName: string | string[], styles?: CSSModuleClasses) => string {
|
|
424
|
+
const cn = cardName.replaceAll(/[/:]/g, "_")
|
|
425
|
+
const cp = cardComp.replaceAll(/[/:]/g, "_")
|
|
426
|
+
return (nodeName: string | string[], styles?: CSSModuleClasses): string => {
|
|
427
|
+
const na: string[] = typeof nodeName === "string" ? [nodeName] : nodeName
|
|
428
|
+
const ca = [] as string[]
|
|
429
|
+
na.forEach((n) => {
|
|
430
|
+
const s = styles?.[n]
|
|
431
|
+
if (s) ca.push(s)
|
|
432
|
+
|
|
433
|
+
const nn = n.replaceAll(/[/:]/g, "_")
|
|
434
|
+
ca.push(`${prefix}-${cn}-${nn}`)
|
|
435
|
+
ca.push(`${prefix}-${cp}-${nn}`)
|
|
436
|
+
})
|
|
437
|
+
return ca.join(" ")
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function propEq(oldP: CompProps, newP: CompProps): boolean {
|
|
442
|
+
let isUnchanged = equal(oldP, newP)
|
|
443
|
+
// for (const [k, v] of Object.entries(newP)) {
|
|
444
|
+
// const ov = oldP[k]
|
|
445
|
+
// if (ov !== v) {
|
|
446
|
+
// // two empty arrays are considered to be different, but we don't agree :)
|
|
447
|
+
// if (!(Array.isArray(v) && !v.length && Array.isArray(ov) && !ov.length)) {
|
|
448
|
+
// isUnchanged = false
|
|
449
|
+
// break
|
|
450
|
+
// }
|
|
451
|
+
// }
|
|
452
|
+
// }
|
|
453
|
+
RegisterCardState.changed(newP.cardName, isUnchanged, newP)
|
|
454
|
+
return isUnchanged
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function appendEventHandlers(
|
|
458
|
+
info: CardInfo,
|
|
459
|
+
cardProps: CompProps,
|
|
460
|
+
cardName: string,
|
|
461
|
+
dispatch: Dispatch<Action>,
|
|
462
|
+
): CompProps {
|
|
463
|
+
RegisterCardState.props(cardName, cardProps, dispatch)
|
|
464
|
+
const cp: CompProps = {
|
|
465
|
+
...cardProps,
|
|
466
|
+
_dispatch: (a: AnyAction) => dispatch(a)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const events = info.cardType?.events
|
|
470
|
+
if (!events) {
|
|
471
|
+
return cp
|
|
472
|
+
}
|
|
473
|
+
const eventMappers = info.mapping.eventMappers
|
|
474
|
+
Object.entries(events).forEach(([name, actionType]) => {
|
|
475
|
+
const m = eventMappers[name]
|
|
476
|
+
if (m) {
|
|
477
|
+
logger.debug("setup mapper", cardName)
|
|
478
|
+
cp[name] = (a: AnyAction) => {
|
|
479
|
+
a.cardID = cardName
|
|
480
|
+
const a2 = m(a)
|
|
481
|
+
if (a2) dispatch(a2)
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
cp[name] = (a: AnyAction) => {
|
|
485
|
+
a.type = actionType
|
|
486
|
+
a.cardID = cardName
|
|
487
|
+
dispatch(a)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
return cp
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function renderUnknownCard(cardName: string): JSX.Element {
|
|
495
|
+
return <div>Unknown card '{cardName}'</div>
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function renderUnknownCardType(cardType: string): JSX.Element {
|
|
499
|
+
return <div>Unknown card type '{cardType}'</div>
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Adding card state to redux state for debugging
|
|
503
|
+
|
|
504
|
+
export const UPDATE_STATE_ACTION = "pi/card/update_state"
|
|
505
|
+
|
|
506
|
+
type CardState = {
|
|
507
|
+
props: (
|
|
508
|
+
cardName: string,
|
|
509
|
+
cardProps: CompProps,
|
|
510
|
+
dispatch: Dispatch<Action>,
|
|
511
|
+
) => void
|
|
512
|
+
changed: (cardName: string, isUnchanged: boolean, props: CompProps) => void
|
|
513
|
+
reducer: (state: ReduxState, action: Action) => ReduxState
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export const RegisterCardState = createCardState()
|
|
517
|
+
|
|
518
|
+
function createCardState(): CardState {
|
|
519
|
+
type S = {
|
|
520
|
+
cardProps?: CompProps
|
|
521
|
+
changedAt: number
|
|
522
|
+
reportedAt: number
|
|
523
|
+
}
|
|
524
|
+
const s: { [name: string]: S } = {}
|
|
525
|
+
let dispatch: Dispatch<Action>
|
|
526
|
+
let timer: number
|
|
527
|
+
let lastReport = 0
|
|
528
|
+
|
|
529
|
+
// const timer
|
|
530
|
+
const getS = (cardName: string, props: CompProps): S => {
|
|
531
|
+
const name = cardName
|
|
532
|
+
let e = s[name]
|
|
533
|
+
if (!e) {
|
|
534
|
+
const ts = Date.now()
|
|
535
|
+
e = {
|
|
536
|
+
changedAt: ts,
|
|
537
|
+
reportedAt: ts,
|
|
538
|
+
} as S
|
|
539
|
+
s[name] = e
|
|
540
|
+
resetTimer()
|
|
541
|
+
}
|
|
542
|
+
return e
|
|
543
|
+
}
|
|
544
|
+
const resetTimer = () => {
|
|
545
|
+
if (timer) {
|
|
546
|
+
clearTimeout(timer)
|
|
547
|
+
}
|
|
548
|
+
timer = window.setTimeout(() => {
|
|
549
|
+
//logger.debug("... timer went off") // , s, dispatch)
|
|
550
|
+
if (dispatch) {
|
|
551
|
+
const changed = Object.values(s).filter((s) => s.changedAt > lastReport)
|
|
552
|
+
if (changed.length > 0) {
|
|
553
|
+
clearTimeout(timer) // just in case
|
|
554
|
+
dispatch({ type: UPDATE_STATE_ACTION })
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}, 1000)
|
|
558
|
+
}
|
|
559
|
+
const props = (
|
|
560
|
+
cardName: string,
|
|
561
|
+
cardProps: CompProps,
|
|
562
|
+
_dispatch: Dispatch<Action>,
|
|
563
|
+
) => {
|
|
564
|
+
const e = getS(cardName, cardProps)
|
|
565
|
+
e.cardProps = cardProps
|
|
566
|
+
dispatch = _dispatch
|
|
567
|
+
}
|
|
568
|
+
const changed = (cardName: string, isUnchanged: boolean, props: CompProps) => {
|
|
569
|
+
const e = getS(cardName, props)
|
|
570
|
+
e.reportedAt = Date.now()
|
|
571
|
+
if (!isUnchanged) {
|
|
572
|
+
logger.debug("card has changed:", cardName)
|
|
573
|
+
e.changedAt = Date.now()
|
|
574
|
+
resetTimer()
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const reducer = (state: ReduxState): ReduxState => {
|
|
578
|
+
const pi = Object.values(s)
|
|
579
|
+
.filter((s) => s.reportedAt > lastReport)
|
|
580
|
+
.reduce((p, s) => {
|
|
581
|
+
const cname = s.cardProps?.cardName
|
|
582
|
+
if (!cname) {
|
|
583
|
+
logger.warn("Unexpected missing card name", s)
|
|
584
|
+
return p
|
|
585
|
+
}
|
|
586
|
+
const name = cname
|
|
587
|
+
const props = copySafeProps(s.cardProps || {})
|
|
588
|
+
delete props.cardName
|
|
589
|
+
delete props._cls
|
|
590
|
+
p[name] = props
|
|
591
|
+
return p
|
|
592
|
+
}, {} as { [k: string]: any })
|
|
593
|
+
state.pihanga = pi
|
|
594
|
+
lastReport = Date.now()
|
|
595
|
+
return state
|
|
596
|
+
}
|
|
597
|
+
return { props, changed, reducer }
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function copySafeProps(props: CompProps): CompProps {
|
|
601
|
+
return Object.entries(props).reduce((p, [k, v]) => {
|
|
602
|
+
// const ok = (typeof v === 'undefined' || typeof v === 'string' || typeof v === 'boolean' || typeof v === 'number' || Array.isArray(v));
|
|
603
|
+
const sv = makeSafe(v)
|
|
604
|
+
p[k] = sv
|
|
605
|
+
return p
|
|
606
|
+
}, {} as CompProps)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function makeSafe(v: any): any {
|
|
610
|
+
const t = typeof v
|
|
611
|
+
if (t === 'undefined' || t === 'string' || t === 'boolean' || t === 'number') {
|
|
612
|
+
return v
|
|
613
|
+
}
|
|
614
|
+
if (t === 'function') {
|
|
615
|
+
return "f(...)"
|
|
616
|
+
}
|
|
617
|
+
if (Array.isArray(v)) {
|
|
618
|
+
return v.map(makeSafe)
|
|
619
|
+
}
|
|
620
|
+
if (t === 'object') {
|
|
621
|
+
return Object.entries(v).reduce((p, [k, v]) => {
|
|
622
|
+
p[k] = makeSafe(v)
|
|
623
|
+
return p
|
|
624
|
+
}, {} as { [k: string]: any })
|
|
625
|
+
}
|
|
626
|
+
logger.warn(">>> reject", v, typeof v)
|
|
627
|
+
return "..."
|
|
628
|
+
}
|