app/js/renderer.js

// 'use strict'

// ----------------------------------------------------------------------------
// IMPORT MODULES
// ----------------------------------------------------------------------------
require('v8-compile-cache')

// ----------------------------------------------------------------------------
// IMPORT DIRGISTERED MODULES
// ----------------------------------------------------------------------------
const utils = require('./js/modules/utils.js') // general utilities
const sentry = require('./js/modules/sentry.js') // sentry for error reporting
const crash = require('./js/modules/crashReporter.js') // crashReporter
const unhandled = require('./js/modules/unhandled.js') // electron-unhandled

// ----------------------------------------------------------------------------
// ERROR HANDLING
// ----------------------------------------------------------------------------
crash.initCrashReporter() // the default electron crash reporter
unhandled.initUnhandled() // to handle unhandled errors
sentry.enableSentry() // sentry is enabled by default

// ----------------------------------------------------------------------------
// VARIABLES
// ----------------------------------------------------------------------------
var currentOutputPath = '' // stores the currently selected output path

// ----------------------------------------------------------------------------
// FUNCTIONS
// ----------------------------------------------------------------------------

/**
* @function titlebarInit
* @summary Init the titlebar for the frameless mainWindow
* @description Creates a custom titlebar for the mainWindow using custom-electron-titlebar (https://github.com/AlexTorresSk/custom-electron-titlebar).
* @memberof renderer
*/
function titlebarInit () {
    // Be aware: the font-size of .window-title (aka application name) is set by app/css/core.css
    const customTitlebar = require('custom-electron-titlebar')

    const myTitlebar = new customTitlebar.Titlebar({
        titleHorizontalAlignment: 'center', // position of window title
        icon: 'img/icon/icon.png',
        drag: true, // whether or not you can drag the window by holding the click on the title bar.
        backgroundColor: customTitlebar.Color.fromHex('#3d3b3b'), // #171717
        minimizable: true,
        maximizable: true,
        closeable: true,
        itemBackgroundColor: customTitlebar.Color.fromHex('#525252') // hover color
    })

    utils.writeConsoleMsg('info', 'titlebarInit ::: Initialized custom titlebar')
}

// ----------------------------------------------------------------------------
// FUNCTIONS: main window
// ----------------------------------------------------------------------------

/**
* @function uiSelectSource
* @summary selects a source folder
* @description selects a source folder
* @memberof renderer
*/
function uiSelectSource () {
    const options = { properties: ['openDirectory'] }
    const { dialog } = require('electron').remote

    utils.writeConsoleMsg('info', 'uiSelectSource ::: User wants to set a source directory. Now opening dialog to select a new source dir')
    dialog.showOpenDialog(options).then(res => {
        // utils.writeConsoleMsg('warn', '_' + res.filePaths + '_')

        if (res.filePaths.length === 0) {
            utils.writeConsoleMsg('warn', 'uiSelectSource ::: User aborted selecting a source dir')
            utils.showNoty('info', 'No changes applied.')
        } else {
            var newDownloadDirectory = res.filePaths.toString()
            updateUILog('Set source dir to: ' + newDownloadDirectory)
            $('#showSelectedSourceFolderPath').val(newDownloadDirectory) // show it in the UI
            utils.writeConsoleMsg('info', 'uiSelectSource ::: User selected the following source dir: _' + newDownloadDirectory + '_.')
            validateSourceAndTarget()
        }
    })
}

/**
* @function uiSelectTarget
* @summary selects a target folder
* @description selects a target folder
* @memberof renderer
*/
function uiSelectTarget () {
    const options = { properties: ['openDirectory'] }
    const { dialog } = require('electron').remote

    utils.writeConsoleMsg('info', 'uiSelectTarget ::: User wants to set a source directory. Now opening dialog to select a new target dir')
    dialog.showOpenDialog(options).then(res => {
        // utils.writeConsoleMsg('warn', '_' + res.filePaths + '_')

        if (res.filePaths.length === 0) {
            utils.writeConsoleMsg('warn', 'uiSelectTarget ::: User aborted selecting a target dir')
            utils.showNoty('info', 'No changes applied.')
        } else {
            var newDownloadDirectory = res.filePaths.toString()
            updateUILog('Set target dir to: ' + newDownloadDirectory)
            $('#showSelectedTargetFolderPath').val(newDownloadDirectory) // show it in the UI
            utils.writeConsoleMsg('info', 'uiSelectTarget ::: User selected the following target dir: _' + newDownloadDirectory + '_.')
            validateSourceAndTarget()
        }
    })
}

