Bin
2025-12-17 611bfe34c3c96199eaaf6cf9e41a75892e44e879
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
103
104
105
106
107
import { getParent, hasParent, types } from "mobx-state-tree";
import { FF_LSDV_4583, isFF } from "../utils/feature-flags";
 
/**
 * Looks for a given visibility parameter in the node or its parents.
 * For convenience users can specify visibility parameters in parent `View` tag up the tree.
 * @todo We should use it for all params but for now it's only used for `whenrole`.
 * @param {Object} node Control tag
 * @param {string} param Visibility parameter name (whenrole, whenlabelvalue, whenchoicevalue)
 * @returns {string|null}
 */
function findVisibilityParam(node, param) {
  while (node) {
    if (node[param]) {
      return node[param];
    }
    // all tags are sitting in `children` array of their parent,
    // so we need to go up 2 levels to get the parent
    if (!hasParent(node, 2)) break;
    node = getParent(node, 2);
  }
  return null;
}
 
const RequiredMixin = types
  .model({
    required: types.optional(types.boolean, false),
    requiredmessage: types.maybeNull(types.string),
  })
  .actions((self) => {
    const Super = {
      validate: self.validate,
    };
 
    return {
      validate() {
        if (!Super.validate()) return false;
        if (!self.required) return true;
 
        if (self.perregion) {
          // validating when choices labeling is done per region,
          // for example choice may be required to be selected for
          // every bbox
          const objectTag = self.toNameTag;
 
          // if regions don't meet visibility conditions skip validation
          for (const reg of objectTag.allRegs) {
            const s = reg.results.find((s) => s.from_name === self);
 
            if (self.visiblewhen === "region-selected") {
              if (self.whentagname) {
                const label = reg.labeling?.from_name?.name;
 
                if (label && label !== self.whentagname) continue;
              }
            }
 
            if (reg.role) {
              const whenRoles = findVisibilityParam(self, "whenrole")?.split(",") ?? null;
              if (whenRoles && !whenRoles.includes(reg.role)) continue;
            }
 
            if (self.whenlabelvalue && !reg.hasLabel(self.whenlabelvalue)) {
              continue;
            }
 
            if (!s?.hasValue) {
              self.annotation.selectArea(reg);
              self.requiredModal();
 
              return false;
            }
          }
        } else if (isFF(FF_LSDV_4583) && self.peritem) {
          // validating when choices labeling is done per item,
          const objectTag = self.toNameTag;
          const maxItemIndex = objectTag.maxItemIndex;
          const existingResultsIndexes = self.annotation.regions.reduce((existingResultsIndexes, reg) => {
            const result = reg.results.find((s) => s.from_name === self);
 
            if (result?.hasValue) {
              existingResultsIndexes.add(reg.item_index);
            }
            return existingResultsIndexes;
          }, new Set());
 
          for (let idx = 0; idx <= maxItemIndex; idx++) {
            if (!existingResultsIndexes.has(idx)) {
              objectTag.setCurrentItem(idx);
              self.requiredModal();
              return false;
            }
          }
        } else {
          // validation when its classifying the whole object
          // isVisible can be undefined (so comparison is true) or boolean (so check for visibility)
          if (!self.holdsState && self.isVisible !== false && getParent(self, 2)?.isVisible !== false) {
            self.requiredModal();
            return false;
          }
        }
        return true;
      },
    };
  });
 
export default RequiredMixin;