diff --git a/resources/report/html/editable-table.html b/resources/report/html/editable-table.html index 11e453fb..40517fc7 100644 --- a/resources/report/html/editable-table.html +++ b/resources/report/html/editable-table.html @@ -148,7 +148,7 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { /* Create a element type that extends from . */ var EditableTableProto = Object.create(HTMLTableElement.prototype, { - handleDrop: { + onDrop: { value: function(evt) { evt.stopPropagation(); @@ -163,7 +163,7 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { } }, - handleDragOver: { + onDragOver: { value: function(evt) { evt.preventDefault(); evt.dataTransfer.dropEffect = 'copy'; @@ -234,21 +234,21 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { } }, - removeRow: { + onRemoveRow: { value: function(evt) { var tr = evt.target.parentElement.parentElement.parentElement; var tbody = tr.parentElement; var table = tbody.parentElement; /* Remove deletion listener. */ - evt.target.removeEventListener('click', table.removeRow, false); + evt.target.removeEventListener('click', table.onRemoveRow, false); /* Now remove row. */ tbody.removeChild(tr); } }, - addEmptyRow: { + onAddEmptyRow: { value: function(evt) { /* Create new row. */ var table = evt.currentTarget.parentElement.parentElement.parentElement.parentElement.parentElement; @@ -264,7 +264,7 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { table.tBodies[0].insertBefore(row, table.tBodies[0].lastElementChild); /* Add deletion listener. */ - table.tBodies[0].lastElementChild.previousElementSibling.getElementsByClassName('delete')[0].addEventListener('click', table.removeRow, false); + table.tBodies[0].lastElementChild.previousElementSibling.getElementsByClassName('delete')[0].addEventListener('click', table.onRemoveRow, false); } }, @@ -292,7 +292,7 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { } /* Add deletion listener. */ - row.getElementsByClassName('delete')[0].addEventListener('click', this.removeRow, false); + row.getElementsByClassName('delete')[0].addEventListener('click', this.onRemoveRow, false); return row; } @@ -312,14 +312,14 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { row = this.tBodies[0].lastElementChild; /* Add new row listener. */ - row.querySelector('paper-icon-button').addEventListener('click', this.addEmptyRow, false); + row.querySelector('paper-icon-button').addEventListener('click', this.onAddEmptyRow, false); /* Add drag 'n' drop listeners. Drag 'n' drop should only be enabled for file-row tables. */ if (this.getAttribute('data-template') == 'fileRow') { - this.addEventListener('drop', this.handleDrop, false); - this.addEventListener('dragover', this.handleDragOver, false); + this.addEventListener('drop', this.onDrop, false); + this.addEventListener('dragover', this.onDragOver, false); } } }, @@ -329,15 +329,15 @@ html /deep/ table[is=editable-table] paper-icon-button[icon=add]:hover { /* Remove deletion listener. */ var icons = this.tBodies[0].getElementsByClassName('delete'); for (var i = 0; i < icons.length; ++i) { - icons[i].removeEventListener('click', this.removeRow, false); + icons[i].removeEventListener('click', this.onRemoveRow, false); } /* Remove new row listener. */ - this.querySelector('tbody tr:last-child paper-icon-button').removeEventListener('click', this.addEmptyRow, false); + this.querySelector('tbody tr:last-child paper-icon-button').removeEventListener('click', this.onAddEmptyRow, false); /* Remove drag 'n' drop listeners. */ - this.removeEventListener('drop', this.handleDrop, false); - this.removeEventListener('dragover', this.handleDragOver, false); + this.removeEventListener('drop', this.onDrop, false); + this.removeEventListener('dragover', this.onDragOver, false); } }, diff --git a/resources/report/html/loot-plugin-item.html b/resources/report/html/loot-plugin-item.html index c185f1bb..04ae8f95 100644 --- a/resources/report/html/loot-plugin-item.html +++ b/resources/report/html/loot-plugin-item.html @@ -149,7 +149,7 @@ return this.textContent.trim(); }, - handleDragStart: function(evt) { + onDragStart: function(evt) { evt.dataTransfer.effectAllowed = 'copy'; evt.dataTransfer.setData('text/plain', evt.currentTarget.getName()); }, diff --git a/resources/report/js/events.js b/resources/report/js/events.js new file mode 100644 index 00000000..f958aad8 --- /dev/null +++ b/resources/report/js/events.js @@ -0,0 +1,674 @@ +function onToggleDisplayCSS(evt) { + var attr = 'data-hide-' + evt.target.getAttribute('data-class'); + if (evt.target.checked) { + document.getElementById('main').setAttribute(attr, true); + } else { + document.getElementById('main').removeAttribute(attr); + } +} +function onOpenLogLocation(evt) { + loot.query('openLogLocation').catch(processCefError); +} +function onChangeGame(evt) { + /* Check that the selected game isn't the current one. */ + if (evt.target.className.indexOf('core-selected') != -1) { + return; + } + + /* Send off a CEF query with the folder name of the new game. */ + showProgress(l10n.jed.translate('Loading game data...').fetch()); + var request = JSON.stringify({ + name: 'changeGame', + args: [ + evt.currentTarget.getAttribute('value') + ] + }); + loot.query(request).then(function(result){ + /* Filters should be re-applied on game change, except the conflicts + filter. Don't need to deactivate the others beforehand. Strictly not + deactivating the conflicts filter either, just resetting it's value. + */ + document.body.removeAttribute('data-conflicts'); + + /* Clear the UI of all existing game-specific data. Also + clear the card and li variables for each plugin object. */ + var globalMessages = document.getElementById('summary').getElementsByTagName('ul')[0]; + while (globalMessages.firstElementChild) { + globalMessages.removeChild(globalMessages.firstElementChild); + } + + /* Parse the data sent from C++. */ + try { + var gameInfo = JSON.parse(result, jsonToPlugin); + loot.game.folder = gameInfo.folder; + loot.game.masterlist = gameInfo.masterlist; + loot.game.globalMessages = gameInfo.globalMessages; + loot.game.plugins = gameInfo.plugins; + + /* Reset virtual list positions. */ + document.getElementById('cardsNav').scrollToItem(0); + document.getElementById('main').lastElementChild.scrollToItem(0); + + /* Now update virtual lists. */ + setFilteredUIData(); + } catch (e) { + console.log(e); + console.log('changeGame response: ' + result); + } + + closeProgressDialog(); + }).catch(processCefError); +} +function onOpenReadme(evt) { + loot.query('openReadme').catch(processCefError); +} +/* Masterlist update process, minus progress dialog. */ +function updateMasterlistNoProgress() { + return loot.query('updateMasterlist').then(JSON.parse).then(function(result){ + if (result) { + /* Update JS variables. */ + loot.game.masterlist = result.masterlist; + loot.game.globalMessages = result.globalMessages; + + result.plugins.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].isDirty = plugin.isDirty; + loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; + loot.game.plugins[i].masterlist = plugin.masterlist; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].modPriority = plugin.modPriority; + loot.game.plugins[i].tags = plugin.tags; + break; + } + } + }); + /* Hack to stop cards overlapping. */ + document.getElementById('main').lastElementChild.updateSize(); + + toast(l10n.jed.translate('Masterlist updated to revision %s.').fetch(loot.game.masterlist.revision)); + } else { + toast(l10n.jed.translate('No masterlist update was necessary.').fetch()); + } + }).catch(processCefError); +} +function onUpdateMasterlist(evt) { + showProgress(l10n.jed.translate('Updating masterlist...').fetch()); + updateMasterlistNoProgress().then(function(result){ + closeProgressDialog(); + }).catch(processCefError); +} +function onSortPlugins(evt) { + var mlistUpdate; + if (loot.settings.updateMasterlist) { + mlistUpdate = updateMasterlistNoProgress(); + } else { + mlistUpdate = new Promise(function(resolve, reject){ + resolve(''); + }); + } + mlistUpdate.then(function(){ + showProgress(l10n.jed.translate('Sorting plugins...').fetch()); + loot.query('sortPlugins').then(JSON.parse).then(function(result){ + if (result) { + loot.game.oldLoadOrder = loot.game.plugins; + loot.game.loadOrder = []; + result.forEach(function(plugin){ + var found = false; + for (var i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name == plugin.name) { + loot.game.plugins[i].crc = plugin.crc; + loot.game.plugins[i].isEmpty = plugin.isEmpty; + + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; + + loot.game.loadOrder.push(loot.game.plugins[i]); + + found = true; + break; + } + } + if (!found) { + loot.game.plugins[i].push(new Plugin(plugin)); + loot.game.loadOrder.push(loot.game.plugins[i]); + } + }); + + if (loot.settings.neverTellMeTheOdds) { + /* Array shuffler from */ + for(var j, x, i = loot.game.loadOrder.length; i; j = Math.floor(Math.random() * i), x = loadOrder[--i], loot.game.loadOrder[i] = loot.game.loadOrder[j], loot.game.loadOrder[j] = x); + } + + /* Now update the UI for the new order. */ + loot.game.plugins = loot.game.loadOrder; + setFilteredUIData(); + + /* Now hide the masterlist update buttons, and display the accept and + cancel sort buttons. */ + hideElement(document.getElementById('updateMasterlistButton')); + hideElement(document.getElementById('sortButton')); + showElement(document.getElementById('applySortButton')); + showElement(document.getElementById('cancelSortButton')); + + /* Disable changing game. */ + document.getElementById('gameMenu').setAttribute('disabled', ''); + closeProgressDialog(); + } + }).catch(processCefError); + }).catch(processCefError); +} +function onApplySort(evt) { + var loadOrder = []; + loot.game.plugins.forEach(function(plugin){ + loadOrder.push(plugin.name); + }); + var request = JSON.stringify({ + name: 'applySort', + args: [ + loadOrder + ] + }); + return loot.query(request).then(function(result){ + /* Remove old load order storage. */ + delete loot.game.loadOrder; + delete loot.game.oldLoadOrder; + + /* Now show the masterlist update buttons, and hide the accept and + cancel sort buttons. */ + showElement(document.getElementById('updateMasterlistButton')); + showElement(document.getElementById('sortButton')); + hideElement(document.getElementById('applySortButton')); + hideElement(document.getElementById('cancelSortButton')); + + /* Enable changing game. */ + document.getElementById('gameMenu').removeAttribute('disabled'); + }).catch(processCefError); +} +function onCancelSort(evt) { + return loot.query('cancelSort').then(function(){ + /* Sort UI elements again according to stored old load order. */ + loot.game.plugins = loot.game.oldLoadOrder; + setFilteredUIData(); + delete loot.game.loadOrder; + delete loot.game.oldLoadOrder; + + /* Now show the masterlist update buttons, and hide the accept and + cancel sort buttons. */ + showElement(document.getElementById('updateMasterlistButton')); + showElement(document.getElementById('sortButton')); + hideElement(document.getElementById('applySortButton')); + hideElement(document.getElementById('cancelSortButton')); + + /* Enable changing game. */ + document.getElementById('gameMenu').removeAttribute('disabled'); + }).catch(processCefError); +} +function onRedatePlugins(evt) { + if (evt.target.hasAttribute('disabled')) { + return; + } + + showMessageDialog(l10n.jed.translate('Redate Plugins?').fetch(), l10n.jed.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?').fetch(), l10n.jed.translate('Redate').fetch(), function(result){ + if (result) { + loot.query('redatePlugins').then(function(response){ + toast('Plugins were successfully redated.'); + }).catch(processCefError); + } + }); +} +function onClearAllMetadata(evt) { + showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from all plugins?').fetch(), l10n.jed.translate('Clear').fetch(), function(result){ + if (result) { + loot.query('clearAllMetadata').then(JSON.parse).then(function(result){ + if (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; + } + } + }); + + toast(l10n.jed.translate('All user-added metadata has been cleared.').fetch()); + } + }).catch(processCefError); + } + }); +} +function onCopyContent(evt) { + var messages = []; + var plugins = []; + + if (loot.game) { + if (loot.game.globalMessages) { + loot.game.globalMessages.forEach(function(message){ + var m = {}; + messages.push({ + type: message.type, + content: message.content[0].str + }); + }); + } + if (loot.game.plugins) { + loot.game.plugins.forEach(function(plugin){ + plugins.push({ + name: plugin.name, + crc: plugin.crc, + version: plugin.version, + isActive: plugin.isActive, + isEmpty: plugin.isEmpty, + loadsBSA: plugin.loadsBSA, + + modPriority: plugin.modPriority, + isGlobalPriority: plugin.isGlobalPriority, + messages: plugin.messages, + tags: plugin.tags, + isDirty: plugin.isDirty + }); + }); + } + } else { + var message = document.getElementById('summary').getElementsByTagName('ul')[0].firstElementChild; + if (message) { + messages.push({ + type: 'error', + content: message.textContent + }); + } + } + + var request = JSON.stringify({ + name: 'copyContent', + args: [{ + messages: messages, + plugins: plugins + }] + }); + + loot.query(request).then(function(){ + toast(l10n.jed.translate("LOOT's content has been copied to the clipboard.").fetch()); + }).catch(processCefError); +} +function onSwitchSidebarTab(evt) { + if (evt.detail.isSelected) { + document.getElementById(evt.target.selected).parentElement.selected = evt.target.selected; + } +} +function onShowAboutDialog(evt) { + document.getElementById('about').showModal(); +} +function areSettingsValid() { + /* Validate inputs individually. */ + var inputs = document.getElementById('settingsDialog').getElementsByTagName('input'); + for (var i = 0; i < inputs.length; ++i) { + if (!inputs[i].checkValidity()) { + return false; + console.log(inputs[i]); + } + } + return true; +} +function onCloseSettingsDialog(evt) { + if (evt.target.classList.contains('accept')) { + if (!areSettingsValid()) { + return; + } + + /* Update the JS variable values. */ + var settings = { + enableDebugLogging: document.getElementById('enableDebugLogging').checked, + game: document.getElementById('defaultGameSelect').value, + games: document.getElementById('gameTable').getRowsData(false), + language: document.getElementById('languageSelect').value, + lastGame: loot.settings.lastGame, + updateMasterlist: document.getElementById('updateMasterlist').checked, + filters: loot.settings.filters, + }; + + /* Send the settings back to the C++ side. */ + var request = JSON.stringify({ + name: 'closeSettings', + args: [ + settings + ] + }); + loot.query(request).then(function(result){ + + try { + loot.installedGames = JSON.parse(result); + } catch (e) { + console.log(e); + console.log('getInstalledGames response: ' + results[1]); + } + + loot.settings = settings; + }).catch(processCefError); + } else { + /* Re-apply the existing settings to the settings dialog elements. */ + loot.updateSettingsUI(); + } + evt.target.parentElement.close(); +} +function onShowSettingsDialog(evt) { + document.getElementById('settingsDialog').showModal(); +} +function onFocusSearch(evt) { + if (evt.ctrlKey && evt.keyCode == 70) { //'f' + document.getElementById('searchBox').focus(); + } +} +function onEditorOpen(evt) { + /* Set up drag 'n' drop event handlers. */ + var elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); + for (var i = 0; i < elements.length; ++i) { + elements[i].draggable = true; + elements[i].addEventListener('dragstart', elements[i].onDragStart, false); + } + + /* Now show editor. */ + evt.target.classList.toggle('flip'); + + /* Enable priority hover in plugins list and enable header + buttons if this is the only editor instance. */ + var numEditors = 0; + if (document.body.hasAttribute('data-editors')) { + numEditors = parseInt(document.body.getAttribute('data-editors'), 10); + } + ++numEditors; + + if (numEditors == 1) { + /* Set the edit mode toggle attribute. */ + document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); + /* Disable the toolbar elements. */ + document.getElementById('wipeUserlistButton').setAttribute('disabled', ''); + document.getElementById('copyContentButton').setAttribute('disabled', ''); + document.getElementById('refreshContentButton').setAttribute('disabled', ''); + document.getElementById('settingsButton').setAttribute('disabled', ''); + document.getElementById('gameMenu').setAttribute('disabled', ''); + document.getElementById('updateMasterlistButton').setAttribute('disabled', ''); + document.getElementById('sortButton').setAttribute('disabled', ''); + } + document.body.setAttribute('data-editors', numEditors); + document.getElementById('cardsNav').updateSize(); + + return loot.query('editorOpened').catch(processCefError); +} +function onEditorClose(evt) { + /* evt.detail is true if the apply button was pressed. */ + if (evt.detail) { + /* Need to record the editor control values and work out what's + changed, and update any UI elements necessary. Offload the + majority of the work to the C++ side of things. */ + + var edits = evt.target.readFromEditor(evt.target.data); + + var request = JSON.stringify({ + name: 'editorClosed', + args: [ + edits + ] + }); + loot.query(request).then(JSON.parse).then(function(result){ + if (result) { + evt.target.data.modPriority = result.modPriority; + evt.target.data.isGlobalPriority = result.isGlobalPriority; + evt.target.data.messages = result.messages; + evt.target.data.tags = result.tags; + evt.target.data.isDirty = result.isDirty; + + evt.target.data.userlist = edits.userlist; + delete evt.target.data.editor; + } + }).catch(processCefError); + } else { + /* Don't need to record changes, but still need to notify C++ side that + the editor has been closed. */ + loot.query('editorClosed').catch(processCefError); + } + + /* Now hide editor. */ + evt.target.classList.toggle('flip'); + + /* Remove drag 'n' drop event handlers. */ + var elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); + for (var i = 0; i < elements.length; ++i) { + elements[i].removeAttribute('draggable'); + elements[i].removeEventListener('dragstart', elements[i].onDragStart, false); + } + + /* Disable priority hover in plugins list and enable header + buttons if this is the only editor instance. */ + var numEditors = parseInt(document.body.getAttribute('data-editors'), 10); + --numEditors; + + if (numEditors == 0) { + document.body.removeAttribute('data-editors'); + /* Set the edit mode toggle attribute. */ + document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); + /* Re-enable toolbar elements. */ + document.getElementById('wipeUserlistButton').removeAttribute('disabled'); + document.getElementById('copyContentButton').removeAttribute('disabled'); + document.getElementById('refreshContentButton').removeAttribute('disabled'); + document.getElementById('settingsButton').removeAttribute('disabled'); + document.getElementById('gameMenu').removeAttribute('disabled'); + document.getElementById('updateMasterlistButton').removeAttribute('disabled'); + document.getElementById('sortButton').removeAttribute('disabled'); + } else { + document.body.setAttribute('data-editors', numEditors); + } + document.getElementById('cardsNav').updateSize(); +} +function onConflictsFilter(evt) { + /* evt.detail is true if the filter has been activated. */ + if (evt.detail) { + /* Un-highlight any existing filter plugin. */ + var cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); + for (var i = 0; i < cards.length; ++i) { + cards[i].classList.toggle('highlight', false); + if (cards[i] != evt.target) { + cards[i].deactivateConflictFilter(); + } + } + evt.target.classList.toggle('highlight', true); + document.body.setAttribute('data-conflicts', evt.target.getName()); + } else { + evt.target.classList.toggle('highlight', false); + document.body.removeAttribute('data-conflicts'); + } + setFilteredUIData(evt); +} +function onCopyMetadata(evt) { + /* evt.detail is the name of the plugin. */ + var request = JSON.stringify({ + name: 'copyMetadata', + args: [ + evt.target.getName(), + ] + }); + + loot.query(request).then(function(){ + toast(l10n.jed.translate('The metadata for "%s" has been copied to the clipboard.').fetch(evt.target.getName())); + }).catch(processCefError); +} +function onClearMetadata(evt) { + showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from "%s"?').fetch(evt.target.getName()), l10n.jed.translate('Clear').fetch(), function(result){ + if (result) { + var request = JSON.stringify({ + name: 'clearPluginMetadata', + args: [ + evt.target.getName() + ] + }); + + 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 == evt.target.id) { + 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; + } + } + toast(l10n.jed.translate('The user-added metadata for "%s" has been cleared.').fetch(evt.target.getName())); + } + }).catch(processCefError); + } + }); +} +function onSidebarClick(evt) { + if (evt.target.hasAttribute('data-index')) { + document.getElementById('main').lastElementChild.scrollToItem(evt.target.getAttribute('data-index')); + + if (evt.type == 'dblclick') { + var card = document.getElementById(evt.target.getAttribute('data-id')); + if (!card.classList.contains('flip')) { + document.getElementById(evt.target.getAttribute('data-id')).onShowEditor(); + } + } + } +} +function onQuit(evt) { + if (!document.getElementById('applySortButton').classList.contains('hidden')) { + handleUnappliedChangesClose(l10n.jed.translate('sorted load order').fetch()); + } else if (document.body.hasAttribute('data-editors')) { + handleUnappliedChangesClose(l10n.jed.translate('metadata edits').fetch()); + } else { + window.close(); + } +} +function onJumpToGeneralInfo(evt) { + window.location.hash = ''; + document.getElementById('main').scrollTop = 0; +} +function onContentRefresh(evt) { + /* Send a query for updated load order and plugin header info. */ + showProgress(l10n.jed.translate('Refreshing data...').fetch()); + loot.query('getGameData').then(function(result){ + /* Parse the data sent from C++. */ + try { + /* We don't want the plugin info creating cards, so don't convert + to plugin objects. */ + var gameInfo = JSON.parse(result); + } catch (e) { + console.log(e); + console.log('getGameData response: ' + result); + } + + /* Now overwrite plugin data with the newly sent data. Also update + card and li vars as they were unset when the game was switched + from before. */ + var pluginNames = []; + gameInfo.plugins.forEach(function(plugin){ + var foundPlugin = false; + for (var i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name == plugin.name) { + + loot.game.plugins[i].isActive = plugin.isActive; + loot.game.plugins[i].isEmpty = plugin.isEmpty; + loot.game.plugins[i].loadsBSA = plugin.loadsBSA; + loot.game.plugins[i].crc = plugin.crc; + loot.game.plugins[i].version = plugin.version; + + 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; + + foundPlugin = true; + break; + } + } + if (!foundPlugin) { + /* A new plugin. */ + loot.game.plugins.push(new Plugin(plugin)); + } + pluginNames.push(plugin.name); + }); + for (var i = 0; i < loot.game.plugins.length;) { + var foundPlugin = false; + for (var j = 0; j < pluginNames.length; ++j) { + if (loot.game.plugins[i].name == pluginNames[j]) { + foundPlugin = true; + break; + } + } + if (!foundPlugin) { + /* Remove plugin. */ + loot.game.plugins.splice(i, 1); + } else { + ++i; + } + } + + /* Reapply filters. */ + setFilteredUIData(); + + closeProgressDialog(); + }).catch(processCefError); +} +function setupEventHandlers() { + /*Set up handlers for filters.*/ + document.getElementById('hideVersionNumbers').addEventListener('change', onToggleDisplayCSS, false); + document.getElementById('hideCRCs').addEventListener('change', onToggleDisplayCSS, false); + document.getElementById('hideBashTags').addEventListener('change', onToggleDisplayCSS, false); + document.getElementById('hideNotes').addEventListener('change', setFilteredUIData, false); + document.getElementById('hideDoNotCleanMessages').addEventListener('change', setFilteredUIData, false); + document.getElementById('hideInactivePlugins').addEventListener('change', setFilteredUIData, false); + document.getElementById('hideAllPluginMessages').addEventListener('change', setFilteredUIData, false); + document.getElementById('hideMessagelessPlugins').addEventListener('change', setFilteredUIData, false); + document.body.addEventListener('loot-filter-conflicts', onConflictsFilter, false); + + /* Set up event handlers for content filter. */ + document.getElementById('searchBox').addEventListener('change', setFilteredUIData, false); + window.addEventListener('keyup', onFocusSearch, false); + + /* Set up handlers for buttons. */ + document.getElementById('redatePluginsButton').addEventListener('click', onRedatePlugins, false); + document.getElementById('openLogButton').addEventListener('click', onOpenLogLocation, false); + document.getElementById('wipeUserlistButton').addEventListener('click', onClearAllMetadata, false); + document.getElementById('copyContentButton').addEventListener('click', onCopyContent, false); + document.getElementById('refreshContentButton').addEventListener('click', onContentRefresh, false); + document.getElementById('settingsButton').addEventListener('click', onShowSettingsDialog, false); + document.getElementById('helpButton').addEventListener('click', onOpenReadme, false); + document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog, false); + document.getElementById('quitButton').addEventListener('click', onQuit, false); + document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist, false); + document.getElementById('sortButton').addEventListener('click', onSortPlugins, false); + document.getElementById('applySortButton').addEventListener('click', onApplySort, false); + document.getElementById('cancelSortButton').addEventListener('click', onCancelSort, false); + document.getElementById('sidebarTabs').addEventListener('core-select', onSwitchSidebarTab, false); + document.getElementById('jumpToGeneralInfo').addEventListener('click', onJumpToGeneralInfo, false); + + /* Set up event handlers for settings dialog. */ + var settings = document.getElementById('settingsDialog'); + settings.getElementsByClassName('accept')[0].addEventListener('click', onCloseSettingsDialog, false); + settings.getElementsByClassName('cancel')[0].addEventListener('click', onCloseSettingsDialog, false); + + /* Set up handler for opening and closing editors. */ + document.body.addEventListener('loot-editor-open', onEditorOpen, false); + document.body.addEventListener('loot-editor-close', onEditorClose, false); + document.body.addEventListener('loot-copy-metadata', onCopyMetadata, false); + document.body.addEventListener('loot-clear-metadata', onClearMetadata, false); + + document.getElementById('cardsNav').addEventListener('click', onSidebarClick, false); + document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick, false); +} \ No newline at end of file diff --git a/resources/report/js/filters.js b/resources/report/js/filters.js index f605f4ac..b2b1faf9 100644 --- a/resources/report/js/filters.js +++ b/resources/report/js/filters.js @@ -146,7 +146,52 @@ var filters = { }, }; -function setFilteredUIData(evt) { +function getConflictingPluginsFromFilter() { + var conflictsPlugin = document.body.getAttribute('data-conflicts'); + if (conflictsPlugin) { + /* Now get conflicts for the plugin. */ + var request = JSON.stringify({ + name: 'getConflictingPlugins', + args: [ + conflictsPlugin + ] + }); + + showProgress(l10n.jed.translate('Checking if plugins have been loaded...').fetch()); + + return loot.query(request).then(JSON.parse).then(function(result){ + if (result) { + /* Filter everything but the plugin itself if there are no + conflicts. */ + var conflicts = [ conflictsPlugin ]; + for (var key in result) { + if (result[key].conflicts) { + conflicts.push(key); + } + for (var i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name == key) { + loot.game.plugins[i].crc = result[key].crc; + loot.game.plugins[i].isEmpty = result[key].isEmpty; + + loot.game.plugins[i].messages = result[key].messages; + loot.game.plugins[i].tags = result[key].tags; + loot.game.plugins[i].isDirty = result[key].isDirty; + break; + } + } + } + closeProgressDialog(); + return conflicts; + } + closeProgressDialog(); + return [ conflictsPlugin ]; + }).catch(processCefError); + } + + return Promise.resolve([]); +} + +function setFilteredUIData() { /* The conflict filter, if enabled, executes C++ code, so needs to be handled using a promise, so the rest of the function should wait until it is completed. diff --git a/resources/report/js/helpers.js b/resources/report/js/helpers.js new file mode 100644 index 00000000..d5231983 --- /dev/null +++ b/resources/report/js/helpers.js @@ -0,0 +1,73 @@ +function processCefError(err) { + /* Error.stack seems to be Chromium-specific. It gives a lot more useful + info than just the error message. Also, this can be used to catch any + promise errors, not just CEF errors. */ + console.log(err.stack); + closeProgressDialog(); + showMessageBox(l10n.jed.translate('Error').fetch(), err.message); +} + +function showElement(element) { + if (element != null) { + element.classList.toggle('hidden', false); + } +} +function hideElement(element) { + if (element != null) { + element.classList.toggle('hidden', true); + } +} +function toast(text) { + var toast = document.getElementById('toast'); + toast.text = text; + toast.show(); +} +function showMessageDialog(title, text, positiveText, closeCallback) { + var dialog = document.createElement('loot-message-dialog'); + dialog.setButtonText(positiveText, l10n.jed.translate('Cancel').fetch()); + dialog.showModal(title, text, closeCallback); + document.body.appendChild(dialog); +} +function showMessageBox(title, text) { + var dialog = document.createElement('loot-message-dialog'); + dialog.setButtonText(l10n.jed.translate('OK').fetch()); + dialog.showModal(title, text); + document.body.appendChild(dialog); +} + +function showProgress(message) { + var progressDialog = document.getElementById('progressDialog'); + if (message) { + progressDialog.getElementsByTagName('p')[0].textContent = message; + } + if (!progressDialog.opened) { + progressDialog.showModal(); + } +} +function closeProgressDialog() { + var progressDialog = document.getElementById('progressDialog'); + if (progressDialog.opened) { + progressDialog.close(); + } +} +function handleUnappliedChangesClose(change) { + showMessageDialog('', l10n.jed.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?').fetch(change), l10n.jed.translate('Quit').fetch(), function(result){ + if (result) { + /* Cancel any sorting and close any editors. Cheat by sending a + cancelSort query for as many times as necessary. */ + var queries = []; + var numQueries = 0; + if (!document.getElementById('applySortButton').classList.contains('hidden')) { + numQueries += 1; + } + numQueries += document.body.getAttribute('data-editors'); + for (var i = 0; i < numQueries; ++i) { + queries.push(loot.query('cancelSort')); + } + Promise.all(queries).then(function(){ + window.close(); + }).catch(processCefError); + } + }); +} + diff --git a/resources/report/js/init.js b/resources/report/js/init.js new file mode 100644 index 00000000..3047bb65 --- /dev/null +++ b/resources/report/js/init.js @@ -0,0 +1,199 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2013-2015 WrinklyNinja + + This file is part of LOOT. + + LOOT is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + LOOT is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LOOT. If not, see + . +*/ +'use strict'; +var marked; +var l10n; + +function initVars() { + loot.query('getVersion').then(function(result){ + try { + loot.version = JSON.parse(result); + + /* The fourth part of the version string is the build number. Trim it. */ + var pos = loot.version.lastIndexOf('.'); + if (loot.version.length > pos + 1) { + document.getElementById('LOOTBuild').textContent = loot.version.substring(pos + 1); + } else { + document.getElementById('LOOTBuild').textContent = l10n.jed.translate('unknown').fetch(); + } + + loot.version = loot.version.substring(0, pos); + document.getElementById('LOOTVersion').textContent = loot.version; + document.getElementById('firstTimeLootVersion').textContent = loot.version; + } catch (e) { + console.log(e); + console.log('Response: ' + result); + } + }).catch(processCefError); + + loot.query('getLanguages').then(function(result){ + try { + loot.languages = JSON.parse(result); + + /* Now fill in language options. */ + var settingsLangSelect = document.getElementById('languageSelect'); + var messageLangSelect = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (messageLangSelect) { + messageLangSelect = messageLangSelect.import.querySelector('#messageRow').content.querySelector('.language'); + } else { + messageLangSelect = document.querySelector('#messageRow').content.querySelector('.language'); + } + + for (var i = 0; i < loot.languages.length; ++i) { + var settingsItem = document.createElement('paper-item'); + settingsItem.setAttribute('value', loot.languages[i].locale); + settingsItem.setAttribute('noink', ''); + settingsItem.textContent = loot.languages[i].name; + settingsLangSelect.appendChild(settingsItem); + messageLangSelect.appendChild(settingsItem.cloneNode(true)); + } + } catch (e) { + console.log(e); + console.log('Response: ' + result); + } + }).catch(processCefError); + + loot.query('getInitErrors').then(JSON.parse).then(function(result){ + if (result) { + var generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; + + result.forEach(function(message){ + var li = document.createElement('li'); + li.className = 'error'; + /* Use the Marked library for Markdown formatting support. */ + li.innerHTML = marked(message); + generalMessagesList.appendChild(li); + }); + + document.getElementById('filterTotalMessageNo').textContent = result.length; + document.getElementById('totalMessageNo').textContent = result.length; + document.getElementById('totalErrorNo').textContent = result.length; + } + + var parallelPromises = [ + loot.query('getGameTypes'), + loot.query('getInstalledGames'), + loot.query('getSettings'), + ]; + + showProgress('Initialising user interface...'); + Promise.all(parallelPromises).then(function(results) { + try { + loot.gameTypes = JSON.parse(results[0]); + } catch (e) { + console.log(e); + console.log('getGameTypes response: ' + results[0]); + } + + /* Fill in game row template's game type options. */ + var select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); + if (select) { + select = select.import.querySelector('#gameRow').content.querySelector('.type') + } else { + select = document.querySelector('#gameRow').content.querySelector('.type'); + } + for (var j = 0; j < loot.gameTypes.length; ++j) { + var item = document.createElement('paper-item'); + item.setAttribute('value', loot.gameTypes[j]); + item.setAttribute('noink', ''); + item.textContent = loot.gameTypes[j]; + select.appendChild(item); + } + + try { + loot.installedGames = JSON.parse(results[1]); + } catch (e) { + console.log(e); + console.log('getInstalledGames response: ' + results[1]); + } + + try { + loot.settings = JSON.parse(results[2]); + } catch (e) { + console.log(e); + console.log('getSettings response: ' + results[2]); + } + }).then(function(){ + return l10n.getJedInstance(loot.settings.language).then(function(jed){ + l10n.translateStaticText(jed); + l10n.jed = jed; + + /* Also need to update the settings UI. */ + loot.updateSettingsUI(); + }).catch(processCefError); + }).then(function(){ + if (result) { + return new Promise(function(resolve, reject){ + closeProgressDialog(); + document.getElementById('settingsButton').click(); + resolve(''); + }); + } else { + return loot.query('getGameData').then(function(result){ + var game = JSON.parse(result, jsonToPlugin); + loot.game.folder = game.folder; + loot.game.masterlist = game.masterlist; + loot.game.globalMessages = game.globalMessages; + loot.game.plugins = game.plugins; + document.getElementById('cardsNav').data = loot.game.plugins; + document.getElementById('main').lastElementChild.data = loot.game.plugins; + + setTimeout(function() { + document.getElementById('cardsNav').updateSize(); + closeProgressDialog(); + }, 100); + + return ''; + }).catch(processCefError); + } + }).then(function(){ + if (!loot.settings.lastVersion || loot.settings.lastVersion != loot.version) { + document.getElementById('firstRun').showModal(); + } + }).catch(processCefError); + }).catch(processCefError); +} + +window.addEventListener('polymer-ready', function(e) { + /* Set the plugin list's scroll target to its parent. */ + document.getElementById('main').lastElementChild.scrollTarget = document.getElementById('main'); + + require(['bower_components/marked/lib/marked', 'js/l10n', 'js/plugin', 'js/helpers', 'js/loot', 'js/filters', 'js/events'], function(markedResponse, l10nResponse) { + + /* Register object observers. */ + Object.observe(loot, loot.observer); + Object.observe(loot.game, loot.gameObserver); + + marked = markedResponse; + l10n = l10nResponse; + /* Make sure settings are what I want. */ + marked.setOptions({ + gfm: true, + tables: true, + sanitize: true + }); + setupEventHandlers(); + initVars(); + }); +}, false); diff --git a/resources/report/js/loot.js b/resources/report/js/loot.js new file mode 100644 index 00000000..183ce78a --- /dev/null +++ b/resources/report/js/loot.js @@ -0,0 +1,309 @@ +var loot = { + installedGames: [], + settings: {}, + game: { + folder: '', + globalMessages: [], + masterlist: {}, + plugins: [], + + /* Call whenever game is changed or game menu / game table are rewritten. */ + updateSelectedGame: function() { + document.getElementById('gameMenu').value = this.folder; + + /* Also disable deletion of the game's row in the settings dialog. */ + var table = document.getElementById('gameTable'); + for (var i = 0; i < table.tBodies[0].rows.length; ++i) { + if (table.tBodies[0].rows[i].getElementsByClassName('folder').length > 0) { + if (table.tBodies[0].rows[i].getElementsByClassName('folder')[0].value == this.folder) { + table.setReadOnly(table.tBodies[0].rows[i], ['delete']); + } else { + table.setReadOnly(table.tBodies[0].rows[i], ['delete'], false); + } + } + } + }, + + /* Call whenever game is changed. */ + updateRedatePluginsButtonState: function() { + /* Also enable/disable the redate plugins option. */ + var index = undefined; + for (var i = 0; i < loot.settings.games.length; ++i) { + if (loot.settings.games[i].folder == this.folder) { + index = i; + break; + } + } + var redateButton = document.getElementById('redatePluginsButton'); + if (index != undefined && loot.settings.games[index].type == 'Skyrim') { + redateButton.removeAttribute('disabled'); + } else { + redateButton.setAttribute('disabled', true); + } + }, + + pluginsObserver: function(changes) { + changes.forEach(function(change){ + /* Need to handle plugin addition and removal. + Update plugin and message counts. */ + var totalChange = 0; + var warnChange = 0; + var errorChange = 0; + var activeChange = 0; + var dirtyChange = 0; + + if (change.addedCount > 0) { + /* Addition */ + for (var i = change.index; i < change.index + change.addedCount; ++i) { + if (change.object[i].isActive) { + ++activeChange; + } + if (change.object[i].isDirty) { + ++dirtyChange; + } + if (change.object[i].messages) { + totalChange += change.object[i].messages.length; + change.object[i].messages.forEach(function(message) { + if (message.type == 'warn') { + ++warnChange; + } else if (message.type == 'error') { + ++errorChange; + } + }); + } + } + } + if (change.removed.length > 0) { + /* Removal */ + change.removed.forEach(function(plugin){ + if (plugin.isActive) { + --activeChange; + } + if (plugin.isDirty) { + --dirtyChange; + } + if (plugin.messages) { + totalChange -= plugin.messages.length; + plugin.messages.forEach(function(message) { + if (message.type == 'warn') { + --warnChange; + } else if (message.type == 'error') { + --errorChange; + } + }); + } + }); + } + document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + totalChange; + document.getElementById('totalMessageNo').textContent = document.getElementById('filterTotalMessageNo').textContent; + document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + warnReduction; + document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + errorReduction; + document.getElementById('filterTotalPluginNo').textContent = parseInt(document.getElementById('filterTotalPluginNo').textContent, 10) + change.addedCount - change.removed.length; + document.getElementById('totalPluginNo').textContent = document.getElementById('filterTotalPluginNo').textContent; + document.getElementById('activePluginNo').textContent = parseInt(document.getElementById('activePluginNo').textContent, 10) + activeReduction; + document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + dirtyReduction; + }); + } + }, + + /* Call whenever installedGames is changed or game menu is rewritten. */ + updateEnabledGames: function() { + /* Update the disabled games in the game menu. */ + var gameMenuItems = document.getElementById('gameMenu').children; + for (var i = 0; i < gameMenuItems.length; ++i) { + if (this.installedGames.indexOf(gameMenuItems[i].getAttribute('value')) == -1) { + gameMenuItems[i].setAttribute('disabled', true); + gameMenuItems[i].removeEventListener('click', onChangeGame, false); + } else { + gameMenuItems[i].removeAttribute('disabled'); + gameMenuItems[i].addEventListener('click', onChangeGame, false); + } + } + }, + + /* Call whenever settings are changed. */ + updateSettingsUI: function() { + var gameSelect = document.getElementById('defaultGameSelect'); + var gameMenu = document.getElementById('gameMenu'); + var gameTable = document.getElementById('gameTable'); + + /* First make sure game listing elements don't have any existing entries. */ + while (gameSelect.children.length > 1) { + gameSelect.removeChild(gameSelect.lastElementChild); + } + while (gameMenu.firstElementChild) { + gameMenu.firstElementChild.removeEventListener('click', onChangeGame, false); + gameMenu.removeChild(gameMenu.firstElementChild); + } + gameTable.clear(); + + /* Now fill with new values. */ + this.settings.games.forEach(function(game){ + var menuItem = document.createElement('paper-item'); + menuItem.setAttribute('value', game.folder); + menuItem.setAttribute('noink', ''); + menuItem.textContent = game.name; + gameMenu.appendChild(menuItem); + gameSelect.appendChild(menuItem.cloneNode(true)); + + var row = gameTable.addRow(game); + gameTable.setReadOnly(row, ['name','folder','type']); + }); + + gameSelect.value = this.settings.game; + document.getElementById('languageSelect').value = this.settings.language; + document.getElementById('enableDebugLogging').checked = this.settings.enableDebugLogging; + document.getElementById('updateMasterlist').checked = this.settings.updateMasterlist; + + this.updateEnabledGames(); + this.game.updateSelectedGame(); + }, + + /* Observer for loot members. */ + observer: function(changes) { + changes.forEach(function(change){ + if (change.name == 'installedGames') { + change.object.updateEnabledGames(); + } else if (change.name == 'settings') { + change.object.updateSettingsUI(); + } + }); + }, + + /* Observer for loot.game members. */ + gameObserver: function(changes) { + changes.forEach(function(change){ + if (change.name == 'folder') { + change.object.updateSelectedGame(); + change.object.updateRedatePluginsButtonState(); + } else if (change.name == 'masterlist') { + if (change.object[change.name] && change.object[change.name].revision) { + document.getElementById('masterlistRevision').textContent = change.object[change.name].revision; + } else { + document.getElementById('masterlistRevision').textContent = l10n.jed.translate("N/A").fetch(); + } + if (change.object[change.name] && change.object[change.name].date) { + document.getElementById('masterlistDate').textContent = change.object[change.name].date; + } else { + document.getElementById('masterlistDate').textContent = l10n.jed.translate("N/A").fetch(); + } + } else if (change.name == 'globalMessages') { + /* For the messages, they don't have a JS 'class' so need to everything + here. Count up the global message types that exist, then remove them + and add the new ones, counting their types, then update the message + counts. I tried using an observer for this, but it wouldn't fire for + some reason. */ + + var oldTotal = change.oldValue.length; + var newTotal = 0; + var oldWarn = 0; + var newWarn = 0; + var oldErr = 0; + var newErr = 0; + + /* Count up old warnings and errors. */ + change.oldValue.forEach(function(message){ + if (message.type == 'warn') { + ++oldWarn; + } else if (message.type == 'error') { + ++oldErr; + } + }); + + /* Remove old messages from UI. */ + var generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; + while (generalMessagesList.firstElementChild) { + generalMessagesList.removeChild(generalMessagesList.firstElementChild); + } + + /* Add new messages. */ + if (change.object[change.name]) { + newTotal = change.object[change.name].length; + change.object[change.name].forEach(function(message){ + var li = document.createElement('li'); + li.className = message.type; + /* Use the Marked library for Markdown formatting support. */ + li.innerHTML = marked(message.content[0].str); + generalMessagesList.appendChild(li); + + if (li.className == 'warn') { + ++newWarn; + } else if (li.className == 'error') { + ++newErr; + } + }); + } + + /* Update message counts in UI. */ + document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + newTotal - oldTotal; + document.getElementById('totalMessageNo').textContent = document.getElementById('filterTotalMessageNo').textContent; + document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + newWarn - oldWarn; + document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + newErr - oldErr; + } else if (change.name == 'plugins') { + /* Update plugin and message counts. Unlike for global messages + it's not worth calculating the count differences, just count + from zero. */ + var totalMessageNo = 0; + var warnMessageNo = 0; + var errorMessageNo = 0; + var activePluginNo = 0; + var dirtyPluginNo = 0; + + if (change.object.globalMessages) { + totalMessageNo = change.object.globalMessages.length; + change.object.globalMessages.forEach(function(message){ + if (message.type == 'warn') { + ++warnMessageNo; + } else if (message.type == 'error') { + ++errorMessageNo; + } + }); + } + + change.object[change.name].forEach(function(plugin) { + if (plugin.isActive) { + ++activePluginNo; + } + if (plugin.isDirty) { + ++dirtyPluginNo; + } + if (plugin.messages) { + totalMessageNo += plugin.messages.length; + plugin.messages.forEach(function(message) { + if (message.type == 'warn') { + ++warnMessageNo; + } else if (message.type == 'error') { + ++errorMessageNo; + } + }); + } + }); + document.getElementById('filterTotalMessageNo').textContent = totalMessageNo; + document.getElementById('totalMessageNo').textContent = totalMessageNo; + document.getElementById('totalWarningNo').textContent = warnMessageNo; + document.getElementById('totalErrorNo').textContent = errorMessageNo; + document.getElementById('filterTotalPluginNo').textContent = change.object[change.name].length; + document.getElementById('totalPluginNo').textContent = change.object[change.name].length; + document.getElementById('activePluginNo').textContent = activePluginNo; + document.getElementById('dirtyPluginNo').textContent = dirtyPluginNo; + + /* Register a new array observer. */ + Array.observe(change.object[change.name], change.object.pluginsObserver); + } + }); + }, + + /* Returns a cefQuery as a Promise. */ + query: function(request) { + return new Promise(function(resolve, reject) { + window.cefQuery({ + request: request, + persistent: false, + onSuccess: resolve, + onFailure: function(errorCode, errorMessage) { + reject(Error('Error code: ' + errorCode + '; ' + errorMessage)) + } + }); + }); + } +}; diff --git a/resources/report/js/script.js b/resources/report/js/script.js deleted file mode 100644 index c3992c40..00000000 --- a/resources/report/js/script.js +++ /dev/null @@ -1,1297 +0,0 @@ -/* LOOT - - A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and - Fallout: New Vegas. - - Copyright (C) 2013-2015 WrinklyNinja - - This file is part of LOOT. - - LOOT is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public License - as published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. - - LOOT is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with LOOT. If not, see - . -*/ -'use strict'; -var marked; -var l10n; -var loot = { - installedGames: [], - settings: {}, - game: { - folder: '', - globalMessages: [], - masterlist: {}, - plugins: [], - - /* Call whenever game is changed or game menu / game table are rewritten. */ - updateSelectedGame: function() { - document.getElementById('gameMenu').value = this.folder; - - /* Also disable deletion of the game's row in the settings dialog. */ - var table = document.getElementById('gameTable'); - for (var i = 0; i < table.tBodies[0].rows.length; ++i) { - if (table.tBodies[0].rows[i].getElementsByClassName('folder').length > 0) { - if (table.tBodies[0].rows[i].getElementsByClassName('folder')[0].value == this.folder) { - table.setReadOnly(table.tBodies[0].rows[i], ['delete']); - } else { - table.setReadOnly(table.tBodies[0].rows[i], ['delete'], false); - } - } - } - }, - - /* Call whenever game is changed. */ - updateRedatePluginsButtonState: function() { - /* Also enable/disable the redate plugins option. */ - var index = undefined; - for (var i = 0; i < loot.settings.games.length; ++i) { - if (loot.settings.games[i].folder == this.folder) { - index = i; - break; - } - } - var redateButton = document.getElementById('redatePluginsButton'); - if (index != undefined && loot.settings.games[index].type == 'Skyrim') { - redateButton.removeAttribute('disabled'); - } else { - redateButton.setAttribute('disabled', true); - } - }, - - pluginsObserver: function(changes) { - changes.forEach(function(change){ - /* Need to handle plugin addition and removal. - Update plugin and message counts. */ - var totalChange = 0; - var warnChange = 0; - var errorChange = 0; - var activeChange = 0; - var dirtyChange = 0; - - if (change.addedCount > 0) { - /* Addition */ - for (var i = change.index; i < change.index + change.addedCount; ++i) { - if (change.object[i].isActive) { - ++activeChange; - } - if (change.object[i].isDirty) { - ++dirtyChange; - } - if (change.object[i].messages) { - totalChange += change.object[i].messages.length; - change.object[i].messages.forEach(function(message) { - if (message.type == 'warn') { - ++warnChange; - } else if (message.type == 'error') { - ++errorChange; - } - }); - } - } - } - if (change.removed.length > 0) { - /* Removal */ - change.removed.forEach(function(plugin){ - if (plugin.isActive) { - --activeChange; - } - if (plugin.isDirty) { - --dirtyChange; - } - if (plugin.messages) { - totalChange -= plugin.messages.length; - plugin.messages.forEach(function(message) { - if (message.type == 'warn') { - --warnChange; - } else if (message.type == 'error') { - --errorChange; - } - }); - } - }); - } - document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + totalChange; - document.getElementById('totalMessageNo').textContent = document.getElementById('filterTotalMessageNo').textContent; - document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + warnReduction; - document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + errorReduction; - document.getElementById('filterTotalPluginNo').textContent = parseInt(document.getElementById('filterTotalPluginNo').textContent, 10) + change.addedCount - change.removed.length; - document.getElementById('totalPluginNo').textContent = document.getElementById('filterTotalPluginNo').textContent; - document.getElementById('activePluginNo').textContent = parseInt(document.getElementById('activePluginNo').textContent, 10) + activeReduction; - document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + dirtyReduction; - }); - } - }, - - /* Call whenever installedGames is changed or game menu is rewritten. */ - updateEnabledGames: function() { - /* Update the disabled games in the game menu. */ - var gameMenuItems = document.getElementById('gameMenu').children; - for (var i = 0; i < gameMenuItems.length; ++i) { - if (this.installedGames.indexOf(gameMenuItems[i].getAttribute('value')) == -1) { - gameMenuItems[i].setAttribute('disabled', true); - gameMenuItems[i].removeEventListener('click', changeGame, false); - } else { - gameMenuItems[i].removeAttribute('disabled'); - gameMenuItems[i].addEventListener('click', changeGame, false); - } - } - }, - - /* Call whenever settings are changed. */ - updateSettingsUI: function() { - var gameSelect = document.getElementById('defaultGameSelect'); - var gameMenu = document.getElementById('gameMenu'); - var gameTable = document.getElementById('gameTable'); - - /* First make sure game listing elements don't have any existing entries. */ - while (gameSelect.children.length > 1) { - gameSelect.removeChild(gameSelect.lastElementChild); - } - while (gameMenu.firstElementChild) { - gameMenu.firstElementChild.removeEventListener('click', changeGame, false); - gameMenu.removeChild(gameMenu.firstElementChild); - } - gameTable.clear(); - - /* Now fill with new values. */ - this.settings.games.forEach(function(game){ - var menuItem = document.createElement('paper-item'); - menuItem.setAttribute('value', game.folder); - menuItem.setAttribute('noink', ''); - menuItem.textContent = game.name; - gameMenu.appendChild(menuItem); - gameSelect.appendChild(menuItem.cloneNode(true)); - - var row = gameTable.addRow(game); - gameTable.setReadOnly(row, ['name','folder','type']); - }); - - gameSelect.value = this.settings.game; - document.getElementById('languageSelect').value = this.settings.language; - document.getElementById('enableDebugLogging').checked = this.settings.enableDebugLogging; - document.getElementById('updateMasterlist').checked = this.settings.updateMasterlist; - - this.updateEnabledGames(); - this.game.updateSelectedGame(); - }, - - /* Observer for loot members. */ - observer: function(changes) { - changes.forEach(function(change){ - if (change.name == 'installedGames') { - change.object.updateEnabledGames(); - } else if (change.name == 'settings') { - change.object.updateSettingsUI(); - } - }); - }, - - /* Observer for loot.game members. */ - gameObserver: function(changes) { - changes.forEach(function(change){ - if (change.name == 'folder') { - change.object.updateSelectedGame(); - change.object.updateRedatePluginsButtonState(); - } else if (change.name == 'masterlist') { - if (change.object[change.name] && change.object[change.name].revision) { - document.getElementById('masterlistRevision').textContent = change.object[change.name].revision; - } else { - document.getElementById('masterlistRevision').textContent = l10n.jed.translate("N/A").fetch(); - } - if (change.object[change.name] && change.object[change.name].date) { - document.getElementById('masterlistDate').textContent = change.object[change.name].date; - } else { - document.getElementById('masterlistDate').textContent = l10n.jed.translate("N/A").fetch(); - } - } else if (change.name == 'globalMessages') { - /* For the messages, they don't have a JS 'class' so need to everything - here. Count up the global message types that exist, then remove them - and add the new ones, counting their types, then update the message - counts. I tried using an observer for this, but it wouldn't fire for - some reason. */ - - var oldTotal = change.oldValue.length; - var newTotal = 0; - var oldWarn = 0; - var newWarn = 0; - var oldErr = 0; - var newErr = 0; - - /* Count up old warnings and errors. */ - change.oldValue.forEach(function(message){ - if (message.type == 'warn') { - ++oldWarn; - } else if (message.type == 'error') { - ++oldErr; - } - }); - - /* Remove old messages from UI. */ - var generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; - while (generalMessagesList.firstElementChild) { - generalMessagesList.removeChild(generalMessagesList.firstElementChild); - } - - /* Add new messages. */ - if (change.object[change.name]) { - newTotal = change.object[change.name].length; - change.object[change.name].forEach(function(message){ - var li = document.createElement('li'); - li.className = message.type; - /* Use the Marked library for Markdown formatting support. */ - li.innerHTML = marked(message.content[0].str); - generalMessagesList.appendChild(li); - - if (li.className == 'warn') { - ++newWarn; - } else if (li.className == 'error') { - ++newErr; - } - }); - } - - /* Update message counts in UI. */ - document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + newTotal - oldTotal; - document.getElementById('totalMessageNo').textContent = document.getElementById('filterTotalMessageNo').textContent; - document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + newWarn - oldWarn; - document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + newErr - oldErr; - } else if (change.name == 'plugins') { - /* Update plugin and message counts. Unlike for global messages - it's not worth calculating the count differences, just count - from zero. */ - var totalMessageNo = 0; - var warnMessageNo = 0; - var errorMessageNo = 0; - var activePluginNo = 0; - var dirtyPluginNo = 0; - - if (change.object.globalMessages) { - totalMessageNo = change.object.globalMessages.length; - change.object.globalMessages.forEach(function(message){ - if (message.type == 'warn') { - ++warnMessageNo; - } else if (message.type == 'error') { - ++errorMessageNo; - } - }); - } - - change.object[change.name].forEach(function(plugin) { - if (plugin.isActive) { - ++activePluginNo; - } - if (plugin.isDirty) { - ++dirtyPluginNo; - } - if (plugin.messages) { - totalMessageNo += plugin.messages.length; - plugin.messages.forEach(function(message) { - if (message.type == 'warn') { - ++warnMessageNo; - } else if (message.type == 'error') { - ++errorMessageNo; - } - }); - } - }); - document.getElementById('filterTotalMessageNo').textContent = totalMessageNo; - document.getElementById('totalMessageNo').textContent = totalMessageNo; - document.getElementById('totalWarningNo').textContent = warnMessageNo; - document.getElementById('totalErrorNo').textContent = errorMessageNo; - document.getElementById('filterTotalPluginNo').textContent = change.object[change.name].length; - document.getElementById('totalPluginNo').textContent = change.object[change.name].length; - document.getElementById('activePluginNo').textContent = activePluginNo; - document.getElementById('dirtyPluginNo').textContent = dirtyPluginNo; - - /* Register a new array observer. */ - Array.observe(change.object[change.name], change.object.pluginsObserver); - } - }); - }, - - /* Returns a cefQuery as a Promise. */ - query: function(request) { - return new Promise(function(resolve, reject) { - window.cefQuery({ - request: request, - persistent: false, - onSuccess: resolve, - onFailure: function(errorCode, errorMessage) { - reject(Error('Error code: ' + errorCode + '; ' + errorMessage)) - } - }); - }); - } -}; -Object.observe(loot, loot.observer); -/* Register game object observer. */ -Object.observe(loot.game, loot.gameObserver); - -function processCefError(err) { - /* Error.stack seems to be Chromium-specific. It gives a lot more useful - info than just the error message. Also, this can be used to catch any - promise errors, not just CEF errors. */ - console.log(err.stack); - closeProgressDialog(); - showMessageBox(l10n.jed.translate('Error').fetch(), err.message); -} - -function showElement(element) { - if (element != null) { - element.classList.toggle('hidden', false); - } -} -function hideElement(element) { - if (element != null) { - element.classList.toggle('hidden', true); - } -} -function toggleDisplayCSS(evt) { - var attr = 'data-hide-' + evt.target.getAttribute('data-class'); - if (evt.target.checked) { - document.getElementById('main').setAttribute(attr, true); - } else { - document.getElementById('main').removeAttribute(attr); - } -} -function toast(text) { - var toast = document.getElementById('toast'); - toast.text = text; - toast.show(); -} -function getConflictingPluginsFromFilter() { - var conflictsPlugin = document.body.getAttribute('data-conflicts'); - if (conflictsPlugin) { - /* Now get conflicts for the plugin. */ - var request = JSON.stringify({ - name: 'getConflictingPlugins', - args: [ - conflictsPlugin - ] - }); - - showProgress(l10n.jed.translate('Checking if plugins have been loaded...').fetch()); - - return loot.query(request).then(JSON.parse).then(function(result){ - if (result) { - /* Filter everything but the plugin itself if there are no - conflicts. */ - var conflicts = [ conflictsPlugin ]; - for (var key in result) { - if (result[key].conflicts) { - conflicts.push(key); - } - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == key) { - loot.game.plugins[i].crc = result[key].crc; - loot.game.plugins[i].isEmpty = result[key].isEmpty; - - loot.game.plugins[i].messages = result[key].messages; - loot.game.plugins[i].tags = result[key].tags; - loot.game.plugins[i].isDirty = result[key].isDirty; - break; - } - } - } - closeProgressDialog(); - return conflicts; - } - closeProgressDialog(); - return [ conflictsPlugin ]; - }).catch(processCefError); - } - - return Promise.resolve([]); -} -function showMessageDialog(title, text, positiveText, closeCallback) { - var dialog = document.createElement('loot-message-dialog'); - dialog.setButtonText(positiveText, l10n.jed.translate('Cancel').fetch()); - dialog.showModal(title, text, closeCallback); - document.body.appendChild(dialog); -} -function showMessageBox(title, text) { - var dialog = document.createElement('loot-message-dialog'); - dialog.setButtonText(l10n.jed.translate('OK').fetch()); - dialog.showModal(title, text); - document.body.appendChild(dialog); -} -function openLogLocation(evt) { - loot.query('openLogLocation').catch(processCefError); -} -function changeGame(evt) { - /* Check that the selected game isn't the current one. */ - if (evt.target.className.indexOf('core-selected') != -1) { - return; - } - - /* Send off a CEF query with the folder name of the new game. */ - showProgress(l10n.jed.translate('Loading game data...').fetch()); - var request = JSON.stringify({ - name: 'changeGame', - args: [ - evt.currentTarget.getAttribute('value') - ] - }); - loot.query(request).then(function(result){ - /* Filters should be re-applied on game change, except the conflicts - filter. Don't need to deactivate the others beforehand. Strictly not - deactivating the conflicts filter either, just resetting it's value. - */ - document.body.removeAttribute('data-conflicts'); - - /* Clear the UI of all existing game-specific data. Also - clear the card and li variables for each plugin object. */ - var globalMessages = document.getElementById('summary').getElementsByTagName('ul')[0]; - while (globalMessages.firstElementChild) { - globalMessages.removeChild(globalMessages.firstElementChild); - } - - /* Parse the data sent from C++. */ - try { - var gameInfo = JSON.parse(result, jsonToPlugin); - loot.game.folder = gameInfo.folder; - loot.game.masterlist = gameInfo.masterlist; - loot.game.globalMessages = gameInfo.globalMessages; - loot.game.plugins = gameInfo.plugins; - - /* Reset virtual list positions. */ - document.getElementById('cardsNav').scrollToItem(0); - document.getElementById('main').lastElementChild.scrollToItem(0); - - /* Now update virtual lists. */ - setFilteredUIData(); - } catch (e) { - console.log(e); - console.log('changeGame response: ' + result); - } - - closeProgressDialog(); - }).catch(processCefError); -} -function openReadme(evt) { - loot.query('openReadme').catch(processCefError); -} -/* Masterlist update process, minus progress dialog. */ -function updateMasterlistNoProgress() { - return loot.query('updateMasterlist').then(JSON.parse).then(function(result){ - if (result) { - /* Update JS variables. */ - loot.game.masterlist = result.masterlist; - loot.game.globalMessages = result.globalMessages; - - result.plugins.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].isDirty = plugin.isDirty; - loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; - loot.game.plugins[i].masterlist = plugin.masterlist; - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].modPriority = plugin.modPriority; - loot.game.plugins[i].tags = plugin.tags; - break; - } - } - }); - /* Hack to stop cards overlapping. */ - document.getElementById('main').lastElementChild.updateSize(); - - toast(l10n.jed.translate('Masterlist updated to revision %s.').fetch(loot.game.masterlist.revision)); - } else { - toast(l10n.jed.translate('No masterlist update was necessary.').fetch()); - } - }).catch(processCefError); -} -function updateMasterlist(evt) { - showProgress(l10n.jed.translate('Updating masterlist...').fetch()); - updateMasterlistNoProgress().then(function(result){ - closeProgressDialog(); - }).catch(processCefError); -} -function showProgress(message) { - var progressDialog = document.getElementById('progressDialog'); - if (message) { - progressDialog.getElementsByTagName('p')[0].textContent = message; - } - if (!progressDialog.opened) { - progressDialog.showModal(); - } -} -function closeProgressDialog() { - var progressDialog = document.getElementById('progressDialog'); - if (progressDialog.opened) { - progressDialog.close(); - } -} -function sortPlugins(evt) { - var mlistUpdate; - if (loot.settings.updateMasterlist) { - mlistUpdate = updateMasterlistNoProgress(); - } else { - mlistUpdate = new Promise(function(resolve, reject){ - resolve(''); - }); - } - mlistUpdate.then(function(){ - showProgress(l10n.jed.translate('Sorting plugins...').fetch()); - loot.query('sortPlugins').then(JSON.parse).then(function(result){ - if (result) { - loot.game.oldLoadOrder = loot.game.plugins; - loot.game.loadOrder = []; - result.forEach(function(plugin){ - var found = false; - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == plugin.name) { - loot.game.plugins[i].crc = plugin.crc; - loot.game.plugins[i].isEmpty = plugin.isEmpty; - - loot.game.plugins[i].messages = plugin.messages; - loot.game.plugins[i].tags = plugin.tags; - loot.game.plugins[i].isDirty = plugin.isDirty; - - loot.game.loadOrder.push(loot.game.plugins[i]); - - found = true; - break; - } - } - if (!found) { - loot.game.plugins[i].push(new Plugin(plugin)); - loot.game.loadOrder.push(loot.game.plugins[i]); - } - }); - - if (loot.settings.neverTellMeTheOdds) { - /* Array shuffler from */ - for(var j, x, i = loot.game.loadOrder.length; i; j = Math.floor(Math.random() * i), x = loadOrder[--i], loot.game.loadOrder[i] = loot.game.loadOrder[j], loot.game.loadOrder[j] = x); - } - - /* Now update the UI for the new order. */ - loot.game.plugins = loot.game.loadOrder; - setFilteredUIData(); - - /* Now hide the masterlist update buttons, and display the accept and - cancel sort buttons. */ - hideElement(document.getElementById('updateMasterlistButton')); - hideElement(document.getElementById('sortButton')); - showElement(document.getElementById('applySortButton')); - showElement(document.getElementById('cancelSortButton')); - - /* Disable changing game. */ - document.getElementById('gameMenu').setAttribute('disabled', ''); - closeProgressDialog(); - } - }).catch(processCefError); - }).catch(processCefError); -} -function applySort(evt) { - var loadOrder = []; - loot.game.plugins.forEach(function(plugin){ - loadOrder.push(plugin.name); - }); - var request = JSON.stringify({ - name: 'applySort', - args: [ - loadOrder - ] - }); - return loot.query(request).then(function(result){ - /* Remove old load order storage. */ - delete loot.game.loadOrder; - delete loot.game.oldLoadOrder; - - /* Now show the masterlist update buttons, and hide the accept and - cancel sort buttons. */ - showElement(document.getElementById('updateMasterlistButton')); - showElement(document.getElementById('sortButton')); - hideElement(document.getElementById('applySortButton')); - hideElement(document.getElementById('cancelSortButton')); - - /* Enable changing game. */ - document.getElementById('gameMenu').removeAttribute('disabled'); - }).catch(processCefError); -} -function cancelSort(evt) { - return loot.query('cancelSort').then(function(){ - /* Sort UI elements again according to stored old load order. */ - loot.game.plugins = loot.game.oldLoadOrder; - setFilteredUIData(); - delete loot.game.loadOrder; - delete loot.game.oldLoadOrder; - - /* Now show the masterlist update buttons, and hide the accept and - cancel sort buttons. */ - showElement(document.getElementById('updateMasterlistButton')); - showElement(document.getElementById('sortButton')); - hideElement(document.getElementById('applySortButton')); - hideElement(document.getElementById('cancelSortButton')); - - /* Enable changing game. */ - document.getElementById('gameMenu').removeAttribute('disabled'); - }).catch(processCefError); -} -function redatePlugins(evt) { - if (evt.target.hasAttribute('disabled')) { - return; - } - - showMessageDialog(l10n.jed.translate('Redate Plugins?').fetch(), l10n.jed.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?').fetch(), l10n.jed.translate('Redate').fetch(), function(result){ - if (result) { - loot.query('redatePlugins').then(function(response){ - toast('Plugins were successfully redated.'); - }).catch(processCefError); - } - }); -} -function clearAllMetadata(evt) { - showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from all plugins?').fetch(), l10n.jed.translate('Clear').fetch(), function(result){ - if (result) { - loot.query('clearAllMetadata').then(JSON.parse).then(function(result){ - if (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; - } - } - }); - - toast(l10n.jed.translate('All user-added metadata has been cleared.').fetch()); - } - }).catch(processCefError); - } - }); -} -function copyContent(evt) { - var messages = []; - var plugins = []; - - if (loot.game) { - if (loot.game.globalMessages) { - loot.game.globalMessages.forEach(function(message){ - var m = {}; - messages.push({ - type: message.type, - content: message.content[0].str - }); - }); - } - if (loot.game.plugins) { - loot.game.plugins.forEach(function(plugin){ - plugins.push({ - name: plugin.name, - crc: plugin.crc, - version: plugin.version, - isActive: plugin.isActive, - isEmpty: plugin.isEmpty, - loadsBSA: plugin.loadsBSA, - - modPriority: plugin.modPriority, - isGlobalPriority: plugin.isGlobalPriority, - messages: plugin.messages, - tags: plugin.tags, - isDirty: plugin.isDirty - }); - }); - } - } else { - var message = document.getElementById('summary').getElementsByTagName('ul')[0].firstElementChild; - if (message) { - messages.push({ - type: 'error', - content: message.textContent - }); - } - } - - var request = JSON.stringify({ - name: 'copyContent', - args: [{ - messages: messages, - plugins: plugins - }] - }); - - loot.query(request).then(function(){ - toast(l10n.jed.translate("LOOT's content has been copied to the clipboard.").fetch()); - }).catch(processCefError); -} -function areSettingsValid() { - /* Validate inputs individually. */ - var inputs = document.getElementById('settingsDialog').getElementsByTagName('input'); - for (var i = 0; i < inputs.length; ++i) { - if (!inputs[i].checkValidity()) { - return false; - console.log(inputs[i]); - } - } - return true; -} -function switchSidebarTab(evt) { - if (evt.detail.isSelected) { - document.getElementById(evt.target.selected).parentElement.selected = evt.target.selected; - } -} - -function showAboutDialog(evt) { - document.getElementById('about').showModal(); -} -function closeSettingsDialog(evt) { - if (evt.target.classList.contains('accept')) { - if (!areSettingsValid()) { - return; - } - - /* Update the JS variable values. */ - var settings = { - enableDebugLogging: document.getElementById('enableDebugLogging').checked, - game: document.getElementById('defaultGameSelect').value, - games: document.getElementById('gameTable').getRowsData(false), - language: document.getElementById('languageSelect').value, - lastGame: loot.settings.lastGame, - updateMasterlist: document.getElementById('updateMasterlist').checked, - filters: loot.settings.filters, - }; - - /* Send the settings back to the C++ side. */ - var request = JSON.stringify({ - name: 'closeSettings', - args: [ - settings - ] - }); - loot.query(request).then(function(result){ - - try { - loot.installedGames = JSON.parse(result); - } catch (e) { - console.log(e); - console.log('getInstalledGames response: ' + results[1]); - } - - loot.settings = settings; - }).catch(processCefError); - } else { - /* Re-apply the existing settings to the settings dialog elements. */ - loot.updateSettingsUI(); - } - evt.target.parentElement.close(); -} - -function showSettingsDialog(evt) { - document.getElementById('settingsDialog').showModal(); -} -function focusSearch(evt) { - if (evt.ctrlKey && evt.keyCode == 70) { //'f' - document.getElementById('searchBox').focus(); - } -} -function handleUnappliedChangesClose(change) { - showMessageDialog('', l10n.jed.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?').fetch(change), l10n.jed.translate('Quit').fetch(), function(result){ - if (result) { - /* Cancel any sorting and close any editors. Cheat by sending a - cancelSort query for as many times as necessary. */ - var queries = []; - var numQueries = 0; - if (!document.getElementById('applySortButton').classList.contains('hidden')) { - numQueries += 1; - } - numQueries += document.body.getAttribute('data-editors'); - for (var i = 0; i < numQueries; ++i) { - queries.push(loot.query('cancelSort')); - } - Promise.all(queries).then(function(){ - window.close(); - }).catch(processCefError); - } - }); -} -function handleEditorOpen(evt) { - /* Set up drag 'n' drop event handlers. */ - var elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); - for (var i = 0; i < elements.length; ++i) { - elements[i].draggable = true; - elements[i].addEventListener('dragstart', elements[i].handleDragStart, false); - } - - /* Now show editor. */ - evt.target.classList.toggle('flip'); - - /* Enable priority hover in plugins list and enable header - buttons if this is the only editor instance. */ - var numEditors = 0; - if (document.body.hasAttribute('data-editors')) { - numEditors = parseInt(document.body.getAttribute('data-editors'), 10); - } - ++numEditors; - - if (numEditors == 1) { - /* Set the edit mode toggle attribute. */ - document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); - /* Disable the toolbar elements. */ - document.getElementById('wipeUserlistButton').setAttribute('disabled', ''); - document.getElementById('copyContentButton').setAttribute('disabled', ''); - document.getElementById('refreshContentButton').setAttribute('disabled', ''); - document.getElementById('settingsButton').setAttribute('disabled', ''); - document.getElementById('gameMenu').setAttribute('disabled', ''); - document.getElementById('updateMasterlistButton').setAttribute('disabled', ''); - document.getElementById('sortButton').setAttribute('disabled', ''); - } - document.body.setAttribute('data-editors', numEditors); - document.getElementById('cardsNav').updateSize(); - - return loot.query('editorOpened').catch(processCefError); -} -function handleEditorClose(evt) { - /* evt.detail is true if the apply button was pressed. */ - if (evt.detail) { - /* Need to record the editor control values and work out what's - changed, and update any UI elements necessary. Offload the - majority of the work to the C++ side of things. */ - - var edits = evt.target.readFromEditor(evt.target.data); - - var request = JSON.stringify({ - name: 'editorClosed', - args: [ - edits - ] - }); - loot.query(request).then(JSON.parse).then(function(result){ - if (result) { - evt.target.data.modPriority = result.modPriority; - evt.target.data.isGlobalPriority = result.isGlobalPriority; - evt.target.data.messages = result.messages; - evt.target.data.tags = result.tags; - evt.target.data.isDirty = result.isDirty; - - evt.target.data.userlist = edits.userlist; - delete evt.target.data.editor; - } - }).catch(processCefError); - } else { - /* Don't need to record changes, but still need to notify C++ side that - the editor has been closed. */ - loot.query('editorClosed').catch(processCefError); - } - - /* Now hide editor. */ - evt.target.classList.toggle('flip'); - - /* Remove drag 'n' drop event handlers. */ - var elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item'); - for (var i = 0; i < elements.length; ++i) { - elements[i].removeAttribute('draggable'); - elements[i].removeEventListener('dragstart', elements[i].handleDragStart, false); - } - - /* Disable priority hover in plugins list and enable header - buttons if this is the only editor instance. */ - var numEditors = parseInt(document.body.getAttribute('data-editors'), 10); - --numEditors; - - if (numEditors == 0) { - document.body.removeAttribute('data-editors'); - /* Set the edit mode toggle attribute. */ - document.getElementById('cardsNav').setAttribute('data-editModeToggle', ''); - /* Re-enable toolbar elements. */ - document.getElementById('wipeUserlistButton').removeAttribute('disabled'); - document.getElementById('copyContentButton').removeAttribute('disabled'); - document.getElementById('refreshContentButton').removeAttribute('disabled'); - document.getElementById('settingsButton').removeAttribute('disabled'); - document.getElementById('gameMenu').removeAttribute('disabled'); - document.getElementById('updateMasterlistButton').removeAttribute('disabled'); - document.getElementById('sortButton').removeAttribute('disabled'); - } else { - document.body.setAttribute('data-editors', numEditors); - } - document.getElementById('cardsNav').updateSize(); -} -function handleConflictsFilter(evt) { - /* evt.detail is true if the filter has been activated. */ - if (evt.detail) { - /* Un-highlight any existing filter plugin. */ - var cards = document.getElementById('main').getElementsByTagName('loot-plugin-card'); - for (var i = 0; i < cards.length; ++i) { - cards[i].classList.toggle('highlight', false); - if (cards[i] != evt.target) { - cards[i].deactivateConflictFilter(); - } - } - evt.target.classList.toggle('highlight', true); - document.body.setAttribute('data-conflicts', evt.target.getName()); - } else { - evt.target.classList.toggle('highlight', false); - document.body.removeAttribute('data-conflicts'); - } - setFilteredUIData(evt); -} -function handleCopyMetadata(evt) { - /* evt.detail is the name of the plugin. */ - var request = JSON.stringify({ - name: 'copyMetadata', - args: [ - evt.target.getName(), - ] - }); - - loot.query(request).then(function(){ - toast(l10n.jed.translate('The metadata for "%s" has been copied to the clipboard.').fetch(evt.target.getName())); - }).catch(processCefError); -} -function handleClearMetadata(evt) { - showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from "%s"?').fetch(evt.target.getName()), l10n.jed.translate('Clear').fetch(), function(result){ - if (result) { - var request = JSON.stringify({ - name: 'clearPluginMetadata', - args: [ - evt.target.getName() - ] - }); - - 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 == evt.target.id) { - 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; - } - } - toast(l10n.jed.translate('The user-added metadata for "%s" has been cleared.').fetch(evt.target.getName())); - } - }).catch(processCefError); - } - }); -} -function handleSidebarClick(evt) { - if (evt.target.hasAttribute('data-index')) { - document.getElementById('main').lastElementChild.scrollToItem(evt.target.getAttribute('data-index')); - - if (evt.type == 'dblclick') { - var card = document.getElementById(evt.target.getAttribute('data-id')); - if (!card.classList.contains('flip')) { - document.getElementById(evt.target.getAttribute('data-id')).onShowEditor(); - } - } - } -} -function handleQuit(evt) { - if (!document.getElementById('applySortButton').classList.contains('hidden')) { - handleUnappliedChangesClose(l10n.jed.translate('sorted load order').fetch()); - } else if (document.body.hasAttribute('data-editors')) { - handleUnappliedChangesClose(l10n.jed.translate('metadata edits').fetch()); - } else { - window.close(); - } -} -function jumpToGeneralInfo(evt) { - window.location.hash = ''; - document.getElementById('main').scrollTop = 0; -} -function setupEventHandlers() { - /*Set up handlers for filters.*/ - document.getElementById('hideVersionNumbers').addEventListener('change', toggleDisplayCSS, false); - document.getElementById('hideCRCs').addEventListener('change', toggleDisplayCSS, false); - document.getElementById('hideBashTags').addEventListener('change', toggleDisplayCSS, false); - document.getElementById('hideNotes').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideDoNotCleanMessages').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideInactivePlugins').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideAllPluginMessages').addEventListener('change', setFilteredUIData, false); - document.getElementById('hideMessagelessPlugins').addEventListener('change', setFilteredUIData, false); - document.body.addEventListener('loot-filter-conflicts', handleConflictsFilter, false); - - /* Set up event handlers for content filter. */ - document.getElementById('searchBox').addEventListener('change', setFilteredUIData, false); - window.addEventListener('keyup', focusSearch, false); - - /* Set up handlers for buttons. */ - document.getElementById('redatePluginsButton').addEventListener('click', redatePlugins, false); - document.getElementById('openLogButton').addEventListener('click', openLogLocation, false); - document.getElementById('wipeUserlistButton').addEventListener('click', clearAllMetadata, false); - document.getElementById('copyContentButton').addEventListener('click', copyContent, false); - document.getElementById('refreshContentButton').addEventListener('click', onContentRefresh, false); - document.getElementById('settingsButton').addEventListener('click', showSettingsDialog, false); - document.getElementById('helpButton').addEventListener('click', openReadme, false); - document.getElementById('aboutButton').addEventListener('click', showAboutDialog, false); - document.getElementById('quitButton').addEventListener('click', handleQuit, false); - document.getElementById('updateMasterlistButton').addEventListener('click', updateMasterlist, false); - document.getElementById('sortButton').addEventListener('click', sortPlugins, false); - document.getElementById('applySortButton').addEventListener('click', applySort, false); - document.getElementById('cancelSortButton').addEventListener('click', cancelSort, false); - document.getElementById('sidebarTabs').addEventListener('core-select', switchSidebarTab, false); - document.getElementById('jumpToGeneralInfo').addEventListener('click', jumpToGeneralInfo, false); - - /* Set up event handlers for settings dialog. */ - var settings = document.getElementById('settingsDialog'); - settings.getElementsByClassName('accept')[0].addEventListener('click', closeSettingsDialog, false); - settings.getElementsByClassName('cancel')[0].addEventListener('click', closeSettingsDialog, false); - - /* Set up handler for opening and closing editors. */ - document.body.addEventListener('loot-editor-open', handleEditorOpen, false); - document.body.addEventListener('loot-editor-close', handleEditorClose, false); - document.body.addEventListener('loot-copy-metadata', handleCopyMetadata, false); - document.body.addEventListener('loot-clear-metadata', handleClearMetadata, false); - - document.getElementById('cardsNav').addEventListener('click', handleSidebarClick, false); - document.getElementById('cardsNav').addEventListener('dblclick', handleSidebarClick, false); -} -function initVars() { - loot.query('getVersion').then(function(result){ - try { - loot.version = JSON.parse(result); - - /* The fourth part of the version string is the build number. Trim it. */ - var pos = loot.version.lastIndexOf('.'); - if (loot.version.length > pos + 1) { - document.getElementById('LOOTBuild').textContent = loot.version.substring(pos + 1); - } else { - document.getElementById('LOOTBuild').textContent = l10n.jed.translate('unknown').fetch(); - } - - loot.version = loot.version.substring(0, pos); - document.getElementById('LOOTVersion').textContent = loot.version; - document.getElementById('firstTimeLootVersion').textContent = loot.version; - } catch (e) { - console.log(e); - console.log('Response: ' + result); - } - }).catch(processCefError); - - loot.query('getLanguages').then(function(result){ - try { - loot.languages = JSON.parse(result); - - /* Now fill in language options. */ - var settingsLangSelect = document.getElementById('languageSelect'); - var messageLangSelect = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (messageLangSelect) { - messageLangSelect = messageLangSelect.import.querySelector('#messageRow').content.querySelector('.language'); - } else { - messageLangSelect = document.querySelector('#messageRow').content.querySelector('.language'); - } - - for (var i = 0; i < loot.languages.length; ++i) { - var settingsItem = document.createElement('paper-item'); - settingsItem.setAttribute('value', loot.languages[i].locale); - settingsItem.setAttribute('noink', ''); - settingsItem.textContent = loot.languages[i].name; - settingsLangSelect.appendChild(settingsItem); - messageLangSelect.appendChild(settingsItem.cloneNode(true)); - } - } catch (e) { - console.log(e); - console.log('Response: ' + result); - } - }).catch(processCefError); - - loot.query('getInitErrors').then(JSON.parse).then(function(result){ - if (result) { - var generalMessagesList = document.getElementById('summary').getElementsByTagName('ul')[0]; - - result.forEach(function(message){ - var li = document.createElement('li'); - li.className = 'error'; - /* Use the Marked library for Markdown formatting support. */ - li.innerHTML = marked(message); - generalMessagesList.appendChild(li); - }); - - document.getElementById('filterTotalMessageNo').textContent = result.length; - document.getElementById('totalMessageNo').textContent = result.length; - document.getElementById('totalErrorNo').textContent = result.length; - } - - var parallelPromises = [ - loot.query('getGameTypes'), - loot.query('getInstalledGames'), - loot.query('getSettings'), - ]; - - showProgress('Initialising user interface...'); - Promise.all(parallelPromises).then(function(results) { - try { - loot.gameTypes = JSON.parse(results[0]); - } catch (e) { - console.log(e); - console.log('getGameTypes response: ' + results[0]); - } - - /* Fill in game row template's game type options. */ - var select = document.querySelector('link[rel="import"][href$="editable-table.html"]'); - if (select) { - select = select.import.querySelector('#gameRow').content.querySelector('.type') - } else { - select = document.querySelector('#gameRow').content.querySelector('.type'); - } - for (var j = 0; j < loot.gameTypes.length; ++j) { - var item = document.createElement('paper-item'); - item.setAttribute('value', loot.gameTypes[j]); - item.setAttribute('noink', ''); - item.textContent = loot.gameTypes[j]; - select.appendChild(item); - } - - try { - loot.installedGames = JSON.parse(results[1]); - } catch (e) { - console.log(e); - console.log('getInstalledGames response: ' + results[1]); - } - - try { - loot.settings = JSON.parse(results[2]); - } catch (e) { - console.log(e); - console.log('getSettings response: ' + results[2]); - } - }).then(function(){ - return l10n.getJedInstance(loot.settings.language).then(function(jed){ - l10n.translateStaticText(jed); - l10n.jed = jed; - - /* Also need to update the settings UI. */ - loot.updateSettingsUI(); - }).catch(processCefError); - }).then(function(){ - if (result) { - return new Promise(function(resolve, reject){ - closeProgressDialog(); - document.getElementById('settingsButton').click(); - resolve(''); - }); - } else { - return loot.query('getGameData').then(function(result){ - var game = JSON.parse(result, jsonToPlugin); - loot.game.folder = game.folder; - loot.game.masterlist = game.masterlist; - loot.game.globalMessages = game.globalMessages; - loot.game.plugins = game.plugins; - document.getElementById('cardsNav').data = loot.game.plugins; - document.getElementById('main').lastElementChild.data = loot.game.plugins; - - setTimeout(function() { - document.getElementById('cardsNav').updateSize(); - closeProgressDialog(); - }, 100); - - return ''; - }).catch(processCefError); - } - }).then(function(){ - if (!loot.settings.lastVersion || loot.settings.lastVersion != loot.version) { - document.getElementById('firstRun').showModal(); - } - }).catch(processCefError); - }).catch(processCefError); -} -function onContentRefresh(evt) { - /* Send a query for updated load order and plugin header info. */ - showProgress(l10n.jed.translate('Refreshing data...').fetch()); - loot.query('getGameData').then(function(result){ - /* Parse the data sent from C++. */ - try { - /* We don't want the plugin info creating cards, so don't convert - to plugin objects. */ - var gameInfo = JSON.parse(result); - } catch (e) { - console.log(e); - console.log('getGameData response: ' + result); - } - - /* Now overwrite plugin data with the newly sent data. Also update - card and li vars as they were unset when the game was switched - from before. */ - var pluginNames = []; - gameInfo.plugins.forEach(function(plugin){ - var foundPlugin = false; - for (var i = 0; i < loot.game.plugins.length; ++i) { - if (loot.game.plugins[i].name == plugin.name) { - - loot.game.plugins[i].isActive = plugin.isActive; - loot.game.plugins[i].isEmpty = plugin.isEmpty; - loot.game.plugins[i].loadsBSA = plugin.loadsBSA; - loot.game.plugins[i].crc = plugin.crc; - loot.game.plugins[i].version = plugin.version; - - 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; - - foundPlugin = true; - break; - } - } - if (!foundPlugin) { - /* A new plugin. */ - loot.game.plugins.push(new Plugin(plugin)); - } - pluginNames.push(plugin.name); - }); - for (var i = 0; i < loot.game.plugins.length;) { - var foundPlugin = false; - for (var j = 0; j < pluginNames.length; ++j) { - if (loot.game.plugins[i].name == pluginNames[j]) { - foundPlugin = true; - break; - } - } - if (!foundPlugin) { - /* Remove plugin. */ - loot.game.plugins.splice(i, 1); - } else { - ++i; - } - } - - /* Reapply filters. */ - setFilteredUIData(); - - closeProgressDialog(); - }).catch(processCefError); -} - -window.addEventListener('polymer-ready', function(e) { - /* Set the plugin list's scroll target to its parent. */ - document.getElementById('main').lastElementChild.scrollTarget = document.getElementById('main'); - - require(['bower_components/marked/lib/marked', 'js/l10n', 'js/plugin', 'js/filters'], function(markedResponse, l10nResponse) { - marked = markedResponse; - l10n = l10nResponse; - /* Make sure settings are what I want. */ - marked.setOptions({ - gfm: true, - tables: true, - sanitize: true - }); - setupEventHandlers(); - initVars(); - }); -}, false); diff --git a/resources/report/report.html b/resources/report/report.html index 49b11d99..a05ff08e 100644 --- a/resources/report/report.html +++ b/resources/report/report.html @@ -298,5 +298,5 @@

LOOT is free, but if you want to show your appreciation with some money, donations may be made to WrinklyNinja (LOOT's creator and main developer) using PayPal, Bitcoin or Flattr.

OK - + diff --git a/src/archive.py b/src/archive.py index 7400f310..fe3dfd67 100644 --- a/src/archive.py +++ b/src/archive.py @@ -141,9 +141,12 @@ def createAppArchive(archive_path): shutil.copy( os.path.join('..', 'resources', 'report', 'bower_components', 'Jed', 'jed.js'), os.path.join(temp_path, 'resources', 'report', 'bower_components', 'Jed') ) shutil.copy( os.path.join('..', 'resources', 'report', 'bower_components', 'jed-gettext-parser', 'jedGettextParser.js'), os.path.join(temp_path, 'resources', 'report', 'bower_components', 'jed-gettext-parser') ) shutil.copytree( os.path.join('..', 'resources', 'report', 'fonts'), os.path.join(temp_path, 'resources', 'report', 'fonts') ) - shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'l10n.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) - shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'plugin.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) + shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'events.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'filters.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) + shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'helpers.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) + shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'l10n.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) + shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'loot.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) + shutil.copy( os.path.join('..', 'resources', 'report', 'js', 'plugin.js'), os.path.join(temp_path, 'resources', 'report', 'js') ) # Docs. shutil.copytree( os.path.join('..', 'docs', 'images'), os.path.join(temp_path, 'docs', 'images') ) diff --git a/src/installer.nsi b/src/installer.nsi index d6de37d0..1a25f7b5 100644 --- a/src/installer.nsi +++ b/src/installer.nsi @@ -348,9 +348,12 @@ FunctionEnd SetOutPath "$INSTDIR\resources\report\fonts" File "..\resources\report\fonts\*" SetOutPath "$INSTDIR\resources\report\js" - File "..\resources\report\js\l10n.js" - File "..\resources\report\js\plugin.js" + File "..\resources\report\js\events.js" File "..\resources\report\js\filters.js" + File "..\resources\report\js\helpers.js" + File "..\resources\report\js\l10n.js" + File "..\resources\report\js\loot.js" + File "..\resources\report\js\plugin.js" ;Install documentation images. SetOutPath "$INSTDIR\docs\images" @@ -508,9 +511,12 @@ FunctionEnd Delete "$INSTDIR\resources\report\bower_components\Jed\jed.js" Delete "$INSTDIR\resources\report\bower_components\jed-gettext-parser\jedGettextParser.js" RMDir /r "$INSTDIR\resources\report\fonts" - Delete "$INSTDIR\resources\report\js\l10n.js" - Delete "$INSTDIR\resources\report\js\plugin.js" + Delete "$INSTDIR\resources\report\js\events.js" Delete "$INSTDIR\resources\report\js\filters.js" + Delete "$INSTDIR\resources\report\js\helpers.js" + Delete "$INSTDIR\resources\report\js\l10n.js" + Delete "$INSTDIR\resources\report\js\loot.js" + Delete "$INSTDIR\resources\report\js\plugin.js" RMDir /r "$INSTDIR\resources\report" ;Remove language files.