Install Path Registry Key The registry key, in HKEY_LOCAL_MACHINE, that contains the install path of the game. This is used to obtain the install path if LOOT has no previous record of the game's install path, or LOOT's stored install path is invalid. Either this or an install path must be supplied.
-Themes
-LOOT's user interface has experimental support for the use of alternative and custom CSS themes. To use a theme, place its CSS file into the resources/ui/css folder inside LOOT's installation folder, and rename the file to theme.css. LOOT will then load this file when it launches. A dark user interface theme is provided with LOOT: to use it, rename dark-theme.css in the resources/ui/css folder to theme.css. Theme support is experimental because future updates to LOOT's user interface may break existing CSS selectors.
-
Contributing & Support
LOOT is very much a community project, and contributions from its users are very welcome, whether they be metadata, translations, code or anything else. The best way to contribute is to make changes yourself at GitHub! It's the fastest way to get changes you want applied, and you'll get your name automatically immortalised in our credits .
diff --git a/package.json b/package.json
index 0be3bf03..8be6274c 100644
--- a/package.json
+++ b/package.json
@@ -16,15 +16,17 @@
"bower": "^1.7.1",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^4.0.0",
+ "eslint-plugin-html": "^1.3.0",
"fs-extra": "^0.26.2",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-connect": "^0.11.2",
"grunt-contrib-watch": "^0.6.1",
"grunt-saucelabs": "^8.6.2",
+ "mkdirp": "^0.5.1",
"mocha": "^2.3.4",
"should": "^8.0.1",
- "vulcanize": "^0.7.11"
+ "vulcanize": "^1.14.5"
},
"scripts": {
"test": "grunt test"
diff --git a/resources/ui/css/dark-theme.css b/resources/ui/css/dark-theme.css
deleted file mode 100644
index 7f260501..00000000
--- a/resources/ui/css/dark-theme.css
+++ /dev/null
@@ -1,111 +0,0 @@
-/* Dark theme CSS overrides. */
-html,
-html /deep/ paper-item, html /deep/ core-menu > paper-item,
-html /deep/ paper-dropdown-menu::shadow #label {
- color: white !important;
-}
-
-html, body {
- background: black;
-}
-
-html /deep/ paper-item[disabled],
-html /deep/ paper-item[disabled] > core-icon,
-html /deep/ paper-input-decorator::shadow .label-text,
-html /deep/ input::-webkit-input-placeholder,
-html /deep/ table[is=editable-table] paper-icon-button[disabled],
-html /deep/ loot-dropdown-menu::shadow paper-dropdown-menu[disabled]::shadow #label,
-html /deep/ loot-dropdown-menu::shadow paper-dropdown-menu::shadow #arrow {
- color: rgba(255, 255, 255, 0.3) !important;
-}
-
-html /deep/ paper-item core-icon,
-html /deep/ paper-tabs::shadow .scroll-button > paper-icon-button,
-html /deep/ table[is=editable-table] paper-icon-button,
-html /deep/ loot-plugin-card::shadow core-tooltip:not(#activeTick) > core-icon,
-html /deep/ loot-plugin-card::shadow #conflictsLabel > core-icon,
-html /deep/ loot-plugin-item::shadow #secondary,
-html /deep/ loot-plugin-editor::shadow core-tooltip:not(#activeTick) > core-icon {
- color: rgba(255, 255, 255, 0.7) !important;
-}
-
-html /deep/ paper-input-decorator::shadow .underline .unfocused-underline,
-.divider {
- background-color: rgba(255, 255, 255, 0.12) !important;
-}
-
-html /deep/ paper-input-decorator[disabled]::shadow .underline,
-html /deep/ table th,
-html /deep/ paper-dropdown-menu {
- border-bottom-color: rgba(255, 255, 255, 0.12) !important;
-}
-
-html /deep/ paper-checkbox::shadow #checkbox.checked {
- border-color: #1A237E !important;
-}
-
-html /deep/ paper-toggle-button::shadow [checked] .toggle-bar {
- background-color: rgba(26, 35, 126, 0.5) !important;
-}
-
-html /deep/ paper-toggle-button::shadow [checked] .toggle-button,
-html /deep/ paper-checkbox::shadow #checkbox.checked {
- background-color: #1A237E !important;
-}
-
-html /deep/ paper-toggle-button::shadow [checked] .toggle-ink {
- color: #1A237E !important;
-}
-
-/* Dark dropdown menus. */
-html /deep/ paper-dropdown::shadow #background,
-loot-dialog,
-loot-message-dialog,
-div[drawer],
-#summary,
-html /deep/ loot-plugin-card::shadow #wrapper,
-html /deep/ loot-plugin-editor::shadow core-selector > * {
- background: #424242 !important;
-}
-
-/* Dark dialogs. */
-loot-dialog,
-loot-message-dialog {
- color: white !important;
-}
-
-/* Dark progress bar background. */
-paper-progress::shadow #progressContainer {
- background-color: #303030 !important;
-}
-
-core-toolbar,
-div[drawer] > div:first-child,
-html /deep/ loot-search::shadow core-toolbar,
-html /deep/ loot-plugin-editor {
- background: #212121 !important;
-}
-
-#main {
- background: #303030 !important;
-}
-
-li.error > p,
-li.warn > p {
- color: black !important;
-}
-
-.version {
- color: #78909C !important;
-}
-.crc {
- color: #8D6E63 !important;
-}
-
-html /deep/ loot-plugin-card.search-result::shadow #editor {
- box-shadow: inset 4px 0 #1A237E !important;
-}
-
-html /deep/ loot-plugin-editor::shadow core-selector > * {
- border-bottom: 4px solid #212121 !important;
-}
diff --git a/scripts/archive.js b/scripts/archive.js
index 8b8a444b..21ea4c6b 100644
--- a/scripts/archive.js
+++ b/scripts/archive.js
@@ -115,10 +115,6 @@ function createAppArchive(rootPath, releasePath, tempPath, destPath) {
path.join(releasePath, 'resources', 'ui', 'index.html'),
path.join(tempPath, 'resources', 'ui', 'index.html')
);
- fs.copySync(
- path.join(rootPath, 'resources', 'ui', 'css', 'dark-theme.css'),
- path.join(tempPath, 'resources', 'ui', 'css', 'dark-theme.css')
- );
fs.copySync(
path.join(rootPath, 'resources', 'ui', 'fonts'),
path.join(tempPath, 'resources', 'ui', 'fonts')
diff --git a/scripts/installer.iss b/scripts/installer.iss
index 9c9bc3cd..01d05dd5 100644
--- a/scripts/installer.iss
+++ b/scripts/installer.iss
@@ -108,8 +108,6 @@ DestDir: "{app}\docs\images"; Flags: ignoreversion
Source: "{#buildir}\Release\resources\ui\index.html"; \
DestDir: "{app}\resources\ui"; Flags: ignoreversion
-Source: "resources\ui\css\dark-theme.css"; \
-DestDir: "{app}\resources\ui\css"; Flags: ignoreversion
Source: "resources\ui\fonts\*"; \
DestDir: "{app}\resources\ui\fonts"; Flags: ignoreversion
diff --git a/scripts/vulcanize.js b/scripts/vulcanize.js
index 34b0cd40..2cd7c306 100755
--- a/scripts/vulcanize.js
+++ b/scripts/vulcanize.js
@@ -5,16 +5,27 @@
const path = require('path');
const fs = require('fs');
const helpers = require('./helpers');
-const vulcanize = require('vulcanize');
+const Vulcanize = require('vulcanize');
+const mkdirp = require('mkdirp');
+// Initialise from command line parameters.
let rootPath = '.';
-if (process.argv.length > 2) {
+let buildType = 'ui';
+if (process.argv.length > 3) {
+ rootPath = process.argv[2];
+ buildType = process.argv[3];
+} else if (process.argv.length > 2) {
rootPath = process.argv[2];
}
+const vulcanize = new Vulcanize({
+ inlineScripts: true,
+ inlineCss: true,
+});
+
function mkdir(dir) {
try {
- fs.mkdirSync(dir);
+ mkdirp.sync(dir);
} catch (e) {
if (e.code !== 'EEXIST') {
console.log(e);
@@ -22,29 +33,55 @@ function mkdir(dir) {
}
}
-function runVulcanize(err) {
- if (err) {
- console.error(err);
- process.exit(1);
- }
- vulcanize.processDocument();
+function vulcanizeFile(inputPath, outputPath) {
+ // Make sure output directory exists first.
+ mkdir(path.dirname(outputPath));
+
+ vulcanize.process(inputPath, (err, inlinedHtml) => {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
+
+ fs.writeFileSync(outputPath, inlinedHtml);
+ });
}
function vulcanizeRelease(releasePath) {
const inputPath = path.join(rootPath, 'src', 'gui', 'html', 'index.html');
const outputPath = path.join(releasePath.path, 'resources', 'ui', 'index.html');
- // Make sure output directory exists first.
- mkdir(path.dirname(outputPath));
-
- vulcanize.setOptions({
- inline: true,
- excludes: {
- styles: ['css/theme.css'],
- },
- output: outputPath,
- input: inputPath,
- }, runVulcanize);
+ vulcanizeFile(inputPath, outputPath);
}
-helpers.getAppReleasePaths(rootPath).forEach(vulcanizeRelease);
+function vulcanizeTests(releasePath) {
+ const inputPath = path.join(rootPath, 'src', 'tests', 'gui', 'html', 'elements');
+ const outputPath = path.join(releasePath.path, 'html_tests', 'elements');
+
+ const tests = [
+ 'test_editable-table.html',
+ 'test_loot-custom-icons.html',
+ 'test_loot-dropdown-menu.html',
+ 'test_loot-menu.html',
+ 'test_loot-message-dialog.html',
+ 'test_loot-plugin-card.html',
+ 'test_loot-plugin-editor.html',
+ 'test_loot-plugin-item.html',
+ 'test_loot-search-toolbar.html',
+ ];
+
+ tests.forEach((test) => {
+ vulcanizeFile(path.join(inputPath, test), path.join(outputPath, test));
+ });
+}
+
+// Run the appropriate build(s).
+const releasePaths = helpers.getAppReleasePaths(rootPath);
+
+if (buildType === 'ui' || buildType === 'all') {
+ releasePaths.forEach(vulcanizeRelease);
+}
+
+if (buildType === 'tests' || buildType === 'all') {
+ releasePaths.forEach(vulcanizeTests);
+}
diff --git a/src/gui/html/css/material.css b/src/gui/html/css/material.css
deleted file mode 100644
index 9bd464cb..00000000
--- a/src/gui/html/css/material.css
+++ /dev/null
@@ -1,110 +0,0 @@
-/* This file contains the CSS overrides for Polymer elements necessary for them
- to better match the Material Design spec. */
-
-/* core-tooltip */
-html /deep/ core-tooltip::shadow .core-tooltip {
- font-size: 0.714rem;
- font-family: Roboto, sans-serif;
- font-weight: 500;
- background-color: rgba(0, 0, 0, 0.9);
-}
-
-/* paper-button */
-html /deep/ paper-button {
- min-width: 88px;
- height: 36px;
- line-height: 36px;
- margin: 6px 4px;
-}
-html /deep/ paper-button::shadow .button-content {
- padding: 0 8px;
-}
-html /deep/ paper-button::shadow .button-content ::content a {
- margin: 0 -8px;
- padding: 0 8px;
- -ms-flex: 1 1 auto;
-}
-
-/* paper-icon-button */
-html /deep/ paper-icon-button[disabled] {
- color: rgba(255, 255, 255, 0.30);
-}
-
-/* paper-item */
-html /deep/ paper-item, html /deep/ core-menu > paper-item {
- color: rgba(0, 0, 0, 0.87);
- height: 48px;
- line-height: 48px;
-}
-html /deep/ paper-item[disabled] {
- color: rgba(0, 0, 0, 0.26);
-}
-html /deep/ paper-item core-icon {
- margin-right: 32px;
- color: rgba(0, 0, 0, 0.54);
-}
-html /deep/ paper-item[disabled] > core-icon {
- color: rgba(0, 0, 0, 0.26);
-}
-html /deep/ paper-item::shadow .button-content {
- padding: 0 16px;
-}
-html /deep/ paper-item::shadow .button-content ::content *:not(core-tooltip) {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-}
-html /deep/ paper-item::shadow .button-content ::content a {
- margin: 0 -16px;
- padding: 0 16px;
- -ms-flex: 1 1 auto;
-}
-
-/* paper-input-decorator & input */
-html /deep/ paper-input-decorator {
- margin: 0 4px;
-}
-html /deep/ paper-input-decorator::shadow .underline .unfocused-underline {
- background-color: rgba(0, 0, 0, 0.12);
-}
-html /deep/ paper-input-decorator[disabled]::shadow .underline {
- border-bottom-color: rgba(0, 0, 0, 0.12);
-}
-html /deep/ paper-input-decorator::shadow .label-text,
-html /deep/ input::-webkit-input-placeholder {
- color: rgba(0, 0, 0, 0.26);
-}
-html /deep/ ::-moz-placeholder {
- opacity: 1 !important;
- color: rgba(0, 0, 0, 0.26) !important;
-}
-html /deep/ :-ms-input-placeholder {
- color: rgba(0, 0, 0, 0.26) !important;
-}
-html /deep/ input {
- text-overflow: ellipsis;
-}
-
-/* paper-dropdown */
-html /deep/ paper-dropdown > core-menu {
- margin: 8px 0;
-}
-html /deep/ paper-dropdown .core-selected {
- background-color: inherit;
-}
-
-/* paper-tabs */
-html /deep/ paper-tabs::shadow .scroll-button > paper-icon-button {
- vertical-align: middle;
- color: rgba(0, 0, 0, 0.54);
-}
-
-/* paper-tab */
-html /deep/ paper-tab::shadow .tab-content {
- line-height: 48px;
-}
-
-/* core-drawer-panel */
-core-drawer-panel::shadow .narrow-layout #drawer {
- width: calc(100vw - 56px) !important;
-}
diff --git a/src/gui/html/css/overrides.css b/src/gui/html/css/overrides.css
deleted file mode 100644
index aa5a1597..00000000
--- a/src/gui/html/css/overrides.css
+++ /dev/null
@@ -1,92 +0,0 @@
-/* These styles override Polymer's own internal styling for certain elements to
- suit LOOT's needs. */
-
-/* Transition icon button colour over same time period that plugin cards flip. */
-paper-icon-button[disabled] {
- transition: color 0.6s;
-}
-/* This override change the positioning of the progress dialog to adapt to content changes. */
-#progressDialog {
- position: inherit !important;
- margin: 0 auto;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
-}
-/* This makes checkboxes have the same padding as icons in lists. */
-html /deep/ core-menu paper-checkbox::shadow #checkboxLabel {
- padding-left: 32px;
-}
-/* Set the sidebar checkboxes to have balanced padding on either side. 32px
- makes the label just seem too far away. */
-#filters paper-checkbox::shadow #checkboxLabel {
- padding-left: 16px;
-}
-/* Set the underline colour of focussed paper inputs. */
-html /deep/ paper-input-decorator::shadow .underline .focused-underline {
- /* line color when the input is focused */
- background-color: #64B5F6;
-}
-/* Set the tab colours. */
-html /deep/ paper-tabs::shadow #selectionBar {
- background-color: #64B5F6;
-}
-html /deep/ paper-tab::shadow #ink {
- color: #64B5F6;
-}
-/* Set the paper tab scroll buttons so they don't cause the cursor to display as
- as a pointer when they are disabled. */
-html /deep/ paper-tabs::shadow .scroll-button .hidden {
- pointer-events: none;
-}
-/* Set the drawer z-index so that it doesn't appear below plugin cards. */
-core-drawer-panel::shadow #drawer {
- /* max-width disabled because the panel doesn't shift over when the max
- is reached, due to the percentage drawer width, and setting it to a
- constant 400px is too wide in many window sizes. */
- /*max-width: 400px;*/
- z-index: 1;
-}
-/* Align dropdown label text with the descriptive text beside it. */
-#settingsDialog loot-dropdown-menu::shadow paper-dropdown-menu {
- padding-top: 4px;
-}
-/* Add a transition to the core-tooltip tooltips. */
-html /deep/ core-tooltip::shadow .core-tooltip {
- opacity: 0;
- transition: opacity 0.3s, visibility 0s 0.3s;
-}
-html /deep/ core-tooltip:hover:not([disabled])::shadow .core-tooltip {
- opacity: 1;
- transition-delay: 0s;
-}
-
-/* The max-height doesn't allow for expansion into newly-created scroll space. */
-#main /deep/ paper-dropdown::shadow #scroller {
- max-height: none !important;
-}
-
-/* Don't center the "Jump to General Information" or active plugin tooltips, as
- they can get cut off by the sidebar. */
-#drawerToggle + core-tooltip::shadow #tooltip,
-loot-plugin-card /deep/ #activeTick::shadow #tooltip {
- left: inherit !important;
-}
-
-/* Style the paper-checkbox background. */
-html /deep/ paper-checkbox::shadow #checkbox.checked {
- border-color: #3F51B5;
- background-color: #3F51B5;
-}
-
-/* Style the paper-toggle-button colours. */
-html /deep/ paper-toggle-button::shadow [checked] .toggle-bar {
- background-color: rgba(63, 81, 181, 0.5);
-}
-html /deep/ paper-toggle-button::shadow [checked] .toggle-button {
- background-color: #3F51B5;
-}
-html /deep/ paper-toggle-button::shadow [checked] .toggle-ink {
- color: #3F51B5;
-}
diff --git a/src/gui/html/css/style.css b/src/gui/html/css/style.css
index d2a94c52..a687df0e 100644
--- a/src/gui/html/css/style.css
+++ b/src/gui/html/css/style.css
@@ -23,9 +23,6 @@
*/
/* Misc */
-html /deep/ paper-button[autofocus] {
- color: #64B5F6;
-}
a {
color: #2196F3;
text-decoration: none;
@@ -38,11 +35,6 @@ tt, code, kbd, samp {
}
/* App bar */
-core-toolbar, div[drawer] > div:first-child {
- background: #3F51B5;
- color: white;
- height: 56px;
-}
#mainToolbar,
#searchBar {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
@@ -52,13 +44,6 @@ core-toolbar, div[drawer] > div:first-child {
#mainToolbar.search {
display: none;
}
-#gameMenu {
- margin-top: -0.5em;
-}
-#drawerToggle, #applySortButton, #cancelSortButton {
- position: relative;
- top: -3px;
-}
#applySortButton {
color: #2196F3;
background-color: white;
@@ -71,55 +56,44 @@ div[drawer] {
div[drawer] > * {
border-right: 1px rgba(0, 0, 0, 0.12) solid;
}
-paper-item > a {
- -webkit-user-drag: none;
- color: inherit;
+div[drawer] > iron-pages {
+ overflow: auto;
}
-paper-item > a:hover {
- text-decoration: none;
-}
-#cardsNav[data-editModeToggle] > loot-plugin-item {
- transition: transform 0.6s;
-}
-#cardsNav > loot-plugin-item {
- margin: 8px 0;
-}
-#contentFilter {
- display: block;
- line-height: initial;
+#cardsNav {
+ padding-top: 8px;
+ padding-bottom: 8px;
}
#filters {
padding: 8px 0;
- overflow: hidden;
+ overflow: auto;
}
#filters > paper-checkbox,
-#filters > core-tooltip,
-#filters > div:not(.divider) {
+#hiddenPlugins,
+#hiddenMessages {
padding: 0 16px;
height: 48px;
line-height: 48px;
}
-#filters > paper-checkbox,
-#filters > core-tooltip {
- display: block;
+#contentFilter {
+ padding: 0 16px;
+ height: 48px;
}
/* Panel */
#main {
- background: #EEE;
+ background-color: #fafafa;
overflow: auto;
position: relative;
z-index: 0;
}
-#main > core-list {
+#main > iron-list {
transform-style: preserve-3d;
+ margin-bottom: 170px;
}
#summary {
- background: white;
+ background-color: white;
margin: 8px;
padding: 16px;
- border-radius: 2px;
- box-shadow: rgba(0, 0, 0, 0.0980392) 0px 2px 4px, rgba(0, 0, 0, 0.0980392) 0px 0px 3px;
}
#summary > h1 {
margin: -16px 0 0;
@@ -132,6 +106,7 @@ paper-item > a:hover {
margin-right: 32px;
width: auto;
background-color: inherit;
+ border-collapse: collapse;
}
#summary > table:last-child {
margin-right: 0;
@@ -167,28 +142,25 @@ li.warn {
/* Dialogs */
#about {
- max-width: 580px;
+ max-width: 550px;
}
#about blockquote {
white-space: pre-line;
}
-#settingsDialog > div > * {
+#settingsDialog > paper-dialog-scrollable > div > * {
margin: 16px;
}
-#settingsDialog > div > div > :first-child {
+#settingsDialog > paper-dialog-scrollable > div > div > :first-child {
margin-right: 16px;
margin-left: 0;
}
-#settingsDialog > div > :last-child {
+#settingsDialog > paper-dialog-scrollable > div > :last-child {
margin-right: 0;
}
-#settingsDialog core-label [for]:last-child {
- margin-left: 16px;
-}
#gameTable {
margin: 32px 0 0;
}
-paper-progress {
+#progressDialog paper-progress {
width: 100%;
}
@@ -199,16 +171,13 @@ paper-progress {
margin-top: -1px;
}
-/* Tables */
-html /deep/ table {
- border-collapse: collapse;
- width: 100%;
+/* Hack for the squashing of icons when text is not wrapped, due to the use of
+ flex display. See */
+paper-icon-item iron-icon {
+ margin-right: 32px;
}
-html /deep/ table td, html /deep/ table th {
- vertical-align: top;
- text-align: left;
- padding: 8px 16px;
-}
-html /deep/ table th {
- border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+
+/* Set icons to use Material Design colouring. */
+iron-icon {
+ color: rgba(0, 0, 0, 0.54);
}
diff --git a/src/gui/html/css/typography.css b/src/gui/html/css/typography.css
index f21c2e57..63bba52b 100644
--- a/src/gui/html/css/typography.css
+++ b/src/gui/html/css/typography.css
@@ -125,80 +125,9 @@ html {
font-weight: 400;
text-rendering: optimizeLegibility;
}
-.display4 {
- font-weight: 300;
- font-size: 8rem;
- color: rgba(0, 0, 0, 0.54);
- /*letter-spacing: -0.01em;*/
-}
-.display3 {
+h1 {
font-weight: 400;
- font-size: 4rem;
- color: rgba(0, 0, 0, 0.54);
- /*letter-spacing: -0.005em;*/
}
-.display2, h1 {
- font-weight: 400;
- font-size: 3.214rem;
- color: rgba(0, 0, 0, 0.54);
- line-height: 1.067;
-}
-.display1 {
- font-weight: 400;
- font-size: 2.429rem;
- color: rgba(0, 0, 0, 0.54);
- line-height: 1.176;
-}
-.headline, h2 {
- font-weight: 400;
- font-size: 1.714rem;
- line-height: 1.333;
-}
-.title, h3 {
- font-weight: 500;
- font-size: 1.429rem;
- /*letter-spacing: 0.005em;*/
-}
-.subhead, h4,
-#summary > h1,
-html /deep/ paper-item {
- font-weight: 400;
+#summary > h1 {
font-size: 1.143rem;
- line-height: 1.75;
- /* Alternate line height: */
- /* line-height: 1.5; */
- /*letter-spacing: 0.01em;*/
-}
-.body2,
-html /deep/ paper-menu-button paper-item,
-html /deep/ paper-menu-button paper-checkbox {
- font-weight: 500;
- font-size: 1rem;
- line-height: 1.714;
- /*letter-spacing: 0.01em;*/
-}
-.body1,
-html /deep/ paper-checkbox,
-paper-toast {
- font-weight: 400;
- font-size: 1rem;
- line-height: 1.429;
- /*letter-spacing: 0.01em;*/
-}
-.caption {
- font-weight: 400;
- font-size: 0.857rem;
- color: rgba(0, 0, 0, 0.54);
- /*letter-spacing: 0.02em;*/
-}
-.button,
-html /deep/ paper-tab,
-html /deep/ paper-button {
- font-weight: 500;
- font-size: 1rem;
- text-transform: uppercase;
- /*letter-spacing: 0.01em;*/
-}
-.display4.dark, .display3.dark, .display2.dark, .display1.dark, .caption.dark {
- color: rgba(255, 255, 255, 0.7);
}
diff --git a/src/gui/html/elements/editable-table.html b/src/gui/html/elements/editable-table.html
index 98c9fbc9..bf7e9948 100644
--- a/src/gui/html/elements/editable-table.html
+++ b/src/gui/html/elements/editable-table.html
@@ -1,340 +1,350 @@
-
-
+
+
+
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ Delete Row
+
+
+
-
-
-
- Note
- Warning
- Error
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Note
+ Warning
+ Error
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete Row
+
+
+
-
-
-
- Add
- Remove
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Add
+ Remove
+
+
+
+
+
+
+
+ Delete Row
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Delete Row
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Delete Row
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete Row
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+ Add New Row
+
+
+
+
-
+
+
+
+
+
diff --git a/src/gui/html/elements/loot-custom-icons.html b/src/gui/html/elements/loot-custom-icons.html
index 88569071..c3eabb4b 100644
--- a/src/gui/html/elements/loot-custom-icons.html
+++ b/src/gui/html/elements/loot-custom-icons.html
@@ -1,15 +1,16 @@
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/html/elements/loot-dialog.html b/src/gui/html/elements/loot-dialog.html
deleted file mode 100644
index b0675b55..00000000
--- a/src/gui/html/elements/loot-dialog.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/gui/html/elements/loot-dropdown-menu.html b/src/gui/html/elements/loot-dropdown-menu.html
index 4eaa925e..e59ac5e5 100644
--- a/src/gui/html/elements/loot-dropdown-menu.html
+++ b/src/gui/html/elements/loot-dropdown-menu.html
@@ -8,58 +8,58 @@ it a -like API. This means:
-->
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/gui/html/elements/loot-menu.html b/src/gui/html/elements/loot-menu.html
new file mode 100644
index 00000000..3d72e608
--- /dev/null
+++ b/src/gui/html/elements/loot-menu.html
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/src/gui/html/elements/loot-message-dialog.html b/src/gui/html/elements/loot-message-dialog.html
index 6cb13536..fb905684 100644
--- a/src/gui/html/elements/loot-message-dialog.html
+++ b/src/gui/html/elements/loot-message-dialog.html
@@ -1,6 +1,6 @@
+
-
+
-
-
+
+
+
+
+
-
+
- OK
- Cancel
-
-
-
-
+
+
diff --git a/src/gui/html/elements/loot-plugin-card.html b/src/gui/html/elements/loot-plugin-card.html
index 01fa3aac..f23ef3b0 100644
--- a/src/gui/html/elements/loot-plugin-card.html
+++ b/src/gui/html/elements/loot-plugin-card.html
@@ -12,408 +12,292 @@ loot-clear-metadata
-->
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ /* Misc Styling. */
+ paper-toolbar {
+ --paper-toolbar-color: var(--loot-primary-text-color);
+ --paper-toolbar-background: inherit;
+ }
+ #content {
+ overflow: hidden;
+ }
+ #title {
+ @apply(--layout-flex);
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ :host-context(body[data-editors]) #editMetadata {
+ color: #9b9b9b;
+ pointer-events: none;
+ }
+ #editMetadata,
+ #copyMetadata,
+ #clearMetadata {
+ cursor: pointer;
+ }
+ #showOnlyConflicts {
+ min-height: 48px;
+ padding: 0 16px;
+ font-weight: 500;
+ --paper-toggle-button-label-spacing: 16px;
+ white-space: nowrap;
+ }
+ [hidden] {
+ display: none;
+ }
+
+
+
+
+ Active Plugin
+
+
+
+
-
-
-
+
+ Polymer({
+ is: 'loot-plugin-card',
+ properties: {
+ data: {
+ notify: true,
+ observer: '_dataChanged',
+ },
+ },
+
+ attached() {
+ this.$.showOnlyConflicts.addEventListener('change', this._onShowOnlyConflicts);
+ this.$.editMetadata.addEventListener('click', this.onShowEditor);
+ this.$.copyMetadata.addEventListener('click', this._onCopyMetadata);
+ this.$.clearMetadata.addEventListener('click', this._onClearMetadata);
+ },
+
+ detached() {
+ this.$.showOnlyConflicts.removeEventListener('change', this._onShowOnlyConflicts);
+ this.$.editMetadata.removeEventListener('click', this.onShowEditor);
+ this.$.copyMetadata.removeEventListener('click', this._onCopyMetadata);
+ this.$.clearMetadata.removeEventListener('click', this._onClearMetadata);
+ },
+
+ _dataChanged(newValue, oldValue) {
+ if (newValue) {
+ /* Initialise the card content data. */
+ this.updateContent(true);
+ this.updateStyling();
+
+ /* Set icons' visibility */
+ this.$.activeTick.hidden = !this.data.isActive;
+ this.$.isMaster.hidden = !this.data.isMaster;
+ this.$.isEmpty.hidden = !this.data.isEmpty;
+ this.$.loadsArchive.hidden = !this.data.loadsArchive;
+
+ /* Also set highlight if the conflict filter is active. */
+ this.classList.toggle('highlight', this.data.isConflictFilterChecked);
+ }
+ },
+
+ _setTagsContent(tags) {
+ if (tags) {
+ const tagsAdded = this.getElementsByClassName('tag add')[0];
+ tagsAdded.textContent = tags.added;
+ tagsAdded.hidden = tags.added.length === 0;
+
+ const tagsRemoved = this.getElementsByClassName('tag remove')[0];
+ tagsRemoved.textContent = tags.removed;
+ tagsRemoved.hidden = tags.removed.length === 0;
+ }
+ },
+
+ _setMessagesContent(messages) {
+ /* First clear any existing messages. */
+ const messageList = this.getElementsByTagName('ul')[0];
+ while (messageList.firstElementChild) {
+ messageList.removeChild(messageList.firstElementChild);
+ }
+
+ if (messages) {
+ /* Now add new messages. */
+ messages.forEach((message) => {
+ const messageListItem = document.createElement('li');
+ messageListItem.className = message.type;
+ // Use the Marked library for Markdown formatting support.
+ messageListItem.innerHTML = window.marked(message.content);
+ messageList.appendChild(messageListItem);
+ });
+ messageList.hidden = messages.length === 0;
+ }
+ },
+
+ updateContent(updateBodyContent) {
+ if (this.data) {
+ const cardContent = this.data.getCardContent(loot.filters);
+
+ this.getElementsByClassName('version')[0].textContent = cardContent.version;
+
+ this.getElementsByClassName('crc')[0].textContent = cardContent.crc;
+
+ if (updateBodyContent) {
+ this._setTagsContent(cardContent.tags);
+ this._setMessagesContent(cardContent.messages);
+
+ /* Notify that this card (may have) changed size. */
+ this.dispatchEvent(new CustomEvent('iron-resize', { bubbles: true }));
+ }
+
+ /* Update the conflict filter toggle state. */
+ this.$.showOnlyConflicts.checked = this.data.isConflictFilterChecked;
+ }
+ },
+
+ updateStyling() {
+ if (this.data) {
+ this.$.hasUserEdits.hidden = !this.data.hasUserEdits;
+
+ /* Set highlight if the plugin is a search result. */
+ this.classList.toggle('search-result', this.data.isSearchResult);
+ }
+ },
+
+ getName() {
+ return this.getElementsByTagName('h1')[0].textContent;
+ },
+
+ onShowEditor() {
+ /* Fire an open event, so that the UI can enter edit mode. */
+ this.dispatchEvent(new CustomEvent('loot-editor-open', {
+ bubbles: true,
+ }));
+ },
+
+ _onShowOnlyConflicts(evt) {
+ evt.target.dispatchEvent(new CustomEvent('loot-filter-conflicts', {
+ detail: evt.currentTarget.checked,
+ bubbles: true,
+ }));
+ },
+
+ _onCopyMetadata(evt) {
+ evt.target.dispatchEvent(new CustomEvent('loot-copy-metadata', {
+ bubbles: true,
+ }));
+ },
+
+ _onClearMetadata(evt) {
+ evt.target.dispatchEvent(new CustomEvent('loot-clear-metadata', {
+ bubbles: true,
+ }));
+ },
+ });
+
+
diff --git a/src/gui/html/elements/loot-plugin-editor.html b/src/gui/html/elements/loot-plugin-editor.html
index 0ff7eb92..ab8f22c4 100644
--- a/src/gui/html/elements/loot-plugin-editor.html
+++ b/src/gui/html/elements/loot-plugin-editor.html
@@ -7,551 +7,440 @@ loot-editor-close
-->
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
{{data.name}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Main
- Load After
- Requirements
- Incompatibilities
- Messages
- Bash Tags
- Dirty Info
- Locations
-
-
-
-
-
- Enable Edits
-
-
-
-
- Global Priority
-
-
-
-
-
-
-
-
- Filename Display Name Condition
-
-
-
-
-
-
-
-
-
- Filename Display Name Condition
-
-
-
-
-
-
-
-
-
- Filename Display Name Condition
-
-
-
-
-
-
-
-
-
- Type Content Condition Language
-
-
-
-
-
-
-
-
-
-
- CRC ITM Count Deleted References Deleted Navmeshes Cleaning Utility
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/gui/html/elements/loot-plugin-item.html b/src/gui/html/elements/loot-plugin-item.html
index cddea96b..9b41ef76 100644
--- a/src/gui/html/elements/loot-plugin-item.html
+++ b/src/gui/html/elements/loot-plugin-item.html
@@ -1,197 +1,128 @@
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
- Has User Metadata
- Editor Is Open
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/gui/html/elements/loot-search-toolbar.html b/src/gui/html/elements/loot-search-toolbar.html
new file mode 100644
index 00000000..e4f73713
--- /dev/null
+++ b/src/gui/html/elements/loot-search-toolbar.html
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[_computeResultNum(_currentResult)]]
+ /
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/html/elements/loot-search.html b/src/gui/html/elements/loot-search.html
deleted file mode 100644
index 5a36f840..00000000
--- a/src/gui/html/elements/loot-search.html
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{currentResult + 1}}/{{numResults}}
-
-
-
-
-
-
-
-
diff --git a/src/gui/html/elements/loot-validated-input.html b/src/gui/html/elements/loot-validated-input.html
deleted file mode 100644
index ec65c9ea..00000000
--- a/src/gui/html/elements/loot-validated-input.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/gui/html/index.html b/src/gui/html/index.html
index a5f92bbf..429dbdd3 100644
--- a/src/gui/html/index.html
+++ b/src/gui/html/index.html
@@ -1,329 +1,471 @@
-
- LOOT
-
-
-
-
-
+
+ LOOT
+
+
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- {{model.name}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Hidden plugins:
-
0 / 0
-
-
-
Hidden messages:
-
0 / 0
-
-
-
+
+
+
+
+
+
+
+
+
+ [[item.name]]
+
+
+
+
+
Hide version numbers
+
Hide CRCs
+
Hide Bash Tags
+
Hide notes
+
Hide 'Do not clean' messages
+
Hide all plugin messages
+
Hide inactive plugins
+
Hide messageless plugins
+
+
Press Enter or click outside the input to set the filter.
+
+
+
+
Hidden plugins:
+
0 / 0
+
+
+
Hidden messages:
+
0 / 0
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Apply
- Cancel
-
-
-
-
-
-
-
-
-
-
-
-
-
- General Information
-
-
-
- Masterlist Revision
- N/A
-
-
- Masterlist Date
- N/A
-
-
-
-
-
-
- Warnings
- 0
-
-
- Errors
- 0
-
-
- Total Messages
- 0
-
-
-
-
-
-
- Active Plugins
- 0
-
-
- Dirty Plugins
- 0
-
-
- Total Plugins
- 0
-
-
-
-
-
-
-
-
- {{model.name}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Calculating the last digit of π...
-
-
-
- Version (build )
- Load order optimisation for Oblivion, Skyrim, Fallout 3, Fallout: New Vegas and Fallout 4.
- http://loot.github.io
- 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 or Bitcoin .
- Copyright © 2012–2016 WrinklyNinja
-
- 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 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 <http://www.gnu.org/licenses/>.
-
- OK
-
-
-
-
-
-
Default Game
-
- Autodetect
-
-
-
-
-
- Language
-
-
-
-
-
-
-
- Enable debug logging
-
-
-
-
- Update masterlist before sorting
-
-
-
-
+
+
+
+ Warnings
+ 0
+
+
+ Errors
+ 0
+
+
+ Total Messages
+ 0
+
+
+
+
+
+
+ Active Plugins
+ 0
+
+
+ Dirty Plugins
+ 0
+
+
+ Total Plugins
+ 0
+
+
+
+
+
+
+
+
+ [[plugin.name]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calculating the last digit of π...
+
-
- This appears to be the first time you have run LOOT v . Here are some tips to help you get started with the interface.
-
- Click buttons to open menus.
- As well as messages, LOOT displays plugin version numbers , CRCs and Bash Tag suggestions for addition and removal .
- CRCs are only displayed after plugins have been loaded, either by conflict filtering, or by sorting.
- Double-click a plugin in the sidebar to quickly open its metadata editor. Multiple metadata editors can be opened at once.
- Plugins can be drag and dropped from the sidebar into editors' "load after, "requirements" and "incompatibility" tables.
- Some features are disabled while there is an editor open, or while there is a sorted load order that has not been applied or discarded.
- Many interface elements have tooltips. If you don't know what something is, try hovering your mouse over it to find out. Otherwise, LOOT's documentation can be accessed through the main menu.
-
+
+ About LOOT
+
+ Version (build )
+ Load order optimisation for Oblivion, Skyrim, Fallout 3, Fallout: New Vegas and Fallout 4.
+ http://loot.github.io
+ 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 or Bitcoin .
+ Copyright © 2012–2015 WrinklyNinja
+
+ 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 any later version.
- 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 or Bitcoin .
- OK
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ 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 <http://www.gnu.org/licenses/>.
+
+
+
+
+
+
+ Settings
+
+
+
+
Default Game
+
+ Autodetect
+
+
+
+
+
Language
+
Language changes will be applied after LOOT is restarted.
+
+
+
+
+
+
Enable debug logging
+
+
The output is logged to the LOOTDebugLog.txt file.
+
+
+
Update masterlist before sorting
+
+
+
+
+
+
+
+
+
+ First-Time Tips
+ This appears to be the first time you have run LOOT v . Here are some tips to help you get started with the interface.
+
+ Click buttons to open menus.
+ As well as messages, LOOT displays plugin version numbers , CRCs and Bash Tag suggestions for addition and removal .
+ CRCs are only displayed after plugins have been loaded, either by conflict filtering, or by sorting.
+ Double-click a plugin in the sidebar to quickly open its metadata editor. Multiple metadata editors can be opened at once.
+ Plugins can be drag and dropped from the sidebar into editors' "load after, "requirements" and "incompatibility" tables.
+ Some features are disabled while there is an editor open, or while there is a sorted load order that has not been applied or discarded.
+ Many interface elements have tooltips. If you don't know what something is, try hovering your mouse over it to find out. Otherwise, LOOT's documentation can be accessed through the main menu.
+
+ 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 or Bitcoin .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/html/js/dialog.js b/src/gui/html/js/dialog.js
index e83f2243..0115910e 100644
--- a/src/gui/html/js/dialog.js
+++ b/src/gui/html/js/dialog.js
@@ -14,29 +14,32 @@
const progressDialog = document.getElementById('progressDialog');
progressDialog.getElementsByTagName('p')[0].textContent = text;
if (!progressDialog.opened) {
- progressDialog.showModal();
+ progressDialog.open();
+ } else {
+ progressDialog.refit();
}
}
static closeProgress() {
const progressDialog = document.getElementById('progressDialog');
if (progressDialog.opened) {
+ progressDialog.refit();
progressDialog.close();
}
}
static showMessage(title, text) {
const dialog = document.createElement('loot-message-dialog');
+ document.body.appendChild(dialog);
dialog.setDismissable(false);
dialog.showModal(title, text);
- document.body.appendChild(dialog);
}
static askQuestion(title, text, confirmText, closeCallback) {
const dialog = document.createElement('loot-message-dialog');
+ document.body.appendChild(dialog);
dialog.setConfirmText(confirmText);
dialog.showModal(title, text, closeCallback);
- document.body.appendChild(dialog);
}
static showNotification(text) {
diff --git a/src/gui/html/js/dom.js b/src/gui/html/js/dom.js
index 5f8c2475..291c3b10 100644
--- a/src/gui/html/js/dom.js
+++ b/src/gui/html/js/dom.js
@@ -52,28 +52,40 @@
}
},
- updateSettingsDialog(settings, installedGames, gameFolder) {
- const gameSelect = document.getElementById('defaultGameSelect');
+ createGameItem(game) {
+ const menuItem = document.createElement('paper-item');
+ menuItem.setAttribute('value', game.folder);
+ menuItem.textContent = game.name;
+
+ return menuItem;
+ },
+
+ setGameMenuItems(games) {
const gameMenu = document.getElementById('gameMenu');
+
+ /* First make sure game listing elements don't have any existing entries. */
+ while (gameMenu.firstElementChild) {
+ gameMenu.removeChild(gameMenu.firstElementChild);
+ }
+
+ games.forEach((game) => {
+ gameMenu.appendChild(this.createGameItem(game));
+ });
+ },
+
+ updateSettingsDialog(settings) {
+ const gameSelect = document.getElementById('defaultGameSelect');
const 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.removeChild(gameMenu.firstElementChild);
- }
gameTable.clear();
/* Now fill with new values. */
settings.games.forEach((game) => {
- const 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));
+ gameSelect.appendChild(this.createGameItem(game));
const row = gameTable.addRow(game);
gameTable.setReadOnly(row, ['name', 'folder', 'type']);
@@ -83,9 +95,6 @@
document.getElementById('languageSelect').value = settings.language;
document.getElementById('enableDebugLogging').checked = settings.enableDebugLogging;
document.getElementById('updateMasterlist').checked = settings.updateMasterlist;
-
- this.updateEnabledGames(installedGames);
- this.updateSelectedGame(gameFolder);
},
};
}));
diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js
index 51d9f319..99577e1d 100644
--- a/src/gui/html/js/events.js
+++ b/src/gui/html/js/events.js
@@ -14,11 +14,9 @@ function onJumpToGeneralInfo() {
document.getElementById('main').scrollTop = 0;
}
function onChangeGame(evt) {
- /* Check that the selected game isn't the current one. */
- if (!evt.detail.isSelected) {
+ if (evt.detail.item.getAttribute('value') === loot.game.folder) {
return;
}
-
/* Send off a CEF query with the folder name of the new game. */
loot.Dialog.showProgress(loot.l10n.translate('Loading game data...'));
loot.query('changeGame', evt.detail.item.getAttribute('value')).then((result) => {
@@ -40,8 +38,8 @@ function onChangeGame(evt) {
loot.game = new loot.Game(gameInfo, loot.l10n);
/* Reset virtual list positions. */
- document.getElementById('cardsNav').scrollToItem(0);
- document.getElementById('main').lastElementChild.scrollToItem(0);
+ document.getElementById('cardsNav').scrollToIndex(0);
+ document.getElementById('pluginCardList').scrollToIndex(0);
/* Now update virtual lists. */
filterPluginData(loot.game.plugins, loot.filters);
@@ -70,8 +68,6 @@ function updateMasterlistNoProgress() {
existingPlugin.tags = resultPlugin.tags;
}
});
- /* Hack to stop cards overlapping. */
- document.getElementById('main').lastElementChild.updateSize();
loot.Dialog.showNotification(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision));
} else {
@@ -326,7 +322,7 @@ function onOpenLogLocation() {
loot.query('openLogLocation').catch(handlePromiseError);
}
function onShowAboutDialog() {
- document.getElementById('about').showModal();
+ document.getElementById('about').open();
}
function handleUnappliedChangesClose(change) {
loot.Dialog.askQuestion('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), (result) => {
@@ -350,17 +346,19 @@ function onQuit() {
}
function onSwitchSidebarTab(evt) {
- if (evt.detail.isSelected) {
- document.getElementById(evt.target.selected).parentElement.selected = evt.target.selected;
- }
+ document.getElementById(evt.target.selected).parentElement.selected = evt.target.selected;
}
function onSidebarClick(evt) {
if (evt.target.hasAttribute('data-index')) {
- document.getElementById('main').lastElementChild.scrollToItem(evt.target.getAttribute('data-index'));
+ const index = parseInt(evt.target.getAttribute('data-index'), 10);
+ document.getElementById('pluginCardList').scrollToIndex(index);
if (evt.type === 'dblclick') {
- const card = document.getElementById(evt.target.getAttribute('data-id'));
- if (!card.classList.contains('flip')) {
+ /* Double-clicking can select the item's text, clear the selection in
+ case that has happened. */
+ window.getSelection().removeAllRanges();
+
+ if (!document.body.hasAttribute('data-editors')) {
document.getElementById(evt.target.getAttribute('data-id')).onShowEditor();
}
}
@@ -368,51 +366,60 @@ function onSidebarClick(evt) {
}
function areSettingsValid() {
- /* Validate inputs individually. */
- const inputs = document.getElementById('settingsDialog').getElementsByTagName('loot-validated-input');
- for (let i = 0; i < inputs.length; ++i) {
- if (!inputs[i].checkValidity()) {
- return false;
- }
+ return document.getElementById('gameTable').validate();
+}
+function onApplySettings(evt) {
+ if (!areSettingsValid()) {
+ evt.stopPropagation();
}
- return true;
}
function onCloseSettingsDialog(evt) {
- if (evt.target.classList.contains('accept')) {
- if (!areSettingsValid()) {
- return;
- }
-
- /* Update the JS variable values. */
- const 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. */
- loot.query('closeSettings', settings).then(JSON.parse).then((installedGames) => {
- loot.installedGames = installedGames;
- loot.dom.updateEnabledGames(installedGames);
- }).catch(handlePromiseError).then(() => {
- loot.settings = settings;
- loot.dom.updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder);
- }).catch(handlePromiseError);
- } else {
- /* Re-apply the existing settings to the settings dialog elements. */
- loot.dom.updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder);
+ if (evt.target.id !== 'settingsDialog') {
+ /* The event can be fired by dropdowns in the settings dialog, so ignore
+ any events that don't come from the dialog itself. */
+ return;
}
- evt.target.parentElement.close();
+ if (!evt.detail.confirmed) {
+ /* Re-apply the existing settings to the settings dialog elements. */
+ loot.dom.updateSettingsDialog(loot.settings);
+ return;
+ }
+
+ /* Update the JS variable values. */
+ const 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. */
+ loot.query('closeSettings', settings).then(JSON.parse).then((installedGames) => {
+ loot.installedGames = installedGames;
+ loot.dom.updateEnabledGames(installedGames);
+ }).catch(handlePromiseError).then(() => {
+ loot.settings = settings;
+ loot.dom.updateSettingsDialog(loot.settings);
+ loot.dom.setGameMenuItems(loot.settings.games);
+ loot.dom.updateEnabledGames(loot.installedGames);
+ loot.dom.updateSelectedGame(loot.game.folder);
+ }).catch(handlePromiseError);
}
function onShowSettingsDialog() {
- document.getElementById('settingsDialog').showModal();
+ document.getElementById('settingsDialog').open();
}
function onEditorOpen(evt) {
+ /* Set the editor data. */
+ document.getElementById('editor').setEditorData(evt.target.data);
+
+ /* Set body attribute so that sidebar items are styled correctly. */
+ document.body.setAttribute('data-editors', true);
+ document.getElementById('cardsNav').notifyResize();
+
/* Set up drag 'n' drop event handlers. */
const elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item');
for (let i = 0; i < elements.length; ++i) {
@@ -420,31 +427,14 @@ function onEditorOpen(evt) {
elements[i].addEventListener('dragstart', elements[i].onDragStart);
}
- /* 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. */
- let 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();
+ /* 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', '');
return loot.query('editorOpened').catch(handlePromiseError);
}
@@ -455,16 +445,19 @@ function onEditorClose(evt) {
/* 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. */
- const edits = evt.target.readFromEditor(evt.target.data);
+ const plugin = loot.game.plugins.find((item) => {
+ return item.name === evt.target.querySelector('h1').textContent;
+ });
+ const edits = evt.target.readFromEditor(plugin);
promise = loot.query('editorClosed', edits).then(JSON.parse).then((result) => {
if (result) {
- evt.target.data.priority = result.priority;
- evt.target.data.isPriorityGlobal = result.isPriorityGlobal;
- evt.target.data.messages = result.messages;
- evt.target.data.tags = result.tags;
- evt.target.data.isDirty = result.isDirty;
+ plugin.priority = result.priority;
+ plugin.isPriorityGlobal = result.isPriorityGlobal;
+ plugin.messages = result.messages;
+ plugin.tags = result.tags;
+ plugin.isDirty = result.isDirty;
- evt.target.data.userlist = edits.userlist;
+ plugin.userlist = edits.userlist;
/* Now perform search again. If there is no current search, this won't
do anything. */
@@ -477,11 +470,9 @@ function onEditorClose(evt) {
promise = loot.query('editorClosed');
}
promise.then(() => {
- delete evt.target.data.editor;
-
- /* Now hide editor. */
- evt.target.classList.toggle('flip');
- evt.target.data.isEditorOpen = false;
+ /* Remove body attribute so that sidebar items are styled correctly. */
+ document.body.removeAttribute('data-editors');
+ document.getElementById('cardsNav').notifyResize();
/* Remove drag 'n' drop event handlers. */
const elements = document.getElementById('cardsNav').getElementsByTagName('loot-plugin-item');
@@ -490,27 +481,14 @@ function onEditorClose(evt) {
elements[i].removeEventListener('dragstart', elements[i].onDragStart);
}
- /* Disable priority hover in plugins list and enable header
- buttons if this is the only editor instance. */
- let 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();
+ /* 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');
}).catch(handlePromiseError);
}
function undoConflictsFilter() {
@@ -588,6 +566,34 @@ function onSearchOpen() {
document.getElementById('mainToolbar').classList.add('search');
document.getElementById('searchBar').focusInput();
}
-function onSearchClose() {
+function onSearchBegin(evt) {
+ loot.game.plugins.forEach((plugin) => {
+ plugin.isSearchResult = false;
+ });
+
+ if (!evt.detail.needle) {
+ return;
+ }
+
+ // Don't push to the target's results property directly, as the
+ // change observer doesn't work correctly unless special Polymer APIs
+ // are used, which I don't want to get into.
+ const results = [];
+ loot.game.plugins.forEach((plugin, index) => {
+ if (plugin.getCardContent(loot.filters).containsText(evt.detail.needle)) {
+ results.push(index);
+ plugin.isSearchResult = true;
+ }
+ });
+
+ evt.target.results = results;
+}
+function onSearchChangeSelection(evt) {
+ document.getElementById('pluginCardList').scrollToIndex(evt.detail.selection);
+}
+function onSearchEnd(evt) {
+ loot.game.plugins.forEach((plugin) => {
+ plugin.isSearchResult = false;
+ });
document.getElementById('mainToolbar').classList.remove('search');
}
diff --git a/src/gui/html/js/helpers.js b/src/gui/html/js/helpers.js
index 08db0c61..04ebc1d6 100644
--- a/src/gui/html/js/helpers.js
+++ b/src/gui/html/js/helpers.js
@@ -44,17 +44,16 @@ function filterPluginData(plugins, filters) {
filters.conflictingPluginNames = conflictingPluginNames;
return plugins.filter(filters.pluginFilter, filters);
}).then((filteredPlugins) => {
- document.getElementById('cardsNav').data = filteredPlugins;
- document.getElementById('pluginCardList').data = filteredPlugins;
+ document.getElementById('cardsNav').items = filteredPlugins;
+ document.getElementById('pluginCardList').items = filteredPlugins;
const pluginCards = document.getElementById('pluginCardList').children;
for (let i = 0; i < pluginCards.length; ++i) {
if (pluginCards[i].data) {
- pluginCards[i].updateContent();
+ pluginCards[i].updateContent(true);
}
}
- document.getElementById('cardsNav').updateSize();
- document.getElementById('pluginCardList').updateSize();
+ document.getElementById('cardsNav').notifyResize();
/* Now perform search again. If there is no current search, this won't
do anything. */
diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js
index ef0aba54..598bfd8e 100644
--- a/src/gui/html/js/init.js
+++ b/src/gui/html/js/init.js
@@ -65,23 +65,25 @@
document.getElementById('helpButton').addEventListener('click', onOpenReadme);
document.getElementById('aboutButton').addEventListener('click', onShowAboutDialog);
document.getElementById('quitButton').addEventListener('click', onQuit);
- document.getElementById('gameMenu').addEventListener('core-select', onChangeGame);
+ document.getElementById('gameMenu').addEventListener('iron-select', onChangeGame);
document.getElementById('updateMasterlistButton').addEventListener('click', onUpdateMasterlist);
document.getElementById('sortButton').addEventListener('click', onSortPlugins);
document.getElementById('applySortButton').addEventListener('click', onApplySort);
document.getElementById('cancelSortButton').addEventListener('click', onCancelSort);
- document.getElementById('sidebarTabs').addEventListener('core-select', onSwitchSidebarTab);
+ document.getElementById('sidebarTabs').addEventListener('iron-select', onSwitchSidebarTab);
document.getElementById('jumpToGeneralInfo').addEventListener('click', onJumpToGeneralInfo);
/* Set up search event handlers. */
document.getElementById('showSearch').addEventListener('click', onSearchOpen);
- document.getElementById('searchBar').addEventListener('loot-search-close', onSearchClose);
+ document.getElementById('searchBar').addEventListener('loot-search-begin', onSearchBegin);
+ document.getElementById('searchBar').addEventListener('loot-search-change-selection', onSearchChangeSelection, false);
+ document.getElementById('searchBar').addEventListener('loot-search-end', onSearchEnd);
window.addEventListener('keyup', onFocusSearch);
/* Set up event handlers for settings dialog. */
const settings = document.getElementById('settingsDialog');
- settings.getElementsByClassName('accept')[0].addEventListener('click', onCloseSettingsDialog);
- settings.getElementsByClassName('cancel')[0].addEventListener('click', onCloseSettingsDialog);
+ settings.addEventListener('iron-overlay-closed', onCloseSettingsDialog);
+ settings.querySelector('[dialog-confirm]').addEventListener('tap', onApplySettings);
/* Set up handler for opening and closing editors. */
document.body.addEventListener('loot-editor-open', onEditorOpen);
@@ -92,9 +94,13 @@
document.getElementById('cardsNav').addEventListener('click', onSidebarClick);
document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick);
- /* Set up handler for plugin message and dirty info changes. */
+ /* Set up handler for plugin data changes. */
document.addEventListener('loot-plugin-message-change', Plugin.onMessageChange);
+ document.addEventListener('loot-plugin-message-change', Plugin.onContentChange);
document.addEventListener('loot-plugin-isdirty-change', Plugin.onIsDirtyChange);
+ document.addEventListener('loot-plugin-card-content-change', Plugin.onContentChange);
+ document.addEventListener('loot-plugin-card-styling-change', Plugin.onCardStylingChange);
+ document.addEventListener('loot-plugin-item-content-change', Plugin.onItemContentChange);
/* Set up event handlers for game member variable changes. */
document.addEventListener('loot-game-folder-change', Game.onFolderChange);
@@ -156,7 +162,6 @@
result.forEach((language) => {
const settingsItem = document.createElement('paper-item');
settingsItem.setAttribute('value', language.locale);
- settingsItem.setAttribute('noink', '');
settingsItem.textContent = language.name;
settingsLangSelect.appendChild(settingsItem);
messageLangSelect.appendChild(settingsItem.cloneNode(true));
@@ -196,7 +201,6 @@
result.forEach((gameType) => {
const item = document.createElement('paper-item');
item.setAttribute('value', gameType);
- item.setAttribute('noink', '');
item.textContent = gameType;
select.appendChild(item);
});
@@ -214,7 +218,10 @@
function setSettings(appData) {
return query('getSettings').then(JSON.parse).then((result) => {
appData.settings = result;
- dom.updateSettingsDialog(appData.settings, appData.installedGames, appData.game.folder);
+ dom.updateSettingsDialog(appData.settings);
+ loot.dom.setGameMenuItems(appData.settings.games);
+ loot.dom.updateEnabledGames(appData.installedGames);
+ loot.dom.updateSelectedGame(appData.game.folder);
});
}
@@ -222,8 +229,8 @@
return query('getGameData').then((result) => {
const game = JSON.parse(result, Plugin.fromJson);
appData.game = new Game(game, appData.l10n);
- document.getElementById('cardsNav').data = appData.game.plugins;
- document.getElementById('main').lastElementChild.data = appData.game.plugins;
+ document.getElementById('cardsNav').items = appData.game.plugins;
+ document.getElementById('pluginCardList').items = appData.game.plugins;
applyEnabledFilters(appData.filters, appData.settings, appData.game.plugins);
Dialog.closeProgress();
});
@@ -231,8 +238,6 @@
return () => {
Dialog.showProgress('Initialising user interface...');
- /* Set the plugin list's scroll target to its parent. */
- document.getElementById('pluginCardList').scrollTarget = document.getElementById('main');
/* Make sure settings are what I want. */
window.marked.setOptions({
@@ -262,7 +267,10 @@
loot.filters = new Filters(loot.l10n);
translateStaticText(loot.l10n);
/* Also need to update the settings UI. */
- dom.updateSettingsDialog(loot.settings, loot.installedGames, loot.game.folder);
+ dom.updateSettingsDialog(loot.settings);
+ loot.dom.setGameMenuItems(loot.settings.games);
+ loot.dom.updateEnabledGames(loot.installedGames);
+ loot.dom.updateSelectedGame(loot.game.folder);
}).then(() => {
return displayInitErrors();
}).then((result) => {
@@ -274,10 +282,8 @@
return setGameData(loot);
}).then(() => {
if (!loot.settings.lastVersion || loot.settings.lastVersion !== loot.version) {
- document.getElementById('firstRun').showModal();
+ document.getElementById('firstRun').open();
}
}).catch(handlePromiseError);
};
}));
-
-window.addEventListener('polymer-ready', loot.initialise);
diff --git a/src/gui/html/js/l10n.js b/src/gui/html/js/l10n.js
index ca3ff8f2..fa0bb4c6 100644
--- a/src/gui/html/js/l10n.js
+++ b/src/gui/html/js/l10n.js
@@ -12,20 +12,15 @@
}(this, () => {
function translatePluginCardTemplate(l10n) {
/* Plugin card template. */
- let pluginCard = document.querySelector('link[rel="import"][href$="loot-plugin-card.html"]');
- if (pluginCard) {
- pluginCard = pluginCard.import.querySelector('template').content;
- } else {
- pluginCard = document.querySelector('polymer-element[name="loot-plugin-card"]').querySelector('template').content;
- }
+ const pluginCard = document.getElementById('loot-plugin-card').querySelector('template').content;
- pluginCard.getElementById('activeTick').setAttribute('label', l10n.translate('Active Plugin'));
- pluginCard.getElementById('isMaster').setAttribute('label', l10n.translate('Master File'));
- pluginCard.getElementById('emptyPlugin').setAttribute('label', l10n.translate('Empty Plugin'));
- pluginCard.getElementById('loadsArchive').setAttribute('label', l10n.translate('Loads Archive'));
- pluginCard.getElementById('hasUserEdits').setAttribute('label', l10n.translate('Has User Metadata'));
+ pluginCard.querySelector('paper-tooltip[for=activeTick]').textContent = l10n.translate('Active Plugin');
+ pluginCard.querySelector('paper-tooltip[for=isMaster]').textContent = l10n.translate('Master File');
+ pluginCard.querySelector('paper-tooltip[for=isEmpty]').textContent = l10n.translate('Empty Plugin');
+ pluginCard.querySelector('paper-tooltip[for=loadsArchive]').textContent = l10n.translate('Loads Archive');
+ pluginCard.querySelector('paper-tooltip[for=hasUserEdits]').textContent = l10n.translate('Has User Metadata');
- pluginCard.getElementById('showOnlyConflicts').previousElementSibling.textContent = l10n.translate('Show Only Conflicts');
+ pluginCard.getElementById('showOnlyConflicts').textContent = l10n.translate('Show Only Conflicts');
pluginCard.getElementById('editMetadata').lastChild.textContent = l10n.translate('Edit Metadata');
pluginCard.getElementById('copyMetadata').lastChild.textContent = l10n.translate('Copy Metadata');
pluginCard.getElementById('clearMetadata').lastChild.textContent = l10n.translate('Clear User Metadata');
@@ -33,35 +28,25 @@
function translatePluginEditorTemplate(l10n) {
/* Plugin editor template. */
- let pluginEditor = document.querySelector('link[rel="import"][href$="loot-plugin-card.html"]');
- if (pluginEditor) {
- pluginEditor = pluginEditor.import.querySelector('link[rel="import"][href$="loot-plugin-editor.html"]').import.querySelector('template').content;
- } else {
- pluginEditor = document.querySelector('polymer-element[name="loot-plugin-editor"]').querySelector('template').content;
- }
-
- pluginEditor.getElementById('activeTick').setAttribute('label', l10n.translate('Active Plugin'));
- pluginEditor.getElementById('isMaster').setAttribute('label', l10n.translate('Master File'));
- pluginEditor.getElementById('emptyPlugin').setAttribute('label', l10n.translate('Empty Plugin'));
- pluginEditor.getElementById('loadsArchive').setAttribute('label', l10n.translate('Loads Archive'));
+ const pluginEditor = document.getElementById('loot-plugin-editor').querySelector('template').content;
pluginEditor.getElementById('enableEdits').previousElementSibling.textContent = l10n.translate('Enable Edits');
- pluginEditor.getElementById('globalPriority').parentElement.parentElement.setAttribute('label', l10n.translate('Global priorities are compared against all other plugins. Normal priorities are compared against only conflicting plugins.'));
- pluginEditor.getElementById('globalPriority').previousElementSibling.textContent = l10n.translate('Global Priority');
- pluginEditor.getElementById('priorityValue').parentElement.previousElementSibling.textContent = l10n.translate('Priority Value');
+ pluginEditor.getElementById('isPriorityGlobal').previousElementSibling.textContent = l10n.translate('Global Priority');
+ pluginEditor.querySelector('paper-tooltip[for=isPriorityGlobal]').textContent = l10n.translate('Global priorities are compared against all other plugins. Normal priorities are compared against only conflicting plugins.');
+ pluginEditor.getElementById('priorityValue').previousElementSibling.textContent = l10n.translate('Priority Value');
pluginEditor.getElementById('tableTabs').querySelector('[data-for=main]').textContent = l10n.translate('Main');
- pluginEditor.getElementById('tableTabs').querySelector('[data-for=loadAfter]').textContent = l10n.translate('Load After');
+ pluginEditor.getElementById('tableTabs').querySelector('[data-for=after]').textContent = l10n.translate('Load After');
pluginEditor.getElementById('tableTabs').querySelector('[data-for=req]').textContent = l10n.translate('Requirements');
pluginEditor.getElementById('tableTabs').querySelector('[data-for=inc]').textContent = l10n.translate('Incompatibilities');
pluginEditor.getElementById('tableTabs').querySelector('[data-for=message]').textContent = l10n.translate('Messages');
pluginEditor.getElementById('tableTabs').querySelector('[data-for=tags]').textContent = l10n.translate('Bash Tags');
pluginEditor.getElementById('tableTabs').querySelector('[data-for=dirty]').textContent = l10n.translate('Dirty Info');
- pluginEditor.getElementById('tableTabs').querySelector('[data-for=locations]').textContent = l10n.translate('Locations');
+ pluginEditor.getElementById('tableTabs').querySelector('[data-for=url]').textContent = l10n.translate('Locations');
- pluginEditor.getElementById('loadAfter').querySelector('th:first-child').textContent = l10n.translate('Filename');
- pluginEditor.getElementById('loadAfter').querySelector('th:nth-child(2)').textContent = l10n.translate('Display Name');
- pluginEditor.getElementById('loadAfter').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition');
+ pluginEditor.getElementById('after').querySelector('th:first-child').textContent = l10n.translate('Filename');
+ pluginEditor.getElementById('after').querySelector('th:nth-child(2)').textContent = l10n.translate('Display Name');
+ pluginEditor.getElementById('after').querySelector('th:nth-child(3)').textContent = l10n.translate('Condition');
pluginEditor.getElementById('req').querySelector('th:first-child').textContent = l10n.translate('Filename');
pluginEditor.getElementById('req').querySelector('th:nth-child(2)').textContent = l10n.translate('Display Name');
@@ -86,152 +71,115 @@
pluginEditor.getElementById('dirty').querySelector('th:nth-child(4)').textContent = l10n.translate('Deleted Navmeshes');
pluginEditor.getElementById('dirty').querySelector('th:nth-child(5)').textContent = l10n.translate('Cleaning Utility');
- pluginEditor.getElementById('locations').querySelector('th:first-child').textContent = l10n.translate('URL');
- pluginEditor.getElementById('locations').querySelector('th:nth-child(2)').textContent = l10n.translate('Name');
+ pluginEditor.getElementById('url').querySelector('th:first-child').textContent = l10n.translate('URL');
+ pluginEditor.getElementById('url').querySelector('th:nth-child(2)').textContent = l10n.translate('Name');
- pluginEditor.getElementById('accept').parentElement.setAttribute('label', l10n.translate('Apply'));
- pluginEditor.getElementById('cancel').parentElement.setAttribute('label', l10n.translate('Cancel'));
+ pluginEditor.querySelector('paper-tooltip[for=accept]').textContent = l10n.translate('Save Metadata');
+ pluginEditor.querySelector('paper-tooltip[for=cancel]').textContent = l10n.translate('Cancel');
}
function translatePluginListItemTemplate(l10n) {
/* Plugin List Item Template */
- let pluginItem = document.querySelector('link[rel="import"][href$="loot-plugin-item.html"]');
- if (pluginItem) {
- pluginItem = pluginItem.import.querySelector('template').content;
- } else {
- pluginItem = document.querySelector('polymer-element[name="loot-plugin-item"]').querySelector('template').content;
- }
- pluginItem.querySelector('#secondary core-tooltip').setAttribute('label', l10n.translate('Global Priority'));
- pluginItem.getElementById('hasUserEditsTooltip').textContent = l10n.translate('Has User Metadata');
- pluginItem.getElementById('editorIsOpenTooltip').textContent = l10n.translate('Editor Is Open');
+ const pluginItem = document.getElementById('loot-plugin-item').querySelector('template').content;
+
+ pluginItem.querySelector('paper-tooltip[for=globalPriorityIcon]').textContent = l10n.translate('Global Priority');
+ pluginItem.querySelector('paper-tooltip[for=hasUserEdits]').textContent = l10n.translate('Has User Metadata');
}
function translateMessageDialogTemplate(l10n) {
/* Plugin List Item Template */
- let messageDialog = document.querySelector('link[rel="import"][href$="loot-message-dialog.html"]');
- if (messageDialog) {
- messageDialog = messageDialog.import.querySelector('template').content;
- } else {
- messageDialog = document.querySelector('polymer-element[name="loot-message-dialog"]').querySelector('template').content;
- }
+ const messageDialog = document.getElementById('loot-message-dialog').querySelector('template').content;
+
messageDialog.getElementById('confirm').textContent = l10n.translate('OK');
messageDialog.getElementById('dismiss').textContent = l10n.translate('Cancel');
}
function translateFileRowTemplate(l10n) {
/* File row template */
- let fileRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (fileRow) {
- fileRow = fileRow.import.querySelector('#fileRow').content;
- } else {
- fileRow = document.querySelector('#fileRow').content;
- }
- fileRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A filename is required.'));
- fileRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row'));
+ const fileRow = document.getElementById('fileRow').content;
+
+ fileRow.querySelector('.name').setAttribute('error-message', l10n.translate('A filename is required.'));
+ fileRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
}
function translateMessageRowTemplate(l10n) {
/* Message row template */
- let messageRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (messageRow) {
- messageRow = messageRow.import.querySelector('#messageRow').content;
- } else {
- messageRow = document.querySelector('#messageRow').content;
- }
+ const messageRow = document.getElementById('messageRow').content;
+
messageRow.querySelector('.type').children[0].textContent = l10n.translate('Note');
messageRow.querySelector('.type').children[1].textContent = l10n.translate('Warning');
messageRow.querySelector('.type').children[2].textContent = l10n.translate('Error');
- messageRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A content string is required.'));
- messageRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row'));
+ messageRow.querySelector('.content').setAttribute('error-message', l10n.translate('A content string is required.'));
+ messageRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
}
function translateTagRowTemplate(l10n) {
/* Tag row template */
- let tagRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (tagRow) {
- tagRow = tagRow.import.querySelector('#tagRow').content;
- } else {
- tagRow = document.querySelector('#tagRow').content;
- }
+ const tagRow = document.getElementById('tagRow').content;
+
tagRow.querySelector('.type').children[0].textContent = l10n.translate('Add');
tagRow.querySelector('.type').children[1].textContent = l10n.translate('Remove');
- tagRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A name is required.'));
- tagRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row'));
+ tagRow.querySelector('.name').setAttribute('error-message', l10n.translate('A name is required.'));
+ tagRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
}
function translateDirtyInfoRowTemplate(l10n) {
/* Dirty Info row template */
- let dirtyInfoRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (dirtyInfoRow) {
- dirtyInfoRow = dirtyInfoRow.import.querySelector('#dirtyInfoRow').content;
- } else {
- dirtyInfoRow = document.querySelector('#dirtyInfoRow').content;
- }
+ const dirtyInfoRow = document.getElementById('dirtyInfoRow').content;
- dirtyInfoRow.querySelector('loot-validated-input.crc').setAttribute('error', l10n.translate('A CRC is required.'));
- dirtyInfoRow.querySelector('loot-validated-input.itm').setAttribute('error', l10n.translate('Values must be integers.'));
- dirtyInfoRow.querySelector('loot-validated-input.udr').setAttribute('error', l10n.translate('Values must be integers.'));
- dirtyInfoRow.querySelector('loot-validated-input.nav').setAttribute('error', l10n.translate('Values must be integers.'));
- dirtyInfoRow.querySelector('loot-validated-input.util').setAttribute('error', l10n.translate('A utility name is required.'));
- dirtyInfoRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row'));
+ dirtyInfoRow.querySelector('.crc').setAttribute('error-message', l10n.translate('A CRC is required.'));
+ dirtyInfoRow.querySelector('.itm').setAttribute('error-message', l10n.translate('Values must be integers.'));
+ dirtyInfoRow.querySelector('.udr').setAttribute('error-message', l10n.translate('Values must be integers.'));
+ dirtyInfoRow.querySelector('.nav').setAttribute('error-message', l10n.translate('Values must be integers.'));
+ dirtyInfoRow.querySelector('.util').setAttribute('error-message', l10n.translate('A utility name is required.'));
+ dirtyInfoRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
}
function translateLocationRowTemplate(l10n) {
/* Location row template */
- let locationRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (locationRow) {
- locationRow = locationRow.import.querySelector('#locationRow').content;
- } else {
- locationRow = document.querySelector('#locationRow').content;
- }
- locationRow.querySelector('loot-validated-input').setAttribute('error', l10n.translate('A link is required.'));
- locationRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row'));
+ const locationRow = document.getElementById('locationRow').content;
+
+ locationRow.querySelector('.link').setAttribute('error-message', l10n.translate('A link is required.'));
+ locationRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
}
function translateGameRowTemplate(l10n) {
/* Game row template */
- let gameRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (gameRow) {
- gameRow = gameRow.import.querySelector('#gameRow').content;
- } else {
- gameRow = document.querySelector('#gameRow').content;
- }
- gameRow.querySelector('loot-validated-input.name').setAttribute('error', l10n.translate('A name is required.'));
- gameRow.querySelector('loot-validated-input.folder').setAttribute('error', l10n.translate('A folder is required.'));
- gameRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Delete Row'));
+ const gameRow = document.getElementById('gameRow').content;
+
+ gameRow.querySelector('.name').setAttribute('error-message', l10n.translate('A name is required.'));
+ gameRow.querySelector('.folder').setAttribute('error-message', l10n.translate('A folder is required.'));
+ gameRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
}
function translateNewRowTemplate(l10n) {
/* New row template */
- let newRow = document.querySelector('link[rel="import"][href$="editable-table.html"]');
- if (newRow) {
- newRow = newRow.import.querySelector('#newRow').content;
- } else {
- newRow = document.querySelector('#newRow').content;
- }
- newRow.querySelector('core-tooltip').setAttribute('label', l10n.translate('Add New Row'));
+ const newRow = document.getElementById('newRow').content;
+
+ newRow.querySelector('paper-tooltip').textContent = l10n.translate('Add New Row');
}
function translateMainToolbar(l10n) {
/* Main toolbar */
- document.getElementById('jumpToGeneralInfo').parentElement.label = l10n.translate('Jump To General Information');
- document.getElementById('sortButton').parentElement.label = l10n.translate('Sort Plugins');
- document.getElementById('updateMasterlistButton').parentElement.label = l10n.translate('Update Masterlist');
+ const mainToolbar = document.getElementById('mainToolbar');
+ mainToolbar.querySelector('paper-tooltip[for=jumpToGeneralInfo]').textContent = l10n.translate('Jump To General Information');
+ mainToolbar.querySelector('paper-tooltip[for=sortButton]').textContent = l10n.translate('Sort Plugins');
+ mainToolbar.querySelector('paper-tooltip[for=updateMasterlistButton]').textContent = l10n.translate('Update Masterlist');
document.getElementById('applySortButton').textContent = l10n.translate('Apply');
document.getElementById('cancelSortButton').textContent = l10n.translate('Cancel');
- document.getElementById('showSearch').parentElement.label = l10n.translate('Search Cards');
+ mainToolbar.querySelector('paper-tooltip[for=showSearch]').textContent = l10n.translate('Search Cards');
/* Toolbar menu */
- document.getElementById('redatePluginsButton').lastElementChild.textContent = l10n.translate('Redate Plugins');
- document.getElementById('openLogButton').lastElementChild.textContent = l10n.translate('Open Debug Log Location');
- document.getElementById('wipeUserlistButton').lastElementChild.textContent = l10n.translate('Clear All User Metadata');
- document.getElementById('copyLoadOrderButton').lastElementChild.textContent = l10n.translate('Copy Load Order');
- document.getElementById('copyContentButton').lastElementChild.textContent = l10n.translate('Copy Content');
- document.getElementById('refreshContentButton').lastElementChild.textContent = l10n.translate('Refresh Content');
- document.getElementById('helpButton').lastElementChild.textContent = l10n.translate('View Documentation');
- document.getElementById('aboutButton').lastElementChild.textContent = l10n.translate('About');
- document.getElementById('settingsButton').lastElementChild.textContent = l10n.translate('Settings');
- document.getElementById('quitButton').lastElementChild.textContent = l10n.translate('Quit');
+ document.getElementById('redatePluginsButton').lastChild.textContent = l10n.translate('Redate Plugins');
+ document.getElementById('openLogButton').lastChild.textContent = l10n.translate('Open Debug Log Location');
+ document.getElementById('wipeUserlistButton').lastChild.textContent = l10n.translate('Clear All User Metadata');
+ document.getElementById('copyLoadOrderButton').lastChild.textContent = l10n.translate('Copy Load Order');
+ document.getElementById('copyContentButton').lastChild.textContent = l10n.translate('Copy Content');
+ document.getElementById('refreshContentButton').lastChild.textContent = l10n.translate('Refresh Content');
+ document.getElementById('helpButton').lastChild.textContent = l10n.translate('View Documentation');
+ document.getElementById('aboutButton').lastChild.textContent = l10n.translate('About');
+ document.getElementById('settingsButton').lastChild.textContent = l10n.translate('Settings');
+ document.getElementById('quitButton').lastChild.textContent = l10n.translate('Quit');
/* Search bar */
document.getElementById('searchBar').shadowRoot.getElementById('search').label = l10n.translate('Search cards');
@@ -241,18 +189,18 @@
/* Nav items */
document.getElementById('sidebarTabs').firstElementChild.textContent = l10n.translate('Plugins');
document.getElementById('sidebarTabs').firstElementChild.nextElementSibling.textContent = l10n.translate('Filters');
- document.getElementById('contentFilter').parentElement.label = l10n.translate('Press Enter or click outside the input to set the filter.');
+ document.getElementById('filters').querySelector('paper-tooltip[for=contentFilter]').textContent = l10n.translate('Press Enter or click outside the input to set the filter.');
document.getElementById('contentFilter').label = l10n.translate('Filter content');
/* Filters */
- document.getElementById('hideVersionNumbers').label = l10n.translate('Hide version numbers');
- document.getElementById('hideCRCs').label = l10n.translate('Hide CRCs');
- document.getElementById('hideBashTags').label = l10n.translate('Hide Bash Tags');
- document.getElementById('hideNotes').label = l10n.translate('Hide notes');
- document.getElementById('hideDoNotCleanMessages').label = l10n.translate('Hide \'Do not clean\' messages');
- document.getElementById('hideAllPluginMessages').label = l10n.translate('Hide all plugin messages');
- document.getElementById('hideInactivePlugins').label = l10n.translate('Hide inactive plugins');
- document.getElementById('hideMessagelessPlugins').label = l10n.translate('Hide messageless plugins');
+ document.getElementById('hideVersionNumbers').textContent = l10n.translate('Hide version numbers');
+ document.getElementById('hideCRCs').textContent = l10n.translate('Hide CRCs');
+ document.getElementById('hideBashTags').textContent = l10n.translate('Hide Bash Tags');
+ document.getElementById('hideNotes').textContent = l10n.translate('Hide notes');
+ document.getElementById('hideDoNotCleanMessages').textContent = l10n.translate('Hide \'Do not clean\' messages');
+ document.getElementById('hideAllPluginMessages').textContent = l10n.translate('Hide all plugin messages');
+ document.getElementById('hideInactivePlugins').textContent = l10n.translate('Hide inactive plugins');
+ document.getElementById('hideMessagelessPlugins').textContent = l10n.translate('Hide messageless plugins');
document.getElementById('hiddenPluginsTxt').textContent = l10n.translate('Hidden plugins:');
document.getElementById('hiddenMessagesTxt').textContent = l10n.translate('Hidden messages:');
}
@@ -272,19 +220,19 @@
function translateSettingsDialog(l10n) {
/* Settings dialog */
- document.getElementById('settingsDialog').heading = l10n.translate('Settings');
+ document.getElementById('settingsDialog').querySelector('h2').textContent = l10n.translate('Settings');
const defaultGameSelect = document.getElementById('defaultGameSelect');
defaultGameSelect.previousElementSibling.textContent = l10n.translate('Default Game');
defaultGameSelect.firstElementChild.textContent = l10n.translate('Autodetect');
/* The selected text doesn't update, so force that translation. */
- defaultGameSelect.shadowRoot.querySelector('paper-dropdown-menu').selectedItemLabel = defaultGameSelect.shadowRoot.querySelector('core-menu').selectedItem.textContent;
+ defaultGameSelect.shadowRoot.querySelector('paper-dropdown-menu').shadowRoot.querySelector('paper-input').value = defaultGameSelect.shadowRoot.querySelector('paper-dropdown-menu').selectedItem.textContent;
- document.getElementById('languageSelect').previousElementSibling.textContent = l10n.translate('Language');
- document.getElementById('languageSelect').previousElementSibling.label = l10n.translate('Language changes will be applied after LOOT is restarted.');
+ document.getElementById('languageLabel').textContent = l10n.translate('Language');
+ document.getElementById('languageLabel').nextElementSibling.textContent = l10n.translate('Language changes will be applied after LOOT is restarted.');
document.getElementById('enableDebugLogging').previousElementSibling.textContent = l10n.translate('Enable debug logging');
- document.getElementById('enableDebugLogging').parentElement.label = l10n.translate('The output is logged to the LOOTDebugLog.txt file.');
+ document.getElementById('enableDebugLogging').nextElementSibling.textContent = l10n.translate('The output is logged to the LOOTDebugLog.txt file.');
document.getElementById('updateMasterlist').previousElementSibling.textContent = l10n.translate('Update masterlist before sorting');
@@ -300,7 +248,7 @@
/* As the game table is attached on launch, its "Add New Row"
tooltip doesn't benefit from the template translation above. */
- gameTable.querySelector('tr:last-child core-tooltip').setAttribute('label', l10n.translate('Add New Row'));
+ gameTable.querySelector('tr:last-child paper-tooltip').textContent = l10n.translate('Add New Row');
document.getElementById('settingsDialog').getElementsByClassName('accept')[0].textContent = l10n.translate('Apply');
document.getElementById('settingsDialog').getElementsByClassName('cancel')[0].textContent = l10n.translate('Cancel');
@@ -309,7 +257,7 @@
function translateFirstRunDialog(l10n) {
/* First-run dialog */
const firstRun = document.getElementById('firstRun');
- firstRun.heading = l10n.translate('First-Time Tips');
+ firstRun.querySelector('h2').textContent = l10n.translate('First-Time Tips');
firstRun.querySelector('li:nth-child(3)').textContent = l10n.translate('CRCs are only displayed after plugins have been loaded, either by conflict filtering, or by sorting.');
firstRun.querySelector('li:nth-child(4)').textContent = l10n.translate('Double-click a plugin in the sidebar to quickly open its metadata editor. Multiple metadata editors can be opened at once.');
diff --git a/src/gui/html/js/plugin.js b/src/gui/html/js/plugin.js
index 72a3f8b5..6561c02d 100644
--- a/src/gui/html/js/plugin.js
+++ b/src/gui/html/js/plugin.js
@@ -25,13 +25,13 @@
(function exportModule(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
- define([], factory);
+ define(['bower_components/lodash/dist/lodash.core.min'], factory);
} else {
// Browser globals
root.loot = root.loot || {};
- root.loot.Plugin = factory();
+ root.loot.Plugin = factory(root._);
}
-}(this, () => {
+}(this, (_) => {
/* Messages, tags, CRCs and version strings can all be hidden by filters.
Use getters with no setters for member variables as data should not be
written to objects of this class. */
@@ -168,7 +168,7 @@
constructor(obj) {
/* Plugin data */
this.name = obj.name;
- this.crc = obj.crc || 0;
+ this._crc = obj.crc || 0;
this.version = obj.version || '';
this.isActive = obj.isActive || false;
this.isEmpty = obj.isEmpty || false;
@@ -176,20 +176,18 @@
this.loadsArchive = obj.loadsArchive || false;
this.masterlist = obj.masterlist;
- this.userlist = obj.userlist;
+ this._userlist = obj.userlist;
- this.priority = obj.priority || 0;
- this.isPriorityGlobal = obj.isPriorityGlobal || false;
+ this._priority = obj.priority || 0;
+ this._isPriorityGlobal = obj.isPriorityGlobal || false;
this._messages = obj.messages || [];
- this.tags = obj.tags;
+ this._tags = obj.tags || [];
this._isDirty = obj.isDirty || false;
/* UI state variables */
this.id = this.name.replace(/\s+/g, '');
- this.isMenuOpen = false;
- this.isEditorOpen = false;
this.isConflictFilterChecked = false;
- this.isSearchResult = false;
+ this._isSearchResult = false;
}
static fromJson(key, value) {
@@ -232,12 +230,30 @@
return rowData;
}
- get priorityString() {
- if (this.priority === 0) {
- return '';
- }
+ _dispatchCardContentChangeEvent(mayChangeCardHeight) {
+ document.dispatchEvent(new CustomEvent('loot-plugin-card-content-change', {
+ detail: {
+ pluginId: this.id,
+ mayChangeCardHeight,
+ },
+ }));
+ }
- return this.priority.toString();
+ _dispatchCardStylingChangeEvent() {
+ document.dispatchEvent(new CustomEvent('loot-plugin-card-styling-change', {
+ detail: { pluginId: this.id },
+ }));
+ }
+
+ _dispatchItemContentChangeEvent() {
+ document.dispatchEvent(new CustomEvent('loot-plugin-item-content-change', {
+ detail: {
+ pluginId: this.id,
+ priority: this.priority,
+ isPriorityGlobal: this.isPriorityGlobal,
+ hasUserEdits: this.hasUserEdits,
+ },
+ }));
}
get messages() {
@@ -273,17 +289,22 @@
}
});
- if (newTotal !== oldTotal || newWarns !== oldWarns || newErrs !== oldErrs) {
+ if (newTotal !== oldTotal
+ || newWarns !== oldWarns
+ || newErrs !== oldErrs
+ || !_.isEqual(this._messages, messages)) {
+ this._messages = messages;
+
document.dispatchEvent(new CustomEvent('loot-plugin-message-change', {
detail: {
+ pluginId: this.id,
+ mayChangeCardHeight: true,
totalDiff: newTotal - oldTotal,
warningDiff: newWarns - oldWarns,
errorDiff: newErrs - oldErrs,
},
}));
}
-
- this._messages = messages;
}
get isDirty() {
@@ -293,14 +314,91 @@
set isDirty(dirty) {
/* Update dirty counts. */
if (dirty !== this._isDirty) {
+ this._isDirty = dirty;
+
document.dispatchEvent(new CustomEvent('loot-plugin-isdirty-change', {
detail: {
isDirty: dirty,
},
}));
}
+ }
- this._isDirty = dirty;
+ get crc() {
+ return this._crc;
+ }
+
+ set crc(crc) {
+ if (this._crc !== crc) {
+ this._crc = crc;
+
+ this._dispatchCardContentChangeEvent(false);
+ }
+ }
+
+ get tags() {
+ return this._tags;
+ }
+
+ set tags(tags) {
+ if (!_.isEqual(this._tags, tags)) {
+ this._tags = tags;
+
+ this._dispatchCardContentChangeEvent(true);
+ }
+ }
+
+ get hasUserEdits() {
+ return this.userlist !== undefined && Object.keys(this.userlist).length > 1;
+ }
+
+ get userlist() {
+ return this._userlist;
+ }
+
+ set userlist(userlist) {
+ if (!_.isEqual(this._userlist, userlist)) {
+ this._userlist = userlist;
+
+ this._dispatchItemContentChangeEvent();
+ this._dispatchCardStylingChangeEvent();
+ }
+ }
+
+ get priority() {
+ return this._priority;
+ }
+
+ set priority(priority) {
+ if (this._priority !== priority) {
+ this._priority = priority;
+
+ this._dispatchItemContentChangeEvent();
+ }
+ }
+
+ get isPriorityGlobal() {
+ return this._isPriorityGlobal;
+ }
+
+ set isPriorityGlobal(isPriorityGlobal) {
+ if (this._isPriorityGlobal !== isPriorityGlobal) {
+ this._isPriorityGlobal = isPriorityGlobal;
+
+ this._dispatchItemContentChangeEvent();
+ }
+ }
+
+ get isSearchResult() {
+ return this._isSearchResult;
+ }
+
+ set isSearchResult(isSearchResult) {
+ if (this._isSearchResult !== isSearchResult) {
+ this._isSearchResult = isSearchResult;
+
+ this._dispatchCardStylingChangeEvent();
+ }
}
getCardContent(filters) {
@@ -313,6 +411,7 @@
document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + evt.detail.warningDiff;
document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + evt.detail.errorDiff;
}
+
static onIsDirtyChange(evt) {
if (evt.detail.isDirty) {
document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + 1;
@@ -320,5 +419,26 @@
document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) - 1;
}
}
+
+ static onContentChange(evt) {
+ const card = document.getElementById(evt.detail.pluginId);
+ if (card) {
+ card.updateContent(evt.detail.mayChangeCardHeight);
+ }
+ }
+
+ static onCardStylingChange(evt) {
+ const card = document.getElementById(evt.detail.pluginId);
+ if (card) {
+ card.updateStyling();
+ }
+ }
+
+ static onItemContentChange(evt) {
+ const item = document.getElementById('cardsNav').querySelector(`[data-id="${evt.detail.pluginId}"]`);
+ if (item) {
+ item.updateStyling(evt.detail);
+ }
+ }
};
}));
diff --git a/src/gui/html/native-shadow-dom.html b/src/gui/html/native-shadow-dom.html
new file mode 100644
index 00000000..8eca2b43
--- /dev/null
+++ b/src/gui/html/native-shadow-dom.html
@@ -0,0 +1,5 @@
+
diff --git a/src/tests/gui/html/elements/test_editable-table.html b/src/tests/gui/html/elements/test_editable-table.html
new file mode 100644
index 00000000..f5e16662
--- /dev/null
+++ b/src/tests/gui/html/elements/test_editable-table.html
@@ -0,0 +1,52 @@
+
+
+
+ test_editable-table.html
+
+
+
+
+
+
+
+
+
+ Filename Display Name Condition
+
+
+
+
+
+
+
+ Type Content Condition Language
+
+
+
+
+
+
+
+ Add/Remove Bash Tag Condition
+
+
+
+
+
+
+
+ CRC ITM Count Deleted References Deleted Navmeshes Cleaning Utility
+
+
+
+
+
+
+
diff --git a/src/tests/gui/html/elements/test_loot-custom-icons.html b/src/tests/gui/html/elements/test_loot-custom-icons.html
new file mode 100644
index 00000000..aaa29bda
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-custom-icons.html
@@ -0,0 +1,16 @@
+
+
+
+ test_loot-custom-icons.html
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/gui/html/elements/test_loot-dropdown-menu.html b/src/tests/gui/html/elements/test_loot-dropdown-menu.html
new file mode 100644
index 00000000..d004fe1d
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-dropdown-menu.html
@@ -0,0 +1,42 @@
+
+
+
+ test_loot-dropdown-menu.html
+
+
+
+
+
+
+
+
+
+
+
A dropdown:
+
+ Note
+ Warning
+ Error
+
+
+
+ 1
+ 2
+ 3
+
+
+
+
+
+ 1
+ 2
+ 3
+
+
+
+ 1
+ 2
+ 3
+
+
+
diff --git a/src/tests/gui/html/elements/test_loot-menu.html b/src/tests/gui/html/elements/test_loot-menu.html
new file mode 100644
index 00000000..dceef923
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-menu.html
@@ -0,0 +1,19 @@
+
+
+
+ test_loot-menu.html
+
+
+
+
+
+
+
+
+
+
+ Note
+ Warning
+ Error
+
+
diff --git a/src/tests/gui/html/elements/test_loot-message-dialog.html b/src/tests/gui/html/elements/test_loot-message-dialog.html
new file mode 100644
index 00000000..9923be27
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-message-dialog.html
@@ -0,0 +1,34 @@
+
+
+
+ test_loot-message-dialog.html
+
+
+
+
+
+
+
+
+
+ Show Message Box
+ Show Message Dialog
+
+
+
diff --git a/src/tests/gui/html/elements/test_loot-plugin-card.html b/src/tests/gui/html/elements/test_loot-plugin-card.html
new file mode 100644
index 00000000..9831c540
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-plugin-card.html
@@ -0,0 +1,86 @@
+
+
+
+ test_loot-plugin-card.html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[plugin.name]]
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/gui/html/elements/test_loot-plugin-editor.html b/src/tests/gui/html/elements/test_loot-plugin-editor.html
new file mode 100644
index 00000000..0af28fa6
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-plugin-editor.html
@@ -0,0 +1,37 @@
+
+
+
+ test_loot-plugin-editor.html
+
+
+
+
+
+
+
+
+
+
+
+
+ Skyrim.esm
+ 1.9
+ ABCDEF01
+
+
+
+
diff --git a/src/tests/gui/html/elements/test_loot-plugin-item.html b/src/tests/gui/html/elements/test_loot-plugin-item.html
new file mode 100644
index 00000000..62cef1b6
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-plugin-item.html
@@ -0,0 +1,18 @@
+
+
+
+ test_loot-plugin-item.html
+
+
+
+
+
+
+
+
+
+ Skyrim.esm
+
diff --git a/src/tests/gui/html/elements/test_loot-search-toolbar.html b/src/tests/gui/html/elements/test_loot-search-toolbar.html
new file mode 100644
index 00000000..8ab4b078
--- /dev/null
+++ b/src/tests/gui/html/elements/test_loot-search-toolbar.html
@@ -0,0 +1,81 @@
+
+
+
+ test_loot-search-toolbar.html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[item.name]]
+
+
+
+
+
diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html
index 44de2886..11efd275 100644
--- a/src/tests/gui/html/js/test.html
+++ b/src/tests/gui/html/js/test.html
@@ -9,6 +9,7 @@
+
diff --git a/src/tests/gui/html/js/test_plugin.js b/src/tests/gui/html/js/test_plugin.js
index 928d391e..dad49226 100644
--- a/src/tests/gui/html/js/test_plugin.js
+++ b/src/tests/gui/html/js/test_plugin.js
@@ -168,6 +168,21 @@ describe('Plugin', () => {
plugin.userlist.should.be.deepEqual({});
});
+ it('should set priority to 0 if no key was passed', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.priority.should.equal(0);
+ });
+
+ it('should set priority to passed key\'s value', () => {
+ const plugin = new loot.Plugin({
+ name: 'test',
+ priority: 5,
+ });
+
+ plugin.priority.should.equal(5);
+ });
+
it('should set isPriorityGlobal value to false if no key was passed', () => {
const plugin = new loot.Plugin({ name: 'test' });
@@ -177,10 +192,62 @@ describe('Plugin', () => {
it('should set isPriorityGlobal to passed key\'s value', () => {
const plugin = new loot.Plugin({
name: 'test',
- isEmpty: true,
+ isPriorityGlobal: true,
});
- plugin.isEmpty.should.be.true();
+ plugin.isPriorityGlobal.should.be.true();
+ });
+
+ it('should set messages value to an empty array if no key was passed', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.messages.length.should.equal(0);
+ });
+
+ it('should set messages to passed key\'s value', () => {
+ const messages = [{
+ type: 'say',
+ content: 'test message',
+ }];
+ const plugin = new loot.Plugin({
+ name: 'test',
+ messages,
+ });
+
+ plugin.messages.should.deepEqual(messages);
+ });
+
+ it('should set tags value to an empty array if no key was passed', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.tags.length.should.equal(0);
+ });
+
+ it('should set tags to passed key\'s value', () => {
+ const tags = [{
+ name: 'Delev',
+ }];
+ const plugin = new loot.Plugin({
+ name: 'test',
+ tags,
+ });
+
+ plugin.tags.should.deepEqual(tags);
+ });
+
+ it('should set isDirty value to false if no key was passed', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.isDirty.should.be.false();
+ });
+
+ it('should set isDirty to passed key\'s value', () => {
+ const plugin = new loot.Plugin({
+ name: 'test',
+ isDirty: true,
+ });
+
+ plugin.isDirty.should.be.true();
});
it('should set id to the plugins name without spaces', () => {
@@ -189,18 +256,6 @@ describe('Plugin', () => {
plugin.id.should.equal('testpluginname');
});
- it('should set isMenuOpen to false', () => {
- const plugin = new loot.Plugin({ name: 'test' });
-
- plugin.isMenuOpen.should.be.false();
- });
-
- it('should set isEditorOpen to false', () => {
- const plugin = new loot.Plugin({ name: 'test' });
-
- plugin.isEditorOpen.should.be.false();
- });
-
it('should set isConflictFilterChecked to false', () => {
const plugin = new loot.Plugin({ name: 'test' });
@@ -301,32 +356,6 @@ describe('Plugin', () => {
});
});
- describe('#priorityString', () => {
- it('should return an empty string if priority is undefined', () => {
- const plugin = new loot.Plugin({ name: 'test' });
-
- plugin.priorityString.should.equal('');
- });
-
- it('should return an empty string if priority is zero', () => {
- const plugin = new loot.Plugin({
- name: 'test',
- priority: 0,
- });
-
- plugin.priorityString.should.equal('');
- });
-
- it('should return priority value as string if non zero', () => {
- const plugin = new loot.Plugin({
- name: 'test',
- priority: -50,
- });
-
- plugin.priorityString.should.equal('-50');
- });
- });
-
describe('#messages', () => {
let handleEvent;
@@ -357,7 +386,7 @@ describe('Plugin', () => {
}];
const plugin = new loot.Plugin({
name: 'test',
- messages: messages,
+ messages,
});
plugin.messages.should.be.deepEqual(messages);
@@ -378,7 +407,7 @@ describe('Plugin', () => {
plugin.messages.should.be.deepEqual(messages);
});
- it('setting messages should not fire an event if no message counts were changed', (done) => {
+ it('setting messages should not fire an event if no messages were changed', (done) => {
const plugin = new loot.Plugin({
name: 'test',
messages: [{
@@ -386,10 +415,6 @@ describe('Plugin', () => {
content: 'test message',
}],
});
- const messages = [{
- type: 'say',
- content: 'another test message',
- }];
handleEvent = () => {
done(new Error('Should not have fired an event'));
@@ -397,12 +422,12 @@ describe('Plugin', () => {
document.addEventListener('loot-plugin-message-change', handleEvent);
- plugin.messages = messages;
+ plugin.messages = plugin.messages;
setTimeout(done, 100);
});
- it('setting messages should fire an event if message counts were changed', (done) => {
+ it('setting messages should fire an event if the messages were changed', (done) => {
const plugin = new loot.Plugin({
name: 'test',
messages: [],
@@ -413,6 +438,7 @@ describe('Plugin', () => {
}];
handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
evt.detail.totalDiff.should.equal(1);
evt.detail.warningDiff.should.equal(0);
evt.detail.errorDiff.should.equal(1);
@@ -483,6 +509,363 @@ describe('Plugin', () => {
});
});
+ describe('#crc', () => {
+ let handleEvent;
+
+ afterEach(() => {
+ document.removeEventListener('loot-plugin-card-content-change', handleEvent);
+ });
+
+ it('getting value should return 0 if crc has not been set in the constructor', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.isDirty.should.be.false();
+ });
+
+ it('getting value should return 0xDEADBEEF if it was set in the constructor', () => {
+ const plugin = new loot.Plugin({
+ name: 'test',
+ crc: 0xDEADBEEF,
+ });
+
+ plugin.crc.should.equal(0xDEADBEEF);
+ });
+
+ it('setting value should store set value', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.crc = 0xDEADBEEF;
+
+ plugin.crc.should.equal(0xDEADBEEF);
+ });
+
+ it('setting value to the current value should not fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = () => {
+ done(new Error('Should not have fired an event'));
+ };
+
+ document.addEventListener('loot-plugin-card-content-change', handleEvent);
+
+ plugin.crc = plugin.crc;
+
+ setTimeout(done, 100);
+ });
+
+ it('setting value not equal to the current value should fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
+ done();
+ };
+
+ document.addEventListener('loot-plugin-card-content-change', handleEvent);
+
+ plugin.crc = 0xDEADBEEF;
+ });
+ });
+
+ describe('#tags', () => {
+ let handleEvent;
+
+ afterEach(() => {
+ document.removeEventListener('loot-plugin-card-content-change', handleEvent);
+ });
+
+ it('getting value should return an empty array if tags have not been set in the constructor', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.tags.length.should.equal(0);
+ });
+
+ it('getting value should return any tags that are set', () => {
+ const tags = [{
+ name: 'Delev',
+ }];
+ const plugin = new loot.Plugin({
+ name: 'test',
+ tags,
+ });
+
+ plugin.tags.should.deepEqual(tags);
+ });
+
+ it('setting value should store set value', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+ const tags = [{
+ name: 'Delev',
+ }];
+
+ plugin.tags = tags;
+
+ plugin.tags.should.deepEqual(tags);
+ });
+
+ it('setting value to the current value should not fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = () => {
+ done(new Error('Should not have fired an event'));
+ };
+
+ document.addEventListener('loot-plugin-card-content-change', handleEvent);
+
+ plugin.tags = plugin.tags;
+
+ setTimeout(done, 100);
+ });
+
+ it('setting value not equal to the current value should fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+ const tags = [{
+ name: 'Delev',
+ }];
+
+ handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
+ done();
+ };
+
+ document.addEventListener('loot-plugin-card-content-change', handleEvent);
+
+ plugin.tags = tags;
+ });
+ });
+
+ describe('#userlist', () => {
+ let handleEvent;
+
+ afterEach(() => {
+ document.removeEventListener('loot-plugin-item-content-change', handleEvent);
+ });
+
+ it('getting value should return undefined if it has not been set in the constructor', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ should(plugin.userlist).be.undefined();
+ });
+
+ it('getting value should return the value that was set', () => {
+ const plugin = new loot.Plugin({
+ name: 'test',
+ userlist: {},
+ });
+
+ plugin.userlist.should.deepEqual({});
+ });
+
+ it('setting value should store set value', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.userlist = {};
+
+ plugin.userlist.should.deepEqual({});
+ });
+
+ it('setting value to the current value should not fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = () => {
+ done(new Error('Should not have fired an event'));
+ };
+
+ document.addEventListener('loot-plugin-item-content-change', handleEvent);
+
+ plugin.userlist = plugin.userlist;
+
+ setTimeout(done, 100);
+ });
+
+ it('setting value not equal to the current value should fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
+ evt.detail.priority.should.equal(plugin.priority);
+ evt.detail.isPriorityGlobal.should.equal(plugin.isPriorityGlobal);
+ evt.detail.hasUserEdits.should.equal(plugin.hasUserEdits);
+ done();
+ };
+
+ document.addEventListener('loot-plugin-item-content-change', handleEvent);
+
+ plugin.userlist = { priority: 1 };
+ });
+ });
+
+ describe('#priority', () => {
+ let handleEvent;
+
+ afterEach(() => {
+ document.removeEventListener('loot-plugin-item-content-change', handleEvent);
+ });
+
+ it('getting value should return 0 if it has not been set in the constructor', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.priority.should.equal(0);
+ });
+
+ it('getting value should return the value that was set', () => {
+ const plugin = new loot.Plugin({
+ name: 'test',
+ priority: 5,
+ });
+
+ plugin.priority.should.equal(5);
+ });
+
+ it('setting value should store set value', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.priority = 5;
+
+ plugin.priority.should.equal(5);
+ });
+
+ it('setting value to the current value should not fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = () => {
+ done(new Error('Should not have fired an event'));
+ };
+
+ document.addEventListener('loot-plugin-item-content-change', handleEvent);
+
+ plugin.priority = plugin.priority;
+
+ setTimeout(done, 100);
+ });
+
+ it('setting value not equal to the current value should fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
+ evt.detail.priority.should.equal(plugin.priority);
+ evt.detail.isPriorityGlobal.should.equal(plugin.isPriorityGlobal);
+ evt.detail.hasUserEdits.should.equal(plugin.hasUserEdits);
+ done();
+ };
+
+ document.addEventListener('loot-plugin-item-content-change', handleEvent);
+
+ plugin.priority = 5;
+ });
+ });
+
+ describe('#isPriorityGlobal', () => {
+ let handleEvent;
+
+ afterEach(() => {
+ document.removeEventListener('loot-plugin-item-content-change', handleEvent);
+ });
+
+ it('getting value should return false if it has not been set in the constructor', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.isPriorityGlobal.should.be.false();
+ });
+
+ it('getting value should return the value that was set', () => {
+ const plugin = new loot.Plugin({
+ name: 'test',
+ isPriorityGlobal: true,
+ });
+
+ plugin.isPriorityGlobal.should.be.true();
+ });
+
+ it('setting value should store set value', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.isPriorityGlobal = true;
+
+ plugin.isPriorityGlobal.should.be.true();
+ });
+
+ it('setting value to the current value should not fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = () => {
+ done(new Error('Should not have fired an event'));
+ };
+
+ document.addEventListener('loot-plugin-item-content-change', handleEvent);
+
+ plugin.isPriorityGlobal = plugin.isPriorityGlobal;
+
+ setTimeout(done, 100);
+ });
+
+ it('setting value not equal to the current value should fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
+ evt.detail.priority.should.equal(plugin.priority);
+ evt.detail.isPriorityGlobal.should.equal(plugin.isPriorityGlobal);
+ evt.detail.hasUserEdits.should.equal(plugin.hasUserEdits);
+ done();
+ };
+
+ document.addEventListener('loot-plugin-item-content-change', handleEvent);
+
+ plugin.isPriorityGlobal = true;
+ });
+ });
+
+ describe('#isSearchResult', () => {
+ let handleEvent;
+
+ afterEach(() => {
+ document.removeEventListener('loot-plugin-card-content-change', handleEvent);
+ });
+
+ it('getting value should return false if it has not been set in the constructor', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.isSearchResult.should.be.false();
+ });
+
+ it('setting value should store set value', () => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ plugin.isSearchResult = true;
+
+ plugin.isSearchResult.should.be.true();
+ });
+
+ it('setting value to the current value should not fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = () => {
+ done(new Error('Should not have fired an event'));
+ };
+
+ document.addEventListener('loot-plugin-card-content-change', handleEvent);
+
+ plugin.isSearchResult = plugin.isSearchResult;
+
+ setTimeout(done, 100);
+ });
+
+ it('setting value not equal to the current value should fire an event', (done) => {
+ const plugin = new loot.Plugin({ name: 'test' });
+
+ handleEvent = (evt) => {
+ evt.detail.pluginId.should.equal(plugin.id);
+ done();
+ };
+
+ document.addEventListener('loot-plugin-card-content-change', handleEvent);
+
+ plugin.isSearchResult = true;
+ });
+ });
+
describe('#getCardContent()', () => {
let plugin;
beforeEach(() => {