Bin
2025-12-17 bc6aa38242b0a7dea4b18bc90e2d78740436a58b
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { ff } from "@humansignal/core";
import { getEnv, getRoot, types } from "mobx-state-tree";
import { FF_DEV_3391 } from "../utils/feature-flags";
import { AnnotationMixin } from "./AnnotationMixin";
 
const ToolMixin = types
  .model({
    selected: types.optional(types.boolean, false),
    group: types.optional(types.string, "default"),
    shortcut: types.optional(types.maybeNull(types.string), null),
    disabled: false,
  })
  .views((self) => ({
    get obj() {
      if (ff.isActive(FF_DEV_3391)) {
        // It's a temporal solution (see root description)
        const root = self.manager?.root;
        if (root?.annotationStore.selected) {
          return root.annotationStore.selected.names.get(self.manager?.name);
        }
      }
      return self.manager?.obj ?? getEnv(self).object;
    },
 
    get manager() {
      return getEnv(self).manager;
    },
 
    get control() {
      if (ff.isActive(FF_DEV_3391)) {
        // It's a temporal solution (see root description)
        const control = getEnv(self).control;
        const { name } = control;
        const root = self.manager?.root;
        if (root?.annotationStore.selected) {
          return root.annotationStore.selected.names.get(name);
        }
        return control;
      }
      return getEnv(self).control;
    },
 
    get viewClass() {
      return () => null;
    },
 
    get fullName() {
      return self.toolName + (self.dynamic ? "-dynamic" : "");
    },
 
    get getActiveShape() {
      // active shape here is the last one that was added
      const obj = self.obj;
 
      return obj.regs[obj.regs.length - 1];
    },
 
    get getSelectedShape() {
      return self.control.annotation.highlightedNode;
    },
 
    get extraShortcuts() {
      return {};
    },
 
    get shouldPreserveSelectedState() {
      if ((!ff.isActive(FF_DEV_3391) && !self.obj) || !self.control) return false;
 
      const settings = getRoot(ff.isActive(FF_DEV_3391) ? self.control : self.obj).settings;
 
      return settings.preserveSelectedTool;
    },
 
    get isPreserved() {
      return window.localStorage.getItem(`selected-tool:${self.obj?.name}`) === self.fullName;
    },
  }))
  .actions((self) => ({
    setSelected(selected, isInitial) {
      self.selected = selected;
      self.afterUpdateSelected();
 
      if (!isInitial && selected && self.obj) {
        const storeName = `selected-tool:${self.obj.name}`;
 
        if (self.shouldPreserveSelectedState) {
          window.localStorage.setItem(storeName, self.fullName);
        }
      }
    },
 
    afterUpdateSelected() {},
 
    event(name, ev, args) {
      const fn = `${name}Ev`;
 
      if (typeof self[fn] !== "undefined") self[fn].call(self, ev, args);
    },
 
    /**
     * Indicates will the tool interact with the regions or not
     * It doesn't affect interactions with the canvas (zooming, drawing, etc.)
     * Some tools might override this method (at least MoveTool and ZoomTool)
     * @param e
     * @returns {boolean}
     */
    shouldSkipInteractions(e) {
      const isCtrlPressed = e.evt && (e.evt.metaKey || e.evt.ctrlKey);
      const hasSelection = self.control.annotation.hasSelection;
 
      return !!isCtrlPressed && !hasSelection;
    },
 
    disable() {
      self.disabled = true;
    },
 
    enable() {
      self.disabled = false;
    },
  }));
 
export default types.compose(ToolMixin, AnnotationMixin);