progressWindow.js (17068B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 27 Zotero.ProgressWindowSet = new function() { 28 this.add = add; 29 this.tile = tile; 30 this.remove = remove; 31 this.updateTimers = updateTimers; 32 33 var _progressWindows = []; 34 35 const X_OFFSET = 25; 36 const Y_OFFSET = 35; 37 const Y_SEPARATOR = 12; 38 const X_WINDOWLESS_OFFSET = 50; 39 const Y_WINDOWLESS_OFFSET = 100; 40 41 function add(progressWindow, instance) { 42 _progressWindows.push({ 43 progressWindow: progressWindow, 44 instance: instance 45 }); 46 } 47 48 49 function tile(progressWin) { 50 var parent = progressWin.opener; 51 var y_sub = null; 52 53 for (var i=0; i<_progressWindows.length; i++) { 54 var p = _progressWindows[i].progressWindow; 55 56 // Skip progress windows from other windows 57 if (p.opener != parent) { 58 continue; 59 } 60 61 if (!y_sub) { 62 y_sub = Y_OFFSET + p.outerHeight; 63 } 64 65 if (parent) { 66 var right = parent.screenX + parent.outerWidth; 67 var bottom = parent.screenY + parent.outerHeight; 68 } 69 else { 70 var right = progressWin.screen.width + X_OFFSET - X_WINDOWLESS_OFFSET; 71 var bottom = progressWin.screen.height + Y_OFFSET - Y_WINDOWLESS_OFFSET; 72 } 73 74 p.moveTo(right - p.outerWidth - X_OFFSET, bottom - y_sub); 75 76 y_sub += p.outerHeight + Y_SEPARATOR; 77 } 78 } 79 80 81 function remove(progressWin) { 82 for (var i=0; i<_progressWindows.length; i++) { 83 if (_progressWindows[i].progressWindow == progressWin) { 84 _progressWindows.splice(i, 1); 85 } 86 } 87 } 88 89 90 function updateTimers() { 91 if (!_progressWindows.length) { 92 return; 93 } 94 95 for (var i=0; i<_progressWindows.length; i++) { 96 // Pass |requireMouseOver| so that the window only closes 97 // if the mouse was over it at some point 98 _progressWindows[i].instance.startCloseTimer(null, true); 99 } 100 } 101 102 103 this.closeAll = function () { 104 _progressWindows.forEach(pw => pw.instance.close()); 105 } 106 } 107 108 109 /* 110 * Handles the display of a div showing progress in scraping, indexing, etc. 111 * 112 * Pass the active window into the constructor 113 */ 114 Zotero.ProgressWindow = function(options = {}) { 115 var _window = options.window || null; 116 var _closeOnClick = typeof options.closeOnClick == 'undefined' ? true : options.closeOnClick; 117 var self = this, 118 _progressWindow = null, 119 _windowLoaded = false, 120 _windowLoading = false, 121 _timeoutID = false, 122 _closing = false, 123 _mouseWasOver = false, 124 _deferredUntilWindowLoad = [], 125 _deferredUntilWindowLoadThis = [], 126 _deferredUntilWindowLoadArgs = []; 127 128 /** 129 * Shows the progress window 130 */ 131 this.show = function show() { 132 if(_windowLoading || _windowLoaded) { // already loading or loaded 133 return false; 134 } 135 136 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]. 137 getService(Components.interfaces.nsIWindowWatcher); 138 139 if (!_window){ 140 _window = Components.classes["@mozilla.org/appshell/window-mediator;1"] 141 .getService(Components.interfaces.nsIWindowMediator) 142 .getMostRecentWindow("navigator:browser"); 143 } 144 145 if (_window) { 146 _progressWindow = _window.openDialog("chrome://zotero/content/progressWindow.xul", 147 "", "chrome,dialog=no,titlebar=no,popup=yes"); 148 } 149 else { 150 _progressWindow = ww.openWindow(null, "chrome://zotero/content/progressWindow.xul", 151 "", "chrome,dialog=no,titlebar=no,popup=yes", null); 152 } 153 _progressWindow.addEventListener("load", _onWindowLoaded, false); 154 _progressWindow.addEventListener("mouseover", _onMouseOver, false); 155 _progressWindow.addEventListener("mouseout", _onMouseOut, false); 156 _progressWindow.addEventListener("mouseup", _onMouseUp, false); 157 _window.addEventListener('close', () => { 158 this.close(); 159 }); 160 161 _windowLoading = true; 162 163 Zotero.ProgressWindowSet.add(_progressWindow, this); 164 165 return true; 166 } 167 168 /** 169 * Changes the "headline" shown at the top of the progress window 170 */ 171 this.changeHeadline = _deferUntilWindowLoad(function changeHeadline(text, icon, postText) { 172 var doc = _progressWindow.document, 173 headline = doc.getElementById("zotero-progress-text-headline"); 174 while(headline.hasChildNodes()) headline.removeChild(headline.firstChild); 175 176 var preNode = doc.createElement("label"); 177 preNode.setAttribute("value", text); 178 preNode.setAttribute("crop", "end"); 179 headline.appendChild(preNode); 180 181 if(icon) { 182 var img = doc.createElement("image"); 183 img.width = 16; 184 img.height = 16; 185 img.setAttribute("src", icon); 186 headline.appendChild(img); 187 } 188 189 if(postText) { 190 var postNode = doc.createElement("label"); 191 postNode.style.marginLeft = 0; 192 postNode.setAttribute("value", " "+postText); 193 postNode.setAttribute("crop", "end"); 194 postNode.setAttribute("flex", "1"); 195 headline.appendChild(postNode); 196 } 197 }); 198 199 /** 200 * Adds a line to the progress window with the specified icon 201 */ 202 this.addLines = _deferUntilWindowLoad(function addLines(labels, icons) { 203 if(typeof labels === "object" && typeof icons === "object") { 204 for (var i in labels) { 205 let progress = new this.ItemProgress(icons[i], labels[i]); 206 progress.setProgress(100); 207 } 208 } else { 209 let progress = new this.ItemProgress(icons, labels); 210 progress.setProgress(100); 211 } 212 213 _move(); 214 }); 215 216 /** 217 * Add a description to the progress window 218 * 219 * <a> elements are turned into XUL links 220 */ 221 this.addDescription = _deferUntilWindowLoad(function addDescription(text) { 222 var newHB = _progressWindow.document.createElement("hbox"); 223 newHB.setAttribute("class", "zotero-progress-item-hbox"); 224 var newDescription = _progressWindow.document.createElement("description"); 225 226 var parts = Zotero.Utilities.parseMarkup(text); 227 for (let part of parts) { 228 if (part.type == 'text') { 229 var elem = _progressWindow.document.createTextNode(part.text); 230 } 231 else if (part.type == 'link') { 232 var elem = _progressWindow.document.createElement('label'); 233 elem.setAttribute('value', part.text); 234 elem.setAttribute('class', 'zotero-text-link'); 235 for (var i in part.attributes) { 236 elem.setAttribute(i, part.attributes[i]); 237 } 238 } 239 240 newDescription.appendChild(elem); 241 } 242 243 newHB.appendChild(newDescription); 244 _progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB); 245 246 _move(); 247 }); 248 249 /** 250 * Sets a timer to close the progress window. If a previous close timer was set, 251 * clears it. 252 * @param {Integer} ms The number of milliseconds to wait before closing the progress 253 * window. 254 * @param {Boolean} [requireMouseOver] If true, wait until the mouse has touched the 255 * window before closing. 256 */ 257 this.startCloseTimer = function startCloseTimer(ms, requireMouseOver) { 258 if (_windowLoaded || _windowLoading) { 259 if (requireMouseOver && !_mouseWasOver) { 260 return; 261 } 262 263 if (_timeoutID) { 264 _disableTimeout(); 265 } 266 267 if (typeof ms != 'number') { 268 ms = 2500; 269 } 270 271 _timeoutID = _progressWindow.setTimeout(_timeout, ms); 272 _closing = true; 273 } 274 } 275 276 /** 277 * Immediately closes the progress window if it is open. 278 */ 279 this.close = function close() { 280 _disableTimeout(); 281 _windowLoaded = false; 282 _windowLoading = false; 283 Zotero.ProgressWindowSet.remove(_progressWindow); 284 285 try { 286 _progressWindow.close(); 287 } 288 catch (e) { 289 Zotero.logError(e); 290 } 291 } 292 293 /** 294 * Creates a new object representing a line in the progressWindow. This is the OO 295 * version of addLines() above. 296 */ 297 this.ItemProgress = _deferUntilWindowLoad(function(iconSrc, text, parentItemProgress) { 298 this.setText(text); 299 300 this._image = _progressWindow.document.createElement("hbox"); 301 this._image.setAttribute("class", "zotero-progress-item-icon"); 302 this._image.setAttribute("flex", 0); 303 this._image.style.width = "16px"; 304 this._image.style.backgroundRepeat = "no-repeat"; 305 this._image.style.backgroundSize = "auto 16px"; 306 this.setIcon(iconSrc); 307 308 this._hbox = _progressWindow.document.createElement("hbox"); 309 this._hbox.setAttribute("class", "zotero-progress-item-hbox"); 310 if(parentItemProgress) { 311 this._hbox.style.marginLeft = "16px"; 312 this._hbox.zoteroIsChildItem; 313 } else { 314 this._hbox.setAttribute("parent", "true"); 315 } 316 this._hbox.style.opacity = "0.5"; 317 318 this._hbox.appendChild(this._image); 319 this._hbox.appendChild(this._itemText); 320 321 var container = _progressWindow.document.getElementById("zotero-progress-text-box"); 322 if(parentItemProgress) { 323 var nextItem = parentItemProgress._hbox.nextSibling; 324 while(nextItem && nextItem.zoteroIsChildItem) { 325 nextItem = nextItem.nextSibling; 326 } 327 container.insertBefore(this._hbox, nextItem); 328 } else { 329 container.appendChild(this._hbox); 330 } 331 332 _move(); 333 }); 334 335 /** 336 * Sets the current save progress for this item. 337 * @param {Integer} percent A percentage from 0 to 100. 338 */ 339 this.ItemProgress.prototype.setProgress = _deferUntilWindowLoad(function(percent) { 340 if(percent != 0 && percent != 100) { 341 // Indication of partial progress, so we will use the circular indicator 342 var nArcs = 20; 343 this._image.style.backgroundImage = "url('chrome://zotero/skin/progress_arcs.png')"; 344 this._image.style.backgroundPosition = "-"+(Math.round(percent/100*nArcs)*16)+"px 0"; 345 this._hbox.style.opacity = percent/200+.5; 346 } else if(percent == 100) { 347 this._image.style.backgroundImage = "url('"+this._iconSrc+"')"; 348 this._image.style.backgroundPosition = ""; 349 this._hbox.style.opacity = "1"; 350 this._hbox.style.filter = ""; 351 } 352 }); 353 354 /** 355 * Sets the icon for this item. 356 * @param {Integer} percent A percentage from 0 to 100. 357 */ 358 this.ItemProgress.prototype.setIcon = _deferUntilWindowLoad(function(iconSrc) { 359 this._image.style.backgroundImage = "url('"+iconSrc+"')"; 360 this._image.style.backgroundPosition = ""; 361 this._iconSrc = iconSrc; 362 }); 363 364 this.ItemProgress.prototype.setText = _deferUntilWindowLoad(function (text) { 365 if (!this._itemText) { 366 this._itemText = _progressWindow.document.createElement("description"); 367 } 368 else { 369 this._itemText.textContent = ''; 370 } 371 this._itemText.appendChild(_progressWindow.document.createTextNode(text)); 372 this._itemText.setAttribute("class", "zotero-progress-item-label"); 373 this._itemText.setAttribute("crop", "end"); 374 }); 375 376 /** 377 * Indicates that an error occurred saving this item. 378 */ 379 this.ItemProgress.prototype.setError = _deferUntilWindowLoad(function() { 380 this._image.style.backgroundImage = "url('chrome://zotero/skin/cross.png')"; 381 this._image.style.backgroundPosition = ""; 382 this._itemText.style.color = "red"; 383 this._hbox.style.opacity = "1"; 384 this._hbox.style.filter = ""; 385 }); 386 387 this.Translation = {}; 388 389 this.Translation.operationInProgress = function() { 390 var desc = Zotero.localeJoin([ 391 Zotero.getString('general.operationInProgress'), 392 Zotero.getString('general.operationInProgress.waitUntilFinishedAndTryAgain') 393 ]); 394 self.Translation._scrapeError(desc); 395 }; 396 397 this.Translation.cannotEditCollection = function() { 398 var desc = Zotero.getString('save.error.cannotMakeChangesToCollection'); 399 self.Translation._scrapeError(desc); 400 }; 401 402 this.Translation.cannotAddToPublications = function () { 403 var desc = Zotero.getString('save.error.cannotAddToMyPublications'); 404 self.Translation._scrapeError(desc); 405 }; 406 407 this.Translation.cannotAddToFeed = function() { 408 var desc = Zotero.getString('save.error.cannotAddToFeed'); 409 self.Translation._scrapeError(desc); 410 }; 411 412 this.Translation.scrapingTo = function(libraryID, collection) { 413 var name; 414 if(collection) { 415 name = collection.name; 416 } else if(libraryID) { 417 name = Zotero.Libraries.getName(libraryID); 418 } else { 419 name = Zotero.getString("pane.collections.library"); 420 } 421 422 self.changeHeadline(Zotero.getString("ingester.scrapingTo"), 423 "chrome://zotero/skin/treesource-"+(collection ? "collection" : "library")+".png", 424 name+"\u2026"); 425 }; 426 427 this.Translation.doneHandler = function(obj, returnValue) { 428 if(!returnValue) { 429 // Include link to translator troubleshooting page 430 var url = "https://www.zotero.org/support/troubleshooting_translator_issues"; 431 var linkText = '<a href="' + url + '" tooltiptext="' + url + '">' 432 + Zotero.getString('ingester.scrapeErrorDescription.linkText') + '</a>'; 433 var desc = Zotero.getString("ingester.scrapeErrorDescription", linkText) 434 self.Translation._scrapeError(desc); 435 } else { 436 self.startCloseTimer(); 437 } 438 }; 439 440 this.Translation.itemDoneHandler = function(_attachmentsMap) { 441 _attachmentsMap = _attachmentsMap || new WeakMap(); 442 return function(obj, dbItem, item) { 443 self.show(); 444 var itemProgress = new self.ItemProgress(Zotero.ItemTypes.getImageSrc(item.itemType), 445 item.title); 446 itemProgress.setProgress(100); 447 for(var i=0; i<item.attachments.length; i++) { 448 var attachment = item.attachments[i]; 449 _attachmentsMap.set(attachment, 450 new self.ItemProgress( 451 Zotero.Utilities.determineAttachmentIcon(attachment), 452 attachment.title, itemProgress)); 453 } 454 } 455 }; 456 457 this.Translation.attachmentProgressHandler = function(_attachmentsMap) { 458 _attachmentsMap = _attachmentsMap || new WeakMap(); 459 return function(obj, attachment, progress, error) { 460 var itemProgress = _attachmentsMap.get(attachment); 461 if(progress === false) { 462 itemProgress.setError(); 463 } else { 464 itemProgress.setProgress(progress); 465 if(progress === 100) { 466 itemProgress.setIcon(Zotero.Utilities.determineAttachmentIcon(attachment)); 467 } 468 } 469 } 470 }; 471 472 this.Translation._scrapeError = function(description) { 473 self.changeHeadline(Zotero.getString("ingester.scrapeError")); 474 self.addDescription(description); 475 self.show(); 476 self.startCloseTimer(8000) 477 } 478 479 function _onWindowLoaded() { 480 _windowLoading = false; 481 _windowLoaded = true; 482 483 _move(); 484 485 // do things we delayed because the window was loading 486 for(var i=0; i<_deferredUntilWindowLoad.length; i++) { 487 _deferredUntilWindowLoad[i].apply(_deferredUntilWindowLoadThis[i], 488 _deferredUntilWindowLoadArgs[i]); 489 } 490 _deferredUntilWindowLoad = []; 491 _deferredUntilWindowLoadThis = []; 492 _deferredUntilWindowLoadArgs = []; 493 } 494 495 function _move() { 496 // sizeToContent() fails in FF3 with multiple lines 497 // if we don't change the height 498 _progressWindow.outerHeight = _progressWindow.outerHeight + 1; 499 _progressWindow.sizeToContent(); 500 Zotero.ProgressWindowSet.tile(_progressWindow); 501 } 502 503 function _timeout() { 504 self.close(); // could check to see if we're really supposed to close yet 505 // (in case multiple scrapers are operating at once) 506 _timeoutID = false; 507 } 508 509 function _disableTimeout() { 510 // FIXME: to prevent errors from translator saving (Create New Item appears to still work) 511 // This shouldn't be necessary, and mouseover isn't properly 512 // causing the popup to remain 513 try { 514 _progressWindow.clearTimeout(_timeoutID); 515 } 516 catch (e) {} 517 _timeoutID = false; 518 } 519 520 /* 521 * Disable the close timer when the mouse is over the window 522 */ 523 function _onMouseOver(e) { 524 _mouseWasOver = true; 525 _disableTimeout(); 526 } 527 528 /** 529 * Start the close timer when the mouse leaves the window 530 * 531 * Note that this onmouseout doesn't work correctly on popups in Fx2, 532 * so 1) we have to calculate the window borders manually to avoid fading 533 * when the mouse is still over the box, and 2) this only does anything 534 * when the mouse is moved off of the browser window -- otherwise the close 535 * is triggered by onmousemove on appcontent in overlay.xul. 536 */ 537 function _onMouseOut(e) { 538 // |this| refers to progressWindow's XUL window 539 var top = this.screenY + (Zotero.isMac ? 22 : 0); 540 if ((e.screenX >= this.screenX && e.screenX <= (this.screenX + this.outerWidth)) 541 && (e.screenY >= top) && e.screenY <= (top + this.outerHeight)) { 542 return; 543 } 544 if(_closing) self.startCloseTimer(); 545 } 546 547 function _onMouseUp(e) { 548 if (_closeOnClick) { 549 self.close(); 550 } 551 } 552 553 /** 554 * Wraps a function to ensure it isn't called until the window is loaded 555 */ 556 function _deferUntilWindowLoad(fn) { 557 return function() { 558 if(_window && _window.closed) return; 559 560 if(_windowLoaded) { 561 fn.apply(this, Array.prototype.slice.call(arguments)); 562 } else { 563 _deferredUntilWindowLoad.push(fn); 564 _deferredUntilWindowLoadThis.push(this); 565 _deferredUntilWindowLoadArgs.push(Array.prototype.slice.call(arguments)); 566 } 567 } 568 } 569 }