/**
* @function validateSourceAndTarget
* @summary Checks if both source and target folder are set and enables the startIndex button
* @description Is executed when either the source or target folder is configured
* @memberof renderer
*/
function validateSourceAndTarget () {
    utils.writeConsoleMsg('info', 'validateSourceAndTarget ::: Trying to validate the source and target situation')

    var misconfigurationDetected = false

    // source
    var sourceFolderPath = $('#showSelectedSourceFolderPath').val()
    utils.writeConsoleMsg('info', 'validateSourceAndTarget ::: Source is set to: ' + sourceFolderPath)

    // target
    var targetFolderPath = $('#showSelectedTargetFolderPath').val()
    utils.writeConsoleMsg('info', 'validateSourceAndTarget ::: Target is set to: ' + targetFolderPath)

    // condition 1: are same -> abort
    if (sourceFolderPath === targetFolderPath) {
        misconfigurationDetected = true
    }

    // condition 2: at least 1 is empty
    if ((sourceFolderPath === '') || (targetFolderPath === '')) {
        misconfigurationDetected = true
    }

    if (misconfigurationDetected === true) {
        $('#button_startIndexing').prop('disabled', true) // disable the start button
    } else {
        $('#button_startIndexing').prop('disabled', false) // enable the start button
    }
}

/**
* @function prepareIndexing
* @summary Adds a delay before the indexing gets started
* @description SAdds a delay before the indexing gets started. Ugly hack because otherwise the modal showing the processing wouldnt be displayed in time
* @memberof renderer
*/
function prepareIndexing () {
    utils.writeConsoleMsg('info', 'prepareIndexing ::: Preparing the indexing process ...')
    processingShow() // show processing modal
    loadingAnimationShow() // start spinner

    setTimeout(
        function () {
            startIndexing()
        }, 1000)
}

/**
* @function startIndexing
* @summary Starts the indexing process
* @description Starts the indexing process
* @memberof renderer
*/
function startIndexing () {
    var path = require('path')
    utils.writeConsoleMsg('info', 'startIndexing ::: Starting')

    $('#button_startIndexing').prop('disabled', true) // disable the start button

    // get source
    var sourceFolderPath = $('#showSelectedSourceFolderPath').val()
    utils.writeConsoleMsg('info', 'startIndexing ::: Source is set to: ' + sourceFolderPath)

    // get target
    var targetFolderPath = $('#showSelectedTargetFolderPath').val()
    utils.writeConsoleMsg('info', 'startIndexing ::: Target is set to:' + targetFolderPath)

    // make new date based folder in target
    var timestamp = utils.generateTimestamp('YYYYMMDD-HHmmss')
    targetFolderPath = path.join(targetFolderPath, timestamp + '-dirgistered-Index')

    if (utils.createFolder(targetFolderPath)) {
        updateUILog('Created date-based target folder: "' + targetFolderPath + '"')
        updateUILog('Starting indexing process ...')
        createSingleHTMLIndex(sourceFolderPath, targetFolderPath, true) // Start html generation madness. True = no 'back' button on initial index.html
    }

    var finalMessage = 'Finished entire index process'

    utils.writeConsoleMsg('info', finalMessage)
    utils.showNoty('success', finalMessage)
    utils.showNotification(finalMessage)
    updateUILog(finalMessage) // update UI log
    loadingAnimationHide() // hide spinner
    processingHide()

    $('#button_openIndex').prop('disabled', false) // enable the showIndex button
}

