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

MarkUsProject / Markus / 12346096475

16 Dec 2024 04:46AM UTC coverage: 91.795% (+0.04%) from 91.76%
12346096475

Pull #7270

github

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

623 of 1353 branches covered (46.05%)

Branch coverage included in aggregate %.

115 of 145 new or added lines in 10 files covered. (79.31%)

1 existing line in 1 file now uncovered.

41320 of 44339 relevant lines covered (93.19%)

120.54 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
    this.setImageURL().then(() => {
×
16
      this.display_annotations();
×
17
      this.adjustPictureSize();
×
18
      this.rotateImage();
×
19
    });
20
  }
21

22
  componentDidMount() {
NEW
23
    this.setImageURL().then(() => {
×
NEW
24
      document.getElementById("image_preview").onload = () => {
×
NEW
25
        this.display_annotations();
×
NEW
26
        this.adjustPictureSize();
×
NEW
27
        this.rotateImage();
×
28
      };
29
    });
30
  }
31

32
  componentWillUnmount() {
33
    window.start_image_zoom = this.state.zoom;
×
34
    window.start_image_rotation = this.state.rotation;
×
35
  }
36

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

51
  display_annotations = () => {
×
52
    if (this.props.resultView && this.props.url) {
×
53
      this.ready_annotations();
×
54
      this.props.annotations.forEach(this.display_annotation);
×
55
    }
56
  };
57

58
  ready_annotations = () => {
×
59
    window.annotation_type = ANNOTATION_TYPES.IMAGE;
×
60

61
    $(".annotation_holder").remove();
×
62
    window.annotation_manager = new ImageAnnotationManager(!this.props.released_to_students);
×
63
    this.annotation_manager = window.annotation_manager;
×
64
  };
65

66
  display_annotation = annotation => {
×
67
    let content = "";
×
68
    if (!annotation.deduction) {
×
69
      content += annotation.content;
×
70
    } else {
71
      content +=
×
72
        annotation.content + " [" + annotation.criterion_name + ": -" + annotation.deduction + "]";
73
    }
74

75
    if (annotation.is_remark) {
×
76
      content += ` (${I18n.t("results.annotation.remark_flag")})`;
×
77
    }
78

79
    let originalImgH = document.getElementById("image_preview").height;
×
80
    let originalImgW = document.getElementById("image_preview").width;
×
81
    let imgW;
82
    let imgH;
83

84
    if (this.state.rotation === 90 || this.state.rotation === 270) {
×
85
      imgW = originalImgH;
×
86
      imgH = originalImgW;
×
87
    } else {
88
      imgW = originalImgW;
×
89
      imgH = originalImgH;
×
90
    }
91

92
    let midWidth = originalImgW / this.state.zoom / 2;
×
93
    let midHeight = originalImgH / this.state.zoom / 2;
×
94
    let midWidthRotated = imgW / 2;
×
95
    let midHeightRotated = imgH / 2;
×
96

97
    let xstart = annotation.x_range.start - midWidth;
×
98
    let xend = annotation.x_range.end - midWidth;
×
99
    let ystart = annotation.y_range.start - midHeight;
×
100
    let yend = annotation.y_range.end - midHeight;
×
101

102
    xstart *= this.state.zoom;
×
103
    xend *= this.state.zoom;
×
104
    yend *= this.state.zoom;
×
105
    ystart *= this.state.zoom;
×
106

107
    let topLeft = [xstart, ystart];
×
108
    let topRight = [xend, ystart];
×
109
    let bottomLeft = [xstart, yend];
×
110
    let bottomRight = [xend, yend];
×
111

112
    let rotatedTR = this.rotatedCoordinate(topRight, this.state.rotation);
×
113
    let rotatedTL = this.rotatedCoordinate(topLeft, this.state.rotation);
×
114
    let rotatedBL = this.rotatedCoordinate(bottomLeft, this.state.rotation);
×
115
    let rotatedBR = this.rotatedCoordinate(bottomRight, this.state.rotation);
×
116

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

136
    this.annotation_manager.addAnnotation(
×
137
      annotation.annotation_text_id,
138
      content,
139
      {
140
        x_range: {
141
          start: Math.floor(midWidthRotated + corners[1][0]),
142
          end: Math.floor(midWidthRotated + corners[2][0]),
143
        },
144
        y_range: {
145
          start: Math.floor(midHeightRotated + corners[1][1]),
146
          end: Math.floor(midHeightRotated + corners[0][1]),
147
        },
148
      },
149
      annotation.id,
150
      annotation.is_remark
151
    );
152
  };
153

154
  rotatedCoordinate = (coordinate, rotation) => {
×
155
    if (rotation > 0) {
×
156
      return this.rotatedCoordinate([-coordinate[1], coordinate[0]], rotation - 90);
×
157
    }
158
    return coordinate;
×
159
  };
160

161
  addRotation = () => {
×
162
    this.setState(
×
163
      {
164
        rotation: this.state.rotation + 90 > 270 ? 0 : this.state.rotation + 90,
×
165
      },
166
      this.rotateImage
167
    );
168
  };
169

170
  adjustPictureSize = () => {
×
171
    let picture = document.getElementById("image_preview");
×
172
    picture.width = Math.floor(picture.naturalWidth * this.state.zoom);
×
173
  };
174

175
  zoomIn = () => {
×
176
    this.setState(prevState => {
×
177
      return {zoom: prevState.zoom + 0.1};
×
178
    }, this.adjustPictureSize);
179
  };
180

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

187
  rotateImage = () => {
×
188
    let picture = document.getElementById("image_preview");
×
189

190
    if (this.state.rotation > 0) {
×
191
      picture.addClass("rotate" + this.state.rotation.toString());
×
192
      picture.removeClass("rotate" + (this.state.rotation - 90).toString());
×
193
    } else {
194
      picture.removeClass("rotate270");
×
195
    }
196
  };
197

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