-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
102 lines (85 loc) · 2.75 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import BaseError from '@ianwalter/base-error'
const findItemByKey = (items = [], key) => items.find(item => item.key === key)
class NoChildrenError extends BaseError {
constructor (node) {
super('No children found to move to', { node })
}
}
class NoLeadsToError extends BaseError {
constructor (key, option) {
super('Cannot determine which child to move to', { key, option })
}
}
class NoParentError extends BaseError {
constructor () {
super('No parent node found to move to')
}
}
class DecisionTree {
constructor (tree = {}, path = [tree], state = {}) {
this.path = path
this.state = state
}
set (key, value) {
if (typeof key === 'object' && value === undefined) {
this.state = key
} else {
this.state[key] = value
}
return this
}
current () {
return this.path[this.path.length - 1]
}
goToNode (node) {
return node ? this.path.push(node) && node : node
}
getNodeFromLeadsTo (currentNode, { leadsTo }) {
const key = typeof leadsTo === 'function' ? leadsTo(this) : leadsTo
return findItemByKey(currentNode && currentNode.children, key)
}
next () {
const currentNode = this.current()
// Get the selected option key from state or extract it from an array if
// multiple options can be selected.
let selectedOptionKey = this.state[currentNode.key]
if (Array.isArray(selectedOptionKey) && selectedOptionKey.length === 1) {
selectedOptionKey = selectedOptionKey[0]
}
// Find the selected option object by it's key.
const selectedOption = findItemByKey(currentNode.options, selectedOptionKey)
// Move to the next node.
if (currentNode.children.length < 1) {
// No children to move to!
throw new NoChildrenError(currentNode)
} else if (currentNode.children.length === 1) {
// Move to the only child.
return this.goToNode(currentNode.children[0])
} else if (selectedOption && selectedOption.leadsTo) {
// Move to what the single selected option tells us to move to.
return this.goToNode(this.getNodeFromLeadsTo(currentNode, selectedOption))
} else if (currentNode.leadsTo) {
// Move to what the currentNode tells us to move to (maybe there are
// multiple options selected).
return this.goToNode(this.getNodeFromLeadsTo(currentNode, currentNode))
}
// Throw an error if the next node to move to can't be determined.
throw new NoLeadsToError(selectedOptionKey, selectedOption)
}
prev () {
const parentNode = this.path[this.path.length - 2]
if (parentNode) {
return this.path.pop()
}
throw new NoParentError()
}
pathKeys () {
return this.path.map(({ key }) => key)
}
}
export {
NoChildrenError,
NoLeadsToError,
NoParentError,
DecisionTree
}