/**
* @function createSingleHTMLIndex
* @summary Creates an html index for a given source dir in a given target dir
* @description Creates an html index for a given source dir in a given target dir. Launches itself for all sub-directories
* @param {string} - sourceFolderPath - The actual source directory path
* @param {string} - targetFolderPath - The actual target directory path
* @param {string} - targetFolderPath - The actual target directory path
* @memberof renderer
*/
function createSingleHTMLIndex (sourceFolderPath, targetFolderPath, initialRun = false) {
    // var fs = require('fs')
    const fs = require('graceful-fs') // see #9

    var path = require('path')
    var process = require('process')

    utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Starting to create a single index')
    utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Current source path: _' + sourceFolderPath + '_.')
    utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Current target path: _' + targetFolderPath + '_.')

    // remember main target on initial run
    if (initialRun === true) {
        currentOutputPath = targetFolderPath
    }

    utils.createFolder(targetFolderPath) // create target folder if it doesnt exists yet

    var moveFrom = sourceFolderPath
    var moveTo = targetFolderPath

    // arrays for folder
    var dirNameArray = [] // name of dir
    var dirFullPathArray = [] // full path to dir
    var dirSize = [] // site of dir

    // arrays for files
    var fileNameArray = [] // name of file
    var fileFullPathArray = [] // full path to file
    var fileType = [] // file type (file or link)
    var fileSize = [] // size of file

    var i // used for loops

    try {
        var filenames = fs.readdirSync(moveFrom)
    } catch (error) {
        utils.writeConsoleMsg('error', 'createSingleHTMLIndex ::: Error while reading directory ' + moveFrom + '. Error: ' + error)
        utils.showNoty('error', 'Unable to read the folder ' + moveFrom + '. Error: ' + error, 0)
        return
    }

    for (i = 0; i < filenames.length; i++) {
        var stats = fs.statSync(path.join(moveFrom, filenames[i]))
        var fromPath = path.join(moveFrom, filenames[i])

        // Store all directory informations in arrays
        //
        if (stats.isDirectory()) {
            dirFullPathArray.push(fromPath) // store entire path
            dirNameArray.push(path.basename(fromPath)) // store folder name itself
            dirSize.push(utils.bytesToSize(stats.size)) // size (File Size in Bytes)
        }

        // Store all file informations in arrays
        //
        if ((stats.isFile()) || (stats.isSymbolicLink())) {
            fileFullPathArray.push(fromPath) // store entire path
            fileNameArray.push(path.basename(fromPath)) // store file name itself
            fileSize.push(utils.bytesToSize(stats.size)) // size

            if (stats.isSymbolicLink()) {
                fileType.push('Link') // store file type
                // utils.writeConsoleMsg('error', 'createSingleHTMLIndex ::: ' + fileFullPathArray.push(fromPath))
            } else {
                fileType.push('File') // store file type
            }
        }
    }
    utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Finished reading content of folder: _' + sourceFolderPath + '_. Found: ' + dirNameArray.length + ' folders and ' + fileNameArray.length + ' files.')

    // generate index.html in target dir
    var curIndexPath = path.join(targetFolderPath, 'index.html')
    utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Current output file is: _' + curIndexPath + '_.')

    // Define variables for all sections
    //
    var indexBaseCode
    var indexDataTablesBodyFolderText
    var indexDataTablesBodyFilesText
    var indexFooter
    var indexFooterPost

    // Index: core
    //
    indexBaseCode = ''
    indexBaseCode += '<!DOCTYPE html>\n'
    indexBaseCode += '<html lang="en">\n'
    indexBaseCode += '<head>\n'
    indexBaseCode += '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>\n'
    indexBaseCode += '<title>dirgistered</title>\n'

    // jQuery
    indexBaseCode += '<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>\n'

    // font awesome
    indexBaseCode += '<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/js/all.min.js" integrity="sha256-MAgcygDRahs+F/Nk5Vz387whB4kSK9NXlDN3w58LLq0=" crossorigin="anonymous"></script>\n'

    // bootstrap
    indexBaseCode += '<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">\n'

    // DataTables - core
    indexBaseCode += '<script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>\n'
    indexBaseCode += '<link rel="stylesheet" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css">\n'
    // DataTables - buttons
    indexBaseCode += '<script src="https://cdn.datatables.net/buttons/1.6.1/js/dataTables.buttons.min.js"></script>\n'
    indexBaseCode += '<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.6.1/css/buttons.dataTables.min.css">\n'
    indexBaseCode += '<script src="https://cdn.datatables.net/buttons/1.6.1/js/buttons.html5.min.js"></script>\n'
    // print
    indexBaseCode += '<script src="https://cdn.datatables.net/buttons/1.6.1/js/buttons.print.min.js"></script>\n'
    // jszip
    indexBaseCode += '<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js"></script>\n'
    // pdfmake
    indexBaseCode += '<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.62/pdfmake.min.js"></script>\n'
    indexBaseCode += '<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.62/vfs_fonts.js"></script>\n'

    // misc
    indexBaseCode += '</head>\n'
    indexBaseCode += '<body>\n'
    indexBaseCode += '<div class="container-fluid">\n'
    indexBaseCode += '<nav class="navbar navbar-light bg-light">\n'
    indexBaseCode += '<span class="navbar-brand mb-0 h1"><img src="https://raw.githubusercontent.com/yafp/dirgistered/master/.github/images/logo/128x128.png" width="32"> Source: <span class="badge badge-secondary">' + sourceFolderPath + '</span></span>\n'

    // add a back button if needed
    if (initialRun === false) {
        indexBaseCode += '<a class="btn btn-sm btn-outline-danger" href="../index.html"><i class="fas fa-backward" aria-hidden="true"></i> Back</a><br>'
    }
    indexBaseCode += '</nav>\n'

    // indexDataTablesHead
    const { indexDataTablesHead } = require('./js/modules/indexGenerator.js')

    // Section: Folders
    indexDataTablesBodyFolderText = ''
    if (dirNameArray.length > 0) {
        for (i = 0; i < dirNameArray.length; i++) {
            indexDataTablesBodyFolderText += '<tr><td><i class="fas fa-folder"></i></td><td><a href="' + dirNameArray[i] + '/index.html">' + dirNameArray[i] + '</a></td><td>Folder</td><td>' + dirSize[i] + '</td> </tr>\n'
        }
    }

    // Section: Files
    indexDataTablesBodyFilesText = ''
    if (fileNameArray.length > 0) {
        for (i = 0; i < fileNameArray.length; i++) {
            var extension = fileNameArray[i].split('.').pop()
            var iconCode = utils.getFontAwesomeFileIcon(extension)
            indexDataTablesBodyFilesText += '<tr><td>' + iconCode + '</td><td>' + fileNameArray[i] + '</td><td>' + fileType[i] + '</td><td>' + fileSize[i] + '</td> </tr>\n'
        }
    }

    // indexDataTablesFoot
    const { indexDataTablesFoot } = require('./js/modules/indexGenerator.js')

    // Section: Document Footer
    //
    indexFooter = ''
    indexFooter += '<footer class="page-footer font-small blue">\n'
    indexFooter += '<div class="footer-copyright text-center py-3">\n'
    indexFooter += 'using <a href="https://github.com/yafp/dirgistered">dirgistered</a> by <a href="https://github.com/yafp">yafp</a>\n'
    indexFooter += ' </div>\n'
    indexFooter += '</footer>\n'

    // Section: DataTables: init on page ready
    //
    indexFooterPost = ''
    indexFooterPost += '<script>\n'
    indexFooterPost += '$(document).ready(function() {\n'
    indexFooterPost += '    $("#example").DataTable( {\n'
    indexFooterPost += '        dom: "Bfrtip",\n'
    indexFooterPost += '        "pageLength":  -1,\n'
    indexFooterPost += '        "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]],\n'

    indexFooterPost += '        buttons: [\n'
    indexFooterPost += '            "print", "copy", "excel", "pdf"\n'
    indexFooterPost += '        ],\n'

    // Dropdown for columns
    indexFooterPost += 'initComplete: function () {\n'
    // Dropdown for all columns
    // this.api().columns().every( function () {

    // dropdown for some columns
    indexFooterPost += 'columns = [2];\n' // Add columns here
    // indexFooterPost += 'columns = [1, 2];\n' // Add columns here
    indexFooterPost += 'this.api().columns(columns).every(function () {\n'
    indexFooterPost += 'var column = this;\n'
    indexFooterPost += 'var select = $(\'<select class=""><option value=""></option></select>\')\n'
    indexFooterPost += '.appendTo( $(column.footer()).empty() )\n'
    indexFooterPost += '.on( \'change\', function () {\n'
    indexFooterPost += 'var val = $.fn.dataTable.util.escapeRegex(\n'
    indexFooterPost += '$(this).val()\n'
    indexFooterPost += ');\n'
    indexFooterPost += 'column\n'
    indexFooterPost += '.search( val ? \'^\'+val+\'$\' : \'\', true, false )\n'
    indexFooterPost += '.draw();\n'
    indexFooterPost += '} );\n'

    indexFooterPost += 'column.data().unique().sort().each( function ( d, j )\n'
    indexFooterPost += '{\n'
    indexFooterPost += 'select.append( \'<option value="\'+d+\'">\'+d+\'</option>\' );\n'
    indexFooterPost += '} );\n'
    indexFooterPost += '} );\n'
    indexFooterPost += '}\n'
    // End Dropdown for columns

    // ...
    indexFooterPost += '    } );\n'
    indexFooterPost += '} );\n'
    indexFooterPost += '</script>\n'

    // create the actual .html file
    fs.writeFileSync(curIndexPath, indexBaseCode, function (error) { // foo
        if (error) {
            utils.writeConsoleMsg('error', 'createSingleHTMLIndex ::: An error ocurred creating the file _' + error.message + '_')
            utils.showNoty('error', 'Error occured while creating the index file: ' + curIndexPath + '. Error: ' + error.message)
        }

        utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Created single index for: ' + sourceFolderPath + '.')
    })
    // Finished writing index.html

    // Appending the generated texts to the index.html
    utils.appendToFile(curIndexPath, indexDataTablesHead)
    utils.appendToFile(curIndexPath, indexDataTablesBodyFolderText)
    utils.appendToFile(curIndexPath, indexDataTablesBodyFilesText)
    utils.appendToFile(curIndexPath, indexDataTablesFoot)
    utils.appendToFile(curIndexPath, indexFooter)
    utils.appendToFile(curIndexPath, indexFooterPost)
    // FIXME: test if appending all at once is better

    utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Finished index creation for _' + sourceFolderPath + '_. Checking for sub-directories now.')
    updateUILog('Indexed directory: "' + sourceFolderPath + '"') // update UI

    // start indexing for each subdir
    for (var j = 0; j < dirFullPathArray.length; j++) {
        utils.writeConsoleMsg('info', 'createSingleHTMLIndex ::: Found a sub-directory: _' + dirNameArray[j] + '_.')
        createSingleHTMLIndex(dirFullPathArray[j], targetFolderPath + '/' + dirNameArray[j], false) // FIXME
    }
}

