Html程序  |  784行  |  21.3 KB

<!DOCTYPE HTML>
<html i18n-values="dir:textdirection;">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
<link rel="icon" href="../../app/theme/downloads_favicon.png">
<style>
body {
  background-color: white;
  color: black;
  margin: 10px;
}

.header {
  overflow: auto;
  clear: both;
}

.header .logo {
  float: left;
}

.header .form {
  float: left;
  margin-top: 22px;
  -webkit-margin-start: 12px;
}

html[dir=rtl] .logo, html[dir=rtl] .form {
  float: right;
}

#downloads-summary {
  margin-top: 12px;
  border-top: 1px solid #9cc2ef;
  background-color: #ebeff9;
  padding: 3px;
  margin-bottom: 6px;
}

#downloads-summary-text {
  font-weight: bold;
}

#downloads-summary > a {
  float: right;
}

html[dir=rtl] #downloads-summary > a {
  float: left;
}

#downloads-display {
  max-width: 740px;
}

.download {
  position: relative;
  margin-top: 6px;
  -webkit-margin-start: 114px;
  -webkit-padding-start: 56px;
  margin-bottom: 15px;
}

.date-container {
  position: absolute;
  left: -110px;
  width: 110px;
}

html[dir=rtl] .date-container {
  left: auto;
  right: -110px;
}

.date-container .since {
  color: black;
}

.date-container .date {
  color: #666;
}

.download .icon {
  position: absolute;
  top: 2px;
  left: 9px;
  width: 32px;
  height: 32px;
}

html[dir=rtl] .icon {
  left: auto;
  right: 9px;
}

.download.otr > .safe,
.download.otr > .show-dangerous {
  background: url('shared/images/otr_icon_standalone.png') no-repeat 100% 100%;
  opacity: .66;
  -webkit-transition: opacity .15s;
}

html[dir=rtl] .download.otr > .safe,
html[dir=rtl] .download.otr > .show-dangerous {
  background-position: 0% 100%;
}

.download.otr > .safe:hover,
.download.otr > .show-dangerous:hover {
  opacity: 1;
}

.progress {
  position: absolute;
  top: -6px;
  left: 0px;
  width: 48px;
  height: 48px;
}

html[dir=rtl] .progress {
  left: auto;
  right: 0px;
}

.progress.background {
  background: url('../../app/theme/download_progress_background32.png');
}

.progress.foreground {
  background: url('../../app/theme/download_progress_foreground32.png');
}

.name {
  display: none;
  -webkit-padding-end: 16px;
  max-width: 450px;
  word-break: break-all;
}

.download .status {
  display: inline;
  color: #999;
  white-space: nowrap;
}

.download .url {
  color: #080;
  max-width: 500px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.controls a {
  color: #777;
  margin-right: 16px;
}

#downloads-pagination {
  padding-top: 24px;
  margin-left: 18px;
}

.page-navigation {
  padding: 8px;
  background-color: #ebeff9;
  margin-right: 4px;
}

.footer {
  height: 24px;
}

</style>
<script src="shared/js/local_strings.js"></script>
<script>

///////////////////////////////////////////////////////////////////////////////
// Helper functions
function $(o) {return document.getElementById(o);}

/**
 * Sets the display style of a node.
 */
function showInline(node, isShow) {
  node.style.display = isShow ? 'inline' : 'none';
}

function showInlineBlock(node, isShow) {
  node.style.display = isShow ? 'inline-block' : 'none';
}

/**
 * Creates an element of a specified type with a specified class name.
 * @param {String} type The node type.
 * @param {String} className The class name to use.
 */
function createElementWithClassName(type, className) {
  var elm = document.createElement(type);
  elm.className = className;
  return elm;
}

/**
 * Creates a link with a specified onclick handler and content
 * @param {String} onclick The onclick handler
 * @param {String} value The link text
 */
function createLink(onclick, value) {
  var link = document.createElement('a');
  link.onclick = onclick;
  link.href = '#';
  link.innerHTML = value;
  return link;
}

/**
 * Creates a button with a specified onclick handler and content
 * @param {String} onclick The onclick handler
 * @param {String} value The button text
 */
function createButton(onclick, value) {
  var button = document.createElement('input');
  button.type = 'button';
  button.value = value;
  button.onclick = onclick;
  return button;
}

///////////////////////////////////////////////////////////////////////////////
// Downloads
/**
 * Class to hold all the information about the visible downloads.
 */
