From 59695e7996ec500661d788565bd9a8a6e674f70b Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Sat, 23 Aug 2014 16:44:13 +0100 Subject: [PATCH] New conflicts filter UI (#224). The option is now stored in each plugin's menu. Activating it for one plugin prevents it being activated for others until it has been deactivated again. Also improved the filtering so that a plugin with no conflicts will cause all others to be hidden instead of no others. The current filter target is also highlighted in the plugin list. Finally, some minor attribute modification and semantics changes. --- resources/report/css/style.css | 11 ++- resources/report/js/custom.js | 166 ++++++++++++++++++++++----------- resources/report/js/plugin.js | 2 +- resources/report/js/script.js | 98 +++++++++---------- resources/report/report.html | 25 +++-- src/gui/handler.cpp | 6 +- 6 files changed, 186 insertions(+), 122 deletions(-) diff --git a/resources/report/css/style.css b/resources/report/css/style.css index 8271f0b8..d922158a 100644 --- a/resources/report/css/style.css +++ b/resources/report/css/style.css @@ -159,7 +159,7 @@ nav a { padding: 0.3em 0.6em; width: 100%; } -#main { +main { overflow: auto; flex-shrink: 10; flex-grow: 1; @@ -388,9 +388,6 @@ body.editMode header #headerOverlay { top: 0; left: 0; } -#conflictsPlugin { - margin-left: 2.5em; -} [draggable] { -webkit-user-select: none; } @@ -398,6 +395,12 @@ body.editMode header #headerOverlay { background: #69aaff; color: #f8f8f8; } +plugin-card.highlight { + box-shadow: inset 0 0 10px 3px #69aaff; + background: white; + color: inherit; + outline: 3px solid #69aaff; +} input:invalid { background-color: pink; color: white; diff --git a/resources/report/js/custom.js b/resources/report/js/custom.js index 9c9938f2..54cd0b76 100644 --- a/resources/report/js/custom.js +++ b/resources/report/js/custom.js @@ -26,56 +26,87 @@ /* Create a element type. */ var pluginMenuProto = Object.create(HTMLElement.prototype, { - onMenuItemClick: { + getPluginCard: { + value: function() { + return document.getElementById(this.getAttribute('data-for')); + } + }, + + onShowOnlyConflicts: { value: function(evt) { + /* Depending on what was clicked, this function may be run before + the checkbox state has updated. Handle both cases. */ + var activateFilter; + if (evt.currentTarget == evt.target) { + /* Clicked on the label, checkbox state isn't updated yet. */ + activateFilter = !evt.target.firstElementChild.checked; + } else { + /* Clicked on the checkbox. */ + activateFilter = evt.target.checked; + } + if (activateFilter) { + document.body.setAttribute('data-conflicts', evt.currentTarget.parentNode.host.getPluginCard().getName()); + evt.currentTarget.parentNode.host.getPluginCard().classList.toggle('highlight', true); + } else { + evt.currentTarget.parentNode.host.getPluginCard().classList.toggle('highlight', false); + document.body.removeAttribute('data-conflicts'); + } + togglePlugins(evt); + } + }, - var pluginID = evt.target.parentNode.host.getAttribute('data-for'); - var pluginCard = document.getElementById(pluginID); + onEditMetadata: { + value: function(evt) { + evt.target.parentNode.host.getPluginCard().showEditor(); + } + }, - if (evt.target.id == 'editMetadata') { - /* Show editing controls. */ - pluginCard.showEditor(); + onCopyMetadata: { + value: function(evt) { + var request = JSON.stringify({ + name: 'copyMetadata', + args: [ + evt.target.parentNode.host.getPluginCard().getName() + ] + }); - } else if (evt.target.id == 'copyMetadata') { - var request = JSON.stringify({ - name: 'copyMetadata', - args: [ - pluginCard.getElementsByTagName('h1')[0].textContent - ] - }); + loot.query(request).catch(processCefError); + } + }, - loot.query(request).catch(processCefError); - } else if (evt.target.id == 'clearMetadata') { - showMessageDialog('Clear Plugin Metadata', 'Are you sure you want to clear all existing user-added metadata from "' + pluginCard.getElementsByTagName('h1')[0].textContent + '"?', function(result){ - if (result) { - var request = JSON.stringify({ - name: 'clearPluginMetadata', - args: [ - pluginCard.getElementsByTagName('h1')[0].textContent - ] - }); + onClearMetadata: { + value: function(evt) { + var pluginCard = evt.target.parentNode.host.getPluginCard(); - loot.query(request).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 == pluginID) { - loot.game.plugins[i].userlist = undefined; + showMessageDialog('Clear Plugin Metadata', 'Are you sure you want to clear all existing user-added metadata from "' + pluginCard.getName() + '"?', function(result){ + if (result) { + var request = JSON.stringify({ + name: 'clearPluginMetadata', + args: [ + pluginCard.getName() + ] + }); - 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; + loot.query(request).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 == pluginCard.id) { + loot.game.plugins[i].userlist = undefined; - break; - } + 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; } } - }).catch(processCefError); - } - }); - } + } + }).catch(processCefError); + } + }); } }, @@ -92,19 +123,40 @@ var pluginMenuProto = Object.create(HTMLElement.prototype, { attachedCallback: { value: function() { /* Add event listeners for the menu items. */ - this.shadowRoot.getElementById('editMetadata').addEventListener('click', this.onMenuItemClick, false); - this.shadowRoot.getElementById('copyMetadata').addEventListener('click', this.onMenuItemClick, false); - this.shadowRoot.getElementById('clearMetadata').addEventListener('click', this.onMenuItemClick, false); + var conflictsPlugin = document.body.getAttribute('data-conflicts'); + if (conflictsPlugin && conflictsPlugin != this.getPluginCard().getName()) { + /* The conflict filter is currently active for another plugin. + Prevent the filter being activated for this one. */ + this.shadowRoot.getElementById('showOnlyConflicts').disabled = true; + this.shadowRoot.getElementById('showOnlyConflicts').parentElement.classList.toggle('disabled', true); + } else { + /* The conflicts filter is either inactive or active for this + plugin. Allow it to be activated or deactivated. For some + reason clicking on the label is processed slower than + the checkbox state, and in the time difference the menu + gets closed, so that the conflicts filter never gets applied. + To get around this, listen for a click on the label rather + than for checkbox state change. */ + if (conflictsPlugin == this.getPluginCard().getName()) { + this.shadowRoot.getElementById('showOnlyConflicts').checked = true; + } + this.shadowRoot.getElementById('showOnlyConflicts').parentElement.addEventListener('click', this.onShowOnlyConflicts, false); + } + this.shadowRoot.getElementById('editMetadata').addEventListener('click', this.onEditMetadata, false); + this.shadowRoot.getElementById('copyMetadata').addEventListener('click', this.onCopyMetadata, false); + this.shadowRoot.getElementById('clearMetadata').addEventListener('click', this.onClearMetadata, false); } }, detachedCallback: { value: function() { - /* Remove event listeners for the menu items. */ - this.shadowRoot.getElementById('editMetadata').removeEventListener('click', this.onMenuItemClick, false); - this.shadowRoot.getElementById('copyMetadata').removeEventListener('click', this.onMenuItemClick, false); - this.shadowRoot.getElementById('clearMetadata').removeEventListener('click', this.onMenuItemClick, false); + /* Nothing happens if we try to remove a listener that doesn't exist, + so don't bother checking first. */ + this.shadowRoot.getElementById('showOnlyConflicts').parentElement.removeEventListener('click', this.onShowOnlyConflicts, false); + this.shadowRoot.getElementById('editMetadata').removeEventListener('click', this.onEditMetadata, false); + this.shadowRoot.getElementById('copyMetadata').removeEventListener('click', this.onCopyMetadata, false); + this.shadowRoot.getElementById('clearMetadata').removeEventListener('click', this.onClearMetadata, false); } } @@ -116,6 +168,12 @@ var PluginMenu = document.registerElement('plugin-menu', {prototype: pluginMenuP /* Create a element type. */ var pluginCardProto = Object.create(HTMLElement.prototype, { + getName: { + value: function() { + return this.getElementsByTagName('h1')[0].textContent; + } + }, + showEditorTable: { value: function(evt) { var tableId = evt.target.getAttribute('data-for'); @@ -427,8 +485,6 @@ var pluginCardProto = Object.create(HTMLElement.prototype, { } } - - break; } } @@ -448,7 +504,7 @@ var pluginCardProto = Object.create(HTMLElement.prototype, { /* Set up drag 'n' drop event handlers. */ elements = document.getElementById('pluginsNav').children; for (var i = 0; i < elements.length; ++i) { - elements[i].setAttribute('draggable', true); + elements[i].draggable = true; elements[i].addEventListener('dragstart', handlePluginDragStart, false); } elements = this.shadowRoot.getElementsByTagName('table'); @@ -487,7 +543,7 @@ var pluginCardProto = Object.create(HTMLElement.prototype, { menu.setAttribute('data-for', card.id); - var main = document.getElementById('main'); + var main = document.getElementsByTagName('main')[0]; main.appendChild(menu); /* Set page position of menu. */ @@ -774,12 +830,12 @@ var EditableTableProto = Object.create(HTMLTableElement.prototype, { if (classMask) { for (var j = 0; j < classMask.length; ++j) { if (inputs[i].classList.contains(classMask[j])) { - inputs[i].setAttribute('readonly', readOnly); + inputs[i].readOnly = readOnly; break; } } } else { - inputs[i].setAttribute('readonly', readOnly); + inputs[i].readOnly = readOnly; } } @@ -788,12 +844,12 @@ var EditableTableProto = Object.create(HTMLTableElement.prototype, { if (classMask) { for (var j = 0; j < classMask.length; ++j) { if (selects[i].classList.contains(classMask[j])) { - selects[i].setAttribute('disabled', readOnly); + selects[i].disabled = readOnly; break; } } } else { - selects[i].setAttribute('disabled', readOnly); + selects[i].disabled = readOnly; } } } diff --git a/resources/report/js/plugin.js b/resources/report/js/plugin.js index f8f9b8ac..740e702d 100644 --- a/resources/report/js/plugin.js +++ b/resources/report/js/plugin.js @@ -157,7 +157,7 @@ function Plugin(obj) { /* Fill in messages. */ this.updateCardMessages(); - document.getElementById('main').appendChild(card); + document.getElementsByTagName('main')[0].appendChild(card); } Plugin.prototype.createListItem = function() { diff --git a/resources/report/js/script.js b/resources/report/js/script.js index bf3e95ce..4d1d837c 100644 --- a/resources/report/js/script.js +++ b/resources/report/js/script.js @@ -72,11 +72,8 @@ function applySavedFilters() { if (loot.settings.filters) { for (var key in loot.settings.filters) { var elem = document.getElementById(key); - if (elem.type == 'checkbox') { - //elem.checked = true; + if (elem) { elem.dispatchEvent(new MouseEvent('click')); - } else { - elem.value = loot.settings.filters[key]; } } } @@ -111,44 +108,45 @@ function toggleDisplayCSS(evt) { } function getConflictingPluginsFromFilter() { - if (document.getElementById('showOnlyConflicts').checked) { - var conflictsPlugin = document.getElementById('conflictsPlugin'); - if (conflictsPlugin.value.length != 0) { + var conflictsPlugin = document.body.getAttribute('data-conflicts'); + if (conflictsPlugin) { + /* Now get conflicts for the plugin. */ + var request = JSON.stringify({ + name: 'getConflictingPlugins', + args: [ + conflictsPlugin + ] + }); - var request = JSON.stringify({ - name: 'getConflictingPlugins', - args: [ - conflictsPlugin.value - ] - }); - - return loot.query(request).then(JSON.parse).then(function(result){ - if (result) { - for (var key in result.crcs) { - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == key) { - loot.game.plugins[i].crc = result.crcs[key]; - break; - } + return loot.query(request).then(JSON.parse).then(function(result){ + if (result) { + for (var key in result.crcs) { + for (var i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name == key) { + loot.game.plugins[i].crc = result.crcs[key]; + break; } } - if (result.conflicts) { - return result.conflicts; - } } - return []; - }).catch(processCefError); - } + if (result.conflicts) { + return result.conflicts; + } else { + /* No conflicts. Filter everything but the plugin itself. */ + return [ conflictsPlugin ]; + } + } + return []; + }).catch(processCefError); } return Promise.resolve([]); } function togglePlugins(evt) { - var sections = document.getElementById('main').children; + var cards = document.getElementsByTagName('main')[0].getElementsByTagName('plugin-card'); var entries = document.getElementById('pluginsNav').children; var hiddenPluginNo = 0; var hiddenMessageNo = 0; - if (sections.length - 2 != entries.length) { + if (cards.length != entries.length) { throw Error("Error: Number of plugins in sidebar doesn't match number of plugins in main area!"); } /* The conflict filter, if enabled, executes C++ code, so needs to be @@ -156,16 +154,15 @@ function togglePlugins(evt) { it is completed. */ getConflictingPluginsFromFilter().then(function(conflicts) { - /* Start at 3rd section to skip summary and general messages. */ - for (var i = 2; i < sections.length; ++i) { + for (var i = 0; i < cards.length; ++i) { var isConflictingPlugin = false; var isMessageless = true; var hasInactivePluginMessages = false; - var messages = sections[i].getElementsByTagName('ul')[0].getElementsByTagName('li'); - if (sections[i].getAttribute('data-active') == 'false') { + var messages = cards[i].getElementsByTagName('ul')[0].getElementsByTagName('li'); + if (cards[i].getAttribute('data-active') == 'false') { hasInactivePluginMessages = true; } - if (conflicts.indexOf(sections[i].getElementsByTagName('h1')[0].textContent) != -1) { + if (conflicts.indexOf(cards[i].getName()) != -1) { isConflictingPlugin = true; } for (var j = 0; j < messages.length; ++j) { @@ -197,11 +194,11 @@ function togglePlugins(evt) { } if ((document.getElementById('hideMessagelessPlugins').checked && isMessageless) || conflicts.length > 0 && !isConflictingPlugin) { - hideElement(sections[i]); + hideElement(cards[i]); hideElement(entries[i - 2]); ++hiddenPluginNo; } else { - showElement(sections[i]); + showElement(cards[i]); showElement(entries[i - 2]); } } @@ -303,17 +300,19 @@ function changeGame(evt) { var elements = document.getElementById('filters').getElementsByTagName('input'); var activeFilters = []; for (var i = 0; i < elements.length; ++i) { - if (elements[i].type == 'checkbox' && elements[i].id != 'showOnlyConflicts' - && elements[i].checked) { + if (elements[i].type == 'checkbox' && elements[i].checked) { activeFilters.push(elements[i]); elements[i].click(); } } - if (document.getElementById('showOnlyConflicts').checked) { - document.getElementById('showOnlyConflicts').click(); + /* Need to do something slightly different for the conflicts filter. + Edit its state and run the filter function manually. */ + if (document.body.hasAttribute('data-conflicts')) { + document.body.removeAttribute('data-conflicts'); + /* Don't need to supply an event arg because togglePlugins doesn't + actually use it. */ + togglePlugins(); } - document.getElementById('conflictsPlugin').value = ''; - /* Clear the UI of all existing game-specific data. Also clear the card and li variables for each plugin object. */ @@ -418,7 +417,7 @@ function updateMasterlist(evt) { function sortUIElements(pluginNames) { /* pluginNames is an array of plugin names in their sorted order. Rearrange the plugin cards and nav entries to match it. */ - var main = document.getElementById('main'); + var main = document.getElementsByTagName('main')[0]; var pluginsNav = document.getElementById('pluginsNav'); var entries = pluginsNav.children; if (main.children.length - 2 != entries.length) { @@ -462,7 +461,7 @@ function sortPlugins(evt) { /* Record the previous order in case the user cancels sorting. */ /* Start at 2 to skip summary and general messages. */ - var cards = document.getElementById('main').children; + var cards = document.getElementsByTagName('main')[0].children; loot.newLoadOrder = result.loadOrder; loot.lastLoadOrder = []; for (var i = 2; i < cards.length; ++i) { @@ -800,11 +799,7 @@ function setupEventHandlers() { /*Set up filter value and CSS setting storage read/write handlers.*/ elements = document.getElementById('filters').getElementsByTagName('input'); for (var i = 0; i < elements.length; ++i) { - if (elements[i].type == 'text') { - elements[i].addEventListener('input', saveFilterState, false); - } else { - elements[i].addEventListener('click', saveFilterState, false); - } + elements[i].addEventListener('click', saveFilterState, false); } /*Set up handlers for filters.*/ @@ -816,7 +811,6 @@ function setupEventHandlers() { document.getElementById('hideInactivePluginMessages').addEventListener('click', togglePlugins, false); document.getElementById('hideAllPluginMessages').addEventListener('click', togglePlugins, false); document.getElementById('hideMessagelessPlugins').addEventListener('click', togglePlugins, false); - document.getElementById('showOnlyConflicts').addEventListener('click', togglePlugins, false); /* Set up handlers for buttons. */ document.getElementById('fileMenu').addEventListener('click', openMenu, false); @@ -981,7 +975,7 @@ function updateInterfaceWithGameInfo(response) { } }); totalMessageNo = loot.game.globalMessages.length; - var pluginsList = document.getElementById('main'); + var pluginsList = document.getElementsByTagName('main')[0]; var pluginsNav = document.getElementById('pluginsNav'); loot.game.plugins.forEach(function(plugin) { diff --git a/resources/report/report.html b/resources/report/report.html index 8578e1be..b5600d2d 100644 --- a/resources/report/report.html +++ b/resources/report/report.html @@ -326,17 +326,29 @@ text-align: left; padding:0; } - div { + div, label { padding: 0.2em 0.5em; cursor: pointer; + display: block; } - div:hover { + div:hover, label:hover { background: #e3e3e3; } span { padding-right: 0.4em; } + input { + position: relative; + top: 0.15em; + margin: 0em 0.3em 0 0.1em; + } + .disabled { + cursor: default; + color: #999; + } + +
Edit Metadata
Copy Metadata As Text
Clear User Metadata
@@ -452,10 +464,7 @@
-
- - - +
Hidden plugins: 0 /
@@ -467,7 +476,7 @@
  • -
    +

    Summary

    @@ -489,7 +498,7 @@ - + diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index 98d266f5..d5da4f90 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -271,6 +271,10 @@ namespace loot { else if (requestName == "closeSettings") { BOOST_LOG_TRIVIAL(trace) << "Settings dialog closed and changes accepted, updating settings object."; g_app_state.UpdateSettings(request["args"][0]); + + // Also update the game details. + g_app_state. + callback->Success(""); return true; } @@ -321,13 +325,11 @@ namespace loot { if (g_app_state.CurrentGame().plugins.begin()->second.FormIDs().size() == 0) g_app_state.CurrentGame().LoadPlugins(false); - map conflictingPlugins; YAML::Node node; for (const auto& pluginPair : g_app_state.CurrentGame().plugins) { if (pluginIt != g_app_state.CurrentGame().plugins.end()) { if (pluginIt->second.DoFormIDsOverlap(pluginPair.second)) { BOOST_LOG_TRIVIAL(debug) << "Found conflicting plugin: " << pluginPair.second.Name(); - conflictingPlugins.emplace(pluginPair.second.Name(), pluginPair.second.Crc()); node["conflicts"].push_back(pluginPair.second.Name()); } }