/**
* @function logScrollToEnd
* @summary Scrolls the UI log to the end
* @description Scrolls the UI log to the end / latest entry
*/
function logScrollToEnd () {
    $('#ta_log').scrollTop($('#ta_log')[0].scrollHeight) // scroll log textarea to the end
}

/**
* @function startIntro
* @summary start an intro / user tutorial
* @description Starts a short intro / tutorial which explains the user-interface. Using introJs
*/
function startIntro () {
    const introJs = require('intro.js')
    introJs().start()
    utils.writeConsoleMsg('info', 'startIntro ::: Started the dirgistered intro')
}

/**
* @function loadingAnimationShow
* @summary Shows the loading animation / download spinner
* @description Shows the loading animation / download spinner. applicationStateSet() is using this function
*/
function loadingAnimationShow () {
    if ($('#div').data('hidden', true)) { // only if it isnt already displayed
        utils.writeConsoleMsg('info', 'loadingAnimationShow ::: Show spinner')
        $('#md_spinner').attr('hidden', false)
    }
}

/**
* @function loadingAnimationHide
* @summary Hides the loading animation / download spinner
* @description Hides the loading animation / download spinner. applicationStateSet() is using this function
*/
function loadingAnimationHide () {
    if ($('#div').data('hidden', false)) { // only if it isnt already hidden
        utils.writeConsoleMsg('info', 'loadingAnimationHide ::: Hide spinner')
        $('#md_spinner').attr('hidden', true)
    }
}

