From 19cfe655fed06ff5c913f841b2e32fa181210cb7 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Fri, 18 Dec 2015 22:38:59 +0000 Subject: [PATCH] Refactor loot.game JS code Refactor the code into a new module that exports a Game class that provides equivalent functionality, with DOM-accessing code moved to event handlers. Also refactored conflicting testing mock definitions to inside the test suite functions where they're used. This will break existing untested code as no Game object is currently instantiated. --- src/gui/html/js/events.js | 70 +++++ src/gui/html/js/game.js | 257 ++++++++++++++++++ src/gui/html/js/helpers.js | 16 ++ src/gui/html/js/loot.js | 229 +--------------- src/tests/gui/html/js/test.html | 2 + src/tests/gui/html/js/test_filters.js | 14 +- src/tests/gui/html/js/test_game.js | 361 ++++++++++++++++++++++++++ src/tests/gui/html/js/test_plugin.js | 1 - 8 files changed, 714 insertions(+), 236 deletions(-) create mode 100644 src/gui/html/js/game.js create mode 100644 src/tests/gui/html/js/test_game.js diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index c07d73e8..a5b0d8e3 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -1,4 +1,68 @@ 'use strict'; +function onGamePluginsChange(evt) { + if (!evt.detail.valuesAreTotals) { + evt.detail.totalMessageNo += parseInt(document.getElementById('totalMessageNo').textContent, 10); + evt.detail.warnMessageNo += parseInt(document.getElementById('totalWarningNo').textContent, 10); + evt.detail.errorMessageNo += parseInt(document.getElementById('totalErrorNo').textContent, 10); + evt.detail.totalPluginNo += parseInt(document.getElementById('totalPluginNo').textContent, 10); + evt.detail.activePluginNo += parseInt(document.getElementById('activePluginNo').textContent, 10); + evt.detail.dirtyPluginNo += parseInt(document.getElementById('dirtyPluginNo').textContent, 10); + } + + document.getElementById('filterTotalMessageNo').textContent = evt.detail.totalMessageNo; + document.getElementById('totalMessageNo').textContent = evt.detail.totalMessageNo; + document.getElementById('totalWarningNo').textContent = evt.detail.warnMessageNo; + document.getElementById('totalErrorNo').textContent = evt.detail.errorMessageNo; + + document.getElementById('filterTotalPluginNo').textContent = evt.detail.totalPluginNo; + document.getElementById('totalPluginNo').textContent = evt.detail.totalPluginNo; + document.getElementById('activePluginNo').textContent = evt.detail.activePluginNo; + document.getElementById('dirtyPluginNo').textContent = evt.detail.dirtyPluginNo; +} +function onGameGlobalMessagesChange(evt) { + document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + evt.detail.totalDiff; + document.getElementById('totalMessageNo').textContent = parseInt(document.getElementById('totalMessageNo').textContent, 10) + evt.detail.totalDiff; + document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + evt.detail.warningDiff; + document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + evt.detail.errorDiff; + + /* Remove old messages from UI. */ + const generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; + while (generalMessagesList.firstElementChild) { + generalMessagesList.removeChild(generalMessagesList.firstElementChild); + } + + /* Add new messages. */ + if (evt.detail.messages) { + evt.detail.messages.forEach((message) => { + const li = document.createElement('li'); + li.className = message.type; + /* Use the Marked library for Markdown formatting support. */ + li.innerHTML = marked(message.content[0].str); + generalMessagesList.appendChild(li); + }); + } +} +function onGameMasterlistChange(evt) { + document.getElementById('masterlistRevision').textContent = evt.detail.revision; + document.getElementById('masterlistDate').textContent = evt.detail.date; +} +function onGameFolderChange(evt) { + updateSelectedGame(evt.detail.folder); + /* Enable/disable the redate plugins option. */ + let index = undefined; + for (let i = 0; i < loot.settings.games.length; ++i) { + if (loot.settings.games[i].folder === evt.detail.folder) { + index = i; + break; + } + } + const redateButton = document.getElementById('redatePluginsButton'); + if (index && loot.settings.games[index].type === 'Skyrim') { + redateButton.removeAttribute('disabled'); + } else { + redateButton.setAttribute('disabled', true); + } +} function onPluginMessageChange(evt) { document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + evt.detail.totalDiff; document.getElementById('totalMessageNo').textContent = parseInt(document.getElementById('totalMessageNo').textContent, 10) + evt.detail.totalDiff; @@ -782,4 +846,10 @@ function setupEventHandlers() { /* Set up handler for plugin message and dirty info changes. */ document.addEventListener('loot-plugin-message-change', onPluginMessageChange); document.addEventListener('loot-plugin-isdirty-change', onPluginIsDirtyChange); + + /* Set up event handlers for game member variable changes. */ + document.addEventListener('loot-game-folder-change', onGameFolderChange); + document.addEventListener('loot-game-masterlist-change', onGameMasterlistChange); + document.addEventListener('loot-game-global-messages-change', onGameGlobalMessagesChange); + document.addEventListener('loot-game-plugins-change', onGamePluginsChange); } diff --git a/src/gui/html/js/game.js b/src/gui/html/js/game.js new file mode 100644 index 00000000..6ccac03f --- /dev/null +++ b/src/gui/html/js/game.js @@ -0,0 +1,257 @@ +'use strict'; + +(function exportModule(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else { + // Browser globals + root.loot = root.loot || {}; + root.loot.Game = factory(); + } +}(this, () => { + return class Game { + constructor(obj, l10n) { + this.folder = obj.folder || ''; + this.globalMessages = obj.globalMessages || []; + this.masterlist = obj.masterlist || {}; + this.plugins = obj.plugins || []; + + this.loadOrder = undefined; + this.oldLoadOrder = undefined; + + this._notApplicableString = l10n.translate('N/A'); + } + + get folder() { + return this._folder; + } + + set folder(folder) { + if (folder !== this._folder) { + document.dispatchEvent(new CustomEvent('loot-game-folder-change', { + detail: { folder }, + })); + } + + this._folder = folder; + } + + get globalMessages() { + return this._globalMessages; + } + + set globalMessages(globalMessages) { + /* Update the message counts. */ + let oldTotal = 0; + let newTotal = 0; + let oldWarns = 0; + let newWarns = 0; + let oldErrs = 0; + let newErrs = 0; + + if (this._globalMessages) { + oldTotal = this._globalMessages.length; + this._globalMessages.forEach((message) => { + if (message.type === 'warn') { + ++oldWarns; + } else if (message.type === 'error') { + ++oldErrs; + } + }); + } + + if (globalMessages) { + newTotal = globalMessages.length; + + globalMessages.forEach((message) => { + if (message.type === 'warn') { + ++newWarns; + } else if (message.type === 'error') { + ++newErrs; + } + }); + } + + if (newTotal !== oldTotal || newWarns !== oldWarns || newErrs !== oldErrs) { + document.dispatchEvent(new CustomEvent('loot-game-global-messages-change', { + detail: { + totalDiff: newTotal - oldTotal, + warningDiff: newWarns - oldWarns, + errorDiff: newErrs - oldErrs, + messages: globalMessages, + }, + })); + } + + this._globalMessages = globalMessages; + } + + get masterlist() { + return this._masterlist; + } + + set masterlist(masterlist) { + if (masterlist !== this._masterlist + && (masterlist === undefined || this._masterlist === undefined + || masterlist.revision !== this._masterlist.revision + || masterlist.date !== this._masterlist.date)) { + let revision = this._notApplicableString; + let date = this._notApplicableString; + if (masterlist && masterlist.revision) { + revision = masterlist.revision; + } + if (masterlist && masterlist.date) { + date = masterlist.date; + } + + document.dispatchEvent(new CustomEvent('loot-game-masterlist-change', { + detail: { + revision, + date, + }, + })); + } + + this._masterlist = masterlist; + } + + get plugins() { + return this._plugins; + } + + set plugins(plugins) { + /* Update plugin and message counts. Unlike for global messages + it's not worth calculating the count differences, just count + from zero. */ + let totalMessageNo = 0; + let warnMessageNo = 0; + let errorMessageNo = 0; + let activePluginNo = 0; + let dirtyPluginNo = 0; + + /* Include global messages in the count. */ + if (this.globalMessages) { + totalMessageNo = this.globalMessages.length; + this.globalMessages.forEach((message) => { + if (message.type === 'warn') { + ++warnMessageNo; + } else if (message.type === 'error') { + ++errorMessageNo; + } + }); + } + + plugins.forEach((plugin) => { + if (plugin.isActive) { + ++activePluginNo; + } + if (plugin.isDirty) { + ++dirtyPluginNo; + } + if (plugin.messages) { + totalMessageNo += plugin.messages.length; + plugin.messages.forEach((message) => { + if (message.type === 'warn') { + ++warnMessageNo; + } else if (message.type === 'error') { + ++errorMessageNo; + } + }); + } + }); + + document.dispatchEvent(new CustomEvent('loot-game-plugins-change', { + detail: { + valuesAreTotals: true, + totalMessageNo, + warnMessageNo, + errorMessageNo, + totalPluginNo: plugins.length, + activePluginNo, + dirtyPluginNo, + }, + })); + + this._plugins = plugins; + } + + appendPlugin(plugin) { + let totalChange = 0; + let warnChange = 0; + let errorChange = 0; + let activeChange = 0; + let dirtyChange = 0; + + if (plugin.isActive) { + ++activeChange; + } + if (plugin.isDirty) { + ++dirtyChange; + } + if (plugin.messages) { + totalChange += plugin.messages.length; + plugin.messages.forEach((message) => { + if (message.type === 'warn') { + ++warnChange; + } else if (message.type === 'error') { + ++errorChange; + } + }); + } + + document.dispatchEvent(new CustomEvent('loot-game-plugins-change', { + detail: { + valuesAreTotals: false, + totalMessageNo: totalChange, + warnMessageNo: warnChange, + errorMessageNo: errorChange, + totalPluginNo: 1, + activePluginNo: activeChange, + dirtyPluginNo: dirtyChange, + }, + })); + + this._plugins.push(plugin); + } + + removePluginAtIndex(index) { + let totalChange = 0; + let warnChange = 0; + let errorChange = 0; + let activeChange = 0; + let dirtyChange = 0; + + if (this._plugins[index].isActive) { + --activeChange; + } + if (this._plugins[index].isDirty) { + --dirtyChange; + } + if (this._plugins[index].messages) { + totalChange -= this._plugins[index].messages.length; + this._plugins[index].messages.forEach((message) => { + if (message.type === 'warn') { + --warnChange; + } else if (message.type === 'error') { + --errorChange; + } + }); + } + + document.dispatchEvent(new CustomEvent('loot-game-plugins-change', { + detail: { + valuesAreTotals: false, + totalMessageNo: totalChange, + warnMessageNo: warnChange, + errorMessageNo: errorChange, + totalPluginNo: -1, + activePluginNo: activeChange, + dirtyPluginNo: dirtyChange, + }, + })); + + this._plugins.splice(index, 1); + } + }; +})); diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index 9f506cb1..37a0118a 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -141,3 +141,19 @@ function setFilteredUIData(filtersState) { document.getElementById('hiddenMessageNo').textContent = hiddenMessageNo; }); } +/* Call whenever game is changed or game menu / game table are rewritten. */ +function updateSelectedGame(gameFolder) { + document.getElementById('gameMenu').value = gameFolder; + + /* Also disable deletion of the game's row in the settings dialog. */ + const table = document.getElementById('gameTable'); + for (let i = 0; i < table.tBodies[0].rows.length; ++i) { + if (table.tBodies[0].rows[i].getElementsByClassName('folder').length > 0) { + if (table.tBodies[0].rows[i].getElementsByClassName('folder')[0].value === gameFolder) { + table.setReadOnly(table.tBodies[0].rows[i], ['delete']); + } else { + table.setReadOnly(table.tBodies[0].rows[i], ['delete'], false); + } + } + } +} diff --git a/src/gui/html/js/loot.js b/src/gui/html/js/loot.js index 6cb6d401..b6aa2df7 100644 --- a/src/gui/html/js/loot.js +++ b/src/gui/html/js/loot.js @@ -1,110 +1,6 @@ var loot = { installedGames: [], settings: {}, - game: { - folder: '', - globalMessages: [], - masterlist: {}, - plugins: [], - - /* Call whenever game is changed or game menu / game table are rewritten. */ - updateSelectedGame: function() { - document.getElementById('gameMenu').value = this.folder; - - /* Also disable deletion of the game's row in the settings dialog. */ - var table = document.getElementById('gameTable'); - for (var i = 0; i < table.tBodies[0].rows.length; ++i) { - if (table.tBodies[0].rows[i].getElementsByClassName('folder').length > 0) { - if (table.tBodies[0].rows[i].getElementsByClassName('folder')[0].value == this.folder) { - table.setReadOnly(table.tBodies[0].rows[i], ['delete']); - } else { - table.setReadOnly(table.tBodies[0].rows[i], ['delete'], false); - } - } - } - }, - - /* Call whenever game is changed. */ - updateRedatePluginsButtonState: function() { - /* Also enable/disable the redate plugins option. */ - var index = undefined; - for (var i = 0; i < loot.settings.games.length; ++i) { - if (loot.settings.games[i].folder == this.folder) { - index = i; - break; - } - } - var redateButton = document.getElementById('redatePluginsButton'); - if (index != undefined && loot.settings.games[index].type == 'Skyrim') { - redateButton.removeAttribute('disabled'); - } else { - redateButton.setAttribute('disabled', true); - } - }, - - pluginsObserver: function(changes) { - changes.forEach(function(change){ - /* Need to handle plugin addition and removal. - Update plugin and message counts. */ - var totalChange = 0; - var warnChange = 0; - var errorChange = 0; - var activeChange = 0; - var dirtyChange = 0; - - if (change.addedCount > 0) { - /* Addition */ - for (var i = change.index; i < change.index + change.addedCount; ++i) { - if (change.object[i].isActive) { - ++activeChange; - } - if (change.object[i].isDirty) { - ++dirtyChange; - } - if (change.object[i].messages) { - totalChange += change.object[i].messages.length; - change.object[i].messages.forEach(function(message) { - if (message.type == 'warn') { - ++warnChange; - } else if (message.type == 'error') { - ++errorChange; - } - }); - } - } - } - if (change.removed.length > 0) { - /* Removal */ - change.removed.forEach(function(plugin){ - if (plugin.isActive) { - --activeChange; - } - if (plugin.isDirty) { - --dirtyChange; - } - if (plugin.messages) { - totalChange -= plugin.messages.length; - plugin.messages.forEach(function(message) { - if (message.type == 'warn') { - --warnChange; - } else if (message.type == 'error') { - --errorChange; - } - }); - } - }); - } - document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + totalChange; - document.getElementById('totalMessageNo').textContent = document.getElementById('filterTotalMessageNo').textContent; - document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + warnReduction; - document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + errorReduction; - document.getElementById('filterTotalPluginNo').textContent = parseInt(document.getElementById('filterTotalPluginNo').textContent, 10) + change.addedCount - change.removed.length; - document.getElementById('totalPluginNo').textContent = document.getElementById('filterTotalPluginNo').textContent; - document.getElementById('activePluginNo').textContent = parseInt(document.getElementById('activePluginNo').textContent, 10) + activeReduction; - document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + dirtyReduction; - }); - } - }, /* Call whenever installedGames is changed or game menu is rewritten. */ updateEnabledGames: function() { @@ -156,7 +52,7 @@ var loot = { document.getElementById('updateMasterlist').checked = this.settings.updateMasterlist; this.updateEnabledGames(); - this.game.updateSelectedGame(); + updateSelectedGame(this.game.folder); }, /* Observer for loot members. */ @@ -170,129 +66,6 @@ var loot = { }); }, - /* Observer for loot.game members. */ - gameObserver: function(changes) { - changes.forEach(function(change){ - if (change.name == 'folder') { - change.object.updateSelectedGame(); - change.object.updateRedatePluginsButtonState(); - } else if (change.name == 'masterlist') { - if (change.object[change.name] && change.object[change.name].revision) { - document.getElementById('masterlistRevision').textContent = change.object[change.name].revision; - } else { - document.getElementById('masterlistRevision').textContent = loot.l10n.translate("N/A"); - } - if (change.object[change.name] && change.object[change.name].date) { - document.getElementById('masterlistDate').textContent = change.object[change.name].date; - } else { - document.getElementById('masterlistDate').textContent = loot.l10n.translate("N/A"); - } - } else if (change.name == 'globalMessages') { - /* For the messages, they don't have a JS 'class' so need to everything - here. Count up the global message types that exist, then remove them - and add the new ones, counting their types, then update the message - counts. I tried using an observer for this, but it wouldn't fire for - some reason. */ - - var oldTotal = change.oldValue.length; - var newTotal = 0; - var oldWarn = 0; - var newWarn = 0; - var oldErr = 0; - var newErr = 0; - - /* Count up old warnings and errors. */ - change.oldValue.forEach(function(message){ - if (message.type == 'warn') { - ++oldWarn; - } else if (message.type == 'error') { - ++oldErr; - } - }); - - /* Remove old messages from UI. */ - var generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; - while (generalMessagesList.firstElementChild) { - generalMessagesList.removeChild(generalMessagesList.firstElementChild); - } - - /* Add new messages. */ - if (change.object[change.name]) { - newTotal = change.object[change.name].length; - change.object[change.name].forEach(function(message){ - var li = document.createElement('li'); - li.className = message.type; - /* Use the Marked library for Markdown formatting support. */ - li.innerHTML = marked(message.content[0].str); - generalMessagesList.appendChild(li); - - if (li.className == 'warn') { - ++newWarn; - } else if (li.className == 'error') { - ++newErr; - } - }); - } - - /* Update message counts in UI. */ - document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + newTotal - oldTotal; - document.getElementById('totalMessageNo').textContent = document.getElementById('filterTotalMessageNo').textContent; - document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + newWarn - oldWarn; - document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + newErr - oldErr; - } else if (change.name == 'plugins') { - /* Update plugin and message counts. Unlike for global messages - it's not worth calculating the count differences, just count - from zero. */ - var totalMessageNo = 0; - var warnMessageNo = 0; - var errorMessageNo = 0; - var activePluginNo = 0; - var dirtyPluginNo = 0; - - if (change.object.globalMessages) { - totalMessageNo = change.object.globalMessages.length; - change.object.globalMessages.forEach(function(message){ - if (message.type == 'warn') { - ++warnMessageNo; - } else if (message.type == 'error') { - ++errorMessageNo; - } - }); - } - - change.object[change.name].forEach(function(plugin) { - if (plugin.isActive) { - ++activePluginNo; - } - if (plugin.isDirty) { - ++dirtyPluginNo; - } - if (plugin.messages) { - totalMessageNo += plugin.messages.length; - plugin.messages.forEach(function(message) { - if (message.type == 'warn') { - ++warnMessageNo; - } else if (message.type == 'error') { - ++errorMessageNo; - } - }); - } - }); - document.getElementById('filterTotalMessageNo').textContent = totalMessageNo; - document.getElementById('totalMessageNo').textContent = totalMessageNo; - document.getElementById('totalWarningNo').textContent = warnMessageNo; - document.getElementById('totalErrorNo').textContent = errorMessageNo; - document.getElementById('filterTotalPluginNo').textContent = change.object[change.name].length; - document.getElementById('totalPluginNo').textContent = change.object[change.name].length; - document.getElementById('activePluginNo').textContent = activePluginNo; - document.getElementById('dirtyPluginNo').textContent = dirtyPluginNo; - - /* Register a new array observer. */ - Array.observe(change.object[change.name], change.object.pluginsObserver); - } - }); - }, - /* Returns a cefQuery as a Promise. */ query: function(request) { return new Promise(function(resolve, reject) { diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html index dcf62cdb..77e945f6 100644 --- a/src/tests/gui/html/js/test.html +++ b/src/tests/gui/html/js/test.html @@ -12,9 +12,11 @@ + +