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
+
+
+
+
+
+
+
+
+
+
+
+