diff --git a/resources/report/css/style.css b/resources/report/css/style.css index c89c6362..c5346452 100644 --- a/resources/report/css/style.css +++ b/resources/report/css/style.css @@ -290,9 +290,14 @@ li.warn{ } .tag.add { color:green; + margin-left: 1.75em; } .tag.remove { color:red; + margin-left: 1.75em; +} +plugin-card > ul { + margin-left: 1.75em; } #settings { diff --git a/resources/report/js/custom.js b/resources/report/js/custom.js index 1051f6cb..7b6f12b6 100644 --- a/resources/report/js/custom.js +++ b/resources/report/js/custom.js @@ -28,16 +28,22 @@ var pluginCardProto = Object.create(HTMLElement.prototype, { onMenuItemClick: { value: function(evt) { + if (evt.target.id == 'editMetadata') { + } else if (evt.target.id == 'copyMetadata') { + + } else if (evt.target.id == 'clearMetadata') { + showMessageDialog('Clear Plugin Metadata', 'Are you sure you want to clear all existing user-added metadata from "' + evt.target.parentElement.parentElement.querySelector('h1').textContent + '"?'); + } } }, onMenuClick: { value: function(evt) { var section = evt.currentTarget.parentElement.parentElement; - section.querySelector('#editMetadata').addEventListener('click', this.onMenuItemClick, false); - section.querySelector('#copyMetadata').addEventListener('click', this.onMenuItemClick, false); - section.querySelector('#clearMetadata').addEventListener('click', this.onMenuItemClick, false); + section.querySelector('#editMetadata').addEventListener('click', section.parentNode.host.onMenuItemClick, false); + section.querySelector('#copyMetadata').addEventListener('click', section.parentNode.host.onMenuItemClick, false); + section.querySelector('#clearMetadata').addEventListener('click', section.parentNode.host.onMenuItemClick, false); section.querySelector('#menu').classList.toggle('hidden'); } @@ -61,6 +67,16 @@ var pluginCardProto = Object.create(HTMLElement.prototype, { version.className = 'version'; this.appendChild(version); + var tagAdd = document.createElement('div'); + tagAdd.className = 'tag add'; + this.appendChild(tagAdd); + var tagRemove = document.createElement('div'); + tagRemove.className = 'tag remove'; + this.appendChild(tagRemove); + + var messages = document.createElement('ul'); + this.appendChild(messages); + this.shadowRoot.querySelector('#menuButton').addEventListener('click', this.onMenuClick, false); } diff --git a/resources/report/js/plugin.js b/resources/report/js/plugin.js index 5fe1a162..6a2a3e8a 100644 --- a/resources/report/js/plugin.js +++ b/resources/report/js/plugin.js @@ -34,18 +34,24 @@ function Plugin(obj) { 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.id = this.name.replace(/\s+/g, ''); Plugin.prototype.getTagsStrings = function () { var tagsAdded = []; var tagsRemoved = []; - if (this.masterlist && this.masterlist.tag) { - for (var i = 0; i < this.masterlist.tag.length; ++i) { - if (this.masterlist.tag[i].name[0] == '-') { - tagsRemoved.push(this.masterlist.tag[i].name); + 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.masterlist.tag[i].name); + tagsAdded.push(this.tags[i].name); } } } @@ -53,28 +59,7 @@ function Plugin(obj) { Prefer the removed list. */ for (var i = 0; i < tagsAdded.length; ++i) { for (var j = 0; j < tagsRemoved.length; ++j) { - if (tagsRemoved[j].name.toLowerCase() == tagsAdded[i].name.toLowerCase()) { - /* Remove tag from the tagsAdded array. */ - tagsAdded.splice(i, 1); - --i; - } - } - } - - if (this.userlist && this.userlist.tag) { - for (var i = 0; i < this.userlist.tag.length; ++i) { - if (this.userlist.tag[i][0] == '-') { - tagsRemoved.push(this.userlist.tag[i]); - } else { - tagsAdded.push(this.userlist.tag[i]); - } - } - } - /* Now again 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].name.toLowerCase() == tagsAdded[i].name.toLowerCase()) { + if (tagsRemoved[j].toLowerCase() == tagsAdded[i].toLowerCase()) { /* Remove tag from the tagsAdded array. */ tagsAdded.splice(i, 1); --i; @@ -95,40 +80,67 @@ function Plugin(obj) { this.card = card; card.id = this.id; - card.querySelector('h1').textContent = this.name; - if (this.crc != '0') { - card.querySelector('.crc').textContent = this.crc; - } - card.querySelector('.version').textContent = this.version; card.setAttribute('data-active', this.isActive); card.setAttribute('data-dummy', this.isDummy); card.setAttribute('data-bsa', this.loadsBSA); card.setAttribute('data-edits', this.userlist != undefined); + /* Fill in name, version, CRC. */ + card.querySelector('h1').textContent = this.name; + card.querySelector('.version').textContent = this.version; + if (this.crc != '0') { + card.querySelector('.crc').textContent = this.crc; + } + + /* Fill in Bash Tag suggestions. */ + var tags = this.getTagsStrings(); + if (tags.tagsAdded) { + card.getElementsByClassName('tag add')[0].textContent = tags.tagsAdded; + } else { + card.getElementsByClassName('tag add')[0].classList.toggle('hidden'); + } + if (tags.tagsRemoved) { + card.getElementsByClassName('tag remove')[0].textContent = tags.tagsRemoved; + } else { + card.getElementsByClassName('tag remove')[0].classList.toggle('hidden'); + } + + /* Fill in messages. */ + if (this.messages && this.messages.length != 0) { + 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); + card.getElementsByTagName('ul')[0].appendChild(messageLi); + + }); + } else { + card.getElementsByTagName('ul')[0].classList.toggle('hidden'); + } + + /* The content elements steal the name, CRC and version, so they + don't get distributed into the editor part of the shadow DOM. + Update the editor elements manually. */ + + card.shadowRoot.querySelector('#editor h1').textContent = this.name; + card.shadowRoot.querySelector('#editor .version').textContent = this.version; + if (this.crc != '0') { + card.shadowRoot.querySelector('#editor .crc').textContent = this.crc; + } + document.getElementById('main').appendChild(card); } Plugin.prototype.getPriorityString = function() { - if (this.userlist) { - var priorityText = 'Priority: ' + this.userlist.modPriority + ', Global: '; - if (this.userlist.isGlobalPriority) { - priorityText += '✓'; - } else { - priorityText += '✗'; - } - return priorityText; - } else if (this.masterlist) { - var priorityText = 'Priority: ' + this.masterlist.modPriority + ', Global: '; - if (this.masterlist.isGlobalPriority) { - priorityText += '✓'; - } else { - priorityText += '✗'; - } - return priorityText; + var priorityText = 'Priority: ' + this.modPriority + ', Global: '; + if (this.isGlobalPriority) { + priorityText += '✓'; } else { - return 'Priority: 0, Global: ✗'; + priorityText += '✗'; } + return priorityText; } Plugin.prototype.createListItem = function() { diff --git a/resources/report/js/script.js b/resources/report/js/script.js index edb1accf..438b6173 100644 --- a/resources/report/js/script.js +++ b/resources/report/js/script.js @@ -763,42 +763,21 @@ function updateInterfaceWithGameInfo(response) { ++activePluginNo; } - /*if (plugin.isDirty) { + if (plugin.isDirty) { ++dirtyPluginNo; - }*/ - -/* - var tags = getTagsStrings(plugin); - if (tags.tagsAdded) { - clone.getElementsByClassName('tag add')[0].textContent = tags.tagsAdded; - } else { - hideElement(clone.getElementsByClassName('tag add')[0]); - } - if (tags.tagsRemoved) { - clone.getElementsByClassName('tag remove')[0].textContent = tags.tagsRemoved; - } else { - hideElement(clone.getElementsByClassName('tag remove')[0]); } - if (plugin.masterlist && plugin.masterlist.msg && plugin.masterlist.msg.length != 0) { - plugin.masterlist.msg.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); - clone.getElementsByTagName('ul')[0].appendChild(messageLi); + if (plugin.messages && plugin.messages.length != 0) { + plugin.messages.forEach(function(message) { - if (messageLi.className == 'warn') { + if (message.type == 'warn') { warnMessageNo++; - } else if (messageLi.className == 'error') { + } else if (message.type == 'error') { errorMessageNo++; } totalMessageNo++; }); - } else { - clone.getElementsByTagName('ul')[0].className += ' hidden'; } -*/ }); document.getElementById('filterTotalMessageNo').textContent = totalMessageNo; document.getElementById('totalMessageNo').textContent = totalMessageNo; diff --git a/resources/report/report.html b/resources/report/report.html index 1d2778ad..72b7948d 100644 --- a/resources/report/report.html +++ b/resources/report/report.html @@ -182,8 +182,8 @@

