@chhsiao1981/use-thunk 9.0.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/LICENSE +21 -0
- package/README.md +437 -0
- package/package.json +63 -0
- package/src/action.ts +20 -0
- package/src/addChild.ts +13 -0
- package/src/addLink.ts +27 -0
- package/src/addRelation.ts +32 -0
- package/src/createReducer.ts +24 -0
- package/src/dispatch.ts +6 -0
- package/src/dispatchFuncMap.ts +22 -0
- package/src/genUUID.ts +38 -0
- package/src/getRelation.ts +35 -0
- package/src/index.ts +62 -0
- package/src/init.ts +82 -0
- package/src/reduceMap.ts +37 -0
- package/src/reducer.ts +9 -0
- package/src/remove.ts +76 -0
- package/src/removeChild.ts +46 -0
- package/src/removeLink.ts +47 -0
- package/src/removeRelation.ts +84 -0
- package/src/setData.ts +23 -0
- package/src/setRoot.ts +14 -0
- package/src/stateTypes.ts +62 -0
- package/src/states.ts +45 -0
- package/src/thunk.ts +16 -0
- package/src/thunkModuleFuncMap.ts +22 -0
- package/src/thunkReducer.ts +73 -0
- package/src/useThunk.ts +93 -0
- package/types/action.d.ts +11 -0
- package/types/addChild.d.ts +5 -0
- package/types/addLink.d.ts +6 -0
- package/types/addRelation.d.ts +6 -0
- package/types/createReducer.d.ts +4 -0
- package/types/dispatch.d.ts +4 -0
- package/types/dispatchFuncMap.d.ts +17 -0
- package/types/genUUID.d.ts +1 -0
- package/types/getRelation.d.ts +5 -0
- package/types/index.d.ts +19 -0
- package/types/init.d.ts +19 -0
- package/types/reduceMap.d.ts +6 -0
- package/types/reducer.d.ts +5 -0
- package/types/reducerModuleFuncMap.d.ts +10 -0
- package/types/remove.d.ts +5 -0
- package/types/removeChild.d.ts +9 -0
- package/types/removeLink.d.ts +9 -0
- package/types/removeRelation.d.ts +12 -0
- package/types/setData.d.ts +5 -0
- package/types/setRoot.d.ts +5 -0
- package/types/stateTypes.d.ts +42 -0
- package/types/states.d.ts +6 -0
- package/types/thunk-reducer.d.ts +16 -0
- package/types/thunk.d.ts +12 -0
- package/types/thunkModuleFuncMap.d.ts +10 -0
- package/types/thunkReducer.d.ts +18 -0
- package/types/useReducer.d.ts +8 -0
- package/types/useThunk.d.ts +8 -0
package/src/genUUID.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
2
|
+
|
|
3
|
+
const _GLOBAL_IDS = new Set()
|
|
4
|
+
|
|
5
|
+
const _GEN_UUID_COUNT = 3
|
|
6
|
+
|
|
7
|
+
const _GEN_UUID_STATE = {
|
|
8
|
+
iterate: 1,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const genUUID = (myuuidv4?: () => string): string => {
|
|
12
|
+
let theID = ''
|
|
13
|
+
let isAdd = false
|
|
14
|
+
for (let i = 0; i < _GEN_UUID_COUNT; i++) {
|
|
15
|
+
theID = genUUIDCore(myuuidv4)
|
|
16
|
+
if (_GLOBAL_IDS.has(theID)) {
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
_GLOBAL_IDS.add(theID)
|
|
20
|
+
isAdd = true
|
|
21
|
+
break
|
|
22
|
+
}
|
|
23
|
+
if (isAdd) {
|
|
24
|
+
return theID
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_GEN_UUID_STATE.iterate += 1
|
|
28
|
+
theID = genUUIDCore(myuuidv4)
|
|
29
|
+
return theID
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const genUUIDCore = (myuuid: () => string = uuidv4): string => {
|
|
33
|
+
let theID = ''
|
|
34
|
+
for (let j = 0; j < _GEN_UUID_STATE.iterate; j++) {
|
|
35
|
+
theID += myuuid()
|
|
36
|
+
}
|
|
37
|
+
return theID
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type NodeState, Relation, type State } from './stateTypes'
|
|
2
|
+
|
|
3
|
+
export const getChildIDs = <S extends State>(me: NodeState<S>, childClass: string): string[] => {
|
|
4
|
+
return getRelationIDs(me, childClass, Relation.CHILDREN)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const getChildID = <S extends State>(me: NodeState<S>, childClass: string): string => {
|
|
8
|
+
return getRelationID(me, childClass, Relation.CHILDREN)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const getLinkIDs = <S extends State>(me: NodeState<S>, linkClass: string): string[] => {
|
|
12
|
+
return getRelationIDs(me, linkClass, Relation.LINKS)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getLinkID = <S extends State>(me: NodeState<S>, linkClass: string): string => {
|
|
16
|
+
return getRelationID(me, linkClass, Relation.LINKS)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const getRelationIDs = <S extends State>(me: NodeState<S>, relationClass: string, relationName: Relation): string[] => {
|
|
20
|
+
const relations = me[relationName]
|
|
21
|
+
if (!relations) {
|
|
22
|
+
return []
|
|
23
|
+
}
|
|
24
|
+
const relationsByClass = relations[relationClass]
|
|
25
|
+
if (!relationsByClass) {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return relationsByClass.list
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const getRelationID = <S extends State>(me: NodeState<S>, relationClass: string, relationName: Relation): string => {
|
|
33
|
+
const ids = getRelationIDs(me, relationClass, relationName)
|
|
34
|
+
return ids.length ? ids[0] : ''
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { GetClassState, Thunk } from './action'
|
|
2
|
+
import { addChild } from './addChild'
|
|
3
|
+
import { addLink } from './addLink'
|
|
4
|
+
import type { AddRelationAction } from './addRelation'
|
|
5
|
+
import type { Dispatch } from './dispatch'
|
|
6
|
+
import type { DispatchFuncMap } from './dispatchFuncMap'
|
|
7
|
+
import { genUUID } from './genUUID'
|
|
8
|
+
import { getChildID, getChildIDs, getLinkID, getLinkIDs } from './getRelation'
|
|
9
|
+
import { type InitParams, init } from './init'
|
|
10
|
+
import { remove } from './remove'
|
|
11
|
+
import { removeChild } from './removeChild'
|
|
12
|
+
import { removeLink } from './removeLink'
|
|
13
|
+
import type { RemoveRelationAction } from './removeRelation'
|
|
14
|
+
import { setData } from './setData'
|
|
15
|
+
import { getNode, getRootID, getState } from './states'
|
|
16
|
+
import type { ClassState, NodeMeta, NodeState, NodeStateMap, State } from './stateTypes'
|
|
17
|
+
import type { ThunkModule, ThunkModuleToFunc } from './thunk'
|
|
18
|
+
import useThunk from './useThunk'
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
useThunk,
|
|
22
|
+
// StateType, // XXX for global state
|
|
23
|
+
type State,
|
|
24
|
+
type NodeState,
|
|
25
|
+
type NodeMeta,
|
|
26
|
+
type NodeStateMap,
|
|
27
|
+
// type NodeStateMapByClass, // XXX for global state
|
|
28
|
+
type ClassState,
|
|
29
|
+
type GetClassState,
|
|
30
|
+
// type BaseAction, // XXX deemphasize action
|
|
31
|
+
type Thunk,
|
|
32
|
+
// type ActionOrThunk, // XXX deemphasize action
|
|
33
|
+
// type ActionFunc, // XXX deemphasize action
|
|
34
|
+
// type Reducer, // XXX deemphasize reducer
|
|
35
|
+
type ThunkModule,
|
|
36
|
+
type ThunkModuleToFunc,
|
|
37
|
+
// type ReduceFunc, // XXX deemphasize reducer
|
|
38
|
+
type Dispatch,
|
|
39
|
+
type DispatchFuncMap,
|
|
40
|
+
// type DefaultDispatchFuncMap, // XXX deemphasize default
|
|
41
|
+
getRootID,
|
|
42
|
+
getNode,
|
|
43
|
+
getState,
|
|
44
|
+
getChildIDs,
|
|
45
|
+
getChildID,
|
|
46
|
+
getLinkIDs,
|
|
47
|
+
getLinkID,
|
|
48
|
+
init,
|
|
49
|
+
type InitParams,
|
|
50
|
+
setData,
|
|
51
|
+
remove,
|
|
52
|
+
addChild,
|
|
53
|
+
removeChild,
|
|
54
|
+
addLink,
|
|
55
|
+
removeLink,
|
|
56
|
+
type AddRelationAction,
|
|
57
|
+
type RemoveRelationAction,
|
|
58
|
+
// type DefaultThunkModuleFuncMap as DefaultReducerModuleFuncMap, // XXX deemphasize default
|
|
59
|
+
// type ReduceMap, // XXX deemphasize reducer
|
|
60
|
+
// createReducer, // XXX deemphasize reducer
|
|
61
|
+
genUUID,
|
|
62
|
+
}
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { BaseAction, Thunk } from './action'
|
|
2
|
+
import { genUUID } from './genUUID'
|
|
3
|
+
import { setRoot } from './setRoot'
|
|
4
|
+
import { type ClassState, type NodeState, type NodeStateMap, PARENT, Relation, type State } from './stateTypes'
|
|
5
|
+
|
|
6
|
+
// InitParams
|
|
7
|
+
export interface InitParams<S extends State> {
|
|
8
|
+
myID?: string
|
|
9
|
+
parentID?: string
|
|
10
|
+
// @ts-expect-error doParent can be any type.
|
|
11
|
+
doParent?: DispatchFuncMap
|
|
12
|
+
parentClass?: string
|
|
13
|
+
|
|
14
|
+
state: S
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const init = <S extends State>(params: InitParams<S>, myuuidv4?: () => string): Thunk<S> => {
|
|
18
|
+
return (dispatch, getClassState) => {
|
|
19
|
+
const myID = params.myID ?? genUUID(myuuidv4)
|
|
20
|
+
|
|
21
|
+
const { parentID, doParent, parentClass, state } = params
|
|
22
|
+
dispatch(initCore(myID, state, parentID, doParent, parentClass))
|
|
23
|
+
|
|
24
|
+
// @ts-expect-error XXX doMe is a hidden variable for children.
|
|
25
|
+
const { myClass, doMe, root } = getClassState()
|
|
26
|
+
|
|
27
|
+
// parent or root
|
|
28
|
+
if (parentID && doParent) {
|
|
29
|
+
doParent.addChild(parentID, { id: myID, theClass: myClass, do: doMe })
|
|
30
|
+
} else if (!root) {
|
|
31
|
+
dispatch(setRoot(myID))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface InitAction<S extends State> extends BaseAction {
|
|
37
|
+
parentID?: string
|
|
38
|
+
// @ts-expect-error doParent can be any type
|
|
39
|
+
doParent?: DispatchFuncMap
|
|
40
|
+
parentClass?: string
|
|
41
|
+
|
|
42
|
+
state: S
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const INIT = '@chhsiao1981/use-thunk/INIT'
|
|
46
|
+
const initCore = <S extends State>(
|
|
47
|
+
myID: string,
|
|
48
|
+
state: S,
|
|
49
|
+
parentID?: string,
|
|
50
|
+
// @ts-expect-error doParent can be any type
|
|
51
|
+
doParent?: DispatchFuncMap,
|
|
52
|
+
parentClass?: string,
|
|
53
|
+
): InitAction<S> => {
|
|
54
|
+
return {
|
|
55
|
+
myID,
|
|
56
|
+
type: INIT,
|
|
57
|
+
parentID,
|
|
58
|
+
doParent,
|
|
59
|
+
parentClass,
|
|
60
|
+
|
|
61
|
+
state,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const reduceInit = <S extends State>(state: ClassState<S>, action: InitAction<S>): ClassState<S> => {
|
|
66
|
+
const { myID, parentID, doParent, parentClass, state: initState } = action
|
|
67
|
+
|
|
68
|
+
const me: NodeState<S> = {
|
|
69
|
+
id: myID,
|
|
70
|
+
state: initState,
|
|
71
|
+
[Relation.CHILDREN]: {},
|
|
72
|
+
[Relation.LINKS]: {},
|
|
73
|
+
}
|
|
74
|
+
if (parentID && doParent) {
|
|
75
|
+
me[PARENT] = { id: parentID, do: doParent, theClass: parentClass ?? '' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const newNodes: NodeStateMap<S> = Object.assign({}, state.nodes, { [myID]: me })
|
|
79
|
+
const newState: ClassState<S> = Object.assign({}, state, { nodes: newNodes })
|
|
80
|
+
|
|
81
|
+
return newState
|
|
82
|
+
}
|
package/src/reduceMap.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ADD_CHILD, reduceAddChild } from './addChild'
|
|
2
|
+
import { ADD_LINK, reduceAddLink } from './addLink'
|
|
3
|
+
import { INIT, reduceInit } from './init'
|
|
4
|
+
import type { ReduceFunc } from './reducer'
|
|
5
|
+
import { REMOVE, reduceRemove } from './remove'
|
|
6
|
+
import { REMOVE_CHILD, reduceRemoveChild } from './removeChild'
|
|
7
|
+
import { REMOVE_LINK, reduceRemoveLink } from './removeLink'
|
|
8
|
+
import { reduceSetData, SET_DATA } from './setData'
|
|
9
|
+
import { reduceSetRoot, SET_ROOT } from './setRoot'
|
|
10
|
+
import type { State } from './stateTypes'
|
|
11
|
+
|
|
12
|
+
export interface ReduceMap<S extends State> {
|
|
13
|
+
[type: string]: ReduceFunc<S>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// default reduceMap
|
|
17
|
+
export const DEFAULT_REDUCE_MAP: <S extends State>() => ReduceMap<S> = () => ({
|
|
18
|
+
// @ts-expect-error baseAction in ReduceMap
|
|
19
|
+
[INIT]: reduceInit,
|
|
20
|
+
[SET_DATA]: reduceSetData,
|
|
21
|
+
[REMOVE]: reduceRemove,
|
|
22
|
+
|
|
23
|
+
// @ts-expect-error baseAction in ReduceMap
|
|
24
|
+
[ADD_CHILD]: reduceAddChild,
|
|
25
|
+
// @ts-expect-error baseAction in ReduceMap
|
|
26
|
+
[REMOVE_CHILD]: reduceRemoveChild,
|
|
27
|
+
|
|
28
|
+
// @ts-expect-error baseAction in ReduceMap
|
|
29
|
+
[ADD_LINK]: reduceAddLink,
|
|
30
|
+
// @ts-expect-error baseAction in ReduceMap
|
|
31
|
+
[REMOVE_LINK]: reduceRemoveLink,
|
|
32
|
+
|
|
33
|
+
// setRoot.
|
|
34
|
+
// Typically we don't need this in programming.
|
|
35
|
+
// The root is automatically determined if root is not set.
|
|
36
|
+
[SET_ROOT]: reduceSetRoot,
|
|
37
|
+
})
|
package/src/reducer.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Reducer as rReducer } from 'react'
|
|
2
|
+
import type { ActionFunc, BaseAction } from './action'
|
|
3
|
+
import type { ClassState, State } from './stateTypes'
|
|
4
|
+
|
|
5
|
+
// Reducer
|
|
6
|
+
export type Reducer<S extends State> = rReducer<ClassState<S>, BaseAction>
|
|
7
|
+
|
|
8
|
+
// ReduceFunc
|
|
9
|
+
export type ReduceFunc<S extends State> = (state: ClassState<S>, action: BaseAction) => ClassState<S>
|
package/src/remove.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { BaseAction, Thunk } from './action'
|
|
2
|
+
import { removeChild } from './removeChild'
|
|
3
|
+
import { removeLink } from './removeLink'
|
|
4
|
+
import { type ClassState, type NodeStateMap, PARENT, Relation, type State } from './stateTypes'
|
|
5
|
+
|
|
6
|
+
export const remove = <S extends State>(myID: string, isFromParent = false): Thunk<S> => {
|
|
7
|
+
return (dispatch, getClassState) => {
|
|
8
|
+
const state = getClassState()
|
|
9
|
+
const {
|
|
10
|
+
myClass,
|
|
11
|
+
nodes: { [myID]: me },
|
|
12
|
+
} = state
|
|
13
|
+
if (!me) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// parent removes me
|
|
18
|
+
const parent = me[PARENT]
|
|
19
|
+
if (!isFromParent && parent) {
|
|
20
|
+
const { id: parentID, do: doParent } = parent
|
|
21
|
+
if (parentID) {
|
|
22
|
+
doParent.removeChild(parentID, myID, myClass, true)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// remove children
|
|
27
|
+
const children = me[Relation.CHILDREN]
|
|
28
|
+
if (children) {
|
|
29
|
+
const realChildren = children
|
|
30
|
+
Object.keys(realChildren).map((eachClass) => {
|
|
31
|
+
const child = realChildren[eachClass]
|
|
32
|
+
child.list.map((eachID) => dispatch(removeChild(myID, eachID, eachClass, false)))
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// remove links
|
|
37
|
+
const links = me[Relation.LINKS] ?? {}
|
|
38
|
+
Object.keys(links).map((eachClass) => {
|
|
39
|
+
const link = links[eachClass]
|
|
40
|
+
link.list.map((eachID) => dispatch(removeLink(myID, eachID, eachClass, false)))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// remove me from myClass list
|
|
44
|
+
dispatch(removeCore(myID))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const REMOVE = '@chhsiao1981/use-thunk/REMOVE'
|
|
49
|
+
const removeCore = (myID: string): BaseAction => ({
|
|
50
|
+
myID,
|
|
51
|
+
type: REMOVE,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export const reduceRemove = <S extends State>(state: ClassState<S>, action: BaseAction): ClassState<S> => {
|
|
55
|
+
const { myID } = action
|
|
56
|
+
|
|
57
|
+
const me = state.nodes[myID]
|
|
58
|
+
if (!me) {
|
|
59
|
+
return state
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const newNodes = Object.keys(state.nodes)
|
|
63
|
+
.filter((each) => each !== myID)
|
|
64
|
+
.reduce((r: NodeStateMap<S>, x) => {
|
|
65
|
+
r[x] = state.nodes[x]
|
|
66
|
+
return r
|
|
67
|
+
}, {})
|
|
68
|
+
|
|
69
|
+
// root
|
|
70
|
+
const newState = Object.assign({}, state, { nodes: newNodes })
|
|
71
|
+
if (newState.root === myID) {
|
|
72
|
+
newState.root = null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return newState
|
|
76
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Thunk } from './action'
|
|
2
|
+
import { type RemoveRelationAction, reduceRemoveRelation, removeRelation } from './removeRelation'
|
|
3
|
+
import { type ClassState, Relation, type State } from './stateTypes'
|
|
4
|
+
|
|
5
|
+
/***
|
|
6
|
+
* remove-child
|
|
7
|
+
*/
|
|
8
|
+
export const removeChild = <S extends State>(
|
|
9
|
+
myID: string,
|
|
10
|
+
childID: string,
|
|
11
|
+
childClass: string,
|
|
12
|
+
isFromChild = false,
|
|
13
|
+
): Thunk<S> => {
|
|
14
|
+
return (dispatch, getClassState) => {
|
|
15
|
+
// @ts-expect-error theDo (from child) can by any type
|
|
16
|
+
const relationRemove = (theDo: DispatchFuncMap) => theDo.remove(childID, true)
|
|
17
|
+
removeRelation(
|
|
18
|
+
dispatch,
|
|
19
|
+
getClassState,
|
|
20
|
+
myID,
|
|
21
|
+
childID,
|
|
22
|
+
childClass,
|
|
23
|
+
isFromChild,
|
|
24
|
+
relationRemove,
|
|
25
|
+
removeChildCore,
|
|
26
|
+
Relation.CHILDREN,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const REMOVE_CHILD = '@chhsiao1981/use-thunk/REMOVE_CHILD'
|
|
32
|
+
const removeChildCore = (myID: string, childID: string, childClass: string): RemoveRelationAction => ({
|
|
33
|
+
myID,
|
|
34
|
+
type: REMOVE_CHILD,
|
|
35
|
+
relationID: childID,
|
|
36
|
+
relationClass: childClass,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const reduceRemoveChild = <S extends State>(
|
|
40
|
+
state: ClassState<S>,
|
|
41
|
+
action: RemoveRelationAction,
|
|
42
|
+
): ClassState<S> => {
|
|
43
|
+
const { myID, relationID, relationClass } = action
|
|
44
|
+
|
|
45
|
+
return reduceRemoveRelation(state, myID, relationID, relationClass, Relation.CHILDREN)
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Thunk } from './action'
|
|
2
|
+
import { type RemoveRelationAction, reduceRemoveRelation, removeRelation } from './removeRelation'
|
|
3
|
+
import { type ClassState, Relation, type State } from './stateTypes'
|
|
4
|
+
|
|
5
|
+
/***
|
|
6
|
+
* remove-link
|
|
7
|
+
*/
|
|
8
|
+
export const removeLink = <S extends State>(
|
|
9
|
+
myID: string,
|
|
10
|
+
linkID: string,
|
|
11
|
+
linkClass: string,
|
|
12
|
+
isFromLink = false,
|
|
13
|
+
): Thunk<S> => {
|
|
14
|
+
return (dispatch, getClassState) => {
|
|
15
|
+
const myClass = getClassState().myClass
|
|
16
|
+
// @ts-expect-error theDo (from link) can be any type
|
|
17
|
+
const relationRemove = (theDo: DispatchFuncMap) => theDo.removeLink(linkID, myID, myClass, true)
|
|
18
|
+
removeRelation(
|
|
19
|
+
dispatch,
|
|
20
|
+
getClassState,
|
|
21
|
+
myID,
|
|
22
|
+
linkID,
|
|
23
|
+
linkClass,
|
|
24
|
+
isFromLink,
|
|
25
|
+
relationRemove,
|
|
26
|
+
removeLinkCore,
|
|
27
|
+
Relation.LINKS,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const REMOVE_LINK = '@chhsiao1981/use-thunk/REMOVE_LINK'
|
|
33
|
+
const removeLinkCore = (myID: string, linkID: string, linkClass: string): RemoveRelationAction => ({
|
|
34
|
+
myID,
|
|
35
|
+
type: REMOVE_LINK,
|
|
36
|
+
relationID: linkID,
|
|
37
|
+
relationClass: linkClass,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const reduceRemoveLink = <S extends State>(
|
|
41
|
+
state: ClassState<S>,
|
|
42
|
+
action: RemoveRelationAction,
|
|
43
|
+
): ClassState<S> => {
|
|
44
|
+
const { myID, relationID, relationClass } = action
|
|
45
|
+
|
|
46
|
+
return reduceRemoveRelation(state, myID, relationID, relationClass, Relation.LINKS)
|
|
47
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { BaseAction, GetClassState } from './action'
|
|
2
|
+
import type { Dispatch } from './dispatch'
|
|
3
|
+
import type { ClassState, Relation, State } from './stateTypes'
|
|
4
|
+
|
|
5
|
+
export interface RemoveRelationAction extends BaseAction {
|
|
6
|
+
relationID: string
|
|
7
|
+
relationClass: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// @ts-expect-error DispatchFuncMap can be any type
|
|
11
|
+
type RelationRemove = (theDo: DispatchFuncMap) => void
|
|
12
|
+
type RemoveRelationCore = (myID: string, relationID: string, relationClass: string) => BaseAction
|
|
13
|
+
|
|
14
|
+
export const removeRelation = <S extends State>(
|
|
15
|
+
dispatch: Dispatch<S>,
|
|
16
|
+
getClassState: GetClassState<S>,
|
|
17
|
+
myID: string,
|
|
18
|
+
relationID: string,
|
|
19
|
+
relationClass: string,
|
|
20
|
+
isFromRelation: boolean,
|
|
21
|
+
relationRemove: RelationRemove,
|
|
22
|
+
removeRelationCore: RemoveRelationCore,
|
|
23
|
+
relationName: Relation,
|
|
24
|
+
) => {
|
|
25
|
+
const state = getClassState()
|
|
26
|
+
|
|
27
|
+
const me = state.nodes[myID]
|
|
28
|
+
if (!me) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const relation = me[relationName]
|
|
33
|
+
if (!relation) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
const relationByClass = relation[relationClass]
|
|
37
|
+
if (!relationByClass) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const newIDs = relationByClass.list.filter((eachID: string) => eachID !== relationID)
|
|
42
|
+
if (relationByClass.list.length === newIDs.length) return
|
|
43
|
+
|
|
44
|
+
if (!isFromRelation) {
|
|
45
|
+
relationRemove(relationByClass.do)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dispatch(removeRelationCore(myID, relationID, relationClass))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const reduceRemoveRelation = <S extends State>(
|
|
52
|
+
state: ClassState<S>,
|
|
53
|
+
myID: string,
|
|
54
|
+
relationID: string,
|
|
55
|
+
relationClass: string,
|
|
56
|
+
relationName: Relation.LINKS | Relation.CHILDREN,
|
|
57
|
+
): ClassState<S> => {
|
|
58
|
+
const me = state.nodes[myID]
|
|
59
|
+
if (!me) return state
|
|
60
|
+
|
|
61
|
+
const relation = me[relationName]
|
|
62
|
+
if (!relation) return state
|
|
63
|
+
|
|
64
|
+
const relationByClass = relation[relationClass]
|
|
65
|
+
if (!relationByClass) return state
|
|
66
|
+
|
|
67
|
+
const relationIDs = relationByClass.list || []
|
|
68
|
+
const newIDs = relationIDs.filter((eachID: string) => eachID !== relationID)
|
|
69
|
+
if (relationIDs.length === newIDs.length) return state
|
|
70
|
+
|
|
71
|
+
const newRelation = Object.assign({}, relation)
|
|
72
|
+
if (newIDs.length === 0) {
|
|
73
|
+
delete newRelation[relationClass]
|
|
74
|
+
} else {
|
|
75
|
+
const newRelationByClass = Object.assign({}, relationByClass, { list: newIDs })
|
|
76
|
+
newRelation[relationClass] = newRelationByClass
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const newMe = Object.assign({}, me, { [relationName]: newRelation })
|
|
80
|
+
const newNodes = Object.assign({}, state.nodes, { [myID]: newMe })
|
|
81
|
+
const newState = Object.assign({}, state, { nodes: newNodes })
|
|
82
|
+
|
|
83
|
+
return newState
|
|
84
|
+
}
|
package/src/setData.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BaseAction } from './action'
|
|
2
|
+
import type { ClassState, State } from './stateTypes'
|
|
3
|
+
|
|
4
|
+
export const SET_DATA = '@chhsiao1981/use-thunk/SET_DATA'
|
|
5
|
+
export const setData = <S extends State>(myID: string, data: S): BaseAction => ({
|
|
6
|
+
myID,
|
|
7
|
+
type: SET_DATA,
|
|
8
|
+
data,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const reduceSetData = <S extends State>(state: ClassState<S>, action: BaseAction): ClassState<S> => {
|
|
12
|
+
const { myID, data } = action
|
|
13
|
+
|
|
14
|
+
const me = state.nodes[myID]
|
|
15
|
+
if (!me) return state
|
|
16
|
+
|
|
17
|
+
const newMyState = Object.assign({}, me.state, data)
|
|
18
|
+
const newMe = Object.assign({}, me, { state: newMyState })
|
|
19
|
+
const newNodes = Object.assign({}, state.nodes, { [myID]: newMe })
|
|
20
|
+
const newState = Object.assign({}, state, { nodes: newNodes })
|
|
21
|
+
|
|
22
|
+
return newState
|
|
23
|
+
}
|
package/src/setRoot.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BaseAction } from './action'
|
|
2
|
+
import type { ClassState, State } from './stateTypes'
|
|
3
|
+
|
|
4
|
+
export const SET_ROOT = '@chhsiao1981/use-thunk/SET_ROOT'
|
|
5
|
+
export const setRoot = (myID: string): BaseAction => ({
|
|
6
|
+
myID,
|
|
7
|
+
type: SET_ROOT,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const reduceSetRoot = <S extends State>(state: ClassState<S>, action: BaseAction): ClassState<S> => {
|
|
11
|
+
const { myID } = action
|
|
12
|
+
|
|
13
|
+
return Object.assign({}, state, { root: myID })
|
|
14
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export enum StateType {
|
|
2
|
+
// SHARED = 'shared',
|
|
3
|
+
LOCAL = 'local',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export enum Relation {
|
|
7
|
+
CHILDREN = '_children',
|
|
8
|
+
LINKS = '_links',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PARENT = '_parent'
|
|
12
|
+
|
|
13
|
+
//State
|
|
14
|
+
export interface State {
|
|
15
|
+
[key: string]: unknown
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// NodeState
|
|
19
|
+
export type NodeState<S extends State> = {
|
|
20
|
+
id: string
|
|
21
|
+
state: S
|
|
22
|
+
[Relation.CHILDREN]?: NodeStateRelationMap | null
|
|
23
|
+
[PARENT]?: NodeMeta | null
|
|
24
|
+
[Relation.LINKS]?: NodeStateRelationMap | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// NodeStateRelation
|
|
28
|
+
type NodeStateRelationMap = {
|
|
29
|
+
[relationClass: string]: NodeStateRelation
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type NodeStateRelation = {
|
|
33
|
+
list: string[]
|
|
34
|
+
// @ts-expect-error do can be any type.
|
|
35
|
+
do: DispatchFuncMap
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type NodeStateMap<S extends State> = {
|
|
39
|
+
[key: string]: NodeState<S>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type NodeStateMapByClass<S extends State> = {
|
|
43
|
+
[className: string]: NodeStateMap<S>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ClassState
|
|
47
|
+
export type ClassState<S extends State> = {
|
|
48
|
+
myClass: string
|
|
49
|
+
root?: string | null
|
|
50
|
+
// XXX doMe is a hidden variable for ClassState
|
|
51
|
+
// used only for parents / children / links.
|
|
52
|
+
// doMe: DispatchFuncMap
|
|
53
|
+
nodes: NodeStateMap<S>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Node
|
|
57
|
+
export type NodeMeta = {
|
|
58
|
+
id: string
|
|
59
|
+
theClass: string
|
|
60
|
+
// @ts-expect-error do can be any type.
|
|
61
|
+
do: DispatchFuncMap
|
|
62
|
+
}
|
package/src/states.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ClassState, NodeState, State } from './stateTypes'
|
|
2
|
+
|
|
3
|
+
export const getRootNode = <S extends State>(state: ClassState<S>): NodeState<S> | null => {
|
|
4
|
+
const root = state.root
|
|
5
|
+
if (!root) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
return state.nodes[root] || null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const getRootID = <S extends State>(state: ClassState<S>): string => {
|
|
12
|
+
return state.root ?? ''
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getRoot = <S extends State>(state: ClassState<S>): S | null => {
|
|
16
|
+
const root = state.root
|
|
17
|
+
if (!root) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
const me = state.nodes[root]
|
|
21
|
+
if (!me) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
return me.state
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const getNode = <S extends State>(state: ClassState<S>, myID?: string): NodeState<S> | null => {
|
|
28
|
+
if (!myID) {
|
|
29
|
+
return getRootNode(state)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return state.nodes[myID] || null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getState = <S extends State>(state: ClassState<S>, myID?: string): S | null => {
|
|
36
|
+
if (!myID) {
|
|
37
|
+
return getRoot(state)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const me = state.nodes[myID]
|
|
41
|
+
if (!me) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
return me.state
|
|
45
|
+
}
|
package/src/thunk.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ActionFunc } from './action'
|
|
2
|
+
import type { Reducer } from './reducer'
|
|
3
|
+
import type { State } from './stateTypes'
|
|
4
|
+
|
|
5
|
+
export interface ThunkModuleFunc<S extends State> {
|
|
6
|
+
[action: string]: ActionFunc<S>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// This is used as the parameter for useThunk.
|
|
10
|
+
export type ThunkModule<S extends State, T extends ThunkModuleFunc<S>> = {
|
|
11
|
+
myClass: string
|
|
12
|
+
default?: Reducer<S>
|
|
13
|
+
defaultState?: S
|
|
14
|
+
} & T
|
|
15
|
+
|
|
16
|
+
export type ThunkModuleToFunc<T> = Omit<T, 'myClass' | 'default' | 'defaultState'>
|