diff --git a/js/downloadManager/downloadManager.js b/js/downloadManager/downloadManager.js new file mode 100644 index 00000000..48ac2162 --- /dev/null +++ b/js/downloadManager/downloadManager.js @@ -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 = ''; + }); + } + } +} \ No newline at end of file diff --git a/js/main.js b/js/main.js index 1ec8f936..0d74cd86 100644 --- a/js/main.js +++ b/js/main.js @@ -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; diff --git a/js/preload/preloadMain.js b/js/preload/preloadMain.js index 69bdbc53..96cdef62 100644 --- a/js/preload/preloadMain.js +++ b/js/preload/preloadMain.js @@ -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 diff --git a/js/windowMgr.js b/js/windowMgr.js index a84a7426..5c938cd2 100644 --- a/js/windowMgr.js +++ b/js/windowMgr.js @@ -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) { diff --git a/package.json b/package.json index 90fd8eca..1bc597f4 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/tests/DownloadManager.test.js b/tests/DownloadManager.test.js new file mode 100644 index 00000000..c8120193 --- /dev/null +++ b/tests/DownloadManager.test.js @@ -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 = + '
' + + '
'; + }); + + 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 = + ''; + }); + + 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 = + ''; + }); + + 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'); + + }); + + }); + +}); \ No newline at end of file