@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
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { RestContentType } from ".."
|
|
2
|
+
import { registerActions } from "../redux"
|
|
3
|
+
import { DispatchF, ReduxAction, ReduxState } from "../types"
|
|
4
|
+
|
|
5
|
+
export const Domain = "pi/rest"
|
|
6
|
+
export const ACTION_TYPES = registerActions(Domain, [
|
|
7
|
+
// "GET_SUBMITTED",
|
|
8
|
+
// "GET_RESULT",
|
|
9
|
+
// "GET_ERROR",
|
|
10
|
+
// "GET_INTERNAL_ERROR",
|
|
11
|
+
// "GET_PERIODIC_TICK",
|
|
12
|
+
"POST_SUBMITTED",
|
|
13
|
+
"POST_RESULT",
|
|
14
|
+
"POST_ERROR",
|
|
15
|
+
"POST_INTERNAL_ERROR",
|
|
16
|
+
"PUT_SUBMITTED",
|
|
17
|
+
"PUT_RESULT",
|
|
18
|
+
"PUT_ERROR",
|
|
19
|
+
"PUT_INTERNAL_ERROR",
|
|
20
|
+
"PATCH_SUBMITTED",
|
|
21
|
+
"PATCH_RESULT",
|
|
22
|
+
"PATCH_ERROR",
|
|
23
|
+
"PATCH_INTERNAL_ERROR",
|
|
24
|
+
"DELETE_SUBMITTED",
|
|
25
|
+
"DELETE_RESULT",
|
|
26
|
+
"DELETE_ERROR",
|
|
27
|
+
"DELETE_INTERNAL_ERROR",
|
|
28
|
+
"UNAUTHORISED_ERROR",
|
|
29
|
+
"PERMISSION_DENIED_ERROR",
|
|
30
|
+
"NOT_FOUND_ERROR",
|
|
31
|
+
"ERROR",
|
|
32
|
+
"CONTEXT_ERROR"
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
export type Bindings = { [key: string]: string | number }
|
|
36
|
+
export type PoPuPaRequest = {
|
|
37
|
+
body: any
|
|
38
|
+
contentType?: string // if not set and body is 'object' then we send it as jsonconst h = {}
|
|
39
|
+
bindings?: Bindings
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type RegisterGenericProps<
|
|
43
|
+
S extends ReduxState,
|
|
44
|
+
A extends ReduxAction,
|
|
45
|
+
R,
|
|
46
|
+
C = any,
|
|
47
|
+
> = {
|
|
48
|
+
name: string
|
|
49
|
+
origin?: string | ((action: A, state: S, context: C) => string | URL) // if defined, will be prepended to 'url' (URL(window.location.href).origin)
|
|
50
|
+
url: string
|
|
51
|
+
trigger: string
|
|
52
|
+
context?: (action: A, state: S) => Promise<C> | null
|
|
53
|
+
guard?: (action: A, state: S, dispatcher: DispatchF, context: C) => boolean
|
|
54
|
+
headers?: (action: A, state: S, context: C) => { [key: string]: string }
|
|
55
|
+
reply: (
|
|
56
|
+
state: S,
|
|
57
|
+
reply: R,
|
|
58
|
+
dispatcher: DispatchF,
|
|
59
|
+
result: ResultAction<A>,
|
|
60
|
+
) => S
|
|
61
|
+
error?: (
|
|
62
|
+
state: S,
|
|
63
|
+
error: ErrorAction<A>,
|
|
64
|
+
requestAction: A,
|
|
65
|
+
dispatch: DispatchF,
|
|
66
|
+
) => S
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type PiRegisterGetProps<
|
|
70
|
+
S extends ReduxState,
|
|
71
|
+
A extends ReduxAction,
|
|
72
|
+
R,
|
|
73
|
+
C = any,
|
|
74
|
+
> = RegisterGenericProps<S, A, R, C> & {
|
|
75
|
+
request?: (action: A, state: S) => Bindings
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type PiRegisterPoPuPaProps<
|
|
79
|
+
S extends ReduxState,
|
|
80
|
+
A extends ReduxAction,
|
|
81
|
+
R,
|
|
82
|
+
C = any,
|
|
83
|
+
> = RegisterGenericProps<S, A, R, C> & {
|
|
84
|
+
request: (action: A, state: S) => PoPuPaRequest
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type PiRegisterDeleteProps<
|
|
88
|
+
S extends ReduxState,
|
|
89
|
+
A extends ReduxAction,
|
|
90
|
+
R,
|
|
91
|
+
C = any,
|
|
92
|
+
> = RegisterGenericProps<S, A, R, C> & {
|
|
93
|
+
request?: (action: A, state: S) => Bindings
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
export type HttpResponse = {
|
|
98
|
+
statusCode: number
|
|
99
|
+
headers: { [k: string]: any }
|
|
100
|
+
content: any
|
|
101
|
+
contentType: RestContentType
|
|
102
|
+
mimeType: string
|
|
103
|
+
size: number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type SubmitAction = ReduxAction & {
|
|
107
|
+
requestID: string
|
|
108
|
+
url: string
|
|
109
|
+
bindings: Bindings
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export enum ErrorKind {
|
|
113
|
+
Unauthorised = "Unauthorised",
|
|
114
|
+
PermissionDenied = "PermissionDenied",
|
|
115
|
+
NotFound = "NotFound",
|
|
116
|
+
Other = "Other",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type ResultAction<R> = ReduxAction & {
|
|
120
|
+
queryID: string
|
|
121
|
+
url: string
|
|
122
|
+
request: R
|
|
123
|
+
} & HttpResponse
|
|
124
|
+
|
|
125
|
+
export type ErrorAction<R> = ReduxAction & {
|
|
126
|
+
requestID: string
|
|
127
|
+
error: ErrorKind
|
|
128
|
+
url: string
|
|
129
|
+
request: R
|
|
130
|
+
} & HttpResponse
|
|
131
|
+
|
|
132
|
+
export type ContextErrorAction = ReduxAction & {
|
|
133
|
+
error: string
|
|
134
|
+
pendingAction: ReduxAction
|
|
135
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { current } from "immer"
|
|
2
|
+
import { DispatchF, PiReducer, ReduxAction, ReduxState } from "../types"
|
|
3
|
+
import {
|
|
4
|
+
ACTION_TYPES,
|
|
5
|
+
Bindings,
|
|
6
|
+
ContextErrorAction,
|
|
7
|
+
ErrorAction,
|
|
8
|
+
ErrorKind,
|
|
9
|
+
HttpResponse,
|
|
10
|
+
RegisterGenericProps,
|
|
11
|
+
ResultAction,
|
|
12
|
+
} from "./types"
|
|
13
|
+
import { RestContentType } from "./enums"
|
|
14
|
+
|
|
15
|
+
export function parseResponse(
|
|
16
|
+
response: Response,
|
|
17
|
+
): Promise<[any, RestContentType, string, Response]> {
|
|
18
|
+
const mimeType = response.headers.get("content-type")
|
|
19
|
+
if (mimeType) {
|
|
20
|
+
switch (mimeType) {
|
|
21
|
+
case "application/json":
|
|
22
|
+
return response.json().then((j) => [j, RestContentType.Object, mimeType, response])
|
|
23
|
+
case "application/jose":
|
|
24
|
+
return response.text().then((t) => [t, RestContentType.Text, mimeType, response])
|
|
25
|
+
default:
|
|
26
|
+
if (mimeType.startsWith("text")) {
|
|
27
|
+
return response.text().then((t) => [t, RestContentType.Text, mimeType, response])
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return response.blob().then((t) => [t, RestContentType.Blob, mimeType || "unknown", response])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createErrorAction<R>(
|
|
35
|
+
type: string,
|
|
36
|
+
resp: HttpResponse,
|
|
37
|
+
name: string,
|
|
38
|
+
url: URL,
|
|
39
|
+
request: R,
|
|
40
|
+
): ErrorAction<R> {
|
|
41
|
+
let error: ErrorKind
|
|
42
|
+
const status = resp.statusCode
|
|
43
|
+
if (status === 401) {
|
|
44
|
+
error = ErrorKind.Unauthorised
|
|
45
|
+
} else if (status === 403) {
|
|
46
|
+
error = ErrorKind.PermissionDenied
|
|
47
|
+
} else if (status === 404) {
|
|
48
|
+
error = ErrorKind.NotFound
|
|
49
|
+
} else {
|
|
50
|
+
error = ErrorKind.Other
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
type,
|
|
54
|
+
requestID: name,
|
|
55
|
+
error,
|
|
56
|
+
url: url.toString(),
|
|
57
|
+
request,
|
|
58
|
+
...resp,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type RequestF<S extends ReduxState, A extends ReduxAction> = (
|
|
63
|
+
state: S,
|
|
64
|
+
action: A,
|
|
65
|
+
) => [RequestInit, Bindings]
|
|
66
|
+
|
|
67
|
+
export function registerCommon<S extends ReduxState, A extends ReduxAction, R, C = any>(
|
|
68
|
+
reducer: PiReducer,
|
|
69
|
+
props: RegisterGenericProps<S, A, R, C>,
|
|
70
|
+
requestF: RequestF<S, A>,
|
|
71
|
+
submitType: string,
|
|
72
|
+
resultType: string,
|
|
73
|
+
errorType: string,
|
|
74
|
+
intErrorType: string,
|
|
75
|
+
) {
|
|
76
|
+
const {
|
|
77
|
+
name,
|
|
78
|
+
origin = window.location.href,
|
|
79
|
+
url,
|
|
80
|
+
trigger,
|
|
81
|
+
context,
|
|
82
|
+
guard,
|
|
83
|
+
headers,
|
|
84
|
+
reply,
|
|
85
|
+
error,
|
|
86
|
+
} = props
|
|
87
|
+
|
|
88
|
+
if (!name) {
|
|
89
|
+
throw Error('Missing "name"')
|
|
90
|
+
}
|
|
91
|
+
if (!url) {
|
|
92
|
+
throw Error('Missing "url"')
|
|
93
|
+
}
|
|
94
|
+
if (!trigger) {
|
|
95
|
+
throw Error('Missing "trigger"')
|
|
96
|
+
}
|
|
97
|
+
if (!reply) {
|
|
98
|
+
throw Error('Missing "reply"')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
reducer.register<S, A>(
|
|
102
|
+
trigger,
|
|
103
|
+
(state: S, action: A, dispatch: DispatchF) => {
|
|
104
|
+
const ctxtP = context ? context(action, state) : null
|
|
105
|
+
if (ctxtP) {
|
|
106
|
+
// const s = current(state) // need
|
|
107
|
+
ctxtP
|
|
108
|
+
.then((ctxt) => handleEvent(null as unknown as S, action, dispatch, ctxt))
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
const a: ContextErrorAction = {
|
|
111
|
+
type: ACTION_TYPES.CONTEXT_ERROR,
|
|
112
|
+
error: err.toString(),
|
|
113
|
+
pendingAction: action
|
|
114
|
+
}
|
|
115
|
+
dispatch(a)
|
|
116
|
+
})
|
|
117
|
+
} else {
|
|
118
|
+
// we should remove state as argument
|
|
119
|
+
handleEvent(null as unknown as S, action, dispatch, {} as any)
|
|
120
|
+
}
|
|
121
|
+
return state
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
function handleEvent(state: S, action: A, dispatch: DispatchF, ctxt: C): S {
|
|
125
|
+
if (guard) {
|
|
126
|
+
if (!guard(action, state, dispatch, ctxt)) {
|
|
127
|
+
return state
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let bindings: Bindings = {}
|
|
131
|
+
let request: RequestInit
|
|
132
|
+
let url2: URL
|
|
133
|
+
try {
|
|
134
|
+
;[request, bindings] = requestF(state, action)
|
|
135
|
+
if (headers) {
|
|
136
|
+
const h = headers(action, state, ctxt)
|
|
137
|
+
request.headers = request.headers ? { ...request.headers, ...h } : h
|
|
138
|
+
}
|
|
139
|
+
let o: string
|
|
140
|
+
if (typeof origin === "function") {
|
|
141
|
+
const ox = origin(action, state, ctxt)
|
|
142
|
+
o = ox instanceof URL ? ox.toString() : ox
|
|
143
|
+
} else {
|
|
144
|
+
o = origin
|
|
145
|
+
}
|
|
146
|
+
url2 = buildURL(url, o, bindings)
|
|
147
|
+
} catch (e: any) {
|
|
148
|
+
dispatch({
|
|
149
|
+
type: intErrorType,
|
|
150
|
+
error: e?.message,
|
|
151
|
+
call: name,
|
|
152
|
+
action,
|
|
153
|
+
bindings,
|
|
154
|
+
})
|
|
155
|
+
return state
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
dispatch({
|
|
159
|
+
type: submitType,
|
|
160
|
+
requestID: name,
|
|
161
|
+
url: url2.toString(),
|
|
162
|
+
bindings,
|
|
163
|
+
})
|
|
164
|
+
_fetch(url2, request)
|
|
165
|
+
.then((resp) => {
|
|
166
|
+
if (resp.statusCode < 300) {
|
|
167
|
+
const a: ResultAction<A> = {
|
|
168
|
+
type: resultType,
|
|
169
|
+
queryID: name,
|
|
170
|
+
...resp,
|
|
171
|
+
url: url2.toString(),
|
|
172
|
+
request: action,
|
|
173
|
+
}
|
|
174
|
+
dispatch(a)
|
|
175
|
+
} else {
|
|
176
|
+
const a = createErrorAction(errorType, resp, name, url2, action)
|
|
177
|
+
dispatch(a)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
.catch((error) => console.log("_fetch", error))
|
|
181
|
+
return state
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
reducer.register<S, ResultAction<A>>(resultType, (state, ra, dispatch) => {
|
|
185
|
+
return reply(state, ra.content, dispatch, ra)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (error) {
|
|
189
|
+
reducer.register<S, ErrorAction<A>>(
|
|
190
|
+
errorType,
|
|
191
|
+
(state, action, dispatch) => {
|
|
192
|
+
return error(state, action, action.request, dispatch)
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildURL(url: string, origin: string, bindings: Bindings): URL {
|
|
199
|
+
const u = new URL(url, origin)
|
|
200
|
+
u.pathname = u.pathname
|
|
201
|
+
.split("/")
|
|
202
|
+
.map((p) => {
|
|
203
|
+
return resolveBinding(p, bindings, encodeURIComponent)
|
|
204
|
+
})
|
|
205
|
+
.join("/")
|
|
206
|
+
if (u.search !== "") {
|
|
207
|
+
const params = new URLSearchParams()
|
|
208
|
+
u.searchParams.forEach((v, k) => {
|
|
209
|
+
const v2 = resolveBinding(v, bindings)
|
|
210
|
+
if (v2) {
|
|
211
|
+
params.set(k, v2)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
u.search = "?" + params.toString()
|
|
215
|
+
}
|
|
216
|
+
return u
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function resolveBinding(
|
|
220
|
+
value: string,
|
|
221
|
+
bindings: Bindings,
|
|
222
|
+
encoder: ((v: string) => string) | undefined = undefined,
|
|
223
|
+
): string | undefined {
|
|
224
|
+
const first = value[0]
|
|
225
|
+
if (first === ":" || first === "?") {
|
|
226
|
+
const k = value.slice(1)
|
|
227
|
+
let v = bindings[k]
|
|
228
|
+
if (v === undefined) {
|
|
229
|
+
if (first === "?") {
|
|
230
|
+
return undefined
|
|
231
|
+
} else {
|
|
232
|
+
throw new Error(`Missing binding '${k}'.`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (typeof v !== "string") {
|
|
236
|
+
v = `${v}`
|
|
237
|
+
}
|
|
238
|
+
return encoder ? encoder(v) : v
|
|
239
|
+
} else {
|
|
240
|
+
return value
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function _fetch(url: URL, request: RequestInit): Promise<HttpResponse> {
|
|
245
|
+
return fetch(url, request)
|
|
246
|
+
.then(parseResponse)
|
|
247
|
+
.then(([content, contentType, mimeType, response]) => {
|
|
248
|
+
return {
|
|
249
|
+
statusCode: response.status,
|
|
250
|
+
content,
|
|
251
|
+
contentType,
|
|
252
|
+
mimeType,
|
|
253
|
+
size: getSize(response),
|
|
254
|
+
headers: response.headers,
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getSize(response: Response): number {
|
|
260
|
+
const s = response.headers.get("Content-Length")
|
|
261
|
+
if (!s) {
|
|
262
|
+
return -1
|
|
263
|
+
}
|
|
264
|
+
return Number(s)
|
|
265
|
+
}
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Update, createBrowserHistory, Location } from 'history';
|
|
2
|
+
import { getLogger } from './logger';
|
|
3
|
+
import { registerActions } from './redux';
|
|
4
|
+
import { DispatchF, PathQuery, PiReducer, ReduceF, ReduxAction, ReduxState, Route } from './types';
|
|
5
|
+
import { PiRegister } from '.';
|
|
6
|
+
|
|
7
|
+
const logger = getLogger("router")
|
|
8
|
+
export const browserHistory = createBrowserHistory();
|
|
9
|
+
|
|
10
|
+
// type Route = {
|
|
11
|
+
// path: string[]
|
|
12
|
+
// query: PathQuery
|
|
13
|
+
// url: string
|
|
14
|
+
// fromBrowser?: boolean
|
|
15
|
+
// }
|
|
16
|
+
|
|
17
|
+
export const ACTION_TYPES = registerActions('pi/router', ['show_page', 'navigate_to_page']);
|
|
18
|
+
|
|
19
|
+
export type ShowPageEvent = {
|
|
20
|
+
path: string[]
|
|
21
|
+
query?: PathQuery
|
|
22
|
+
fromBrowser?: boolean
|
|
23
|
+
}
|
|
24
|
+
export const onShowPage = createOnAction<ShowPageEvent>(ACTION_TYPES.SHOW_PAGE)
|
|
25
|
+
|
|
26
|
+
export const showPage = (dispatch: DispatchF, path: string[], query?: PathQuery) => {
|
|
27
|
+
dispatch(createShowPageAction(path, query))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const createShowPageAction = (path: string[], query?: PathQuery): ReduxAction & ShowPageEvent => ({
|
|
31
|
+
type: ACTION_TYPES.SHOW_PAGE,
|
|
32
|
+
path, query, fromBrowser: false
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export type NavigateToPageEvent = {
|
|
36
|
+
url: string
|
|
37
|
+
fromBrowser: boolean
|
|
38
|
+
}
|
|
39
|
+
export const onNavigateToPage = createOnAction<NavigateToPageEvent>(ACTION_TYPES.NAVIGATE_TO_PAGE)
|
|
40
|
+
|
|
41
|
+
export const ON_INIT_ACTION = "pi/start"
|
|
42
|
+
export const onInit = createOnAction<{}>(ON_INIT_ACTION)
|
|
43
|
+
|
|
44
|
+
export function currentRoute(pathPrefix = ''): Route {
|
|
45
|
+
const f = route_functions(pathPrefix)
|
|
46
|
+
const r2 = f.url2route(window.location.href)
|
|
47
|
+
const r = f.location2route(browserHistory.location)
|
|
48
|
+
return r
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function init(reducer: PiReducer, pathPrefix = ''): Route {
|
|
53
|
+
let workingURL: string
|
|
54
|
+
const f = route_functions(pathPrefix)
|
|
55
|
+
|
|
56
|
+
browserHistory.listen(({ action, location }: Update) => {
|
|
57
|
+
// location is an object like window.location
|
|
58
|
+
const { url } = f.location2route(location)
|
|
59
|
+
if (workingURL !== url) {
|
|
60
|
+
logger.info('browser history:', url, action)
|
|
61
|
+
setTimeout(() => navigateToPage(url, action === 'POP'))
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function browserPath(): Route {
|
|
66
|
+
return f.location2route(browserHistory.location)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function navigateToPage(url: string, fromBrowser = false) {
|
|
70
|
+
reducer.dispatch({
|
|
71
|
+
type: ACTION_TYPES.NAVIGATE_TO_PAGE,
|
|
72
|
+
url,
|
|
73
|
+
fromBrowser,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
reducer.register<ReduxState, ReduxAction & NavigateToPageEvent>(
|
|
78
|
+
ACTION_TYPES.NAVIGATE_TO_PAGE,
|
|
79
|
+
(state, { url, fromBrowser }, dispatch) => {
|
|
80
|
+
const r = f.url2route(url)
|
|
81
|
+
r.fromBrowser = fromBrowser
|
|
82
|
+
if (workingURL && state.route?.url === r.url) {
|
|
83
|
+
return state;
|
|
84
|
+
}
|
|
85
|
+
dispatch({
|
|
86
|
+
type: ACTION_TYPES.SHOW_PAGE,
|
|
87
|
+
...r,
|
|
88
|
+
});
|
|
89
|
+
return state
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
reducer.register<ReduxState, ReduxAction & ShowPageEvent>(
|
|
93
|
+
ACTION_TYPES.SHOW_PAGE,
|
|
94
|
+
(state, { path, query = {}, fromBrowser = false }) => {
|
|
95
|
+
const route = f.pathl2route(path, query)
|
|
96
|
+
workingURL = route.url
|
|
97
|
+
if (!fromBrowser) {
|
|
98
|
+
const hp = browserPath()
|
|
99
|
+
if (route.url !== hp.url) {
|
|
100
|
+
browserHistory.push(route.url);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
state.route = {
|
|
104
|
+
...route,
|
|
105
|
+
fromBrowser,
|
|
106
|
+
}
|
|
107
|
+
return state
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
reducer.register('@@INIT', (state) => {
|
|
111
|
+
const url = browserPath().url
|
|
112
|
+
logger.info(`Request navigation to '${url}'`);
|
|
113
|
+
setTimeout(() => navigateToPage(url, true));
|
|
114
|
+
return state;
|
|
115
|
+
});
|
|
116
|
+
return f.location2route(browserHistory.location)
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function route_functions(pathPrefix = '') {
|
|
120
|
+
const location2route = (location: Location): Route => {
|
|
121
|
+
const [p, s] = [location.pathname, location.search]
|
|
122
|
+
const url = s ? `${p}${s}` : p
|
|
123
|
+
return url2route(url)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const url2route = (url: string): Route => {
|
|
127
|
+
const [pn, search] = url.split("?")
|
|
128
|
+
|
|
129
|
+
const path = pn.substring(pathPrefix.length).split('/').filter(s => s !== '');
|
|
130
|
+
const query = {} as PathQuery
|
|
131
|
+
const s = search
|
|
132
|
+
if (s && s.length > 0) {
|
|
133
|
+
s.split('&').forEach((el) => {
|
|
134
|
+
const [k, v] = el.split('=');
|
|
135
|
+
query[decodeURI(k)] = v ? decodeURI(v) : true;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { url, path, query }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const pathl2route = (path: string[], query: PathQuery): Route => {
|
|
142
|
+
let url = `${pathPrefix}/${path.join('/')}`
|
|
143
|
+
if (query) {
|
|
144
|
+
const qa = Object.entries(query);
|
|
145
|
+
if (qa.length > 0) {
|
|
146
|
+
const s = qa.map(([k, v]) => {
|
|
147
|
+
const n = encodeURI(k)
|
|
148
|
+
if (typeof v === 'boolean') {
|
|
149
|
+
return n
|
|
150
|
+
} else if (typeof v === 'number') {
|
|
151
|
+
return `${n}=${v}`
|
|
152
|
+
} else {
|
|
153
|
+
return `${n}=${encodeURI(v)}`
|
|
154
|
+
}
|
|
155
|
+
}).join('&')
|
|
156
|
+
url = `${url}?${s}`
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { url, path, query }
|
|
160
|
+
}
|
|
161
|
+
return { location2route, url2route, pathl2route }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createOnAction<E>(actionType: string): <S extends ReduxState>(
|
|
165
|
+
register: PiRegister,
|
|
166
|
+
f: ReduceF<S, ReduxAction & E>,
|
|
167
|
+
) => void {
|
|
168
|
+
return (register, f) => {
|
|
169
|
+
register.reducer.register(actionType, f)
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
|
|
2
|
+
// import counterReducer from "../UNUSED/features/counter/counterSlice"
|
|
3
|
+
|
|
4
|
+
// export type Store = any
|
|
5
|
+
|
|
6
|
+
// export const store: Store = configureStore({
|
|
7
|
+
// reducer: {
|
|
8
|
+
// counter: counterReducer,
|
|
9
|
+
// },
|
|
10
|
+
// })
|
|
11
|
+
|
|
12
|
+
// export type AppDispatch = typeof store.dispatch
|
|
13
|
+
// export type RootState = ReturnType<typeof store.getState>
|
|
14
|
+
// export type AppThunk<ReturnType = void> = ThunkAction<
|
|
15
|
+
// ReturnType,
|
|
16
|
+
// RootState,
|
|
17
|
+
// unknown,
|
|
18
|
+
// Action<string>
|
|
19
|
+
// >
|