PageRenderTime 73ms CodeModel.GetById 12ms app.highlight 54ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/portals-list.user.js

https://github.com/mdunc/ingress-intel-total-conversion
JavaScript | 611 lines | 468 code | 55 blank | 88 comment | 71 complexity | 69650910ffb87dbc507e0e85ae63c4ff MD5 | raw file
  1// ==UserScript==
  2// @id             iitc-plugin-portals-list@teo96
  3// @name           IITC plugin: show list of portals
  4// @category       Info
  5// @version        0.1.1.@@DATETIMEVERSION@@
  6// @namespace      https://github.com/jonatkins/ingress-intel-total-conversion
  7// @updateURL      @@UPDATEURL@@
  8// @downloadURL    @@DOWNLOADURL@@
  9// @description    [@@BUILDNAME@@-@@BUILDDATE@@] Display a sortable list of all visible portals with full details about the team, resonators, links, etc.
 10// @include        https://www.ingress.com/intel*
 11// @include        http://www.ingress.com/intel*
 12// @match          https://www.ingress.com/intel*
 13// @match          http://www.ingress.com/intel*
 14// @grant          none
 15// ==/UserScript==
 16
 17@@PLUGINSTART@@
 18
 19// PLUGIN START ////////////////////////////////////////////////////////
 20
 21  /* whatsnew
 22   * 0.1.0 : Using the new data format
 23   * 0.0.15: Add 'age' column to display how long each portal has been controlled by its current owner.
 24   * 0.0.14: Add support to new mods (S:Shield - T:Turret - LA:Link Amp - H:Heat-sink - M:Multi-hack - FA:Force Amp)
 25   * 0.0.12: Use dialog() instead of alert so the user can drag the box around
 26   * 0.0.11: Add nominal energy column and # links, fix sort bug when opened even amounts of times, nits
 27   * 0.0.10: Fixed persistent css problem with alert
 28   * 0.0.9 : bugs hunt
 29   * 0.0.8 : Aborted to avoid problems with Niantic (export portals informations as csv or kml file)
 30   * 0.0.7 : more informations available via tooltips (who deployed, energy, ...), new E/AP column
 31   * 0.0.6 : Add power charge information into a new column + bugfix
 32   * 0.0.5 : Filter portals by clicking on 'All portals', 'Res Portals' or 'Enl Portals'
 33   * 0.0.4 : Add link to portals name, one click to display full information in portal panel, double click to zoom on portal, hover to show address
 34   * 0.0.3 : sorting ascending/descending and add numbers of portals by faction on top on table
 35   * 0.0.2 : add sorting feature when click on header column
 36   * 0.0.1 : initial release, show list of portals with level, team, resonators and shield information
 37   *
 38   * Display code inspired from @vita10gy's scoreboard plugin : iitc-plugin-scoreboard@vita10gy - https://github.com/breunigs/ingress-intel-total-conversion
 39   * Portal link code from xelio - iitc: AP List - https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/plugins/ap-list.user.js
 40   *
 41   * todo : export as GPX, Open in Google Maps, more statistics in the header, what else ?
 42   */
 43
 44// use own namespace for plugin
 45window.plugin.portalslist = function() {};
 46
 47window.plugin.portalslist.listPortals = [];
 48window.plugin.portalslist.sortBy = 'level';
 49window.plugin.portalslist.sortOrder = -1;
 50window.plugin.portalslist.enlP = 0;
 51window.plugin.portalslist.resP = 0;
 52window.plugin.portalslist.filter = 0;
 53
 54//fill the listPortals array with portals avaliable on the map (level filtered portals will not appear in the table)
 55window.plugin.portalslist.getPortals = function() {
 56  //filter : 0 = All, 1 = Res, 2 = Enl
 57  var retval=false;
 58
 59  var displayBounds = map.getBounds();
 60
 61  window.plugin.portalslist.listPortals = [];
 62  $.each(window.portals, function(i, portal) {
 63    // eliminate offscreen portals (selected, and in padding)
 64    if(!displayBounds.contains(portal.getLatLng())) return true;
 65
 66    retval=true;
 67    var d = portal.options.data;
 68    var teamN = portal.options.team;
 69
 70    switch (teamN) {
 71      case TEAM_RES:
 72        window.plugin.portalslist.resP++;
 73        break;
 74      case TEAM_ENL:
 75        window.plugin.portalslist.enlP++;
 76        break;
 77    }
 78    var l = window.getPortalLinks(i);
 79    var f = window.getPortalFields(i);
 80    var ap = portalApGainMaths(d.resCount, l.in.length+l.out.length, f.length);
 81
 82    var thisPortal = {
 83      'portal': portal,
 84      'guid': i,
 85      'teamN': teamN, // TEAM_NONE, TEAM_RES or TEAM_ENL
 86      'team': d.team, // "NEUTRAL", "RESISTANCE" or "ENLIGHTENED"
 87      'name': d.title || '(untitled)',
 88      'nameLower': d.title && d.title.toLowerCase(),
 89      'level': portal.options.level,
 90      'health': d.health,
 91      'resCount': d.resCount,
 92      'img': d.img,
 93      'linkCount': l.in.length + l.out.length,
 94      'link' : l,
 95      'fieldCount': f.length,
 96      'field' : f,
 97      'enemyAp': ap.enemyAp,
 98      'ap': ap,
 99    };
100    window.plugin.portalslist.listPortals.push(thisPortal);
101  });
102
103  return retval;
104}
105
106window.plugin.portalslist.displayPL = function() {
107  var html = '';
108  window.plugin.portalslist.sortBy = 'level';
109  window.plugin.portalslist.sortOrder = -1;
110  window.plugin.portalslist.enlP = 0;
111  window.plugin.portalslist.resP = 0;
112  window.plugin.portalslist.filter = 0;
113
114  if (window.plugin.portalslist.getPortals()) {
115    html += window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy, window.plugin.portalslist.sortOrder,window.plugin.portalslist.filter);
116  } else {
117    html = '<table class="noPortals"><tr><td>Nothing to show!</td></tr></table>';
118  };
119
120  if(window.useAndroidPanes()) {
121    $('<div id="portalslist" class="mobile">' + html + '</div>').appendTo(document.body);
122  } else {
123    dialog({
124      html: '<div id="portalslist">' + html + '</div>',
125      dialogClass: 'ui-dialog-portalslist',
126      title: 'Portal list: ' + window.plugin.portalslist.listPortals.length + ' ' + (window.plugin.portalslist.listPortals.length == 1 ? 'portal' : 'portals'),
127      id: 'portal-list',
128      width: 700,
129      buttons: {'Download KML': function() {window.plugin.portalslist.downloadKML();}}
130    });
131  }
132}
133
134window.plugin.portalslist.downloadKML = function() {
135  var portals = window.plugin.portalslist.listPortals;
136  var kmlData = '<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2"><Document>';
137  $.each(portals, function(ind, portal) {
138    var coords = portal.portal.getLatLng();
139    var descData = "<strong>Level:</strong>" + portal.level + "<br>" 
140                 + "<strong>Team:</strong>" + portal.team + "<br>"
141                 + "<strong>Link:</strong>" + window.plugin.portalslist.getPortalLink(portal, portal.guid) + "<br>"
142                 + "<img src='" + portal.portal.options.data.image + "' /><br>";
143    var labelColor = null;
144    var icon = null;
145    switch (portal.teamN) {
146      case TEAM_NONE:
147        labelColor = "cc333333";
148        icon = "http://maps.google.com/mapfiles/kml/paddle/wht-blank.png";
149        break;
150      case TEAM_ENL:
151        labelColor = "cc0C9119";
152        icon = "http://maps.google.com/mapfiles/kml/paddle/grn-blank.png";
153        break;
154      case TEAM_RES:
155        labelColor = "ccAD5910";
156        icon = "http://maps.google.com/mapfiles/kml/paddle/blu-blank.png";
157        break;
158    }
159    kmlData += '<Placemark>'
160             + '<Style><IconStyle><Icon><href>' + icon + '</href></Icon></IconStyle>'
161             + '<LabelStyle><color>' + labelColor + '</color></LabelStyle></Style>'
162             + '<name>' + portal.name + '</name>'
163             + '<description><![CDATA[' + descData + ']]></description>'
164             + '<Point><coordinates>' + coords.lng + ',' + coords.lat + '</coordinates></Point>'
165             + '</Placemark>';
166  });
167  kmlData += '</Document></kml>';
168  saveAs(new Blob([kmlData], {type: 'text/plain;charset=' + document.characterSet}), "portals.kml");
169}
170
171window.plugin.portalslist.portalTable = function(sortBy, sortOrder, filter) {
172  // save the sortBy/sortOrder/filter
173  window.plugin.portalslist.sortBy = sortBy;
174  window.plugin.portalslist.sortOrder = sortOrder;
175  window.plugin.portalslist.filter = filter;
176
177  var portals=window.plugin.portalslist.listPortals;
178
179  //Array sort
180  window.plugin.portalslist.listPortals.sort(function(a, b) {
181    var retVal = 0;
182
183    var aComp = a[sortBy];
184    var bComp = b[sortBy];
185
186    if (aComp < bComp) {
187      retVal = -1;
188    } else if (aComp > bComp) {
189      retVal = 1;
190    } else {
191      // equal - compare GUIDs to ensure consistent (but arbitrary) order
192      retVal = a.guid < b.guid ? -1 : 1;
193    }
194
195    // sortOrder is 1 (normal) or -1 (reversed)
196    retVal = retVal * sortOrder;
197    return retVal;
198  });
199
200  var sortAttr = window.plugin.portalslist.portalTableHeaderSortAttr;
201  var html = window.plugin.portalslist.stats();
202  html += '<table class="portals">'
203    + '<tr class="header">'
204    + '<th>#</th>'
205    + '<th ' + sortAttr('nameLower', sortBy, 1, 'portalTitle') + '>Portal Name</th>'
206    + '<th ' + sortAttr('level', sortBy, -1) + '>Level</th>'
207    + '<th ' + sortAttr('teamN', sortBy, 1) + '>Team</th>'
208    + '<th ' + sortAttr('health', sortBy, -1) + '>Health</th>'
209    + '<th ' + sortAttr('resCount', sortBy, -1) + '>Res</th>'
210    + '<th ' + sortAttr('linkCount', sortBy, -1) + '>Links</th>'
211    + '<th ' + sortAttr('fieldCount', sortBy, -1) + '>Fields</th>'
212    + '<th ' + sortAttr('enemyAp', sortBy, -1) + '>AP</th>'
213    + '</tr>\n';
214
215  var rowNum = 1;
216
217  $.each(portals, function(ind, portal) {
218    if (filter === TEAM_NONE || filter === portal.teamN) {
219
220      html += '<tr class="' + (portal.teamN === window.TEAM_RES ? 'res' : (portal.teamN === window.TEAM_ENL ? 'enl' : 'neutral')) + '">'
221        + '<td>'+rowNum+'</td>'
222        + '<td class="portalTitle" style="">' + window.plugin.portalslist.getPortalLink(portal, portal.guid) + '</td>'
223        + '<td class="L' + portal.level +'" style="background-color: '+COLORS_LVL[portal.level]+'">' + portal.level + '</td>'
224        + '<td style="text-align:center;">' + portal.team.substr(0,3) + '</td>';
225
226      html += '<td>' + (portal.teamN!=TEAM_NONE?portal.health+'%':'-') + '</td>'
227        + '<td>' + portal.resCount + '</td>'
228        + '<td class="help" title="In: ' + portal.link.in.length + ' Out: ' + portal.link.out.length + '">' + (portal.linkCount?portal.linkCount:'-') + '</td>'
229        + '<td>' + (portal.fieldCount?portal.fieldCount:'-') + '</td>';
230
231      var apTitle = '';
232      if (PLAYER.team == portal.team) {
233        apTitle += 'Friendly AP:\t'+portal.ap.friendlyAp+'\n'
234                 + '- deploy '+(8-portal.resCount)+' resonator(s)\n'
235                 + '- upgrades/mods unknown\n';
236      }
237      apTitle += 'Enemy AP:\t'+portal.ap.enemyAp+'\n'
238               + '- Destroy AP:\t'+portal.ap.destroyAp+'\n'
239               + '- Capture AP:\t'+portal.ap.captureAp;
240
241      html += '<td class="help apGain" title="' + apTitle + '">' + digits(portal.ap.enemyAp) + '</td>';
242
243      html+= '</tr>';
244
245      rowNum++;
246    }
247  });
248  html += '</table>';
249
250  html += '<div class="disclaimer">Click on portals table headers to sort by that column. '
251    + 'Click on <b>All Portals, Resistance Portals, Enlightened Portals</b> to filter</div>';
252
253  return html;
254}
255
256window.plugin.portalslist.stats = function(sortBy) {
257  var html = '<table class="teamFilter"><tr>'
258    + '<td class="filterAll" style="cursor:pointer"><a href=""></a>All Portals : (click to filter)</td><td class="filterAll">' + window.plugin.portalslist.listPortals.length + '</td>'
259    + '<td class="filterRes" style="cursor:pointer" class="sorted">Resistance Portals : </td><td class="filterRes">' + window.plugin.portalslist.resP +' (' + Math.floor(window.plugin.portalslist.resP/window.plugin.portalslist.listPortals.length*100) + '%)</td>'
260    + '<td class="filterEnl" style="cursor:pointer" class="sorted">Enlightened Portals : </td><td class="filterEnl">'+ window.plugin.portalslist.enlP +' (' + Math.floor(window.plugin.portalslist.enlP/window.plugin.portalslist.listPortals.length*100) + '%)</td>'
261    + '</tr>'
262    + '</table>';
263  return html;
264}
265
266// A little helper function so the above isn't so messy
267window.plugin.portalslist.portalTableHeaderSortAttr = function(name, by, defOrder, extraClass) {
268  // data-sort attr: used by jquery .data('sort') below
269  var retVal = 'data-sort="'+name+'" data-defaultorder="'+defOrder+'" class="'+(extraClass?extraClass+' ':'')+'sortable'+(name==by?' sorted':'')+'"';
270
271  return retVal;
272};
273
274// portal link - single click: select portal
275//               double click: zoom to and select portal
276//               hover: show address
277// code from getPortalLink function by xelio from iitc: AP List - https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/plugins/ap-list.user.js
278window.plugin.portalslist.getPortalLink = function(portal,guid) {
279  var coord = portal.portal.getLatLng();
280  var latlng = [coord.lat, coord.lng].join();
281  var jsSingleClick = 'window.renderPortalDetails(\''+guid+'\');return false';
282  var jsDoubleClick = 'window.zoomToAndShowPortal(\''+guid+'\', ['+latlng+']);return false';
283  var perma = 'https://www.ingress.com/intel?ll='+coord.lat+','+coord.lng+'&z=17&pll='+coord.lat+','+coord.lng;
284
285  //Use Jquery to create the link, which escape characters in TITLE and ADDRESS of portal
286  var a = $('<a>',{
287    text: portal.name,
288    title: portal.name,
289    href: perma,
290    onClick: jsSingleClick,
291    onDblClick: jsDoubleClick
292  })[0].outerHTML;
293
294  return a;
295}
296
297window.plugin.portalslist.onPaneChanged = function(pane) {
298  if(pane == "plugin-portalslist")
299    window.plugin.portalslist.displayPL();
300  else
301    $("#portalslist").remove()
302};
303
304var setup =  function() {
305  if(window.useAndroidPanes()) {
306    android.addPane("plugin-portalslist", "Portals list", "ic_action_paste");
307    addHook("paneChanged", window.plugin.portalslist.onPaneChanged);
308  } else {
309    $('#toolbox').append(' <a onclick="window.plugin.portalslist.displayPL()" title="Display a list of portals in the current view">Portals list</a>');
310  }
311
312  $('head').append('<style>' +
313    '#portalslist.mobile {background: transparent; border: 0 none !important; height: 100% !important; width: 100% !important; left: 0 !important; top: 0 !important; position: absolute; overflow: auto; }' +
314    '#portalslist table { margin-top:5px; border-collapse: collapse; empty-cells: show; width: 100%; clear: both; }' +
315    '#portalslist table td, #portalslist table th {border-bottom: 1px solid #0b314e; padding:3px; color:white; background-color:#1b415e}' +
316    '#portalslist table tr.res td { background-color: #005684; }' +
317    '#portalslist table tr.enl td { background-color: #017f01; }' +
318    '#portalslist table tr.neutral td { background-color: #000000; }' +
319    '#portalslist table th { text-align: center; }' +
320    '#portalslist table td { text-align: center; }' +
321    '#portalslist table.portals td { white-space: nowrap; }' +
322    '#portalslist table td.portalTitle { text-align: left;}' +
323    '#portalslist table th.sortable { cursor:pointer;}' +
324    '#portalslist table th.portalTitle { text-align: left;}' +
325    '#portalslist table .portalTitle { min-width: 120px !important; max-width: 240px !important; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }' +
326    '#portalslist table .apGain { text-align: right !important; }' +
327    '#portalslist .sorted { color:#FFCE00; }' +
328    '#portalslist .filterAll { margin-top: 10px;}' +
329    '#portalslist .filterRes { margin-top: 10px; background-color: #005684  }' +
330    '#portalslist .filterEnl { margin-top: 10px; background-color: #017f01  }' +
331    '#portalslist .disclaimer { margin-top: 10px; font-size:10px; }' +
332    '</style>');
333
334  // Setup sorting
335  $(document).on('click.portalslist', '#portalslist table th.sortable', function() {
336    var sortBy = $(this).data('sort');
337    // if this is the currently selected column, toggle the sort order - otherwise use the columns default sort order
338    var sortOrder = sortBy == window.plugin.portalslist.sortBy ? window.plugin.portalslist.sortOrder*-1 : parseInt($(this).data('defaultorder'));
339    $('#portalslist').html(window.plugin.portalslist.portalTable(sortBy,sortOrder,window.plugin.portalslist.filter));
340  });
341
342  $(document).on('click.portalslist', '#portalslist .filterAll', function() {
343    $('#portalslist').html(window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy,window.plugin.portalslist.sortOrder,0));
344  });
345  $(document).on('click.portalslist', '#portalslist .filterRes', function() {
346    $('#portalslist').html(window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy,window.plugin.portalslist.sortOrder,1));
347  });
348  $(document).on('click.portalslist', '#portalslist .filterEnl', function() {
349    $('#portalslist').html(window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy,window.plugin.portalslist.sortOrder,2));
350  });
351}
352
353
354/*! FileSaver.js
355* A saveAs() FileSaver implementation.
356* 2014-01-24
357*
358* By Eli Grey, http://eligrey.com
359* License: X11/MIT
360* See LICENSE.md
361*/
362
363/*global self */
364/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
365
366/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
367
368var saveAs = saveAs
369  // IE 10+ (native saveAs)
370  || (typeof navigator !== "undefined" &&
371      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
372  // Everyone else
373  || (function(view) {
374"use strict";
375// IE <10 is explicitly unsupported
376if (typeof navigator !== "undefined" &&
377/MSIE [1-9]\./.test(navigator.userAgent)) {
378return;
379}
380var
381doc = view.document
382// only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
383, get_URL = function() {
384return view.URL || view.webkitURL || view;
385}
386, URL = view.URL || view.webkitURL || view
387, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
388, can_use_save_link = !view.externalHost && "download" in save_link
389, click = function(node) {
390var event = doc.createEvent("MouseEvents");
391event.initMouseEvent(
392"click", true, false, view, 0, 0, 0, 0, 0
393, false, false, false, false, 0, null
394);
395node.dispatchEvent(event);
396}
397, webkit_req_fs = view.webkitRequestFileSystem
398, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
399, throw_outside = function(ex) {
400(view.setImmediate || view.setTimeout)(function() {
401throw ex;
402}, 0);
403}
404, force_saveable_type = "application/octet-stream"
405, fs_min_size = 0
406, deletion_queue = []
407, process_deletion_queue = function() {
408var i = deletion_queue.length;
409while (i--) {
410var file = deletion_queue[i];
411if (typeof file === "string") { // file is an object URL
412URL.revokeObjectURL(file);
413} else { // file is a File
414file.remove();
415}
416}
417deletion_queue.length = 0; // clear queue
418}
419, dispatch = function(filesaver, event_types, event) {
420event_types = [].concat(event_types);
421var i = event_types.length;
422while (i--) {
423var listener = filesaver["on" + event_types[i]];
424if (typeof listener === "function") {
425try {
426listener.call(filesaver, event || filesaver);
427} catch (ex) {
428throw_outside(ex);
429}
430}
431}
432}
433, FileSaver = function(blob, name) {
434// First try a.download, then web filesystem, then object URLs
435var
436filesaver = this
437, type = blob.type
438, blob_changed = false
439, object_url
440, target_view
441, get_object_url = function() {
442var object_url = get_URL().createObjectURL(blob);
443deletion_queue.push(object_url);
444return object_url;
445}
446, dispatch_all = function() {
447dispatch(filesaver, "writestart progress write writeend".split(" "));
448}
449// on any filesys errors revert to saving with object URLs
450, fs_error = function() {
451// don't create more object URLs than needed
452if (blob_changed || !object_url) {
453object_url = get_object_url(blob);
454}
455if (target_view) {
456target_view.location.href = object_url;
457} else {
458window.open(object_url, "_blank");
459}
460filesaver.readyState = filesaver.DONE;
461dispatch_all();
462}
463, abortable = function(func) {
464return function() {
465if (filesaver.readyState !== filesaver.DONE) {
466return func.apply(this, arguments);
467}
468};
469}
470, create_if_not_found = {create: true, exclusive: false}
471, slice
472;
473filesaver.readyState = filesaver.INIT;
474if (!name) {
475name = "download";
476}
477if (can_use_save_link) {
478object_url = get_object_url(blob);
479// FF for Android has a nasty garbage collection mechanism
480// that turns all objects that are not pure javascript into 'deadObject'
481// this means `doc` and `save_link` are unusable and need to be recreated
482// `view` is usable though:
483doc = view.document;
484save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
485save_link.href = object_url;
486save_link.download = name;
487var event = doc.createEvent("MouseEvents");
488event.initMouseEvent(
489"click", true, false, view, 0, 0, 0, 0, 0
490, false, false, false, false, 0, null
491);
492save_link.dispatchEvent(event);
493filesaver.readyState = filesaver.DONE;
494dispatch_all();
495return;
496}
497// Object and web filesystem URLs have a problem saving in Google Chrome when
498// viewed in a tab, so I force save with application/octet-stream
499// http://code.google.com/p/chromium/issues/detail?id=91158
500if (view.chrome && type && type !== force_saveable_type) {
501slice = blob.slice || blob.webkitSlice;
502blob = slice.call(blob, 0, blob.size, force_saveable_type);
503blob_changed = true;
504}
505// Since I can't be sure that the guessed media type will trigger a download
506// in WebKit, I append .download to the filename.
507// https://bugs.webkit.org/show_bug.cgi?id=65440
508if (webkit_req_fs && name !== "download") {
509name += ".download";
510}
511if (type === force_saveable_type || webkit_req_fs) {
512target_view = view;
513}
514if (!req_fs) {
515fs_error();
516return;
517}
518fs_min_size += blob.size;
519req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
520fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
521var save = function() {
522dir.getFile(name, create_if_not_found, abortable(function(file) {
523file.createWriter(abortable(function(writer) {
524writer.onwriteend = function(event) {
525target_view.location.href = file.toURL();
526deletion_queue.push(file);
527filesaver.readyState = filesaver.DONE;
528dispatch(filesaver, "writeend", event);
529};
530writer.onerror = function() {
531var error = writer.error;
532if (error.code !== error.ABORT_ERR) {
533fs_error();
534}
535};
536"writestart progress write abort".split(" ").forEach(function(event) {
537writer["on" + event] = filesaver["on" + event];
538});
539writer.write(blob);
540filesaver.abort = function() {
541writer.abort();
542filesaver.readyState = filesaver.DONE;
543};
544filesaver.readyState = filesaver.WRITING;
545}), fs_error);
546}), fs_error);
547};
548dir.getFile(name, {create: false}, abortable(function(file) {
549// delete file if it already exists
550file.remove();
551save();
552}), abortable(function(ex) {
553if (ex.code === ex.NOT_FOUND_ERR) {
554save();
555} else {
556fs_error();
557}
558}));
559}), fs_error);
560}), fs_error);
561}
562, FS_proto = FileSaver.prototype
563, saveAs = function(blob, name) {
564return new FileSaver(blob, name);
565}
566;
567FS_proto.abort = function() {
568var filesaver = this;
569filesaver.readyState = filesaver.DONE;
570dispatch(filesaver, "abort");
571};
572FS_proto.readyState = FS_proto.INIT = 0;
573FS_proto.WRITING = 1;
574FS_proto.DONE = 2;
575
576FS_proto.error =
577FS_proto.onwritestart =
578FS_proto.onprogress =
579FS_proto.onwrite =
580FS_proto.onabort =
581FS_proto.onerror =
582FS_proto.onwriteend =
583null;
584
585view.addEventListener("unload", process_deletion_queue, false);
586saveAs.unload = function() {
587process_deletion_queue();
588view.removeEventListener("unload", process_deletion_queue, false);
589};
590return saveAs;
591}(
592typeof self !== "undefined" && self
593|| typeof window !== "undefined" && window
594|| this.content
595));
596// `self` is undefined in Firefox for Android content script context
597// while `this` is nsIContentFrameMessageManager
598// with an attribute `content` that corresponds to the window
599
600if (typeof module !== "undefined" && module !== null) {
601  module.exports = saveAs;
602} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
603  define([], function() {
604    return saveAs;
605  });
606}
607
608
609// PLUGIN END //////////////////////////////////////////////////////////
610
611@@PLUGINEND@@