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

MarkUsProject / Markus / 25973610160

16 May 2026 09:38PM UTC coverage: 90.173% (-1.6%) from 91.776%
25973610160

push

github

web-flow
Moved annotation-related Javascript to be bundled with webpack (#7953)

971 of 2151 branches covered (45.14%)

Branch coverage included in aggregate %.

4 of 157 new or added lines in 5 files covered. (2.55%)

45700 of 49606 relevant lines covered (92.13%)

128.1 hits per line

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

61.0
/app/javascript/Components/Result/pdf_viewer.jsx
1
import React from "react";
2
import {SingleSelectDropDown} from "../DropDown/SingleSelectDropDown";
3
import {PdfAnnotationManager} from "../../common/annotations/pdf_annotation_manager";
4

5
export class PDFViewer extends React.PureComponent {
6
  constructor(props) {
7
    super(props);
8✔
8
    this.pdfContainer = React.createRef();
8✔
9
    this.state = {
8✔
10
      zoom: "page-width",
11
      rotation: 0, // NOTE: this is in degrees
12
    };
13
  }
14

15
  componentDidMount() {
16
    this.eventBus = new pdfjsViewer.EventBus();
8✔
17
    this.pdfViewer = new pdfjsViewer.PDFViewer({
8✔
18
      eventBus: this.eventBus,
19
      container: this.pdfContainer.current,
20
    });
21
    window.pdfViewer = this; // For fixing display when pane width changes
8✔
22

23
    if (this.props.resultView) {
8!
24
      this.eventBus.on("pagesinit", this.ready_annotations);
8✔
25
      this.eventBus.on("pagesloaded", this.refresh_annotations);
8✔
26
    } else {
27
      this.eventBus.on("pagesloaded", this.update_pdf_view);
×
28
    }
29

30
    if (this.props.url) {
8!
31
      this.loadPDFFile();
×
32
    }
33
  }
34

35
  componentDidUpdate(prevProps) {
36
    if (this.props.url && this.props.url !== prevProps.url) {
10!
37
      this.loadPDFFile();
×
38
    } else {
39
      if (this.props.resultView) {
10!
40
        this.refresh_annotations();
10✔
41
      } else {
42
        this.update_pdf_view();
×
43
      }
44
    }
45
  }
46

47
  loadPDFFile = () => {
8✔
48
    pdfjs.getDocument(this.props.url).promise.then(pdfDocument => {
×
49
      this.pdfViewer.setDocument(pdfDocument);
×
50
    });
51
  };
52

53
  ready_annotations = () => {
8✔
NEW
54
    window.annotation_type = window.ANNOTATION_TYPES.PDF;
×
55

56
    window.annotation_manager = new PdfAnnotationManager(!this.props.released_to_students);
×
57
    window.annotation_manager.resetAngle();
×
58
    this.annotation_manager = window.annotation_manager;
×
59
  };
60

61
  componentWillUnmount() {
62
    let box = document.getElementById("sel_box");
8✔
63
    if (box) {
8!
64
      box.style.display = "none";
8✔
65
      box.style.width = "0";
8✔
66
      box.style.height = "0";
8✔
67
    }
68
    this.eventBus = null;
8✔
69
    window.pdfViewer = undefined;
8✔
70
  }
71

72
  update_pdf_view = () => {
8✔
73
    if (
10!
74
      !!document.getElementById("pdfContainer") &&
20✔
75
      !!document.getElementById("pdfContainer").offsetParent
76
    ) {
77
      this.pdfViewer.currentScaleValue = this.state.zoom;
10✔
78
      this.pdfViewer.pagesRotation = this.state.rotation;
10✔
79
    }
80
  };
81

82
  refresh_annotations = () => {
8✔
83
    $(".annotation_holder").remove();
10✔
84
    this.update_pdf_view();
10✔
85
    this.props.annotations.forEach(this.display_annotation);
10✔
86
    if (!!this.props.annotationFocus) {
10!
87
      document.getElementById("annotation_holder_" + this.props.annotationFocus).scrollIntoView();
×
88
    }
89
  };
90

91
  rotate = () => {
8✔
92
    if (this.props.resultView) {
5!
93
      annotation_manager.rotateClockwise90();
5✔
94
    }
95

96
    this.setState(({rotation}) => ({
5✔
97
      rotation: (rotation + 90) % 360,
98
    }));
99
  };
100

101
  display_annotation = annotation => {
8✔
102
    if (annotation.x_range === undefined || annotation.y_range === undefined) {
×
103
      return;
×
104
    }
105
    let content = "";
×
106
    if (!annotation.deduction) {
×
107
      content += annotation.content;
×
108
    } else {
109
      content +=
×
110
        annotation.content + " [" + annotation.criterion_name + ": -" + annotation.deduction + "]";
111
    }
112

113
    if (annotation.is_remark) {
×
114
      content += ` (${I18n.t("results.annotation.remark_flag")})`;
×
115
    }
116

117
    this.annotation_manager.addAnnotation(
×
118
      annotation.annotation_text_id,
119
      safe_marked(content),
120
      {
121
        x1: annotation.x_range.start,
122
        x2: annotation.x_range.end,
123
        y1: annotation.y_range.start,
124
        y2: annotation.y_range.end,
125
        page: annotation.page,
126
      },
127
      annotation.id,
128
      annotation.is_remark
129
    );
130
  };
131

132
  getZoomValuesToDisplayName = () => {
8✔
133
    // 25-200 in increments of 25
134
    const zoomLevels = Array.from({length: (200 - 25) / 25 + 1}, (_, i) =>
18✔
135
      ((i * 25 + 25) / 100).toFixed(2)
144✔
136
    );
137

138
    const valueToDisplayName = zoomLevels.reduce(
18✔
139
      (acc, value) => {
140
        acc[value] = `${(value * 100).toFixed(0)} %`;
144✔
141
        return acc;
144✔
142
      },
143
      {"page-width": I18n.t("results.fit_to_page_width")}
144
    );
145

146
    return valueToDisplayName;
18✔
147
  };
148

149
  render() {
150
    const cursor = this.props.released_to_students ? "default" : "crosshair";
18!
151
    const userSelect = this.props.released_to_students ? "default" : "none";
18!
152
    const zoomValuesToDisplayName = this.getZoomValuesToDisplayName();
18✔
153

154
    return (
18✔
155
      <React.Fragment>
156
        <div className="toolbar">
157
          <div className="toolbar-actions">
158
            {I18n.t("results.current_rotation", {rotation: this.state.rotation})}
159
            <button onClick={this.rotate} className={"inline-button"}>
160
              {I18n.t("results.rotate_image")}
161
            </button>
162
            <span style={{marginLeft: "7px"}}>{I18n.t("results.zoom")}</span>
163
            <SingleSelectDropDown
164
              valueToDisplayName={zoomValuesToDisplayName}
165
              options={Object.keys(zoomValuesToDisplayName)}
166
              selected={this.state.zoom}
167
              dropdownStyle={{
168
                minWidth: "auto",
169
                width: "fit-content",
170
                marginLeft: "5px",
171
                verticalAlign: "middle",
172
              }}
173
              selectionStyle={{width: "90px", marginRight: "0px"}}
174
              hideXMark={true}
175
              onSelect={selection => {
176
                this.setState({zoom: selection});
5✔
177
              }}
178
            />
179
          </div>
180
        </div>
181
        <div className="pdfContainerParent">
182
          <div
183
            id="pdfContainer"
184
            className="pdfContainer"
185
            style={{cursor, userSelect}}
186
            ref={this.pdfContainer}
187
          >
188
            <div id="viewer" className="pdfViewer" />
189
            <div
190
              key="sel_box"
191
              id="sel_box"
192
              className="annotation-holder-active"
193
              style={{display: "none"}}
194
            />
195
          </div>
196
        </div>
197
      </React.Fragment>
198
    );
199
  }
200
}
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