/**
* @function processingShow
* @summary Shows the processing / progress modal
* @description Shows the processing / progress modal
* @memberof renderer
*/
function processingShow () {
    $('#myModal').modal('show')
    utils.writeConsoleMsg('info', 'processingShow ::: Show processing modal')
}

/**
* @function processingHide
* @summary hides the processing / progress modal
* @description hides the processing / progress modal
* @memberof renderer
*/
function processingHide () {
    $('#myModal').modal('hide')
    utils.writeConsoleMsg('info', 'processingShow ::: Hide processing modal')
}

/**
* @function updateUILog
* @summary updates the UI log
* @description updates the UI log
* @memberof renderer
*/
function updateUILog (logText) {
    var timestamp = utils.generateTimestamp()
    $('#ta_log').val($('#ta_log').val() + timestamp + ' | ' + logText + '\n')
    logScrollToEnd()
}

/**
* @function resetMainUI
* @summary Resets the UI of the Main tab back to defaults
* @description Resets the UI of the Main tab back to defaults
* @memberof renderer
*/
function resetMainUI () {
    $('#showSelectedSourceFolderPath').val('') // source
    $('#showSelectedTargetFolderPath').val('') // target
    $('#ta_log').val('') // log

    $('#button_startIndexing').prop('disabled', true) // disable the start button
    $('#button_openIndex').prop('disabled', true) // disable the showIndex button

    utils.writeConsoleMsg('info', 'resetMainUI ::: Finished resetting the main UI.')
    utils.showNoty('success', 'Finished resetting the UI')
}

/**
* @function openIndex
* @summary Opens the generated main index
* @description Opens the generated main index in the default browser
* @memberof renderer
*/
function openIndex () {
    const { shell } = require('electron') // deconstructing assignment
    var path = require('path')
    var pathToMainIndex = path.join(currentOutputPath, 'index.html')
    utils.writeConsoleMsg('info', 'openIndex ::: Should open: _' + pathToMainIndex + '_ in the default browser')
    //shell.openItem(pathToMainIndex)
    shell.openPath(pathToMainIndex)
}

