<!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 type="text/css"> div.header { border-bottom: 1px solid #7289E2; padding: 8px; margin: 0; width: 100%; left: 0; top: 0; height: 32px; position: absolute; box-sizing: border-box; background-image: -webkit-linear-gradient(#D0DAF8, #A6BAF7); border-bottom-color: #999; border-bottom-width: 1px; border-left-color: #999; border-left-width: 1px; border-right-color: #999; border-right-width: 1px; color: black; } *:-khtml-drag { background-color: rgba(238,238,238, 0.5); } *[draggable] { -khtml-user-drag: element; cursor: move; } ul.downloadlist { list-style-type: none; margin: 0; padding: 0; position: relative; } .menuicon { position: absolute; right: 9px; top: 11px; height: 100%; width: 5px; margin-left: 0; background: url('chrome://resources/images/active_downloads_menu.png'); margin-top: 5px; background-repeat: no-repeat; } .menubutton { position: absolute; margin-top: -36px; right: 0; height: 35px; width: 24px; border-bottom: 1px solid #CCC; cursor: pointer; } .rowbg { border-bottom: 1px solid #CCC; background: -webkit-gradient(linear, left top, left bottom, from(#f3f3f3), to(#ebebeb), color-stop(0.8, #ededed)); } .rowbg:hover { background: -webkit-gradient(linear, left top, left bottom, from(#fdfdfd), to(#f1f1f1), color-stop(0.8, #f5f5f5)); } .rowbg:active { background: -webkit-gradient(linear, left top, left bottom, from(#d2e0f0), to(#dee6f0), color-stop(0.1, #e3e9f0)); } .downloadrow { height: 36px; } .rowbutton { padding: 5px 5px 0 34px; position: relative; right: 24px; border-right: 1px solid #CCC; height: 30px; } .rowbutton div.icon { float: left; margin-top: 1px; display: inline position: relative; width: 21px; height: 17px; background-repeat: no-repeat; } .rowbutton span.title { position: relative; text-overflow: ellipsis; white-space: nowrap; display: inline-block; overflow: hidden; width: 189px; color: #325282; } .rowbutton span.downloading { top: -6px; font-size: .8em; } .rowbutton span.downloaded { font-size: .8em; } .rowbutton span.error { font-size: .6em; } .allowdownload { margin: -10px 5px 5px 30px; display: block; } .allowdownloadtext { font-size: .6em; color: #325282; } .confirm { font-size: .6em; text-decoration: underline; margin-left: 5px; color: #254f9b; cursor: pointer; } .progress { font-size: .6em; text-align: right; margin-left: auto; margin-top: -5px; } div.columnlist { width: 100%; top: 0; left: 0; bottom: 29px; /* space for Show All Downloads */ position: absolute; background: #e8e8e8 } span.showalldownloadstext { color: #254f9b; cursor: pointer; text-decoration: underline; font-size: 12px; height: 15px; } div.showalldownloads { width: 100%; bottom: 0; height: 29px; position: absolute; margin-left: -8px; text-align: center; background: #e8e8e8 } .menu { top: 14px; right: 2px; -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 3px; border-bottom-left-radius: 4px 4px; border-bottom-right-radius: 4px 4px; border-top-left-radius: 4px 4px; border-top-right-radius: 0px 0px; position: absolute; display: none; z-index: 999; background: white; border-top-left-radius: 4px; border: 1px solid rgba(0, 0, 0, 0.6); padding: 5px; } .menuitem { width: 100%; height: 20px; font-size: .8em; text-align: left; cursor: pointer; left: 0; color: #0D0052; -webkit-transition: color 1.0s ease-out ; } .menuitem:hover { text-decoration: underline; color: #20c; background: #ebeff9; -webkit-transition: color 0.0s ease-out ; } div.iconmedia { background: url('chrome://resources/images/icon_media.png'); } div.iconfolder { background: url('chrome://resources/images/icon_folder.png'); } div.iconfile { background: url('chrome://resources/images/icon_file.png'); } div.iconphoto { background: url('chrome://resources/images/icon_photo.png'); } div.iconmusic { background: url('chrome://resources/images/icon_media.png'); } </style> <script src="shared/js/local_strings.js"></script> <script src="shared/js/media_common.js"></script> <script src="shared/js/util.js"></script> <script> var localStrings = null; var downloadRowList = null; function init() { localStrings = new LocalStrings(); initTestHarness(); $('header').style.display = 'none'; $('showalldownloadstext').textContent = localStrings.getString('showalldownloads'); downloadRowList = new DownloadRowList(); chrome.send('getDownloads', []); } /** * Testing. Allow this page to be loaded in a browser. * Create stubs for localStrings and chrome.send. */ var testHarnessEnabled = false; function initTestHarness() { if (testHarnessEnabled) { localStrings = { getString: function(name) { if (name == 'showalldownloads') return 'Show All Downloads'; if (name == 'allowdownload') return 'Allow Download?'; if (name == 'confirmyes') return 'Yes'; if (name == 'confirmcancel') return 'Cancel'; return name; }, getStringF: function(name, path) { return path + ' - Unknown file type.'; }, }; chrome.send = function(name, ary) { console.log('chrome.send ' + name + ' ' + ary); if (name == 'getDownloads' || (name == 'openNewFullWindow' && ary[0] == 'chrome://downloads')) sendTestResults(); } } } /** * Create a results array with test data and call downloadsList. */ var id = 1; var results = []; function sendTestResults() { results.push({ state: (id % 2 ? 'DANGEROUS' : 'COMPLETE'), percent: (id % 2 ? 90 : 100), id: id, file_name: ' Test' + id + '.pdf', file_path: '/home/achuith/Downloads/Test' + id + '.pdf', progress_status_text : (id % 2 ? 'download progressing nicely' : 'download complete'), }); id++; downloadsList(results); } /** * Current Menu. */ var menu = { current_: null, /** * Close the current menu. */ clear: function() { var current = this.current_; if (current) { current.firstChild.style.display = 'none'; current.style.opacity = ''; this.current_ = null; } }, /** * If it's a second click on an open menu, close the menu. * Otherwise, close any other open menu and open the clicked menu. */ clicked: function(row) { var menuicon = row.menuicon; if (this.current_ === menuicon) { this.clear(); return; } this.clear(); if (menuicon.firstChild.style.display != 'block') { menuicon.firstChild.style.display = 'block'; menuicon.style.opacity = '1'; menuicon.scrollIntoView(); this.current_ = menuicon; } window.event.stopPropagation(); }, }; /** * C++ api calls. */ function downloadsList(results) { downloadRowList.list(results); } function downloadUpdated(result) { downloadRowList.update(result); } function showAllDownloads() { chrome.send('openNewFullWindow', ['chrome://downloads']); dialogClose(); } function dialogClose() { chrome.send('DialogClose', ['']); } /** * DownloadRow contains all the elements that go into a row of the downloads * list. It represents a single DownloadItem. * * @param {DownloadRowList} list Global DownloadRowList. * @param {Object} result JSON representation of DownloadItem. * @constructor */ function DownloadRow(list, result) { this.path = result.file_path; this.name = result.file_name; this.list = list; this.id = result.id; this.createRow_(list); this.createRowButton_(); this.createMenu_(); } DownloadRow.prototype = { /** * Create the row html element and book-keeping for the row. * @param {DownloadRowList} list global DownloadRowList instance. * @private */ createRow_: function(list) { var row = document.createElement('li'); row.className = 'downloadrow'; row.id = this.path; row.downloadRow = this; list.append(row); this.element = row; this.list.downloadList.push(this); }, getIconClass_: function() { if (pathIsImageFile(this.path)) { return 'icon iconphoto'; } else if (pathIsVideoFile(this.path)) { return 'icon iconmedia'; } else if (pathIsAudioFile(this.path)) { return 'icon iconmusic'; } return 'icon iconfile'; }, setErrorText_: function(text) { this.filename.textContent = text; this.filename.className = 'error title'; }, supportsPdf_: function() { return 'application/pdf' in navigator.mimeTypes; }, openFilePath_: function() { chrome.send('openNewFullWindow', ['file://' + this.path]); }, /** * Determine onclick behavior based on filename. * @private */ getFunctionForItem_: function() { var path = this.path; var self = this; if (pathIsAudioFile(path)) { return function() { chrome.send('playMediaFile', [path]); }; } if (pathIsVideoFile(path)) { return function() { chrome.send('playMediaFile', [path]); }; } if (pathIsImageFile(path)) { return function() { self.openFilePath_(); } } if (pathIsHtmlFile(path)) { return function() { self.openFilePath_(); } } if (pathIsPdfFile(path) && this.supportsPdf_()) { return function() { self.openFilePath_(); } } return function() { self.setErrorText_(localStrings.getStringF('error_unknown_file_type', self.name)); }; }, /** * Create a child element. * * @param {string} type The type - div, span, etc. * @param {string} className The class name * @param {HTMLElement} parent Parent to append this child to. * @param {string} textContent optional text content of child. * @param {function(*)} onclick onclick function of child. * @private */ createChild_: function(type, className, parent, textContent, onclick) { var elem = document.createElement(type); elem.className = className; if (textContent !== undefined) elem.textContent = textContent; elem.onclick = onclick; parent.appendChild(elem); return elem; }, /** * Create the row button for the left of the row. * This contains the icon, filename and error elements. * @private */ createRowButton_: function () { this.rowbutton = this.createChild_('div', 'rowbutton rowbg', this.element); // Icon. var icon = this.createChild_('div', this.getIconClass_(), this.rowbutton); // Filename. this.filename = this.createChild_('span', 'downloaded title', this.rowbutton, this.name); }, /** * Create the menu button on the right of the row. * This contains the menuicon. The menuicon contains the menu, which * contains items for Open, Pause/Resume and Cancel. * @private */ createMenu_: function() { var self = this; this.menubutton = this.createChild_('div', 'menubutton rowbg', this.element, '', function() { menu.clicked(self); }); this.menuicon = this.createChild_('div', 'menuicon', this.menubutton); this.menuicon.align = 'right'; var menudiv = this.createChild_('div', 'menu', this.menuicon); this.open = this.createChild_('div', 'menuitem', menudiv, localStrings.getString('open'), this.getFunctionForItem_()); this.pause = this.createChild_('div', 'menuitem', menudiv, localStrings.getString('pause'), function() { self.pauseToggleDownload_(); }); this.cancel = this.createChild_('div', 'menuitem', menudiv, localStrings.getString('cancel'), function() { self.cancelDownload_(); }); this.pause.style.display = 'none'; this.cancel.style.display = 'none'; }, allowDownload_: function() { chrome.send('allowDownload', ['' + this.id]); }, cancelDownload_: function() { chrome.send('cancelDownload', ['' + this.id]); }, pauseToggleDownload_: function() { this.pause.textContent = (this.pause.textContent == localStrings.getString('pause')) ? localStrings.getString('resume') : localStrings.getString('pause'); // Convert id to string before send. chrome.send('pauseToggleDownload', ['' + this.id]); }, resetRow_: function() { this.rowbutton.onclick = ''; this.rowbutton.style.cursor = ''; this.rowbutton.setAttribute('draggable', 'false'); }, createAllowDownload_: function() { if (this.allowdownload) return; this.allowdownload = this.createChild_('div', 'allowdownload', this.rowbutton); this.createChild_('span', 'allowdownloadtext', this.allowdownload, localStrings.getString('allowdownload')); var self = this; this.createChild_('span', 'confirm', this.allowdownload, localStrings.getString('confirmyes'), function() { self.allowDownload_(); }); this.createChild_('span', 'confirm', this.allowdownload, localStrings.getString('confirmcancel'), function() { self.cancelDownload_(); }); this.resetRow_(); this.menubutton.onclick = ''; }, removeAllowDownload_: function() { if (this.allowdownload) { this.rowbutton.removeChild(this.allowdownload); this.allowdownload = null; var self = this; this.menubutton.onclick = function() { menu.clicked(self); }; } }, createProgress_: function() { if (this.progress) return; this.progress = this.createChild_('div', 'progress', this.rowbutton); // Menu has Pause/Cancel. Open hidden. this.open.style.display = 'none'; this.pause.style.display = ''; this.cancel.style.display = ''; }, removeProgress_: function() { if (this.progress) { this.rowbutton.removeChild(this.progress); this.progress = null; } }, updatePause_: function(result) { var pause = this.pause; var pauseStr = localStrings.getString('pause'); var resumeStr = localStrings.getString('resume'); if (pause && result.state == 'PAUSED' && pause.textContent != resumeStr) { pause.textContent = resumeStr; } else if (pause && result.state == 'IN_PROGRESS' && pause.textContent != pauseStr) { pause.textContent = pauseStr; } }, updateProgress_: function(result) { this.removeAllowDownload_(); this.createProgress_(); this.progress.textContent = result.progress_status_text; this.updatePause_(result); }, /** * Called when the item has finished downloading. Switch the menu * and remove the progress bar. * @private */ finishedDownloading_: function() { this.filename.className = 'downloaded title'; // Menu has Open. Pause/Cancel hidden. this.open.style.display = ''; this.pause.style.display = 'none'; this.cancel.style.display = 'none'; // Make rowbutton clickable. this.rowbutton.onclick = this.getFunctionForItem_(); this.rowbutton.style.cursor = 'pointer'; // Make rowbutton draggable. this.rowbutton.setAttribute('draggable', 'true'); var self = this; this.rowbutton.addEventListener('dragstart', function(e) { e.dataTransfer.effectAllowed = 'copy'; e.dataTransfer.setData('Text', self.path); e.dataTransfer.setData('URL', 'file:///' + self.path); }, false); this.removeAllowDownload_(); this.removeProgress_(); }, /** * One of the DownloadItem we are observing has updated. * @param {Object} result JSON representation of DownloadItem. */ downloadUpdated: function(result) { this.filename.textContent = result.file_name; this.filename.className = 'downloading title'; if (result.state == 'CANCELLED' || result.state == 'INTERRUPTED') { this.list.remove(this); } else if (result.state == 'DANGEROUS') { this.createAllowDownload_(); } else if (result.percent < 100) { this.updateProgress_(result); } else { this.finishedDownloading_(); } }, }; /** * DownloadRowList is a container for DownloadRows. */ function DownloadRowList() { var downloadpath = localStrings.getString('downloadpath'); var list = document.createElement('ul'); list.className = 'downloadlist'; list.id = downloadpath; this.element = list; this.rows = []; document.title = downloadpath.split('/').pop(); $('main').appendChild(list); } DownloadRowList.prototype = { /** * ROW_HEIGHT is height of each row. * MAX_ROWS is maximum number of rows displayed (to display a new row * beyond MAX_ROWS, we delete the oldest row). * MIN_ROWS is the minimum number of rows displayed. * numRows is the current number of rows. * downloadList is the list of DownloadRow elements. */ ROW_HEIGHT: 36, MAX_ROWS: 5, MIN_ROWS: 1, numRows: 0, downloadList: [], numRowsOutsideRange_: function() { return this.numRows > this.MIN_ROWS && this.numRows < this.MAX_ROWS; }, /** * Remove a row from the list, as when a download is canceled, or * the the number of rows has exceeded the max allowed. * * @param {DownloadRow} row Row to be removed. * @private */ remove: function(row) { this.downloadList.splice(this.downloadList.indexOf(row), 1); this.element.removeChild(row.element); row.element.downloadRow = null; this.numRows--; if (this.numRowsOutsideRange_()) window.resizeBy(0, -this.ROW_HEIGHT); }, /** * Append a new row to the list, removing the last row if we exceed the * maximum allowed. * @param {DownloadRow} row Row to be removed. */ append: function(row) { if (this.numRowsOutsideRange_()) window.resizeBy(0, this.ROW_HEIGHT); this.numRows++; var list = this.element; if (this.numRows > this.MAX_ROWS) this.remove(list.lastChild.downloadRow); if (list.firstChild) { list.insertBefore(row, list.firstChild); } else { list.appendChild(row); } }, /** * Handle list callback with list of DownloadItems. * @param {Array} results Array of JSONified DownloadItems. */ list: function(results) { var removeList = []; removeList.pushUnique = function(element) { if (this.indexOf(element) == -1) this.push(element); }; for (var y = 0; y < this.downloadList.length; y++) { var found = false; for (var x = 0; x < results.length; x++) { var element = $(results[x].file_path); if (this.downloadList[y].element == element) { found = true; break; } } if (!found) removeList.pushUnique(this.downloadList[y]); } for (var i = 0; i < results.length; i++) { this.update(results[i]); } for (i = 0; i < removeList.length; i++) { this.remove(removeList[i]); } }, /** * Handle update of a DownloadItem we're observing. * @param {Object} result JSON representation of DownloadItem. */ update: function(result) { var element = $(result.file_path); var row = element && element.downloadRow; if (!row && result.state != 'CANCELLED' && result.state != 'INTERRUPTED') { row = new DownloadRow(this, result); } row && row.downloadUpdated(result); }, }; </script> <body onload="init();" onclick="menu.clear()" onselectstart="return false" i18n-values=".style.fontFamily:fontfamily"> <div id="header"> <div id="currenttitle"></div> </div><br> <div id="main" class="columnlist"></div> <div id="showalldownloads" class="showalldownloads"> <span id="showalldownloadstext" class="showalldownloadstext" onclick="showAllDownloads()"></span> </div> </body> </html>