function Downloads() {
  this.downloads_ = {};
  this.node_ = $('downloads-display');
  this.summary_ = $('downloads-summary-text');
  this.searchText_ = '';

  // Keep track of the dates of the newest and oldest downloads so that we
  // know where to insert them.
  this.newestTime_ = -1;
}

/**
 * Called when a download has been updated or added.
 * @param {Object} download A backend download object (see downloads_ui.cc)
 */
Downloads.prototype.updated = function(download) {
  var id = download.id;
  if (!!this.downloads_[id]) {
    this.downloads_[id].update(download);
  } else {
    this.downloads_[id] = new Download(download);
    // We get downloads in display order, so we don't have to worry about
    // maintaining correct order - we can assume that any downloads not in
    // display order are new ones and so we can add them to the top of the
    // list.
    if (download.started > this.newestTime_) {
      this.node_.insertBefore(this.downloads_[id].node, this.node_.firstChild);
      this.newestTime_ = download.started;
    } else {
      this.node_.appendChild(this.downloads_[id].node);
    }
    this.updateDateDisplay_();
  }
}

/**
 * Set our display search text.
 * @param {String} searchText The string we're searching for.
 */
Downloads.prototype.setSearchText = function(searchText) {
  this.searchText_ = searchText;
}

/**
 * Update the summary block above the results
 */
Downloads.prototype.updateSummary = function() {
  if (this.searchText_) {
    this.summary_.textContent = localStrings.getStringF('searchresultsfor',
                                                        this.searchText_);
  } else {
    this.summary_.innerHTML = localStrings.getString('downloads');
  }

  var hasDownloads = false;
  for (var i in this.downloads_) {
    hasDownloads = true;
    break;
  }

  if (!hasDownloads) {
    this.node_.innerHTML = localStrings.getString('noresults');
  }
}

/**
 * Update the date visibility in our nodes so that no date is
 * repeated.
 */
Downloads.prototype.updateDateDisplay_ = function() {
  var dateContainers = document.getElementsByClassName('date-container');
  var displayed = {};
  for (var i = 0, container; container = dateContainers[i]; i++) {
    var dateString = container.getElementsByClassName('date')[0].innerHTML;
    if (!!displayed[dateString]) {
      container.style.display = 'none';
    } else {
      displayed[dateString] = true;
      container.style.display = 'block';
    }
  }
}

/**
 * Remove a download.
 * @param {Number} id The id of the download to remove.
 */
Downloads.prototype.remove = function(id) {
  this.node_.removeChild(this.downloads_[id].node);
  delete this.downloads_[id];
  this.updateDateDisplay_();
}

/**
 * Clear all downloads and reset us back to a null state.
 */
Downloads.prototype.clear = function() {
  for (var id in this.downloads_) {
    this.downloads_[id].clear();
    this.remove(id);
  }
}

///////////////////////////////////////////////////////////////////////////////
// Download
/**
 * A download and the DOM representation for that download.
 * @param {Object} download A backend download object (see downloads_ui.cc)
 */
