www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

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 }