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

MarkUsProject / Markus / 12436096582

20 Dec 2024 05:53PM UTC coverage: 91.764% (+0.004%) from 91.76%
12436096582

Pull #7270

github

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

623 of 1357 branches covered (45.91%)

Branch coverage included in aggregate %.

114 of 148 new or added lines in 9 files covered. (77.03%)

2 existing lines in 2 files now uncovered.

41179 of 44197 relevant lines covered (93.17%)

120.52 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
    }
22
  }
23

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

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

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

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

60
  ready_annotations = () => {
×
61
    window.annotation_type = ANNOTATION_TYPES.IMAGE;
×
62

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

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

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

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

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

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

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

104
    xstart *= this.state.zoom;
×
105
    xend *= this.state.zoom;
×
106
    yend *= this.state.zoom;
×
107
    ystart *= this.state.zoom;
×
108

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

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

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

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

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

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

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

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

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

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

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

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