OLD | NEW |
(Empty) | |
| 1 <!DOCTYPE HTML> |
| 2 <html id="t"> |
| 3 <head> |
| 4 <meta charset="utf-8"> |
| 5 <title jscontent="title"></title> |
| 6 <style type="text/css"> |
| 7 body { |
| 8 font-family:arial; |
| 9 background-color:white; |
| 10 color:black; |
| 11 font-size:84%; |
| 12 margin:10px; |
| 13 } |
| 14 .header { |
| 15 overflow:auto; |
| 16 clear:both; |
| 17 } |
| 18 .header .logo { |
| 19 float:left; |
| 20 } |
| 21 .header .form { |
| 22 float:left; |
| 23 margin-top:22px; |
| 24 margin-left:12px; |
| 25 } |
| 26 #downloads-summary { |
| 27 margin-top:12px; |
| 28 border-top:1px solid #9cc2ef; |
| 29 background-color:#ebeff9; |
| 30 font-weight:bold; |
| 31 padding:3px; |
| 32 margin-bottom:6px; |
| 33 } |
| 34 #downloads-display { |
| 35 max-width:740px; |
| 36 } |
| 37 .download { |
| 38 position:relative; |
| 39 padding-left:45px; |
| 40 margin-bottom:16px; |
| 41 } |
| 42 .download .icon { |
| 43 position:absolute; |
| 44 top:2px; |
| 45 left:2px; |
| 46 width:32px; |
| 47 height:32px; |
| 48 } |
| 49 .name { |
| 50 display:none; |
| 51 padding-right:16px; |
| 52 max-width:450px; |
| 53 text-overflow:ellipsis; |
| 54 } |
| 55 .download .status { |
| 56 display:inline; |
| 57 color:#999; |
| 58 } |
| 59 .download .url { |
| 60 color:#080; |
| 61 width:500px; |
| 62 white-space:nowrap; |
| 63 overflow:hidden; |
| 64 text-overflow:ellipsis; |
| 65 } |
| 66 .controls a { |
| 67 color:#777; |
| 68 } |
| 69 #downloads-pagination { |
| 70 padding-top:24px; |
| 71 margin-left:18px; |
| 72 } |
| 73 .page-navigation { |
| 74 padding:8px; |
| 75 background-color:#ebeff9; |
| 76 margin-right:4px; |
| 77 } |
| 78 .footer { |
| 79 height:24px; |
| 80 } |
| 81 </style> |
| 82 <script type="text/javascript"> |
| 83 /////////////////////////////////////////////////////////////////////////////// |
| 84 // localStrings: |
| 85 /** |
| 86 * We get strings into the page by using JSTemplate to populate some elements |
| 87 * with localized content, then reading the content of those elements into |
| 88 * this global strings object. |
| 89 * @param {Node} node The DOM node containing all our strings. |
| 90 */ |
| 91 function LocalStrings(node) { |
| 92 this.strings_ = {}; |
| 93 |
| 94 var children = node.childNodes; |
| 95 for (var i = 0, child; child = children[i]; i++) { |
| 96 var id = child.id; |
| 97 if (id) { |
| 98 this.strings_[id] = child.innerHTML; |
| 99 } |
| 100 } |
| 101 } |
| 102 |
| 103 /** |
| 104 * Gets a localized string by its id. |
| 105 * @param {string} s The id of the string we want |
| 106 * @return {string} The localized string |
| 107 */ |
| 108 LocalStrings.prototype.getString = function(s) { |
| 109 return (s in this.strings_) ? this.strings_[s] : ''; |
| 110 } |
| 111 |
| 112 /** |
| 113 * Returns a formatted localized string (where all %s contents are replaced |
| 114 * by the second argument). |
| 115 * @param {string} s The id of the string we want |
| 116 * @param {string} d The string to include in the formatted string |
| 117 * @return {string} The formatted string. |
| 118 */ |
| 119 LocalStrings.prototype.formatString = function(s, d) { |
| 120 return (s in this.strings_) ? this.strings_[s].replace(/\%s/, d) : ''; |
| 121 } |
| 122 |
| 123 /////////////////////////////////////////////////////////////////////////////// |
| 124 // Helper functions |
| 125 function $(o) {return document.getElementById(o);} |
| 126 |
| 127 function bind(fn, selfObj, var_args) { |
| 128 var boundArgs = Array.prototype.slice.call(arguments, 2); |
| 129 return function() { |
| 130 var args = Array.prototype.slice.call(arguments); |
| 131 args.unshift.apply(args, boundArgs); |
| 132 return fn.apply(selfObj, args); |
| 133 } |
| 134 } |
| 135 |
| 136 /** |
| 137 * Sets the display style of a node. |
| 138 */ |
| 139 function showInline(node, isShow) { |
| 140 node.style.display = isShow ? 'inline' : 'none'; |
| 141 } |
| 142 |
| 143 /** |
| 144 * Creates an element of a specified type with a specified class name. |
| 145 * @param {String} type The node type. |
| 146 * @param {String} className The class name to use. |
| 147 */ |
| 148 function createElementWithClassName(type, className) { |
| 149 var elm = document.createElement(type); |
| 150 elm.className = className; |
| 151 return elm; |
| 152 } |
| 153 |
| 154 /** |
| 155 * Creates a link with a specified onclick handler and content |
| 156 * @param {String} onclick The onclick handler |
| 157 * @param {String} value The link text |
| 158 */ |
| 159 function createLink(onclick, value) { |
| 160 var link = document.createElement("a"); |
| 161 link.onclick = onclick; |
| 162 link.href = '#'; |
| 163 link.innerHTML = value; |
| 164 return link; |
| 165 } |
| 166 |
| 167 /** |
| 168 * Creates a button with a specified onclick handler and content |
| 169 * @param {String} onclick The onclick handler |
| 170 * @param {String} value The button text |
| 171 */ |
| 172 function createButton(onclick, value) { |
| 173 var button = document.createElement("input"); |
| 174 button.type = 'button'; |
| 175 button.value = value; |
| 176 button.onclick = onclick; |
| 177 return button; |
| 178 } |
| 179 |
| 180 /////////////////////////////////////////////////////////////////////////////// |
| 181 // Downloads |
| 182 /** |
| 183 * Class to hold all the information about the visible downloads. |
| 184 */ |
| 185 function Downloads() { |
| 186 this.downloads_ = {}; |
| 187 this.node_ = $('downloads-display'); |
| 188 this.summary_ = $('downloads-summary'); |
| 189 this.searchText_ = ""; |
| 190 } |
| 191 |
| 192 /** |
| 193 * Called when a download has been updated or added. |
| 194 * @param {Object} download A backend download object (see downloads_ui.cc) |
| 195 */ |
| 196 Downloads.prototype.updated = function(download) { |
| 197 var id = download.id; |
| 198 if (!!this.downloads_[id]) { |
| 199 this.downloads_[id].update(download); |
| 200 } else { |
| 201 this.downloads_[id] = new Download(download); |
| 202 // We get downloads in display order, so we don't have to worry about |
| 203 // maintaining correct order for now. |
| 204 this.node_.insertBefore(this.downloads_[id].node, |
| 205 this.node_.firstChild); |
| 206 } |
| 207 } |
| 208 |
| 209 /** |
| 210 * Set our display search text. |
| 211 * @param {String} searchText The string we're searching for. |
| 212 */ |
| 213 Downloads.prototype.setSearchText = function(searchText) { |
| 214 this.searchText_ = searchText; |
| 215 } |
| 216 |
| 217 /** |
| 218 * Update the summary block above the results |
| 219 */ |
| 220 Downloads.prototype.updateSummary = function() { |
| 221 if (this.searchText_) { |
| 222 this.summary_.innerHTML = localStrings.formatString( |
| 223 'searchresultsfor', this.searchText_); |
| 224 } else { |
| 225 this.summary_.innerHTML = localStrings.getString('downloads'); |
| 226 } |
| 227 |
| 228 var hasDownloads = false; |
| 229 for (var i in this.downloads_) { |
| 230 hasDownloads = true; |
| 231 break; |
| 232 } |
| 233 |
| 234 if (!hasDownloads) { |
| 235 this.node_.innerHTML = localStrings.getString('noresults'); |
| 236 } |
| 237 } |
| 238 |
| 239 /** |
| 240 * Remove a download. |
| 241 * @param {Number} id The id of the download to remove. |
| 242 */ |
| 243 Downloads.prototype.remove = function(id) { |
| 244 this.node_.removeChild(this.downloads_[id].node); |
| 245 delete this.downloads_[id]; |
| 246 } |
| 247 |
| 248 /** |
| 249 * Clear all downloads and reset us back to a null state. |
| 250 */ |
| 251 Downloads.prototype.clear = function() { |
| 252 for (var id in this.downloads_) { |
| 253 this.downloads_[id].clear(); |
| 254 this.remove(id); |
| 255 } |
| 256 } |
| 257 |
| 258 /////////////////////////////////////////////////////////////////////////////// |
| 259 // Download |
| 260 /** |
| 261 * A download and the DOM representation for that download. |
| 262 * @param {Object} download A backend download object (see downloads_ui.cc) |
| 263 */ |
| 264 function Download(download) { |
| 265 // Create DOM |
| 266 this.node = createElementWithClassName('div', 'download'); |
| 267 |
| 268 // Container for all 'safe download' UI. |
| 269 this.safe_ = document.createElement("div"); |
| 270 this.safe_.ondragstart = bind(this.drag_, this); |
| 271 this.node.appendChild(this.safe_); |
| 272 |
| 273 this.nodeImg_ = createElementWithClassName('img', 'icon'); |
| 274 this.safe_.appendChild(this.nodeImg_); |
| 275 |
| 276 // FileLink is used for completed downloads, otherwise we show FileName. |
| 277 this.nodeTitleArea_ = createElementWithClassName('div', 'title-area'); |
| 278 this.safe_.appendChild(this.nodeTitleArea_); |
| 279 |
| 280 this.nodeFileLink_ = createLink(bind(this.openFile_, this), ''); |
| 281 this.nodeFileLink_.className = 'name'; |
| 282 this.nodeFileLink_.style.display = 'none'; |
| 283 this.nodeTitleArea_.appendChild(this.nodeFileLink_); |
| 284 |
| 285 this.nodeFileName_ = createElementWithClassName('span', 'name'); |
| 286 this.nodeFileName_.style.display = 'none'; |
| 287 this.nodeTitleArea_.appendChild(this.nodeFileName_); |
| 288 |
| 289 this.nodeStatus_ = createElementWithClassName('span', 'status'); |
| 290 this.nodeTitleArea_.appendChild(this.nodeStatus_); |
| 291 |
| 292 this.nodeURL_ = createElementWithClassName('div', 'url'); |
| 293 this.safe_.appendChild(this.nodeURL_); |
| 294 |
| 295 // Controls. |
| 296 this.nodeControls_ = createElementWithClassName('div', 'controls'); |
| 297 this.safe_.appendChild(this.nodeControls_); |
| 298 |
| 299 this.controlShow_ = createLink(bind(this.show_, this), |
| 300 localStrings.getString('control_showinfolder')); |
| 301 this.nodeControls_.appendChild(this.controlShow_); |
| 302 |
| 303 this.controlPause_ = createLink(bind(this.pause_, this), |
| 304 localStrings.getString('control_pause')); |
| 305 this.nodeControls_.appendChild(this.controlPause_); |
| 306 |
| 307 this.controlCancel_ = createLink(bind(this.cancel_, this), |
| 308 localStrings.getString('control_cancel')); |
| 309 this.nodeControls_.appendChild(this.controlCancel_); |
| 310 |
| 311 // Container for 'unsafe download' UI. |
| 312 this.danger_ = createElementWithClassName('div', 'show-dangerous'); |
| 313 this.node.appendChild(this.danger_); |
| 314 |
| 315 this.dangerDesc_ = document.createElement("div"); |
| 316 this.danger_.appendChild(this.dangerDesc_); |
| 317 |
| 318 this.dangerSave_ = createButton(bind(this.saveDangerous_, this), |
| 319 localStrings.getString("danger_save")); |
| 320 this.danger_.appendChild(this.dangerSave_); |
| 321 |
| 322 this.dangerDiscard_ = createButton(bind(this.discardDangerous_, this), |
| 323 localStrings.getString("danger_discard")); |
| 324 this.danger_.appendChild(this.dangerDiscard_); |
| 325 |
| 326 // Update member vars. |
| 327 this.update(download); |
| 328 } |
| 329 |
| 330 /** |
| 331 * The states a download can be in. These correspond to states defined in |
| 332 * DownloadsDOMHandler::CreateDownloadItemValue |
| 333 */ |
| 334 Download.States = { |
| 335 IN_PROGRESS : "IN_PROGRESS", |
| 336 CANCELLED : "CANCELLED", |
| 337 COMPLETE : "COMPLETE", |
| 338 PAUSED : "PAUSED", |
| 339 DANGEROUS : "DANGEROUS", |
| 340 } |
| 341 |
| 342 /** |
| 343 * Updates the download to reflect new data. |
| 344 * @param {Object} download A backend download object (see downloads_ui.cc) |
| 345 */ |
| 346 Download.prototype.update = function(download) { |
| 347 this.id_ = download.id; |
| 348 this.filePath_ = download.file_path; |
| 349 this.fileName_ = download.file_name; |
| 350 this.url_ = download.url; |
| 351 this.state_ = download.state; |
| 352 this.percent_ = download.percent; |
| 353 this.speed_ = download.speed; |
| 354 this.received_ = download.received; |
| 355 |
| 356 if (this.state_ == Download.States.DANGEROUS) { |
| 357 this.dangerDesc_.innerHTML = localStrings.formatString('danger_desc', |
| 358 this.fileName_); |
| 359 this.danger_.style.display = 'block'; |
| 360 this.safe_.style.display = 'none'; |
| 361 } else { |
| 362 this.nodeImg_.src = 'chrome-ui://fileicon/' + this.filePath_; |
| 363 |
| 364 if (this.state_ == Download.States.COMPLETE) { |
| 365 this.nodeFileLink_.innerHTML = this.fileName_; |
| 366 this.nodeFileLink_.href = this.filePath_; |
| 367 } else { |
| 368 this.nodeFileName_.innerHTML = this.fileName_; |
| 369 } |
| 370 |
| 371 showInline(this.nodeFileLink_, this.state_ == Download.States.COMPLETE); |
| 372 showInline(this.nodeFileName_, this.state_ != Download.States.COMPLETE); |
| 373 |
| 374 showInline(this.controlShow_, this.state_ == Download.States.COMPLETE); |
| 375 showInline(this.controlPause_, this.state_ == Download.States.IN_PROGRESS); |
| 376 showInline(this.controlCancel_, this.state_ == Download.States.IN_PROGRESS); |
| 377 |
| 378 this.nodeURL_.innerHTML = this.url_; |
| 379 this.nodeStatus_.innerHTML = this.getStatusText_(); |
| 380 |
| 381 this.danger_.style.display = 'none'; |
| 382 this.safe_.style.display = 'block'; |
| 383 } |
| 384 } |
| 385 |
| 386 /** |
| 387 * Removes applicable bits from the DOM in preparation for deletion. |
| 388 */ |
| 389 Download.prototype.clear = function() { |
| 390 this.safe_.ondragstart = null; |
| 391 this.nodeFileLink_.onclick = null; |
| 392 this.controlShow_.onclick = null; |
| 393 this.controlCancel_.onclick = null; |
| 394 this.controlPause_.onclick = null; |
| 395 this.dangerDiscard_.onclick = null; |
| 396 |
| 397 this.node.innerHTML = ''; |
| 398 } |
| 399 |
| 400 /** |
| 401 * @return {String} User-visible status update text. |
| 402 */ |
| 403 Download.prototype.getStatusText_ = function() { |
| 404 switch (this.state_) { |
| 405 case Download.States.IN_PROGRESS: |
| 406 // TODO(glen): Make pretty, localize, etc. |
| 407 return this.percent_ + '% at ' + this.speed_; |
| 408 case Download.States.CANCELLED: |
| 409 return localStrings.getString('status_cancelled'); |
| 410 case Download.States.PAUSED: |
| 411 return localStrings.getString('status_paused'); |
| 412 case Download.States.DANGEROUS: |
| 413 return localStrings.getString('danger_desc'); |
| 414 case Download.States.COMPLETE: |
| 415 return ""; |
| 416 } |
| 417 } |
| 418 |
| 419 /** |
| 420 * Tells the backend to initiate a drag, allowing users to drag |
| 421 * files from the download page and have them appear as native file |
| 422 * drags. |
| 423 */ |
| 424 Download.prototype.drag_ = function() { |
| 425 chrome.send("drag", [this.id_.toString()]); |
| 426 return false; |
| 427 } |
| 428 |
| 429 /** |
| 430 * Tells the backend to open this file. |
| 431 */ |
| 432 Download.prototype.openFile_ = function() { |
| 433 chrome.send("openFile", [this.id_.toString()]); |
| 434 return false; |
| 435 } |
| 436 |
| 437 /** |
| 438 * Tells the backend that the user chose to save a dangerous file. |
| 439 */ |
| 440 Download.prototype.saveDangerous_ = function() { |
| 441 chrome.send("saveDangerous", [this.id_.toString()]); |
| 442 return false; |
| 443 } |
| 444 |
| 445 /** |
| 446 * Tells the backend that the user chose to discard a dangerous file. |
| 447 */ |
| 448 Download.prototype.discardDangerous_ = function() { |
| 449 chrome.send("discardDangerous", [this.id_.toString()]); |
| 450 downloads.remove(this.id_); |
| 451 return false; |
| 452 } |
| 453 |
| 454 /** |
| 455 * Tells the backend to show the file in explorer. |
| 456 */ |
| 457 Download.prototype.show_ = function() { |
| 458 chrome.send("show", [this.id_.toString()]); |
| 459 return false; |
| 460 } |
| 461 |
| 462 /** |
| 463 * Tells the backend to pause this download. |
| 464 */ |
| 465 Download.prototype.pause_ = function() { |
| 466 chrome.send("pause", [this.id_.toString()]); |
| 467 return false; |
| 468 } |
| 469 |
| 470 /** |
| 471 * Tells the backend to cancel this download. |
| 472 */ |
| 473 Download.prototype.cancel_ = function() { |
| 474 chrome.send("cancel", [this.id_.toString()]); |
| 475 return false; |
| 476 } |
| 477 |
| 478 /////////////////////////////////////////////////////////////////////////////// |
| 479 // Page: |
| 480 var downloads, localStrings; |
| 481 |
| 482 function load() { |
| 483 localStrings = new LocalStrings($('l10n')); |
| 484 downloads = new Downloads(); |
| 485 setSearch(""); |
| 486 } |
| 487 |
| 488 function setSearch(searchText) { |
| 489 downloads.clear(); |
| 490 downloads.setSearchText(searchText); |
| 491 chrome.send("getDownloads", [searchText.toString()]); |
| 492 } |
| 493 |
| 494 /////////////////////////////////////////////////////////////////////////////// |
| 495 // Chrome callbacks: |
| 496 /** |
| 497 * Our history system calls this function with results from searches or when |
| 498 * downloads are added or removed. |
| 499 */ |
| 500 function downloadsList(results) { |
| 501 downloadUpdated(results); |
| 502 downloads.updateSummary(); |
| 503 } |
| 504 |
| 505 /** |
| 506 * When a download is updated (progress, state change), this is called. |
| 507 */ |
| 508 function downloadUpdated(results) { |
| 509 for (var i = 0, thisResult; thisResult = results[i]; i++) { |
| 510 downloads.updated(thisResult); |
| 511 } |
| 512 } |
| 513 </script> |
| 514 </head> |
| 515 <body onload="load();"> |
| 516 <div class="header"> |
| 517 <a href="" onclick="setSearch(''); return false;"> |
| 518 <img src="../../app/theme/downloads_section.png" |
| 519 width="67" height="67" class="logo" border="0" /></a> |
| 520 <form method="post" action="" |
| 521 onsubmit="setSearch(this.term.value); return false;" |
| 522 class="form"> |
| 523 <input type="text" name="term" id="term" /> |
| 524 <input type="submit" name="submit" jsvalues="value:searchbutton" /> |
| 525 </form> |
| 526 </div> |
| 527 <div class="main"> |
| 528 <div id="downloads-summary"></div> |
| 529 <div id="downloads-display"></div> |
| 530 </div> |
| 531 <div class="footer"> |
| 532 </div> |
| 533 <div id="l10n" style="display:none;"> |
| 534 <span id="searchresultsfor" jscontent="searchresultsfor">Search results for '%
s'</span> |
| 535 <span id="no_results" jscontent="no_results">No results found.</span> |
| 536 <span id="downloads" jscontent="downloads">Downloads</span> |
| 537 |
| 538 <span id="status_cancelled" jscontent="status_cancelled">Cancelled</span> |
| 539 <span id="status_paused" jscontent="status_paused">Paused</span> |
| 540 |
| 541 <span id="danger_desc" jscontent="danger_desc">This is a dangerous file</span> |
| 542 <span id="danger_save" jscontent="danger_save">Save</span> |
| 543 <span id="danger_discard" jscontent="danger_discard">Discard</span> |
| 544 |
| 545 <span id="control_pause" jscontent="control_pause">Pause</span> |
| 546 <span id="control_showinfolder" jscontent="control_showinfolder">Show in folde
r</span> |
| 547 <span id="control_cancel" jscontent="control_cancel">Cancel</span> |
| 548 <span id="control_resume" jscontent="control_resume">Resume</span> |
| 549 </div> |
| 550 </body> |
| 551 </html> |
OLD | NEW |