1 | import { TreeNode } from '../types/Node'
|
2 | import { IterableValue, State as StateType } from '../types/State'
|
3 |
|
4 | import { recurseDown, rootElement } from '../utils/traveler'
|
5 | import {
|
6 | isRoot,
|
7 | copyObject,
|
8 | copyArray,
|
9 | hasOwnProp
|
10 | } from '../utils/index'
|
11 |
|
12 | function iterable(key: any, value: any): IterableValue[] {
|
13 | if ('string' === typeof key) {
|
14 | return [[key, value]]
|
15 | }
|
16 |
|
17 | if (!value) {
|
18 | const res: IterableValue[] = []
|
19 |
|
20 | for (const i in key) {
|
21 | if (hasOwnProp.call(key, i)) {
|
22 | res.push([i, key[i]])
|
23 | }
|
24 | }
|
25 |
|
26 | return res
|
27 | }
|
28 |
|
29 | return []
|
30 | }
|
31 |
|
32 | function replace(node: TreeNode): TreeNode {
|
33 | if (!node || !node.child) {
|
34 | return node
|
35 | }
|
36 |
|
37 | node.child = copyArray(node.child)
|
38 | node.child.forEach((child: TreeNode) => {
|
39 | const replaced = replace(child)
|
40 | replaced.parent = node
|
41 |
|
42 | return replaced
|
43 | })
|
44 |
|
45 | return node
|
46 | }
|
47 |
|
48 | function itemById(id: string, targetState: any): any {
|
49 | return Object.keys(targetState)
|
50 | .find((k: string) => targetState[k].id === id) || null
|
51 | }
|
52 |
|
53 | function updateChild(parentNode: TreeNode) {
|
54 | parentNode.child.forEach((child: TreeNode) => {
|
55 | child.parent = parentNode
|
56 | })
|
57 |
|
58 | return parentNode
|
59 | }
|
60 |
|
61 | export default class State {
|
62 | nodes: TreeNode[]
|
63 |
|
64 | constructor(state: TreeNode[]) {
|
65 | this.nodes = state
|
66 | }
|
67 |
|
68 | updateRoot(node: TreeNode, iterableValue?: IterableValue[]) {
|
69 | const i = itemById(node.id, this.nodes)
|
70 | const newObj = copyObject(node)
|
71 |
|
72 | if (iterableValue) {
|
73 | iterableValue.forEach(([key, value]: IterableValue) => {
|
74 | node[key] = value
|
75 | newObj[key] = value
|
76 | })
|
77 | }
|
78 |
|
79 | if (null !== i) {
|
80 | this.nodes[i] = updateChild(newObj as TreeNode)
|
81 | }
|
82 | }
|
83 |
|
84 | updateLeaf(node: TreeNode, iterableValue?: IterableValue[]) {
|
85 | const root: TreeNode | null = rootElement(node)
|
86 | const parentNode: TreeNode | null | undefined = node.parent
|
87 |
|
88 | if (!parentNode || !root || !parentNode.child) {
|
89 | return
|
90 | }
|
91 |
|
92 | const index = this.getIndex(node)
|
93 |
|
94 | if (null === index) {
|
95 | return
|
96 | }
|
97 |
|
98 | if (iterableValue) {
|
99 | iterableValue.forEach(([key, value]: IterableValue) => {
|
100 | node[key] = value
|
101 | parentNode.child[index][key] = value
|
102 | })
|
103 | }
|
104 |
|
105 | this.updateRoot(replace(root))
|
106 | }
|
107 |
|
108 | set(id: string, key: any, value?: any): StateType {
|
109 | const node = this.byId(id)
|
110 |
|
111 | if (!node) {
|
112 | return this
|
113 | }
|
114 |
|
115 | if (isRoot(node)) {
|
116 | this.updateRoot(node, iterable(key, value))
|
117 | } else {
|
118 | this.updateLeaf(node, iterable(key, value))
|
119 | }
|
120 |
|
121 | return this
|
122 | }
|
123 |
|
124 | getIndex(node: TreeNode): number | null {
|
125 | const parent: TreeNode | null = node.parent
|
126 | const nodeId: string = node.id
|
127 |
|
128 | if (parent && parent.child) {
|
129 | let childIndex: number | null = null
|
130 |
|
131 | parent.child.some((node: TreeNode, i: number): boolean => {
|
132 | if (nodeId === node.id) {
|
133 | childIndex = i
|
134 | return true
|
135 | }
|
136 |
|
137 | return false
|
138 | })
|
139 |
|
140 | return childIndex
|
141 | }
|
142 |
|
143 | for (let i = 0; i < this.nodes.length; i++ ) {
|
144 | if (this.nodes[i].id === nodeId) {
|
145 | return i
|
146 | }
|
147 | }
|
148 |
|
149 | return null
|
150 | }
|
151 |
|
152 | insertAt(parent: TreeNode | null, nodes: TreeNode[], index: number): TreeNode[] | TreeNode {
|
153 | if (parent && parent.child) {
|
154 | const child: TreeNode[] = copyArray(parent.child)
|
155 | child.splice(index, 0, ...nodes)
|
156 | } else {
|
157 | this.nodes.splice(index, 0, ...nodes)
|
158 | }
|
159 |
|
160 | return nodes
|
161 | }
|
162 |
|
163 | remove(id: string): TreeNode | null {
|
164 | const node: TreeNode | null = this.byId(id)
|
165 |
|
166 | if (!node) {
|
167 | return null
|
168 | }
|
169 |
|
170 | const index: number | null = this.getIndex(node)
|
171 |
|
172 | if (null === index || !~index) {
|
173 | return null
|
174 | }
|
175 |
|
176 | const parent: TreeNode | null = node.parent
|
177 |
|
178 | if (!parent) {
|
179 | this.nodes.splice(index, 1)
|
180 | } else {
|
181 | const child = copyArray(parent.child)
|
182 | child.splice(index, 1)
|
183 | this.set(parent.id, 'child', child)
|
184 | }
|
185 |
|
186 | return node
|
187 | }
|
188 |
|
189 | byId(id: string): TreeNode | null {
|
190 | let node: TreeNode | null = null
|
191 |
|
192 | recurseDown(this.nodes, (obj: TreeNode): any => {
|
193 | if (obj.id === id) {
|
194 | node = obj
|
195 | return false
|
196 | }
|
197 | })
|
198 |
|
199 | return node
|
200 | }
|
201 |
|
202 | get(): TreeNode[] {
|
203 | return this.nodes
|
204 | }
|
205 |
|
206 | toArray(): TreeNode[] {
|
207 | const result: TreeNode[] = []
|
208 | const state = this.nodes
|
209 |
|
210 | for (const i in state) {
|
211 | if (hasOwnProp.call(state, i)) {
|
212 | result.push(state[i])
|
213 | }
|
214 | }
|
215 |
|
216 | return result
|
217 | }
|
218 | } |
\ | No newline at end of file |