From dd77312133ec5c236e958752df7a3d4c7c4f02ee Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 13 Dec 2015 15:29:40 +0000 Subject: [PATCH 01/30] Use ESLint and the AirBnB style guide To help enforce a consistent JS code style and improve code quality by catching various syntax gotchas. I'm editing in Atom with the linter-eslint package installed, and it gives me inline warnings and errors, very handy. I'm not going to run ESLint on Travis (yet) though, as it spits out far too many errors for it to be useful. --- .eslintrc.yml | 22 ++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 .eslintrc.yml diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 00000000..53d7b43b --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,22 @@ +env: + es6: true + mocha: true + amd: true + node: true + browser: true + +ecmaFeatures: + modules: false + +globals: + loot: false + +extends: + - "eslint:recommended" + - "airbnb/base" + +rules: + strict: + - 2 + - global + no-new: 1 diff --git a/package.json b/package.json index 2ee8b1f9..a92de249 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "private": true, "devDependencies": { "bower": "^1.7.1", + "eslint": "^1.10.3", + "eslint-config-airbnb": "^2.0.0", "fs-extra": "^0.26.2", "vulcanize": "^0.7.11" } From 56a7170d1cb238074f9ff788700efb5e4607bd91 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 13 Dec 2015 11:00:35 +0000 Subject: [PATCH 02/30] Started to refactor JS Plugin class Start with making plugin.js into a module, and re-implementing Plugin using the class-based ES6 syntax, as it's a lot cleaner. Dispatch events from setters instead of using Object.observe when changing values that prompt external changes. Vulcanize's whitespace stripper doesn't like the ES6 syntax though, so disable that. Also start implementing some tests using Mocha and should.js. Finish refactoring JS Plugin code --- package.json | 2 + scripts/vulcanize.js | 1 - src/gui/html/elements/loot-plugin-card.html | 18 +- src/gui/html/elements/loot-plugin-editor.html | 8 +- src/gui/html/elements/loot-search.html | 9 +- src/gui/html/index.html | 8 +- src/gui/html/js/events.js | 24 +- src/gui/html/js/filters.js | 10 +- src/gui/html/js/init.js | 2 +- src/gui/html/js/plugin.js | 334 ++++++++-------- src/tests/gui/html/js/test.html | 18 + src/tests/gui/html/js/test_plugin.js | 365 ++++++++++++++++++ 12 files changed, 607 insertions(+), 192 deletions(-) create mode 100644 src/tests/gui/html/js/test.html create mode 100644 src/tests/gui/html/js/test_plugin.js diff --git a/package.json b/package.json index a92de249..c7133252 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "eslint": "^1.10.3", "eslint-config-airbnb": "^2.0.0", "fs-extra": "^0.26.2", + "mocha": "^2.3.4", + "should": "^8.0.1", "vulcanize": "^0.7.11" } } diff --git a/scripts/vulcanize.js b/scripts/vulcanize.js index bf53b4a3..4fc5a950 100755 --- a/scripts/vulcanize.js +++ b/scripts/vulcanize.js @@ -34,7 +34,6 @@ for (var i = 0; i < release_paths.length; ++i) { child_process.execFileSync(vulcanize, [ '--inline', - '--strip', '--config', path.join(root_path, 'scripts', 'vulcanize.config.json'), '-o', diff --git a/src/gui/html/elements/loot-plugin-card.html b/src/gui/html/elements/loot-plugin-card.html index eadc58d2..c2959880 100644 --- a/src/gui/html/elements/loot-plugin-card.html +++ b/src/gui/html/elements/loot-plugin-card.html @@ -220,7 +220,7 @@ loot-clear-metadata observe: { 'data.tags': 'onTagsChange', - 'data.computed.messages': 'onMessagesChange', + 'data.messages': 'onMessagesChange', 'data.userlist': 'onUserlistChange', 'data.isSearchResult' : 'onSearchResultChange', 'data.isMenuOpen': 'onMenuToggle' @@ -278,8 +278,8 @@ loot-clear-metadata onTagsChange: function(oldValue, newValue) { if (this.data) { - this.getElementsByClassName('tag add')[0].classList.toggle('hidden', this.data.computed.tags.added.length == 0); - this.getElementsByClassName('tag remove')[0].classList.toggle('hidden', this.data.computed.tags.removed.length == 0); + this.getElementsByClassName('tag add')[0].classList.toggle('hidden', this.data.tagStrings.added.length == 0); + this.getElementsByClassName('tag remove')[0].classList.toggle('hidden', this.data.tagStrings.removed.length == 0); } }, @@ -291,10 +291,16 @@ loot-clear-metadata messageUL.removeChild(messageUL.firstElementChild); } /* Now add new messages. */ - this.data.computed.messages.forEach(function(message){ - messageUL.appendChild(message); + var visibleMessages = filters.applyMessageFilters(this.data.messages); + visibleMessages.forEach(function(message) { + var messageLi = document.createElement('li'); + messageLi.className = message.type; + // Use the Marked library for Markdown formatting support. + messageLi.innerHTML = marked(message.content[0].str); + messageUL.appendChild(messageLi); + }); - messageUL.classList.toggle('hidden', this.data.computed.messages.length == 0); + messageUL.classList.toggle('hidden', visibleMessages.length == 0); } }, diff --git a/src/gui/html/elements/loot-plugin-editor.html b/src/gui/html/elements/loot-plugin-editor.html index fba03d6e..cbefbbb0 100644 --- a/src/gui/html/elements/loot-plugin-editor.html +++ b/src/gui/html/elements/loot-plugin-editor.html @@ -126,7 +126,7 @@ loot-editor-close

{{data.name}}