/**
* @function openSettings
* @summary Opens the settings window
* @description Opens the settings UI
* @memberof renderer
*/
function openSettings () {
    const { ipcRenderer } = require('electron')
    windowMainBlurSet(true) // blur the main UI
    ipcRenderer.send('settingsUiLoad') // tell main.js to load settings UI
}

/**
* @function windowMainBlurSet
* @summary Can set a blur level for entire main ui
* @description Can set a blur level for entire main ui. Is used on the mainUI when the settingsUI is open
* @param {boolean} enable- To enable or disable blur
*/
function windowMainBlurSet (enable) {
    if (enable === true) {
        // mainContainer
        $('#mainContainer').css('filter', 'blur(2px)') // blur
        $('#mainContainer').css('pointer-events', 'none') // disable click events
        // titlebar
        $('.titlebar').css('filter', 'blur(2px)') // blur
        $('.titlebar').css('pointer-events', 'none') // disable click events
        utils.writeConsoleMsg('info', 'windowMainBlurSet ::: Enabled blur effect')
    } else {
        // mainContainer
        $('#mainContainer').css('filter', 'blur(0px)') // unblur
        $('#mainContainer').css('pointer-events', 'auto') // enable click-events
        // titlebar
        $('.titlebar').css('filter', 'blur(0px)') // unblur
        $('.titlebar').css('pointer-events', 'auto') // enable click-events
        utils.writeConsoleMsg('info', 'windowMainBlurSet ::: Disabled blur effect')
    }
}

/**
* @function settingsLoadAllOnAppStart
* @summary Reads all user-setting-files and fills some global variables
* @description Reads all user-setting-files and fills some global variables
* @memberof renderer
*/
function settingsLoadAllOnAppStart () {
    utils.writeConsoleMsg('info', 'settingsLoadAllOnAppStart ::: Gonna read several user config files now ...')
    utils.userSettingRead('enablePrereleases') // pre-releases
    utils.userSettingRead('enableErrorReporting') // get setting for error-reporting
}

// ----------------------------------------------------------------------------
// FUNCTIONS: Settings UI
// ----------------------------------------------------------------------------

/**
* @function settingsLoadAllOnSettingsUiLoad
* @summary Reads all user-setting-files and fills some global variables and adjusts the settings UI
* @description Reads all user-setting-files and fills some global variables and adjusts the settings UI
* @memberof renderer
*/
function settingsLoadAllOnSettingsUiLoad () {
    utils.writeConsoleMsg('info', 'settingsLoadAllOnAppStart ::: Gonna read several user config files now and adjust the settings UI')
    utils.userSettingRead('enablePrereleases', true) // pre-releases
    utils.userSettingRead('enableErrorReporting', true) // get setting for error-reporting
}

/**
* @function windowSettingsClickIconBug
* @summary Handles the click on the bug icon
* @description Triggered from the settingsWindow.
* @memberof renderer
*/
function windowSettingsClickIconBug () {
    const { ipcRenderer } = require('electron')
    ipcRenderer.send('settingsToggleDevTools')
}

/**
* @function windowSettingsClickIconUserSettingsDir
* @summary Handles the click on the settings icon
* @description Triggered from the settingsWindow. Starts the open-settings-folder function from the module settings
* @memberof renderer
*/
function windowSettingsClickIconUserSettingsDir () {
    utils.writeConsoleMsg('info', 'windowSettingsClickIconUserSettingsDir ::: User wants to open the folder with user config files.')
    const { ipcRenderer } = require('electron')
    ipcRenderer.send('settingsFolderOpen')
}

function windowSettingsClickCheckboxUpdatePolicy () {
    if ($('#checkboxEnablePreReleases').is(':checked')) {
        utils.writeConsoleMsg('info', 'settingsTogglePrereleases ::: Update-Search will now include pre-releases')
        utils.userSettingWrite('enablePrereleases', true)
    } else {
        utils.writeConsoleMsg('info', 'settingsTogglePrereleases ::: Update-Search will ignore pre-releases')
        utils.userSettingWrite('enablePrereleases', false)
    }
}