function Download(download) {
  // Create DOM
  this.node = createElementWithClassName('div','download' +
                                         (download.otr ? ' otr' : ''));

  // Dates
  this.dateContainer_ = createElementWithClassName('div', 'date-container');
  this.node.appendChild(this.dateContainer_);

  this.nodeSince_ = createElementWithClassName('div', 'since');
  this.nodeDate_ = createElementWithClassName('div', 'date');
  this.dateContainer_.appendChild(this.nodeSince_);
  this.dateContainer_.appendChild(this.nodeDate_);

  // Container for all 'safe download' UI.
  this.safe_ = createElementWithClassName('div', 'safe');
  this.safe_.ondragstart = this.drag_.bind(this);
  this.node.appendChild(this.safe_);

  if (download.state != Download.States.COMPLETE) {
    this.nodeProgressBackground_ =
        createElementWithClassName('div', 'progress background');
    this.safe_.appendChild(this.nodeProgressBackground_);

    this.canvasProgress_ =
        document.getCSSCanvasContext('2d', 'canvas_' + download.id,
            Download.Progress.width,
            Download.Progress.height);

    this.nodeProgressForeground_ =
        createElementWithClassName('div', 'progress foreground');
    this.nodeProgressForeground_.style.webkitMask =
        '-webkit-canvas(canvas_'+download.id+')';
    this.safe_.appendChild(this.nodeProgressForeground_);
  }

  this.nodeImg_ = createElementWithClassName('img', 'icon');
  this.safe_.appendChild(this.nodeImg_);

  // FileLink is used for completed downloads, otherwise we show FileName.
  this.nodeTitleArea_ = createElementWithClassName('div', 'title-area');
  this.safe_.appendChild(this.nodeTitleArea_);

  this.nodeFileLink_ = createLink(this.openFile_.bind(this), '');
  this.nodeFileLink_.className = 'name';
  this.nodeFileLink_.style.display = 'none';
  this.nodeTitleArea_.appendChild(this.nodeFileLink_);

  this.nodeFileName_ = createElementWithClassName('span', 'name');
  this.nodeFileName_.style.display = 'none';
  this.nodeTitleArea_.appendChild(this.nodeFileName_);

  this.nodeStatus_ = createElementWithClassName('span', 'status');
  this.nodeTitleArea_.appendChild(this.nodeStatus_);

  this.nodeURL_ = createElementWithClassName('div', 'url');
  this.safe_.appendChild(this.nodeURL_);

  // Controls.
  this.nodeControls_ = createElementWithClassName('div', 'controls');
  this.safe_.appendChild(this.nodeControls_);

  // We don't need "show in folder" in chromium os. See download_ui.cc and
  // http://code.google.com/p/chromium-os/issues/detail?id=916.
  var showinfolder = localStrings.getString('control_showinfolder');
  if (showinfolder) {
    this.controlShow_ = createLink(this.show_.bind(this), showinfolder);
    this.nodeControls_.appendChild(this.controlShow_);
  } else {
    this.controlShow_ = null;
  }

  this.controlRetry_ = document.createElement('a');
  this.controlRetry_.textContent = localStrings.getString('control_retry');
  this.nodeControls_.appendChild(this.controlRetry_);

  // Pause/Resume are a toggle.
  this.controlPause_ = createLink(this.togglePause_.bind(this),
      localStrings.getString('control_pause'));
  this.nodeControls_.appendChild(this.controlPause_);

  this.controlResume_ = createLink(this.togglePause_.bind(this),
      localStrings.getString('control_resume'));
  this.nodeControls_.appendChild(this.controlResume_);

  this.controlRemove_ = createLink(this.remove_.bind(this),
      localStrings.getString('control_removefromlist'));
  this.nodeControls_.appendChild(this.controlRemove_);

  this.controlCancel_ = createLink(this.cancel_.bind(this),
      localStrings.getString('control_cancel'));
  this.nodeControls_.appendChild(this.controlCancel_);

  // Container for 'unsafe download' UI.
  this.danger_ = createElementWithClassName('div', 'show-dangerous');
  this.node.appendChild(this.danger_);

  this.dangerDesc_ = document.createElement('div');
  this.danger_.appendChild(this.dangerDesc_);

  this.dangerSave_ = createButton(this.saveDangerous_.bind(this),
      localStrings.getString('danger_save'));
  this.danger_.appendChild(this.dangerSave_);

  this.dangerDiscard_ = createButton(this.discardDangerous_.bind(this),
      localStrings.getString('danger_discard'));
  this.danger_.appendChild(this.dangerDiscard_);

  // Update member vars.
  this.update(download);
}

/**
 * The states a download can be in. These correspond to states defined in
 * DownloadsDOMHandler::CreateDownloadItemValue
 */
Download.States = {
  IN_PROGRESS : "IN_PROGRESS",
  CANCELLED : "CANCELLED",
  COMPLETE : "COMPLETE",
  PAUSED : "PAUSED",
  DANGEROUS : "DANGEROUS",
  INTERRUPTED : "INTERRUPTED",
}

/**
 * Explains why a download is in DANGEROUS state.
 */
Download.DangerType = {
  NOT_DANGEROUS: "NOT_DANGEROUS",
  DANGEROUS_FILE: "DANGEROUS_FILE",
  DANGEROUS_URL: "DANGEROUS_URL",
}

/**
 * Constants for the progress meter.
 */
Download.Progress = {
  width : 48,
  height : 48,
  radius : 24,
  centerX : 24,
  centerY : 24,
  base : -0.5 * Math.PI,
  dir : false,
}

/**
 * Updates the download to reflect new data.
 * @param {Object} download A backend download object (see downloads_ui.cc)
 */