-
-
+ +
diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index d9d06905..b705936e 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -221,9 +221,9 @@ namespace loot { // Now store plugin data. for (const auto& plugin : installed) { - // Test data has 'hasUserEdits', and 'tagsAdd', 'tagsRemove' keys, but - // the first will be handled by userlist lookups, and the other two are probably - // going to get moved around, haven't decided how best to handle the split between masterlist, userlist and plugin-sourced metadata. + /* Each plugin has members while hold its raw masterlist and userlist data for + the editor, and also processed data for the main display. + */ YAML::Node pluginNode; pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. pluginNode["name"] = plugin.Name(); @@ -239,22 +239,7 @@ namespace loot { mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(plugin.Name())); if (!mlistPlugin.HasNameOnly()) { - //Evaluate any conditions - BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; - try { - mlistPlugin.EvalAllConditions(g_app_state.CurrentGame(), language); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "\"" << mlistPlugin.Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); - g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, (format(loc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % mlistPlugin.Name() % e.what()).str())); - } - - //Also check install validity. - BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data."; - mlistPlugin.CheckInstallValidity(g_app_state.CurrentGame()); - - // Now add the masterlist metadata to the pluginNode. - + // Now add the masterlist metadata to the pluginNode.] pluginNode["masterlist"]["modPriority"] = modulo(mlistPlugin.Priority(), max_priority); pluginNode["masterlist"]["isGlobalPriority"] = (abs(mlistPlugin.Priority()) >= max_priority); pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); @@ -273,20 +258,6 @@ namespace loot { ulistPlugin.MergeMetadata(g_app_state.CurrentGame().userlist.FindPlugin(plugin.Name())); if (!ulistPlugin.HasNameOnly()) { - //Evaluate any conditions - BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; - try { - ulistPlugin.EvalAllConditions(g_app_state.CurrentGame(), language); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "\"" << ulistPlugin.Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); - g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, (format(loc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % ulistPlugin.Name() % e.what()).str())); - } - - //Also check install validity. - BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data."; - ulistPlugin.CheckInstallValidity(g_app_state.CurrentGame()); - // Now add the masterlist metadata to the pluginNode. pluginNode["userlist"]["modPriority"] = modulo(ulistPlugin.Priority(), max_priority); @@ -299,6 +270,66 @@ namespace loot { pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); } + // Now merge masterlist and userlist metadata and evaluate, + // putting any resulting metadata into the base of the pluginNode. + + mlistPlugin.MergeMetadata(ulistPlugin); + + //Evaluate any conditions + BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; + try { + mlistPlugin.EvalAllConditions(g_app_state.CurrentGame(), language); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "\"" << mlistPlugin.Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); + g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, (format(loc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % mlistPlugin.Name() % e.what()).str())); + } + + //Also check install validity. + BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data."; + mlistPlugin.CheckInstallValidity(g_app_state.CurrentGame()); + + // Also evaluate dirty info. + std::list messages = mlistPlugin.Messages(); + std::set dirtyInfo = mlistPlugin.DirtyInfo(); + size_t numDirtyInfo(0); + for (const auto &element : dirtyInfo) { + boost::format f; + if (element.ITMs() > 0 && element.UDRs() > 0 && element.DeletedNavmeshes() > 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records, %2% UDR records and %3% deleted navmeshes. Clean with %4%.")) % element.ITMs() % element.UDRs() % element.DeletedNavmeshes() % element.CleaningUtility(); + else if (element.ITMs() == 0 && element.UDRs() == 0 && element.DeletedNavmeshes() == 0) + f = boost::format(boost::locale::translate("Clean with %1%.")) % element.CleaningUtility(); + + + else if (element.ITMs() == 0 && element.UDRs() > 0 && element.DeletedNavmeshes() > 0) + f = boost::format(boost::locale::translate("Contains %1% UDR records and %2% deleted navmeshes. Clean with %3%.")) % element.UDRs() % element.DeletedNavmeshes() % element.CleaningUtility(); + else if (element.ITMs() == 0 && element.UDRs() == 0 && element.DeletedNavmeshes() > 0) + f = boost::format(boost::locale::translate("Contains %1% deleted navmeshes. Clean with %2%.")) % element.DeletedNavmeshes() % element.CleaningUtility(); + else if (element.ITMs() == 0 && element.UDRs() > 0 && element.DeletedNavmeshes() == 0) + f = boost::format(boost::locale::translate("Contains %1% UDR records. Clean with %2%.")) % element.UDRs() % element.CleaningUtility(); + + else if (element.ITMs() > 0 && element.UDRs() == 0 && element.DeletedNavmeshes() > 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%.")) % element.ITMs() % element.DeletedNavmeshes() % element.CleaningUtility(); + else if (element.ITMs() > 0 && element.UDRs() == 0 && element.DeletedNavmeshes() == 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records. Clean with %2%.")) % element.ITMs() % element.CleaningUtility(); + + else if (element.ITMs() > 0 && element.UDRs() > 0 && element.DeletedNavmeshes() == 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% UDR records. Clean with %3%.")) % element.ITMs() % element.UDRs() % element.CleaningUtility(); + + messages.push_back(loot::Message(loot::Message::warn, f.str())); + ++numDirtyInfo; + } + + // Now add to pluginNode. + pluginNode["modPriority"] = modulo(mlistPlugin.Priority(), max_priority); + pluginNode["isGlobalPriority"] = (abs(mlistPlugin.Priority()) >= max_priority); + pluginNode["messages"] = messages; + pluginNode["tags"] = mlistPlugin.Tags(); + pluginNode["isDirty"] = (numDirtyInfo > 0); + + BOOST_LOG_TRIVIAL(trace) << "messages length: " << messages.size(); + BOOST_LOG_TRIVIAL(trace) << "tags length: " << mlistPlugin.Tags().size(); + gameNode["plugins"].push_back(pluginNode); }