• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zooniverse / front-end-monorepo / 13169035417

06 Feb 2025 12:14AM UTC coverage: 48.161%. First build
13169035417

Pull #6668

github

web-flow
Merge 8344f9346 into 17a396941
Pull Request #6668: Persist video volume + playback speed between classifications

6347 of 16521 branches covered (38.42%)

Branch coverage included in aggregate %.

27 of 31 new or added lines in 3 files covered. (87.1%)

11383 of 20293 relevant lines covered (56.09%)

51.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.46
/packages/lib-classifier/src/store/SubjectViewerStore/SubjectViewerStore.js
1
import asyncStates from '@zooniverse/async-states'
1✔
2
import { autorun, reaction } from 'mobx'
1✔
3
import { addDisposer, getRoot, isValidReference, tryReference, types } from 'mobx-state-tree'
1✔
4

5
const SubjectViewer = types
1✔
6
  .model('SubjectViewer', {
7
    annotate: types.optional(types.boolean, true),
8
    dimensions: types.array(types.frozen({
9
      clientHeight: types.integer,
10
      clientWidth: types.integer,
11
      naturalHeight: types.integer,
12
      naturalWidth: types.integer
13
    })),
14
    flipbookSpeed: types.optional(types.number, 1),
15
    frame: types.optional(types.integer, 0),
16
    fullscreen: types.optional(types.boolean, false),
17
    invert: types.optional(types.boolean, false),
18
    loadingState: types.optional(types.enumeration('loadingState', asyncStates.values), asyncStates.initialized),
19
    move: types.optional(types.boolean, false),
20
    rotationEnabled: types.optional(types.boolean, false),
21
    rotation: types.optional(types.number, 0),
22
    separateFramesView: types.optional(types.boolean, false),
23
    videoSpeed: types.optional(types.string, '1x'),
24
    volume: types.optional(types.number, 1)
25
  })
26

27
  .volatile(self => ({
36✔
28
    /*
29
    Callback function for subject viewers with custom zoom handlers.
30
    - 'type': 'zoomin', 'zoomout', 'zoomto'
31
    - 'zoomValue' defines amount zoomed in/out, or current zoom value of 'zoomto'.
32
     */
33
    onZoom: function (type, zoomValue) {},
34
    /*
35
    Callback function for subject viewers with custom pan handlers.
36
    - 'xDirection': -1: left, 0: ignored, 1: right
37
    - 'yDirection': -1: up, 0: ignored, 1: down
38
     */
39
    onPan: function (xDirection, yDirection) {}
40
  }))
41

42
  .views(self => ({
36✔
43
    get disableImageToolbar () {
44
      const subject = tryReference(() => getRoot(self).subjects?.active)
×
45
      const frameType = subject?.locations[self.frame].type
×
46
      if (frameType === 'text' || frameType === 'video') {
×
47
        return true
×
48
      }
49
      return false
×
50
    },
51

52
    get hasAnnotateTask () {
53
      return getRoot(self)?.workflowSteps.hasAnnotateTask
35!
54
    },
55

56
    get interactionMode () {
57
      // Default interaction mode is 'annotate'
58
      return (!self.annotate && self.move) ? 'move' : 'annotate'
×
59
    }
60
  }))
61

