Bin
2025-12-17 dcf780a91c16b6be28635b6e2e0e702060ee19f2
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
import { getRoot, protect, types, unprotect } from "mobx-state-tree";
import ProcessAttrsMixin from "./ProcessAttrs";
import { parseValue } from "../utils/data";
import { guidGenerator } from "../utils/unique";
import { FF_DEV_3391 } from "../utils/feature-flags";
import { ff } from "@humansignal/core";
 
const DynamicChildrenMixin = types
  .model({})
  .views(() => ({
    get defaultChildType() {
      console.error("DynamicChildrenMixin needs to implement defaultChildType getter in views");
      return undefined;
    },
  }))
  .actions((self) => {
    const prepareDynamicChildrenData = (data, store, parent) => {
      // Right now with the FF on, we are traverseing the Tree in the store::initRoot and the
      // AnnotationStore::afterCreate which will generate duplicated children so we only need to
      // run this once, when the annotation store is not yet initialized
      if (ff.isActive(FF_DEV_3391) && store.annotationStore.initialized) return;
 
      if (data && data.length) {
        for (const obj of data) {
          // No matter if using Interactive View mode or not, we add the ids for consistency
          const id = obj.id ?? guidGenerator();
          parent.children.push({
            type: self.defaultChildType,
            ...obj,
            id,
            children: [],
          });
 
          const child = parent.children[parent.children.length - 1];
 
          child.updateValue?.(store);
          prepareDynamicChildrenData(obj.children, store, child);
        }
      }
    };
 
    const postprocessDynamicChildren = (children, store) => {
      children?.forEach((item) => {
        postprocessDynamicChildren(item.children, store);
        item.updateValue?.(store);
      });
    };
 
    return {
      updateWithDynamicChildren(data, store) {
        const root = getRoot(self);
 
        self.children = self.children ?? [];
 
        unprotect(root);
        prepareDynamicChildrenData(data, store, self);
        protect(root);
      },
 
      updateValue(store) {
        // If we want to use resolveValue or another asynchronous method here
        // we may need to rewrite this, initRoot and the other related methods
        // (actually a lot of them) to work asynchronously as well
 
        if (ff.isActive(FF_DEV_3391)) {
          self.updateDynamicChildren(store);
        } else {
          setTimeout(() => {
            self.updateDynamicChildren(store);
          });
        }
      },
 
      updateDynamicChildren(store) {
        if (self.locked !== true) {
          const valueFromTask = parseValue(self.value, store.task?.dataObj);
 
          if (!valueFromTask) return;
 
          self.updateWithDynamicChildren(valueFromTask, store);
          if (self.annotation) {
            self.annotation.setupHotKeys();
            self.needsUpdate?.();
          }
        }
      },
 
      generateDynamicChildren(data, store) {
        if (self.children) {
          const children = self.children;
          const len = children.length;
          const start = len - data.length;
          const slice = children.slice(start, len);
 
          postprocessDynamicChildren(slice, store);
        }
      },
    };
  });
 
export default types.compose(ProcessAttrsMixin, DynamicChildrenMixin);