-
Notifications
You must be signed in to change notification settings - Fork 79
/
getDescendants.ts
92 lines (79 loc) · 3.29 KB
/
getDescendants.ts
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
import _ from 'lodash'
import { compareByRank, sort, unroot } from '../util'
import { getChildrenRanked } from '../selectors'
import { Context, Thought, State, ThoughtId } from '../@types'
import { getAllChildrenAsThoughts } from './getChildren'
import childIdsToThoughts from './childIdsToThoughts'
import getThoughtById from './getThoughtById'
interface OptionsPath {
filterFunction?: (thought: Thought) => boolean
ordered?: boolean
}
// use an internal flag to differentiate recursive calls
interface OptionsPathInternal extends OptionsPath {
recur?: boolean
}
interface OptionsContext {
filterFunction?: (thought: Thought, context: Context) => boolean
ordered?: boolean
}
// use an internal flag to differentiate recursive calls
interface OptionsContextInternal extends OptionsContext {
recur?: boolean
}
/** Generates a flat list of all descendant Contexts. If a filterFunction is provided, descendants of thoughts that are filtered out are not traversed. */
export const getDescendantContexts = (state: State, context: Context, options: OptionsContext = {}): Context[] => {
const { filterFunction, ordered, recur } = options as OptionsContextInternal
const children = (ordered ? getChildrenRanked : getAllChildrenAsThoughts)(state, context)
const filteredChildren = filterFunction ? children.filter(thought => filterFunction(thought, context)) : children
// only append current thought in recursive calls
return (recur ? [context] : []).concat(
_.flatMap(filteredChildren, child =>
getDescendantContexts(state, unroot([...context, child.value]), {
filterFunction,
recur: true,
ordered,
} as OptionsContext),
),
)
}
/** Generates a flat list of all descendant Paths. If a filterFunction is provided, descendants of thoughts that are filtered out are not traversed. */
export const getDescendantThoughtIds = (state: State, thoughtId: ThoughtId, options: OptionsPath = {}): ThoughtId[] => {
const { filterFunction, ordered, recur } = options as OptionsPathInternal
const thought = getThoughtById(state, thoughtId)
if (!thought) return []
const thoughts = childIdsToThoughts(state, thought.children) || []
const children = ordered ? sort(thoughts, compareByRank) : thoughts
if (!children) return []
const filteredChildren = filterFunction ? children.filter(thought => filterFunction(thought)) : children
// only append current thought in recursive calls
return (recur ? [thoughtId] : []).concat(
_.flatMap(filteredChildren, child =>
getDescendantThoughtIds(state, child.id, {
filterFunction,
recur: true,
ordered,
} as OptionsPath),
),
)
}
/** Returns true if any descendants of the given Context fulfills the predicate. Short circuits once found. */
export const someDescendants = (
state: State,
context: Context,
predicate: (thought: Thought, context: Context) => boolean,
) => {
let found = false
// ignore the return value of getDescendants
// we are just using its filterFunction to check pending
getDescendantContexts(state, context, {
filterFunction: (thought, context) => {
if (predicate(thought, context)) {
found = true
}
// if pending has been found, return false to filter out all remaining children and short circuit
return !found
},
})
return found
}