{{data.version}} - {{data.computed.crc}} + {{data.crcString}}
@@ -349,7 +349,7 @@ loot-editor-close plugin.userlist.msg = rowsData; } else if (tables[j].parentElement.id == 'tags') { rowsData.forEach(function(value, index, arr){ - arr[index] = oldData.convTagObj(value); + arr[index] = Plugin.tagFromRowData(value); }); plugin.userlist.tag = rowsData; } else if (tables[j].parentElement.id == 'dirty') { @@ -493,14 +493,14 @@ loot-editor-close if (newData.masterlist && newData.masterlist.tag) { newData.masterlist.tag.forEach(function(tag) { - var tagData = newData.convTagObj(tag); + var tagData = Plugin.tagToRowData(tag); var row = tables[j].addRow(tagData); tables[j].setReadOnly(row); }, newData); } if (tempData.userlist && tempData.userlist.tag) { tempData.userlist.tag.forEach(function(tag) { - var tagData = Plugin.prototype.convTagObj(tag); + var tagData = Plugin.tagToRowData(tag); tables[j].addRow(tagData); }, tempData); } diff --git a/src/gui/html/elements/loot-search.html b/src/gui/html/elements/loot-search.html index 719c66e9..fc4fa2a2 100644 --- a/src/gui/html/elements/loot-search.html +++ b/src/gui/html/elements/loot-search.html @@ -137,7 +137,7 @@ searchTarget is the ID of the core-list element to search the elements of. document.getElementById(host.searchTarget).data.forEach(function(plugin, index){ if (plugin.name.toLowerCase().indexOf(needle) != -1 || (!versionHidden && plugin.version.toLowerCase().indexOf(needle) != -1) - || (!crcHidden && plugin.getCrcString().toLowerCase().indexOf(needle) != -1)) { + || (!crcHidden && plugin.crcString.toLowerCase().indexOf(needle) != -1)) { host.results.push(index); plugin.isSearchResult = true; @@ -145,7 +145,7 @@ searchTarget is the ID of the core-list element to search the elements of. } if (!bashTagHidden) { - var tags = plugin.getTagStrings(); + var tags = plugin.tagStrings; if (tags.added.toLowerCase().indexOf(needle) != -1 || tags.removed.toLowerCase().indexOf(needle) != -1) { @@ -155,8 +155,9 @@ searchTarget is the ID of the core-list element to search the elements of. } } - for (var i = 0; i < plugin.computed.messages.length; ++i) { - if (plugin.computed.messages[i].textContent.toLowerCase().indexOf(needle) != -1) { + var visibleMessages = filters.applyMessageFilters(this.data.messages); + for (var i = 0; i < visibleMessages.length; ++i) { + if (visibleMessages[i].content[0].str.toLowerCase().indexOf(needle) != -1) { host.results.push(index); plugin.isSearchResult = true; return; diff --git a/src/gui/html/index.html b/src/gui/html/index.html index 65928919..ecbd6486 100644 --- a/src/gui/html/index.html +++ b/src/gui/html/index.html @@ -214,9 +214,9 @@

{{model.name}}

{{model.version}} - {{model.computed.crc}} - {{model.computed.tags.added}} - {{model.computed.tags.removed}} + {{model.crcString}} + {{model.tagStrings.added}} + {{model.tagStrings.removed}}
    @@ -316,9 +316,9 @@ - + diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 2eab2f20..ba492c46 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -1,3 +1,17 @@ +'use strict'; +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; + 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; +} +function onPluginIsDirtyChange(evt) { + if (evt.detail.isDirty) { + document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + 1; + } else { + document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) - 1; + } +} function saveFilterState(evt) { var request = JSON.stringify({ name: 'saveFilterState', @@ -62,7 +76,7 @@ function onChangeGame(evt) { /* Parse the data sent from C++. */ try { - var gameInfo = JSON.parse(result, jsonToPlugin); + var gameInfo = JSON.parse(result, loot.Plugin.fromJson); loot.game.folder = gameInfo.folder; loot.game.masterlist = gameInfo.masterlist; loot.game.globalMessages = gameInfo.globalMessages; @@ -163,7 +177,7 @@ function onSortPlugins(evt) { } } if (!found) { - loot.game.plugins.push(new Plugin(plugin)); + loot.game.plugins.push(new loot.Plugin(plugin)); loot.game.loadOrder.push(loot.game.plugins[loot.game.plugins.length - 1]); } }); @@ -668,7 +682,7 @@ function onContentRefresh(evt) { } if (!foundPlugin) { /* A new plugin. */ - loot.game.plugins.push(new Plugin(plugin)); + loot.game.plugins.push(new loot.Plugin(plugin)); } pluginNames.push(plugin.name); }); @@ -760,4 +774,8 @@ function setupEventHandlers() { document.getElementById('cardsNav').addEventListener('click', onSidebarClick, false); document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick, false); + + /* Set up handler for plugin message and dirty info changes. */ + document.addEventListener('loot-plugin-message-change', onPluginMessageChange); + document.addEventListener('loot-plugin-isdirty-change', onPluginIsDirtyChange); } diff --git a/src/gui/html/js/filters.js b/src/gui/html/js/filters.js index 33bde7ac..febd8971 100644 --- a/src/gui/html/js/filters.js +++ b/src/gui/html/js/filters.js @@ -12,13 +12,13 @@ var filters = { } if (plugin.name.toLowerCase().indexOf(needle) != -1 - || plugin.getCrcString().toLowerCase().indexOf(needle) != -1 + || plugin.crcString.toLowerCase().indexOf(needle) != -1 || plugin.version.toLowerCase().indexOf(needle) != -1) { return true; } - var tags = plugin.getTagStrings(); + var tags = plugin.tagStrings; if (tags.added.toLowerCase().indexOf(needle) != -1 || tags.removed.toLowerCase().indexOf(needle) != -1) { @@ -202,9 +202,11 @@ function setFilteredUIData() { document.getElementById('cardsNav').data = filtered; document.getElementById('main').lastElementChild.data = filtered; - /* Also run message filters on the filtered plugins. */ filtered.forEach(function(plugin){ - plugin.computed.messages = plugin.getUIMessages(); + var element = document.getElementById(plugin.id); + if (element) { + element.onMessagesChange(); + } }); /* Now perform search again. If there is no current search, this won't diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 8c011628..dc650cb0 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -155,7 +155,7 @@ function initVars() { }); } else { return loot.query('getGameData').then(function(result){ - var game = JSON.parse(result, jsonToPlugin); + var game = JSON.parse(result, loot.Plugin.fromJson); loot.game.folder = game.folder; loot.game.masterlist = game.masterlist; loot.game.globalMessages = game.globalMessages; diff --git a/src/gui/html/js/plugin.js b/src/gui/html/js/plugin.js index 64202b6a..94733205 100644 --- a/src/gui/html/js/plugin.js +++ b/src/gui/html/js/plugin.js @@ -22,193 +22,197 @@ . */ 'use strict'; -/* Plugin object for managing data and UI interaction. */ -function Plugin(obj) { - this.name = obj.name; - this.crc = obj.crc; - this.version = obj.version; - this.isActive = obj.isActive; - this.isEmpty = obj.isEmpty; - this.isMaster = obj.isMaster; - this.loadsArchive = obj.loadsArchive; +(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.Plugin = factory(); + } +}(this, () => { + return class Plugin { + constructor(obj) { + /* Plugin data */ + this.name = obj.name; + this.crc = obj.crc; + this.version = obj.version; + this.isActive = obj.isActive; + this.isEmpty = obj.isEmpty; + this.isMaster = obj.isMaster; + this.loadsArchive = obj.loadsArchive; - this.masterlist = obj.masterlist; - this.userlist = obj.userlist; + this.masterlist = obj.masterlist; + this.userlist = obj.userlist; - this.modPriority = obj.modPriority; - this.isGlobalPriority = obj.isGlobalPriority; - this.messages = obj.messages; - this.tags = obj.tags; - this.isDirty = obj.isDirty; + this.modPriority = obj.modPriority; + this.isGlobalPriority = obj.isGlobalPriority; + this._messages = obj.messages; + this.tags = obj.tags; + this._isDirty = obj.isDirty || false; - this.id = this.name.replace(/\s+/g, ''); - this.isMenuOpen = false; - this.isEditorOpen = false; - this.isConflictFilterChecked = false; - this.isSearchResult = false; - - /* Converts between the LOOT metadata object for tags, and their - editor row representation. */ - Plugin.prototype.convTagObj = function(tag) { - var newTag = { - condition: tag.condition - }; - if (tag.type) { - /* Input is row data. */ - if (tag.type == 'remove') { - newTag.name = '-' + tag.name; - } else { - newTag.name = tag.name; - } - } else { - /* Input is metadata object. */ - if (tag.name[0] == '-') { - newTag.type = 'remove'; - newTag.name = tag.name.substr(1); - } else { - newTag.type = 'add'; - newTag.name = tag.name; - } - } - return newTag; + /* UI state variables */ + this.id = this.name.replace(/\s+/g, ''); + this.isMenuOpen = false; + this.isEditorOpen = false; + this.isConflictFilterChecked = false; + this.isSearchResult = false; } - Plugin.prototype.getTagStrings = function() { - var tagsAdded = []; - var tagsRemoved = []; - - if (this.tags) { - for (var i = 0; i < this.tags.length; ++i) { - if (this.tags[i].name[0] == '-') { - tagsRemoved.push(this.tags[i].name.substr(1)); - } else { - tagsAdded.push(this.tags[i].name); - } - } - } - /* Now make sure that the same tag doesn't appear in both arrays. - Prefer the removed list. */ - for (var i = 0; i < tagsAdded.length; ++i) { - for (var j = 0; j < tagsRemoved.length; ++j) { - if (tagsRemoved[j].toLowerCase() == tagsAdded[i].toLowerCase()) { - /* Remove tag from the tagsAdded array. */ - tagsAdded.splice(i, 1); - --i; - } - } - } - - return { - added: tagsAdded.join(', '), - removed: tagsRemoved.join(', ') - }; + static fromJson(key, value) { + if (value !== null && value.__type === 'Plugin') { + return new Plugin(value); + } + return value; } - Plugin.prototype.getPriorityString = function() { - if (this.modPriority != 0) { - return this.modPriority.toString(); - } else { - return ''; - } + static tagFromRowData(rowData) { + if (rowData.condition === undefined || rowData.name === undefined || rowData.type === undefined) { + throw new TypeError('Row data members are undefined'); + } + const tag = { + condition: rowData.condition, + name: '', + }; + + if (rowData.type === 'remove') { + tag.name = '-'; + } + tag.name += rowData.name; + + return tag; } - Plugin.prototype.getCrcString = function() { - if (this.crc == 0) { - return ''; - } else { - /* Pad CRC string to 8 characters. */ - return ('00000000' + this.crc.toString(16).toUpperCase()).slice(-8); - } + static tagToRowData(tag) { + const rowData = { + condition: tag.condition, + }; + + if (tag.name[0] === '-') { + rowData.type = 'remove'; + rowData.name = tag.name.substr(1); + } else { + rowData.type = 'add'; + rowData.name = tag.name; + } + + return rowData; } - Plugin.prototype.getUIMessages = function() { - var uiMessages = []; - /* Now add the new messages. */ - if (this.messages && this.messages.length != 0) { - filters.applyMessageFilters(this.messages).forEach(function(message) { - var messageLi = document.createElement('li'); - messageLi.className = message.type; - // Use the Marked library for Markdown formatting support. - messageLi.innerHTML = marked(message.content[0].str); - uiMessages.push(messageLi); + get tagStrings() { + const tagsAdded = []; + const tagsRemoved = []; - }); + if (this.tags) { + for (let i = 0; i < this.tags.length; ++i) { + if (this.tags[i].name[0] === '-') { + tagsRemoved.push(this.tags[i].name.substr(1)); + } else { + tagsAdded.push(this.tags[i].name); + } } + } + /* Now make sure that the same tag doesn't appear in both arrays. + Prefer the removed list. */ + for (let i = 0; i < tagsAdded.length; ++i) { + for (let j = 0; j < tagsRemoved.length; ++j) { + if (tagsRemoved[j].toLowerCase() === tagsAdded[i].toLowerCase()) { + /* Remove tag from the tagsAdded array. */ + tagsAdded.splice(i, 1); + --i; + } + } + } - return uiMessages; + return { + added: tagsAdded.join(', '), + removed: tagsRemoved.join(', '), + }; } - Plugin.prototype.observer = function(changes) { - changes.forEach(function(change) { - if (change.name == 'tags') { - change.object.computed.tags = change.object.getTagStrings(); - } else if (change.name == 'modPriority') { - change.object.computed.priority = change.object.getPriorityString(); - } else if (change.name == 'crc') { - change.object.computed.crc = change.object.getCrcString(); - } else if (change.name == 'messages') { - /* Update computed list items. */ - change.object.computed.messages = change.object.getUIMessages(); + get priorityString() { + if (this.modPriority === undefined || this.modPriority === 0) { + return ''; + } - /* Update the message counts. */ - var oldTotal = 0; - var newTotal = 0; - var oldWarns = 0; - var newWarns = 0; - var oldErrs = 0; - var newErrs = 0; + return this.modPriority.toString(); + } - if (change.oldValue) { - oldTotal = change.oldValue.length; + get crcString() { + if (this.crc === undefined || this.crc === 0) { + return ''; + } - change.oldValue.forEach(function(message){ - if (message.type == 'warn') { - ++oldWarns; - } else if (message.type == 'error') { - ++oldErrs; - } - }); - } - if (change.object[change.name]) { - newTotal = change.object[change.name].length; + /* Pad CRC string to 8 characters. */ + return ('00000000' + this.crc.toString(16).toUpperCase()).slice(-8); + } - change.object[change.name].forEach(function(message){ - if (message.type == 'warn') { - ++newWarns; - } else if (message.type == 'error') { - ++newErrs; - } - }); - } + get messages() { + return this._messages; + } - document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + newTotal - oldTotal; - document.getElementById('totalMessageNo').textContent = parseInt(document.getElementById('totalMessageNo').textContent, 10) + newTotal - oldTotal; - document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + newWarns - oldWarns; - document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + newErrs - oldErrs; - } else if (change.name == 'isDirty') { - /* Update dirty counts. */ - if (change.object[change.name]) { - document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + 1; - } else { - document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) - 1; - } - } + set messages(messages) { + /* Update the message counts. */ + let oldTotal = 0; + let newTotal = 0; + let oldWarns = 0; + let newWarns = 0; + let oldErrs = 0; + let newErrs = 0; + + if (this._messages) { + oldTotal = this._messages.length; + + this._messages.forEach((message) => { + if (message.type === 'warn') { + ++oldWarns; + } else if (message.type === 'error') { + ++oldErrs; + } }); + } + + if (messages) { + newTotal = messages.length; + + messages.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-plugin-message-change', { + detail: { + totalDiff: newTotal - oldTotal, + warningDiff: newWarns - oldWarns, + errorDiff: newErrs - oldErrs, + }, + })); + } + + this._messages = messages; } - this.computed = { - tags: this.getTagStrings(), - priority: this.getPriorityString(), - crc: this.getCrcString(), - messages: this.getUIMessages(), - }; - Object.observe(this, this.observer); -} - -function jsonToPlugin(key, value) { - if (value !== null && value.__type === 'Plugin') { - var p = new Plugin(value); - return p; + get isDirty() { + return this._isDirty; } - return value; -} + + set isDirty(dirty) { + /* Update dirty counts. */ + if (dirty !== this._isDirty) { + document.dispatchEvent(new CustomEvent('loot-plugin-isdirty-change', { + detail: { + isDirty: dirty, + }, + })); + } + + this._isDirty = dirty; + } + }; +})); diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html new file mode 100644 index 00000000..a5d71d3c --- /dev/null +++ b/src/tests/gui/html/js/test.html @@ -0,0 +1,18 @@ + +Mocha Tests + + +
    + + + + + + + + + + diff --git a/src/tests/gui/html/js/test_plugin.js b/src/tests/gui/html/js/test_plugin.js new file mode 100644 index 00000000..78bb99a7 --- /dev/null +++ b/src/tests/gui/html/js/test_plugin.js @@ -0,0 +1,365 @@ +'use strict'; + +describe('Plugin', () => { + describe('#Plugin()', () => { + it('should throw if nothing is passed', () => { + (() => { new loot.Plugin(); }).should.throw(); + }); + + it('should throw if an object with no name key is passed', () => { + (() => { new loot.Plugin({}); }).should.throw(); + }); + + it('should not throw if some members are undefined', () => { + (() => { new loot.Plugin({ name: 'test' }); }).should.not.throw(); + }); + }); + + describe('#fromJson()', () => { + it('should return the value object if the JSON is not of the Plugin type', () => { + const testInputObj = { + name: 'test', + crc: 0xDEADBEEF, + }; + const testInputJson = JSON.stringify(testInputObj); + + JSON.parse(testInputJson, loot.Plugin.fromJson).should.deepEqual(testInputObj); + }); + + it('should return a Plugin object if the JSON is of the Plugin type', () => { + const testInputObj = { + name: 'test', + crc: 0xDEADBEEF, + __type: 'Plugin', + }; + const testInputJson = JSON.stringify(testInputObj); + + JSON.parse(testInputJson, loot.Plugin.fromJson).should.be.instanceof(loot.Plugin); + }); + }); + + describe('#tagFromRowData()', () => { + it('should throw if passed nothing', () => { + (() => { loot.Plugin.tagFromRowData(); }).should.throw(); + }); + + it('should return an empty object if passed nothing', () => { + (() => { loot.Plugin.tagFromRowData({}); }).should.throw(); + }); + + it('should return a raw metadata object if passed a row data object that removes a tag', () => { + loot.Plugin.tagFromRowData({ + condition: 'foo', + type: 'remove', + name: 'bar', + }).should.deepEqual({ + condition: 'foo', + name: '-bar', + }); + }); + + it('should return a raw metadata object if passed a row data object that adds a tag', () => { + loot.Plugin.tagFromRowData({ + condition: 'foo', + type: 'add', + name: 'bar', + }).should.deepEqual({ + condition: 'foo', + name: 'bar', + }); + }); + }); + + describe('#tagToRowData()', () => { + it('should throw if passed nothing', () => { + (() => { loot.Plugin.tagToRowData(); }).should.throw(); + }); + + it('should return an empty object if passed nothing', () => { + (() => { loot.Plugin.tagToRowData({}); }).should.throw(); + }); + + it('should return a row data object if passed a raw metadata object that removes a tag', () => { + loot.Plugin.tagToRowData({ + condition: 'foo', + name: '-bar', + }).should.deepEqual({ + condition: 'foo', + type: 'remove', + name: 'bar', + }); + }); + + it('should return a row data object if passed a raw metadata object that adds a tag', () => { + loot.Plugin.tagToRowData({ + condition: 'foo', + name: 'bar', + }).should.deepEqual({ + condition: 'foo', + type: 'add', + name: 'bar', + }); + }); + }); + + describe('#tagStrings', () => { + it('should return an object containing empty strings if no tags are set', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.tagStrings.should.deepEqual({ + added: '', + removed: '', + }); + }); + + it('should return an object containing strings of comma-separated tag names if tags are set', () => { + const plugin = new loot.Plugin({ + name: 'test', + tags: [ + { name: 'Relev' }, + { name: 'Delev' }, + { name: 'Names' }, + { name: '-C.Climate' }, + { name: '-Actor.ABCS' }, + ], + }); + + plugin.tagStrings.should.deepEqual({ + added: 'Relev, Delev, Names', + removed: 'C.Climate, Actor.ABCS', + }); + }); + + it('should output a tag in the removed string if it appears as both added and removed', () => { + const plugin = new loot.Plugin({ + name: 'test', + tags: [ + { name: 'Relev' }, + { name: '-Relev' }, + ], + }); + + plugin.tagStrings.should.deepEqual({ + added: '', + removed: 'Relev', + }); + }); + }); + + describe('#priorityString', () => { + it('should return an empty string if priority is undefined', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.priorityString.should.equal(''); + }); + + it('should return an empty string if priority is zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + modPriority: 0, + }); + + plugin.priorityString.should.equal(''); + }); + + it('should return priority valueAs string if non zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + modPriority: -50, + }); + + plugin.priorityString.should.equal('-50'); + }); + }); + + describe('#crcString', () => { + it('should return an empty string if crc is undefined', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.crcString.should.equal(''); + }); + + it('should return an empty string if crc is zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + crc: 0, + }); + + plugin.crcString.should.equal(''); + }); + + it('should return crc value as string if non zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + crc: 0xDEADBEEF, + }); + + plugin.crcString.should.equal('DEADBEEF'); + }); + + it('should pad crc value to eight digits', () => { + const plugin = new loot.Plugin({ + name: 'test', + crc: 0xBEEF, + }); + + plugin.crcString.should.equal('0000BEEF'); + }); + }); + + describe('#messages', () => { + let handleEvent; + + afterEach(() => { + document.removeEventListener('loot-plugin-message-change', handleEvent); + }); + + it('getting messages if they are undefined should return undefined', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.should.not.have.ownProperty('messages'); + }); + + it('getting messages if the array is empty should return an empty array', () => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [], + }); + + plugin.messages.should.be.Array(); + plugin.messages.should.be.empty(); + }); + + it('getting messages should return any that are set', () => { + const messages = [{ + type: 'say', + content: 'test message', + }]; + const plugin = new loot.Plugin({ + name: 'test', + messages: messages, + }); + + plugin.messages.should.be.deepEqual(messages); + }); + + it('setting messages should store any set', () => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [], + }); + const messages = [{ + type: 'say', + content: 'test message', + }]; + + plugin.messages = messages; + + plugin.messages.should.be.deepEqual(messages); + }); + + it('setting messages should not fire an event if no message counts were changed', (done) => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [{ + type: 'say', + content: 'test message', + }], + }); + const messages = [{ + type: 'say', + content: 'another test message', + }]; + + handleEvent = () => { + done(new Error('Should not have fired an event')); + }; + + document.addEventListener('loot-plugin-message-change', handleEvent); + + plugin.messages = messages; + + setTimeout(done, 100); + }); + + it('setting messages should fire an event if message counts were changed', (done) => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [], + }); + const messages = [{ + type: 'error', + content: 'test message', + }]; + + handleEvent = (evt) => { + evt.detail.totalDiff.should.equal(1); + evt.detail.warningDiff.should.equal(0); + evt.detail.errorDiff.should.equal(1); + done(); + }; + + document.addEventListener('loot-plugin-message-change', handleEvent); + + plugin.messages = messages; + }); + }); + + describe('#isDirty', () => { + let handleEvent; + + afterEach(() => { + document.removeEventListener('loot-plugin-isdirty-change', handleEvent); + }); + + it('getting value should return false if isDirty has not been set in the constructor', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.isDirty.should.be.false(); + }); + + it('getting value should return true if isDirty is set to true in the constructor', () => { + const plugin = new loot.Plugin({ + name: 'test', + isDirty: true, + }); + + plugin.isDirty.should.be.true(); + }); + + it('setting value should store set value', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.isDirty = true; + + plugin.isDirty.should.be.true(); + }); + + it('setting value to the current value should not fire an event', (done) => { + const plugin = new loot.Plugin({ name: 'test' }); + + handleEvent = () => { + done(new Error('Should not have fired an event')); + }; + + document.addEventListener('loot-plugin-isdirty-change', handleEvent); + + plugin.isDirty = plugin.isDirty; + + setTimeout(done, 100); + }); + + it('setting value not equal to the current value should fire an event', (done) => { + const plugin = new loot.Plugin({ name: 'test' }); + + handleEvent = (evt) => { + evt.detail.isDirty.should.be.true(); + done(); + }; + + document.addEventListener('loot-plugin-isdirty-change', handleEvent); + + plugin.isDirty = !plugin.isDirty; + }); + }); +}); From ceccbaef73224a4964d082bdc3f686666585b5e3 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 13 Dec 2015 18:27:43 +0000 Subject: [PATCH 03/30] Fix code style issues in JS scripts --- scripts/archive.js | 357 +++++++++++++++++++++---------------------- scripts/helpers.js | 183 +++++++++++----------- scripts/vulcanize.js | 62 ++++---- 3 files changed, 301 insertions(+), 301 deletions(-) diff --git a/scripts/archive.js b/scripts/archive.js index 21b15bd8..1dadfee4 100644 --- a/scripts/archive.js +++ b/scripts/archive.js @@ -2,206 +2,203 @@ // Archive packaging script. Takes one argument, which is the path to the // repository's root. Requires 7-zip and Git to be installed, and Git to be // available on the system path. +'use strict'; +const childProcess = require('child_process'); +const path = require('path'); +const fs = require('fs-extra'); +const os = require('os'); +const helpers = require('./helpers'); -var child_process = require('child_process'); -var path = require('path'); -var fs = require('fs-extra'); -var os = require('os'); -var helpers = require('./helpers'); - -function vulcanize() { - return child_process.execFileSync('node', [ - 'scripts/vulcanize.js', - root_path - ]); +function vulcanize(rootPath) { + return childProcess.execFileSync('node', [ + 'scripts/vulcanize.js', + rootPath, + ]); } function getGitDescription() { - return String(child_process.execFileSync('git', [ - 'describe', - '--tags', - '--long' - ])).slice(0, -1); + return String(childProcess.execFileSync('git', [ + 'describe', + '--tags', + '--long', + ])).slice(0, -1); } -function compress(source_path, dest_path) { - var sevenzip_path = ''; - if (os.platform() == 'win32') { - sevenzip_path = path.join('C:\\', 'Program Files', '7-Zip', '7z.exe'); - } else { - sevenzip_path = '/usr/bin/7z'; +function compress(sourcePath, destPath) { + let sevenzipPath = ''; + if (os.platform() === 'win32') { + sevenzipPath = path.join('C:\\', 'Program Files', '7-Zip', '7z.exe'); + } else { + sevenzipPath = '/usr/bin/7z'; + } + + // First remove any existing archive. + fs.removeSync(destPath); + + // The last argument must have a leading dot for the subdirectory not to + // be present in the archive, but path.join removes it, so it's prefixed. + return childProcess.execFileSync(sevenzipPath, [ + 'a', + '-r', + destPath, + '.' + path.sep + path.join(sourcePath, '*'), + ]); +} + +function createAppArchive(rootPath, releasePath, tempPath, destPath) { + // Ensure that the output directory is empty. + fs.emptyDirSync(tempPath); + + // Copy LOOT exectuable and CEF files. + let binaries = []; + if (os.platform() === 'win32') { + binaries = [ + 'LOOT.exe', + 'd3dcompiler_47.dll', + 'libEGL.dll', + 'libGLESv2.dll', + 'libcef.dll', + 'natives_blob.bin', + 'snapshot_blob.bin', + 'cef.pak', + 'cef_100_percent.pak', + 'cef_200_percent.pak', + 'devtools_resources.pak', + 'icudtl.dat', + ]; + + if (helpers.fileExists(path.join(releasePath, 'wow_helper.exe'))) { + binaries.push('wow_helper.exe'); } + } else { + binaries = [ + 'LOOT', + 'chrome-sandbox', + 'libcef.so', + 'natives_blob.bin', + 'snapshot_blob.bin', + 'cef.pak', + 'cef_100_percent.pak', + 'cef_200_percent.pak', + 'devtools_resources.pak', + 'icudtl.dat', + ]; + } + binaries.forEach((file) => { + fs.copySync( + path.join(releasePath, file), + path.join(tempPath, file) + ); + }); - // First remove any existing archive. - fs.removeSync(dest_path); + // CEF locale file. + fs.mkdirsSync(path.join(tempPath, 'resources', 'l10n')); + fs.copySync( + path.join(releasePath, 'resources', 'l10n', 'en-US.pak'), + path.join(tempPath, 'resources', 'l10n', 'en-US.pak') + ); - // The last argument must have a leading dot for the subdirectory not to - // be present in the archive, but path.join removes it, so it's prefixed. - return child_process.execFileSync(sevenzip_path, [ - 'a', - '-r', - dest_path, - '.' + path.sep + path.join(source_path, '*') - ]); + // Translation files. + [ + 'es', 'ru', 'fr', 'zh_CN', 'pl', 'pt_BR', 'fi', 'de', 'da', 'ko', + ].forEach((lang) => { + fs.mkdirsSync(path.join(tempPath, 'resources', 'l10n', lang, 'LC_MESSAGES')); + fs.copySync( + path.join(rootPath, 'resources', 'l10n', lang, 'LC_MESSAGES', 'loot.mo'), + path.join(tempPath, 'resources', 'l10n', lang, 'LC_MESSAGES', 'loot.mo') + ); + }); + + // UI files. + fs.mkdirsSync(path.join(tempPath, 'resources', 'ui', 'css')); + fs.copySync( + path.join(releasePath, 'resources', 'ui', 'index.html'), + path.join(tempPath, 'resources', 'ui', 'index.html') + ); + fs.copySync( + path.join(rootPath, 'resources', 'ui', 'css', 'dark-theme.css'), + path.join(tempPath, 'resources', 'ui', 'css', 'dark-theme.css') + ); + fs.copySync( + path.join(rootPath, 'resources', 'ui', 'fonts'), + path.join(tempPath, 'resources', 'ui', 'fonts') + ); + + // Docs. + fs.mkdirsSync(path.join(tempPath, 'docs')); + [ + 'images', + 'licenses', + 'LOOT Metadata Syntax.html', + 'LOOT Readme.html', + ].forEach((item) => { + fs.copySync( + path.join(rootPath, 'docs', item), + path.join(tempPath, 'docs', item) + ); + }); + + // Now compress the folder to a 7-zip archive. + compress(tempPath, destPath); + + // Finally, delete the temporary folder. + fs.removeSync(tempPath); } -function createAppArchive(release_path, dest_path) { - // Ensure that the output directory is empty. - fs.emptyDirSync(temp_path); +function createApiArchive(rootPath, binaryPath, tempPath, destPath) { + // Ensure that the output directory is empty. + fs.emptyDirSync(tempPath); - // Copy LOOT exectuable and CEF files. - var binaries = []; - if (os.platform() == 'win32') { - binaries = [ - 'LOOT.exe', - 'd3dcompiler_47.dll', - 'libEGL.dll', - 'libGLESv2.dll', - 'libcef.dll', - 'natives_blob.bin', - 'snapshot_blob.bin', - 'cef.pak', - 'cef_100_percent.pak', - 'cef_200_percent.pak', - 'devtools_resources.pak', - 'icudtl.dat' - ]; + // API binary/binaries. + fs.copySync( + binaryPath, + path.join(tempPath, path.basename(binaryPath)) + ); - if (helpers.fileExists(path.join(release_path, 'wow_helper.exe'))) { - binaries.push('wow_helper.exe'); - } - } else { - binaries = [ - 'LOOT', - 'chrome-sandbox', - 'libcef.so', - 'natives_blob.bin', - 'snapshot_blob.bin', - 'cef.pak', - 'cef_100_percent.pak', - 'cef_200_percent.pak', - 'devtools_resources.pak', - 'icudtl.dat' - ]; - } - binaries.forEach(function(file){ - fs.copySync( - path.join(release_path, file), - path.join(temp_path, file) - ); - }); + // API header file. + fs.mkdirsSync(path.join(tempPath, 'include', 'loot')); + fs.copySync( + path.join(rootPath, 'include', 'loot', 'api.h'), + path.join(tempPath, 'include', 'loot', 'api.h') + ); - // CEF locale file. - fs.mkdirsSync(path.join(temp_path, 'resources', 'l10n')); - fs.copySync( - path.join(release_path, 'resources', 'l10n', 'en-US.pak'), - path.join(temp_path, 'resources', 'l10n', 'en-US.pak') - ); + // Docs. + fs.mkdirsSync(path.join(tempPath, 'docs')); + fs.copySync( + path.join(rootPath, 'docs', 'latex', 'refman.pdf'), + path.join(tempPath, 'docs', 'readme.pdf') + ); + fs.copySync( + path.join(rootPath, 'docs', 'licenses'), + path.join(tempPath, 'docs', 'licenses') + ); - // Translation files. - [ - 'es', 'ru', 'fr', 'zh_CN', 'pl', - 'pt_BR', 'fi', 'de', 'da', 'ko' - ].forEach(function(lang){ - fs.mkdirsSync(path.join(temp_path, 'resources', 'l10n', lang, 'LC_MESSAGES')); - fs.copySync( - path.join(root_path, 'resources', 'l10n', lang, 'LC_MESSAGES', 'loot.mo'), - path.join(temp_path, 'resources', 'l10n', lang, 'LC_MESSAGES', 'loot.mo') - ); - }); + // Now compress the folder to a 7-zip archive. + compress(tempPath, destPath); - // UI files. - fs.mkdirsSync(path.join(temp_path, 'resources', 'ui', 'css')); - fs.copySync( - path.join(release_path, 'resources', 'ui', 'index.html'), - path.join(temp_path, 'resources', 'ui', 'index.html') - ); - fs.copySync( - path.join(root_path, 'resources', 'ui', 'css', 'dark-theme.css'), - path.join(temp_path, 'resources', 'ui', 'css', 'dark-theme.css') - ); - fs.copySync( - path.join(root_path, 'resources', 'ui', 'fonts'), - path.join(temp_path, 'resources', 'ui', 'fonts') - ); - - // Docs. - fs.mkdirsSync(path.join(temp_path, 'docs')); - [ - 'images', - 'licenses', - 'LOOT Metadata Syntax.html', - 'LOOT Readme.html' - ].forEach(function(item){ - fs.copySync( - path.join(root_path, 'docs', item), - path.join(temp_path, 'docs', item) - ); - }); - - // Now compress the folder to a 7-zip archive. - compress(temp_path, dest_path); - - // Finally, delete the temporary folder. - fs.removeSync(temp_path); + // Finally, delete the temporary folder. + fs.removeSync(tempPath); } -function createApiArchive(binary_path, dest_path) { - // Ensure that the output directory is empty. - fs.emptyDirSync(temp_path); +let rootPath = '.'; +if (process.argv.length > 2) { + rootPath = process.argv[2]; +} +const tempPath = path.join(rootPath, 'build', 'archive.tmp'); - // API binary/binaries. - fs.copySync( - binary_path, - path.join(temp_path, path.basename(binary_path)) - ); +const gitDesc = getGitDescription(); +vulcanize(rootPath); - // API header file. - fs.mkdirsSync(path.join(temp_path, 'include', 'loot')); - fs.copySync( - path.join(root_path, 'include', 'loot', 'api.h'), - path.join(temp_path, 'include', 'loot', 'api.h') - ); - - // Docs. - fs.mkdirsSync(path.join(temp_path, 'docs')); - fs.copySync( - path.join(root_path, 'docs', 'latex', 'refman.pdf'), - path.join(temp_path, 'docs', 'readme.pdf') - ); - fs.copySync( - path.join(root_path, 'docs', 'licenses'), - path.join(temp_path, 'docs', 'licenses') - ); - - - // Now compress the folder to a 7-zip archive. - compress(temp_path, dest_path); - - // Finally, delete the temporary folder. - fs.removeSync(temp_path); +const releasePaths = helpers.getAppReleasePaths(rootPath); +for (let i = 0; i < releasePaths.length; ++i) { + if (releasePaths[i].label) { + createAppArchive(rootPath, releasePaths[i].path, tempPath, path.join(rootPath, 'build', 'LOOT ' + gitDesc + ' (' + releasePaths[i].label + ').7z')); + } else { + createAppArchive(releasePaths[i].path, path.join(rootPath, 'build', 'LOOT ' + gitDesc + '.7z')); + } } -if (process.argv.length < 3) { - var root_path = '.'; -} else { - var root_path = process.argv[2]; -} -var temp_path = path.join(root_path, 'build', 'archive.tmp'); - -var git_desc = getGitDescription(); -vulcanize(); - -var release_paths = helpers.getAppReleasePaths(root_path); -for (var i = 0; i < release_paths.length; ++i) { - if (release_paths[i].label) { - createAppArchive(release_paths[i].path, path.join(root_path, 'build', 'LOOT ' + git_desc + ' (' + release_paths[i].label + ').7z')); - } else { - createAppArchive(release_paths[i].path, path.join(root_path, 'build', 'LOOT ' + git_desc + '.7z')); - } -} - -var binary_paths = helpers.getApiBinaryPaths(root_path); -for (var i = 0; i < binary_paths.length; ++i) { - createApiArchive(binary_paths[i].path, path.join(root_path, 'build', 'LOOT API ' + git_desc + ' (' + binary_paths[i].label + ').7z')); +const binaryPaths = helpers.getApiBinaryPaths(rootPath); +for (let i = 0; i < binaryPaths.length; ++i) { + createApiArchive(rootPath, binaryPaths[i].path, tempPath, path.join(rootPath, 'build', 'LOOT API ' + gitDesc + ' (' + binaryPaths[i].label + ').7z')); } diff --git a/scripts/helpers.js b/scripts/helpers.js index 8f4a531c..45dcba71 100644 --- a/scripts/helpers.js +++ b/scripts/helpers.js @@ -1,113 +1,116 @@ // Helper functions shared across scripts. -var path = require('path'); -var fs = require('fs'); -var os = require('os'); +'use strict'; +const path = require('path'); +const fs = require('fs'); +const os = require('os'); -function fileExists(file_path) { - try { - // Query the entry - stats = fs.lstatSync(file_path); +function fileExists(filePath) { + try { + // Query the entry + const stats = fs.lstatSync(filePath); - // Is it a directory? - if (stats.isFile()) { - return true; - } - } catch (e) {} + // Is it a directory? + if (stats.isFile()) { + return true; + } + } catch (e) { + /* Don't do anything, it's not an error. */ + } - return false; + return false; } -function getAppReleasePaths(root_path) { - var paths = []; - var file = 'LOOT'; - var paths_to_try = [ - { - path: path.join(root_path, 'build'), - label: null - }, - { - path: path.join(root_path, 'build', '32'), - label: '32 bit' - }, - { - path: path.join(root_path, 'build', '64'), - label: '64 bit' - } - ]; +function getAppReleasePaths(rootPath) { + const paths = []; + const pathsToTry = [ + { + path: path.join(rootPath, 'build'), + label: null, + }, + { + path: path.join(rootPath, 'build', '32'), + label: '32 bit', + }, + { + path: path.join(rootPath, 'build', '64'), + label: '64 bit', + }, + ]; - if (os.platform() == 'win32') { - file += '.exe'; + let file = 'LOOT'; + if (os.platform() === 'win32') { + file += '.exe'; + } + + for (let i = 0; i < pathsToTry.length; ++i) { + if (os.platform() === 'win32') { + pathsToTry[i].path = path.join(pathsToTry[i].path, 'Release'); } - for (var i = 0; i < paths_to_try.length; ++i) { - if (os.platform() == 'win32') { - paths_to_try[i].path = path.join(paths_to_try[i].path, 'Release'); - } - - if (fileExists(path.join(paths_to_try[i].path, file))) { - paths.push(paths_to_try[i]); - } + if (fileExists(path.join(pathsToTry[i].path, file))) { + paths.push(pathsToTry[i]); } + } - return paths; + return paths; } -function getApiBinaryPaths(root_path) { - var paths = []; - var files = []; - var paths_to_try = [ +function getApiBinaryPaths(rootPath) { + const paths = []; + const pathsToTry = [ + { + path: path.join(rootPath, 'build'), + label: null, + }, + { + path: path.join(rootPath, 'build', '32'), + label: '32 bit', + }, + { + path: path.join(rootPath, 'build', '64'), + label: '64 bit', + }, + ]; + + for (let i = 0; i < pathsToTry.length; ++i) { + let files = []; + if (os.platform() === 'win32') { + pathsToTry[i].path = path.join(pathsToTry[i].path, 'Release'); + + files = [ { - path: path.join(root_path, 'build'), - label: null + name: 'loot32.dll', + label: '32 bit', }, { - path: path.join(root_path, 'build', '32'), - label: '32 bit' + name: 'loot64.dll', + label: '64 bit', + }, + ]; + } else { + files = [ + { + name: 'libloot32.so', + label: '32 bit', }, { - path: path.join(root_path, 'build', '64'), - label: '64 bit' - } - ]; - - for (var i = 0; i < paths_to_try.length; ++i) { - if (os.platform() == 'win32') { - paths_to_try[i].path = path.join(paths_to_try[i].path, 'Release'); - - files = [ - { - name: 'loot32.dll', - label: '32 bit' - }, - { - name: 'loot64.dll', - label: '64 bit' - } - ]; - } else { - files = [ - { - name: 'libloot32.so', - label: '32 bit' - }, - { - name: 'libloot64.so', - label: '64 bit' - } - ]; - } - - for (var j = 0; j < files.length; ++j) { - if (fileExists(path.join(paths_to_try[i].path, files[j].name))) { - paths_to_try[i].path = path.join(paths_to_try[i].path, files[j].name); - paths_to_try[i].label = files[j].label; - paths.push(paths_to_try[i]); - break; - } - } + name: 'libloot64.so', + label: '64 bit', + }, + ]; } - return paths; + for (let j = 0; j < files.length; ++j) { + if (fileExists(path.join(pathsToTry[i].path, files[j].name))) { + pathsToTry[i].path = path.join(pathsToTry[i].path, files[j].name); + pathsToTry[i].label = files[j].label; + paths.push(pathsToTry[i]); + break; + } + } + } + + return paths; } module.exports.fileExists = fileExists; diff --git a/scripts/vulcanize.js b/scripts/vulcanize.js index 4fc5a950..38caf50b 100755 --- a/scripts/vulcanize.js +++ b/scripts/vulcanize.js @@ -1,43 +1,43 @@ #!/usr/bin/env node // Build the UI's index.html file. Takes one argument, which is the path to the // repository's root. -var child_process = require('child_process'); -var path = require('path'); -var fs = require('fs'); -var os = require('os'); -var helpers = require('./helpers'); +'use strict'; +const childProcess = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const helpers = require('./helpers'); -if (process.argv.length < 3) { - var root_path = '.'; -} else { - var root_path = process.argv[2]; +let rootPath = '.'; +if (process.argv.length > 2) { + rootPath = process.argv[2]; } -var release_paths = helpers.getAppReleasePaths(root_path); +const releasePaths = helpers.getAppReleasePaths(rootPath); -for (var i = 0; i < release_paths.length; ++i) { - var output_path = path.join(release_paths[i].path, 'resources', 'ui'); +for (let i = 0; i < releasePaths.length; ++i) { + const outputPath = path.join(releasePaths[i].path, 'resources', 'ui'); - // Makes sure output directory exists first. - try { - fs.mkdirSync(output_path); - } catch (e) { - if (e.code != 'EEXIST') { - console.log(e); - } + // Makes sure output directory exists first. + try { + fs.mkdirSync(outputPath); + } catch (e) { + if (e.code !== 'EEXIST') { + console.log(e); } + } - var vulcanize = path.join(root_path, 'node_modules', '.bin', 'vulcanize'); - if (os.platform() == 'win32') { - vulcanize += '.cmd'; - } + let vulcanize = path.join(rootPath, 'node_modules', '.bin', 'vulcanize'); + if (os.platform() === 'win32') { + vulcanize += '.cmd'; + } - child_process.execFileSync(vulcanize, [ - '--inline', - '--config', - path.join(root_path, 'scripts', 'vulcanize.config.json'), - '-o', - path.join(output_path, 'index.html'), - path.join(root_path, 'src', 'gui', 'html', 'index.html') - ]); + childProcess.execFileSync(vulcanize, [ + '--inline', + '--config', + path.join(rootPath, 'scripts', 'vulcanize.config.json'), + '-o', + path.join(outputPath, 'index.html'), + path.join(rootPath, 'src', 'gui', 'html', 'index.html'), + ]); } From e31c97b3e9a40eb50fdaeb6fe16ce000c0d002dd Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 13 Dec 2015 13:12:17 +0000 Subject: [PATCH 04/30] Run JavaScript tests on Travis Using Sauce Labs and Grunt. Also install runtime dependencies using Bower to get the Jed package that's used in testing, and save the Bower version resolutions for Travis so that step can be automated. --- .travis.yml | 20 ++++++++++++++++- bower.json | 17 ++++++++++++++ gruntfile.js | 39 +++++++++++++++++++++++++++++++++ package.json | 8 +++++++ src/tests/gui/html/js/test.html | 23 +++++++++++++++++-- 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 gruntfile.js diff --git a/.travis.yml b/.travis.yml index 4b10c77c..7e9dab43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ addons: - libssl-dev - gcc-5 - g++-5 + sauce_connect: true install: # Use GCC 5. @@ -27,6 +28,16 @@ install: # Run install step script to download and build dependencies. - chmod +x scripts/install-step.travis.sh - ./scripts/install-step.travis.sh + # Install the latest stable Node.js. The default Node.js version is too old + # for some of the JavaScript syntax used. + - npm install -g nvm + - nvm install node + # Install Node.js dependencies + - npm install + # Add local binary path to PATH + - export PATH="./node_modules/.bin:$PATH" + # Install runtime dependencies using Bower + - bower install before_script: # Build Google Test @@ -46,7 +57,9 @@ before_script: # Travis machines are 64 bit, and the dependencies use dynamic linking. - cmake .. -DPROJECT_ARCH=64 -DPROJECT_STATIC_RUNTIME=OFF -DBUILD_SHARED_LIBS=OFF -DGTEST_ROOT=../../googletest-release-1.7.0 -script: make tests && ./tests +script: + - npm test + - make tests && ./tests before_deploy: # Build the metadata validator @@ -69,3 +82,8 @@ notifications: - "chat.freenode.net#loot" use_notice: true skip_join: true + +env: + global: + - secure: OWgDkff/b+0eKk/P75rBnecaPDv1E/gMkCzsTUUtofmzj+sttpMUsh8VOVUvckGs5m3fRwRhc/t99gR4FuDtMOVE5JZ8CqZn7tcv2wNAspQ613pS0nWyIX/IUbyF+gjtPW2dWy81hKVtTP4yFvUn1Gk+JRjJLSyxM53Xt6oTOlY= + - secure: XpGbqizjWfaGiWDkrcW4UhSW8ijm00jgSo2qkthza3QmHQ3C0Vye3gQjjITCq+y6izQTPa46fxaI+Ozligyh4aQ1G8SpCa77tXMBfvo31led+s81FlAOSVGDJ/2eDOoawFuj5dTKTPR1zLniDYH8AqG8EDZuLoK6fhx7CchU2Yw= diff --git a/bower.json b/bower.json index 1a57f4da..6895c6a3 100644 --- a/bower.json +++ b/bower.json @@ -37,5 +37,22 @@ "jed-gettext-parser": "~1.0.0", "paper-toggle-button": "Polymer/paper-toggle-button#~0.5.4", "core-overlay": "WrinklyNinja/core-overlay#fix-focus-self-as-next" + }, + "resolutions": { + "polymer": "^0.5", + "core-icons": "^0.5", + "core-icon": "^0.5", + "core-collapse": "^0.5", + "core-focusable": "^0.5", + "core-icon-button": "^0.5", + "core-resizable": "^0.5", + "core-iconset-svg": "^0.5", + "core-dropdown": "null-dimensions-positions-fix", + "core-transition": "^0.5", + "core-overlay": "fix-focus-self-as-next", + "webcomponentsjs": "^0.7.18", + "core-iconset": "^0.5", + "core-selection": "^0.5", + "core-meta": "^0.5" } } diff --git a/gruntfile.js b/gruntfile.js new file mode 100644 index 00000000..137560c0 --- /dev/null +++ b/gruntfile.js @@ -0,0 +1,39 @@ +'use strict'; +module.exports = (grunt) => { + grunt.initConfig({ + connect: { + server: { + options: { + base: '', + port: 9999, + }, + }, + }, + 'saucelabs-mocha': { + all: { + options: { + urls: ['http://127.0.0.1:9999/src/tests/gui/html/js/test.html'], + build: process.env.TRAVIS_JOB_ID, + throttled: 3, + browsers: [{ + browserName: 'chrome', + platform: 'Windows 10', + version: '47', + }], + testname: 'LOOT UI JS tests', + }, + }, + }, + watch: {}, + }); + + // Loading dependencies + for (const key in grunt.file.readJSON('package.json').devDependencies) { + if (key !== 'grunt' && key.indexOf('grunt') === 0) { + grunt.loadNpmTasks(key); + } + } + + grunt.registerTask('dev', ['connect', 'watch']); + grunt.registerTask('test', ['connect', 'saucelabs-mocha']); +}; diff --git a/package.json b/package.json index c7133252..609731bb 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,16 @@ "eslint": "^1.10.3", "eslint-config-airbnb": "^2.0.0", "fs-extra": "^0.26.2", + "grunt": "^0.4.5", + "grunt-cli": "^0.1.13", + "grunt-contrib-connect": "^0.11.2", + "grunt-contrib-watch": "^0.6.1", + "grunt-saucelabs": "^8.6.2", "mocha": "^2.3.4", "should": "^8.0.1", "vulcanize": "^0.7.11" + }, + "scripts": { + "test": "grunt test" } } diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html index a5d71d3c..7d52378c 100644 --- a/src/tests/gui/html/js/test.html +++ b/src/tests/gui/html/js/test.html @@ -11,8 +11,27 @@ From ce1b53d5d5e46796e60ea06a5899c60f08cd36b5 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Mon, 14 Dec 2015 11:15:18 +0000 Subject: [PATCH 05/30] Reimplement UI translation JS code Add a new Translator class that handles the loading of translation data and provides a simpler translate() API. Also refactor l10n.js to export a single function for translating static text, splitting it up into separate function calls for each different area of the UI with strings to translate. --- src/gui/html/index.html | 3 +- src/gui/html/js/events.js | 32 +- src/gui/html/js/filters.js | 4 +- src/gui/html/js/helpers.js | 9 +- src/gui/html/js/init.js | 14 +- src/gui/html/js/l10n.js | 567 +++++++++++------------ src/gui/html/js/loot.js | 4 +- src/gui/html/js/translator.js | 74 +++ src/tests/gui/html/js/test.html | 5 + src/tests/gui/html/js/test_translator.js | 96 ++++ 10 files changed, 490 insertions(+), 318 deletions(-) create mode 100644 src/gui/html/js/translator.js create mode 100644 src/tests/gui/html/js/test_translator.js diff --git a/src/gui/html/index.html b/src/gui/html/index.html index ecbd6486..a3a9873b 100644 --- a/src/gui/html/index.html +++ b/src/gui/html/index.html @@ -315,9 +315,10 @@ - + + diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index ba492c46..e9bf8bf8 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -53,7 +53,7 @@ function onChangeGame(evt) { } /* Send off a CEF query with the folder name of the new game. */ - showProgress(l10n.jed.translate('Loading game data...').fetch()); + showProgress(loot.l10n.translate('Loading game data...')); var request = JSON.stringify({ name: 'changeGame', args: [ @@ -123,14 +123,14 @@ function updateMasterlistNoProgress() { /* Hack to stop cards overlapping. */ document.getElementById('main').lastElementChild.updateSize(); - toast(l10n.jed.translate('Masterlist updated to revision %s.').fetch(loot.game.masterlist.revision)); + toast(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision)); } else { - toast(l10n.jed.translate('No masterlist update was necessary.').fetch()); + toast(loot.l10n.translate('No masterlist update was necessary.')); } }).catch(processCefError); } function onUpdateMasterlist(evt) { - showProgress(l10n.jed.translate('Updating masterlist...').fetch()); + showProgress(loot.l10n.translate('Updating masterlist...')); updateMasterlistNoProgress().then(function(result){ closeProgressDialog(); }).catch(processCefError); @@ -154,7 +154,7 @@ function onSortPlugins(evt) { promise = promise.then(updateMasterlistNoProgress()); } promise.then(function(){ - showProgress(l10n.jed.translate('Sorting plugins...').fetch()); + showProgress(loot.l10n.translate('Sorting plugins...')); loot.query('sortPlugins').then(JSON.parse).then(function(result){ if (result) { loot.game.oldLoadOrder = loot.game.plugins; @@ -256,7 +256,7 @@ function onRedatePlugins(evt) { return; } - showMessageDialog(l10n.jed.translate('Redate Plugins?').fetch(), l10n.jed.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?').fetch(), l10n.jed.translate('Redate').fetch(), function(result){ + showMessageDialog(loot.l10n.translate('Redate Plugins?'), loot.l10n.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?'), loot.l10n.translate('Redate'), function(result){ if (result) { loot.query('redatePlugins').then(function(response){ toast('Plugins were successfully redated.'); @@ -265,7 +265,7 @@ function onRedatePlugins(evt) { }); } function onClearAllMetadata(evt) { - showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from all plugins?').fetch(), l10n.jed.translate('Clear').fetch(), function(result){ + showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from all plugins?'), loot.l10n.translate('Clear'), function(result){ if (result) { loot.query('clearAllMetadata').then(JSON.parse).then(function(result){ if (result) { @@ -287,7 +287,7 @@ function onClearAllMetadata(evt) { } }); - toast(l10n.jed.translate('All user-added metadata has been cleared.').fetch()); + toast(loot.l10n.translate('All user-added metadata has been cleared.')); } }).catch(processCefError); } @@ -344,7 +344,7 @@ function onCopyContent(evt) { }); loot.query(request).then(function(){ - toast(l10n.jed.translate("LOOT's content has been copied to the clipboard.").fetch()); + toast(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); }).catch(processCefError); } function onCopyLoadOrder(evt) { @@ -366,7 +366,7 @@ function onCopyLoadOrder(evt) { }); loot.query(request).then(function(){ - toast(l10n.jed.translate("The load order has been copied to the clipboard.").fetch()); + toast(loot.l10n.translate("The load order has been copied to the clipboard.")); }).catch(processCefError); } function onSwitchSidebarTab(evt) { @@ -577,11 +577,11 @@ function onCopyMetadata(evt) { }); loot.query(request).then(function(){ - toast(l10n.jed.translate('The metadata for "%s" has been copied to the clipboard.').fetch(evt.target.getName())); + toast(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); }).catch(processCefError); } function onClearMetadata(evt) { - showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from "%s"?').fetch(evt.target.getName()), l10n.jed.translate('Clear').fetch(), function(result){ + showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){ if (result) { var request = JSON.stringify({ name: 'clearPluginMetadata', @@ -607,7 +607,7 @@ function onClearMetadata(evt) { break; } } - toast(l10n.jed.translate('The user-added metadata for "%s" has been cleared.').fetch(evt.target.getName())); + toast(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName())); /* Now perform search again. If there is no current search, this won't do anything. */ document.getElementById('searchBar').search(); @@ -630,9 +630,9 @@ function onSidebarClick(evt) { } function onQuit(evt) { if (!document.getElementById('applySortButton').classList.contains('hidden')) { - handleUnappliedChangesClose(l10n.jed.translate('sorted load order').fetch()); + handleUnappliedChangesClose(loot.l10n.translate('sorted load order')); } else if (document.body.hasAttribute('data-editors')) { - handleUnappliedChangesClose(l10n.jed.translate('metadata edits').fetch()); + handleUnappliedChangesClose(loot.l10n.translate('metadata edits')); } else { window.close(); } @@ -643,7 +643,7 @@ function onJumpToGeneralInfo(evt) { } function onContentRefresh(evt) { /* Send a query for updated load order and plugin header info. */ - showProgress(l10n.jed.translate('Refreshing data...').fetch()); + showProgress(loot.l10n.translate('Refreshing data...')); loot.query('getGameData').then(function(result){ /* Parse the data sent from C++. */ try { diff --git a/src/gui/html/js/filters.js b/src/gui/html/js/filters.js index febd8971..dcebfc87 100644 --- a/src/gui/html/js/filters.js +++ b/src/gui/html/js/filters.js @@ -118,7 +118,7 @@ var filters = { doNotCleanFilter: function(message) { if (document.getElementById('hideDoNotCleanMessages').checked) { - return message.content[0].str.indexOf(l10n.jed.translate("Do not clean").fetch()) == -1; + return message.content[0].str.indexOf(loot.l10n.translate("Do not clean")) == -1; } else { return true; } @@ -157,7 +157,7 @@ function getConflictingPluginsFromFilter() { ] }); - showProgress(l10n.jed.translate('Checking if plugins have been loaded...').fetch()); + showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); return loot.query(request).then(JSON.parse).then(function(result){ if (result) { diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index d5231983..086f3f5d 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -4,7 +4,7 @@ function processCefError(err) { promise errors, not just CEF errors. */ console.log(err.stack); closeProgressDialog(); - showMessageBox(l10n.jed.translate('Error').fetch(), err.message); + showMessageBox(loot.l10n.translate('Error'), err.message); } function showElement(element) { @@ -24,13 +24,13 @@ function toast(text) { } function showMessageDialog(title, text, positiveText, closeCallback) { var dialog = document.createElement('loot-message-dialog'); - dialog.setButtonText(positiveText, l10n.jed.translate('Cancel').fetch()); + dialog.setButtonText(positiveText, loot.l10n.translate('Cancel')); dialog.showModal(title, text, closeCallback); document.body.appendChild(dialog); } function showMessageBox(title, text) { var dialog = document.createElement('loot-message-dialog'); - dialog.setButtonText(l10n.jed.translate('OK').fetch()); + dialog.setButtonText(loot.l10n.translate('OK')); dialog.showModal(title, text); document.body.appendChild(dialog); } @@ -51,7 +51,7 @@ function closeProgressDialog() { } } function handleUnappliedChangesClose(change) { - showMessageDialog('', l10n.jed.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?').fetch(change), l10n.jed.translate('Quit').fetch(), function(result){ + showMessageDialog('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), function(result){ if (result) { /* Cancel any sorting and close any editors. Cheat by sending a cancelSort query for as many times as necessary. */ @@ -70,4 +70,3 @@ function handleUnappliedChangesClose(change) { } }); } - diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index dc650cb0..0b8d618e 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -35,7 +35,7 @@ function initVars() { if (loot.version.length > pos + 1) { document.getElementById('LOOTBuild').textContent = loot.version.substring(pos + 1); } else { - document.getElementById('LOOTBuild').textContent = l10n.jed.translate('unknown').fetch(); + document.getElementById('LOOTBuild').textContent = loot.l10n.translate('unknown'); } loot.version = loot.version.substring(0, pos); @@ -139,12 +139,12 @@ function initVars() { console.log('getSettings response: ' + results[2]); } }).then(function(){ - return l10n.getJedInstance(loot.settings.language).then(function(jed){ - l10n.translateStaticText(jed); - l10n.jed = jed; - - /* Also need to update the settings UI. */ - loot.updateSettingsUI(); + /* Translate static text. */ + loot.l10n = new loot.Translator(loot.settings.language); + loot.l10n.load().then(() => { + loot.translateStaticText(loot.l10n); + /* Also need to update the settings UI. */ + loot.updateSettingsUI(); }).catch(processCefError); }).then(function(){ if (result) { diff --git a/src/gui/html/js/l10n.js b/src/gui/html/js/l10n.js index 65aa32dc..80eeebb7 100644 --- a/src/gui/html/js/l10n.js +++ b/src/gui/html/js/l10n.js @@ -1,334 +1,331 @@ 'use strict'; -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['bower_components/Jed/jed', 'bower_components/jed-gettext-parser/jedGettextParser'], factory); +(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.translateStaticText = factory(); + } +}(this, () => { + function translatePluginCardTemplate(l10n) { + /* Plugin card template. */ + let pluginCard = document.querySelector('link[rel="import"][href$="loot-plugin-card.html"]'); + if (pluginCard) { + pluginCard = pluginCard.import.querySelector('template').content; } else { - // Browser globals - root.l10n = factory(root.Jed, root.jedGettextParser); + pluginCard = document.querySelector('polymer-element[name="loot-plugin-card"]').querySelector('template').content; } -}(this, function (jed, jedGettextParser) { - var defaultData = { - "messages": { - "": { - "domain" : "messages", - "lang" : "en", - "plural_forms" : "nplurals=2; plural=(n != 1);" - } - } - }; + pluginCard.getElementById('activeTick').setAttribute('label', l10n.translate('Active Plugin')); + pluginCard.getElementById('isMaster').setAttribute('label', l10n.translate('Master File')); + pluginCard.getElementById('emptyPlugin').setAttribute('label', l10n.translate('Empty Plugin')); + pluginCard.getElementById('loadsArchive').setAttribute('label', l10n.translate('Loads Archive')); + pluginCard.getElementById('hasUserEdits').setAttribute('label', l10n.translate('Has User Metadata')); - return { + pluginCard.getElementById('showOnlyConflicts').previousElementSibling.textContent = l10n.translate('Show Only Conflicts'); + pluginCard.getElementById('editMetadata').lastChild.textContent = l10n.translate('Edit Metadata'); + pluginCard.getElementById('copyMetadata').lastChild.textContent = l10n.translate('Copy Metadata'); + pluginCard.getElementById('clearMetadata').lastChild.textContent = l10n.translate('Clear User Metadata'); + } - loadLocaleData: function(locale) { - if (locale == 'en') { - return new Promise(function(resolve, reject){ - resolve(defaultData); - }); - } + function translatePluginEditorTemplate(l10n) { + /* Plugin editor template. */ + let pluginEditor = document.querySelector('link[rel="import"][href$="loot-plugin-card.html"]'); + if (pluginEditor) { + pluginEditor = pluginEditor.import.querySelector('link[rel="import"][href$="loot-plugin-editor.html"]').import.querySelector('template').content; + } else { + pluginEditor = document.querySelector('polymer-element[name="loot-plugin-editor"]').querySelector('template').content; + } - var url = 'loot://l10n/' + locale + '/LC_MESSAGES/loot.mo'; + pluginEditor.getElementById('activeTick').setAttribute('label', l10n.translate('Active Plugin')); + pluginEditor.getElementById('isMaster').setAttribute('label', l10n.translate('Master File')); + pluginEditor.getElementById('emptyPlugin').setAttribute('label', l10n.translate('Empty Plugin')); + pluginEditor.getElementById('loadsArchive').setAttribute('label', l10n.translate('Loads Archive')); - return new Promise(function(resolve, reject){ - var xhr = new XMLHttpRequest(); - xhr.open('GET', url); - xhr.responseType = 'arraybuffer'; - xhr.addEventListener('readystatechange', function(evt){ - if (evt.target.readyState == 4) { - /* Status is 0 for local file URL loading. */ - if (evt.target.status >= 200 && evt.target.status < 400) { - resolve(jedGettextParser.mo.parse(evt.target.response)); - } else { - reject(new Error('XHR Error')); - } - } - }, false); - xhr.send(); - }); - }, + pluginEditor.getElementById('enableEdits').previousElementSibling.textContent = l10n.translate('Enable Edits'); + pluginEditor.getElementById('globalPriority').parentElement.parentElement.setAttribute('label', l10n.translate('Global priorities are compared against all other plugins. Normal priorities are compared against only conflicting plugins.')); + pluginEditor.getElementById('globalPriority').previousElementSibling.textContent = l10n.translate('Global Priority'); + pluginEditor.getElementById('priorityValue').parentElement.previousElementSibling.textContent = l10n.translate('Priority Value'); - translateStaticText: function(l10n) { - /* Plugin card template. */ - var pluginCard = document.querySelector('link[rel="import"][href$="loot-plugin-card.html"]'); - if (pluginCard) { - pluginCard = pluginCard.import.querySelector('template').content; - } else { - pluginCard = document.querySelector('polymer-element[name="loot-plugin-card"]').querySelector('template').content; - } + pluginEditor.getElementById('tableTabs').querySelector('[data-for=main]').textContent = l10n.translate('Main'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=loadAfter]').textContent = l10n.translate('Load After'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=req]').textContent = l10n.translate('Requirements'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=inc]').textContent = l10n.translate('Incompatibilities'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=message]').textContent = l10n.translate('Messages'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=tags]').textContent = l10n.translate('Bash Tags'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=dirty]').textContent = l10n.translate('Dirty Info'); + pluginEditor.getElementById('tableTabs').querySelector('[data-for=locations]').textContent = l10n.translate('Locations'); - pluginCard.getElementById('activeTick').setAttribute('label', l10n.translate("Active Plugin").fetch()); - pluginCard.getElementById('isMaster').setAttribute('label', l10n.translate("Master File").fetch()); - pluginCard.getElementById('emptyPlugin').setAttribute('label', l10n.translate("Empty Plugin").fetch()); - pluginCard.getElementById('loadsArchive').setAttribute('label', l10n.translate("Loads Archive").fetch()); - pluginCard.getElementById('hasUserEdits').setAttribute('label', l10n.translate("Has User Metadata").fetch()); + pluginEditor.getElementById('loadAfter').querySelector('th:first-child').textContent = l10n.translate('Filename'); + pluginEditor.getElementById('loadAfter').querySelector('th:nth-child(2)').textContent = l10n.translate('Display Name'); + pluginEditor.getElementById('loadAfter').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition'); - pluginCard.getElementById('showOnlyConflicts').previousElementSibling.textContent = l10n.translate("Show Only Conflicts").fetch(); - pluginCard.getElementById('editMetadata').lastChild.textContent = l10n.translate("Edit Metadata").fetch(); - pluginCard.getElementById('copyMetadata').lastChild.textContent = l10n.translate("Copy Metadata").fetch(); - pluginCard.getElementById('clearMetadata').lastChild.textContent = l10n.translate("Clear User Metadata").fetch(); + pluginEditor.getElementById('req').querySelector('th:first-child').textContent = l10n.translate('Filename'); + pluginEditor.getElementById('req').querySelector('th:nth-child(2)').textContent = l10n.translate('Display Name'); + pluginEditor.getElementById('req').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition'); - /* Plugin editor template. */ - var pluginEditor = document.querySelector('link[rel="import"][href$="loot-plugin-card.html"]'); - if (pluginEditor) { - pluginEditor = pluginEditor.import.querySelector('link[rel="import"][href$="loot-plugin-editor.html"]').import.querySelector('template').content; - } else { - pluginEditor = document.querySelector('polymer-element[name="loot-plugin-editor"]').querySelector('template').content; - } + pluginEditor.getElementById('inc').querySelector('th:first-child').textContent = l10n.translate('Filename'); + pluginEditor.getElementById('inc').querySelector('th:nth-child(2)').textContent = l10n.translate('Display Name'); + pluginEditor.getElementById('inc').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition'); - pluginEditor.getElementById('activeTick').setAttribute('label', l10n.translate("Active Plugin").fetch()); - pluginEditor.getElementById('isMaster').setAttribute('label', l10n.translate("Master File").fetch()); - pluginEditor.getElementById('emptyPlugin').setAttribute('label', l10n.translate("Empty Plugin").fetch()); - pluginEditor.getElementById('loadsArchive').setAttribute('label', l10n.translate("Loads Archive").fetch()); + pluginEditor.getElementById('message').querySelector('th:first-child').textContent = l10n.translate('Type'); + pluginEditor.getElementById('message').querySelector('th:nth-child(2)').textContent = l10n.translate('Content'); + pluginEditor.getElementById('message').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition'); + pluginEditor.getElementById('message').querySelector('th:nth-child(4)').textContent = l10n.translate('Language'); - pluginEditor.getElementById('enableEdits').previousElementSibling.textContent = l10n.translate("Enable Edits").fetch(); - pluginEditor.getElementById('globalPriority').parentElement.parentElement.setAttribute('label', l10n.translate("Global priorities are compared against all other plugins. Normal priorities are compared against only conflicting plugins.").fetch()); - pluginEditor.getElementById('globalPriority').previousElementSibling.textContent = l10n.translate("Global Priority").fetch(); - pluginEditor.getElementById('priorityValue').parentElement.previousElementSibling.textContent = l10n.translate("Priority Value").fetch(); + pluginEditor.getElementById('tags').querySelector('th:first-child').textContent = l10n.translate('Add/Remove'); + pluginEditor.getElementById('tags').querySelector('th:nth-child(2)').textContent = l10n.translate('Bash Tag'); + pluginEditor.getElementById('tags').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition'); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=main]').textContent = l10n.translate("Main").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=loadAfter]').textContent = l10n.translate("Load After").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=req]').textContent = l10n.translate("Requirements").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=inc]').textContent = l10n.translate("Incompatibilities").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=message]').textContent = l10n.translate("Messages").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=tags]').textContent = l10n.translate("Bash Tags").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=dirty]').textContent = l10n.translate("Dirty Info").fetch(); - pluginEditor.getElementById('tableTabs').querySelector('[data-for=locations]').textContent = l10n.translate("Locations").fetch(); + pluginEditor.getElementById('dirty').querySelector('th:first-child').textContent = l10n.translate('CRC'); + pluginEditor.getElementById('dirty').querySelector('th:nth-child(2)').textContent = l10n.translate('ITM Count'); + pluginEditor.getElementById('dirty').querySelector('th:nth-child(3)').textContent = l10n.translate('Deleted References'); + pluginEditor.getElementById('dirty').querySelector('th:nth-child(4)').textContent = l10n.translate('Deleted Navmeshes'); + pluginEditor.getElementById('dirty').querySelector('th:nth-child(5)').textContent = l10n.translate('Cleaning Utility'); - pluginEditor.getElementById('loadAfter').querySelector('th:first-child').textContent = l10n.translate("Filename").fetch(); - pluginEditor.getElementById('loadAfter').querySelector('th:nth-child(2)').textContent = l10n.translate("Display Name").fetch(); - pluginEditor.getElementById('loadAfter').querySelector('th:nth-child(3)').textContent = l10n.translate("Condition").fetch(); + pluginEditor.getElementById('locations').querySelector('th:first-child').textContent = l10n.translate('URL'); + pluginEditor.getElementById('locations').querySelector('th:nth-child(2)').textContent = l10n.translate('Name'); - pluginEditor.getElementById('req').querySelector('th:first-child').textContent = l10n.translate("Filename").fetch(); - pluginEditor.getElementById('req').querySelector('th:nth-child(2)').textContent = l10n.translate("Display Name").fetch(); - pluginEditor.getElementById('req').querySelector('th:nth-child(3)').textContent = l10n.translate("Condition").fetch(); + pluginEditor.getElementById('accept').parentElement.setAttribute('label', l10n.translate('Apply')); + pluginEditor.getElementById('cancel').parentElement.setAttribute('label', l10n.translate('Cancel')); + } - pluginEditor.getElementById('inc').querySelector('th:first-child').textContent = l10n.translate("Filename").fetch(); - pluginEditor.getElementById('inc').querySelector('th:nth-child(2)').textContent = l10n.translate("Display Name").fetch(); - pluginEditor.getElementById('inc').querySelector('th:nth-child(3)').textContent = l10n.translate("Condition").fetch(); + function translatePluginListItemTemplate(l10n) { + /* Plugin List Item Template */ + let pluginItem = document.querySelector('link[rel="import"][href$="loot-plugin-item.html"]'); + if (pluginItem) { + pluginItem = pluginItem.import.querySelector('template').content; + } else { + pluginItem = document.querySelector('polymer-element[name="loot-plugin-item"]').querySelector('template').content; + } + pluginItem.querySelector('#secondary core-tooltip').setAttribute('label', l10n.translate('Global Priority')); + pluginItem.getElementById('hasUserEditsTooltip').textContent = l10n.translate('Has User Metadata'); + pluginItem.getElementById('editorIsOpenTooltip').textContent = l10n.translate('Editor Is Open'); + } - pluginEditor.getElementById('message').querySelector('th:first-child').textContent = l10n.translate("Type").fetch(); - pluginEditor.getElementById('message').querySelector('th:nth-child(2)').textContent = l10n.translate("Content").fetch(); - pluginEditor.getElementById('message').querySelector('th:nth-child(3)').textContent = l10n.translate("Condition").fetch(); - pluginEditor.getElementById('message').querySelector('th:nth-child(4)').textContent = l10n.translate("Language").fetch(); + function translateFileRowTemplate(l10n) { + /* File row template */ + let fileRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (fileRow) { + fileRow = fileRow.import.querySelector('#fileRow').content; + } else { + fileRow = document.querySelector('#fileRow').content; + } + fileRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A filename is required.')); + fileRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row')); + } - pluginEditor.getElementById('tags').querySelector('th:first-child').textContent = l10n.translate("Add/Remove").fetch(); - pluginEditor.getElementById('tags').querySelector('th:nth-child(2)').textContent = l10n.translate("Bash Tag").fetch(); - pluginEditor.getElementById('tags').querySelector('th:nth-child(3)').textContent = l10n.translate("Condition").fetch(); + function translateMessageRowTemplate(l10n) { + /* Message row template */ + let messageRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (messageRow) { + messageRow = messageRow.import.querySelector('#messageRow').content; + } else { + messageRow = document.querySelector('#messageRow').content; + } + messageRow.querySelector('.type').children[0].textContent = l10n.translate('Note'); + messageRow.querySelector('.type').children[1].textContent = l10n.translate('Warning'); + messageRow.querySelector('.type').children[2].textContent = l10n.translate('Error'); + messageRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A content string is required.')); + messageRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row')); + } - pluginEditor.getElementById('dirty').querySelector('th:first-child').textContent = l10n.translate("CRC").fetch(); - pluginEditor.getElementById('dirty').querySelector('th:nth-child(2)').textContent = l10n.translate("ITM Count").fetch(); - pluginEditor.getElementById('dirty').querySelector('th:nth-child(3)').textContent = l10n.translate("Deleted References").fetch(); - pluginEditor.getElementById('dirty').querySelector('th:nth-child(4)').textContent = l10n.translate("Deleted Navmeshes").fetch(); - pluginEditor.getElementById('dirty').querySelector('th:nth-child(5)').textContent = l10n.translate("Cleaning Utility").fetch(); + function translateTagRowTemplate(l10n) { + /* Tag row template */ + let tagRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (tagRow) { + tagRow = tagRow.import.querySelector('#tagRow').content; + } else { + tagRow = document.querySelector('#tagRow').content; + } + tagRow.querySelector('.type').children[0].textContent = l10n.translate('Add'); + tagRow.querySelector('.type').children[1].textContent = l10n.translate('Remove'); + tagRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A name is required.')); + tagRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row')); + } - pluginEditor.getElementById('locations').querySelector('th:first-child').textContent = l10n.translate("URL").fetch(); - pluginEditor.getElementById('locations').querySelector('th:nth-child(2)').textContent = l10n.translate("Name").fetch(); + function translateDirtyInfoRowTemplate(l10n) { + /* Dirty Info row template */ + let dirtyInfoRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (dirtyInfoRow) { + dirtyInfoRow = dirtyInfoRow.import.querySelector('#dirtyInfoRow').content; + } else { + dirtyInfoRow = document.querySelector('#dirtyInfoRow').content; + } - pluginEditor.getElementById('accept').parentElement.setAttribute('label', l10n.translate("Apply").fetch()); - pluginEditor.getElementById('cancel').parentElement.setAttribute('label', l10n.translate("Cancel").fetch()); + dirtyInfoRow.querySelector('loot-validated-input.crc').setAttribute('error', l10n.translate('A CRC is required.')); + dirtyInfoRow.querySelector('loot-validated-input.itm').setAttribute('error', l10n.translate('Values must be integers.')); + dirtyInfoRow.querySelector('loot-validated-input.udr').setAttribute('error', l10n.translate('Values must be integers.')); + dirtyInfoRow.querySelector('loot-validated-input.nav').setAttribute('error', l10n.translate('Values must be integers.')); + dirtyInfoRow.querySelector('loot-validated-input.util').setAttribute('error', l10n.translate('A utility name is required.')); + dirtyInfoRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row')); + } - /* Plugin List Item Template */ - var pluginItem = document.querySelector('link[rel="import"][href$="loot-plugin-item.html"]'); - if (pluginItem) { - pluginItem = pluginItem.import.querySelector('template').content; - } else { - pluginItem = document.querySelector('polymer-element[name="loot-plugin-item"]').querySelector('template').content; - } - pluginItem.querySelector('#secondary core-tooltip').setAttribute('label', l10n.translate("Global Priority").fetch()); - pluginItem.getElementById('hasUserEditsTooltip').textContent = l10n.translate("Has User Metadata").fetch(); - pluginItem.getElementById('editorIsOpenTooltip').textContent = l10n.translate("Editor Is Open").fetch(); + function translateLocationRowTemplate(l10n) { + /* Location row template */ + let locationRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (locationRow) { + locationRow = locationRow.import.querySelector('#locationRow').content; + } else { + locationRow = document.querySelector('#locationRow').content; + } + locationRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A link is required.')); + locationRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row')); + } - /* File row template */ - var fileRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (fileRow) { - fileRow = fileRow.import.querySelector('#fileRow').content; - } else { - fileRow = document.querySelector('#fileRow').content; - } - fileRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate("A filename is required.").fetch()); - fileRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Delete Row").fetch()); + function translateGameRowTemplate(l10n) { + /* Game row template */ + let gameRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (gameRow) { + gameRow = gameRow.import.querySelector('#gameRow').content; + } else { + gameRow = document.querySelector('#gameRow').content; + } + gameRow.querySelector('loot-validated-input.name').setAttribute('error', l10n.translate('A name is required.')); + gameRow.querySelector('loot-validated-input.folder').setAttribute('error', l10n.translate('A folder is required.')); + gameRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row')); + } - /* Message row template */ - var messageRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (messageRow) { - messageRow = messageRow.import.querySelector('#messageRow').content; - } else { - messageRow = document.querySelector('#messageRow').content; - } - messageRow.querySelector('.type').children[0].textContent = l10n.translate("Note").fetch(); - messageRow.querySelector('.type').children[1].textContent = l10n.translate("Warning").fetch(); - messageRow.querySelector('.type').children[2].textContent = l10n.translate("Error").fetch(); - messageRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate("A content string is required.").fetch()); - messageRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Delete Row").fetch()); + function translateNewRowTemplate(l10n) { + /* New row template */ + let newRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (newRow) { + newRow = newRow.import.querySelector('#newRow').content; + } else { + newRow = document.querySelector('#newRow').content; + } + newRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Add New Row')); + } - /* Tag row template */ - var tagRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (tagRow) { - tagRow = tagRow.import.querySelector('#tagRow').content; - } else { - tagRow = document.querySelector('#tagRow').content; - } - tagRow.querySelector('.type').children[0].textContent = l10n.translate("Add").fetch(); - tagRow.querySelector('.type').children[1].textContent = l10n.translate("Remove").fetch(); - tagRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate("A name is required.").fetch()); - tagRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Delete Row").fetch()); + function translateMainToolbar(l10n) { + /* Main toolbar */ + document.getElementById('jumpToGeneralInfo').parentElement.label = l10n.translate('Jump To General Information'); + document.getElementById('sortButton').parentElement.label = l10n.translate('Sort Plugins'); + document.getElementById('updateMasterlistButton').parentElement.label = l10n.translate('Update Masterlist'); + document.getElementById('applySortButton').textContent = l10n.translate('Apply'); + document.getElementById('cancelSortButton').textContent = l10n.translate('Cancel'); + document.getElementById('showSearch').parentElement.label = l10n.translate('Search Cards'); - /* Dirty Info row template */ - var dirtyInfoRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (dirtyInfoRow) { - dirtyInfoRow = dirtyInfoRow.import.querySelector('#dirtyInfoRow').content; - } else { - dirtyInfoRow = document.querySelector('#dirtyInfoRow').content; - } - dirtyInfoRow.querySelector('loot-validated-input.crc').setAttribute('error', l10n.translate("A CRC is required.").fetch()); - dirtyInfoRow.querySelector('loot-validated-input.itm').setAttribute('error', l10n.translate("Values must be integers.").fetch()); - dirtyInfoRow.querySelector('loot-validated-input.udr').setAttribute('error', l10n.translate("Values must be integers.").fetch()); - dirtyInfoRow.querySelector('loot-validated-input.nav').setAttribute('error', l10n.translate("Values must be integers.").fetch()); - dirtyInfoRow.querySelector('loot-validated-input.util').setAttribute('error', l10n.translate("A utility name is required.").fetch()); - dirtyInfoRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Delete Row").fetch()); + /* Toolbar menu */ + document.getElementById('redatePluginsButton').lastElementChild.textContent = l10n.translate('Redate Plugins'); + document.getElementById('openLogButton').lastElementChild.textContent = l10n.translate('Open Debug Log Location'); + document.getElementById('wipeUserlistButton').lastElementChild.textContent = l10n.translate('Clear All User Metadata'); + document.getElementById('copyLoadOrderButton').lastElementChild.textContent = l10n.translate('Copy Load Order'); + document.getElementById('copyContentButton').lastElementChild.textContent = l10n.translate('Copy Content'); + document.getElementById('refreshContentButton').lastElementChild.textContent = l10n.translate('Refresh Content'); + document.getElementById('helpButton').lastElementChild.textContent = l10n.translate('View Documentation'); + document.getElementById('aboutButton').lastElementChild.textContent = l10n.translate('About'); + document.getElementById('settingsButton').lastElementChild.textContent = l10n.translate('Settings'); + document.getElementById('quitButton').lastElementChild.textContent = l10n.translate('Quit'); - /* Location row template */ - var locationRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (locationRow) { - locationRow = locationRow.import.querySelector('#locationRow').content; - } else { - locationRow = document.querySelector('#locationRow').content; - } - locationRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate("A link is required.").fetch()); - locationRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Delete Row").fetch()); + /* Search bar */ + document.getElementById('searchBar').shadowRoot.getElementById('search').label = l10n.translate('Search cards'); + } - /* Game row template */ - var gameRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (gameRow) { - gameRow = gameRow.import.querySelector('#gameRow').content; - } else { - gameRow = document.querySelector('#gameRow').content; - } - gameRow.querySelector('loot-validated-input.name').setAttribute('error', l10n.translate("A name is required.").fetch()); - gameRow.querySelector('loot-validated-input.folder').setAttribute('error', l10n.translate("A folder is required.").fetch()); - gameRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Delete Row").fetch()); + function translateSidebar(l10n) { + /* Nav items */ + document.getElementById('sidebarTabs').firstElementChild.textContent = l10n.translate('Plugins'); + document.getElementById('sidebarTabs').firstElementChild.nextElementSibling.textContent = l10n.translate('Filters'); + document.getElementById('contentFilter').parentElement.label = l10n.translate('Press Enter or click outside the input to set the filter.'); + document.getElementById('contentFilter').label = l10n.translate('Filter content'); - /* New row template */ - var newRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (newRow) { - newRow = newRow.import.querySelector('#newRow').content; - } else { - newRow = document.querySelector('#newRow').content; - } - newRow.querySelector('core-tooltip').setAttribute('label', l10n.translate("Add New Row").fetch()); + /* Filters */ + document.getElementById('hideVersionNumbers').label = l10n.translate('Hide version numbers'); + document.getElementById('hideCRCs').label = l10n.translate('Hide CRCs'); + document.getElementById('hideBashTags').label = l10n.translate('Hide Bash Tags'); + document.getElementById('hideNotes').label = l10n.translate('Hide notes'); + document.getElementById('hideDoNotCleanMessages').label = l10n.translate('Hide \'Do not clean\' messages'); + document.getElementById('hideAllPluginMessages').label = l10n.translate('Hide all plugin messages'); + document.getElementById('hideInactivePlugins').label = l10n.translate('Hide inactive plugins'); + document.getElementById('hideMessagelessPlugins').label = l10n.translate('Hide messageless plugins'); + document.getElementById('hiddenPluginsTxt').textContent = l10n.translate('Hidden plugins:'); + document.getElementById('hiddenMessagesTxt').textContent = l10n.translate('Hidden messages:'); + } - /* Main toolbar */ - document.getElementById('jumpToGeneralInfo').parentElement.label = l10n.translate("Jump To General Information").fetch(); - document.getElementById('sortButton').parentElement.label = l10n.translate("Sort Plugins").fetch(); - document.getElementById('updateMasterlistButton').parentElement.label = l10n.translate("Update Masterlist").fetch(); - document.getElementById('applySortButton').textContent = l10n.translate("Apply").fetch(); - document.getElementById('cancelSortButton').textContent = l10n.translate("Cancel").fetch(); - document.getElementById('showSearch').parentElement.label = l10n.translate("Search Cards").fetch(); + function translateSummaryCard(l10n) { + /* Summary */ + document.getElementById('summary').firstElementChild.textContent = l10n.translate('General Information'); + document.getElementById('masterlistRevision').previousElementSibling.textContent = l10n.translate('Masterlist Revision'); + document.getElementById('masterlistDate').previousElementSibling.textContent = l10n.translate('Masterlist Date'); + document.getElementById('totalWarningNo').previousElementSibling.textContent = l10n.translate('Warnings'); + document.getElementById('totalErrorNo').previousElementSibling.textContent = l10n.translate('Errors'); + document.getElementById('totalMessageNo').previousElementSibling.textContent = l10n.translate('Total Messages'); + document.getElementById('activePluginNo').previousElementSibling.textContent = l10n.translate('Active Plugins'); + document.getElementById('dirtyPluginNo').previousElementSibling.textContent = l10n.translate('Dirty Plugins'); + document.getElementById('totalPluginNo').previousElementSibling.textContent = l10n.translate('Total Plugins'); + } - /* Toolbar menu */ - document.getElementById('redatePluginsButton').lastElementChild.textContent = l10n.translate("Redate Plugins").fetch(); - document.getElementById('openLogButton').lastElementChild.textContent = l10n.translate("Open Debug Log Location").fetch(); - document.getElementById('wipeUserlistButton').lastElementChild.textContent = l10n.translate("Clear All User Metadata").fetch(); - document.getElementById('copyLoadOrderButton').lastElementChild.textContent = l10n.translate("Copy Load Order").fetch(); - document.getElementById('copyContentButton').lastElementChild.textContent = l10n.translate("Copy Content").fetch(); - document.getElementById('refreshContentButton').lastElementChild.textContent = l10n.translate("Refresh Content").fetch(); - document.getElementById('helpButton').lastElementChild.textContent = l10n.translate("View Documentation").fetch(); - document.getElementById('aboutButton').lastElementChild.textContent = l10n.translate("About").fetch(); - document.getElementById('settingsButton').lastElementChild.textContent = l10n.translate("Settings").fetch(); - document.getElementById('quitButton').lastElementChild.textContent = l10n.translate("Quit").fetch(); + function translateSettingsDialog(l10n) { + /* Settings dialog */ + document.getElementById('settingsDialog').heading = l10n.translate('Settings'); - /* Search bar */ - document.getElementById('searchBar').shadowRoot.getElementById('search').label = l10n.translate("Search cards").fetch(); + const defaultGameSelect = document.getElementById('defaultGameSelect'); + defaultGameSelect.previousElementSibling.textContent = l10n.translate('Default Game'); + defaultGameSelect.firstElementChild.textContent = l10n.translate('Autodetect'); + /* The selected text doesn't update, so force that translation. */ + defaultGameSelect.shadowRoot.querySelector('paper-dropdown-menu').selectedItemLabel = defaultGameSelect.shadowRoot.querySelector('core-menu').selectedItem.textContent; - /* Nav items */ - document.getElementById('sidebarTabs').firstElementChild.textContent = l10n.translate("Plugins").fetch(); - document.getElementById('sidebarTabs').firstElementChild.nextElementSibling.textContent = l10n.translate("Filters").fetch(); - document.getElementById('contentFilter').parentElement.label = l10n.translate("Press Enter or click outside the input to set the filter.").fetch(); - document.getElementById('contentFilter').label = l10n.translate("Filter content").fetch(); + document.getElementById('languageSelect').previousElementSibling.textContent = l10n.translate('Language'); + document.getElementById('languageSelect').previousElementSibling.label = l10n.translate('Language changes will be applied after LOOT is restarted.'); - /* Filters */ - document.getElementById('hideVersionNumbers').label = l10n.translate("Hide version numbers").fetch(); - document.getElementById('hideCRCs').label = l10n.translate("Hide CRCs").fetch(); - document.getElementById('hideBashTags').label = l10n.translate("Hide Bash Tags").fetch(); - document.getElementById('hideNotes').label = l10n.translate("Hide notes").fetch(); - document.getElementById('hideDoNotCleanMessages').label = l10n.translate("Hide 'Do not clean' messages").fetch(); - document.getElementById('hideAllPluginMessages').label = l10n.translate("Hide all plugin messages").fetch(); - document.getElementById('hideInactivePlugins').label = l10n.translate("Hide inactive plugins").fetch(); - document.getElementById('hideMessagelessPlugins').label = l10n.translate("Hide messageless plugins").fetch(); - document.getElementById('hiddenPluginsTxt').textContent = l10n.translate("Hidden plugins:").fetch(); - document.getElementById('hiddenMessagesTxt').textContent = l10n.translate("Hidden messages:").fetch(); + document.getElementById('enableDebugLogging').previousElementSibling.textContent = l10n.translate('Enable debug logging'); + document.getElementById('enableDebugLogging').parentElement.label = l10n.translate('The output is logged to the LOOTDebugLog.txt file.'); - /* Summary */ - document.getElementById('summary').firstElementChild.textContent = l10n.translate("General Information").fetch(); - document.getElementById('masterlistRevision').previousElementSibling.textContent = l10n.translate("Masterlist Revision").fetch(); - document.getElementById('masterlistDate').previousElementSibling.textContent = l10n.translate("Masterlist Date").fetch(); - document.getElementById('totalWarningNo').previousElementSibling.textContent = l10n.translate("Warnings").fetch(); - document.getElementById('totalErrorNo').previousElementSibling.textContent = l10n.translate("Errors").fetch(); - document.getElementById('totalMessageNo').previousElementSibling.textContent = l10n.translate("Total Messages").fetch(); - document.getElementById('activePluginNo').previousElementSibling.textContent = l10n.translate("Active Plugins").fetch(); - document.getElementById('dirtyPluginNo').previousElementSibling.textContent = l10n.translate("Dirty Plugins").fetch(); - document.getElementById('totalPluginNo').previousElementSibling.textContent = l10n.translate("Total Plugins").fetch(); + document.getElementById('updateMasterlist').previousElementSibling.textContent = l10n.translate('Update masterlist before sorting'); - /* Settings dialog */ - document.getElementById('settingsDialog').heading = l10n.translate("Settings").fetch(); + const gameTable = document.getElementById('gameTable'); + gameTable.querySelector('th:first-child').textContent = l10n.translate('Name'); + gameTable.querySelector('th:nth-child(2)').textContent = l10n.translate('Base Game'); + gameTable.querySelector('th:nth-child(3)').textContent = l10n.translate('LOOT Folder'); + gameTable.querySelector('th:nth-child(4)').textContent = l10n.translate('Master File'); + gameTable.querySelector('th:nth-child(5)').textContent = l10n.translate('Masterlist Repository URL'); + gameTable.querySelector('th:nth-child(6)').textContent = l10n.translate('Masterlist Repository Branch'); + gameTable.querySelector('th:nth-child(7)').textContent = l10n.translate('Install Path'); + gameTable.querySelector('th:nth-child(8)').textContent = l10n.translate('Install Path Registry Key'); - var defaultGameSelect = document.getElementById('defaultGameSelect'); - defaultGameSelect.previousElementSibling.textContent = l10n.translate("Default Game").fetch(); - defaultGameSelect.firstElementChild.textContent = l10n.translate("Autodetect").fetch(); - /* The selected text doesn't update, so force that translation. */ - defaultGameSelect.shadowRoot.querySelector('paper-dropdown-menu').selectedItemLabel = defaultGameSelect.shadowRoot.querySelector('core-menu').selectedItem.textContent; + /* As the game table is attached on launch, its "Add New Row" + tooltip doesn't benefit from the template translation above. */ + gameTable.querySelector('tr:last-child core-tooltip').setAttribute('label', l10n.translate('Add New Row')); - document.getElementById('languageSelect').previousElementSibling.textContent = l10n.translate("Language").fetch(); - document.getElementById('languageSelect').previousElementSibling.label = l10n.translate("Language changes will be applied after LOOT is restarted.").fetch(); + document.getElementById('settingsDialog').getElementsByClassName('accept')[0].textContent = l10n.translate('Apply'); + document.getElementById('settingsDialog').getElementsByClassName('cancel')[0].textContent = l10n.translate('Cancel'); + } - document.getElementById('enableDebugLogging').previousElementSibling.textContent = l10n.translate("Enable debug logging").fetch(); - document.getElementById('enableDebugLogging').parentElement.label = l10n.translate("The output is logged to the LOOTDebugLog.txt file.").fetch(); + function translateFirstRunDialog(l10n) { + /* First-run dialog */ + const firstRun = document.getElementById('firstRun'); + firstRun.heading = l10n.translate('First-Time Tips'); - document.getElementById('updateMasterlist').previousElementSibling.textContent = l10n.translate("Update masterlist before sorting").fetch(); + firstRun.querySelector('li:nth-child(3)').textContent = l10n.translate('CRCs are only displayed after plugins have been loaded, either by conflict filtering, or by sorting.'); + firstRun.querySelector('li:nth-child(4)').textContent = l10n.translate('Double-click a plugin in the sidebar to quickly open its metadata editor. Multiple metadata editors can be opened at once.'); + firstRun.querySelector('li:nth-child(5)').textContent = l10n.translate('Plugins can be drag and dropped from the sidebar into editors\' "load after", "requirements" and "incompatibility" tables.'); + firstRun.querySelector('li:nth-child(6)').textContent = l10n.translate('Some features are disabled while there is an editor open, or while there is a sorted load order that has not been applied or discarded.'); + firstRun.querySelector('li:last-child').textContent = l10n.translate('Many interface elements have tooltips. If you don\'t know what something is, try hovering your mouse over it to find out. Otherwise, LOOT\'s documentation can be accessed through the main menu.'); - var gameTable = document.getElementById('gameTable'); - gameTable.querySelector('th:first-child').textContent = l10n.translate("Name").fetch(); - gameTable.querySelector('th:nth-child(2)').textContent = l10n.translate("Base Game").fetch(); - gameTable.querySelector('th:nth-child(3)').textContent = l10n.translate("LOOT Folder").fetch(); - gameTable.querySelector('th:nth-child(4)').textContent = l10n.translate("Master File").fetch(); - gameTable.querySelector('th:nth-child(5)').textContent = l10n.translate("Masterlist Repository URL").fetch(); - gameTable.querySelector('th:nth-child(6)').textContent = l10n.translate("Masterlist Repository Branch").fetch(); - gameTable.querySelector('th:nth-child(7)').textContent = l10n.translate("Install Path").fetch(); - gameTable.querySelector('th:nth-child(8)').textContent = l10n.translate("Install Path Registry Key").fetch(); + firstRun.getElementsByTagName('paper-button')[0].textContent = l10n.translate('OK'); + } - /* As the game table is attached on launch, its "Add New Row" - tooltip doesn't benefit from the template translation above. */ - gameTable.querySelector('tr:last-child core-tooltip').setAttribute('label', l10n.translate("Add New Row").fetch()); + return (l10n) => { + translatePluginCardTemplate(l10n); + translatePluginEditorTemplate(l10n); + translatePluginListItemTemplate(l10n); - document.getElementById('settingsDialog').getElementsByClassName('accept')[0].textContent = l10n.translate("Apply").fetch(); - document.getElementById('settingsDialog').getElementsByClassName('cancel')[0].textContent = l10n.translate("Cancel").fetch(); + translateFileRowTemplate(l10n); + translateMessageRowTemplate(l10n); + translateTagRowTemplate(l10n); + translateDirtyInfoRowTemplate(l10n); + translateLocationRowTemplate(l10n); + translateGameRowTemplate(l10n); + translateNewRowTemplate(l10n); - /* First-run dialog */ - var firstRun = document.getElementById('firstRun'); - firstRun.heading = l10n.translate("First-Time Tips").fetch(); + translateMainToolbar(l10n); + translateSidebar(l10n); - firstRun.querySelector('li:nth-child(3)').textContent = l10n.translate("CRCs are only displayed after plugins have been loaded, either by conflict filtering, or by sorting.").fetch(); - firstRun.querySelector('li:nth-child(4)').textContent = l10n.translate("Double-click a plugin in the sidebar to quickly open its metadata editor. Multiple metadata editors can be opened at once.").fetch(); - firstRun.querySelector('li:nth-child(5)').textContent = l10n.translate("Plugins can be drag and dropped from the sidebar into editors' \"load after\", \"requirements\" and \"incompatibility\" tables.").fetch(); - firstRun.querySelector('li:nth-child(6)').textContent = l10n.translate("Some features are disabled while there is an editor open, or while there is a sorted load order that has not been applied or discarded.").fetch(); - firstRun.querySelector('li:last-child').textContent = l10n.translate("Many interface elements have tooltips. If you don't know what something is, try hovering your mouse over it to find out. Otherwise, LOOT's documentation can be accessed through the main menu.").fetch(); - - firstRun.getElementsByTagName('paper-button')[0].textContent = l10n.translate("OK").fetch(); - }, - - getJedInstance: function(locale) { - return this.loadLocaleData(locale).catch(function(error){ - console.log(error); - return defaultData; - }).then(function(result){ - return new jed({ - 'locale_data': result, - 'domain': 'messages' - }); - }); - } - - }; + translateSummaryCard(l10n); + translateSettingsDialog(l10n); + translateFirstRunDialog(l10n); + }; })); diff --git a/src/gui/html/js/loot.js b/src/gui/html/js/loot.js index 183ce78a..6cb6d401 100644 --- a/src/gui/html/js/loot.js +++ b/src/gui/html/js/loot.js @@ -180,12 +180,12 @@ var loot = { if (change.object[change.name] && change.object[change.name].revision) { document.getElementById('masterlistRevision').textContent = change.object[change.name].revision; } else { - document.getElementById('masterlistRevision').textContent = l10n.jed.translate("N/A").fetch(); + 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 = l10n.jed.translate("N/A").fetch(); + 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 diff --git a/src/gui/html/js/translator.js b/src/gui/html/js/translator.js new file mode 100644 index 00000000..bfd01567 --- /dev/null +++ b/src/gui/html/js/translator.js @@ -0,0 +1,74 @@ +'use strict'; + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['bower_components/Jed/jed', 'bower_components/jed-gettext-parser/jedGettextParser'], factory); + } else { + // Browser globals + root.loot = root.loot || {}; + root.loot.Translator = factory(root.Jed, root.jedGettextParser); + } +}(this, (Jed, jedGettextParser) => { + return class Translator { + /* Returns a Promise */ + constructor(locale) { + this.locale = locale || 'en'; + this.jed = undefined; + } + + load() { + const defaultTranslationData = { + 'messages': { + '': { + 'domain': 'messages', + 'lang': 'en', + 'plural_forms': 'nplurals=2; plural=(n != 1);', + }, + }, + }; + + let translationDataPromise; + if (this.locale === 'en') { + /* Just resolve to an empty data set. */ + translationDataPromise = Promise.resolve(defaultTranslationData); + } else { + translationDataPromise = new Promise((resolve, reject) => { + const url = 'loot://l10n/' + this.locale + '/LC_MESSAGES/loot.mo'; + const xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'arraybuffer'; + xhr.addEventListener('readystatechange', (evt) => { + if (evt.target.readyState === 4) { + /* Status is 0 for local file URL loading. */ + if (evt.target.status >= 200 && evt.target.status < 400) { + resolve(jedGettextParser.mo.parse(evt.target.response)); + } else { + reject(new Error(evt.target.statusText)); + } + } + }, false); + xhr.send(); + }); + } + + return translationDataPromise.catch((error) => { + console.log('Error loading translation data: ' + error.message); + return defaultTranslationData; + }).then((result) => { + this.jed = new Jed({ + 'locale_data': result, + 'domain': 'messages', + }); + }); + } + + translate(text, ...substitutions) { + if (text === undefined) { + return ''; + } + const func = this.jed.translate(text); + return func.fetch.apply(func, substitutions); + } + }; +})); diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html index 7d52378c..f8068dff 100644 --- a/src/tests/gui/html/js/test.html +++ b/src/tests/gui/html/js/test.html @@ -7,9 +7,14 @@ + + + + + + + + - - - - + diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index e9bf8bf8..c07d73e8 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -111,10 +111,10 @@ function updateMasterlistNoProgress() { for (var i = 0; i < loot.game.plugins.length; ++i) { if (loot.game.plugins[i].name == plugin.name) { loot.game.plugins[i].isDirty = plugin.isDirty; - loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; loot.game.plugins[i].masterlist = plugin.masterlist; loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].modPriority = plugin.modPriority; + loot.game.plugins[i].priority = plugin.priority; loot.game.plugins[i].tags = plugin.tags; break; } @@ -276,8 +276,8 @@ function onClearAllMetadata(evt) { loot.game.plugins[i].userlist = undefined; loot.game.plugins[i].editor = undefined; - loot.game.plugins[i].modPriority = plugin.modPriority; - loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; + loot.game.plugins[i].priority = plugin.priority; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; loot.game.plugins[i].messages = plugin.messages; loot.game.plugins[i].tags = plugin.tags; loot.game.plugins[i].isDirty = plugin.isDirty; @@ -317,8 +317,8 @@ function onCopyContent(evt) { isEmpty: plugin.isEmpty, loadsArchive: plugin.loadsArchive, - modPriority: plugin.modPriority, - isGlobalPriority: plugin.isGlobalPriority, + priority: plugin.priority, + isPriorityGlobal: plugin.isPriorityGlobal, messages: plugin.messages, tags: plugin.tags, isDirty: plugin.isDirty @@ -491,8 +491,8 @@ function onEditorClose(evt) { }); promise = loot.query(request).then(JSON.parse).then(function(result){ if (result) { - evt.target.data.modPriority = result.modPriority; - evt.target.data.isGlobalPriority = result.isGlobalPriority; + evt.target.data.priority = result.priority; + evt.target.data.isPriorityGlobal = result.isPriorityGlobal; evt.target.data.messages = result.messages; evt.target.data.tags = result.tags; evt.target.data.isDirty = result.isDirty; @@ -598,8 +598,8 @@ function onClearMetadata(evt) { loot.game.plugins[i].userlist = undefined; loot.game.plugins[i].editor = undefined; - loot.game.plugins[i].modPriority = result.modPriority; - loot.game.plugins[i].isGlobalPriority = result.isGlobalPriority; + loot.game.plugins[i].priority = result.priority; + loot.game.plugins[i].isPriorityGlobal = result.isPriorityGlobal; loot.game.plugins[i].messages = result.messages; loot.game.plugins[i].tags = result.tags; loot.game.plugins[i].isDirty = result.isDirty; @@ -670,8 +670,8 @@ function onContentRefresh(evt) { loot.game.plugins[i].crc = plugin.crc; loot.game.plugins[i].version = plugin.version; - loot.game.plugins[i].modPriority = plugin.modPriority; - loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; + loot.game.plugins[i].priority = plugin.priority; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; loot.game.plugins[i].messages = plugin.messages; loot.game.plugins[i].tags = plugin.tags; loot.game.plugins[i].isDirty = plugin.isDirty; @@ -715,6 +715,15 @@ function onSearchOpen(evt) { function onSearchClose(evt) { document.getElementById('mainToolbar').classList.remove('search'); } +function onSidebarFilterToggle(evt) { + if (evt.target.id !== 'contentFilter') { + loot.filters[evt.target.id] = evt.target.checked; + } else { + loot.filters.contentSearchString = evt.target.value; + } + saveFilterState(evt); + setFilteredUIData(); +} function setupEventHandlers() { /*Set up handlers for filters.*/ document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS, false); @@ -723,20 +732,15 @@ function setupEventHandlers() { document.getElementById('hideCRCs').addEventListener('change', saveFilterState, false); document.getElementById('hideBashTags').addEventListener('change', onToggleBashTags, false); document.getElementById('hideBashTags').addEventListener('change', saveFilterState, false); - document.getElementById('hideNotes').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideNotes').addEventListener('change', saveFilterState, false); - document.getElementById('hideDoNotCleanMessages').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideDoNotCleanMessages').addEventListener('change', saveFilterState, false); - document.getElementById('hideInactivePlugins').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideInactivePlugins').addEventListener('change', saveFilterState, false); - document.getElementById('hideAllPluginMessages').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideAllPluginMessages').addEventListener('change', saveFilterState, false); - document.getElementById('hideMessagelessPlugins').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideMessagelessPlugins').addEventListener('change', saveFilterState, false); + document.getElementById('hideNotes').addEventListener('change', onSidebarFilterToggle, false); + document.getElementById('hideDoNotCleanMessages').addEventListener('change', onSidebarFilterToggle, false); + document.getElementById('hideInactivePlugins').addEventListener('change', onSidebarFilterToggle, false); + document.getElementById('hideAllPluginMessages').addEventListener('change', onSidebarFilterToggle, false); + document.getElementById('hideMessagelessPlugins').addEventListener('change', onSidebarFilterToggle, false); document.body.addEventListener('loot-filter-conflicts', onConflictsFilter, false); /* Set up event handlers for content filter. */ - document.getElementById('contentFilter').addEventListener('change', setFilteredUIData, false); + document.getElementById('contentFilter').addEventListener('change', onSidebarFilterToggle, false); /* Set up handlers for buttons. */ document.getElementById('redatePluginsButton').addEventListener('click', onRedatePlugins, false); diff --git a/src/gui/html/js/filters.js b/src/gui/html/js/filters.js index dcebfc87..7f229173 100644 --- a/src/gui/html/js/filters.js +++ b/src/gui/html/js/filters.js @@ -1,252 +1,67 @@ -var filters = { - /* Filter functions return true if the given plugin passes the filter and - should be displayed, otherwise false. */ +'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.Filters = factory(); + } +}(this, () => { + return class Filters { + constructor(l10n) { + /* Plugin filters */ + this.hideMessagelessPlugins = false; + this.hideInactivePlugins = false; + this.conflictingPluginNames = []; + this.contentSearchString = ''; - hiddenPluginNo: 0, - hiddenMessageNo: 0, - conflicts: [], + /* Plugin content filters */ + this.hideVersionNumbers = false; + this.hideCRCs = false; + this.hideBashTags = false; + this.hideAllPluginMessages = false; + this.hideNotes = false; + this.hideDoNotCleanMessages = false; - searchFilter: function(plugin, needle) { - if (needle.length == 0) { - return true; + this._doNotCleanString = l10n.translate('Do not clean').toLowerCase(); + } + + pluginFilter(plugin) { + if (this.hideInactivePlugins && !plugin.isActive) { + return false; } - if (plugin.name.toLowerCase().indexOf(needle) != -1 - || plugin.crcString.toLowerCase().indexOf(needle) != -1 - || plugin.version.toLowerCase().indexOf(needle) != -1) { - - return true; + if (this.conflictingPluginNames.length !== 0 && this.conflictingPluginNames.indexOf(plugin.name) === -1) { + return false; } - var tags = plugin.tagStrings; - if (tags.added.toLowerCase().indexOf(needle) != -1 - || tags.removed.toLowerCase().indexOf(needle) != -1) { - - return true; + if (this.hideMessagelessPlugins && plugin.getCardContent(this).messages.length === 0) { + return false; } - for (var i = 0; i < plugin.messages.length; ++i) { - if (plugin.messages[i].content[0].str.toLowerCase().indexOf(needle) != -1) { - return true; - } - } - }, - - messagelessFilter: function(plugin) { - /* This function could be further optimised to perform fewer checks, - but it's also responsible for setting the hidden message count, so - has to go through everything. */ - - var hasMessages = false; - /* If any messages exist, check if they are hidden or not. Note - that the messages may not be present as elements, so the check - is actually if they would be hidden according to the message - filters. */ - if (this.allMessageFilter()) { - plugin.messages.forEach(function(message){ - if (this.noteFilter(message) - && this.doNotCleanFilter(message)) { - - hasMessages = true; - return; - } - ++hiddenMessageNo; - }, this); - } else { - hiddenMessageNo += plugin.messages.length; + if (this.contentSearchString.length !== 0 && !plugin.getCardContent(this).containsText(this.contentSearchString)) { + return false; } - if (document.getElementById('hideMessagelessPlugins').checked) { - return hasMessages; - } else { - return true; - } - }, + return true; + } - inactiveFilter: function(plugin) { - if (document.getElementById('hideInactivePlugins').checked) { - return plugin.isActive; - } else { - return true; - } - }, - - conflictsFilter: function(plugin) { - if (this.conflicts.length > 0) { - return this.conflicts.indexOf(plugin.name) != -1; - } else { - return true; - } - }, - - applyPluginFilters: function(plugins) { - var search = document.getElementById('contentFilter').value.toLowerCase(); - hiddenPluginNo = 0; - hiddenMessageNo = 0; - var filteredPlugins = []; - - plugins.forEach(function(plugin){ - /* Messageless filter needs to run first. */ - if (this.messagelessFilter(plugin) - && this.inactiveFilter(plugin) - && this.conflictsFilter(plugin) - && this.searchFilter(plugin, search)) { - - filteredPlugins.push(plugin); - return; - } - ++hiddenPluginNo; - }, this); - - document.getElementById('hiddenPluginNo').textContent = hiddenPluginNo; - document.getElementById('hiddenMessageNo').textContent = hiddenMessageNo; - - return filteredPlugins; - }, - - /* Message filter functions are run from within plugin cards, when the card's - messages are to be added as elements. Each filter should return true if the - message is to be displayed. */ - - noteFilter: function(message) { - if (document.getElementById('hideNotes').checked) { - return message.type != 'say'; - } else { - return true; - } - }, - - doNotCleanFilter: function(message) { - if (document.getElementById('hideDoNotCleanMessages').checked) { - return message.content[0].str.indexOf(loot.l10n.translate("Do not clean")) == -1; - } else { - return true; - } - }, - - allMessageFilter: function() { - return !document.getElementById('hideAllPluginMessages').checked; - }, - - applyMessageFilters: function(messages) { - var filteredMessages = []; - - if (this.allMessageFilter()) { - messages.forEach(function(message){ - if (this.noteFilter(message) - && this.doNotCleanFilter(message)) { - - filteredMessages.push(message); - return; - } - }, this); + messageFilter(message) { + if (this.hideAllPluginMessages) { + return false; } - return filteredMessages; - }, -}; - -function getConflictingPluginsFromFilter() { - var conflictsPlugin = document.body.getAttribute('data-conflicts'); - if (conflictsPlugin) { - /* Now get conflicts for the plugin. */ - var request = JSON.stringify({ - name: 'getConflictingPlugins', - args: [ - conflictsPlugin - ] - }); - - showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); - - return loot.query(request).then(JSON.parse).then(function(result){ - if (result) { - /* Filter everything but the plugin itself if there are no - conflicts. */ - var conflicts = [ conflictsPlugin ]; - for (var key in result) { - if (result[key].conflicts) { - conflicts.push(key); - } - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == key) { - loot.game.plugins[i].crc = result[key].crc; - loot.game.plugins[i].isEmpty = result[key].isEmpty; - - loot.game.plugins[i].messages = result[key].messages; - loot.game.plugins[i].tags = result[key].tags; - loot.game.plugins[i].isDirty = result[key].isDirty; - break; - } - } - } - closeProgressDialog(); - return conflicts; - } - closeProgressDialog(); - return [ conflictsPlugin ]; - }).catch(processCefError); - } - - return Promise.resolve([]); -} - -function setFilteredUIData() { - /* The conflict filter, if enabled, executes C++ code, so needs to be - handled using a promise, so the rest of the function should wait until - it is completed. - */ - getConflictingPluginsFromFilter().then(function(conflicts) { - filters.conflicts = conflicts; - var filtered = filters.applyPluginFilters(loot.game.plugins); - document.getElementById('cardsNav').data = filtered; - document.getElementById('main').lastElementChild.data = filtered; - - filtered.forEach(function(plugin){ - var element = document.getElementById(plugin.id); - if (element) { - element.onMessagesChange(); - } - }); - - /* Now perform search again. If there is no current search, this won't - do anything. */ - document.getElementById('searchBar').search(); - }); -} -function restoreFilterStates() { - if (loot.settings.filters) { - document.getElementById('hideMessagelessPlugins').checked = loot.settings.filters.hideMessagelessPlugins; - document.getElementById('hideInactivePlugins').checked = loot.settings.filters.hideInactivePlugins; - document.getElementById('hideNotes').checked = loot.settings.filters.hideNotes; - document.getElementById('hideDoNotCleanMessages').checked = loot.settings.filters.hideDoNotCleanMessages; - document.getElementById('hideAllPluginMessages').checked = loot.settings.filters.hideAllPluginMessages; - document.getElementById('hideVersionNumbers').checked = loot.settings.filters.hideVersionNumbers; - document.getElementById('hideCRCs').checked = loot.settings.filters.hideCRCs; - document.getElementById('hideBashTags').checked = loot.settings.filters.hideBashTags; - } -} - -function applyEnabledFilters() { - if (loot.settings.filters) { - if (loot.settings.filters.hideMessagelessPlugins - || loot.settings.filters.hideInactivePlugins - || loot.settings.filters.hideNotes - || loot.settings.filters.hideDoNotCleanMessages - || loot.settings.filters.hideAllPluginMessages) { - setFilteredUIData(); + if (this.hideNotes && message.type === 'say') { + return false; } - if (loot.settings.filters.hideVersionNumbers) { - document.getElementById('hideVersionNumbers').dispatchEvent(new Event('change')); + if (this.hideDoNotCleanMessages && message.content.toLowerCase().indexOf(this._doNotCleanString) !== -1) { + return false; } - if (loot.settings.filters.hideCRCs) { - document.getElementById('hideCRCs').dispatchEvent(new Event('change')); - } - - if (loot.settings.filters.hideBashTags) { - document.getElementById('hideBashTags').dispatchEvent(new Event('change')); - } - } -} + return true; + } + }; +})); diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index 086f3f5d..9f506cb1 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -70,3 +70,74 @@ function handleUnappliedChangesClose(change) { } }); } +function getConflictingPlugins(pluginName) { + if (!pluginName) { + return Promise.resolve([]); + } + + /* Now get conflicts for the plugin. */ + const request = JSON.stringify({ + name: 'getConflictingPlugins', + args: [ + pluginName, + ], + }); + + showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); + + return loot.query(request).then(JSON.parse).then((result) => { + if (result) { + /* Filter everything but the plugin itself if there are no + conflicts. */ + const conflicts = [pluginName]; + for (const key in result) { + if (result[key].conflicts) { + conflicts.push(key); + } + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name === key) { + loot.game.plugins[i].crc = result[key].crc; + loot.game.plugins[i].isEmpty = result[key].isEmpty; + + loot.game.plugins[i].messages = result[key].messages; + loot.game.plugins[i].tags = result[key].tags; + loot.game.plugins[i].isDirty = result[key].isDirty; + break; + } + } + } + closeProgressDialog(); + return conflicts; + } + closeProgressDialog(); + return [pluginName]; + }).catch(processCefError); +} +function setFilteredUIData(filtersState) { + getConflictingPlugins(loot.filters.conflictTargetPluginName).then((conflictingPluginNames) => { + loot.filters.conflictingPluginNames = conflictingPluginNames; + return loot.game.plugins.filter(loot.filters.pluginFilter, loot.filters); + }).then((filteredPlugins) => { + document.getElementById('cardsNav').data = filteredPlugins; + document.getElementById('pluginCardList').data = filteredPlugins; + + filteredPlugins.forEach((plugin) => { + const element = document.getElementById(plugin.id); + if (element) { + element.onMessagesChange(); + } + }); + + /* Now perform search again. If there is no current search, this won't + do anything. */ + document.getElementById('searchBar').search(); + + /* Re-count all hidden plugins and messages. */ + document.getElementById('hiddenPluginNo').textContent = loot.game.plugins.length - filteredPlugins.length; + let hiddenMessageNo = 0; + loot.game.plugins.forEach((plugin) => { + hiddenMessageNo += plugin.messages.length - plugin.getCardContent(loot.filters).messages.length; + }); + document.getElementById('hiddenMessageNo').textContent = hiddenMessageNo; + }); +} diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 0b8d618e..e5263c9e 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -25,6 +25,53 @@ var marked; var l10n; +function restoreFilterStates() { + if (loot.settings.filters && loot.filters) { + loot.filters.hideMessagelessPlugins = loot.settings.filters.hideMessagelessPlugins; + loot.filters.hideInactivePlugins = loot.settings.filters.hideInactivePlugins; + loot.filters.hideNotes = loot.settings.filters.hideNotes; + loot.filters.hideDoNotCleanMessages = loot.settings.filters.hideDoNotCleanMessages; + loot.filters.hideAllPluginMessages = loot.settings.filters.hideAllPluginMessages; + loot.filters.hideVersionNumbers = loot.settings.filters.hideVersionNumbers; + loot.filters.hideCRCs = loot.settings.filters.hideCRCs; + loot.filters.hideBashTags = loot.settings.filters.hideBashTags; + + document.getElementById('hideMessagelessPlugins').checked = loot.settings.filters.hideMessagelessPlugins; + document.getElementById('hideInactivePlugins').checked = loot.settings.filters.hideInactivePlugins; + document.getElementById('hideNotes').checked = loot.settings.filters.hideNotes; + document.getElementById('hideDoNotCleanMessages').checked = loot.settings.filters.hideDoNotCleanMessages; + document.getElementById('hideAllPluginMessages').checked = loot.settings.filters.hideAllPluginMessages; + document.getElementById('hideVersionNumbers').checked = loot.settings.filters.hideVersionNumbers; + document.getElementById('hideCRCs').checked = loot.settings.filters.hideCRCs; + document.getElementById('hideBashTags').checked = loot.settings.filters.hideBashTags; + } +} + +function applyEnabledFilters() { + if (!loot.filters) { + return; + } + + if (loot.filters.hideMessagelessPlugins + || loot.filters.hideInactivePlugins + || loot.filters.hideNotes + || loot.filters.hideDoNotCleanMessages + || loot.filters.hideAllPluginMessages) { + setFilteredUIData(); + } + + if (loot.filters.hideVersionNumbers) { + document.getElementById('hideVersionNumbers').dispatchEvent(new Event('change')); + } + + if (loot.filters.hideCRCs) { + document.getElementById('hideCRCs').dispatchEvent(new Event('change')); + } + + if (loot.filters.hideBashTags) { + document.getElementById('hideBashTags').dispatchEvent(new Event('change')); + } +} function initVars() { loot.query('getVersion').then(function(result){ try { diff --git a/src/gui/html/js/plugin.js b/src/gui/html/js/plugin.js index 94733205..875a85c0 100644 --- a/src/gui/html/js/plugin.js +++ b/src/gui/html/js/plugin.js @@ -32,23 +32,155 @@ root.loot.Plugin = factory(); } }(this, () => { + /* Messages, tags, CRCs and version strings can all be hidden by filters. + Use getters with no setters for member variables as data should not be + written to objects of this class. */ + class PluginCardContent { + constructor(plugin, filters) { + this._name = plugin.name; + this._isActive = plugin.isActive || false; + this._isEmpty = plugin.isEmpty; + this._isMaster = plugin.isMaster; + this._loadsArchive = plugin.loadsArchive; + + if (!filters.hideVersionNumbers) { + this._version = plugin.version; + } else { + this._version = ''; + } + + if (!filters.hideCRCs) { + this._crc = plugin.crc; + } else { + this._crc = 0; + } + + if (!filters.hideBashTags) { + this._tags = plugin.tags; + } else { + this._tags = []; + } + + this._messages = plugin.messages.map((message) => { + return { + type: message.type, + content: message.content[0].str, + }; + }).filter(filters.messageFilter, filters); + } + + get name() { + return this._name; + } + + get isActive() { + return this._isActive; + } + + get isEmpty() { + return this._isEmpty; + } + + get isMaster() { + return this._isMaster; + } + + get loadsArchive() { + return this._loadsArchive; + } + + get version() { + return this._version; + } + + get crc() { + if (this._crc === 0) { + return ''; + } + + /* Pad CRC string to 8 characters. */ + return ('00000000' + this._crc.toString(16).toUpperCase()).slice(-8); + } + + get tags() { + const tagsAdded = []; + const tagsRemoved = []; + + if (this._tags) { + for (let i = 0; i < this._tags.length; ++i) { + if (this._tags[i].name[0] === '-') { + tagsRemoved.push(this._tags[i].name.substr(1)); + } else { + tagsAdded.push(this._tags[i].name); + } + } + } + /* Now make sure that the same tag doesn't appear in both arrays. + Prefer the removed list. */ + for (let i = 0; i < tagsAdded.length; ++i) { + for (let j = 0; j < tagsRemoved.length; ++j) { + if (tagsRemoved[j].toLowerCase() === tagsAdded[i].toLowerCase()) { + /* Remove tag from the tagsAdded array. */ + tagsAdded.splice(i, 1); + --i; + } + } + } + + return { + added: tagsAdded.join(', '), + removed: tagsRemoved.join(', '), + }; + } + + get messages() { + return this._messages; + } + + containsText(text) { + if (text === undefined || text.length === 0) { + return true; + } + const needle = text.toLowerCase(); + + if (this.name.toLowerCase().indexOf(needle) !== -1 + || this.crc.toLowerCase().indexOf(needle) !== -1 + || this.version.toLowerCase().indexOf(needle) !== -1) { + return true; + } + + if (this.tags.added.toLowerCase().indexOf(needle) !== -1 + || this.tags.removed.toLowerCase().indexOf(needle) !== -1) { + return true; + } + + for (let i = 0; i < this.messages.length; ++i) { + if (this.messages[i].content.toLowerCase().indexOf(needle) !== -1) { + return true; + } + } + + return false; + } + } + return class Plugin { constructor(obj) { /* Plugin data */ this.name = obj.name; - this.crc = obj.crc; - this.version = obj.version; - this.isActive = obj.isActive; - this.isEmpty = obj.isEmpty; - this.isMaster = obj.isMaster; - this.loadsArchive = obj.loadsArchive; + this.crc = obj.crc || 0; + this.version = obj.version || ''; + this.isActive = obj.isActive || false; + this.isEmpty = obj.isEmpty || false; + this.isMaster = obj.isMaster || false; + this.loadsArchive = obj.loadsArchive || false; this.masterlist = obj.masterlist; this.userlist = obj.userlist; - this.modPriority = obj.modPriority; - this.isGlobalPriority = obj.isGlobalPriority; - this._messages = obj.messages; + this.priority = obj.priority || 0; + this.isPriorityGlobal = obj.isPriorityGlobal || false; + this._messages = obj.messages || []; this.tags = obj.tags; this._isDirty = obj.isDirty || false; @@ -100,52 +232,12 @@ return rowData; } - get tagStrings() { - const tagsAdded = []; - const tagsRemoved = []; - - if (this.tags) { - for (let i = 0; i < this.tags.length; ++i) { - if (this.tags[i].name[0] === '-') { - tagsRemoved.push(this.tags[i].name.substr(1)); - } else { - tagsAdded.push(this.tags[i].name); - } - } - } - /* Now make sure that the same tag doesn't appear in both arrays. - Prefer the removed list. */ - for (let i = 0; i < tagsAdded.length; ++i) { - for (let j = 0; j < tagsRemoved.length; ++j) { - if (tagsRemoved[j].toLowerCase() === tagsAdded[i].toLowerCase()) { - /* Remove tag from the tagsAdded array. */ - tagsAdded.splice(i, 1); - --i; - } - } - } - - return { - added: tagsAdded.join(', '), - removed: tagsRemoved.join(', '), - }; - } - get priorityString() { - if (this.modPriority === undefined || this.modPriority === 0) { + if (this.priority === 0) { return ''; } - return this.modPriority.toString(); - } - - get crcString() { - if (this.crc === undefined || this.crc === 0) { - return ''; - } - - /* Pad CRC string to 8 characters. */ - return ('00000000' + this.crc.toString(16).toUpperCase()).slice(-8); + return this.priority.toString(); } get messages() { @@ -161,29 +253,25 @@ let oldErrs = 0; let newErrs = 0; - if (this._messages) { - oldTotal = this._messages.length; + oldTotal = this._messages.length; - this._messages.forEach((message) => { - if (message.type === 'warn') { - ++oldWarns; - } else if (message.type === 'error') { - ++oldErrs; - } - }); - } + this._messages.forEach((message) => { + if (message.type === 'warn') { + ++oldWarns; + } else if (message.type === 'error') { + ++oldErrs; + } + }); - if (messages) { - newTotal = messages.length; + newTotal = messages.length; - messages.forEach((message) => { - if (message.type === 'warn') { - ++newWarns; - } else if (message.type === 'error') { - ++newErrs; - } - }); - } + messages.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-plugin-message-change', { @@ -214,5 +302,9 @@ this._isDirty = dirty; } + + getCardContent(filters) { + return new PluginCardContent(this, filters); + } }; })); diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html index 1bc13433..dcf62cdb 100644 --- a/src/tests/gui/html/js/test.html +++ b/src/tests/gui/html/js/test.html @@ -11,8 +11,10 @@ + + + + + diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 87378a27..a064f59f 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -77,14 +77,7 @@ function onPluginIsDirtyChange(evt) { } } function saveFilterState(evt) { - var request = JSON.stringify({ - name: 'saveFilterState', - args: [ - evt.target.id, - evt.target.checked, - ] - }); - loot.query(request).catch(processCefError); + loot.query('saveFilterState', evt.target.id, evt.target.checked).catch(processCefError); } function onToggleDisplayCSS(evt) { var attr = 'data-hide-' + evt.target.getAttribute('data-class'); @@ -118,13 +111,7 @@ function onChangeGame(evt) { /* Send off a CEF query with the folder name of the new game. */ showProgress(loot.l10n.translate('Loading game data...')); - var request = JSON.stringify({ - name: 'changeGame', - args: [ - evt.currentTarget.getAttribute('value') - ] - }); - loot.query(request).then(function(result){ + loot.query('changeGame', evt.currentTarget.getAttribute('value')).then(function(result){ /* Filters should be re-applied on game change, except the conflicts filter. Don't need to deactivate the others beforehand. Strictly not deactivating the conflicts filter either, just resetting it's value. @@ -274,13 +261,7 @@ function onApplySort(evt) { loot.game.plugins.forEach(function(plugin){ loadOrder.push(plugin.name); }); - var request = JSON.stringify({ - name: 'applySort', - args: [ - loadOrder - ] - }); - return loot.query(request).then(function(result){ + return loot.query('applySort', loadOrder).then(function(result){ /* Remove old load order storage. */ delete loot.game.loadOrder; delete loot.game.oldLoadOrder; @@ -399,15 +380,10 @@ function onCopyContent(evt) { } } - var request = JSON.stringify({ - name: 'copyContent', - args: [{ - messages: messages, - plugins: plugins - }] - }); - - loot.query(request).then(function(){ + loot.query('copyContent', { + messages: messages, + plugins: plugins + }).then(function(){ toast(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); }).catch(processCefError); } @@ -422,14 +398,7 @@ function onCopyLoadOrder(evt) { } } - var request = JSON.stringify({ - name: 'copyLoadOrder', - args: [ - plugins - ] - }); - - loot.query(request).then(function(){ + loot.query('copyLoadOrder', plugins).then(function(){ toast(loot.l10n.translate("The load order has been copied to the clipboard.")); }).catch(processCefError); } @@ -469,13 +438,7 @@ function onCloseSettingsDialog(evt) { }; /* Send the settings back to the C++ side. */ - var request = JSON.stringify({ - name: 'closeSettings', - args: [ - settings - ] - }); - loot.query(request).then(function(result){ + loot.query('closeSettings', settings).then(function(result){ try { setInstalledGames(JSON.parse(result)); @@ -547,14 +510,7 @@ function onEditorClose(evt) { majority of the work to the C++ side of things. */ var edits = evt.target.readFromEditor(evt.target.data); - - var request = JSON.stringify({ - name: 'editorClosed', - args: [ - edits - ] - }); - promise = loot.query(request).then(JSON.parse).then(function(result){ + promise = loot.query('editorClosed', edits).then(JSON.parse).then(function(result){ if (result) { evt.target.data.priority = result.priority; evt.target.data.isPriorityGlobal = result.isPriorityGlobal; @@ -633,29 +589,14 @@ function onConflictsFilter(evt) { setFilteredUIData(evt); } function onCopyMetadata(evt) { - /* evt.detail is the name of the plugin. */ - var request = JSON.stringify({ - name: 'copyMetadata', - args: [ - evt.target.getName(), - ] - }); - - loot.query(request).then(function(){ + loot.query('copyMetadata', evt.target.getName()).then(function(){ toast(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); }).catch(processCefError); } function onClearMetadata(evt) { showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){ if (result) { - var request = JSON.stringify({ - name: 'clearPluginMetadata', - args: [ - evt.target.getName() - ] - }); - - loot.query(request).then(JSON.parse).then(function(result){ + loot.query('clearPluginMetadata', evt.target.getName()).then(JSON.parse).then(function(result){ if (result) { /* Need to empty the UI-side user metadata. */ for (var i = 0; i < loot.game.plugins.length; ++i) { diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index bea6e87b..23bec811 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -77,16 +77,9 @@ function getConflictingPlugins(pluginName) { } /* Now get conflicts for the plugin. */ - const request = JSON.stringify({ - name: 'getConflictingPlugins', - args: [ - pluginName, - ], - }); - showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); - return loot.query(request).then(JSON.parse).then((result) => { + return loot.query('getConflictingPlugins', pluginName).then(JSON.parse).then((result) => { if (result) { /* Filter everything but the plugin itself if there are no conflicts. */ @@ -214,17 +207,3 @@ function updateSettingsUI() { updateEnabledGames(loot.installedGames); updateSelectedGame(loot.game.folder); } -/* Returns a cefQuery as a Promise. */ -var loot = loot || {}; -loot.query = function query(request) { - return new Promise(function(resolve, reject) { - window.cefQuery({ - request: request, - persistent: false, - onSuccess: resolve, - onFailure: function(errorCode, errorMessage) { - reject(Error('Error code: ' + errorCode + '; ' + errorMessage)) - } - }); - }); -} diff --git a/src/gui/html/js/query.js b/src/gui/html/js/query.js new file mode 100644 index 00000000..8c5c7696 --- /dev/null +++ b/src/gui/html/js/query.js @@ -0,0 +1,36 @@ +'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.query = factory(); + } +}(this, () => { + return (requestName, ...args) => { + if (!requestName) { + throw new Error('No request name passed'); + } + let request; + if (args.length === 0) { + request = requestName; + } else { + request = JSON.stringify({ + name: requestName, + args, + }); + } + return new Promise((resolve, reject) => { + window.cefQuery({ + request, + persistent: false, + onSuccess: resolve, + onFailure: (errorCode, errorMessage) => { + reject(new Error('Error code: ' + errorCode + '; ' + errorMessage)); + }, + }); + }); + }; +})); diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html index 77e945f6..44de2886 100644 --- a/src/tests/gui/html/js/test.html +++ b/src/tests/gui/html/js/test.html @@ -14,10 +14,12 @@ + + + diff --git a/src/gui/html/js/dialog.js b/src/gui/html/js/dialog.js new file mode 100644 index 00000000..e83f2243 --- /dev/null +++ b/src/gui/html/js/dialog.js @@ -0,0 +1,48 @@ +'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.Dialog = factory(); + } +}(this, () => { + return class Dialog { + static showProgress(text) { + const progressDialog = document.getElementById('progressDialog'); + progressDialog.getElementsByTagName('p')[0].textContent = text; + if (!progressDialog.opened) { + progressDialog.showModal(); + } + } + + static closeProgress() { + const progressDialog = document.getElementById('progressDialog'); + if (progressDialog.opened) { + progressDialog.close(); + } + } + + static showMessage(title, text) { + const dialog = document.createElement('loot-message-dialog'); + dialog.setDismissable(false); + dialog.showModal(title, text); + document.body.appendChild(dialog); + } + + static askQuestion(title, text, confirmText, closeCallback) { + const dialog = document.createElement('loot-message-dialog'); + dialog.setConfirmText(confirmText); + dialog.showModal(title, text, closeCallback); + document.body.appendChild(dialog); + } + + static showNotification(text) { + const toast = document.getElementById('toast'); + toast.text = text; + toast.show(); + } + }; +})); diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index a064f59f..5e056f22 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -110,7 +110,7 @@ function onChangeGame(evt) { } /* Send off a CEF query with the folder name of the new game. */ - showProgress(loot.l10n.translate('Loading game data...')); + loot.Dialog.showProgress(loot.l10n.translate('Loading game data...')); loot.query('changeGame', evt.currentTarget.getAttribute('value')).then(function(result){ /* Filters should be re-applied on game change, except the conflicts filter. Don't need to deactivate the others beforehand. Strictly not @@ -144,7 +144,7 @@ function onChangeGame(evt) { console.log('changeGame response: ' + result); } - closeProgressDialog(); + loot.Dialog.closeProgress(); }).catch(processCefError); } function onOpenReadme(evt) { @@ -174,16 +174,16 @@ function updateMasterlistNoProgress() { /* Hack to stop cards overlapping. */ document.getElementById('main').lastElementChild.updateSize(); - toast(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision)); + loot.Dialog.showNotification(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision)); } else { - toast(loot.l10n.translate('No masterlist update was necessary.')); + loot.Dialog.showNotification(loot.l10n.translate('No masterlist update was necessary.')); } }).catch(processCefError); } function onUpdateMasterlist(evt) { - showProgress(loot.l10n.translate('Updating masterlist...')); + loot.Dialog.showProgress(loot.l10n.translate('Updating masterlist...')); updateMasterlistNoProgress().then(function(result){ - closeProgressDialog(); + loot.Dialog.closeProgress(); }).catch(processCefError); } function onSortPlugins(evt) { @@ -205,7 +205,7 @@ function onSortPlugins(evt) { promise = promise.then(updateMasterlistNoProgress()); } promise.then(function(){ - showProgress(loot.l10n.translate('Sorting plugins...')); + loot.Dialog.showProgress(loot.l10n.translate('Sorting plugins...')); loot.query('sortPlugins').then(JSON.parse).then(function(result){ if (result) { loot.game.oldLoadOrder = loot.game.plugins; @@ -251,7 +251,7 @@ function onSortPlugins(evt) { /* Disable changing game. */ document.getElementById('gameMenu').setAttribute('disabled', ''); - closeProgressDialog(); + loot.Dialog.closeProgress(); } }).catch(processCefError); }).catch(processCefError); @@ -301,16 +301,16 @@ function onRedatePlugins(evt) { return; } - showMessageDialog(loot.l10n.translate('Redate Plugins?'), loot.l10n.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?'), loot.l10n.translate('Redate'), function(result){ + loot.Dialog.askQuestion(loot.l10n.translate('Redate Plugins?'), loot.l10n.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?'), loot.l10n.translate('Redate'), function(result){ if (result) { loot.query('redatePlugins').then(function(response){ - toast('Plugins were successfully redated.'); + loot.Dialog.showNotification('Plugins were successfully redated.'); }).catch(processCefError); } }); } function onClearAllMetadata(evt) { - showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from all plugins?'), loot.l10n.translate('Clear'), function(result){ + loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from all plugins?'), loot.l10n.translate('Clear'), function(result){ if (result) { loot.query('clearAllMetadata').then(JSON.parse).then(function(result){ if (result) { @@ -332,7 +332,7 @@ function onClearAllMetadata(evt) { } }); - toast(loot.l10n.translate('All user-added metadata has been cleared.')); + loot.Dialog.showNotification(loot.l10n.translate('All user-added metadata has been cleared.')); } }).catch(processCefError); } @@ -384,7 +384,7 @@ function onCopyContent(evt) { messages: messages, plugins: plugins }).then(function(){ - toast(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); + loot.Dialog.showNotification(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); }).catch(processCefError); } function onCopyLoadOrder(evt) { @@ -399,7 +399,7 @@ function onCopyLoadOrder(evt) { } loot.query('copyLoadOrder', plugins).then(function(){ - toast(loot.l10n.translate("The load order has been copied to the clipboard.")); + loot.Dialog.showNotification(loot.l10n.translate("The load order has been copied to the clipboard.")); }).catch(processCefError); } function onSwitchSidebarTab(evt) { @@ -590,11 +590,11 @@ function onConflictsFilter(evt) { } function onCopyMetadata(evt) { loot.query('copyMetadata', evt.target.getName()).then(function(){ - toast(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); + loot.Dialog.showNotification(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); }).catch(processCefError); } function onClearMetadata(evt) { - showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){ + loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){ if (result) { loot.query('clearPluginMetadata', evt.target.getName()).then(JSON.parse).then(function(result){ if (result) { @@ -613,7 +613,7 @@ function onClearMetadata(evt) { break; } } - toast(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName())); + loot.Dialog.showNotification(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName())); /* Now perform search again. If there is no current search, this won't do anything. */ document.getElementById('searchBar').search(); @@ -649,7 +649,7 @@ function onJumpToGeneralInfo(evt) { } function onContentRefresh(evt) { /* Send a query for updated load order and plugin header info. */ - showProgress(loot.l10n.translate('Refreshing data...')); + loot.Dialog.showProgress(loot.l10n.translate('Refreshing data...')); loot.query('getGameData').then(function(result){ /* Parse the data sent from C++. */ try { @@ -711,7 +711,7 @@ function onContentRefresh(evt) { /* Reapply filters. */ setFilteredUIData(); - closeProgressDialog(); + loot.Dialog.closeProgress(); }).catch(processCefError); } function onSearchOpen(evt) { diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index 23bec811..398492a0 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -4,8 +4,8 @@ function processCefError(err) { info than just the error message. Also, this can be used to catch any promise errors, not just CEF errors. */ console.log(err.stack); - closeProgressDialog(); - showMessageBox(loot.l10n.translate('Error'), err.message); + loot.Dialog.closeProgress(); + loot.Dialog.showMessage(loot.l10n.translate('Error'), err.message); } function showElement(element) { @@ -18,41 +18,8 @@ function hideElement(element) { element.classList.toggle('hidden', true); } } -function toast(text) { - var toast = document.getElementById('toast'); - toast.text = text; - toast.show(); -} -function showMessageDialog(title, text, positiveText, closeCallback) { - var dialog = document.createElement('loot-message-dialog'); - dialog.setButtonText(positiveText, loot.l10n.translate('Cancel')); - dialog.showModal(title, text, closeCallback); - document.body.appendChild(dialog); -} -function showMessageBox(title, text) { - var dialog = document.createElement('loot-message-dialog'); - dialog.setButtonText(loot.l10n.translate('OK')); - dialog.showModal(title, text); - document.body.appendChild(dialog); -} - -function showProgress(message) { - var progressDialog = document.getElementById('progressDialog'); - if (message) { - progressDialog.getElementsByTagName('p')[0].textContent = message; - } - if (!progressDialog.opened) { - progressDialog.showModal(); - } -} -function closeProgressDialog() { - var progressDialog = document.getElementById('progressDialog'); - if (progressDialog.opened) { - progressDialog.close(); - } -} function handleUnappliedChangesClose(change) { - showMessageDialog('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), function(result){ + loot.Dialog.askQuestion('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), function(result){ if (result) { /* Cancel any sorting and close any editors. Cheat by sending a cancelSort query for as many times as necessary. */ @@ -77,7 +44,7 @@ function getConflictingPlugins(pluginName) { } /* Now get conflicts for the plugin. */ - showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); + loot.Dialog.showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); return loot.query('getConflictingPlugins', pluginName).then(JSON.parse).then((result) => { if (result) { @@ -100,10 +67,10 @@ function getConflictingPlugins(pluginName) { } } } - closeProgressDialog(); + loot.Dialog.closeProgress(); return conflicts; } - closeProgressDialog(); + loot.Dialog.closeProgress(); return [pluginName]; }).catch(processCefError); } diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 8d02243f..f55eb924 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -146,7 +146,7 @@ function initVars() { loot.query('getSettings'), ]; - showProgress('Initialising user interface...'); + loot.Dialog.showProgress('Initialising user interface...'); Promise.all(parallelPromises).then(function(results) { try { loot.gameTypes = JSON.parse(results[0]); @@ -197,7 +197,7 @@ function initVars() { }).then(function(){ if (result) { return new Promise(function(resolve, reject){ - closeProgressDialog(); + loot.Dialog.closeProgress(); document.getElementById('settingsButton').click(); resolve(''); }); @@ -214,7 +214,7 @@ function initVars() { setTimeout(function() { document.getElementById('cardsNav').updateSize(); - closeProgressDialog(); + loot.Dialog.closeProgress(); }, 100); return ''; diff --git a/src/gui/html/js/l10n.js b/src/gui/html/js/l10n.js index 80eeebb7..ca3ff8f2 100644 --- a/src/gui/html/js/l10n.js +++ b/src/gui/html/js/l10n.js @@ -106,6 +106,18 @@ pluginItem.getElementById('editorIsOpenTooltip').textContent = l10n.translate('Editor Is Open'); } + function translateMessageDialogTemplate(l10n) { + /* Plugin List Item Template */ + let messageDialog = document.querySelector('link[rel="import"][href$="loot-message-dialog.html"]'); + if (messageDialog) { + messageDialog = messageDialog.import.querySelector('template').content; + } else { + messageDialog = document.querySelector('polymer-element[name="loot-message-dialog"]').querySelector('template').content; + } + messageDialog.getElementById('confirm').textContent = l10n.translate('OK'); + messageDialog.getElementById('dismiss').textContent = l10n.translate('Cancel'); + } + function translateFileRowTemplate(l10n) { /* File row template */ let fileRow = document.querySelector('link[rel="import"][href$="editable-table.html"]'); @@ -312,6 +324,7 @@ translatePluginCardTemplate(l10n); translatePluginEditorTemplate(l10n); translatePluginListItemTemplate(l10n); + translateMessageDialogTemplate(l10n); translateFileRowTemplate(l10n); translateMessageRowTemplate(l10n); From afaf6981cd293e83eb6e590cc8f6546ef25fbed6 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 19 Dec 2015 17:34:29 +0000 Subject: [PATCH 13/30] Started to refactor init.js Split initVars() into separate functions for its component promises and flatten the structure slightly. Also fixed uninitialised expected loot members and removed a few unnecessary globals and an unnecessary translation. --- .eslintrc.yml | 1 + src/gui/html/js/events.js | 10 +- src/gui/html/js/init.js | 346 +++++++++++++++++--------------------- 3 files changed, 164 insertions(+), 193 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index 9e3a5da1..a7b620b8 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -20,3 +20,4 @@ rules: strict: - 2 - global + guard-for-in: 0 diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 5e056f22..6675d70c 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -50,10 +50,12 @@ 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; + if (loot.settings && loot.settings.games) { + 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'); diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index f55eb924..c7bc4a2d 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -22,36 +22,18 @@ . */ 'use strict'; -var marked; -var l10n; - -function restoreFilterStates() { - if (loot.settings.filters && loot.filters) { - loot.filters.hideMessagelessPlugins = loot.settings.filters.hideMessagelessPlugins; - loot.filters.hideInactivePlugins = loot.settings.filters.hideInactivePlugins; - loot.filters.hideNotes = loot.settings.filters.hideNotes; - loot.filters.hideDoNotCleanMessages = loot.settings.filters.hideDoNotCleanMessages; - loot.filters.hideAllPluginMessages = loot.settings.filters.hideAllPluginMessages; - loot.filters.hideVersionNumbers = loot.settings.filters.hideVersionNumbers; - loot.filters.hideCRCs = loot.settings.filters.hideCRCs; - loot.filters.hideBashTags = loot.settings.filters.hideBashTags; - - document.getElementById('hideMessagelessPlugins').checked = loot.settings.filters.hideMessagelessPlugins; - document.getElementById('hideInactivePlugins').checked = loot.settings.filters.hideInactivePlugins; - document.getElementById('hideNotes').checked = loot.settings.filters.hideNotes; - document.getElementById('hideDoNotCleanMessages').checked = loot.settings.filters.hideDoNotCleanMessages; - document.getElementById('hideAllPluginMessages').checked = loot.settings.filters.hideAllPluginMessages; - document.getElementById('hideVersionNumbers').checked = loot.settings.filters.hideVersionNumbers; - document.getElementById('hideCRCs').checked = loot.settings.filters.hideCRCs; - document.getElementById('hideBashTags').checked = loot.settings.filters.hideBashTags; - } -} - function applyEnabledFilters() { if (!loot.filters) { return; } + if (loot.settings.filters) { + for (const filter in loot.settings.filters) { + loot.filters[filter] = loot.settings.filters[filter]; + document.getElementById(filter).checked = loot.filters[filter]; + } + } + if (loot.filters.hideMessagelessPlugins || loot.filters.hideInactivePlugins || loot.filters.hideNotes @@ -72,172 +54,158 @@ function applyEnabledFilters() { document.getElementById('hideBashTags').dispatchEvent(new Event('change')); } } -function initVars() { - loot.query('getVersion').then(function(result){ - try { - loot.version = JSON.parse(result); - /* The fourth part of the version string is the build number. Trim it. */ - var pos = loot.version.lastIndexOf('.'); - if (loot.version.length > pos + 1) { - document.getElementById('LOOTBuild').textContent = loot.version.substring(pos + 1); - } else { - document.getElementById('LOOTBuild').textContent = loot.l10n.translate('unknown'); - } - - loot.version = loot.version.substring(0, pos); - document.getElementById('LOOTVersion').textContent = loot.version; - document.getElementById('firstTimeLootVersion').textContent = loot.version; - } catch (e) { - console.log(e); - console.log('Response: ' + result); - } - }).catch(processCefError); - - loot.query('getLanguages').then(function(result){ - try { - loot.languages = JSON.parse(result); - - /* Now fill in language options. */ - var settingsLangSelect = document.getElementById('languageSelect'); - var messageLangSelect = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (messageLangSelect) { - messageLangSelect = messageLangSelect.import.querySelector('#messageRow').content.querySelector('.language'); - } else { - messageLangSelect = document.querySelector('#messageRow').content.querySelector('.language'); - } - - for (var i = 0; i < loot.languages.length; ++i) { - var settingsItem = document.createElement('paper-item'); - settingsItem.setAttribute('value', loot.languages[i].locale); - settingsItem.setAttribute('noink', ''); - settingsItem.textContent = loot.languages[i].name; - settingsLangSelect.appendChild(settingsItem); - messageLangSelect.appendChild(settingsItem.cloneNode(true)); - } - - messageLangSelect.setAttribute('value', messageLangSelect.firstElementChild.getAttribute('value')); - } catch (e) { - console.log(e); - console.log('Response: ' + result); - } - }).catch(processCefError); - - loot.query('getInitErrors').then(JSON.parse).then(function(result){ - if (result) { - var generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; - - result.forEach(function(message){ - var li = document.createElement('li'); - li.className = 'error'; - /* Use the Marked library for Markdown formatting support. */ - li.innerHTML = marked(message); - generalMessagesList.appendChild(li); - }); - - document.getElementById('filterTotalMessageNo').textContent = result.length; - document.getElementById('totalMessageNo').textContent = result.length; - document.getElementById('totalErrorNo').textContent = result.length; - } - - var parallelPromises = [ - loot.query('getGameTypes'), - loot.query('getInstalledGames'), - loot.query('getSettings'), - ]; - - loot.Dialog.showProgress('Initialising user interface...'); - Promise.all(parallelPromises).then(function(results) { - try { - loot.gameTypes = JSON.parse(results[0]); - } catch (e) { - console.log(e); - console.log('getGameTypes response: ' + results[0]); - } - - /* Fill in game row template's game type options. */ - var select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (select) { - select = select.import.querySelector('#gameRow').content.querySelector('.type') - } else { - select = document.querySelector('#gameRow').content.querySelector('.type'); - } - for (var j = 0; j < loot.gameTypes.length; ++j) { - var item = document.createElement('paper-item'); - item.setAttribute('value', loot.gameTypes[j]); - item.setAttribute('noink', ''); - item.textContent = loot.gameTypes[j]; - select.appendChild(item); - } - select.setAttribute('value', select.firstElementChild.getAttribute('value')); - - try { - setInstalledGames(JSON.parse(results[1])); - } catch (e) { - console.log(e); - console.log('getInstalledGames response: ' + results[1]); - } - - try { - loot.settings = JSON.parse(results[2]); - updateSettingsUI(); - restoreFilterStates(); - } catch (e) { - console.log(e); - console.log('getSettings response: ' + results[2]); - } - }).then(function(){ - /* Translate static text. */ - loot.l10n = new loot.Translator(loot.settings.language); - loot.l10n.load().then(() => { - loot.translateStaticText(loot.l10n); - /* Also need to update the settings UI. */ - updateSettingsUI(); - }).catch(processCefError); - }).then(function(){ - if (result) { - return new Promise(function(resolve, reject){ - loot.Dialog.closeProgress(); - document.getElementById('settingsButton').click(); - resolve(''); - }); - } else { - return loot.query('getGameData').then(function(result){ - var game = JSON.parse(result, loot.Plugin.fromJson); - loot.game.folder = game.folder; - loot.game.masterlist = game.masterlist; - loot.game.globalMessages = game.globalMessages; - loot.game.plugins = game.plugins; - document.getElementById('cardsNav').data = loot.game.plugins; - document.getElementById('main').lastElementChild.data = loot.game.plugins; - applyEnabledFilters(); - - setTimeout(function() { - document.getElementById('cardsNav').updateSize(); - loot.Dialog.closeProgress(); - }, 100); - - return ''; - }).catch(processCefError); - } - }).then(function(){ - if (!loot.settings.lastVersion || loot.settings.lastVersion != loot.version) { - document.getElementById('firstRun').showModal(); - } - }).catch(processCefError); - }).catch(processCefError); +function getVersion() { + return loot.query('getVersion').then(JSON.parse).then((result) => { + /* The fourth part of the version string is the build number. Trim it. */ + const pos = result.lastIndexOf('.'); + loot.version = result.substring(0, pos); + document.getElementById('LOOTVersion').textContent = loot.version; + document.getElementById('firstTimeLootVersion').textContent = loot.version; + document.getElementById('LOOTBuild').textContent = result.substring(pos + 1); + }); } -window.addEventListener('polymer-ready', function(e) { - /* Set the plugin list's scroll target to its parent. */ - document.getElementById('main').lastElementChild.scrollTarget = document.getElementById('main'); +function getLanguages() { + return loot.query('getLanguages').then(JSON.parse).then((result) => { + /* Now fill in language options. */ + const settingsLangSelect = document.getElementById('languageSelect'); + let messageLangSelect = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (messageLangSelect) { + messageLangSelect = messageLangSelect.import.querySelector('#messageRow').content.querySelector('.language'); + } else { + messageLangSelect = document.querySelector('#messageRow').content.querySelector('.language'); + } - /* Make sure settings are what I want. */ - marked.setOptions({ - gfm: true, - tables: true, - sanitize: true + for (let i = 0; i < result.length; ++i) { + const settingsItem = document.createElement('paper-item'); + settingsItem.setAttribute('value', result[i].locale); + settingsItem.setAttribute('noink', ''); + settingsItem.textContent = result[i].name; + settingsLangSelect.appendChild(settingsItem); + messageLangSelect.appendChild(settingsItem.cloneNode(true)); + } + + messageLangSelect.setAttribute('value', messageLangSelect.firstElementChild.getAttribute('value')); + }); +} + +function getInitErrors() { + return loot.query('getInitErrors').then(JSON.parse).then((result) => { + if (!result) { + return result; + } + const generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; + + result.forEach((message) => { + const li = document.createElement('li'); + li.className = 'error'; + /* Use the Marked library for Markdown formatting support. */ + li.innerHTML = window.marked(message); + generalMessagesList.appendChild(li); }); - setupEventHandlers(); - initVars(); -}, false); + + document.getElementById('filterTotalMessageNo').textContent = result.length; + document.getElementById('totalMessageNo').textContent = result.length; + document.getElementById('totalErrorNo').textContent = result.length; + + return result; + }); +} + +function getGameTypes() { + return loot.query('getGameTypes').then(JSON.parse).then((result) => { + /* Fill in game row template's game type options. */ + let select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (select) { + select = select.import.querySelector('#gameRow').content.querySelector('.type'); + } else { + select = document.querySelector('#gameRow').content.querySelector('.type'); + } + for (let j = 0; j < result.length; ++j) { + const item = document.createElement('paper-item'); + item.setAttribute('value', result[j]); + item.setAttribute('noink', ''); + item.textContent = result[j]; + select.appendChild(item); + } + select.setAttribute('value', select.firstElementChild.getAttribute('value')); + }); +} + +function getInstalledGames() { + return loot.query('getInstalledGames').then(JSON.parse).then(setInstalledGames); +} + +function getSettings() { + return loot.query('getSettings').then(JSON.parse).then((result) => { + loot.settings = result; + updateSettingsUI(); + }); +} + +function getGameData() { + return loot.query('getGameData').then((result) => { + const game = JSON.parse(result, loot.Plugin.fromJson); + loot.game = new loot.Game(game, loot.l10n); + document.getElementById('cardsNav').data = loot.game.plugins; + document.getElementById('main').lastElementChild.data = loot.game.plugins; + applyEnabledFilters(); + + setTimeout(() => { + document.getElementById('cardsNav').updateSize(); + loot.Dialog.closeProgress(); + }, 100); + }); +} + +function initialise() { + loot.Dialog.showProgress('Initialising user interface...'); + /* Set the plugin list's scroll target to its parent. */ + document.getElementById('pluginCardList').scrollTarget = document.getElementById('main'); + + /* Make sure settings are what I want. */ + window.marked.setOptions({ + gfm: true, + tables: true, + sanitize: true, + }); + setupEventHandlers(); + + loot.l10n = new loot.Translator(); + loot.l10n.load().then(() => { + loot.filters = new loot.Filters(loot.l10n); + loot.game = new loot.Game({}, loot.l10n); + }).then(() => { + return Promise.all([ + getVersion(), + getLanguages(), + getGameTypes(), + getInstalledGames(), + getSettings(), + ]); + }).then(() => { + /* Translate static text. */ + loot.l10n = new loot.Translator(loot.settings.language); + return loot.l10n.load(); + }).then(() => { + loot.translateStaticText(loot.l10n); + /* Also need to update the settings UI. */ + updateSettingsUI(); + }).then(() => { + return getInitErrors(); + }).then((result) => { + if (result) { + loot.Dialog.closeProgress(); + document.getElementById('settingsButton').click(); + return Promise.resolve(''); + } + return getGameData(); + }).then(() => { + if (!loot.settings.lastVersion || loot.settings.lastVersion !== loot.version) { + document.getElementById('firstRun').showModal(); + } + }).catch(processCefError); +} + +window.addEventListener('polymer-ready', initialise); From 693ececf1de57a0104e298038b1c1205d0ef58d9 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Mon, 21 Dec 2015 11:23:07 +0000 Subject: [PATCH 14/30] Reduce ESLint errors in helpers.js Also rename processCefError to handlePromiseError, which is more accurate. --- src/gui/html/js/events.js | 40 +++++----- src/gui/html/js/helpers.js | 145 ++++++++++++++++++------------------- src/gui/html/js/init.js | 2 +- 3 files changed, 93 insertions(+), 94 deletions(-) diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 6675d70c..c533309e 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -79,7 +79,7 @@ function onPluginIsDirtyChange(evt) { } } function saveFilterState(evt) { - loot.query('saveFilterState', evt.target.id, evt.target.checked).catch(processCefError); + loot.query('saveFilterState', evt.target.id, evt.target.checked).catch(handlePromiseError); } function onToggleDisplayCSS(evt) { var attr = 'data-hide-' + evt.target.getAttribute('data-class'); @@ -103,7 +103,7 @@ function onToggleBashTags(evt) { document.getElementById('searchBar').search(); } function onOpenLogLocation(evt) { - loot.query('openLogLocation').catch(processCefError); + loot.query('openLogLocation').catch(handlePromiseError); } function onChangeGame(evt) { /* Check that the selected game isn't the current one. */ @@ -147,10 +147,10 @@ function onChangeGame(evt) { } loot.Dialog.closeProgress(); - }).catch(processCefError); + }).catch(handlePromiseError); } function onOpenReadme(evt) { - loot.query('openReadme').catch(processCefError); + loot.query('openReadme').catch(handlePromiseError); } /* Masterlist update process, minus progress dialog. */ function updateMasterlistNoProgress() { @@ -180,13 +180,13 @@ function updateMasterlistNoProgress() { } else { loot.Dialog.showNotification(loot.l10n.translate('No masterlist update was necessary.')); } - }).catch(processCefError); + }).catch(handlePromiseError); } function onUpdateMasterlist(evt) { loot.Dialog.showProgress(loot.l10n.translate('Updating masterlist...')); updateMasterlistNoProgress().then(function(result){ loot.Dialog.closeProgress(); - }).catch(processCefError); + }).catch(handlePromiseError); } function onSortPlugins(evt) { if (document.body.hasAttribute('data-conflicts')) { @@ -255,8 +255,8 @@ function onSortPlugins(evt) { document.getElementById('gameMenu').setAttribute('disabled', ''); loot.Dialog.closeProgress(); } - }).catch(processCefError); - }).catch(processCefError); + }).catch(handlePromiseError); + }).catch(handlePromiseError); } function onApplySort(evt) { var loadOrder = []; @@ -277,7 +277,7 @@ function onApplySort(evt) { /* Enable changing game. */ document.getElementById('gameMenu').removeAttribute('disabled'); - }).catch(processCefError); + }).catch(handlePromiseError); } function onCancelSort(evt) { return loot.query('cancelSort').then(function(){ @@ -296,7 +296,7 @@ function onCancelSort(evt) { /* Enable changing game. */ document.getElementById('gameMenu').removeAttribute('disabled'); - }).catch(processCefError); + }).catch(handlePromiseError); } function onRedatePlugins(evt) { if (evt.target.hasAttribute('disabled')) { @@ -307,7 +307,7 @@ function onRedatePlugins(evt) { if (result) { loot.query('redatePlugins').then(function(response){ loot.Dialog.showNotification('Plugins were successfully redated.'); - }).catch(processCefError); + }).catch(handlePromiseError); } }); } @@ -336,7 +336,7 @@ function onClearAllMetadata(evt) { loot.Dialog.showNotification(loot.l10n.translate('All user-added metadata has been cleared.')); } - }).catch(processCefError); + }).catch(handlePromiseError); } }); } @@ -387,7 +387,7 @@ function onCopyContent(evt) { plugins: plugins }).then(function(){ loot.Dialog.showNotification(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); - }).catch(processCefError); + }).catch(handlePromiseError); } function onCopyLoadOrder(evt) { var plugins = []; @@ -402,7 +402,7 @@ function onCopyLoadOrder(evt) { loot.query('copyLoadOrder', plugins).then(function(){ loot.Dialog.showNotification(loot.l10n.translate("The load order has been copied to the clipboard.")); - }).catch(processCefError); + }).catch(handlePromiseError); } function onSwitchSidebarTab(evt) { if (evt.detail.isSelected) { @@ -451,7 +451,7 @@ function onCloseSettingsDialog(evt) { loot.settings = settings; updateSettingsUI(); - }).catch(processCefError); + }).catch(handlePromiseError); } else { /* Re-apply the existing settings to the settings dialog elements. */ updateSettingsUI(); @@ -501,7 +501,7 @@ function onEditorOpen(evt) { document.body.setAttribute('data-editors', numEditors); document.getElementById('cardsNav').updateSize(); - return loot.query('editorOpened').catch(processCefError); + return loot.query('editorOpened').catch(handlePromiseError); } function onEditorClose(evt) { /* evt.detail is true if the apply button was pressed. */ @@ -567,7 +567,7 @@ function onEditorClose(evt) { document.body.setAttribute('data-editors', numEditors); } document.getElementById('cardsNav').updateSize(); - }).catch(processCefError); + }).catch(handlePromiseError); } function onConflictsFilter(evt) { /* Deactivate any existing plugin conflict filter. */ @@ -593,7 +593,7 @@ function onConflictsFilter(evt) { function onCopyMetadata(evt) { loot.query('copyMetadata', evt.target.getName()).then(function(){ loot.Dialog.showNotification(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); - }).catch(processCefError); + }).catch(handlePromiseError); } function onClearMetadata(evt) { loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){ @@ -620,7 +620,7 @@ function onClearMetadata(evt) { do anything. */ document.getElementById('searchBar').search(); } - }).catch(processCefError); + }).catch(handlePromiseError); } }); } @@ -714,7 +714,7 @@ function onContentRefresh(evt) { setFilteredUIData(); loot.Dialog.closeProgress(); - }).catch(processCefError); + }).catch(handlePromiseError); } function onSearchOpen(evt) { document.getElementById('mainToolbar').classList.add('search'); diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index 398492a0..cfe8cd99 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -1,44 +1,43 @@ 'use strict'; -function processCefError(err) { - /* Error.stack seems to be Chromium-specific. It gives a lot more useful - info than just the error message. Also, this can be used to catch any - promise errors, not just CEF errors. */ - console.log(err.stack); - loot.Dialog.closeProgress(); - loot.Dialog.showMessage(loot.l10n.translate('Error'), err.message); +function handlePromiseError(err) { + /* Error.stack seems to be Chromium-specific. */ + console.log(err.stack); + loot.Dialog.closeProgress(); + loot.Dialog.showMessage(loot.l10n.translate('Error'), err.message); } function showElement(element) { - if (element != null) { - element.classList.toggle('hidden', false); - } + if (element !== null) { + element.classList.toggle('hidden', false); + } } function hideElement(element) { - if (element != null) { - element.classList.toggle('hidden', true); - } + if (element !== null) { + element.classList.toggle('hidden', true); + } } function handleUnappliedChangesClose(change) { - loot.Dialog.askQuestion('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), function(result){ - if (result) { - /* Cancel any sorting and close any editors. Cheat by sending a - cancelSort query for as many times as necessary. */ - var queries = []; - var numQueries = 0; - if (!document.getElementById('applySortButton').classList.contains('hidden')) { - numQueries += 1; - } - numQueries += document.body.getAttribute('data-editors'); - for (var i = 0; i < numQueries; ++i) { - queries.push(loot.query('cancelSort')); - } - Promise.all(queries).then(function(){ - window.close(); - }).catch(processCefError); - } - }); + loot.Dialog.askQuestion('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), (result) => { + if (!result) { + return; + } + /* Cancel any sorting and close any editors. Cheat by sending a + cancelSort query for as many times as necessary. */ + const queries = []; + let numQueries = 0; + if (!document.getElementById('applySortButton').classList.contains('hidden')) { + numQueries += 1; + } + numQueries += document.body.getAttribute('data-editors'); + for (let i = 0; i < numQueries; ++i) { + queries.push(loot.query('cancelSort')); + } + Promise.all(queries).then(() => { + window.close(); + }).catch(handlePromiseError); + }); } -function getConflictingPlugins(pluginName) { +function getConflictingPlugins(pluginName) { if (!pluginName) { return Promise.resolve([]); } @@ -72,9 +71,9 @@ function getConflictingPlugins(pluginName) { } loot.Dialog.closeProgress(); return [pluginName]; - }).catch(processCefError); + }).catch(handlePromiseError); } -function setFilteredUIData(filtersState) { +function setFilteredUIData() { getConflictingPlugins(loot.filters.conflictTargetPluginName).then((conflictingPluginNames) => { loot.filters.conflictingPluginNames = conflictingPluginNames; return loot.game.plugins.filter(loot.filters.pluginFilter, loot.filters); @@ -121,17 +120,17 @@ function updateSelectedGame(gameFolder) { /* Call whenever installedGames is changed or game menu is rewritten. */ function updateEnabledGames(installedGames) { - /* Update the disabled games in the game menu. */ - var gameMenuItems = document.getElementById('gameMenu').children; - for (var i = 0; i < gameMenuItems.length; ++i) { - if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) == -1) { - gameMenuItems[i].setAttribute('disabled', true); - gameMenuItems[i].removeEventListener('click', onChangeGame, false); - } else { - gameMenuItems[i].removeAttribute('disabled'); - gameMenuItems[i].addEventListener('click', onChangeGame, false); - } + /* Update the disabled games in the game menu. */ + const gameMenuItems = document.getElementById('gameMenu').children; + for (let i = 0; i < gameMenuItems.length; ++i) { + if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) === -1) { + gameMenuItems[i].setAttribute('disabled', true); + gameMenuItems[i].removeEventListener('click', onChangeGame); + } else { + gameMenuItems[i].removeAttribute('disabled'); + gameMenuItems[i].addEventListener('click', onChangeGame); } + } } function setInstalledGames(installedGames) { loot.installedGames = installedGames; @@ -139,38 +138,38 @@ function setInstalledGames(installedGames) { } /* Call whenever settings are changed. */ function updateSettingsUI() { - var gameSelect = document.getElementById('defaultGameSelect'); - var gameMenu = document.getElementById('gameMenu'); - var gameTable = document.getElementById('gameTable'); + const gameSelect = document.getElementById('defaultGameSelect'); + const gameMenu = document.getElementById('gameMenu'); + const gameTable = document.getElementById('gameTable'); - /* First make sure game listing elements don't have any existing entries. */ - while (gameSelect.children.length > 1) { - gameSelect.removeChild(gameSelect.lastElementChild); - } - while (gameMenu.firstElementChild) { - gameMenu.firstElementChild.removeEventListener('click', onChangeGame, false); - gameMenu.removeChild(gameMenu.firstElementChild); - } - gameTable.clear(); + /* First make sure game listing elements don't have any existing entries. */ + while (gameSelect.children.length > 1) { + gameSelect.removeChild(gameSelect.lastElementChild); + } + while (gameMenu.firstElementChild) { + gameMenu.firstElementChild.removeEventListener('click', onChangeGame); + gameMenu.removeChild(gameMenu.firstElementChild); + } + gameTable.clear(); - /* Now fill with new values. */ - loot.settings.games.forEach(function(game){ - var menuItem = document.createElement('paper-item'); - menuItem.setAttribute('value', game.folder); - menuItem.setAttribute('noink', ''); - menuItem.textContent = game.name; - gameMenu.appendChild(menuItem); - gameSelect.appendChild(menuItem.cloneNode(true)); + /* Now fill with new values. */ + loot.settings.games.forEach((game) => { + const menuItem = document.createElement('paper-item'); + menuItem.setAttribute('value', game.folder); + menuItem.setAttribute('noink', ''); + menuItem.textContent = game.name; + gameMenu.appendChild(menuItem); + gameSelect.appendChild(menuItem.cloneNode(true)); - var row = gameTable.addRow(game); - gameTable.setReadOnly(row, ['name','folder','type']); - }); + const row = gameTable.addRow(game); + gameTable.setReadOnly(row, ['name', 'folder', 'type']); + }); - gameSelect.value = loot.settings.game; - document.getElementById('languageSelect').value = loot.settings.language; - document.getElementById('enableDebugLogging').checked = loot.settings.enableDebugLogging; - document.getElementById('updateMasterlist').checked = loot.settings.updateMasterlist; + gameSelect.value = loot.settings.game; + document.getElementById('languageSelect').value = loot.settings.language; + document.getElementById('enableDebugLogging').checked = loot.settings.enableDebugLogging; + document.getElementById('updateMasterlist').checked = loot.settings.updateMasterlist; - updateEnabledGames(loot.installedGames); - updateSelectedGame(loot.game.folder); + updateEnabledGames(loot.installedGames); + updateSelectedGame(loot.game.folder); } diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index c7bc4a2d..195f3c23 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -205,7 +205,7 @@ function initialise() { if (!loot.settings.lastVersion || loot.settings.lastVersion !== loot.version) { document.getElementById('firstRun').showModal(); } - }).catch(processCefError); + }).catch(handlePromiseError); } window.addEventListener('polymer-ready', initialise); From f0df5e707f2bd43c30a4b218e52944b2f5206ee9 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Mon, 21 Dec 2015 11:58:45 +0000 Subject: [PATCH 15/30] Fix most ESLint errors in events.js The remaining errors are due to undefined functions. --- src/gui/html/js/events.js | 1283 ++++++++++++++++++------------------- 1 file changed, 632 insertions(+), 651 deletions(-) diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index c533309e..1318c91a 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -79,649 +79,630 @@ function onPluginIsDirtyChange(evt) { } } function saveFilterState(evt) { - loot.query('saveFilterState', evt.target.id, evt.target.checked).catch(handlePromiseError); + loot.query('saveFilterState', evt.target.id, evt.target.checked).catch(handlePromiseError); } function onToggleDisplayCSS(evt) { - var attr = 'data-hide-' + evt.target.getAttribute('data-class'); - if (evt.target.checked) { - document.getElementById('main').setAttribute(attr, true); - } else { - document.getElementById('main').removeAttribute(attr); - } + const attr = 'data-hide-' + evt.target.getAttribute('data-class'); + if (evt.target.checked) { + document.getElementById('main').setAttribute(attr, true); + } else { + document.getElementById('main').removeAttribute(attr); + } - if (evt.target.id != 'hideBashTags') { - /* Now perform search again. If there is no current search, this won't - do anything. */ - document.getElementById('searchBar').search(); - } -} -function onToggleBashTags(evt) { - onToggleDisplayCSS(evt); - document.getElementById('main').lastElementChild.updateSize(); + if (evt.target.id !== 'hideBashTags') { /* Now perform search again. If there is no current search, this won't do anything. */ document.getElementById('searchBar').search(); + } +} +function onToggleBashTags(evt) { + onToggleDisplayCSS(evt); + document.getElementById('main').lastElementChild.updateSize(); + /* Now perform search again. If there is no current search, this won't + do anything. */ + document.getElementById('searchBar').search(); } function onOpenLogLocation(evt) { - loot.query('openLogLocation').catch(handlePromiseError); + loot.query('openLogLocation').catch(handlePromiseError); } function onChangeGame(evt) { - /* Check that the selected game isn't the current one. */ - if (evt.target.className.indexOf('core-selected') != -1) { - return; + /* Check that the selected game isn't the current one. */ + if (evt.target.className.indexOf('core-selected') !== -1) { + return; + } + + /* Send off a CEF query with the folder name of the new game. */ + loot.Dialog.showProgress(loot.l10n.translate('Loading game data...')); + loot.query('changeGame', evt.currentTarget.getAttribute('value')).then((result) => { + /* Filters should be re-applied on game change, except the conflicts + filter. Don't need to deactivate the others beforehand. Strictly not + deactivating the conflicts filter either, just resetting it's value. + */ + document.body.removeAttribute('data-conflicts'); + + /* Clear the UI of all existing game-specific data. Also + clear the card and li variables for each plugin object. */ + const globalMessages = document.getElementById('summary').getElementsByTagName('ul')[0]; + while (globalMessages.firstElementChild) { + globalMessages.removeChild(globalMessages.firstElementChild); } - /* Send off a CEF query with the folder name of the new game. */ - loot.Dialog.showProgress(loot.l10n.translate('Loading game data...')); - loot.query('changeGame', evt.currentTarget.getAttribute('value')).then(function(result){ - /* Filters should be re-applied on game change, except the conflicts - filter. Don't need to deactivate the others beforehand. Strictly not - deactivating the conflicts filter either, just resetting it's value. - */ - document.body.removeAttribute('data-conflicts'); + /* Parse the data sent from C++. */ + const gameInfo = JSON.parse(result, loot.Plugin.fromJson); + loot.game.folder = gameInfo.folder; + loot.game.masterlist = gameInfo.masterlist; + loot.game.globalMessages = gameInfo.globalMessages; + loot.game.plugins = gameInfo.plugins; - /* Clear the UI of all existing game-specific data. Also - clear the card and li variables for each plugin object. */ - var globalMessages = document.getElementById('summary').getElementsByTagName('ul')[0]; - while (globalMessages.firstElementChild) { - globalMessages.removeChild(globalMessages.firstElementChild); - } + /* Reset virtual list positions. */ + document.getElementById('cardsNav').scrollToItem(0); + document.getElementById('main').lastElementChild.scrollToItem(0); - /* Parse the data sent from C++. */ - try { - var gameInfo = JSON.parse(result, loot.Plugin.fromJson); - loot.game.folder = gameInfo.folder; - loot.game.masterlist = gameInfo.masterlist; - loot.game.globalMessages = gameInfo.globalMessages; - loot.game.plugins = gameInfo.plugins; + /* Now update virtual lists. */ + setFilteredUIData(); - /* Reset virtual list positions. */ - document.getElementById('cardsNav').scrollToItem(0); - document.getElementById('main').lastElementChild.scrollToItem(0); - - /* Now update virtual lists. */ - setFilteredUIData(); - } catch (e) { - console.log(e); - console.log('changeGame response: ' + result); - } - - loot.Dialog.closeProgress(); - }).catch(handlePromiseError); + loot.Dialog.closeProgress(); + }).catch(handlePromiseError); } function onOpenReadme(evt) { - loot.query('openReadme').catch(handlePromiseError); + loot.query('openReadme').catch(handlePromiseError); } /* Masterlist update process, minus progress dialog. */ function updateMasterlistNoProgress() { - return loot.query('updateMasterlist').then(JSON.parse).then(function(result){ - if (result) { - /* Update JS variables. */ - loot.game.masterlist = result.masterlist; - loot.game.globalMessages = result.globalMessages; + return loot.query('updateMasterlist').then(JSON.parse).then((result) => { + if (result) { + /* Update JS variables. */ + loot.game.masterlist = result.masterlist; + loot.game.globalMessages = result.globalMessages; - result.plugins.forEach(function(plugin){ - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == plugin.name) { - loot.game.plugins[i].isDirty = plugin.isDirty; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].masterlist = plugin.masterlist; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].tags = plugin.tags; - break; - } - } - }); - /* Hack to stop cards overlapping. */ - document.getElementById('main').lastElementChild.updateSize(); - - loot.Dialog.showNotification(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision)); - } else { - loot.Dialog.showNotification(loot.l10n.translate('No masterlist update was necessary.')); + result.plugins.forEach((plugin) => { + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name === plugin.name) { + loot.game.plugins[i].isDirty = plugin.isDirty; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; + loot.game.plugins[i].masterlist = plugin.masterlist; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].priority = plugin.priority; + loot.game.plugins[i].tags = plugin.tags; + break; + } } - }).catch(handlePromiseError); -} -function onUpdateMasterlist(evt) { - loot.Dialog.showProgress(loot.l10n.translate('Updating masterlist...')); - updateMasterlistNoProgress().then(function(result){ - loot.Dialog.closeProgress(); - }).catch(handlePromiseError); -} -function onSortPlugins(evt) { - if (document.body.hasAttribute('data-conflicts')) { - /* Deactivate any existing plugin conflict filter. */ - for (var i = 0; i < loot.game.plugins.length; ++i) { - loot.game.plugins[i].isConflictFilterChecked = false; - } - /* Un-highlight any existing filter plugin. */ - var cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); - for (var i = 0; i < cards.length; ++i) { - cards[i].classList.toggle('highlight', false); - } - document.body.removeAttribute('data-conflicts'); - } + }); + /* Hack to stop cards overlapping. */ + document.getElementById('main').lastElementChild.updateSize(); - var promise = Promise.resolve(''); - if (loot.settings.updateMasterlist) { - promise = promise.then(updateMasterlistNoProgress()); - } - promise.then(function(){ - loot.Dialog.showProgress(loot.l10n.translate('Sorting plugins...')); - loot.query('sortPlugins').then(JSON.parse).then(function(result){ - if (result) { - loot.game.oldLoadOrder = loot.game.plugins; - loot.game.loadOrder = []; - result.forEach(function(plugin){ - var found = false; - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == plugin.name) { - loot.game.plugins[i].crc = plugin.crc; - loot.game.plugins[i].isEmpty = plugin.isEmpty; - - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - loot.game.loadOrder.push(loot.game.plugins[i]); - - found = true; - break; - } - } - if (!found) { - loot.game.plugins.push(new loot.Plugin(plugin)); - loot.game.loadOrder.push(loot.game.plugins[loot.game.plugins.length - 1]); - } - }); - - if (loot.settings.neverTellMeTheOdds) { - /* Array shuffler from */ - for(var j, x, i = loot.game.loadOrder.length; i; j = Math.floor(Math.random() * i), x = loadOrder[--i], loot.game.loadOrder[i] = loot.game.loadOrder[j], loot.game.loadOrder[j] = x); - } - - /* Now update the UI for the new order. */ - loot.game.plugins = loot.game.loadOrder; - setFilteredUIData(); - - /* Now hide the masterlist update buttons, and display the accept and - cancel sort buttons. */ - hideElement(document.getElementById('updateMasterlistButton')); - hideElement(document.getElementById('sortButton')); - showElement(document.getElementById('applySortButton')); - showElement(document.getElementById('cancelSortButton')); - - /* Disable changing game. */ - document.getElementById('gameMenu').setAttribute('disabled', ''); - loot.Dialog.closeProgress(); - } - }).catch(handlePromiseError); - }).catch(handlePromiseError); -} -function onApplySort(evt) { - var loadOrder = []; - loot.game.plugins.forEach(function(plugin){ - loadOrder.push(plugin.name); - }); - return loot.query('applySort', loadOrder).then(function(result){ - /* Remove old load order storage. */ - delete loot.game.loadOrder; - delete loot.game.oldLoadOrder; - - /* Now show the masterlist update buttons, and hide the accept and - cancel sort buttons. */ - showElement(document.getElementById('updateMasterlistButton')); - showElement(document.getElementById('sortButton')); - hideElement(document.getElementById('applySortButton')); - hideElement(document.getElementById('cancelSortButton')); - - /* Enable changing game. */ - document.getElementById('gameMenu').removeAttribute('disabled'); - }).catch(handlePromiseError); -} -function onCancelSort(evt) { - return loot.query('cancelSort').then(function(){ - /* Sort UI elements again according to stored old load order. */ - loot.game.plugins = loot.game.oldLoadOrder; - setFilteredUIData(); - delete loot.game.loadOrder; - delete loot.game.oldLoadOrder; - - /* Now show the masterlist update buttons, and hide the accept and - cancel sort buttons. */ - showElement(document.getElementById('updateMasterlistButton')); - showElement(document.getElementById('sortButton')); - hideElement(document.getElementById('applySortButton')); - hideElement(document.getElementById('cancelSortButton')); - - /* Enable changing game. */ - document.getElementById('gameMenu').removeAttribute('disabled'); - }).catch(handlePromiseError); -} -function onRedatePlugins(evt) { - if (evt.target.hasAttribute('disabled')) { - return; - } - - loot.Dialog.askQuestion(loot.l10n.translate('Redate Plugins?'), loot.l10n.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?'), loot.l10n.translate('Redate'), function(result){ - if (result) { - loot.query('redatePlugins').then(function(response){ - loot.Dialog.showNotification('Plugins were successfully redated.'); - }).catch(handlePromiseError); - } - }); -} -function onClearAllMetadata(evt) { - loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from all plugins?'), loot.l10n.translate('Clear'), function(result){ - if (result) { - loot.query('clearAllMetadata').then(JSON.parse).then(function(result){ - if (result) { - /* Need to empty the UI-side user metadata. */ - result.forEach(function(plugin){ - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == plugin.name) { - loot.game.plugins[i].userlist = undefined; - loot.game.plugins[i].editor = undefined; - - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - break; - } - } - }); - - loot.Dialog.showNotification(loot.l10n.translate('All user-added metadata has been cleared.')); - } - }).catch(handlePromiseError); - } - }); -} -function onCopyContent(evt) { - var messages = []; - var plugins = []; - - if (loot.game) { - if (loot.game.globalMessages) { - loot.game.globalMessages.forEach(function(message){ - var m = {}; - messages.push({ - type: message.type, - content: message.content[0].str - }); - }); - } - if (loot.game.plugins) { - loot.game.plugins.forEach(function(plugin){ - plugins.push({ - name: plugin.name, - crc: plugin.crc, - version: plugin.version, - isActive: plugin.isActive, - isEmpty: plugin.isEmpty, - loadsArchive: plugin.loadsArchive, - - priority: plugin.priority, - isPriorityGlobal: plugin.isPriorityGlobal, - messages: plugin.messages, - tags: plugin.tags, - isDirty: plugin.isDirty - }); - }); - } + loot.Dialog.showNotification(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision)); } else { - var message = document.getElementById('summary').getElementsByTagName('ul')[0].firstElementChild; - if (message) { - messages.push({ - type: 'error', - content: message.textContent - }); - } + loot.Dialog.showNotification(loot.l10n.translate('No masterlist update was necessary.')); } - - loot.query('copyContent', { - messages: messages, - plugins: plugins - }).then(function(){ - loot.Dialog.showNotification(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); - }).catch(handlePromiseError); + }).catch(handlePromiseError); } -function onCopyLoadOrder(evt) { - var plugins = []; - - if (loot.game) { - if (loot.game.plugins) { - loot.game.plugins.forEach(function(plugin){ - plugins.push(plugin.name); - }); - } - } - - loot.query('copyLoadOrder', plugins).then(function(){ - loot.Dialog.showNotification(loot.l10n.translate("The load order has been copied to the clipboard.")); - }).catch(handlePromiseError); +function onUpdateMasterlist() { + loot.Dialog.showProgress(loot.l10n.translate('Updating masterlist...')); + updateMasterlistNoProgress().then(() => { + loot.Dialog.closeProgress(); + }).catch(handlePromiseError); } -function onSwitchSidebarTab(evt) { - if (evt.detail.isSelected) { - document.getElementById(evt.target.selected).parentElement.selected = evt.target.selected; - } -} -function onShowAboutDialog(evt) { - document.getElementById('about').showModal(); -} -function areSettingsValid() { - /* Validate inputs individually. */ - var inputs = document.getElementById('settingsDialog').getElementsByTagName('loot-validated-input'); - for (var i = 0; i < inputs.length; ++i) { - if (!inputs[i].checkValidity()) { - return false; - } - } - return true; -} -function onCloseSettingsDialog(evt) { - if (evt.target.classList.contains('accept')) { - if (!areSettingsValid()) { - return; - } - - /* Update the JS variable values. */ - var settings = { - enableDebugLogging: document.getElementById('enableDebugLogging').checked, - game: document.getElementById('defaultGameSelect').value, - games: document.getElementById('gameTable').getRowsData(false), - language: document.getElementById('languageSelect').value, - lastGame: loot.settings.lastGame, - updateMasterlist: document.getElementById('updateMasterlist').checked, - filters: loot.settings.filters, - }; - - /* Send the settings back to the C++ side. */ - loot.query('closeSettings', settings).then(function(result){ - - try { - setInstalledGames(JSON.parse(result)); - } catch (e) { - console.log(e); - console.log('getInstalledGames response: ' + results[1]); - } - - loot.settings = settings; - updateSettingsUI(); - }).catch(handlePromiseError); - } else { - /* Re-apply the existing settings to the settings dialog elements. */ - updateSettingsUI(); - } - evt.target.parentElement.close(); -} -function onShowSettingsDialog(evt) { - document.getElementById('settingsDialog').showModal(); -} -function onFocusSearch(evt) { - if (evt.ctrlKey && evt.keyCode == 70) { //'f' - document.getElementById('mainToolbar').classList.add('search'); - document.getElementById('searchBar').focusInput(); - } -} -function onEditorOpen(evt) { - /* Set up drag 'n' drop event handlers. */ - var elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); - for (var i = 0; i < elements.length; ++i) { - elements[i].draggable = true; - elements[i].addEventListener('dragstart', elements[i].onDragStart, false); - } - - /* Now show editor. */ - evt.target.classList.toggle('flip'); - - /* Enable priority hover in plugins list and enable header - buttons if this is the only editor instance. */ - var numEditors = 0; - if (document.body.hasAttribute('data-editors')) { - numEditors = parseInt(document.body.getAttribute('data-editors'), 10); - } - ++numEditors; - - if (numEditors == 1) { - /* Set the edit mode toggle attribute. */ - document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); - /* Disable the toolbar elements. */ - document.getElementById('wipeUserlistButton').setAttribute('disabled', ''); - document.getElementById('copyContentButton').setAttribute('disabled', ''); - document.getElementById('refreshContentButton').setAttribute('disabled', ''); - document.getElementById('settingsButton').setAttribute('disabled', ''); - document.getElementById('gameMenu').setAttribute('disabled', ''); - document.getElementById('updateMasterlistButton').setAttribute('disabled', ''); - document.getElementById('sortButton').setAttribute('disabled', ''); - } - document.body.setAttribute('data-editors', numEditors); - document.getElementById('cardsNav').updateSize(); - - return loot.query('editorOpened').catch(handlePromiseError); -} -function onEditorClose(evt) { - /* evt.detail is true if the apply button was pressed. */ - var promise; - if (evt.detail) { - /* Need to record the editor control values and work out what's - changed, and update any UI elements necessary. Offload the - majority of the work to the C++ side of things. */ - - var edits = evt.target.readFromEditor(evt.target.data); - promise = loot.query('editorClosed', edits).then(JSON.parse).then(function(result){ - if (result) { - evt.target.data.priority = result.priority; - evt.target.data.isPriorityGlobal = result.isPriorityGlobal; - evt.target.data.messages = result.messages; - evt.target.data.tags = result.tags; - evt.target.data.isDirty = result.isDirty; - - evt.target.data.userlist = edits.userlist; - - /* Now perform search again. If there is no current search, this won't - do anything. */ - document.getElementById('searchBar').search(); - } - }); - } else { - /* Don't need to record changes, but still need to notify C++ side that - the editor has been closed. */ - promise = loot.query('editorClosed'); - } - promise.then(function(){ - delete evt.target.data.editor; - - /* Now hide editor. */ - evt.target.classList.toggle('flip'); - evt.target.data.isEditorOpen = false; - - /* Remove drag 'n' drop event handlers. */ - var elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); - for (var i = 0; i < elements.length; ++i) { - elements[i].removeAttribute('draggable'); - elements[i].removeEventListener('dragstart', elements[i].onDragStart, false); - } - - /* Disable priority hover in plugins list and enable header - buttons if this is the only editor instance. */ - var numEditors = parseInt(document.body.getAttribute('data-editors'), 10); - --numEditors; - - if (numEditors == 0) { - document.body.removeAttribute('data-editors'); - /* Set the edit mode toggle attribute. */ - document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); - /* Re-enable toolbar elements. */ - document.getElementById('wipeUserlistButton').removeAttribute('disabled'); - document.getElementById('copyContentButton').removeAttribute('disabled'); - document.getElementById('refreshContentButton').removeAttribute('disabled'); - document.getElementById('settingsButton').removeAttribute('disabled'); - document.getElementById('gameMenu').removeAttribute('disabled'); - document.getElementById('updateMasterlistButton').removeAttribute('disabled'); - document.getElementById('sortButton').removeAttribute('disabled'); - } else { - document.body.setAttribute('data-editors', numEditors); - } - document.getElementById('cardsNav').updateSize(); - }).catch(handlePromiseError); -} -function onConflictsFilter(evt) { +function onSortPlugins() { + if (document.body.hasAttribute('data-conflicts')) { /* Deactivate any existing plugin conflict filter. */ - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].id != evt.target.id) { - loot.game.plugins[i].isConflictFilterChecked = false; - } + for (let i = 0; i < loot.game.plugins.length; ++i) { + loot.game.plugins[i].isConflictFilterChecked = false; } /* Un-highlight any existing filter plugin. */ - var cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); - for (var i = 0; i < cards.length; ++i) { - cards[i].classList.toggle('highlight', false); + const cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); + for (let i = 0; i < cards.length; ++i) { + cards[i].classList.toggle('highlight', false); } - /* evt.detail is true if the filter has been activated. */ - if (evt.detail) { - document.body.setAttribute('data-conflicts', evt.target.getName()); - evt.target.classList.toggle('highlight', true); - } else { - document.body.removeAttribute('data-conflicts'); + document.body.removeAttribute('data-conflicts'); + } + + let promise = Promise.resolve(''); + if (loot.settings.updateMasterlist) { + promise = promise.then(updateMasterlistNoProgress()); + } + promise.then(() => { + loot.Dialog.showProgress(loot.l10n.translate('Sorting plugins...')); + return loot.query('sortPlugins').then(JSON.parse); + }).then((result) => { + if (!result) { + return; } - setFilteredUIData(evt); -} -function onCopyMetadata(evt) { - loot.query('copyMetadata', evt.target.getName()).then(function(){ - loot.Dialog.showNotification(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); - }).catch(handlePromiseError); -} -function onClearMetadata(evt) { - loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){ - if (result) { - loot.query('clearPluginMetadata', evt.target.getName()).then(JSON.parse).then(function(result){ - if (result) { - /* Need to empty the UI-side user metadata. */ - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].id == evt.target.id) { - loot.game.plugins[i].userlist = undefined; - loot.game.plugins[i].editor = undefined; + loot.game.oldLoadOrder = loot.game.plugins; + loot.game.loadOrder = []; + result.forEach((plugin) => { + let found = false; + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name === plugin.name) { + loot.game.plugins[i].crc = plugin.crc; + loot.game.plugins[i].isEmpty = plugin.isEmpty; - loot.game.plugins[i].priority = result.priority; - loot.game.plugins[i].isPriorityGlobal = result.isPriorityGlobal; - loot.game.plugins[i].messages = result.messages; - loot.game.plugins[i].tags = result.tags; - loot.game.plugins[i].isDirty = result.isDirty; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; - break; - } - } - loot.Dialog.showNotification(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName())); - /* Now perform search again. If there is no current search, this won't - do anything. */ - document.getElementById('searchBar').search(); - } - }).catch(handlePromiseError); + loot.game.loadOrder.push(loot.game.plugins[i]); + + found = true; + break; } + } + if (!found) { + loot.game.plugins.push(new loot.Plugin(plugin)); + loot.game.loadOrder.push(loot.game.plugins[loot.game.plugins.length - 1]); + } }); -} -function onSidebarClick(evt) { - if (evt.target.hasAttribute('data-index')) { - document.getElementById('main').lastElementChild.scrollToItem(evt.target.getAttribute('data-index')); - if (evt.type == 'dblclick') { - var card = document.getElementById(evt.target.getAttribute('data-id')); - if (!card.classList.contains('flip')) { - document.getElementById(evt.target.getAttribute('data-id')).onShowEditor(); - } - } + /* Now update the UI for the new order. */ + loot.game.plugins = loot.game.loadOrder; + setFilteredUIData(); + + /* Now hide the masterlist update buttons, and display the accept and + cancel sort buttons. */ + hideElement(document.getElementById('updateMasterlistButton')); + hideElement(document.getElementById('sortButton')); + showElement(document.getElementById('applySortButton')); + showElement(document.getElementById('cancelSortButton')); + + /* Disable changing game. */ + document.getElementById('gameMenu').setAttribute('disabled', ''); + loot.Dialog.closeProgress(); + }).catch(handlePromiseError); +} +function onApplySort() { + const loadOrder = []; + loot.game.plugins.forEach((plugin) => { + loadOrder.push(plugin.name); + }); + return loot.query('applySort', loadOrder).then((result) => { + /* Remove old load order storage. */ + delete loot.game.loadOrder; + delete loot.game.oldLoadOrder; + + /* Now show the masterlist update buttons, and hide the accept and + cancel sort buttons. */ + showElement(document.getElementById('updateMasterlistButton')); + showElement(document.getElementById('sortButton')); + hideElement(document.getElementById('applySortButton')); + hideElement(document.getElementById('cancelSortButton')); + + /* Enable changing game. */ + document.getElementById('gameMenu').removeAttribute('disabled'); + }).catch(handlePromiseError); +} +function onCancelSort(evt) { + return loot.query('cancelSort').then(() => { + /* Sort UI elements again according to stored old load order. */ + loot.game.plugins = loot.game.oldLoadOrder; + setFilteredUIData(); + delete loot.game.loadOrder; + delete loot.game.oldLoadOrder; + + /* Now show the masterlist update buttons, and hide the accept and + cancel sort buttons. */ + showElement(document.getElementById('updateMasterlistButton')); + showElement(document.getElementById('sortButton')); + hideElement(document.getElementById('applySortButton')); + hideElement(document.getElementById('cancelSortButton')); + + /* Enable changing game. */ + document.getElementById('gameMenu').removeAttribute('disabled'); + }).catch(handlePromiseError); +} +function onRedatePlugins(evt) { + if (evt.target.hasAttribute('disabled')) { + return; + } + + loot.Dialog.askQuestion(loot.l10n.translate('Redate Plugins?'), loot.l10n.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?'), loot.l10n.translate('Redate'), (result) => { + if (result) { + loot.query('redatePlugins').then(() => { + loot.Dialog.showNotification('Plugins were successfully redated.'); + }).catch(handlePromiseError); } + }); } -function onQuit(evt) { - if (!document.getElementById('applySortButton').classList.contains('hidden')) { - handleUnappliedChangesClose(loot.l10n.translate('sorted load order')); - } else if (document.body.hasAttribute('data-editors')) { - handleUnappliedChangesClose(loot.l10n.translate('metadata edits')); - } else { - window.close(); +function onClearAllMetadata() { + loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from all plugins?'), loot.l10n.translate('Clear'), (result) => { + if (!result) { + return; } -} -function onJumpToGeneralInfo(evt) { - window.location.hash = ''; - document.getElementById('main').scrollTop = 0; -} -function onContentRefresh(evt) { - /* Send a query for updated load order and plugin header info. */ - loot.Dialog.showProgress(loot.l10n.translate('Refreshing data...')); - loot.query('getGameData').then(function(result){ - /* Parse the data sent from C++. */ - try { - /* We don't want the plugin info creating cards, so don't convert - to plugin objects. */ - var gameInfo = JSON.parse(result); - } catch (e) { - console.log(e); - console.log('getGameData response: ' + result); + loot.query('clearAllMetadata').then(JSON.parse).then((plugins) => { + if (!plugins) { + return; + } + /* Need to empty the UI-side user metadata. */ + plugins.forEach((plugin) => { + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name === plugin.name) { + loot.game.plugins[i].userlist = undefined; + loot.game.plugins[i].editor = undefined; + + loot.game.plugins[i].priority = plugin.priority; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; + + break; + } } + }); - /* Now overwrite plugin data with the newly sent data. Also update - card and li vars as they were unset when the game was switched - from before. */ - var pluginNames = []; - gameInfo.plugins.forEach(function(plugin){ - var foundPlugin = false; - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == plugin.name) { - - loot.game.plugins[i].isActive = plugin.isActive; - loot.game.plugins[i].isEmpty = plugin.isEmpty; - loot.game.plugins[i].loadsArchive = plugin.loadsArchive; - loot.game.plugins[i].crc = plugin.crc; - loot.game.plugins[i].version = plugin.version; - - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - foundPlugin = true; - break; - } - } - if (!foundPlugin) { - /* A new plugin. */ - loot.game.plugins.push(new loot.Plugin(plugin)); - } - pluginNames.push(plugin.name); - }); - for (var i = 0; i < loot.game.plugins.length;) { - var foundPlugin = false; - for (var j = 0; j < pluginNames.length; ++j) { - if (loot.game.plugins[i].name == pluginNames[j]) { - foundPlugin = true; - break; - } - } - if (!foundPlugin) { - /* Remove plugin. */ - loot.game.plugins.splice(i, 1); - } else { - ++i; - } - } - - /* Reapply filters. */ - setFilteredUIData(); - - loot.Dialog.closeProgress(); + loot.Dialog.showNotification(loot.l10n.translate('All user-added metadata has been cleared.')); }).catch(handlePromiseError); + }); } -function onSearchOpen(evt) { +function onCopyContent() { + const messages = []; + const plugins = []; + + if (loot.game) { + if (loot.game.globalMessages) { + loot.game.globalMessages.forEach((message) => { + messages.push({ + type: message.type, + content: message.content[0].str, + }); + }); + } + if (loot.game.plugins) { + loot.game.plugins.forEach((plugin) => { + plugins.push({ + name: plugin.name, + crc: plugin.crc, + version: plugin.version, + isActive: plugin.isActive, + isEmpty: plugin.isEmpty, + loadsArchive: plugin.loadsArchive, + + priority: plugin.priority, + isPriorityGlobal: plugin.isPriorityGlobal, + messages: plugin.messages, + tags: plugin.tags, + isDirty: plugin.isDirty, + }); + }); + } + } else { + const message = document.getElementById('summary').getElementsByTagName('ul')[0].firstElementChild; + if (message) { + messages.push({ + type: 'error', + content: message.textContent, + }); + } + } + + loot.query('copyContent', { + messages: messages, + plugins: plugins, + }).then(() => { + loot.Dialog.showNotification(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); + }).catch(handlePromiseError); +} +function onCopyLoadOrder() { + const plugins = []; + + if (loot.game) { + if (loot.game.plugins) { + loot.game.plugins.forEach((plugin) =>{ + plugins.push(plugin.name); + }); + } + } + + loot.query('copyLoadOrder', plugins).then(() => { + loot.Dialog.showNotification(loot.l10n.translate('The load order has been copied to the clipboard.')); + }).catch(handlePromiseError); +} +function onSwitchSidebarTab(evt) { + if (evt.detail.isSelected) { + document.getElementById(evt.target.selected).parentElement.selected = evt.target.selected; + } +} +function onShowAboutDialog() { + document.getElementById('about').showModal(); +} +function areSettingsValid() { + /* Validate inputs individually. */ + const inputs = document.getElementById('settingsDialog').getElementsByTagName('loot-validated-input'); + for (let i = 0; i < inputs.length; ++i) { + if (!inputs[i].checkValidity()) { + return false; + } + } + return true; +} +function onCloseSettingsDialog(evt) { + if (evt.target.classList.contains('accept')) { + if (!areSettingsValid()) { + return; + } + + /* Update the JS variable values. */ + const settings = { + enableDebugLogging: document.getElementById('enableDebugLogging').checked, + game: document.getElementById('defaultGameSelect').value, + games: document.getElementById('gameTable').getRowsData(false), + language: document.getElementById('languageSelect').value, + lastGame: loot.settings.lastGame, + updateMasterlist: document.getElementById('updateMasterlist').checked, + filters: loot.settings.filters, + }; + + /* Send the settings back to the C++ side. */ + loot.query('closeSettings', settings).then(JSON.parse).then((result) => { + setInstalledGames(result); + }).catch(handlePromiseError).then(() => { + loot.settings = settings; + updateSettingsUI(); + }).catch(handlePromiseError); + } else { + /* Re-apply the existing settings to the settings dialog elements. */ + updateSettingsUI(); + } + evt.target.parentElement.close(); +} +function onShowSettingsDialog() { + document.getElementById('settingsDialog').showModal(); +} +function onFocusSearch(evt) { + if (evt.ctrlKey && evt.keyCode === 70) { // 'f' document.getElementById('mainToolbar').classList.add('search'); document.getElementById('searchBar').focusInput(); + } } -function onSearchClose(evt) { - document.getElementById('mainToolbar').classList.remove('search'); +function onEditorOpen(evt) { + /* Set up drag 'n' drop event handlers. */ + const elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); + for (let i = 0; i < elements.length; ++i) { + elements[i].draggable = true; + elements[i].addEventListener('dragstart', elements[i].onDragStart); + } + + /* Now show editor. */ + evt.target.classList.toggle('flip'); + + /* Enable priority hover in plugins list and enable header + buttons if this is the only editor instance. */ + let numEditors = 0; + if (document.body.hasAttribute('data-editors')) { + numEditors = parseInt(document.body.getAttribute('data-editors'), 10); + } + ++numEditors; + + if (numEditors === 1) { + /* Set the edit mode toggle attribute. */ + document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); + /* Disable the toolbar elements. */ + document.getElementById('wipeUserlistButton').setAttribute('disabled', ''); + document.getElementById('copyContentButton').setAttribute('disabled', ''); + document.getElementById('refreshContentButton').setAttribute('disabled', ''); + document.getElementById('settingsButton').setAttribute('disabled', ''); + document.getElementById('gameMenu').setAttribute('disabled', ''); + document.getElementById('updateMasterlistButton').setAttribute('disabled', ''); + document.getElementById('sortButton').setAttribute('disabled', ''); + } + document.body.setAttribute('data-editors', numEditors); + document.getElementById('cardsNav').updateSize(); + + return loot.query('editorOpened').catch(handlePromiseError); +} +function onEditorClose(evt) { + /* evt.detail is true if the apply button was pressed. */ + let promise; + if (evt.detail) { + /* Need to record the editor control values and work out what's + changed, and update any UI elements necessary. Offload the + majority of the work to the C++ side of things. */ + const edits = evt.target.readFromEditor(evt.target.data); + promise = loot.query('editorClosed', edits).then(JSON.parse).then((result) => { + if (result) { + evt.target.data.priority = result.priority; + evt.target.data.isPriorityGlobal = result.isPriorityGlobal; + evt.target.data.messages = result.messages; + evt.target.data.tags = result.tags; + evt.target.data.isDirty = result.isDirty; + + evt.target.data.userlist = edits.userlist; + + /* Now perform search again. If there is no current search, this won't + do anything. */ + document.getElementById('searchBar').search(); + } + }); + } else { + /* Don't need to record changes, but still need to notify C++ side that + the editor has been closed. */ + promise = loot.query('editorClosed'); + } + promise.then(() => { + delete evt.target.data.editor; + + /* Now hide editor. */ + evt.target.classList.toggle('flip'); + evt.target.data.isEditorOpen = false; + + /* Remove drag 'n' drop event handlers. */ + const elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); + for (let i = 0; i < elements.length; ++i) { + elements[i].removeAttribute('draggable'); + elements[i].removeEventListener('dragstart', elements[i].onDragStart); + } + + /* Disable priority hover in plugins list and enable header + buttons if this is the only editor instance. */ + let numEditors = parseInt(document.body.getAttribute('data-editors'), 10); + --numEditors; + + if (numEditors === 0) { + document.body.removeAttribute('data-editors'); + /* Set the edit mode toggle attribute. */ + document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); + /* Re-enable toolbar elements. */ + document.getElementById('wipeUserlistButton').removeAttribute('disabled'); + document.getElementById('copyContentButton').removeAttribute('disabled'); + document.getElementById('refreshContentButton').removeAttribute('disabled'); + document.getElementById('settingsButton').removeAttribute('disabled'); + document.getElementById('gameMenu').removeAttribute('disabled'); + document.getElementById('updateMasterlistButton').removeAttribute('disabled'); + document.getElementById('sortButton').removeAttribute('disabled'); + } else { + document.body.setAttribute('data-editors', numEditors); + } + document.getElementById('cardsNav').updateSize(); + }).catch(handlePromiseError); +} +function onConflictsFilter(evt) { + /* Deactivate any existing plugin conflict filter. */ + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].id !== evt.target.id) { + loot.game.plugins[i].isConflictFilterChecked = false; + } + } + /* Un-highlight any existing filter plugin. */ + const cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); + for (let i = 0; i < cards.length; ++i) { + cards[i].classList.toggle('highlight', false); + } + /* evt.detail is true if the filter has been activated. */ + if (evt.detail) { + document.body.setAttribute('data-conflicts', evt.target.getName()); + evt.target.classList.toggle('highlight', true); + } else { + document.body.removeAttribute('data-conflicts'); + } + setFilteredUIData(); +} +function onCopyMetadata(evt) { + loot.query('copyMetadata', evt.target.getName()).then(() => { + loot.Dialog.showNotification(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName())); + }).catch(handlePromiseError); +} +function onClearMetadata(evt) { + loot.Dialog.askQuestion('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), (result) => { + if (!result) { + return; + } + loot.query('clearPluginMetadata', evt.target.getName()).then(JSON.parse).then((plugin) => { + if (!result) { + return; + } + /* Need to empty the UI-side user metadata. */ + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].id === evt.target.id) { + loot.game.plugins[i].userlist = undefined; + loot.game.plugins[i].editor = undefined; + + loot.game.plugins[i].priority = plugin.priority; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; + + break; + } + } + loot.Dialog.showNotification(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName())); + /* Now perform search again. If there is no current search, this won't + do anything. */ + document.getElementById('searchBar').search(); + }).catch(handlePromiseError); + }); +} +function onSidebarClick(evt) { + if (evt.target.hasAttribute('data-index')) { + document.getElementById('main').lastElementChild.scrollToItem(evt.target.getAttribute('data-index')); + + if (evt.type === 'dblclick') { + const card = document.getElementById(evt.target.getAttribute('data-id')); + if (!card.classList.contains('flip')) { + document.getElementById(evt.target.getAttribute('data-id')).onShowEditor(); + } + } + } +} +function onQuit(evt) { + if (!document.getElementById('applySortButton').classList.contains('hidden')) { + handleUnappliedChangesClose(loot.l10n.translate('sorted load order')); + } else if (document.body.hasAttribute('data-editors')) { + handleUnappliedChangesClose(loot.l10n.translate('metadata edits')); + } else { + window.close(); + } +} +function onJumpToGeneralInfo() { + window.location.hash = ''; + document.getElementById('main').scrollTop = 0; +} +function onContentRefresh() { + /* Send a query for updated load order and plugin header info. */ + loot.Dialog.showProgress(loot.l10n.translate('Refreshing data...')); + loot.query('getGameData').then(JSON.parse).then((result) => { + /* Parse the data sent from C++. */ + /* We don't want the plugin info creating cards, so don't convert + to plugin objects. */ + const gameInfo = result; + + /* Now overwrite plugin data with the newly sent data. Also update + card and li vars as they were unset when the game was switched + from before. */ + const pluginNames = []; + gameInfo.plugins.forEach((plugin) => { + let foundPlugin = false; + for (let i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name === plugin.name) { + loot.game.plugins[i].isActive = plugin.isActive; + loot.game.plugins[i].isEmpty = plugin.isEmpty; + loot.game.plugins[i].loadsArchive = plugin.loadsArchive; + loot.game.plugins[i].crc = plugin.crc; + loot.game.plugins[i].version = plugin.version; + + loot.game.plugins[i].priority = plugin.priority; + loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; + + foundPlugin = true; + break; + } + } + if (!foundPlugin) { + /* A new plugin. */ + loot.game.plugins.push(new loot.Plugin(plugin)); + } + pluginNames.push(plugin.name); + }); + for (let i = 0; i < loot.game.plugins.length;) { + let foundPlugin = false; + for (let j = 0; j < pluginNames.length; ++j) { + if (loot.game.plugins[i].name === pluginNames[j]) { + foundPlugin = true; + break; + } + } + if (!foundPlugin) { + /* Remove plugin. */ + loot.game.plugins.splice(i, 1); + } else { + ++i; + } + } + + /* Reapply filters. */ + setFilteredUIData(); + + loot.Dialog.closeProgress(); + }).catch(handlePromiseError); +} +function onSearchOpen() { + document.getElementById('mainToolbar').classList.add('search'); + document.getElementById('searchBar').focusInput(); +} +function onSearchClose() { + document.getElementById('mainToolbar').classList.remove('search'); } function onSidebarFilterToggle(evt) { if (evt.target.id !== 'contentFilter') { @@ -733,67 +714,67 @@ function onSidebarFilterToggle(evt) { setFilteredUIData(); } function setupEventHandlers() { - /*Set up handlers for filters.*/ - document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS, false); - document.getElementById('hideVersionNumbers').addEventListener('change', saveFilterState, false); - document.getElementById('hideCRCs').addEventListener('change', onToggleDisplayCSS, false); - document.getElementById('hideCRCs').addEventListener('change', saveFilterState, false); - document.getElementById('hideBashTags').addEventListener('change', onToggleBashTags, false); - document.getElementById('hideBashTags').addEventListener('change', saveFilterState, false); - document.getElementById('hideNotes').addEventListener('change', onSidebarFilterToggle, false); - document.getElementById('hideDoNotCleanMessages').addEventListener('change', onSidebarFilterToggle, false); - document.getElementById('hideInactivePlugins').addEventListener('change', onSidebarFilterToggle, false); - document.getElementById('hideAllPluginMessages').addEventListener('change', onSidebarFilterToggle, false); - document.getElementById('hideMessagelessPlugins').addEventListener('change', onSidebarFilterToggle, false); - document.body.addEventListener('loot-filter-conflicts', onConflictsFilter, false); + /* Set up handlers for filters. */ + document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS); + document.getElementById('hideVersionNumbers').addEventListener('change', saveFilterState); + document.getElementById('hideCRCs').addEventListener('change', onToggleDisplayCSS); + document.getElementById('hideCRCs').addEventListener('change', saveFilterState); + document.getElementById('hideBashTags').addEventListener('change', onToggleBashTags); + document.getElementById('hideBashTags').addEventListener('change', saveFilterState); + document.getElementById('hideNotes').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideDoNotCleanMessages').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideInactivePlugins').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideAllPluginMessages').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideMessagelessPlugins').addEventListener('change', onSidebarFilterToggle); + document.body.addEventListener('loot-filter-conflicts', onConflictsFilter); - /* Set up event handlers for content filter. */ - document.getElementById('contentFilter').addEventListener('change', onSidebarFilterToggle, false); + /* Set up event handlers for content filter. */ + document.getElementById('contentFilter').addEventListener('change', onSidebarFilterToggle); - /* Set up handlers for buttons. */ - document.getElementById('redatePluginsButton').addEventListener('click', onRedatePlugins, false); - document.getElementById('openLogButton').addEventListener('click', onOpenLogLocation, false); - document.getElementById('wipeUserlistButton').addEventListener('click', onClearAllMetadata, false); - document.getElementById('copyLoadOrderButton').addEventListener('click', onCopyLoadOrder, false); - document.getElementById('copyContentButton').addEventListener('click', onCopyContent, false); - document.getElementById('refreshContentButton').addEventListener('click', onContentRefresh, false); - document.getElementById('settingsButton').addEventListener('click', onShowSettingsDialog, false); - document.getElementById('helpButton').addEventListener('click', onOpenReadme, false); - document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog, false); - document.getElementById('quitButton').addEventListener('click', onQuit, false); - document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist, false); - document.getElementById('sortButton').addEventListener('click', onSortPlugins, false); - document.getElementById('applySortButton').addEventListener('click', onApplySort, false); - document.getElementById('cancelSortButton').addEventListener('click', onCancelSort, false); - document.getElementById('sidebarTabs').addEventListener('core-select', onSwitchSidebarTab, false); - document.getElementById('jumpToGeneralInfo').addEventListener('click', onJumpToGeneralInfo, false); + /* Set up handlers for buttons. */ + document.getElementById('redatePluginsButton').addEventListener('click', onRedatePlugins); + document.getElementById('openLogButton').addEventListener('click', onOpenLogLocation); + document.getElementById('wipeUserlistButton').addEventListener('click', onClearAllMetadata); + document.getElementById('copyLoadOrderButton').addEventListener('click', onCopyLoadOrder); + document.getElementById('copyContentButton').addEventListener('click', onCopyContent); + document.getElementById('refreshContentButton').addEventListener('click', onContentRefresh); + document.getElementById('settingsButton').addEventListener('click', onShowSettingsDialog); + document.getElementById('helpButton').addEventListener('click', onOpenReadme); + document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog); + document.getElementById('quitButton').addEventListener('click', onQuit); + document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist); + document.getElementById('sortButton').addEventListener('click', onSortPlugins); + document.getElementById('applySortButton').addEventListener('click', onApplySort); + document.getElementById('cancelSortButton').addEventListener('click', onCancelSort); + document.getElementById('sidebarTabs').addEventListener('core-select', onSwitchSidebarTab); + document.getElementById('jumpToGeneralInfo').addEventListener('click', onJumpToGeneralInfo); - /* Set up search event handlers. */ - document.getElementById('showSearch').addEventListener('click', onSearchOpen, false); - document.getElementById('searchBar').addEventListener('loot-search-close', onSearchClose, false); - window.addEventListener('keyup', onFocusSearch, false); + /* Set up search event handlers. */ + document.getElementById('showSearch').addEventListener('click', onSearchOpen); + document.getElementById('searchBar').addEventListener('loot-search-close', onSearchClose); + window.addEventListener('keyup', onFocusSearch); - /* Set up event handlers for settings dialog. */ - var settings = document.getElementById('settingsDialog'); - settings.getElementsByClassName('accept')[0].addEventListener('click', onCloseSettingsDialog, false); - settings.getElementsByClassName('cancel')[0].addEventListener('click', onCloseSettingsDialog, false); + /* Set up event handlers for settings dialog. */ + const settings = document.getElementById('settingsDialog'); + settings.getElementsByClassName('accept')[0].addEventListener('click', onCloseSettingsDialog); + settings.getElementsByClassName('cancel')[0].addEventListener('click', onCloseSettingsDialog); - /* Set up handler for opening and closing editors. */ - document.body.addEventListener('loot-editor-open', onEditorOpen, false); - document.body.addEventListener('loot-editor-close', onEditorClose, false); - document.body.addEventListener('loot-copy-metadata', onCopyMetadata, false); - document.body.addEventListener('loot-clear-metadata', onClearMetadata, false); + /* Set up handler for opening and closing editors. */ + document.body.addEventListener('loot-editor-open', onEditorOpen); + document.body.addEventListener('loot-editor-close', onEditorClose); + document.body.addEventListener('loot-copy-metadata', onCopyMetadata); + document.body.addEventListener('loot-clear-metadata', onClearMetadata); - document.getElementById('cardsNav').addEventListener('click', onSidebarClick, false); - document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick, false); + document.getElementById('cardsNav').addEventListener('click', onSidebarClick); + document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick); - /* 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 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); + /* 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); } From 3e51af2cc5486857e4542f4cb65c9587a09b469d Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Thu, 24 Dec 2015 11:45:01 +0000 Subject: [PATCH 16/30] Don't throw if translating before loading Just return the passed string. This makes initialisation simpler, as a promise is unnecessary. --- src/gui/html/js/translator.js | 3 +++ src/tests/gui/html/js/test_translator.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/html/js/translator.js b/src/gui/html/js/translator.js index bb0e0f13..60720c77 100644 --- a/src/gui/html/js/translator.js +++ b/src/gui/html/js/translator.js @@ -67,6 +67,9 @@ if (text === undefined) { return ''; } + if (this.jed === undefined) { + return text; + } const func = this.jed.translate(text); return func.fetch.apply(func, substitutions); } diff --git a/src/tests/gui/html/js/test_translator.js b/src/tests/gui/html/js/test_translator.js index c48737e6..461b8694 100644 --- a/src/tests/gui/html/js/test_translator.js +++ b/src/tests/gui/html/js/test_translator.js @@ -37,8 +37,8 @@ describe('Translator', () => { l10n = new loot.Translator(); }); - it('should throw if the translator has not been loaded', () => { - (() => { l10n.translate('foo'); }).should.throw(); + it('should return original string if the translator has not been loaded', () => { + l10n.translate('foo').should.equal('foo'); }); it('should return an empty string if nothing is passed', () => { From ca2274372672058caf060447571a350588dbfcf8 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 12:33:13 +0000 Subject: [PATCH 17/30] Move handleUnappliedChangesClose --- src/gui/html/js/events.js | 21 +++++++++++++++++++++ src/gui/html/js/helpers.js | 21 --------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 1318c91a..e6088e12 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -623,6 +623,27 @@ function onSidebarClick(evt) { } } } +function handleUnappliedChangesClose(change) { + loot.Dialog.askQuestion('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), (result) => { + if (!result) { + return; + } + /* Cancel any sorting and close any editors. Cheat by sending a + cancelSort query for as many times as necessary. */ + const queries = []; + let numQueries = 0; + if (!document.getElementById('applySortButton').classList.contains('hidden')) { + numQueries += 1; + } + numQueries += document.body.getAttribute('data-editors'); + for (let i = 0; i < numQueries; ++i) { + queries.push(loot.query('cancelSort')); + } + Promise.all(queries).then(() => { + window.close(); + }).catch(handlePromiseError); + }); +} function onQuit(evt) { if (!document.getElementById('applySortButton').classList.contains('hidden')) { handleUnappliedChangesClose(loot.l10n.translate('sorted load order')); diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index cfe8cd99..d84ce2af 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -16,27 +16,6 @@ function hideElement(element) { element.classList.toggle('hidden', true); } } -function handleUnappliedChangesClose(change) { - loot.Dialog.askQuestion('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), (result) => { - if (!result) { - return; - } - /* Cancel any sorting and close any editors. Cheat by sending a - cancelSort query for as many times as necessary. */ - const queries = []; - let numQueries = 0; - if (!document.getElementById('applySortButton').classList.contains('hidden')) { - numQueries += 1; - } - numQueries += document.body.getAttribute('data-editors'); - for (let i = 0; i < numQueries; ++i) { - queries.push(loot.query('cancelSort')); - } - Promise.all(queries).then(() => { - window.close(); - }).catch(handlePromiseError); - }); -} function getConflictingPlugins(pluginName) { if (!pluginName) { return Promise.resolve([]); From 1f3784c27986d0f2e1d52912b1ef5a660f6205ad Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 12:35:53 +0000 Subject: [PATCH 18/30] Move setupEventHandlers --- src/gui/html/js/events.js | 65 --------------------------------------- src/gui/html/js/init.js | 65 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index e6088e12..4b4678e9 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -734,68 +734,3 @@ function onSidebarFilterToggle(evt) { saveFilterState(evt); setFilteredUIData(); } -function setupEventHandlers() { - /* Set up handlers for filters. */ - document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS); - document.getElementById('hideVersionNumbers').addEventListener('change', saveFilterState); - document.getElementById('hideCRCs').addEventListener('change', onToggleDisplayCSS); - document.getElementById('hideCRCs').addEventListener('change', saveFilterState); - document.getElementById('hideBashTags').addEventListener('change', onToggleBashTags); - document.getElementById('hideBashTags').addEventListener('change', saveFilterState); - document.getElementById('hideNotes').addEventListener('change', onSidebarFilterToggle); - document.getElementById('hideDoNotCleanMessages').addEventListener('change', onSidebarFilterToggle); - document.getElementById('hideInactivePlugins').addEventListener('change', onSidebarFilterToggle); - document.getElementById('hideAllPluginMessages').addEventListener('change', onSidebarFilterToggle); - document.getElementById('hideMessagelessPlugins').addEventListener('change', onSidebarFilterToggle); - document.body.addEventListener('loot-filter-conflicts', onConflictsFilter); - - /* Set up event handlers for content filter. */ - document.getElementById('contentFilter').addEventListener('change', onSidebarFilterToggle); - - /* Set up handlers for buttons. */ - document.getElementById('redatePluginsButton').addEventListener('click', onRedatePlugins); - document.getElementById('openLogButton').addEventListener('click', onOpenLogLocation); - document.getElementById('wipeUserlistButton').addEventListener('click', onClearAllMetadata); - document.getElementById('copyLoadOrderButton').addEventListener('click', onCopyLoadOrder); - document.getElementById('copyContentButton').addEventListener('click', onCopyContent); - document.getElementById('refreshContentButton').addEventListener('click', onContentRefresh); - document.getElementById('settingsButton').addEventListener('click', onShowSettingsDialog); - document.getElementById('helpButton').addEventListener('click', onOpenReadme); - document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog); - document.getElementById('quitButton').addEventListener('click', onQuit); - document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist); - document.getElementById('sortButton').addEventListener('click', onSortPlugins); - document.getElementById('applySortButton').addEventListener('click', onApplySort); - document.getElementById('cancelSortButton').addEventListener('click', onCancelSort); - document.getElementById('sidebarTabs').addEventListener('core-select', onSwitchSidebarTab); - document.getElementById('jumpToGeneralInfo').addEventListener('click', onJumpToGeneralInfo); - - /* Set up search event handlers. */ - document.getElementById('showSearch').addEventListener('click', onSearchOpen); - document.getElementById('searchBar').addEventListener('loot-search-close', onSearchClose); - window.addEventListener('keyup', onFocusSearch); - - /* Set up event handlers for settings dialog. */ - const settings = document.getElementById('settingsDialog'); - settings.getElementsByClassName('accept')[0].addEventListener('click', onCloseSettingsDialog); - settings.getElementsByClassName('cancel')[0].addEventListener('click', onCloseSettingsDialog); - - /* Set up handler for opening and closing editors. */ - document.body.addEventListener('loot-editor-open', onEditorOpen); - document.body.addEventListener('loot-editor-close', onEditorClose); - document.body.addEventListener('loot-copy-metadata', onCopyMetadata); - document.body.addEventListener('loot-clear-metadata', onClearMetadata); - - document.getElementById('cardsNav').addEventListener('click', onSidebarClick); - document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick); - - /* 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/init.js b/src/gui/html/js/init.js index 195f3c23..858663cf 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -26,11 +26,76 @@ function applyEnabledFilters() { if (!loot.filters) { return; } + function setupEventHandlers() { + /* Set up handlers for filters. */ + document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS); + document.getElementById('hideVersionNumbers').addEventListener('change', saveFilterState); + document.getElementById('hideCRCs').addEventListener('change', onToggleDisplayCSS); + document.getElementById('hideCRCs').addEventListener('change', saveFilterState); + document.getElementById('hideBashTags').addEventListener('change', onToggleBashTags); + document.getElementById('hideBashTags').addEventListener('change', saveFilterState); + document.getElementById('hideNotes').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideDoNotCleanMessages').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideInactivePlugins').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideAllPluginMessages').addEventListener('change', onSidebarFilterToggle); + document.getElementById('hideMessagelessPlugins').addEventListener('change', onSidebarFilterToggle); + document.body.addEventListener('loot-filter-conflicts', onConflictsFilter); if (loot.settings.filters) { for (const filter in loot.settings.filters) { loot.filters[filter] = loot.settings.filters[filter]; document.getElementById(filter).checked = loot.filters[filter]; + /* Set up event handlers for content filter. */ + document.getElementById('contentFilter').addEventListener('change', onSidebarFilterToggle); + + /* Set up handlers for buttons. */ + document.getElementById('redatePluginsButton').addEventListener('click', onRedatePlugins); + document.getElementById('openLogButton').addEventListener('click', onOpenLogLocation); + document.getElementById('wipeUserlistButton').addEventListener('click', onClearAllMetadata); + document.getElementById('copyLoadOrderButton').addEventListener('click', onCopyLoadOrder); + document.getElementById('copyContentButton').addEventListener('click', onCopyContent); + document.getElementById('refreshContentButton').addEventListener('click', onContentRefresh); + document.getElementById('settingsButton').addEventListener('click', onShowSettingsDialog); + document.getElementById('helpButton').addEventListener('click', onOpenReadme); + document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog); + document.getElementById('quitButton').addEventListener('click', onQuit); + document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist); + document.getElementById('sortButton').addEventListener('click', onSortPlugins); + document.getElementById('applySortButton').addEventListener('click', onApplySort); + document.getElementById('cancelSortButton').addEventListener('click', onCancelSort); + document.getElementById('sidebarTabs').addEventListener('core-select', onSwitchSidebarTab); + document.getElementById('jumpToGeneralInfo').addEventListener('click', onJumpToGeneralInfo); + + /* Set up search event handlers. */ + document.getElementById('showSearch').addEventListener('click', onSearchOpen); + document.getElementById('searchBar').addEventListener('loot-search-close', onSearchClose); + window.addEventListener('keyup', onFocusSearch); + + /* Set up event handlers for settings dialog. */ + const settings = document.getElementById('settingsDialog'); + settings.getElementsByClassName('accept')[0].addEventListener('click', onCloseSettingsDialog); + settings.getElementsByClassName('cancel')[0].addEventListener('click', onCloseSettingsDialog); + + /* Set up handler for opening and closing editors. */ + document.body.addEventListener('loot-editor-open', onEditorOpen); + document.body.addEventListener('loot-editor-close', onEditorClose); + document.body.addEventListener('loot-copy-metadata', onCopyMetadata); + document.body.addEventListener('loot-clear-metadata', onClearMetadata); + + document.getElementById('cardsNav').addEventListener('click', onSidebarClick); + document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick); + + /* 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); + } + } } From 5e7ef9a5ef13e0d263a3cd4a2c1b9b7e6f7bfde5 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 12:37:57 +0000 Subject: [PATCH 19/30] Rename setFilteredUIData() to filterPluginData() --- src/gui/html/js/events.js | 12 ++++++------ src/gui/html/js/helpers.js | 16 +++++++++------- src/gui/html/js/init.js | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 4b4678e9..eafbb9b5 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -139,7 +139,7 @@ function onChangeGame(evt) { document.getElementById('main').lastElementChild.scrollToItem(0); /* Now update virtual lists. */ - setFilteredUIData(); + filterPluginData(loot.game.plugins, loot.filters); loot.Dialog.closeProgress(); }).catch(handlePromiseError); @@ -235,7 +235,7 @@ function onSortPlugins() { /* Now update the UI for the new order. */ loot.game.plugins = loot.game.loadOrder; - setFilteredUIData(); + filterPluginData(loot.game.plugins, loot.filters); /* Now hide the masterlist update buttons, and display the accept and cancel sort buttons. */ @@ -274,7 +274,7 @@ function onCancelSort(evt) { return loot.query('cancelSort').then(() => { /* Sort UI elements again according to stored old load order. */ loot.game.plugins = loot.game.oldLoadOrder; - setFilteredUIData(); + filterPluginData(loot.game.plugins, loot.filters); delete loot.game.loadOrder; delete loot.game.oldLoadOrder; @@ -573,7 +573,7 @@ function onConflictsFilter(evt) { } else { document.body.removeAttribute('data-conflicts'); } - setFilteredUIData(); + filterPluginData(loot.game.plugins, loot.filters); } function onCopyMetadata(evt) { loot.query('copyMetadata', evt.target.getName()).then(() => { @@ -713,7 +713,7 @@ function onContentRefresh() { } /* Reapply filters. */ - setFilteredUIData(); + filterPluginData(loot.game.plugins, loot.filters); loot.Dialog.closeProgress(); }).catch(handlePromiseError); @@ -732,5 +732,5 @@ function onSidebarFilterToggle(evt) { loot.filters.contentSearchString = evt.target.value; } saveFilterState(evt); - setFilteredUIData(); + filterPluginData(loot.game.plugins, loot.filters); } diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index d84ce2af..d57c08eb 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -52,10 +52,10 @@ function getConflictingPlugins(pluginName) { return [pluginName]; }).catch(handlePromiseError); } -function setFilteredUIData() { - getConflictingPlugins(loot.filters.conflictTargetPluginName).then((conflictingPluginNames) => { - loot.filters.conflictingPluginNames = conflictingPluginNames; - return loot.game.plugins.filter(loot.filters.pluginFilter, loot.filters); +function filterPluginData(plugins, filters) { + getConflictingPlugins(filters.conflictTargetPluginName).then((conflictingPluginNames) => { + filters.conflictingPluginNames = conflictingPluginNames; + return plugins.filter(filters.pluginFilter, filters); }).then((filteredPlugins) => { document.getElementById('cardsNav').data = filteredPlugins; document.getElementById('pluginCardList').data = filteredPlugins; @@ -66,16 +66,18 @@ function setFilteredUIData() { element.onMessagesChange(); } }); + document.getElementById('cardsNav').updateSize(); + document.getElementById('pluginCardList').updateSize(); /* Now perform search again. If there is no current search, this won't do anything. */ document.getElementById('searchBar').search(); /* Re-count all hidden plugins and messages. */ - document.getElementById('hiddenPluginNo').textContent = loot.game.plugins.length - filteredPlugins.length; + document.getElementById('hiddenPluginNo').textContent = plugins.length - filteredPlugins.length; let hiddenMessageNo = 0; - loot.game.plugins.forEach((plugin) => { - hiddenMessageNo += plugin.messages.length - plugin.getCardContent(loot.filters).messages.length; + plugins.forEach((plugin) => { + hiddenMessageNo += plugin.messages.length - plugin.getCardContent(filters).messages.length; }); document.getElementById('hiddenMessageNo').textContent = hiddenMessageNo; }); diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 858663cf..74b83808 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -104,7 +104,7 @@ function applyEnabledFilters() { || loot.filters.hideNotes || loot.filters.hideDoNotCleanMessages || loot.filters.hideAllPluginMessages) { - setFilteredUIData(); + filterPluginData(plugins, filters); } if (loot.filters.hideVersionNumbers) { From d77372562e4fc37adafe2e9fc2041a6c5a2fdcfa Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 12:43:10 +0000 Subject: [PATCH 20/30] Move some helper functions into new file Move the helper functions that only touched the DOM and didn't modify any JS data to a new dom.js file. They probably won't stay as they are now, but it's a first step to refactoring them. I also renamed updateSettingsUI() to updateSettingsDialog() for clarity. --- src/gui/html/index.html | 1 + src/gui/html/js/dom.js | 78 +++++++++++++++++++++++++++++++++++++ src/gui/html/js/events.js | 4 +- src/gui/html/js/helpers.js | 79 -------------------------------------- src/gui/html/js/init.js | 2 +- 5 files changed, 82 insertions(+), 82 deletions(-) create mode 100644 src/gui/html/js/dom.js diff --git a/src/gui/html/index.html b/src/gui/html/index.html index 74abbc27..3e4b5092 100644 --- a/src/gui/html/index.html +++ b/src/gui/html/index.html @@ -316,6 +316,7 @@ + diff --git a/src/gui/html/js/dom.js b/src/gui/html/js/dom.js new file mode 100644 index 00000000..2da58605 --- /dev/null +++ b/src/gui/html/js/dom.js @@ -0,0 +1,78 @@ +'use strict'; +function showElement(element) { + if (element !== null) { + element.classList.toggle('hidden', false); + } +} +function hideElement(element) { + if (element !== null) { + element.classList.toggle('hidden', true); + } +} +/* 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); + } + } + } +} +/* Call whenever installedGames is changed or game menu is rewritten. */ +function updateEnabledGames(installedGames) { + /* Update the disabled games in the game menu. */ + const gameMenuItems = document.getElementById('gameMenu').children; + for (let i = 0; i < gameMenuItems.length; ++i) { + if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) === -1) { + gameMenuItems[i].setAttribute('disabled', true); + gameMenuItems[i].removeEventListener('click', onChangeGame); + } else { + gameMenuItems[i].removeAttribute('disabled'); + gameMenuItems[i].addEventListener('click', onChangeGame); + } + } +} +/* Call whenever settings are changed. */ +function updateSettingsDialog(settings, installedGames, gameFolder) { + const gameSelect = document.getElementById('defaultGameSelect'); + const gameMenu = document.getElementById('gameMenu'); + const gameTable = document.getElementById('gameTable'); + + /* First make sure game listing elements don't have any existing entries. */ + while (gameSelect.children.length > 1) { + gameSelect.removeChild(gameSelect.lastElementChild); + } + while (gameMenu.firstElementChild) { + gameMenu.firstElementChild.removeEventListener('click', onChangeGame); + gameMenu.removeChild(gameMenu.firstElementChild); + } + gameTable.clear(); + + /* Now fill with new values. */ + settings.games.forEach((game) => { + const menuItem = document.createElement('paper-item'); + menuItem.setAttribute('value', game.folder); + menuItem.setAttribute('noink', ''); + menuItem.textContent = game.name; + gameMenu.appendChild(menuItem); + gameSelect.appendChild(menuItem.cloneNode(true)); + + const row = gameTable.addRow(game); + gameTable.setReadOnly(row, ['name', 'folder', 'type']); + }); + + gameSelect.value = settings.game; + document.getElementById('languageSelect').value = settings.language; + document.getElementById('enableDebugLogging').checked = settings.enableDebugLogging; + document.getElementById('updateMasterlist').checked = settings.updateMasterlist; + + updateEnabledGames(installedGames); + updateSelectedGame(gameFolder); +} diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index eafbb9b5..ed4a564e 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -436,11 +436,11 @@ function onCloseSettingsDialog(evt) { setInstalledGames(result); }).catch(handlePromiseError).then(() => { loot.settings = settings; - updateSettingsUI(); + updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); }).catch(handlePromiseError); } else { /* Re-apply the existing settings to the settings dialog elements. */ - updateSettingsUI(); + updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); } evt.target.parentElement.close(); } diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index d57c08eb..bc0610e4 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -5,17 +5,6 @@ function handlePromiseError(err) { loot.Dialog.closeProgress(); loot.Dialog.showMessage(loot.l10n.translate('Error'), err.message); } - -function showElement(element) { - if (element !== null) { - element.classList.toggle('hidden', false); - } -} -function hideElement(element) { - if (element !== null) { - element.classList.toggle('hidden', true); - } -} function getConflictingPlugins(pluginName) { if (!pluginName) { return Promise.resolve([]); @@ -82,75 +71,7 @@ function filterPluginData(plugins, filters) { 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); - } - } - } -} - -/* Call whenever installedGames is changed or game menu is rewritten. */ -function updateEnabledGames(installedGames) { - /* Update the disabled games in the game menu. */ - const gameMenuItems = document.getElementById('gameMenu').children; - for (let i = 0; i < gameMenuItems.length; ++i) { - if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) === -1) { - gameMenuItems[i].setAttribute('disabled', true); - gameMenuItems[i].removeEventListener('click', onChangeGame); - } else { - gameMenuItems[i].removeAttribute('disabled'); - gameMenuItems[i].addEventListener('click', onChangeGame); - } - } -} function setInstalledGames(installedGames) { loot.installedGames = installedGames; updateEnabledGames(installedGames); } -/* Call whenever settings are changed. */ -function updateSettingsUI() { - const gameSelect = document.getElementById('defaultGameSelect'); - const gameMenu = document.getElementById('gameMenu'); - const gameTable = document.getElementById('gameTable'); - - /* First make sure game listing elements don't have any existing entries. */ - while (gameSelect.children.length > 1) { - gameSelect.removeChild(gameSelect.lastElementChild); - } - while (gameMenu.firstElementChild) { - gameMenu.firstElementChild.removeEventListener('click', onChangeGame); - gameMenu.removeChild(gameMenu.firstElementChild); - } - gameTable.clear(); - - /* Now fill with new values. */ - loot.settings.games.forEach((game) => { - const menuItem = document.createElement('paper-item'); - menuItem.setAttribute('value', game.folder); - menuItem.setAttribute('noink', ''); - menuItem.textContent = game.name; - gameMenu.appendChild(menuItem); - gameSelect.appendChild(menuItem.cloneNode(true)); - - const row = gameTable.addRow(game); - gameTable.setReadOnly(row, ['name', 'folder', 'type']); - }); - - gameSelect.value = loot.settings.game; - document.getElementById('languageSelect').value = loot.settings.language; - document.getElementById('enableDebugLogging').checked = loot.settings.enableDebugLogging; - document.getElementById('updateMasterlist').checked = loot.settings.updateMasterlist; - - updateEnabledGames(loot.installedGames); - updateSelectedGame(loot.game.folder); -} diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 74b83808..7fe85bf6 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -205,7 +205,7 @@ function getInstalledGames() { function getSettings() { return loot.query('getSettings').then(JSON.parse).then((result) => { loot.settings = result; - updateSettingsUI(); + updateSettingsDialog(appData.settings, appData.installedGames, appData.game.folder); }); } From 485490b0575b624d21f0061e2deb8724c5422de8 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 12:44:55 +0000 Subject: [PATCH 21/30] Rewrite init.js as a module This is another step towards a full refactoring of init.js, but it's still too tightly coupled to dom.js, events.js and init.js. --- src/gui/html/js/dom.js | 7 + src/gui/html/js/events.js | 5 +- src/gui/html/js/helpers.js | 4 - src/gui/html/js/init.js | 334 +++++++++++++++++++------------------ 4 files changed, 181 insertions(+), 169 deletions(-) diff --git a/src/gui/html/js/dom.js b/src/gui/html/js/dom.js index 2da58605..610be306 100644 --- a/src/gui/html/js/dom.js +++ b/src/gui/html/js/dom.js @@ -4,6 +4,13 @@ function showElement(element) { element.classList.toggle('hidden', false); } } +function getElementInTableRowTemplate(rowTemplateId, elementClass) { + const select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (select) { + return select.import.querySelector('#' + rowTemplateId).content.querySelector('.' + elementClass); + } + return document.querySelector('#' + rowTemplateId).content.querySelector('.' + elementClass); +} function hideElement(element) { if (element !== null) { element.classList.toggle('hidden', true); diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index ed4a564e..9b76f7a7 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -432,8 +432,9 @@ function onCloseSettingsDialog(evt) { }; /* Send the settings back to the C++ side. */ - loot.query('closeSettings', settings).then(JSON.parse).then((result) => { - setInstalledGames(result); + loot.query('closeSettings', settings).then(JSON.parse).then((installedGames) => { + loot.installedGames = installedGames; + updateEnabledGames(installedGames); }).catch(handlePromiseError).then(() => { loot.settings = settings; updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index bc0610e4..3c214d77 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -71,7 +71,3 @@ function filterPluginData(plugins, filters) { document.getElementById('hiddenMessageNo').textContent = hiddenMessageNo; }); } -function setInstalledGames(installedGames) { - loot.installedGames = installedGames; - updateEnabledGames(installedGames); -} diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 7fe85bf6..2308cba2 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -22,10 +22,22 @@ . */ 'use strict'; -function applyEnabledFilters() { - if (!loot.filters) { - return; +(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.initialise = factory(root.loot.Dialog, + root.loot.Filters, + root.loot.Game, + root.loot.translateStaticText, + root.loot.Plugin, + root.loot.query, + root.loot.Translator); } +}(this, (Dialog, Filters, Game, translateStaticText, Plugin, query, Translator) => { function setupEventHandlers() { /* Set up handlers for filters. */ document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS); @@ -41,10 +53,6 @@ function applyEnabledFilters() { document.getElementById('hideMessagelessPlugins').addEventListener('change', onSidebarFilterToggle); document.body.addEventListener('loot-filter-conflicts', onConflictsFilter); - if (loot.settings.filters) { - for (const filter in loot.settings.filters) { - loot.filters[filter] = loot.settings.filters[filter]; - document.getElementById(filter).checked = loot.filters[filter]; /* Set up event handlers for content filter. */ document.getElementById('contentFilter').addEventListener('change', onSidebarFilterToggle); @@ -96,181 +104,181 @@ function applyEnabledFilters() { document.addEventListener('loot-game-plugins-change', onGamePluginsChange); } + function applyEnabledFilters(filters, settings, plugins) { + if (!filters) { + return; } - } - if (loot.filters.hideMessagelessPlugins - || loot.filters.hideInactivePlugins - || loot.filters.hideNotes - || loot.filters.hideDoNotCleanMessages - || loot.filters.hideAllPluginMessages) { + if (settings.filters) { + for (const filter in settings.filters) { + filters[filter] = settings.filters[filter]; + document.getElementById(filter).checked = filters[filter]; + } + } + + if (filters.hideMessagelessPlugins + || filters.hideInactivePlugins + || filters.hideNotes + || filters.hideDoNotCleanMessages + || filters.hideAllPluginMessages) { filterPluginData(plugins, filters); - } - - if (loot.filters.hideVersionNumbers) { - document.getElementById('hideVersionNumbers').dispatchEvent(new Event('change')); - } - - if (loot.filters.hideCRCs) { - document.getElementById('hideCRCs').dispatchEvent(new Event('change')); - } - - if (loot.filters.hideBashTags) { - document.getElementById('hideBashTags').dispatchEvent(new Event('change')); - } -} - -function getVersion() { - return loot.query('getVersion').then(JSON.parse).then((result) => { - /* The fourth part of the version string is the build number. Trim it. */ - const pos = result.lastIndexOf('.'); - loot.version = result.substring(0, pos); - document.getElementById('LOOTVersion').textContent = loot.version; - document.getElementById('firstTimeLootVersion').textContent = loot.version; - document.getElementById('LOOTBuild').textContent = result.substring(pos + 1); - }); -} - -function getLanguages() { - return loot.query('getLanguages').then(JSON.parse).then((result) => { - /* Now fill in language options. */ - const settingsLangSelect = document.getElementById('languageSelect'); - let messageLangSelect = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (messageLangSelect) { - messageLangSelect = messageLangSelect.import.querySelector('#messageRow').content.querySelector('.language'); - } else { - messageLangSelect = document.querySelector('#messageRow').content.querySelector('.language'); } - for (let i = 0; i < result.length; ++i) { - const settingsItem = document.createElement('paper-item'); - settingsItem.setAttribute('value', result[i].locale); - settingsItem.setAttribute('noink', ''); - settingsItem.textContent = result[i].name; - settingsLangSelect.appendChild(settingsItem); - messageLangSelect.appendChild(settingsItem.cloneNode(true)); + if (filters.hideVersionNumbers) { + document.getElementById('hideVersionNumbers').dispatchEvent(new Event('change')); } - messageLangSelect.setAttribute('value', messageLangSelect.firstElementChild.getAttribute('value')); - }); -} - -function getInitErrors() { - return loot.query('getInitErrors').then(JSON.parse).then((result) => { - if (!result) { - return result; + if (filters.hideCRCs) { + document.getElementById('hideCRCs').dispatchEvent(new Event('change')); } - const generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; - result.forEach((message) => { - const li = document.createElement('li'); - li.className = 'error'; - /* Use the Marked library for Markdown formatting support. */ - li.innerHTML = window.marked(message); - generalMessagesList.appendChild(li); + if (filters.hideBashTags) { + document.getElementById('hideBashTags').dispatchEvent(new Event('change')); + } + } + + function setVersion(appData) { + return query('getVersion').then(JSON.parse).then((result) => { + /* The fourth part of the version string is the build number. Trim it. */ + const pos = result.lastIndexOf('.'); + appData.version = result.substring(0, pos); + document.getElementById('LOOTVersion').textContent = appData.version; + document.getElementById('firstTimeLootVersion').textContent = appData.version; + document.getElementById('LOOTBuild').textContent = result.substring(pos + 1); }); + } - document.getElementById('filterTotalMessageNo').textContent = result.length; - document.getElementById('totalMessageNo').textContent = result.length; - document.getElementById('totalErrorNo').textContent = result.length; + function setLanguages() { + return query('getLanguages').then(JSON.parse).then((result) => { + /* Now fill in language options. */ + const settingsLangSelect = document.getElementById('languageSelect'); + const messageLangSelect = getElementInTableRowTemplate('messageRow', 'language'); - return result; - }); -} + result.forEach((language) => { + const settingsItem = document.createElement('paper-item'); + settingsItem.setAttribute('value', language.locale); + settingsItem.setAttribute('noink', ''); + settingsItem.textContent = language.name; + settingsLangSelect.appendChild(settingsItem); + messageLangSelect.appendChild(settingsItem.cloneNode(true)); + }); -function getGameTypes() { - return loot.query('getGameTypes').then(JSON.parse).then((result) => { - /* Fill in game row template's game type options. */ - let select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (select) { - select = select.import.querySelector('#gameRow').content.querySelector('.type'); - } else { - select = document.querySelector('#gameRow').content.querySelector('.type'); - } - for (let j = 0; j < result.length; ++j) { - const item = document.createElement('paper-item'); - item.setAttribute('value', result[j]); - item.setAttribute('noink', ''); - item.textContent = result[j]; - select.appendChild(item); - } - select.setAttribute('value', select.firstElementChild.getAttribute('value')); - }); -} + messageLangSelect.setAttribute('value', messageLangSelect.firstElementChild.getAttribute('value')); + }); + } -function getInstalledGames() { - return loot.query('getInstalledGames').then(JSON.parse).then(setInstalledGames); -} + function displayInitErrors() { + return query('getInitErrors').then(JSON.parse).then((result) => { + if (!result) { + return result; + } + const generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; -function getSettings() { - return loot.query('getSettings').then(JSON.parse).then((result) => { - loot.settings = result; + result.forEach((message) => { + const li = document.createElement('li'); + li.className = 'error'; + /* Use the Marked library for Markdown formatting support. */ + li.innerHTML = window.marked(message); + generalMessagesList.appendChild(li); + }); + + document.getElementById('filterTotalMessageNo').textContent = result.length; + document.getElementById('totalMessageNo').textContent = result.length; + document.getElementById('totalErrorNo').textContent = result.length; + + return result; + }); + } + + function setGameTypes() { + return query('getGameTypes').then(JSON.parse).then((result) => { + /* Fill in game row template's game type options. */ + const select = getElementInTableRowTemplate('gameRow', 'type'); + result.forEach((gameType) => { + const item = document.createElement('paper-item'); + item.setAttribute('value', gameType); + item.setAttribute('noink', ''); + item.textContent = gameType; + select.appendChild(item); + }); + select.setAttribute('value', select.firstElementChild.getAttribute('value')); + }); + } + + function setInstalledGames(appData) { + return query('getInstalledGames').then(JSON.parse).then((installedGames) => { + appData.installedGames = installedGames; + updateEnabledGames(installedGames); + }); + } + + function setSettings(appData) { + return query('getSettings').then(JSON.parse).then((result) => { + appData.settings = result; updateSettingsDialog(appData.settings, appData.installedGames, appData.game.folder); - }); -} + }); + } -function getGameData() { - return loot.query('getGameData').then((result) => { - const game = JSON.parse(result, loot.Plugin.fromJson); - loot.game = new loot.Game(game, loot.l10n); - document.getElementById('cardsNav').data = loot.game.plugins; - document.getElementById('main').lastElementChild.data = loot.game.plugins; - applyEnabledFilters(); + function setGameData(appData) { + return query('getGameData').then((result) => { + const game = JSON.parse(result, Plugin.fromJson); + appData.game = new Game(game, appData.l10n); + document.getElementById('cardsNav').data = appData.game.plugins; + document.getElementById('main').lastElementChild.data = appData.game.plugins; + applyEnabledFilters(appData.filters, appData.settings, appData.game.plugins); + Dialog.closeProgress(); + }); + } - setTimeout(() => { - document.getElementById('cardsNav').updateSize(); - loot.Dialog.closeProgress(); - }, 100); - }); -} + return () => { + Dialog.showProgress('Initialising user interface...'); + /* Set the plugin list's scroll target to its parent. */ + document.getElementById('pluginCardList').scrollTarget = document.getElementById('main'); -function initialise() { - loot.Dialog.showProgress('Initialising user interface...'); - /* Set the plugin list's scroll target to its parent. */ - document.getElementById('pluginCardList').scrollTarget = document.getElementById('main'); + /* Make sure settings are what I want. */ + window.marked.setOptions({ + gfm: true, + tables: true, + sanitize: true, + }); + setupEventHandlers(); - /* Make sure settings are what I want. */ - window.marked.setOptions({ - gfm: true, - tables: true, - sanitize: true, - }); - setupEventHandlers(); + loot.version = ''; + loot.settings = {}; + loot.l10n = new Translator(); + loot.game = new Game({}, loot.l10n); + loot.filters = new Filters(loot.l10n); - loot.l10n = new loot.Translator(); - loot.l10n.load().then(() => { - loot.filters = new loot.Filters(loot.l10n); - loot.game = new loot.Game({}, loot.l10n); - }).then(() => { - return Promise.all([ - getVersion(), - getLanguages(), - getGameTypes(), - getInstalledGames(), - getSettings(), - ]); - }).then(() => { - /* Translate static text. */ - loot.l10n = new loot.Translator(loot.settings.language); - return loot.l10n.load(); - }).then(() => { - loot.translateStaticText(loot.l10n); - /* Also need to update the settings UI. */ - updateSettingsUI(); - }).then(() => { - return getInitErrors(); - }).then((result) => { - if (result) { - loot.Dialog.closeProgress(); - document.getElementById('settingsButton').click(); - return Promise.resolve(''); - } - return getGameData(); - }).then(() => { - if (!loot.settings.lastVersion || loot.settings.lastVersion !== loot.version) { - document.getElementById('firstRun').showModal(); - } - }).catch(handlePromiseError); -} + Promise.all([ + setLanguages(), + setGameTypes(), + setInstalledGames(loot), + setVersion(loot), + setSettings(loot), + ]).then(() => { + /* Translate static text. */ + loot.l10n = new Translator(loot.settings.language); + return loot.l10n.load(); + }).then(() => { + loot.filters = new Filters(loot.l10n); + translateStaticText(loot.l10n); + /* Also need to update the settings UI. */ + updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); + }).then(() => { + return displayInitErrors(); + }).then((result) => { + if (result) { + Dialog.closeProgress(); + document.getElementById('settingsButton').click(); + return Promise.resolve(); + } + return setGameData(loot); + }).then(() => { + if (!loot.settings.lastVersion || loot.settings.lastVersion !== loot.version) { + document.getElementById('firstRun').showModal(); + } + }).catch(handlePromiseError); + }; +})); -window.addEventListener('polymer-ready', initialise); +window.addEventListener('polymer-ready', loot.initialise); From 392b68eea47393cdedc7c355fdac445678c5f4e7 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 14:47:39 +0000 Subject: [PATCH 22/30] Modularise dom.js --- src/gui/html/js/dom.js | 161 ++++++++++++++++++++------------------ src/gui/html/js/events.js | 32 ++++---- src/gui/html/js/init.js | 13 +-- 3 files changed, 107 insertions(+), 99 deletions(-) diff --git a/src/gui/html/js/dom.js b/src/gui/html/js/dom.js index 610be306..8d27275c 100644 --- a/src/gui/html/js/dom.js +++ b/src/gui/html/js/dom.js @@ -1,85 +1,92 @@ 'use strict'; -function showElement(element) { - if (element !== null) { - element.classList.toggle('hidden', false); +(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.dom = factory(); } -} -function getElementInTableRowTemplate(rowTemplateId, elementClass) { - const select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (select) { - return select.import.querySelector('#' + rowTemplateId).content.querySelector('.' + elementClass); - } - return document.querySelector('#' + rowTemplateId).content.querySelector('.' + elementClass); -} -function hideElement(element) { - if (element !== null) { - element.classList.toggle('hidden', true); - } -} -/* 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); +}(this, () => { + return { + getElementInTableRowTemplate(rowTemplateId, elementClass) { + const select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (select) { + return select.import.querySelector('#' + rowTemplateId).content.querySelector('.' + elementClass); } - } - } -} -/* Call whenever installedGames is changed or game menu is rewritten. */ -function updateEnabledGames(installedGames) { - /* Update the disabled games in the game menu. */ - const gameMenuItems = document.getElementById('gameMenu').children; - for (let i = 0; i < gameMenuItems.length; ++i) { - if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) === -1) { - gameMenuItems[i].setAttribute('disabled', true); - gameMenuItems[i].removeEventListener('click', onChangeGame); - } else { - gameMenuItems[i].removeAttribute('disabled'); - gameMenuItems[i].addEventListener('click', onChangeGame); - } - } -} -/* Call whenever settings are changed. */ -function updateSettingsDialog(settings, installedGames, gameFolder) { - const gameSelect = document.getElementById('defaultGameSelect'); - const gameMenu = document.getElementById('gameMenu'); - const gameTable = document.getElementById('gameTable'); + return document.querySelector('#' + rowTemplateId).content.querySelector('.' + elementClass); + }, - /* First make sure game listing elements don't have any existing entries. */ - while (gameSelect.children.length > 1) { - gameSelect.removeChild(gameSelect.lastElementChild); - } - while (gameMenu.firstElementChild) { - gameMenu.firstElementChild.removeEventListener('click', onChangeGame); - gameMenu.removeChild(gameMenu.firstElementChild); - } - gameTable.clear(); + show(elementId) { + document.getElementById(elementId).classList.toggle('hidden', false); + }, - /* Now fill with new values. */ - settings.games.forEach((game) => { - const menuItem = document.createElement('paper-item'); - menuItem.setAttribute('value', game.folder); - menuItem.setAttribute('noink', ''); - menuItem.textContent = game.name; - gameMenu.appendChild(menuItem); - gameSelect.appendChild(menuItem.cloneNode(true)); + hide(elementId) { + document.getElementById(elementId).classList.toggle('hidden', true); + }, - const row = gameTable.addRow(game); - gameTable.setReadOnly(row, ['name', 'folder', 'type']); - }); + updateSelectedGame(gameFolder) { + document.getElementById('gameMenu').value = gameFolder; - gameSelect.value = settings.game; - document.getElementById('languageSelect').value = settings.language; - document.getElementById('enableDebugLogging').checked = settings.enableDebugLogging; - document.getElementById('updateMasterlist').checked = settings.updateMasterlist; + /* 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) { + const folderElements = table.tBodies[0].rows[i].getElementsByClassName('folder'); + if (folderElements.length === 1) { + table.setReadOnly(table.tBodies[0].rows[i], ['delete'], folderElements[0].value === gameFolder); + } + } + }, - updateEnabledGames(installedGames); - updateSelectedGame(gameFolder); -} + updateEnabledGames(installedGames) { + const gameMenuItems = document.getElementById('gameMenu').children; + for (let i = 0; i < gameMenuItems.length; ++i) { + if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) === -1) { + gameMenuItems[i].setAttribute('disabled', true); + gameMenuItems[i].removeEventListener('click', onChangeGame); + } else { + gameMenuItems[i].removeAttribute('disabled'); + gameMenuItems[i].addEventListener('click', onChangeGame); + } + } + }, + + updateSettingsDialog(settings, installedGames, gameFolder) { + const gameSelect = document.getElementById('defaultGameSelect'); + const gameMenu = document.getElementById('gameMenu'); + const gameTable = document.getElementById('gameTable'); + + /* First make sure game listing elements don't have any existing entries. */ + while (gameSelect.children.length > 1) { + gameSelect.removeChild(gameSelect.lastElementChild); + } + while (gameMenu.firstElementChild) { + gameMenu.firstElementChild.removeEventListener('click', onChangeGame); + gameMenu.removeChild(gameMenu.firstElementChild); + } + gameTable.clear(); + + /* Now fill with new values. */ + settings.games.forEach((game) => { + const menuItem = document.createElement('paper-item'); + menuItem.setAttribute('value', game.folder); + menuItem.setAttribute('noink', ''); + menuItem.textContent = game.name; + gameMenu.appendChild(menuItem); + gameSelect.appendChild(menuItem.cloneNode(true)); + + const row = gameTable.addRow(game); + gameTable.setReadOnly(row, ['name', 'folder', 'type']); + }); + + gameSelect.value = settings.game; + document.getElementById('languageSelect').value = settings.language; + document.getElementById('enableDebugLogging').checked = settings.enableDebugLogging; + document.getElementById('updateMasterlist').checked = settings.updateMasterlist; + + this.updateEnabledGames(installedGames); + this.updateSelectedGame(gameFolder); + }, + }; +})); diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 9b76f7a7..60ee0df9 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -47,7 +47,7 @@ function onGameMasterlistChange(evt) { document.getElementById('masterlistDate').textContent = evt.detail.date; } function onGameFolderChange(evt) { - updateSelectedGame(evt.detail.folder); + loot.dom.updateSelectedGame(evt.detail.folder); /* Enable/disable the redate plugins option. */ let index = undefined; if (loot.settings && loot.settings.games) { @@ -239,10 +239,10 @@ function onSortPlugins() { /* Now hide the masterlist update buttons, and display the accept and cancel sort buttons. */ - hideElement(document.getElementById('updateMasterlistButton')); - hideElement(document.getElementById('sortButton')); - showElement(document.getElementById('applySortButton')); - showElement(document.getElementById('cancelSortButton')); + loot.dom.hide('updateMasterlistButton'); + loot.dom.hide('sortButton'); + loot.dom.show('applySortButton'); + loot.dom.show('cancelSortButton'); /* Disable changing game. */ document.getElementById('gameMenu').setAttribute('disabled', ''); @@ -261,10 +261,10 @@ function onApplySort() { /* Now show the masterlist update buttons, and hide the accept and cancel sort buttons. */ - showElement(document.getElementById('updateMasterlistButton')); - showElement(document.getElementById('sortButton')); - hideElement(document.getElementById('applySortButton')); - hideElement(document.getElementById('cancelSortButton')); + loot.dom.show('updateMasterlistButton'); + loot.dom.show('sortButton'); + loot.dom.hide('applySortButton'); + loot.dom.hide('cancelSortButton'); /* Enable changing game. */ document.getElementById('gameMenu').removeAttribute('disabled'); @@ -280,10 +280,10 @@ function onCancelSort(evt) { /* Now show the masterlist update buttons, and hide the accept and cancel sort buttons. */ - showElement(document.getElementById('updateMasterlistButton')); - showElement(document.getElementById('sortButton')); - hideElement(document.getElementById('applySortButton')); - hideElement(document.getElementById('cancelSortButton')); + loot.dom.show('updateMasterlistButton'); + loot.dom.show('sortButton'); + loot.dom.hide('applySortButton'); + loot.dom.hide('cancelSortButton'); /* Enable changing game. */ document.getElementById('gameMenu').removeAttribute('disabled'); @@ -434,14 +434,14 @@ function onCloseSettingsDialog(evt) { /* Send the settings back to the C++ side. */ loot.query('closeSettings', settings).then(JSON.parse).then((installedGames) => { loot.installedGames = installedGames; - updateEnabledGames(installedGames); + loot.dom.updateEnabledGames(installedGames); }).catch(handlePromiseError).then(() => { loot.settings = settings; - updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); + loot.dom.updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); }).catch(handlePromiseError); } else { /* Re-apply the existing settings to the settings dialog elements. */ - updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); + loot.dom.updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); } evt.target.parentElement.close(); } diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 2308cba2..abf8d7ff 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -30,6 +30,7 @@ // Browser globals root.loot = root.loot || {}; root.loot.initialise = factory(root.loot.Dialog, + root.loot.dom, root.loot.Filters, root.loot.Game, root.loot.translateStaticText, @@ -37,7 +38,7 @@ root.loot.query, root.loot.Translator); } -}(this, (Dialog, Filters, Game, translateStaticText, Plugin, query, Translator) => { +}(this, (Dialog, dom, Filters, Game, translateStaticText, Plugin, query, Translator) => { function setupEventHandlers() { /* Set up handlers for filters. */ document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS); @@ -152,7 +153,7 @@ return query('getLanguages').then(JSON.parse).then((result) => { /* Now fill in language options. */ const settingsLangSelect = document.getElementById('languageSelect'); - const messageLangSelect = getElementInTableRowTemplate('messageRow', 'language'); + const messageLangSelect = dom.getElementInTableRowTemplate('messageRow', 'language'); result.forEach((language) => { const settingsItem = document.createElement('paper-item'); @@ -193,7 +194,7 @@ function setGameTypes() { return query('getGameTypes').then(JSON.parse).then((result) => { /* Fill in game row template's game type options. */ - const select = getElementInTableRowTemplate('gameRow', 'type'); + const select = dom.getElementInTableRowTemplate('gameRow', 'type'); result.forEach((gameType) => { const item = document.createElement('paper-item'); item.setAttribute('value', gameType); @@ -208,14 +209,14 @@ function setInstalledGames(appData) { return query('getInstalledGames').then(JSON.parse).then((installedGames) => { appData.installedGames = installedGames; - updateEnabledGames(installedGames); + dom.updateEnabledGames(installedGames); }); } function setSettings(appData) { return query('getSettings').then(JSON.parse).then((result) => { appData.settings = result; - updateSettingsDialog(appData.settings, appData.installedGames, appData.game.folder); + dom.updateSettingsDialog(appData.settings, appData.installedGames, appData.game.folder); }); } @@ -263,7 +264,7 @@ loot.filters = new Filters(loot.l10n); translateStaticText(loot.l10n); /* Also need to update the settings UI. */ - updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); + dom.updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder); }).then(() => { return displayInitErrors(); }).then((result) => { From 1ddcd05e3665e711dd821c23807198adf21203a9 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 27 Dec 2015 14:56:14 +0000 Subject: [PATCH 23/30] Simplify game menu event handling Handle selection changes rather than menu item clicks, which cleans up dom.js and means fewer event handlers need to be registered. --- src/gui/html/js/dom.js | 3 --- src/gui/html/js/events.js | 4 ++-- src/gui/html/js/init.js | 1 + 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/gui/html/js/dom.js b/src/gui/html/js/dom.js index 8d27275c..4403ec76 100644 --- a/src/gui/html/js/dom.js +++ b/src/gui/html/js/dom.js @@ -44,10 +44,8 @@ for (let i = 0; i < gameMenuItems.length; ++i) { if (installedGames.indexOf(gameMenuItems[i].getAttribute('value')) === -1) { gameMenuItems[i].setAttribute('disabled', true); - gameMenuItems[i].removeEventListener('click', onChangeGame); } else { gameMenuItems[i].removeAttribute('disabled'); - gameMenuItems[i].addEventListener('click', onChangeGame); } } }, @@ -62,7 +60,6 @@ gameSelect.removeChild(gameSelect.lastElementChild); } while (gameMenu.firstElementChild) { - gameMenu.firstElementChild.removeEventListener('click', onChangeGame); gameMenu.removeChild(gameMenu.firstElementChild); } gameTable.clear(); diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 60ee0df9..6f8f25a4 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -107,13 +107,13 @@ function onOpenLogLocation(evt) { } function onChangeGame(evt) { /* Check that the selected game isn't the current one. */ - if (evt.target.className.indexOf('core-selected') !== -1) { + if (!evt.detail.isSelected) { return; } /* Send off a CEF query with the folder name of the new game. */ loot.Dialog.showProgress(loot.l10n.translate('Loading game data...')); - loot.query('changeGame', evt.currentTarget.getAttribute('value')).then((result) => { + loot.query('changeGame', evt.detail.item.getAttribute('value')).then((result) => { /* Filters should be re-applied on game change, except the conflicts filter. Don't need to deactivate the others beforehand. Strictly not deactivating the conflicts filter either, just resetting it's value. diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index abf8d7ff..c0640f46 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -68,6 +68,7 @@ document.getElementById('helpButton').addEventListener('click', onOpenReadme); document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog); document.getElementById('quitButton').addEventListener('click', onQuit); + document.getElementById('gameMenu').addEventListener('core-select', onChangeGame); document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist); document.getElementById('sortButton').addEventListener('click', onSortPlugins); document.getElementById('applySortButton').addEventListener('click', onApplySort); From 44818ae9b4420896807ebcec5d44448528afb016 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Mon, 28 Dec 2015 19:28:45 +0000 Subject: [PATCH 24/30] Minor helpers.js tidying --- src/gui/html/js/helpers.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js index 3c214d77..fd92f52c 100644 --- a/src/gui/html/js/helpers.js +++ b/src/gui/html/js/helpers.js @@ -14,31 +14,29 @@ function getConflictingPlugins(pluginName) { loot.Dialog.showProgress(loot.l10n.translate('Checking if plugins have been loaded...')); return loot.query('getConflictingPlugins', pluginName).then(JSON.parse).then((result) => { + const conflicts = [pluginName]; if (result) { /* Filter everything but the plugin itself if there are no conflicts. */ - const conflicts = [pluginName]; for (const key in result) { if (result[key].conflicts) { conflicts.push(key); } - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name === key) { - loot.game.plugins[i].crc = result[key].crc; - loot.game.plugins[i].isEmpty = result[key].isEmpty; + const plugin = loot.game.plugins.find((item) => { + return item.name === key; + }); + if (plugin) { + plugin.crc = result[key].crc; + plugin.isEmpty = result[key].isEmpty; - loot.game.plugins[i].messages = result[key].messages; - loot.game.plugins[i].tags = result[key].tags; - loot.game.plugins[i].isDirty = result[key].isDirty; - break; - } + plugin.messages = result[key].messages; + plugin.tags = result[key].tags; + plugin.isDirty = result[key].isDirty; } } - loot.Dialog.closeProgress(); - return conflicts; } loot.Dialog.closeProgress(); - return [pluginName]; + return conflicts; }).catch(handlePromiseError); } function filterPluginData(plugins, filters) { @@ -69,5 +67,5 @@ function filterPluginData(plugins, filters) { hiddenMessageNo += plugin.messages.length - plugin.getCardContent(filters).messages.length; }); document.getElementById('hiddenMessageNo').textContent = hiddenMessageNo; - }); + }).catch(handlePromiseError); } From fee132ac9530e540c1da3fd23759daeeddbec8d9 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Mon, 28 Dec 2015 20:22:19 +0000 Subject: [PATCH 25/30] Remove external Vulcanize config file Call Vulcanize as a Node module rather than an executable, allowing excludes to be passed in the function call rather than in a config file. --- scripts/vulcanize.config.json | 5 ----- scripts/vulcanize.js | 40 +++++++++++++++++------------------ 2 files changed, 20 insertions(+), 25 deletions(-) delete mode 100644 scripts/vulcanize.config.json diff --git a/scripts/vulcanize.config.json b/scripts/vulcanize.config.json deleted file mode 100644 index b7f85720..00000000 --- a/scripts/vulcanize.config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "excludes": { - "styles": [ "css/theme.css" ] - } -} \ No newline at end of file diff --git a/scripts/vulcanize.js b/scripts/vulcanize.js index 38caf50b..af00c399 100755 --- a/scripts/vulcanize.js +++ b/scripts/vulcanize.js @@ -2,21 +2,26 @@ // Build the UI's index.html file. Takes one argument, which is the path to the // repository's root. 'use strict'; -const childProcess = require('child_process'); const path = require('path'); const fs = require('fs'); -const os = require('os'); const helpers = require('./helpers'); +const vulcanize = require('vulcanize'); + +function runVulcanize(err) { + if (err) { + console.error(err); + process.exit(1); + } + vulcanize.processDocument(); +} let rootPath = '.'; if (process.argv.length > 2) { rootPath = process.argv[2]; } -const releasePaths = helpers.getAppReleasePaths(rootPath); - -for (let i = 0; i < releasePaths.length; ++i) { - const outputPath = path.join(releasePaths[i].path, 'resources', 'ui'); +helpers.getAppReleasePaths(rootPath).forEach((releasePath) => { + const outputPath = path.join(releasePath.path, 'resources', 'ui'); // Makes sure output directory exists first. try { @@ -27,17 +32,12 @@ for (let i = 0; i < releasePaths.length; ++i) { } } - let vulcanize = path.join(rootPath, 'node_modules', '.bin', 'vulcanize'); - if (os.platform() === 'win32') { - vulcanize += '.cmd'; - } - - childProcess.execFileSync(vulcanize, [ - '--inline', - '--config', - path.join(rootPath, 'scripts', 'vulcanize.config.json'), - '-o', - path.join(outputPath, 'index.html'), - path.join(rootPath, 'src', 'gui', 'html', 'index.html'), - ]); -} + vulcanize.setOptions({ + inline: true, + excludes: { + styles: ['css/theme.css'], + }, + output: path.join(outputPath, 'index.html'), + input: path.join(rootPath, 'src', 'gui', 'html', 'index.html'), + }, runVulcanize); +}) From 29d5f69facbf8829c3b34cf3581831220235ec0c Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Mon, 28 Dec 2015 21:43:28 +0000 Subject: [PATCH 26/30] Simplify some event handling code --- src/gui/html/js/events.js | 245 ++++++++++++++------------------------ src/gui/html/js/init.js | 5 +- 2 files changed, 89 insertions(+), 161 deletions(-) diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 6f8f25a4..c5d8a5e6 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -49,17 +49,14 @@ function onGameMasterlistChange(evt) { function onGameFolderChange(evt) { loot.dom.updateSelectedGame(evt.detail.folder); /* Enable/disable the redate plugins option. */ - let index = undefined; + let gameSettings = undefined; if (loot.settings && loot.settings.games) { - for (let i = 0; i < loot.settings.games.length; ++i) { - if (loot.settings.games[i].folder === evt.detail.folder) { - index = i; - break; - } - } + gameSettings = loot.settings.games.find((game) => { + return game.folder === evt.detail.folder; + }); } const redateButton = document.getElementById('redatePluginsButton'); - if (index && loot.settings.games[index].type === 'Skyrim') { + if (gameSettings && gameSettings.type === 'Skyrim') { redateButton.removeAttribute('disabled'); } else { redateButton.setAttribute('disabled', true); @@ -82,6 +79,7 @@ function saveFilterState(evt) { loot.query('saveFilterState', evt.target.id, evt.target.checked).catch(handlePromiseError); } function onToggleDisplayCSS(evt) { + saveFilterState(evt); const attr = 'data-hide-' + evt.target.getAttribute('data-class'); if (evt.target.checked) { document.getElementById('main').setAttribute(attr, true); @@ -89,20 +87,14 @@ function onToggleDisplayCSS(evt) { document.getElementById('main').removeAttribute(attr); } - if (evt.target.id !== 'hideBashTags') { - /* Now perform search again. If there is no current search, this won't - do anything. */ - document.getElementById('searchBar').search(); + if (evt.target.id === 'hideBashTags') { + document.getElementById('main').lastElementChild.updateSize(); } -} -function onToggleBashTags(evt) { - onToggleDisplayCSS(evt); - document.getElementById('main').lastElementChild.updateSize(); /* Now perform search again. If there is no current search, this won't do anything. */ document.getElementById('searchBar').search(); } -function onOpenLogLocation(evt) { +function onOpenLogLocation() { loot.query('openLogLocation').catch(handlePromiseError); } function onChangeGame(evt) { @@ -129,10 +121,7 @@ function onChangeGame(evt) { /* Parse the data sent from C++. */ const gameInfo = JSON.parse(result, loot.Plugin.fromJson); - loot.game.folder = gameInfo.folder; - loot.game.masterlist = gameInfo.masterlist; - loot.game.globalMessages = gameInfo.globalMessages; - loot.game.plugins = gameInfo.plugins; + loot.game = new loot.Game(gameInfo, loot.l10n); /* Reset virtual list positions. */ document.getElementById('cardsNav').scrollToItem(0); @@ -144,7 +133,7 @@ function onChangeGame(evt) { loot.Dialog.closeProgress(); }).catch(handlePromiseError); } -function onOpenReadme(evt) { +function onOpenReadme() { loot.query('openReadme').catch(handlePromiseError); } /* Masterlist update process, minus progress dialog. */ @@ -155,17 +144,17 @@ function updateMasterlistNoProgress() { loot.game.masterlist = result.masterlist; loot.game.globalMessages = result.globalMessages; - result.plugins.forEach((plugin) => { - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name === plugin.name) { - loot.game.plugins[i].isDirty = plugin.isDirty; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].masterlist = plugin.masterlist; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].tags = plugin.tags; - break; - } + result.plugins.forEach((resultPlugin) => { + const existingPlugin = loot.game.plugins.find((plugin) => { + return plugin.name === resultPlugin.name; + }); + if (existingPlugin) { + existingPlugin.isDirty = resultPlugin.isDirty; + existingPlugin.isPriorityGlobal = resultPlugin.isPriorityGlobal; + existingPlugin.masterlist = resultPlugin.masterlist; + existingPlugin.messages = resultPlugin.messages; + existingPlugin.priority = resultPlugin.priority; + existingPlugin.tags = resultPlugin.tags; } }); /* Hack to stop cards overlapping. */ @@ -186,9 +175,9 @@ function onUpdateMasterlist() { function onSortPlugins() { if (document.body.hasAttribute('data-conflicts')) { /* Deactivate any existing plugin conflict filter. */ - for (let i = 0; i < loot.game.plugins.length; ++i) { - loot.game.plugins[i].isConflictFilterChecked = false; - } + loot.game.plugins.forEach((plugin) => { + plugin.isConflictFilterChecked = false; + }); /* Un-highlight any existing filter plugin. */ const cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); for (let i = 0; i < cards.length; ++i) { @@ -197,9 +186,9 @@ function onSortPlugins() { document.body.removeAttribute('data-conflicts'); } - let promise = Promise.resolve(''); + let promise = Promise.resolve(); if (loot.settings.updateMasterlist) { - promise = promise.then(updateMasterlistNoProgress()); + promise = promise.then(updateMasterlistNoProgress); } promise.then(() => { loot.Dialog.showProgress(loot.l10n.translate('Sorting plugins...')); @@ -211,26 +200,20 @@ function onSortPlugins() { loot.game.oldLoadOrder = loot.game.plugins; loot.game.loadOrder = []; result.forEach((plugin) => { - let found = false; - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name === plugin.name) { - loot.game.plugins[i].crc = plugin.crc; - loot.game.plugins[i].isEmpty = plugin.isEmpty; + let existingPlugin = loot.game.plugins.find((item) => { + return item.name === plugin.name; + }); + if (existingPlugin) { + existingPlugin.crc = plugin.crc; + existingPlugin.isEmpty = plugin.isEmpty; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - loot.game.loadOrder.push(loot.game.plugins[i]); - - found = true; - break; - } - } - if (!found) { - loot.game.plugins.push(new loot.Plugin(plugin)); - loot.game.loadOrder.push(loot.game.plugins[loot.game.plugins.length - 1]); + existingPlugin.messages = plugin.messages; + existingPlugin.tags = plugin.tags; + existingPlugin.isDirty = plugin.isDirty; + } else { + existingPlugin = new loot.Plugin(plugin); } + loot.game.loadOrder.push(existingPlugin); }); /* Now update the UI for the new order. */ @@ -250,11 +233,10 @@ function onSortPlugins() { }).catch(handlePromiseError); } function onApplySort() { - const loadOrder = []; - loot.game.plugins.forEach((plugin) => { - loadOrder.push(plugin.name); + const loadOrder = loot.game.plugins.map((plugin) => { + return plugin.name; }); - return loot.query('applySort', loadOrder).then((result) => { + return loot.query('applySort', loadOrder).then(() => { /* Remove old load order storage. */ delete loot.game.loadOrder; delete loot.game.oldLoadOrder; @@ -270,7 +252,7 @@ function onApplySort() { document.getElementById('gameMenu').removeAttribute('disabled'); }).catch(handlePromiseError); } -function onCancelSort(evt) { +function onCancelSort() { return loot.query('cancelSort').then(() => { /* Sort UI elements again according to stored old load order. */ loot.game.plugins = loot.game.oldLoadOrder; @@ -313,19 +295,18 @@ function onClearAllMetadata() { } /* Need to empty the UI-side user metadata. */ plugins.forEach((plugin) => { - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name === plugin.name) { - loot.game.plugins[i].userlist = undefined; - loot.game.plugins[i].editor = undefined; + const existingPlugin = loot.game.plugins.find((item) => { + return item.name === plugin.name; + }); + if (existingPlugin) { + existingPlugin.userlist = undefined; + existingPlugin.editor = undefined; - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - break; - } + existingPlugin.priority = plugin.priority; + existingPlugin.isPriorityGlobal = plugin.isPriorityGlobal; + existingPlugin.messages = plugin.messages; + existingPlugin.tags = plugin.tags; + existingPlugin.isDirty = plugin.isDirty; } }); @@ -334,21 +315,21 @@ function onClearAllMetadata() { }); } function onCopyContent() { - const messages = []; - const plugins = []; + let messages = []; + let plugins = []; if (loot.game) { if (loot.game.globalMessages) { - loot.game.globalMessages.forEach((message) => { - messages.push({ + messages = loot.game.globalMessages.map((message) => { + return { type: message.type, content: message.content[0].str, - }); + }; }); } if (loot.game.plugins) { - loot.game.plugins.forEach((plugin) => { - plugins.push({ + plugins = loot.game.plugins.map((plugin) => { + return { name: plugin.name, crc: plugin.crc, version: plugin.version, @@ -361,7 +342,7 @@ function onCopyContent() { messages: plugin.messages, tags: plugin.tags, isDirty: plugin.isDirty, - }); + }; }); } } else { @@ -375,21 +356,19 @@ function onCopyContent() { } loot.query('copyContent', { - messages: messages, - plugins: plugins, + messages, + plugins, }).then(() => { loot.Dialog.showNotification(loot.l10n.translate("LOOT's content has been copied to the clipboard.")); }).catch(handlePromiseError); } function onCopyLoadOrder() { - const plugins = []; + let plugins = []; - if (loot.game) { - if (loot.game.plugins) { - loot.game.plugins.forEach((plugin) =>{ - plugins.push(plugin.name); - }); - } + if (loot.game && loot.game.plugins) { + plugins = loot.game.plugins.map((plugin) => { + return plugin.name; + }); } loot.query('copyLoadOrder', plugins).then(() => { @@ -557,11 +536,11 @@ function onEditorClose(evt) { } function onConflictsFilter(evt) { /* Deactivate any existing plugin conflict filter. */ - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].id !== evt.target.id) { - loot.game.plugins[i].isConflictFilterChecked = false; + loot.game.plugins.forEach((plugin) => { + if (plugin.id !== evt.target.id) { + plugin.isConflictFilterChecked = false; } - } + }); /* Un-highlight any existing filter plugin. */ const cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); for (let i = 0; i < cards.length; ++i) { @@ -591,19 +570,18 @@ function onClearMetadata(evt) { return; } /* Need to empty the UI-side user metadata. */ - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].id === evt.target.id) { - loot.game.plugins[i].userlist = undefined; - loot.game.plugins[i].editor = undefined; + const existingPlugin = loot.game.plugins.find((item) => { + return item.id === evt.target.id; + }); + if (existingPlugin) { + existingPlugin.userlist = undefined; + existingPlugin.editor = undefined; - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - break; - } + existingPlugin.priority = plugin.priority; + existingPlugin.isPriorityGlobal = plugin.isPriorityGlobal; + existingPlugin.messages = plugin.messages; + existingPlugin.tags = plugin.tags; + existingPlugin.isDirty = plugin.isDirty; } loot.Dialog.showNotification(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName())); /* Now perform search again. If there is no current search, this won't @@ -645,7 +623,7 @@ function handleUnappliedChangesClose(change) { }).catch(handlePromiseError); }); } -function onQuit(evt) { +function onQuit() { if (!document.getElementById('applySortButton').classList.contains('hidden')) { handleUnappliedChangesClose(loot.l10n.translate('sorted load order')); } else if (document.body.hasAttribute('data-editors')) { @@ -661,57 +639,10 @@ function onJumpToGeneralInfo() { function onContentRefresh() { /* Send a query for updated load order and plugin header info. */ loot.Dialog.showProgress(loot.l10n.translate('Refreshing data...')); - loot.query('getGameData').then(JSON.parse).then((result) => { + loot.query('getGameData').then((result) => { /* Parse the data sent from C++. */ - /* We don't want the plugin info creating cards, so don't convert - to plugin objects. */ - const gameInfo = result; - - /* Now overwrite plugin data with the newly sent data. Also update - card and li vars as they were unset when the game was switched - from before. */ - const pluginNames = []; - gameInfo.plugins.forEach((plugin) => { - let foundPlugin = false; - for (let i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name === plugin.name) { - loot.game.plugins[i].isActive = plugin.isActive; - loot.game.plugins[i].isEmpty = plugin.isEmpty; - loot.game.plugins[i].loadsArchive = plugin.loadsArchive; - loot.game.plugins[i].crc = plugin.crc; - loot.game.plugins[i].version = plugin.version; - - loot.game.plugins[i].priority = plugin.priority; - loot.game.plugins[i].isPriorityGlobal = plugin.isPriorityGlobal; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - foundPlugin = true; - break; - } - } - if (!foundPlugin) { - /* A new plugin. */ - loot.game.plugins.push(new loot.Plugin(plugin)); - } - pluginNames.push(plugin.name); - }); - for (let i = 0; i < loot.game.plugins.length;) { - let foundPlugin = false; - for (let j = 0; j < pluginNames.length; ++j) { - if (loot.game.plugins[i].name === pluginNames[j]) { - foundPlugin = true; - break; - } - } - if (!foundPlugin) { - /* Remove plugin. */ - loot.game.plugins.splice(i, 1); - } else { - ++i; - } - } + const game = JSON.parse(result, loot.Plugin.fromJson); + loot.game = new loot.Game(game, loot.l10n); /* Reapply filters. */ filterPluginData(loot.game.plugins, loot.filters); diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index c0640f46..8e152a1e 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -42,11 +42,8 @@ function setupEventHandlers() { /* Set up handlers for filters. */ document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS); - document.getElementById('hideVersionNumbers').addEventListener('change', saveFilterState); document.getElementById('hideCRCs').addEventListener('change', onToggleDisplayCSS); - document.getElementById('hideCRCs').addEventListener('change', saveFilterState); - document.getElementById('hideBashTags').addEventListener('change', onToggleBashTags); - document.getElementById('hideBashTags').addEventListener('change', saveFilterState); + document.getElementById('hideBashTags').addEventListener('change', onToggleDisplayCSS); document.getElementById('hideNotes').addEventListener('change', onSidebarFilterToggle); document.getElementById('hideDoNotCleanMessages').addEventListener('change', onSidebarFilterToggle); document.getElementById('hideInactivePlugins').addEventListener('change', onSidebarFilterToggle); From 9cc5f149278ad4277c4f205ac0f53f64c17fa6f2 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Tue, 29 Dec 2015 15:10:01 +0000 Subject: [PATCH 27/30] Use hidden HTML attribute instead of class Fixes #520. --- src/gui/html/css/style.css | 3 --- src/gui/html/elements/loot-message-dialog.html | 2 +- src/gui/html/elements/loot-plugin-card.html | 8 +++----- src/gui/html/elements/loot-plugin-item.html | 9 ++------- src/gui/html/elements/loot-search.html | 9 +++------ src/gui/html/index.html | 4 ++-- src/gui/html/js/dom.js | 4 ++-- src/gui/html/js/events.js | 4 ++-- 8 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/gui/html/css/style.css b/src/gui/html/css/style.css index 65380f33..17f2b0b7 100644 --- a/src/gui/html/css/style.css +++ b/src/gui/html/css/style.css @@ -38,9 +38,6 @@ html /deep/ ::-webkit-scrollbar-thumb { html /deep/ paper-button[autofocus] { color: #64B5F6; } -.hidden { - display:none; -} a { color: #2196F3; text-decoration: none; diff --git a/src/gui/html/elements/loot-message-dialog.html b/src/gui/html/elements/loot-message-dialog.html index e606a2cb..e6596124 100644 --- a/src/gui/html/elements/loot-message-dialog.html +++ b/src/gui/html/elements/loot-message-dialog.html @@ -38,7 +38,7 @@ was pressed. }, setDismissable: function(isDialogDismissable) { - this.shadowRoot.getElementById('dismiss').classList.toggle('hidden', !isDialogDismissable); + this.shadowRoot.getElementById('dismiss').hidden = !isDialogDismissable; }, onButtonClick: function(evt) { diff --git a/src/gui/html/elements/loot-plugin-card.html b/src/gui/html/elements/loot-plugin-card.html index c8ddd05a..e72ef050 100644 --- a/src/gui/html/elements/loot-plugin-card.html +++ b/src/gui/html/elements/loot-plugin-card.html @@ -116,8 +116,6 @@ loot-clear-metadata font-weight: 400; font-size: 1rem; } - .hidden, - content::content > .hidden, :host-context(#main[data-hide-crc]) content::content > .crc, :host-context(#main[data-hide-tag]) content::content > .tag, :host-context(#main[data-hide-version]) content::content > .version { @@ -285,10 +283,10 @@ loot-clear-metadata var cardContent = this.data.getCardContent(loot.filters); var tagsAdded = this.getElementsByClassName('tag add')[0]; tagsAdded.textContent = cardContent.tags.added; - tagsAdded.classList.toggle('hidden', cardContent.tags.added.length == 0); + tagsAdded.hidden = cardContent.tags.added.length === 0; var tagsRemoved = this.getElementsByClassName('tag remove')[0]; tagsRemoved.textContent = cardContent.tags.removed; - tagsRemoved.classList.toggle('hidden', cardContent.tags.removed.length == 0); + tagsRemoved.hidden = cardContent.tags.removed.length === 0; } }, @@ -308,7 +306,7 @@ loot-clear-metadata messageLi.innerHTML = marked(message.content); messageUL.appendChild(messageLi); }); - messageUL.classList.toggle('hidden', cardContent.messages.length == 0); + messageUL.hidden = cardContent.messages.length === 0; } }, diff --git a/src/gui/html/elements/loot-plugin-item.html b/src/gui/html/elements/loot-plugin-item.html index 2cfb7675..cddea96b 100644 --- a/src/gui/html/elements/loot-plugin-item.html +++ b/src/gui/html/elements/loot-plugin-item.html @@ -165,19 +165,14 @@ onPriorityChange: function(oldValue, newValue) { if (this.data && this.data.priority) { this.shadowRoot.getElementById('priority').textContent = this.data.priority; - this.shadowRoot.getElementById('secondary').classList.remove('hidden'); } else { this.shadowRoot.getElementById('priority').textContent = ''; - this.shadowRoot.getElementById('secondary').classList.add('hidden'); } + this.shadowRoot.getElementById('secondary').hidden = !(this.data && this.data.priority); }, onPriorityIsGlobalChange: function(oldValue, newValue) { - if (this.data && this.data.isPriorityGlobal) { - this.shadowRoot.getElementById('secondary').firstElementChild.classList.remove('hidden'); - } else { - this.shadowRoot.getElementById('secondary').firstElementChild.classList.add('hidden'); - } + this.shadowRoot.getElementById('secondary').hidden = !(this.data && this.data.isPriorityGlobal); }, onEditorStateChange: function(oldValue, newValue) { diff --git a/src/gui/html/elements/loot-search.html b/src/gui/html/elements/loot-search.html index 1fc91aee..5a36f840 100644 --- a/src/gui/html/elements/loot-search.html +++ b/src/gui/html/elements/loot-search.html @@ -29,13 +29,10 @@ searchTarget is the ID of the core-list element to search the elements of. font-size: 0.857rem; color: rgba(255, 255, 255, 0.7); } - .hidden { - display: none; - } -