1 <!DOCTYPE HTML> 2 <html i18n-values="dir:textdirection;"> 3 <head> 4 <meta charset="utf-8"> 5 <title i18n-content="title"></title> 6 <link rel="icon" href="../../app/theme/downloads_favicon.png"> 7 <style type="text/css"> 8 div.header { 9 border-bottom: 1px solid #7289E2; 10 padding: 8px; 11 margin: 0; 12 width: 100%; 13 left: 0; 14 top: 0; 15 height: 32px; 16 position: absolute; 17 box-sizing: border-box; 18 background-image: -webkit-linear-gradient(#D0DAF8, #A6BAF7); 19 border-bottom-color: #999; 20 border-bottom-width: 1px; 21 border-left-color: #999; 22 border-left-width: 1px; 23 border-right-color: #999; 24 border-right-width: 1px; 25 color: black; 26 } 27 28 *:-khtml-drag { 29 background-color: rgba(238,238,238, 0.5); 30 } 31 32 *[draggable] { 33 -khtml-user-drag: element; 34 cursor: move; 35 } 36 37 ul.downloadlist { 38 list-style-type: none; 39 margin: 0; 40 padding: 0; 41 position: relative; 42 } 43 44 .menuicon { 45 position: absolute; 46 right: 9px; 47 top: 11px; 48 height: 100%; 49 width: 5px; 50 margin-left: 0; 51 background: url('chrome://resources/images/active_downloads_menu.png'); 52 margin-top: 5px; 53 background-repeat: no-repeat; 54 } 55 56 .menubutton { 57 position: absolute; 58 margin-top: -36px; 59 right: 0; 60 height: 35px; 61 width: 24px; 62 border-bottom: 1px solid #CCC; 63 cursor: pointer; 64 } 65 66 .rowbg { 67 border-bottom: 1px solid #CCC; 68 background: -webkit-gradient(linear, left top, left bottom, 69 from(#f3f3f3), to(#ebebeb), color-stop(0.8, #ededed)); 70 } 71 72 .rowbg:hover { 73 background: -webkit-gradient(linear, left top, left bottom, 74 from(#fdfdfd), to(#f1f1f1), color-stop(0.8, #f5f5f5)); 75 } 76 77 .rowbg:active { 78 background: -webkit-gradient(linear, left top, left bottom, 79 from(#d2e0f0), to(#dee6f0), color-stop(0.1, #e3e9f0)); 80 } 81 82 .downloadrow { 83 height: 36px; 84 } 85 86 .rowbutton { 87 padding: 5px 5px 0 34px; 88 position: relative; 89 right: 24px; 90 border-right: 1px solid #CCC; 91 height: 30px; 92 } 93 94 .rowbutton div.icon { 95 float: left; 96 margin-top: 1px; 97 display: inline 98 position: relative; 99 width: 21px; 100 height: 17px; 101 background-repeat: no-repeat; 102 } 103 104 .rowbutton span.title { 105 position: relative; 106 text-overflow: ellipsis; 107 white-space: nowrap; 108 display: inline-block; 109 overflow: hidden; 110 width: 189px; 111 color: #325282; 112 } 113 114 .rowbutton span.downloading { 115 top: -6px; 116 font-size: .8em; 117 } 118 119 .rowbutton span.downloaded { 120 font-size: .8em; 121 } 122 123 .rowbutton span.error { 124 font-size: .6em; 125 } 126 127 .allowdownload { 128 margin: -10px 5px 5px 30px; 129 display: block; 130 } 131 132 .allowdownloadtext { 133 font-size: .6em; 134 color: #325282; 135 } 136 137 .confirm { 138 font-size: .6em; 139 text-decoration: underline; 140 margin-left: 5px; 141 color: #254f9b; 142 cursor: pointer; 143 } 144 145 .progress { 146 font-size: .6em; 147 text-align: right; 148 margin-left: auto; 149 margin-top: -5px; 150 } 151 152 div.columnlist { 153 width: 100%; 154 top: 0; 155 left: 0; 156 bottom: 29px; /* space for Show All Downloads */ 157 position: absolute; 158 background: #e8e8e8 159 } 160 161 span.showalldownloadstext { 162 color: #254f9b; 163 cursor: pointer; 164 text-decoration: underline; 165 font-size: 12px; 166 height: 15px; 167 } 168 169 div.showalldownloads { 170 width: 100%; 171 bottom: 0; 172 height: 29px; 173 position: absolute; 174 margin-left: -8px; 175 text-align: center; 176 background: #e8e8e8 177 } 178 179 .menu { 180 top: 14px; 181 right: 2px; 182 -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 3px; 183 border-bottom-left-radius: 4px 4px; 184 border-bottom-right-radius: 4px 4px; 185 border-top-left-radius: 4px 4px; 186 border-top-right-radius: 0px 0px; 187 position: absolute; 188 display: none; 189 z-index: 999; 190 background: white; 191 border-top-left-radius: 4px; 192 border: 1px solid rgba(0, 0, 0, 0.6); 193 padding: 5px; 194 } 195 196 .menuitem { 197 width: 100%; 198 height: 20px; 199 font-size: .8em; 200 text-align: left; 201 cursor: pointer; 202 left: 0; 203 color: #0D0052; 204 -webkit-transition: color 1.0s ease-out ; 205 } 206 207 .menuitem:hover { 208 text-decoration: underline; 209 color: #20c; 210 background: #ebeff9; 211 -webkit-transition: color 0.0s ease-out ; 212 } 213 214 div.iconmedia { 215 background: url('chrome://resources/images/icon_media.png'); 216 } 217 218 div.iconfolder { 219 background: url('chrome://resources/images/icon_folder.png'); 220 } 221 222 div.iconfile { 223 background: url('chrome://resources/images/icon_file.png'); 224 } 225 226 div.iconphoto { 227 background: url('chrome://resources/images/icon_photo.png'); 228 } 229 230 div.iconmusic { 231 background: url('chrome://resources/images/icon_media.png'); 232 } 233 234 </style> 235 <script src="shared/js/local_strings.js"></script> 236 <script src="shared/js/media_common.js"></script> 237 <script src="shared/js/util.js"></script> 238 <script> 239 240 var localStrings = null; 241 var downloadRowList = null; 242 243 function init() { 244 localStrings = new LocalStrings(); 245 initTestHarness(); 246 247 $('header').style.display = 'none'; 248 249 $('showalldownloadstext').textContent = 250 localStrings.getString('showalldownloads'); 251 252 downloadRowList = new DownloadRowList(); 253 chrome.send('getDownloads', []); 254 } 255 256 /** 257 * Testing. Allow this page to be loaded in a browser. 258 * Create stubs for localStrings and chrome.send. 259 */ 260 var testHarnessEnabled = false; 261 function initTestHarness() { 262 if (testHarnessEnabled) { 263 localStrings = { 264 getString: function(name) { 265 if (name == 'showalldownloads') 266 return 'Show All Downloads'; 267 if (name == 'allowdownload') 268 return 'Allow Download?'; 269 if (name == 'confirmyes') 270 return 'Yes'; 271 if (name == 'confirmcancel') 272 return 'Cancel'; 273 return name; 274 }, 275 getStringF: function(name, path) { 276 return path + ' - Unknown file type.'; 277 }, 278 }; 279 chrome.send = function(name, ary) { 280 console.log('chrome.send ' + name + ' ' + ary); 281 if (name == 'getDownloads' || 282 (name == 'openNewFullWindow' && 283 ary[0] == 'chrome://downloads')) 284 sendTestResults(); 285 } 286 } 287 } 288 289 /** 290 * Create a results array with test data and call downloadsList. 291 */ 292 var id = 1; 293 var results = []; 294 function sendTestResults() { 295 results.push({ 296 state: (id % 2 ? 'DANGEROUS' : 'COMPLETE'), 297 percent: (id % 2 ? 90 : 100), 298 id: id, 299 file_name: ' Test' + id + '.pdf', 300 file_path: '/home/achuith/Downloads/Test' + id + '.pdf', 301 progress_status_text : (id % 2 ? 302 'download progressing nicely' : 'download complete'), 303 }); 304 id++; 305 downloadsList(results); 306 } 307 308 /** 309 * Current Menu. 310 */ 311 var menu = { 312 current_: null, 313 314 /** 315 * Close the current menu. 316 */ 317 clear: function() { 318 var current = this.current_; 319 if (current) { 320 current.firstChild.style.display = 'none'; 321 current.style.opacity = ''; 322 this.current_ = null; 323 } 324 }, 325 326 /** 327 * If it's a second click on an open menu, close the menu. 328 * Otherwise, close any other open menu and open the clicked menu. 329 */ 330 clicked: function(row) { 331 var menuicon = row.menuicon; 332 if (this.current_ === menuicon) { 333 this.clear(); 334 return; 335 } 336 this.clear(); 337 if (menuicon.firstChild.style.display != 'block') { 338 menuicon.firstChild.style.display = 'block'; 339 menuicon.style.opacity = '1'; 340 menuicon.scrollIntoView(); 341 this.current_ = menuicon; 342 } 343 window.event.stopPropagation(); 344 }, 345 }; 346 347 /** 348 * C++ api calls. 349 */ 350 function downloadsList(results) { 351 downloadRowList.list(results); 352 } 353 354 function downloadUpdated(result) { 355 downloadRowList.update(result); 356 } 357 358 function showAllDownloads() { 359 chrome.send('openNewFullWindow', ['chrome://downloads']); 360 dialogClose(); 361 } 362 363 function dialogClose() { 364 chrome.send('DialogClose', ['']); 365 } 366 367 /** 368 * DownloadRow contains all the elements that go into a row of the downloads 369 * list. It represents a single DownloadItem. 370 * 371 * @param {DownloadRowList} list Global DownloadRowList. 372 * @param {Object} result JSON representation of DownloadItem. 373 * @constructor 374 */ 375 function DownloadRow(list, result) { 376 this.path = result.file_path; 377 this.name = result.file_name; 378 this.list = list; 379 this.id = result.id; 380 381 this.createRow_(list); 382 this.createRowButton_(); 383 this.createMenu_(); 384 } 385 386 DownloadRow.prototype = { 387 /** 388 * Create the row html element and book-keeping for the row. 389 * @param {DownloadRowList} list global DownloadRowList instance. 390 * @private 391 */ 392 createRow_: function(list) { 393 var row = document.createElement('li'); 394 row.className = 'downloadrow'; 395 row.id = this.path; 396 row.downloadRow = this; 397 list.append(row); 398 399 this.element = row; 400 this.list.downloadList.push(this); 401 }, 402 403 getIconClass_: function() { 404 if (pathIsImageFile(this.path)) { 405 return 'icon iconphoto'; 406 } else if (pathIsVideoFile(this.path)) { 407 return 'icon iconmedia'; 408 } else if (pathIsAudioFile(this.path)) { 409 return 'icon iconmusic'; 410 } 411 return 'icon iconfile'; 412 }, 413 414 setErrorText_: function(text) { 415 this.filename.textContent = text; 416 this.filename.className = 'error title'; 417 }, 418 419 supportsPdf_: function() { 420 return 'application/pdf' in navigator.mimeTypes; 421 }, 422 423 openFilePath_: function() { 424 chrome.send('openNewFullWindow', ['file://' + this.path]); 425 }, 426 427 /** 428 * Determine onclick behavior based on filename. 429 * @private 430 */ 431 getFunctionForItem_: function() { 432 var path = this.path; 433 var self = this; 434 435 if (pathIsAudioFile(path)) { 436 return function() { 437 chrome.send('playMediaFile', [path]); 438 }; 439 } 440 if (pathIsVideoFile(path)) { 441 return function() { 442 chrome.send('playMediaFile', [path]); 443 }; 444 } 445 if (pathIsImageFile(path)) { 446 return function() { 447 self.openFilePath_(); 448 } 449 } 450 if (pathIsHtmlFile(path)) { 451 return function() { 452 self.openFilePath_(); 453 } 454 } 455 if (pathIsPdfFile(path) && this.supportsPdf_()) { 456 return function() { 457 self.openFilePath_(); 458 } 459 } 460 461 return function() { 462 self.setErrorText_(localStrings.getStringF('error_unknown_file_type', 463 self.name)); 464 }; 465 }, 466 467 /** 468 * Create a child element. 469 * 470 * @param {string} type The type - div, span, etc. 471 * @param {string} className The class name 472 * @param {HTMLElement} parent Parent to append this child to. 473 * @param {string} textContent optional text content of child. 474 * @param {function(*)} onclick onclick function of child. 475 * @private 476 */ 477 createChild_: function(type, className, parent, textContent, onclick) { 478 var elem = document.createElement(type); 479 elem.className = className; 480 if (textContent !== undefined) 481 elem.textContent = textContent; 482 elem.onclick = onclick; 483 parent.appendChild(elem); 484 return elem; 485 }, 486 487 /** 488 * Create the row button for the left of the row. 489 * This contains the icon, filename and error elements. 490 * @private 491 */ 492 createRowButton_: function () { 493 this.rowbutton = this.createChild_('div', 'rowbutton rowbg', this.element); 494 495 // Icon. 496 var icon = this.createChild_('div', this.getIconClass_(), this.rowbutton); 497 498 // Filename. 499 this.filename = this.createChild_('span', 'downloaded title', 500 this.rowbutton, this.name); 501 }, 502 503 /** 504 * Create the menu button on the right of the row. 505 * This contains the menuicon. The menuicon contains the menu, which 506 * contains items for Open, Pause/Resume and Cancel. 507 * @private 508 */ 509 createMenu_: function() { 510 var self = this; 511 this.menubutton = this.createChild_('div', 'menubutton rowbg', 512 this.element, '', 513 function() { 514 menu.clicked(self); 515 }); 516 517 this.menuicon = this.createChild_('div', 'menuicon', this.menubutton); 518 this.menuicon.align = 'right'; 519 520 var menudiv = this.createChild_('div', 'menu', this.menuicon); 521 522 this.open = this.createChild_('div', 'menuitem', menudiv, 523 localStrings.getString('open'), this.getFunctionForItem_()); 524 525 this.pause = this.createChild_('div', 'menuitem', menudiv, 526 localStrings.getString('pause'), function() { 527 self.pauseToggleDownload_(); 528 }); 529 530 this.cancel = this.createChild_('div', 'menuitem', menudiv, 531 localStrings.getString('cancel'), function() { 532 self.cancelDownload_(); 533 }); 534 535 this.pause.style.display = 'none'; 536 this.cancel.style.display = 'none'; 537 }, 538 539 allowDownload_: function() { 540 chrome.send('allowDownload', ['' + this.id]); 541 }, 542 543 cancelDownload_: function() { 544 chrome.send('cancelDownload', ['' + this.id]); 545 }, 546 547 pauseToggleDownload_: function() { 548 this.pause.textContent = 549 (this.pause.textContent == localStrings.getString('pause')) ? 550 localStrings.getString('resume') : 551 localStrings.getString('pause'); 552 553 // Convert id to string before send. 554 chrome.send('pauseToggleDownload', ['' + this.id]); 555 }, 556 557 resetRow_: function() { 558 this.rowbutton.onclick = ''; 559 this.rowbutton.style.cursor = ''; 560 this.rowbutton.setAttribute('draggable', 'false'); 561 }, 562 563 createAllowDownload_: function() { 564 if (this.allowdownload) 565 return; 566 567 this.allowdownload = this.createChild_('div', 'allowdownload', 568 this.rowbutton); 569 570 this.createChild_('span', 'allowdownloadtext', this.allowdownload, 571 localStrings.getString('allowdownload')); 572 573 var self = this; 574 this.createChild_('span', 'confirm', this.allowdownload, 575 localStrings.getString('confirmyes'), 576 function() { 577 self.allowDownload_(); 578 }); 579 this.createChild_('span', 'confirm', this.allowdownload, 580 localStrings.getString('confirmcancel'), 581 function() { 582 self.cancelDownload_(); 583 }); 584 585 this.resetRow_(); 586 this.menubutton.onclick = ''; 587 }, 588 589 removeAllowDownload_: function() { 590 if (this.allowdownload) { 591 this.rowbutton.removeChild(this.allowdownload); 592 this.allowdownload = null; 593 var self = this; 594 this.menubutton.onclick = function() { 595 menu.clicked(self); 596 }; 597 } 598 }, 599 600 createProgress_: function() { 601 if (this.progress) 602 return; 603 604 this.progress = this.createChild_('div', 'progress', this.rowbutton); 605 606 // Menu has Pause/Cancel. Open hidden. 607 this.open.style.display = 'none'; 608 this.pause.style.display = ''; 609 this.cancel.style.display = ''; 610 }, 611 612 removeProgress_: function() { 613 if (this.progress) { 614 this.rowbutton.removeChild(this.progress); 615 this.progress = null; 616 } 617 }, 618 619 updatePause_: function(result) { 620 var pause = this.pause; 621 var pauseStr = localStrings.getString('pause'); 622 var resumeStr = localStrings.getString('resume'); 623 624 if (pause && 625 result.state == 'PAUSED' && 626 pause.textContent != resumeStr) { 627 pause.textContent = resumeStr; 628 } else if (pause && 629 result.state == 'IN_PROGRESS' && 630 pause.textContent != pauseStr) { 631 pause.textContent = pauseStr; 632 } 633 }, 634 635 updateProgress_: function(result) { 636 this.removeAllowDownload_(); 637 this.createProgress_(); 638 this.progress.textContent = result.progress_status_text; 639 this.updatePause_(result); 640 }, 641 642 /** 643 * Called when the item has finished downloading. Switch the menu 644 * and remove the progress bar. 645 * @private 646 */ 647 finishedDownloading_: function() { 648 this.filename.className = 'downloaded title'; 649 650 // Menu has Open. Pause/Cancel hidden. 651 this.open.style.display = ''; 652 this.pause.style.display = 'none'; 653 this.cancel.style.display = 'none'; 654 655 // Make rowbutton clickable. 656 this.rowbutton.onclick = this.getFunctionForItem_(); 657 this.rowbutton.style.cursor = 'pointer'; 658 659 // Make rowbutton draggable. 660 this.rowbutton.setAttribute('draggable', 'true'); 661 var self = this; 662 this.rowbutton.addEventListener('dragstart', function(e) { 663 e.dataTransfer.effectAllowed = 'copy'; 664 e.dataTransfer.setData('Text', self.path); 665 e.dataTransfer.setData('URL', 'file:///' + self.path); 666 }, false); 667 668 this.removeAllowDownload_(); 669 this.removeProgress_(); 670 }, 671 672 /** 673 * One of the DownloadItem we are observing has updated. 674 * @param {Object} result JSON representation of DownloadItem. 675 */ 676 downloadUpdated: function(result) { 677 this.filename.textContent = result.file_name; 678 this.filename.className = 'downloading title'; 679 680 if (result.state == 'CANCELLED' || 681 result.state == 'INTERRUPTED') { 682 this.list.remove(this); 683 } else if (result.state == 'DANGEROUS') { 684 this.createAllowDownload_(); 685 } else if (result.percent < 100) { 686 this.updateProgress_(result); 687 } else { 688 this.finishedDownloading_(); 689 } 690 }, 691 }; 692 693 /** 694 * DownloadRowList is a container for DownloadRows. 695 */ 696 function DownloadRowList() { 697 var downloadpath = localStrings.getString('downloadpath'); 698 699 var list = document.createElement('ul'); 700 list.className = 'downloadlist'; 701 list.id = downloadpath; 702 this.element = list; 703 this.rows = []; 704 705 document.title = downloadpath.split('/').pop(); 706 707 $('main').appendChild(list); 708 } 709 710 DownloadRowList.prototype = { 711 712 /** 713 * ROW_HEIGHT is height of each row. 714 * MAX_ROWS is maximum number of rows displayed (to display a new row 715 * beyond MAX_ROWS, we delete the oldest row). 716 * MIN_ROWS is the minimum number of rows displayed. 717 * numRows is the current number of rows. 718 * downloadList is the list of DownloadRow elements. 719 */ 720 ROW_HEIGHT: 36, 721 MAX_ROWS: 5, 722 MIN_ROWS: 1, 723 numRows: 0, 724 downloadList: [], 725 726 numRowsOutsideRange_: function() { 727 return this.numRows > this.MIN_ROWS && this.numRows < this.MAX_ROWS; 728 }, 729 730 /** 731 * Remove a row from the list, as when a download is canceled, or 732 * the the number of rows has exceeded the max allowed. 733 * 734 * @param {DownloadRow} row Row to be removed. 735 * @private 736 */ 737 remove: function(row) { 738 this.downloadList.splice(this.downloadList.indexOf(row), 1); 739 this.element.removeChild(row.element); 740 row.element.downloadRow = null; 741 742 this.numRows--; 743 if (this.numRowsOutsideRange_()) 744 window.resizeBy(0, -this.ROW_HEIGHT); 745 }, 746 747 /** 748 * Append a new row to the list, removing the last row if we exceed the 749 * maximum allowed. 750 * @param {DownloadRow} row Row to be removed. 751 */ 752 append: function(row) { 753 if (this.numRowsOutsideRange_()) 754 window.resizeBy(0, this.ROW_HEIGHT); 755 756 this.numRows++; 757 758 var list = this.element; 759 if (this.numRows > this.MAX_ROWS) 760 this.remove(list.lastChild.downloadRow); 761 762 if (list.firstChild) { 763 list.insertBefore(row, list.firstChild); 764 } else { 765 list.appendChild(row); 766 } 767 }, 768 769 /** 770 * Handle list callback with list of DownloadItems. 771 * @param {Array} results Array of JSONified DownloadItems. 772 */ 773 list: function(results) { 774 var removeList = []; 775 removeList.pushUnique = function(element) { 776 if (this.indexOf(element) == -1) 777 this.push(element); 778 }; 779 780 for (var y = 0; y < this.downloadList.length; y++) { 781 var found = false; 782 for (var x = 0; x < results.length; x++) { 783 var element = $(results[x].file_path); 784 if (this.downloadList[y].element == element) { 785 found = true; 786 break; 787 } 788 } 789 if (!found) 790 removeList.pushUnique(this.downloadList[y]); 791 } 792 793 for (var i = 0; i < results.length; i++) { 794 this.update(results[i]); 795 } 796 797 for (i = 0; i < removeList.length; i++) { 798 this.remove(removeList[i]); 799 } 800 }, 801 802 /** 803 * Handle update of a DownloadItem we're observing. 804 * @param {Object} result JSON representation of DownloadItem. 805 */ 806 update: function(result) { 807 var element = $(result.file_path); 808 var row = element && element.downloadRow; 809 810 if (!row && 811 result.state != 'CANCELLED' && 812 result.state != 'INTERRUPTED') { 813 row = new DownloadRow(this, result); 814 } 815 816 row && row.downloadUpdated(result); 817 }, 818 }; 819 820 </script> 821 <body onload="init();" onclick="menu.clear()" onselectstart="return false" 822 i18n-values=".style.fontFamily:fontfamily"> 823 <div id="header"> 824 <div id="currenttitle"></div> 825 </div><br> 826 <div id="main" class="columnlist"></div> 827 <div id="showalldownloads" class="showalldownloads"> 828 <span id="showalldownloadstext" class="showalldownloadstext" 829 onclick="showAllDownloads()"></span> 830 </div> 831 </body> 832 </html> 833