/**
* @function windowSettingsClickCheckboxErrorReporting
* @summary Handles the click on the checkbox error reporting
* @description Triggered from the settingsWindow.
* @memberof renderer
*/
function windowSettingsClickCheckboxErrorReporting () {
    if ($('#checkboxEnableErrorReporting').is(':checked')) {
        utils.writeConsoleMsg('info', 'settingsToggleErrorReporting ::: Error reporting is now enabled')
        utils.userSettingWrite('enableErrorReporting', true)
        sentry.enableSentry()
    } else {
        // ask if user really wants to disable error-reporting (using a confirm dialog)
        const Noty = require('noty')
        var n = new Noty(
            {
                theme: 'bootstrap-v4',
                layout: 'bottom',
                type: 'info',
                closeWith: [''], // to prevent closing the confirm-dialog by clicking something other then a confirm-dialog-button
                text: '<b>Do you really want to disable reporting?</b><br><br>* We don\'t track users<br>* We don\'t store any IP informations<br>* We only collect error reports<br><br>This helps improving dirgistered',
                buttons: [
                    Noty.button('Yes', 'btn btn-danger mediaDupes_btnDownloadActionWidth', function () {
                        n.close()
                        utils.writeConsoleMsg('info', 'settingsToggleErrorReporting ::: Error reporting is now disabled')
                        utils.userSettingWrite('enableErrorReporting', false)
                        sentry.disableSentry()
                    },
                    {
                        id: 'button1', 'data-status': 'ok'
                    }),

                    Noty.button('No', 'btn btn-success mediaDupes_btnDownloadActionWidth float-right', function () {
                        n.close()
                        $('#checkboxEnableErrorReporting').prop('checked', true) // revert state of checkbox
                        utils.showNoty('success', '<b>Thanks</b> for supporting dirgistered development with your error reports.')
                        utils.writeConsoleMsg('info', 'settingsToggleErrorReporting ::: User cancelled disabling of error-reporting')
                    })
                ]
            })

        n.show() // show the noty dialog
    }
}

// ----------------------------------------------------------------------------
// FUNCTIONS: Software Update
// ----------------------------------------------------------------------------

/**
* @function searchUpdate
* @summary Checks if there is a new dirgistered release available
* @description Compares the local app version number with the tag of the latest github release. Displays a notification in the settings window if an update is available. Is executed on app launch NOT on reload.
* @memberof renderer
* @param {booean} [silent] - Boolean with default value. Shows a feedback in case of no available updates If 'silent' = false. Special handling for manually triggered update search
*/
function searchUpdate (silent = true) {
    const { urlGithubApiReleases } = require('./js/modules/githubUrls.js') // get url for github releases / api
    var curEnablePrereleasesSetting = utils.globalObjectGet('enablePrereleases') // check if pre-releases should be included or not

    var remoteAppVersionLatest = '0.0.0'
    var remoteAppVersionLatestPrerelease = false
    var localAppVersion = '0.0.0'
    var versions

    // get local version
    //
    localAppVersion = require('electron').remote.app.getVersion()

    var updateStatus = $.get(urlGithubApiReleases, function (data, status) {
        utils.writeConsoleMsg('info', 'searchUpdate ::: Accessing _' + urlGithubApiReleases + '_ ended with: _' + status + '_')

        // success
        versions = data.sort(function (v1, v2) {
            // return semver.compare(v2.tag_name, v1.tag_name);
            // console.error(v1.tag_name)
            // console.error(v2.tag_name)
        })

        if (curEnablePrereleasesSetting === true) {
            // user wants the latest release - ignoring if it is a prerelease or an official one
            utils.writeConsoleMsg('info', 'searchUpdate ::: Including pre-releases in update search')
            remoteAppVersionLatest = versions[0].tag_name // Example: 0.4.2
            remoteAppVersionLatestPrerelease = versions[0].prerelease // boolean
        } else {
            // user wants official releases only
            utils.writeConsoleMsg('info', 'searchUpdate ::: Ignoring pre-releases in update search')
            // find the latest non pre-release build
            // loop over the versions array to find the latest non-pre-release
            var latestOfficialRelease
            for (var i = 0; i < versions.length; i++) {
                if (versions[i].prerelease === false) {
                    latestOfficialRelease = i
                    break
                }
            }

            remoteAppVersionLatest = versions[i].tag_name // Example: 0.4.2
            remoteAppVersionLatestPrerelease = versions[i].prerelease // boolean
        }

        // simulate different  update scenarios:
        //
        // localAppVersion = '0.0.1'; //  overwrite variable to simulate
        // remoteAppVersionLatest = 'v0.6.0' //  overwrite variable to simulate

        // strip the v away
        // - up to 0.5.0 the tag used on github did not start with v.
        // - comapring versions without leading chars is much easier.
        localAppVersion = localAppVersion.replace('v', '')
        remoteAppVersionLatest = remoteAppVersionLatest.replace('v', '')

        utils.writeConsoleMsg('info', 'searchUpdate ::: Local dirgistered version: ' + localAppVersion)
        utils.writeConsoleMsg('info', 'searchUpdate ::: Latest dirgistered version: ' + remoteAppVersionLatest)

        // If a stable (not a prelease) update is available - see #73
        if (localAppVersion < remoteAppVersionLatest) {
            utils.writeConsoleMsg('info', 'searchUpdate ::: Found update, notify user')

            // prepare the message for the user - depending on the fact if it is a pre-release or not
            var updateText
            if (remoteAppVersionLatestPrerelease === false) {
                updateText = 'A dirgistered update from <b>' + localAppVersion + '</b> to version <b>' + remoteAppVersionLatest + '</b> is available. Do you want to visit the release page?'
            } else {
                updateText = 'A dirgistered <b>pre-release</b> update from <b>' + localAppVersion + '</b> to version <b>' + remoteAppVersionLatest + '</b> is available. Do you want to visit the release page?'
            }

            // ask user using a noty confirm dialog
            const Noty = require('noty')
            var n = new Noty(
                {
                    theme: 'bootstrap-v4',
                    layout: 'bottom',
                    type: 'info',
                    closeWith: [''], // to prevent closing the confirm-dialog by clicking something other then a confirm-dialog-button
                    text: updateText,
                    buttons: [
                        Noty.button('Yes', 'btn btn-success mediaDupes_btnDefaultWidth', function () {
                            n.close()
                            openReleasesOverview()
                        },
                        {
                            id: 'button1', 'data-status': 'ok'
                        }),

                        Noty.button('No', 'btn btn-secondary mediaDupes_btnDefaultWidth float-right', function () {
                            n.close()
                        })
                    ]
                })

            // show the noty dialog
            n.show()
        } else {
            utils.writeConsoleMsg('info', 'searchUpdate ::: No newer version of dirgistered found.')

            // when executed manually via menu -> user should see result of this search
            if (silent === false) {
                utils.showNoty('info', 'No updates for <b>dirgistered (' + localAppVersion + ')</b> available.')
            }
        }

        utils.writeConsoleMsg('info', 'searchUpdate ::: Successfully checked ' + urlGithubApiReleases + ' for available releases')
    })
        .done(function () {
        // utils.writeConsoleMsg('info', 'searchUpdate ::: Successfully checked ' + urlGithubApiReleases + ' for available releases');
        })

        .fail(function () {
            utils.writeConsoleMsg('info', 'searchUpdate ::: Checking ' + urlGithubApiReleases + ' for available releases failed.')
            utils.showNoty('error', 'Checking <b>' + urlGithubApiReleases + '</b> for available dirgistered releases failed. Please troubleshoot your network connection.', 0)
        })

        .always(function () {
            utils.writeConsoleMsg('info', 'searchUpdate ::: Finished checking ' + urlGithubApiReleases + ' for available releases')
        })
}