Download.prototype.update = function(download) {
  this.id_ = download.id;
  this.filePath_ = download.file_path;
  this.fileName_ = download.file_name;
  this.url_ = download.url;
  this.state_ = download.state;
  this.dangerType_ = download.danger_type;

  this.since_ = download.since_string;
  this.date_ = download.date_string;

  // See DownloadItem::PercentComplete
  this.percent_ = Math.max(download.percent, 0);
  this.progressStatusText_ = download.progress_status_text;
  this.received_ = download.received;

  if (this.state_ == Download.States.DANGEROUS) {
    if (this.dangerType_ == Download.DangerType.DANGEROUS_FILE) {
      this.dangerDesc_.innerHTML = localStrings.getStringF('danger_file_desc',
                                                           this.fileName_);
    } else {
      this.dangerDesc_.innerHTML = localStrings.getString('danger_url_desc');
    }
    this.danger_.style.display = 'block';
    this.safe_.style.display = 'none';
  } else {
    this.nodeImg_.src = 'chrome://fileicon/' + this.filePath_;

    if (this.state_ == Download.States.COMPLETE) {
      this.nodeFileLink_.innerHTML = this.fileName_;
      this.nodeFileLink_.href = this.filePath_;
    } else {
      this.nodeFileName_.innerHTML = this.fileName_;
    }

    showInline(this.nodeFileLink_, this.state_ == Download.States.COMPLETE);
    // nodeFileName_ has to be inline-block to avoid the 'interaction' with
    // nodeStatus_. If both are inline, it appears that their text contents
    // are merged before the bidi algorithm is applied leading to an
    // undesirable reordering. http://crbug.com/13216
    showInlineBlock(this.nodeFileName_, this.state_ != Download.States.COMPLETE);

    if (this.state_ == Download.States.IN_PROGRESS) {
      this.nodeProgressForeground_.style.display = 'block';
      this.nodeProgressBackground_.style.display = 'block';

      // Draw a pie-slice for the progress.
      this.canvasProgress_.clearRect(0, 0,
                                     Download.Progress.width,
                                     Download.Progress.height);
      this.canvasProgress_.beginPath();
      this.canvasProgress_.moveTo(Download.Progress.centerX,
                                  Download.Progress.centerY);

      // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
      this.canvasProgress_.arc(Download.Progress.centerX,
                               Download.Progress.centerY,
                               Download.Progress.radius,
                               Download.Progress.base,
                               Download.Progress.base + Math.PI * 0.02 *
                               Number(this.percent_),
                               false);

      this.canvasProgress_.lineTo(Download.Progress.centerX,
                                  Download.Progress.centerY);
      this.canvasProgress_.fill();
      this.canvasProgress_.closePath();
    } else if (this.nodeProgressBackground_) {
      this.nodeProgressForeground_.style.display = 'none';
      this.nodeProgressBackground_.style.display = 'none';
    }

    if (this.controlShow_) {
      showInline(this.controlShow_, this.state_ == Download.States.COMPLETE);
    }
    showInline(this.controlRetry_, this.state_ == Download.States.CANCELLED);
    this.controlRetry_.href = this.url_;
    showInline(this.controlPause_, this.state_ == Download.States.IN_PROGRESS);
    showInline(this.controlResume_, this.state_ == Download.States.PAUSED);
    var showCancel = this.state_ == Download.States.IN_PROGRESS ||
                     this.state_ == Download.States.PAUSED;
    showInline(this.controlCancel_, showCancel);
    showInline(this.controlRemove_, !showCancel);

    this.nodeSince_.innerHTML = this.since_;
    this.nodeDate_.innerHTML = this.date_;
    // Don't unnecessarily update the url, as doing so will remove any
    // text selection the user has started (http://crbug.com/44982).
    if (this.nodeURL_.textContent != this.url_)
      this.nodeURL_.textContent = this.url_;
    this.nodeStatus_.innerHTML = this.getStatusText_();

    this.danger_.style.display = 'none';
    this.safe_.style.display = 'block';
  }
}

/**
 * Removes applicable bits from the DOM in preparation for deletion.
 */
Download.prototype.clear = function() {
  this.safe_.ondragstart = null;
  this.nodeFileLink_.onclick = null;
  if (this.controlShow_) {
    this.controlShow_.onclick = null;
  }
  this.controlCancel_.onclick = null;
  this.controlPause_.onclick = null;
  this.controlResume_.onclick = null;
  this.dangerDiscard_.onclick = null;

  this.node.innerHTML = '';
}

/**
 * @return {String} User-visible status update text.
 */
