diff --git a/resources/report/js/custom.js b/resources/report/js/custom.js index 29ed4e0c..013d70f6 100644 --- a/resources/report/js/custom.js +++ b/resources/report/js/custom.js @@ -55,11 +55,18 @@ var pluginMenuProto = Object.create(HTMLElement.prototype, { ] }); - loot.query(request).then(function(result){ - /* Need to also empty the UI-side user metadata. */ + loot.query(request).then(JSON.parse).then(function(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 == pluginID) { loot.game.plugins[i].userlist = undefined; + + loot.game.plugins[i].modPriority = result.modPriority; + loot.game.plugins[i].isGlobalPriority = result.isGlobalPriority; + loot.game.plugins[i].messages = result.messages; + loot.game.plugins[i].tags = result.tags; + loot.game.plugins[i].isDirty = result.isDirty; + break; } } diff --git a/resources/report/js/plugin.js b/resources/report/js/plugin.js index 49e45388..2fa437f6 100644 --- a/resources/report/js/plugin.js +++ b/resources/report/js/plugin.js @@ -58,7 +58,9 @@ function Plugin(obj) { return data; } - Plugin.prototype.getTagsStrings = function () { + + + Plugin.prototype.updateCardTags = function() { var tagsAdded = []; var tagsRemoved = []; @@ -83,11 +85,16 @@ function Plugin(obj) { } } - return { - tagsAdded: tagsAdded.join(', '), - tagsRemoved: tagsRemoved.join(', ') - }; - + if (tagsAdded.length != 0) { + this.card.getElementsByClassName('tag add')[0].textContent = tagsAdded.join(', '); + } else { + this.card.getElementsByClassName('tag add')[0].classList.toggle('hidden'); + } + if (tagsRemoved.length != 0) { + this.card.getElementsByClassName('tag remove')[0].textContent = tagsRemoved.join(', '); + } else { + this.card.getElementsByClassName('tag remove')[0].classList.toggle('hidden'); + } } Plugin.prototype.getPriorityString = function() { @@ -100,6 +107,27 @@ function Plugin(obj) { return priorityText; } + Plugin.prototype.updateCardMessages = function() { + var messageUL = this.card.getElementsByTagName('ul')[0]; + /* First clear any existing messages. */ + while(messageUL.firstElementChild) { + messageUL.removeChild(messageUL.firstElementChild); + } + /* Now add the new 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); + messageUL.appendChild(messageLi); + + }); + } else { + this.card.getElementsByTagName('ul')[0].classList.toggle('hidden'); + } + } + Plugin.prototype.createCard = function() { var card = new PluginCard(); this.card = card; @@ -119,31 +147,10 @@ function Plugin(obj) { } /* 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'); - } + this.updateCardTags(); /* 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'); - } + this.updateCardMessages(); /* The content elements steal the name, CRC and version, so they don't get distributed into the editor part of the shadow DOM. @@ -328,6 +335,57 @@ function Plugin(obj) { if (change.name == 'userlist') { change.object.li.setAttribute('data-edits', change.object[change.name] != undefined); change.object.card.setAttribute('data-edits', change.object[change.name] != undefined); + } else if (change.name == 'modPriority') { + change.object.li.querySelector('.priority').textContent = change.object.getPriorityString(); + change.object.card.shadowRoot.getElementById('priorityValue').value = change.object[change.name]; + } else if (change.name == 'isGlobalPriority') { + change.object.li.querySelector('.priority').textContent = change.object.getPriorityString(); + } else if (change.name == 'messages') { + change.object.updateCardMessages(); + /* For messages, the card's messages need updating, + as do the message counts. */ + var oldTotal = 0; + var newTotal = 0; + var oldWarns = 0; + var newWarns = 0; + var oldErrs = 0; + var newErrs = 0; + + if (change.oldValue) { + oldTotal = change.oldValue.length; + + 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; + + change.object[change.name].forEach(function(message){ + if (message.type == 'warn') { + ++newWarns; + } else if (message.type == 'error') { + ++newErrs; + } + }); + } + + 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 == 'tags') { + change.object.updateCardTags(); + } else if (change.name == 'isDirty') { + if (change.object[change.name]) { + document.getElementById('dirtyPluginNo').textContent = ++parseInt(document.getElementById('dirtyPluginNo').textContent, 10); + } else { + document.getElementById('dirtyPluginNo').textContent = --parseInt(document.getElementById('dirtyPluginNo').textContent, 10); + } } }); } diff --git a/resources/report/js/script.js b/resources/report/js/script.js index 5e9e25a4..dc5f563d 100644 --- a/resources/report/js/script.js +++ b/resources/report/js/script.js @@ -308,10 +308,22 @@ function redatePlugins(evt) { function clearAllMetadata(evt) { showMessageDialog('Clear All Metadata', 'Are you sure you want to clear all existing user-added metadata from all plugins?', function(result){ if (result) { - loot.query('clearAllMetadata').then(function(result){ - /* Need to also empty the UI-side user metadata. */ - loot.game.plugins.forEach(function(plugin){ - plugin.userlist = undefined; + loot.query('clearAllMetadata').then(JSON.parse).then(function(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].modPriority = plugin.modPriority; + loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; + + break; + } + } }); }).catch(processCefError); } @@ -707,9 +719,9 @@ function updateInterfaceWithGameInfo(response) { generalMessagesList.appendChild(li); if (li.className == 'warn') { - warnMessageNo++; + ++warnMessageNo; } else if (li.className == 'error') { - errorMessageNo++; + ++errorMessageNo; } } totalMessageNo = loot.game.globalMessages.length; @@ -729,11 +741,11 @@ function updateInterfaceWithGameInfo(response) { plugin.messages.forEach(function(message) { if (message.type == 'warn') { - warnMessageNo++; + ++warnMessageNo; } else if (message.type == 'error') { - errorMessageNo++; + ++errorMessageNo; } - totalMessageNo++; + ++totalMessageNo; }); } }); diff --git a/src/backend/metadata.cpp b/src/backend/metadata.cpp index a990ad67..1bad1066 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/metadata.cpp @@ -725,7 +725,7 @@ namespace loot { return false; } - void Plugin::CheckInstallValidity(const Game& game) { + bool Plugin::CheckInstallValidity(const Game& game) { unsigned int messageType; if (game.IsActive(name)) messageType = loot::Message::error; @@ -755,6 +755,37 @@ namespace loot { messages.push_back(loot::Message(messageType, (boost::format(boost::locale::translate("This plugin is incompatible with \"%1%\", but both are present.")) % inc.Name()).str())); } } + + // Also evaluate dirty info. + bool isDirty = false; + 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())); + isDirty = true; + } + + return isDirty; } bool Plugin::LoadsBSA(const Game& game) const { diff --git a/src/backend/metadata.h b/src/backend/metadata.h index 9633c40d..b897dcae 100644 --- a/src/backend/metadata.h +++ b/src/backend/metadata.h @@ -220,7 +220,7 @@ namespace loot { bool MustLoadAfter(const Plugin& plugin) const; //Checks masters, reqs and loadAfter. //Validity checks. - void CheckInstallValidity(const Game& game); //Checks that reqs and masters are all present, and that no incs are present. Returns a map of filenames and whether they are missing (if bool is true, then filename is a req or master, otherwise it's an inc). + bool CheckInstallValidity(const Game& game); //Checks that reqs and masters are all present, and that no incs are present. Returns true if the plugin is dirty. private: std::string name; bool enabled; //Default to true. diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index ea4a5a8c..ddd2d7a8 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -131,8 +131,57 @@ namespace loot { return true; } else if (request == "clearAllMetadata") { + // First regenerate the derived metadata (priority, messages, tags and dirty state) + // for any plugins with userlist entries, ignoring the user metadata. + + //Set language. + unsigned int language; + if (g_app_state.GetSettings()["language"]) + language = Language(g_app_state.GetSettings()["language"].as()).Code(); + else + language = Language::any; + + YAML::Node pluginsNode; + for (const auto &plugin : g_app_state.CurrentGame().userlist.plugins) { + YAML::Node temp; + + auto pluginIt = g_app_state.CurrentGame().plugins.find(plugin.Name()); + if (pluginIt != g_app_state.CurrentGame().plugins.end()) { + Plugin tempPlugin(pluginIt->second); + + tempPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(pluginIt->first)); + + //Evaluate any conditions + BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; + try { + tempPlugin.EvalAllConditions(g_app_state.CurrentGame(), language); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "\"" << tempPlugin.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%")) % tempPlugin.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."; + bool isDirty = tempPlugin.CheckInstallValidity(g_app_state.CurrentGame()); + + // Now add to pluginNode. + temp["name"] = tempPlugin.Name(); + temp["modPriority"] = modulo(tempPlugin.Priority(), max_priority); + temp["isGlobalPriority"] = (abs(tempPlugin.Priority()) >= max_priority); + temp["messages"] = tempPlugin.Messages(); + temp["tags"] = tempPlugin.Tags(); + temp["isDirty"] = isDirty; + } + + pluginsNode.push_back(temp); + } + // Now clear the user metadata. g_app_state.CurrentGame().userlist.clear(); - callback->Success(""); + if (pluginsNode.size() > 0) + callback->Success(JSON::stringify(pluginsNode)); + else + callback->Success("[]"); return true; } else { @@ -270,13 +319,50 @@ namespace loot { const string pluginName = req["args"][0].as(); BOOST_LOG_TRIVIAL(debug) << "Clearing user metadata for plugin " << pluginName; - auto pluginIt = find(g_app_state.CurrentGame().userlist.plugins.begin(), g_app_state.CurrentGame().userlist.plugins.end(), Plugin(pluginName)); + auto ulistPluginIt = find(g_app_state.CurrentGame().userlist.plugins.begin(), g_app_state.CurrentGame().userlist.plugins.end(), Plugin(pluginName)); - if (pluginIt != g_app_state.CurrentGame().userlist.plugins.end()) { - g_app_state.CurrentGame().userlist.plugins.erase(pluginIt); + if (ulistPluginIt != g_app_state.CurrentGame().userlist.plugins.end()) { + g_app_state.CurrentGame().userlist.plugins.erase(ulistPluginIt); } - callback->Success(""); + //Set language. + unsigned int language; + if (g_app_state.GetSettings()["language"]) + language = Language(g_app_state.GetSettings()["language"].as()).Code(); + else + language = Language::any; + + // Now rederive the displayed metadata from the masterlist. + YAML::Node pluginNode; + auto pluginIt = g_app_state.CurrentGame().plugins.find(pluginName); + if (pluginIt != g_app_state.CurrentGame().plugins.end()) { + Plugin tempPlugin(pluginIt->second); + + tempPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(pluginIt->first)); + + //Evaluate any conditions + BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; + try { + tempPlugin.EvalAllConditions(g_app_state.CurrentGame(), language); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "\"" << tempPlugin.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%")) % tempPlugin.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."; + bool isDirty = tempPlugin.CheckInstallValidity(g_app_state.CurrentGame()); + + // Now add to pluginNode. + pluginNode["modPriority"] = modulo(tempPlugin.Priority(), max_priority); + pluginNode["isGlobalPriority"] = (abs(tempPlugin.Priority()) >= max_priority); + pluginNode["messages"] = tempPlugin.Messages(); + pluginNode["tags"] = tempPlugin.Tags(); + pluginNode["isDirty"] = isDirty; + } + + callback->Success(JSON::stringify(pluginNode)); return true; } } @@ -464,49 +550,14 @@ namespace loot { //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; - } + bool isDirty = mlistPlugin.CheckInstallValidity(g_app_state.CurrentGame()); // Now add to pluginNode. pluginNode["modPriority"] = modulo(mlistPlugin.Priority(), max_priority); pluginNode["isGlobalPriority"] = (abs(mlistPlugin.Priority()) >= max_priority); - pluginNode["messages"] = messages; + pluginNode["messages"] = mlistPlugin.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(); - + pluginNode["isDirty"] = isDirty; gameNode["plugins"].push_back(pluginNode); }