/**
* @function openReleasesOverview
* @summary Opens the dirgistered release page
* @description Opens the url https://github.com/yafp/dirgistered/releases in the default browser. Used in searchUpdate().
* @memberof renderer
*/
function openReleasesOverview () {
    const { urlGitHubReleases } = require('./js/modules/githubUrls.js')
    utils.openURL(urlGitHubReleases)
    utils.writeConsoleMsg('info', 'openReleasesOverview ::: Opening _' + urlGitHubReleases + '_ to show available releases.')
}

// ----------------------------------------------------------------------------
// IPC
// ----------------------------------------------------------------------------

/**
* @name unblurMainUI
* @summary Triggers unbluring the UI
* @description Called via ipc from main.js when the settings UI got closed to trigger unblur'ing the main UI
* @memberof renderer
*/
require('electron').ipcRenderer.on('unblurMainUI', function () {
    windowMainBlurSet(false)
})

/**
* @name blurMainUI
* @summary Triggers bluring the UI
* @description Called via ipc from main.js when the main window is ready-to-show
* @memberof renderer
*/
require('electron').ipcRenderer.on('blurMainUI', function () {
    windowMainBlurSet(true)
})

// ----------------------------------------------------------------------------
// IPC - on ready-to-show
// ----------------------------------------------------------------------------

/**
* @name startSearchUpdatesSilent
* @summary Triggers the check for dirgistered updates in silent mode
* @description Called via ipc from main.js on-ready to start the search for dirgistered updates
* @memberof renderer
*/
require('electron').ipcRenderer.on('startSearchUpdatesSilent', function () {
    searchUpdate(true) // If silent = false -> Forces result feedback, even if no update is available
})

/**
* @name startSearchUpdatesVerbose
* @summary Start searching for updates in non-silent mode
* @description Called via ipc from main.js / menu to search for applicatipn updates
* @memberof renderer
*/
require('electron').ipcRenderer.on('startSearchUpdatesVerbose', function () {
    searchUpdate(false) // silent = false. Forces result feedback, even if no update is available
})