Download.prototype.getStatusText_ = function() {
  switch (this.state_) {
    case Download.States.IN_PROGRESS:
      return this.progressStatusText_;
    case Download.States.CANCELLED:
      return localStrings.getString('status_cancelled');
    case Download.States.PAUSED:
      return localStrings.getString('status_paused');
    case Download.States.DANGEROUS:
      var desc = this.dangerType_ == Download.DangerType.DANGEROUS_FILE ?
          'danger_file_desc' : 'danger_url_desc';
      return localStrings.getString(desc);
    case Download.States.INTERRUPTED:
      return localStrings.getString('status_interrupted');
    case Download.States.COMPLETE:
      return '';
  }
}

/**
 * Tells the backend to initiate a drag, allowing users to drag
 * files from the download page and have them appear as native file
 * drags.
 */
Download.prototype.drag_ = function() {
  chrome.send('drag', [this.id_.toString()]);
  return false;
}

/**
 * Tells the backend to open this file.
 */
Download.prototype.openFile_ = function() {
  chrome.send('openFile', [this.id_.toString()]);
  return false;
}

/**
 * Tells the backend that the user chose to save a dangerous file.
 */
Download.prototype.saveDangerous_ = function() {
  chrome.send('saveDangerous', [this.id_.toString()]);
  return false;
}

/**
 * Tells the backend that the user chose to discard a dangerous file.
 */
Download.prototype.discardDangerous_ = function() {
  chrome.send('discardDangerous', [this.id_.toString()]);
  downloads.remove(this.id_);
  return false;
}

/**
 * Tells the backend to show the file in explorer.
 */
Download.prototype.show_ = function() {
  chrome.send('show', [this.id_.toString()]);
  return false;
}

/**
 * Tells the backend to pause this download.
 */
Download.prototype.togglePause_ = function() {
  chrome.send('togglepause', [this.id_.toString()]);
  return false;
}

/**
 * Tells the backend to remove this download from history and download shelf.
 */
 Download.prototype.remove_ = function() {
  chrome.send('remove', [this.id_.toString()]);
  return false;
}

/**
 * Tells the backend to cancel this download.
 */
Download.prototype.cancel_ = function() {
  chrome.send('cancel', [this.id_.toString()]);
  return false;
}

///////////////////////////////////////////////////////////////////////////////
// Page:
var downloads, localStrings, resultsTimeout;

function load() {
  localStrings = new LocalStrings();
  downloads = new Downloads();
  $('term').focus();
  setSearch('');
}

function setSearch(searchText) {
  downloads.clear();
  downloads.setSearchText(searchText);
  chrome.send('getDownloads', [searchText.toString()]);
}

function clearAll() {
  downloads.clear();
  downloads.setSearchText('');
  chrome.send('clearAll', []);
  return false;
}

///////////////////////////////////////////////////////////////////////////////
// Chrome callbacks:
/**
 * Our history system calls this function with results from searches or when
 * downloads are added or removed.
 */
function downloadsList(results) {
  if (resultsTimeout)
    clearTimeout(resultsTimeout);
  window.console.log('results');
  downloads.clear();
  downloadUpdated(results);
  downloads.updateSummary();
}

/**
 * When a download is updated (progress, state change), this is called.
 */
function downloadUpdated(results) {
  // Sometimes this can get called too early.
  if (!downloads)
    return;

  var start = Date.now();
  for (var i = 0; i < results.length; i++) {
    downloads.updated(results[i]);
    // Do as much as we can in 50ms.
    if (Date.now() - start > 50) {
      clearTimeout(resultsTimeout);
      resultsTimeout = setTimeout(downloadUpdated, 5, results.slice(i + 1));
      break;
    }
  }
}

</script>
</head>
<body onload="load();" i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
<div class="header">
  <a href="" onclick="setSearch(''); return false;">
    <img src="shared/images/downloads_section.png"
         width="67" height="67" class="logo" border="0" /></a>
  <form method="post" action=""
      onsubmit="setSearch(this.term.value); return false;"
      class="form">
    <input type="text" name="term" id="term" />
    <input type="submit" name="submit" i18n-values="value:searchbutton" />
  </form>
</div>
<div class="main">
  <div id="downloads-summary">
    <span id="downloads-summary-text" i18n-content="downloads">Downloads</span>
    <a id="clear-all" href="" onclick="clearAll();" i18n-content="clear_all">Clear All</a>
  </div>
  <div id="downloads-display"></div>
</div>
<div class="footer">
</div>
</body>
</html>