62
  .actions(self => {
63
    function createSubjectObserver () {
64
      const subjectDisposer = autorun(() => {
35✔
65
        const validSubjectReference = isValidReference(() => getRoot(self).subjects.active)
35✔
66
        if (validSubjectReference) {
35✔
67
          const subject = getRoot(self).subjects.active
35✔
68
          self.resetSubject(subject)
35✔
69
        }
70
      }, { name: 'SubjectViewerStore Subject Observer autorun' })
71
      addDisposer(self, subjectDisposer)
35✔
72
    }
73

74
    return {
36✔
75
      afterAttach () {
76
        function _syncAnnotateVisibility() {
77
          // Make sure the right button is active in the ImageToolbar
78
          self.setAnnotateVisibility(self.hasAnnotateTask)
35✔
79
          if (self.hasAnnotateTask) {
35✔
80
            self.enableAnnotate()
6✔
81
          } else {
82
            self.enableMove()
29✔
83
          }
84
        }
85
        addDisposer(self, autorun(_syncAnnotateVisibility))
35✔
86
        createSubjectObserver()
35✔
87
      },
88

89
      enableAnnotate () {
90
        self.annotate = true
12✔
91
        self.move = false
12✔
92
      },
93

94
      enableFullscreen () {
95
        self.fullscreen = true
×
96
      },
97

98
      enableMove () {
99
        self.annotate = false
58✔
100
        self.move = true
58✔
101
      },
102

103
      enableRotation () {
104
        self.rotationEnabled = true
5✔
105
      },
106

107
      disableFullscreen () {
108
        self.fullscreen = false
×
109
      },
110

111
      invertView () {
112
        self.invert = !self.invert
×
113
      },
114

115
      onError (error) {
116
        if (process.browser || process.env.NODE_ENV === 'test') console.error(error)
×
117
        self.loadingState = asyncStates.error
×
118
      },
119

120
      onSubjectReady (event) {
121
        const { target = {} } = event || {}
×
122
        const {
123
          clientHeight = 0,
×
124
          clientWidth = 0,
×
125
          naturalHeight = 0,
×
126
          naturalWidth = 0
×
127
        } = target || {}
×
128
        self.dimensions.push({ clientHeight, clientWidth, naturalHeight, naturalWidth })
×
129
        self.rotation = 0
×
130
        self.loadingState = asyncStates.success
×
131
      },
132

133
      panLeft () {
134
        self.onPan && self.onPan(-1, 0)
×
135
      },
136

137
      panRight () {
138
        self.onPan && self.onPan(1, 0)
×
139
      },
140

141
      panUp () {
142
        self.onPan && self.onPan(0, -1)
×
143
      },
144

145
      panDown () {
146
        self.onPan && self.onPan(0, 1)
×
147
      },
148

149
      resetSubject (subject) {
150
        let frame = 0
35✔
151
        // teams set default frame in the project builder
152
        // here we're converting it to index
153
        if (subject?.metadata?.default_frame > 0) {
35!
154
          frame = parseInt(subject.metadata.default_frame - 1)
1✔
155
        }
156
        self.dimensions = []
35✔
157
        self.frame = frame
35✔
158
        self.invert = false
35✔
159
        self.loadingState = asyncStates.loading
35✔
160
        self.rotation = 0
35✔
161
      },
162

163
      resetView () {
164
        console.log('resetting view')
×
165
        self.invert = false
×
166
        self.onZoom && self.onZoom('zoomto', 1.0)
×
167
        self.rotation = 0
×
168
      },
169

170
      rotate () {
171
        console.log('rotating subject')
×
172
        self.rotation -= 90
×
173
      },
174

175
      setAnnotateVisibility (canAnnotate) {
176
        if (canAnnotate) {
35✔
177
          self.enableAnnotate()
6✔
178
        } else {
179
          self.enableMove()
29✔
180
        }
181

182
        self.showAnnotate = canAnnotate
35✔
183
      },
184

185
      setFlipbookSpeed (speed) {
186
        self.flipbookSpeed = speed
×
187
      },
188

189
      setFrame (index) {
190
        self.frame = index
×
191
      },
192

193
      setOnZoom (callback) {
194
        self.onZoom = callback
×
195
      },
196

197
      setOnPan (callback) {
198
        self.onPan = callback
×
199
      },
200

201
      setSeparateFramesView(mode) {
202
        self.separateFramesView = mode
5✔
203
      },
204

205
      setVideoSpeed(value) {
NEW
206
        self.videoSpeed = value
×
207
      },
208

209
      setVolume(value) {
NEW
210
        self.volume = value
×
211
      },
212

213
      zoomIn () {
214
        console.log('zooming in')
×
215
        self.onZoom && self.onZoom('zoomin', 1)
×
216
      },
217

218
      zoomOut () {
219
        console.log('zooming out')
×
220
        self.onZoom && self.onZoom('zoomout', -1)
×
221
      }
222
    }
223
  })
1✔
224

225
export default SubjectViewer
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc