The responsibilities of renderer.js
file are:
- Respond to users, such as, when a user click on a button or want to quit
- Handle child processes
- Communicate with walkerHelper.js
- Send results to webview
- Show dialog boxes
- Close app
Let’s start writing the code:
const remote = require('electron').remote, cp = require('child_process'),
The remote
module helps to access modules restricted to main process, such as, dialog
module for opening directories and displaying confirmation boxes. See Using Electron’s remote module.
The child_process
module helps to execute walkerHelper.js
as a child process. See Child Process in Node.
walker = cp.fork('./walkerkHelper.js'), OPT = require('./options.js'),
The cp.fork('./walkerkHelper.js')
method returns a new communicable child process. See Create walkerHelper.js to control file walker.
The options.js returns an object storing app configuration. See Create app options module.
Duplicate File Finder app has five states: started
, stopped
, paused
, resumed
and done
.
- Application
close
button, shows a confirmation dialog box to close the current window. Add folder
button, shows directory selector dialog box. This button gets change when the state of app changes. The Add folder button get replace with following buttons when app change its state:Add folder
button shows when app state isstopped
Cancel
button shows when state isstarted
,paused
orresumed
. The Cancel button send the reset command towalkerHelper.js
which removes the all pending entries and emit thedone
event.New
button shows when state isdone
. The New button reset the GUI to default and change state tostopped
so theAdd folder
button appears.
Start button
starts the application for scanning the provided folders. This button also gets change when the state of app changes. The Start button replace with following four button:Start
button shows when the app state isstopped
. This button is used to start the folders scanning. The stat of application will change tostarted
when application successfully start scanning.Pause
button shows when state isstarted
orresumed
. This button is used to pause folder scanning.Resume
button shows when state ispaused
. This button is used to resume folder scanning.Finished
disabled button shows when scanning completed and state changed todone
- Heading div for added directories
- List of directories / folders to scan
- The WebView tag which loads the grid.html to display the duplicate results.
- App status bar shows
Ready
,Paused
,Done
or a folder path (which is currently being scanned). - Delete location (path) from the list
- Location status. The location (path) will not scan if
Excluded
- Directory location (path).
Lets star writing the renderer.js file:
//renderer.js // 1 The close button appCloseBtn = document.querySelector('#close'), // 2 The Add folder button addBtn = document.querySelector('#addBtn'), // 3 The Start button startBtn = document.querySelector('#startBtn'), // 4 Heading div pathsDivHead = document.querySelector('#pathsHead'), // 5 Shows a list of directories added by Add folder button pathsDiv = document.querySelector('#paths'), // 6 WebView tag, loads grid.html webview = document.querySelector('webview'), // 7 App's status bar appStatusBar = document.querySelector('#status');
Next, write the following code:
let debug = false, dir = dirObject, dirs = [],
Set debug
to true if you want to see error messages on Chromium devtools. dir
creates a reference to dirObject
, see Making GUI - Creating dirObject class. dirs
stores the dir
objects.
prevStatusBarMsg = '', appStatus = OPT.STOPPED;
The prevStatusBarMsg
stores the current working path when a user hit the Pause
button and prints the stored path on the status bar when user hit the Resume
button.
The appStatus
stores the current app status, the default status is STOPPED
.
//Show confirmation box before closing the window appCloseBtn.addEventListener('click', e =>{ const options = { type: 'warning', buttons: ['Yes', 'No'], message: 'Do you really want to quit?' } remote.dialog.showMessageBox(options, i => { if (i == 1) return; remote.getCurrentWindow().close(); }) });
A Yes No confirmation box will appear when a user hits the close button.
startBtn.addEventListener('click',()=>{ debug&&console.log(appStatus); if (dirs.length === 0) return switch (appStatus){ case OPT.STOPPED: startApp(); break; case OPT.PAUSED: resumeApp(); break; case OPT.STARTED: case OPT.RESUMED: pauseApp(); } });
As we already discussed , the startBtn
is used to start, pause or resume the application.
addBtn.addEventListener('click', () =>{ debug&&console.log(appStatus); switch (appStatus){ case OPT.STOPPED: appStoppedAddBtnClicked(); break; case OPT.DONE: appDoneAddBtnClicked(); break; case OPT.PAUSED: case OPT.STARTED: case OPT.RESUMED: stopAppAddBtnClicked(); } });
If the app status is stopped the addBtn
displays the directory selector dialog box by executing the appStoppedAddBtnClicked()
method.
And, if the app status is done the addBtn
displays the confirmation dialog box to reset the whole app (file walker, dir objects and clear the queue) by executing the appDoneAddBtnClicked()
method.
If the app status is paused, started or resumed the addBtn display the confirmation box to cancel the current scanning by executing the stopAppAddBtnClicked()
method.
function appStoppedAddBtnClicked(){ let options = { properties: ['openDirectory', 'multiSelections'] } remote.dialog.showOpenDialog(options, (paths) => { if (!paths) return paths.forEach(path =>{ let isExist = dirs.some((dir) => { return dir.path == path }) if (isExist){ alert(path + ' already added in the list') ; return; } new dir(path); }) }) }
The appStoppedAddBtnClicked()
method shows the directory selector dialog box. The new dir
object will create or display a message if the selected path already added.
function appDoneAddBtnClicked(){ const options = { type: 'warning', buttons: ['Yes', 'No'], message: 'Do you wan to start a new empty project?' } remote.dialog.showMessageBox(options, i => { if (i == 1) return; walker.send({walker:OPT.RESET}); dirs = []; pathsDiv.innerHTML = ''; pathsDivHead.className = 'hide'; addBtn.innerHTML = '<span class="icon-folder"></span>Add folder'; startBtn.innerHTML = '<span class="icon-play"></span>Start'; startBtn.disabled = false; appStatusBar.innerHTML = 'Ready'; webview.style.height = ''; webview.reload(); appStatus = OPT.STOPPED; }) }
The appDoneAddBtnClicked()
method displays a confirmation dialog box when addBtn
clicked. After successful confirmation this method resets the whole application.
function stopAppAddBtnClicked(){ const currAppStatus = appStatus; if (currAppStatus !== OPT.PAUSED){ pauseApp(); } const options = { type: 'warning', buttons: ['Yes', 'No'], message: 'Do you really want to cancel duplicate files search?' } remote.dialog.showMessageBox(options, i => { if (i == 1) { if (currAppStatus !== OPT.PAUSED){ resumeApp(); } return; } resumeApp(); walker.send({walker:OPT.RESET}); }) }
The stopAppAddBtnClicked()
method displays a confirmation box to stop the current task. It first stores the current status of app and then pause the application if it already not paused, then it shows the confirmation box. After successful confirmation it resume the app and send reset command to walker
(child process of walkerHelper.js), which empties the queue and then walker returns the done
event.
function startApp(){ addBtn.disabled = true; startBtn.disabled = true; document .querySelectorAll('.delDir, .typeDir') .forEach(btn => { btn.className += ' disableBtn'; }); let obj = { walker:OPT.START, dirs:dirs } walker.send(obj); }
The startApp()
method disable all visible buttons, starts the scanning by sending the dirs
array (storing dir
objects added by Add folder
button) and OPT.START
command to walker
(walkerHelper.js child process).
function resumeApp(){ startBtn.disabled = true; let obj = { walker:OPT.RESUME } walker.send(obj); } function pauseApp(){ startBtn.disabled = true; let obj = { walker:OPT.PAUSE } walker.send(obj); }
The resumeApp()
and pauseApp()
methods are used to resume and pause the application.
Next, the walker.on
receive the message from its child process (walkerHelper.js), for example, when “file walker” paused, stopped or done scanning :
walker.on('message', (m) => { switch (m.walker) { case OPT.WEBVIEW: webview.executeJavaScript(m.addRows, r => { r ? webview.style.height = r : ''; }); break; case OPT.DIR: appStatusBar.innerHTML = '📂 '+ m.dir; break; case OPT.PAUSED: appStatus = m.walker; startBtn.innerHTML = '<span class="icon-play"></span>Resume'; startBtn.disabled = false; prevStatusBarMsg = appStatusBar.innerHTML; appStatusBar.innerHTML = 'Paused'; break; case OPT.STARTED: addBtn.innerHTML = '<span class="icon-stop"></span>Cancel'; addBtn.disabled = false; startBtn.disabled= false; case OPT.RESUMED: appStatus = m.walker; startBtn.innerHTML = '<span class="icon-pause"></span>Pause'; startBtn.disabled = false; appStatusBar.innerHTML = prevStatusBarMsg; break; case OPT.DONE: appStatus = m.walker; startBtn.innerHTML = 'Finished'; startBtn.disabled = true; addBtn.innerHTML = '<span class="icon-new"></span>New'; appStatusBar.innerHTML = 'Done Scanned '+m.totalFiles+' files & '+m.totalDirs+' folders'; prevStatusBarMsg = ''; break; } });
To compare received message we’ll use switch case statement:
case OPT.WEBVIEW
walker
child process sent the duplicate files. We’ll send these files to webview
(grid.html).
case OPT.DIR
walker
sent the current working directory. We’ll display it on the app’s status bar.
case OPT.PAUSED
walker
paused the scanning. We’ll update the appStatus
to paused, replace the icon and text of startBtn
and display the Paused
message on app’s status bar.
case OPT.STARTED
walker
started the scanning. We’ll update the addBtn's
icon and text.
case OPT.RESUMED
This code runs when walker
started or resumed. We’ll update the startBtn
icon and text and replace the status bar text with current working directory.
case OPT.DONE
walker
finished the scanning of provided folders. We’ll update the startBtn
text to Finished
and make it disabled. We’ll also update the addBtn
icon and text to display the New text. Lastly we’ll update the app’s status bar by writing the Done
message, total scanned files and folders.
function dirObject (path){ this.path = path; this.isIncluded = true; dirs.push (this); pathsDivHead.className = ''; // 8 The delete button let del = document.createElement('span'); del.className = 'icon-close delDir'; // 9 Include / Exclude button let type = document.createElement('span'); type.className = 'typeDir'; type.innerHTML = 'included'; //10 Directory path let textNode = document.createTextNode(path), div = document.createElement('div'); div.appendChild(del); div.appendChild(type); div.appendChild(textNode); pathsDiv.appendChild(div); del.addEventListener('click', () => { if (appStatus !== OPT.STOPPED) return; pathsDiv.removeChild(div); let index = dirs.indexOf(this); if (index !== -1) dirs.splice(index, 1); if (dirs.length === 0 ) pathsDivHead.className = 'hide'; }) type.addEventListener('click', () => { if (appStatus !== OPT.STOPPED) return; this.isIncluded = !this.isIncluded; if (this.isIncluded){ type.className = 'typeDir'; type.innerHTML = 'included'; } else { type.className = 'typeDir disableBtn'; type.innerHTML = 'excluded'; } }) }
The dirObject
is responsible to create an object which holds the directory path and its status i.e. included or excluded. When a user clicks on Add folder button and select a folder, that folder then adds in the list as shown in above image at point # 5. See tutorial Creating the dirObject
class for more information.
Click here to download the complete project (zip file).