Improved metadata change handling.

* The clear metadata functions now rederive the derived metadata, and
send the new derivations to the JS side.
* The JS side now observes changes to the derived metadata and updates
the UI where necessary to reflect changes.
* Dirty message generation has been refactored into
Plugin::CheckInstallValidity, which now returns true if the plugin is
dirty.

The editor panel is still not yet updated though.
This commit is contained in:
WrinklyNinja
2014-08-12 17:36:36 +01:00
parent 9579440ad4
commit 2fbb487b15
6 changed files with 244 additions and 85 deletions
+9 -2
View File
@@ -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;
}
}
+87 -29
View File
@@ -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);
}
}
});
}
+21 -9
View File
@@ -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;
});
}
});
+32 -1
View File
@@ -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 {
+1 -1
View File
@@ -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.
+94 -43
View File
@@ -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<string>()).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<string>();
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<string>()).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<Message> messages = mlistPlugin.Messages();
std::set<PluginDirtyInfo> 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);
}