Commit | Line | Data |
---|---|---|
5c386472 DW |
1 | // This file is part of Moodle - http://moodle.org/ |
2 | // | |
3 | // Moodle is free software: you can redistribute it and/or modify | |
4 | // it under the terms of the GNU General Public License as published by | |
5 | // the Free Software Foundation, either version 3 of the License, or | |
6 | // (at your option) any later version. | |
7 | // | |
8 | // Moodle is distributed in the hope that it will be useful, | |
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | // GNU General Public License for more details. | |
12 | // | |
13 | // You should have received a copy of the GNU General Public License | |
14 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
ad3f8cd1 | 15 | /* global SELECTOR, COMMENTCOLOUR, COMMENTTEXTCOLOUR */ |
5c386472 DW |
16 | |
17 | /** | |
1f777e5c | 18 | * Provides an in browser PDF editor. |
5c386472 DW |
19 | * |
20 | * @module moodle-assignfeedback_editpdf-editor | |
21 | */ | |
22 | ||
23 | /** | |
1f777e5c | 24 | * Class representing a list of comments. |
5c386472 DW |
25 | * |
26 | * @namespace M.assignfeedback_editpdf | |
27 | * @class comment | |
28 | * @param M.assignfeedback_editpdf.editor editor | |
29 | * @param Int gradeid | |
30 | * @param Int pageno | |
31 | * @param Int x | |
32 | * @param Int y | |
33 | * @param Int width | |
34 | * @param String colour | |
35 | * @param String rawtext | |
36 | */ | |
557f44d9 | 37 | var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) { |
5c386472 DW |
38 | |
39 | /** | |
40 | * Reference to M.assignfeedback_editpdf.editor. | |
41 | * @property editor | |
42 | * @type M.assignfeedback_editpdf.editor | |
43 | * @public | |
44 | */ | |
45 | this.editor = editor; | |
46 | ||
47 | /** | |
48 | * Grade id | |
49 | * @property gradeid | |
50 | * @type Int | |
51 | * @public | |
52 | */ | |
53 | this.gradeid = gradeid || 0; | |
54 | ||
55 | /** | |
56 | * X position | |
57 | * @property x | |
58 | * @type Int | |
59 | * @public | |
60 | */ | |
61 | this.x = parseInt(x, 10) || 0; | |
62 | ||
63 | /** | |
64 | * Y position | |
65 | * @property y | |
66 | * @type Int | |
67 | * @public | |
68 | */ | |
69 | this.y = parseInt(y, 10) || 0; | |
70 | ||
71 | /** | |
72 | * Comment width | |
73 | * @property width | |
74 | * @type Int | |
75 | * @public | |
76 | */ | |
77 | this.width = parseInt(width, 10) || 0; | |
78 | ||
79 | /** | |
80 | * Comment rawtext | |
81 | * @property rawtext | |
82 | * @type String | |
83 | * @public | |
84 | */ | |
85 | this.rawtext = rawtext || ''; | |
86 | ||
87 | /** | |
88 | * Comment page number | |
89 | * @property pageno | |
90 | * @type Int | |
91 | * @public | |
92 | */ | |
93 | this.pageno = pageno || 0; | |
94 | ||
95 | /** | |
96 | * Comment background colour. | |
97 | * @property colour | |
98 | * @type String | |
99 | * @public | |
100 | */ | |
101 | this.colour = colour || 'yellow'; | |
102 | ||
103 | /** | |
104 | * Reference to M.assignfeedback_editpdf.drawable | |
105 | * @property drawable | |
106 | * @type M.assignfeedback_editpdf.drawable | |
107 | * @public | |
108 | */ | |
109 | this.drawable = false; | |
110 | ||
111 | /** | |
112 | * Boolean used by a timeout to delete empty comments after a short delay. | |
113 | * @property deleteme | |
114 | * @type Boolean | |
115 | * @public | |
116 | */ | |
117 | this.deleteme = false; | |
118 | ||
119 | /** | |
120 | * Reference to the link that opens the menu. | |
121 | * @property menulink | |
122 | * @type Y.Node | |
123 | * @public | |
124 | */ | |
125 | this.menulink = null; | |
126 | ||
127 | /** | |
128 | * Reference to the dialogue that is the context menu. | |
129 | * @property menu | |
130 | * @type M.assignfeedback_editpdf.dropdown | |
131 | * @public | |
132 | */ | |
133 | this.menu = null; | |
134 | ||
135 | /** | |
136 | * Clean a comment record, returning an oject with only fields that are valid. | |
137 | * @public | |
138 | * @method clean | |
139 | * @return {} | |
140 | */ | |
141 | this.clean = function() { | |
142 | return { | |
5bb4f444 DP |
143 | gradeid: this.gradeid, |
144 | x: parseInt(this.x, 10), | |
145 | y: parseInt(this.y, 10), | |
146 | width: parseInt(this.width, 10), | |
147 | rawtext: this.rawtext, | |
148 | pageno: this.currentpage, | |
149 | colour: this.colour | |
5c386472 DW |
150 | }; |
151 | }; | |
152 | ||
153 | /** | |
154 | * Draw a comment. | |
155 | * @public | |
156 | * @method draw_comment | |
157 | * @param boolean focus - Set the keyboard focus to the new comment if true | |
158 | * @return M.assignfeedback_editpdf.drawable | |
159 | */ | |
160 | this.draw = function(focus) { | |
161 | var drawable = new M.assignfeedback_editpdf.drawable(this.editor), | |
162 | node, | |
667cec9b | 163 | drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION), |
5c386472 DW |
164 | container, |
165 | menu, | |
166 | position, | |
167 | scrollheight; | |
168 | ||
169 | // Lets add a contenteditable div. | |
170 | node = Y.Node.create('<textarea/>'); | |
171 | container = Y.Node.create('<div class="commentdrawable"/>'); | |
172 | menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>'); | |
173 | ||
174 | this.menulink = menu; | |
175 | container.append(node); | |
176 | ||
177 | if (!this.editor.get('readonly')) { | |
178 | container.append(menu); | |
179 | } else { | |
180 | node.setAttribute('readonly', 'readonly'); | |
181 | } | |
182 | if (this.width < 100) { | |
183 | this.width = 100; | |
184 | } | |
185 | ||
186 | position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y)); | |
187 | node.setStyles({ | |
188 | width: this.width + 'px', | |
1d38083c DW |
189 | backgroundColor: COMMENTCOLOUR[this.colour], |
190 | color: COMMENTTEXTCOLOUR | |
5c386472 DW |
191 | }); |
192 | ||
667cec9b | 193 | drawingregion.append(container); |
d79d1bd8 | 194 | container.setStyle('position', 'absolute'); |
5c386472 DW |
195 | container.setX(position.x); |
196 | container.setY(position.y); | |
50c12f01 | 197 | drawable.store_position(container, position.x, position.y); |
5c386472 DW |
198 | drawable.nodes.push(container); |
199 | node.set('value', this.rawtext); | |
8b766dd1 | 200 | scrollheight = node.get('scrollHeight'); |
5c386472 | 201 | node.setStyles({ |
5bb4f444 | 202 | 'height': scrollheight + 'px', |
5c386472 DW |
203 | 'overflow': 'hidden' |
204 | }); | |
205 | if (!this.editor.get('readonly')) { | |
206 | this.attach_events(node, menu); | |
207 | } | |
208 | if (focus) { | |
209 | node.focus(); | |
210 | } | |
211 | this.drawable = drawable; | |
212 | ||
213 | ||
214 | return drawable; | |
215 | }; | |
216 | ||
217 | /** | |
218 | * Delete an empty comment if it's menu hasn't been opened in time. | |
219 | * @method delete_comment_later | |
220 | */ | |
221 | this.delete_comment_later = function() { | |
222 | if (this.deleteme) { | |
223 | this.remove(); | |
224 | } | |
225 | }; | |
226 | ||
227 | /** | |
228 | * Comment nodes have a bunch of event handlers attached to them directly. | |
229 | * This is all done here for neatness. | |
230 | * | |
231 | * @protected | |
232 | * @method attach_comment_events | |
233 | * @param node - The Y.Node representing the comment. | |
234 | * @param menu - The Y.Node representing the menu. | |
235 | */ | |
236 | this.attach_events = function(node, menu) { | |
237 | // Save the text on blur. | |
238 | node.on('blur', function() { | |
239 | // Save the changes back to the comment. | |
240 | this.rawtext = node.get('value'); | |
241 | this.width = parseInt(node.getStyle('width'), 10); | |
242 | ||
243 | // Trim. | |
244 | if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') { | |
245 | // Delete empty comments. | |
246 | this.deleteme = true; | |
247 | Y.later(400, this, this.delete_comment_later); | |
248 | } | |
249 | this.editor.save_current_page(); | |
60b87080 | 250 | this.editor.editingcomment = false; |
5c386472 DW |
251 | }, this); |
252 | ||
253 | // For delegated event handler. | |
254 | menu.setData('comment', this); | |
255 | ||
256 | node.on('keyup', function() { | |
257 | var scrollheight = node.get('scrollHeight'), | |
258 | height = parseInt(node.getStyle('height'), 10); | |
259 | ||
260 | // Webkit scrollheight fix. | |
261 | if (scrollheight === height + 8) { | |
262 | scrollheight -= 8; | |
263 | } | |
264 | node.setStyle('height', scrollheight + 'px'); | |
265 | ||
266 | }); | |
267 | ||
268 | node.on('gesturemovestart', function(e) { | |
b37b6cd8 | 269 | if (editor.currentedit.tool === 'select') { |
243ddbcf | 270 | e.preventDefault(); |
b37b6cd8 TB |
271 | node.setData('dragging', true); |
272 | node.setData('offsetx', e.clientX - node.getX()); | |
273 | node.setData('offsety', e.clientY - node.getY()); | |
274 | } | |
5c386472 DW |
275 | }); |
276 | node.on('gesturemoveend', function() { | |
b37b6cd8 TB |
277 | if (editor.currentedit.tool === 'select') { |
278 | node.setData('dragging', false); | |
279 | this.editor.save_current_page(); | |
280 | } | |
5c386472 DW |
281 | }, null, this); |
282 | node.on('gesturemove', function(e) { | |
b37b6cd8 TB |
283 | if (editor.currentedit.tool === 'select') { |
284 | var x = e.clientX - node.getData('offsetx'), | |
285 | y = e.clientY - node.getData('offsety'), | |
286 | nodewidth, | |
287 | nodeheight, | |
288 | newlocation, | |
289 | windowlocation, | |
290 | bounds; | |
291 | ||
292 | nodewidth = parseInt(node.getStyle('width'), 10); | |
293 | nodeheight = parseInt(node.getStyle('height'), 10); | |
294 | ||
295 | newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y)); | |
296 | bounds = this.editor.get_canvas_bounds(true); | |
297 | bounds.x = 0; | |
298 | bounds.y = 0; | |
299 | ||
300 | bounds.width -= nodewidth + 42; | |
301 | bounds.height -= nodeheight + 8; | |
302 | // Clip to the window size - the comment size. | |
303 | newlocation.clip(bounds); | |
304 | ||
305 | this.x = newlocation.x; | |
306 | this.y = newlocation.y; | |
307 | ||
308 | windowlocation = this.editor.get_window_coordinates(newlocation); | |
309 | node.ancestor().setX(windowlocation.x); | |
310 | node.ancestor().setY(windowlocation.y); | |
311 | this.drawable.store_position(node.ancestor(), windowlocation.x, windowlocation.y); | |
312 | } | |
5c386472 DW |
313 | }, null, this); |
314 | ||
315 | this.menu = new M.assignfeedback_editpdf.commentmenu({ | |
316 | buttonNode: this.menulink, | |
317 | comment: this | |
318 | }); | |
319 | }; | |
320 | ||
321 | /** | |
322 | * Delete a comment. | |
323 | * @method remove | |
324 | */ | |
325 | this.remove = function() { | |
bc8b6dc6 DP |
326 | var i = 0; |
327 | var comments; | |
5c386472 DW |
328 | |
329 | comments = this.editor.pages[this.editor.currentpage].comments; | |
330 | for (i = 0; i < comments.length; i++) { | |
331 | if (comments[i] === this) { | |
332 | comments.splice(i, 1); | |
333 | this.drawable.erase(); | |
334 | this.editor.save_current_page(); | |
335 | return; | |
336 | } | |
337 | } | |
338 | }; | |
339 | ||
340 | /** | |
341 | * Event handler to remove a comment from the users quicklist. | |
342 | * | |
343 | * @protected | |
344 | * @method remove_from_quicklist | |
345 | */ | |
346 | this.remove_from_quicklist = function(e, quickcomment) { | |
114913e3 DS |
347 | e.preventDefault(); |
348 | ||
5c386472 DW |
349 | this.menu.hide(); |
350 | ||
351 | this.editor.quicklist.remove(quickcomment); | |
352 | }; | |
353 | ||
354 | /** | |
355 | * A quick comment was selected in the list, update the active comment and redraw the page. | |
356 | * | |
357 | * @param Event e | |
358 | * @protected | |
359 | * @method set_from_quick_comment | |
360 | */ | |
361 | this.set_from_quick_comment = function(e, quickcomment) { | |
114913e3 DS |
362 | e.preventDefault(); |
363 | ||
5c386472 DW |
364 | this.menu.hide(); |
365 | ||
366 | this.rawtext = quickcomment.rawtext; | |
367 | this.width = quickcomment.width; | |
368 | this.colour = quickcomment.colour; | |
369 | ||
370 | this.editor.save_current_page(); | |
371 | ||
372 | this.editor.redraw(); | |
373 | }; | |
374 | ||
375 | /** | |
376 | * Event handler to add a comment to the users quicklist. | |
377 | * | |
378 | * @protected | |
379 | * @method add_to_quicklist | |
380 | */ | |
114913e3 DS |
381 | this.add_to_quicklist = function(e) { |
382 | e.preventDefault(); | |
5c386472 DW |
383 | this.menu.hide(); |
384 | this.editor.quicklist.add(this); | |
385 | }; | |
386 | ||
387 | /** | |
388 | * Draw the in progress edit. | |
389 | * | |
390 | * @public | |
391 | * @method draw_current_edit | |
392 | * @param M.assignfeedback_editpdf.edit edit | |
393 | */ | |
394 | this.draw_current_edit = function(edit) { | |
395 | var drawable = new M.assignfeedback_editpdf.drawable(this.editor), | |
396 | shape, | |
397 | bounds; | |
398 | ||
399 | bounds = new M.assignfeedback_editpdf.rect(); | |
400 | bounds.bound([edit.start, edit.end]); | |
401 | ||
402 | // We will draw a box with the current background colour. | |
403 | shape = this.editor.graphic.addShape({ | |
404 | type: Y.Rect, | |
405 | width: bounds.width, | |
406 | height: bounds.height, | |
407 | fill: { | |
408 | color: COMMENTCOLOUR[edit.commentcolour] | |
409 | }, | |
410 | x: bounds.x, | |
411 | y: bounds.y | |
412 | }); | |
413 | ||
414 | drawable.shapes.push(shape); | |
415 | ||
416 | return drawable; | |
417 | }; | |
418 | ||
419 | /** | |
420 | * Promote the current edit to a real comment. | |
421 | * | |
422 | * @public | |
423 | * @method init_from_edit | |
424 | * @param M.assignfeedback_editpdf.edit edit | |
baf881b8 | 425 | * @return bool true if comment bound is more than min width/height, else false. |
5c386472 DW |
426 | */ |
427 | this.init_from_edit = function(edit) { | |
428 | var bounds = new M.assignfeedback_editpdf.rect(); | |
429 | bounds.bound([edit.start, edit.end]); | |
430 | ||
431 | // Minimum comment width. | |
432 | if (bounds.width < 100) { | |
433 | bounds.width = 100; | |
434 | } | |
435 | ||
436 | // Save the current edit to the server and the current page list. | |
437 | ||
438 | this.gradeid = this.editor.get('gradeid'); | |
439 | this.pageno = this.editor.currentpage; | |
440 | this.x = bounds.x; | |
441 | this.y = bounds.y; | |
442 | this.width = bounds.width; | |
443 | this.colour = edit.commentcolour; | |
444 | this.rawtext = ''; | |
baf881b8 RT |
445 | |
446 | return (bounds.has_min_width() && bounds.has_min_height()); | |
5c386472 DW |
447 | }; |
448 | ||
449 | }; | |
450 | ||
451 | M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; | |
452 | M.assignfeedback_editpdf.comment = COMMENT; |