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

MarkUsProject / Markus / 12455551235

22 Dec 2024 03:34PM UTC coverage: 91.758% (-0.002%) from 91.76%
12455551235

Pull #7270

github

web-flow
Merge 31cdf3131 into ea33a3f40
Pull Request #7270: Refactor file viewer

623 of 1357 branches covered (45.91%)

Branch coverage included in aggregate %.

115 of 149 new or added lines in 9 files covered. (77.18%)

2 existing lines in 2 files now uncovered.

41180 of 44201 relevant lines covered (93.17%)

120.51 hits per line

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

0.0
/app/javascript/Components/Result/image_viewer.jsx
1
import React from "react";
2
import heic2any from "heic2any";
3

4
export class ImageViewer extends React.PureComponent {
5
  constructor(props) {
6
    super(props);
×
7
    this.state = {
×
8
      rotation: window.start_image_rotation || 0,
×
9
      zoom: window.start_image_zoom || 1,
×
10
      url: null,
11
    };
12
  }
13

14
  componentDidUpdate(prevProps, prevState) {
NEW
15
    if (this.props.url && this.props.url !== prevProps.url) {
×
NEW
16
      this.setImageURL().then(() => {
×
NEW
17
        this.display_annotations();
×
NEW
18
        this.adjustPictureSize();
×
NEW
19
        this.rotateImage();
×
20
      });
21
    } else {
22
      this.display_annotations();
×
23
      this.adjustPictureSize();
×
24
      this.rotateImage();
×
25
    }
26
  }
27

28
  componentDidMount() {
NEW
29
    this.setImageURL().then(() => {
×
NEW
30
      document.getElementById("image_preview").onload = () => {
×
NEW
31
        this.display_annotations();
×
NEW
32
        this.adjustPictureSize();
×
NEW
33
        this.rotateImage();
×
34
      };
35
    });
36
  }
37

38
  componentWillUnmount() {
39
    window.start_image_zoom = this.state.zoom;
×
40
    window.start_image_rotation = this.state.rotation;
×
41
  }
42

NEW
43
  setImageURL = () => {
×
44
    // Since HEIC and HEIF files can't be displayed, they must first be converted to JPEG.
NEW
45
    if (["image/heic", "image/heif"].includes(this.props.mime_type)) {
×
46
      // Returns a promise containing an object URL for a JPEG image converted from the HEIC/HEIF format.
NEW
47
      return fetch(this.props.url)
×
NEW
48
        .then(res => res.blob())
×
NEW
49
        .then(blob => heic2any({blob, toType: "image/jpeg"}))
×
NEW
50
        .then(conversionResult => URL.createObjectURL(conversionResult))
×
NEW
51
        .then(JPEGObjectURL => this.setState({url: JPEGObjectURL}));
×
52
    } else {
NEW
53
      return new Promise(() => this.setState({url: this.props.url}));
×
54
    }
55
  };
56

57
  display_annotations = () => {
×
58
    if (this.props.resultView && this.props.url) {
×
59
      this.ready_annotations();
×
60
      this.props.annotations.forEach(this.display_annotation);
×
61
    }
62
  };
63

64
  ready_annotations = () => {
×
65
    window.annotation_type = ANNOTATION_TYPES.IMAGE;
×
66

67
    $(".annotation_holder").remove();
×
68
    window.annotation_manager = new ImageAnnotationManager(!this.props.released_to_students);
×
69
    this.annotation_manager = window.annotation_manager;
×
70
  };
71

72
  display_annotation = annotation => {
×
73
    let content = "";
×
74
    if (!annotation.deduction) {
×
75
      content += annotation.content;
×
76
    } else {
77
      content +=
×
78
        annotation.content + " [" + annotation.criterion_name + ": -" + annotation.deduction + "]";
79
    }
80

81
    if (annotation.is_remark) {
×
82
      content += ` (${I18n.t("results.annotation.remark_flag")})`;
×
83
    }
84

85
    let originalImgH = document.getElementById("image_preview").height;
×
86
    let originalImgW = document.getElementById("image_preview").width;
×
87
    let imgW;
88
    let imgH;
89

90
    if (this.state.rotation === 90 || this.state.rotation === 270) {
×
91
      imgW = originalImgH;
×
92
      imgH = originalImgW;
×
93
    } else {
94
      imgW = originalImgW;
×
95
      imgH = originalImgH;
×
96
    }
97

98
    let midWidth = originalImgW / this.state.zoom / 2;
×
99
    let midHeight = originalImgH / this.state.zoom / 2;
×
100
    let midWidthRotated = imgW / 2;
×
101
    let midHeightRotated = imgH / 2;
×
102

103
    let xstart = annotation.x_range.start - midWidth;
×
104
    let xend = annotation.x_range.end - midWidth;
×
105
    let ystart = annotation.y_range.start - midHeight;
×
106
    let yend = annotation.y_range.end - midHeight;
×
107

108
    xstart *= this.state.zoom;
×
109
    xend *= this.state.zoom;
×
110
    yend *= this.state.zoom;
×
111
    ystart *= this.state.zoom;
×
112

113
    let topLeft = [xstart, ystart];
×
114
    let topRight = [xend, ystart];
×
115
    let bottomLeft = [xstart, yend];
×
116
    let bottomRight = [xend, yend];
×
117

118
    let rotatedTR = this.rotatedCoordinate(topRight, this.state.rotation);
×
119
    let rotatedTL = this.rotatedCoordinate(topLeft, this.state.rotation);
×
120
    let rotatedBL = this.rotatedCoordinate(bottomLeft, this.state.rotation);
×
121
    let rotatedBR = this.rotatedCoordinate(bottomRight, this.state.rotation);
×
122

123
    let corners;
124
    // index of coordinates in corners array matching position in plane
125
    //    1 - 2
126
    //    |
127
    //    0
128
    switch (this.state.rotation) {
×
129
      case 90:
130
        corners = [rotatedBR, rotatedBL, rotatedTL];
×
131
        break;
×
132
      case 180:
133
        corners = [rotatedTR, rotatedBR, rotatedBL];
×
134
        break;
×
135
      case 270:
136
        corners = [rotatedTL, rotatedTR, rotatedBR];
×
137
        break;
×
138
      default:
139
        corners = [rotatedBL, rotatedTL, rotatedTR];
×
140
    }
141

142
    this.annotation_manager.addAnnotation(
×
143
      annotation.annotation_text_id,
144
      content,
145
      {
146
        x_range: {
147
          start: Math.floor(midWidthRotated + corners[1][0]),
148
          end: Math.floor(midWidthRotated + corners[2][0]),
149
        },
150
        y_range: {
151
          start: Math.floor(midHeightRotated + corners[1][1]),
152
          end: Math.floor(midHeightRotated + corners[0][1]),
153
        },
154
      },
155
      annotation.id,
156
      annotation.is_remark
157
    );
158
  };
159

160
  rotatedCoordinate = (coordinate, rotation) => {
×
161
    if (rotation > 0) {
×
162
      return this.rotatedCoordinate([-coordinate[1], coordinate[0]], rotation - 90);
×
163
    }
164
    return coordinate;
×
165
  };
166

167
  addRotation = () => {
×
168
    this.setState(
×
169
      {
170
        rotation: this.state.rotation + 90 > 270 ? 0 : this.state.rotation + 90,
×
171
      },
172
      this.rotateImage
173
    );
174
  };
175

176
  adjustPictureSize = () => {
×
177
    let picture = document.getElementById("image_preview");
×
178
    picture.width = Math.floor(picture.naturalWidth * this.state.zoom);
×
179
  };
180

181
  zoomIn = () => {
×
182
    this.setState(prevState => {
×
183
      return {zoom: prevState.zoom + 0.1};
×
184
    }, this.adjustPictureSize);
185
  };
186

187
  zoomOut = () => {
×
188
    this.setState(prevState => {
×
189
      return {zoom: prevState.zoom - 0.1};
×
190
    }, this.adjustPictureSize);
191
  };
192

193
  rotateImage = () => {
×
194
    let picture = document.getElementById("image_preview");
×
195

196
    if (this.state.rotation > 0) {
×
197
      picture.addClass("rotate" + this.state.rotation.toString());
×
198
      picture.removeClass("rotate" + (this.state.rotation - 90).toString());
×
199
    } else {
200
      picture.removeClass("rotate270");
×
201
    }
202
  };
203

204
  render() {
205
    return (
×
206
      <React.Fragment>
207
        <p key={"image_toolbar"}>
208
          {I18n.t("results.current_rotation", {rotation: this.state.rotation})}
209
          <button onClick={this.addRotation} className={"inline-button"}>
210
            {I18n.t("results.rotate_image")}
211
          </button>
212
          {I18n.t("results.current_zoom_level", {
213
            level: Math.floor(this.state.zoom * 100),
214
          })}
215
          <button onClick={this.zoomIn} className={"inline-button"}>
216
            {I18n.t("results.zoom_in_image")}+
217
          </button>
218
          <button onClick={this.zoomOut} className={"inline-button"}>
219
            {I18n.t("results.zoom_out_image")}-
220
          </button>
221
        </p>
222
        <div id="image_container" key={"image_container"}>
223
          <div
224
            key="sel_box"
225
            id="sel_box"
226
            className="annotation-holder-active"
227
            style={{display: "none"}}
228
          />
229
          <img
230
            id="image_preview"
231
            src={this.state.url}
232
            data-zoom={this.state.zoom}
233
            className={this.props.released_to_students ? "" : "enable-annotations"}
×
234
            onLoad={this.display_annotations}
235
            alt={I18n.t("results.cant_display_image")}
236
          />
237
        </div>
238
      </React.Fragment>
239
    );
240
  }
241
}
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