Electron 44 File Download Experience (#116)

* Implemented File Download Experience

1. Initiate download manager when a file download is initiated.
2. Add items to download manager when new items are downloaded.
3. Allow user to open file in default OS app.
4. Allow user to show file in finder/explorer.
5. Allow user to close download bar.

* 1. Removed underscore
2. Added safety checks
3. Added support for popouts
4. Creating most elements of download manager if electron

* 1. Moved download manager logic to a separate file

* 1. Added styles to help pushing up the content of the app when the download manager is being shown.

* 1. Added tests for Download Manager.

* Removed unnecessary code.

* Made adjustments to handle positioning of the download manager through the flexbox model rather than the fixed positioning way.

* Removed data attributes being added to body to handle download manager.
This commit is contained in:
Vikas Shashidhar 2017-06-27 20:38:58 +05:30 committed by Lynn
parent 150ebaf70a
commit 299e75eca3
6 changed files with 310 additions and 1 deletions

View File

@ -0,0 +1,188 @@
'use strict';
const { ipcRenderer, remote } = require('electron');
const local = {
ipcRenderer: ipcRenderer,
downloadItems: []
};
// listen for file download complete event
local.ipcRenderer.on('downloadCompleted', (event, arg) => {
createDOM(arg);
});
// listen for file download progress event
local.ipcRenderer.on('downloadProgress', () => {
initiate();
});
/**
* Open file in default app.
*/
function openFile(id) {
let fileIndex = local.downloadItems.findIndex((item) => {
return item._id === id
});
if (fileIndex !== -1){
let openResponse = remote.shell.openExternal(`file:///${local.downloadItems[fileIndex].savedPath}`);
if (!openResponse) {
remote.dialog.showErrorBox("File not found", 'The file you are trying to open cannot be found in the specified path.');
}
}
}
/**
* Show downloaded file in explorer or finder.
*/
function showInFinder(id) {
let showFileIndex = local.downloadItems.findIndex((item) => {
return item._id === id
});
if (showFileIndex !== -1) {
let showResponse = remote.shell.showItemInFolder(local.downloadItems[showFileIndex].savedPath);
if (!showResponse) {
remote.dialog.showErrorBox("File not found", 'The file you are trying to access cannot be found in the specified path.');
}
}
}
function createDOM(arg) {
if (arg && arg._id) {
local.downloadItems.push(arg);
let downloadItemKey = arg._id;
let ul = document.getElementById('download-main');
if (ul) {
let li = document.createElement('li');
li.id = downloadItemKey;
li.classList.add('download-element');
ul.insertBefore(li, ul.childNodes[0]);
let itemDiv = document.createElement('div');
itemDiv.classList.add('download-item');
itemDiv.id = 'dl-item';
li.appendChild(itemDiv);
let openMainFile = document.getElementById('dl-item');
openMainFile.addEventListener('click', () => {
let id = openMainFile.parentNode.id;
openFile(id);
});
let fileDetails = document.createElement('div');
fileDetails.classList.add('file');
itemDiv.appendChild(fileDetails);
let downProgress = document.createElement('div');
downProgress.id = 'download-progress';
downProgress.classList.add('download-complete');
downProgress.classList.add('flash');
setTimeout(() => {
downProgress.classList.remove('flash');
}, 4000);
fileDetails.appendChild(downProgress);
let fileIcon = document.createElement('span');
fileIcon.classList.add('tempo-icon');
fileIcon.classList.add('tempo-icon--download');
fileIcon.classList.add('download-complete-color');
setTimeout(() => {
fileIcon.classList.remove('download-complete-color');
fileIcon.classList.remove('tempo-icon--download');
fileIcon.classList.add('tempo-icon--document');
}, 4000);
downProgress.appendChild(fileIcon);
let fileNameDiv = document.createElement('div');
fileNameDiv.classList.add('downloaded-filename');
itemDiv.appendChild(fileNameDiv);
let h2FileName = document.createElement('h2');
h2FileName.classList.add('text-cutoff');
h2FileName.innerHTML = arg.fileName;
fileNameDiv.appendChild(h2FileName);
let fileProgressTitle = document.createElement('span');
fileProgressTitle.id = 'per';
fileProgressTitle.innerHTML = arg.total + ' Downloaded';
fileNameDiv.appendChild(fileProgressTitle);
let caret = document.createElement('div');
caret.id = 'menu';
caret.classList.add('caret');
caret.classList.add('tempo-icon');
caret.classList.add('tempo-icon--dropdown');
li.appendChild(caret);
let actionMenu = document.createElement('div');
actionMenu.id = 'download-action-menu';
actionMenu.classList.add('download-action-menu');
caret.appendChild(actionMenu);
let caretUL = document.createElement('ul');
caretUL.id = downloadItemKey;
actionMenu.appendChild(caretUL);
let caretLiOpen = document.createElement('li');
caretLiOpen.id = 'download-open';
caretLiOpen.innerHTML = 'Open';
caretUL.appendChild(caretLiOpen);
let openFileDocument = document.getElementById('download-open');
openFileDocument.addEventListener('click', () => {
let id = openFileDocument.parentNode.id;
openFile(id);
});
let caretLiShow = document.createElement('li');
caretLiShow.id = 'download-show-in-folder';
caretLiShow.innerHTML = 'Show in Folder';
caretUL.appendChild(caretLiShow);
let showInFinderDocument = document.getElementById('download-show-in-folder');
showInFinderDocument.addEventListener('click', () => {
let id = showInFinderDocument.parentNode.id;
showInFinder(id);
});
}
}
}
function initiate() {
let mainFooter = document.getElementById('footer');
let mainDownloadDiv = document.getElementById('download-manager-footer');
if (mainDownloadDiv) {
mainFooter.classList.remove('hidden');
let ulFind = document.getElementById('download-main');
if (!ulFind){
let uList = document.createElement('ul');
uList.id = 'download-main';
mainDownloadDiv.appendChild(uList);
}
let closeSpanFind = document.getElementById('close-download-bar');
if (!closeSpanFind){
let closeSpan = document.createElement('span');
closeSpan.id = 'close-download-bar';
closeSpan.classList.add('close-download-bar');
closeSpan.classList.add('tempo-icon');
closeSpan.classList.add('tempo-icon--close');
mainDownloadDiv.appendChild(closeSpan);
}
let closeDownloadManager = document.getElementById('close-download-bar');
if (closeDownloadManager) {
closeDownloadManager.addEventListener('click', () => {
local.downloadItems = [];
document.getElementById('footer').classList.add('hidden');
document.getElementById('download-main').innerHTML = '';
});
}
}
}

View File

@ -11,6 +11,8 @@ const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
const getCmdLineArg = require('./utils/getCmdLineArg.js')
require('electron-dl')();
// used to check if a url was opened when the app was already open
let isAppAlreadyOpen = false;

View File

@ -19,6 +19,8 @@ const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const getMediaSources = require('../desktopCapturer/getSources');
require('../downloadManager/downloadManager');
const nodeURL = require('url');
// hold ref so doesn't get GC'ed

View File

@ -6,6 +6,7 @@ const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const nodeURL = require('url');
const querystring = require('querystring');
const filesize = require('filesize');
const { getTemplate, getMinimizeOnClose } = require('./menus/menuTemplate.js');
const loadErrors = require('./dialogs/showLoadError.js');
@ -188,6 +189,25 @@ function doCreateMainWindow(initialUrl, initialBounds) {
mainWindow.on('closed', destroyAllWindows);
// Manage File Downloads
mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
// When download is in progress, send necessary data to indicate the same
webContents.send('downloadProgress');
// Send file path when download is complete
item.once('done', (event, state) => {
if (state === 'completed') {
let data = {
_id: getGuid(),
savedPath: item.getSavePath() ? item.getSavePath() : '',
total: filesize(item.getTotalBytes() ? item.getTotalBytes() : 0),
fileName: item.getFilename() ? item.getFilename() : 'No name'
};
webContents.send('downloadCompleted', data);
}
});
});
// bug in electron is preventing this from working in sandboxed evt...
// https://github.com/electron/electron/issues/8841
mainWindow.webContents.on('will-navigate', function(event, willNavUrl) {

View File

@ -86,7 +86,9 @@
"electron-context-menu": "^0.8.0",
"electron-squirrel-startup": "^1.0.0",
"keymirror": "0.1.1",
"winreg": "^1.2.3"
"winreg": "^1.2.3",
"electron-dl": "^1.9.0",
"filesize": "^3.5.10"
},
"optionalDependencies": {
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1"

View File

@ -0,0 +1,95 @@
const downloadManager = require('../js/downloadManager/downloadManager');
const electron = require('./__mocks__/electron');
const $ = require('jquery');
describe('download manager', function() {
describe('Download Manager to create DOM once download is initiated', function () {
beforeEach(function () {
global.document.body.innerHTML =
'<div id="download-main">' +
'</div>';
});
it('should inject download bar element into DOM once download is initiated', function() {
electron.ipcRenderer.send('downloadCompleted', {_id: '12345', fileName: 'test', total: 100});
expect(document.getElementsByClassName('text-cutoff')[0].innerHTML).toBe('test');
expect(document.getElementById('per').innerHTML).toBe('100 Downloaded');
});
it('should inject multiple download items during multiple downloads', function() {
electron.ipcRenderer.send('downloadCompleted', {_id: '12345', fileName: 'test', total: 100});
electron.ipcRenderer.send('downloadCompleted', {_id: '67890', fileName: 'test1', total: 200});
let fileNames = document.getElementsByClassName('text-cutoff');
expect(fileNames[0].innerHTML).toBe('test1');
expect(fileNames[1].innerHTML).toBe('test');
expect(document.getElementById('per').innerHTML).toBe('100 Downloaded');
let downloadElements = document.getElementsByClassName('download-element');
expect(downloadElements[0].id).toBe('67890');
expect(downloadElements[1].id).toBe('12345');
});
});
describe('Download Manager to initiate footer', function () {
beforeEach(function () {
global.document.body.innerHTML =
'<div id="download-manager-footer" class="hidden">' +
'<div id="download-main">' +
'</div>' +
'</div>';
});
it('should inject dom element once download is completed', function() {
electron.ipcRenderer.send('downloadProgress');
expect(document.getElementById('download-manager-footer').classList).not.toContain('hidden');
});
it('should remove the download bar and clear up the download items', function() {
electron.ipcRenderer.send('downloadProgress');
console.log(document.getElementById('download-manager-footer').classList);
expect(document.getElementById('download-manager-footer').classList).not.toContain('hidden');
$('#close-download-bar').click();
expect(document.getElementById('download-manager-footer').classList).toContain('hidden');
});
});
describe('Download Manager to initiate footer', function () {
beforeEach(function () {
global.document.body.innerHTML =
'<div id="download-manager-footer" class="hidden">' +
'</div>';
});
it('should inject ul element if not found', function() {
electron.ipcRenderer.send('downloadProgress');
expect(document.getElementById('download-main')).not.toBeNull();
expect(document.getElementById('download-manager-footer').classList).not.toContain('hidden');
});
});
});