Custom colors!

This commit is contained in:
punkfairie 2023-05-14 15:44:52 -07:00
parent ce573865f6
commit 68bda87253
9 changed files with 269 additions and 136 deletions

36
package-lock.json generated
View file

@ -14,9 +14,11 @@
"electron-squirrel-startup": "^1.0.0",
"elite-matrix": "^1.0.0",
"glob": "^10.2.2",
"ini": "^4.1.0",
"lodash-es": "^4.17.21",
"reverse-line-reader": "^0.2.6",
"tail": "^2.2.6"
"tail": "^2.2.6",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@electron-forge/cli": "^6.1.1",
@ -3796,6 +3798,12 @@
"node": ">=0.10.0"
}
},
"node_modules/global-prefix/node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"node_modules/global-prefix/node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@ -4075,10 +4083,12 @@
"dev": true
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.0.tgz",
"integrity": "sha512-HLR38RSF2iulAzc3I/sma4CoYxQP844rPYCNfzGDOHqa/YqVlwuuZgBx6M50/X8dKgzk0cm1qRg3+47mK2N+cQ==",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/interpret": {
"version": "3.1.1",
@ -5982,6 +5992,11 @@
"node": ">=14.0.0"
}
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
@ -6951,6 +6966,17 @@
}
}
},
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
"dependencies": {
"sax": "^1.2.4"
},
"bin": {
"xml-js": "bin/cli.js"
}
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",

View file

@ -45,8 +45,10 @@
"electron-squirrel-startup": "^1.0.0",
"elite-matrix": "^1.0.0",
"glob": "^10.2.2",
"ini": "^4.1.0",
"lodash-es": "^4.17.21",
"reverse-line-reader": "^0.2.6",
"tail": "^2.2.6"
"tail": "^2.2.6",
"xml-js": "^1.6.11"
}
}

View file

@ -45,9 +45,9 @@
</div>
<div class="row ms-1 me-1">
<div class="col col-form-label system charted">
<label class="col col-form-label system charted">
Where to get RGB matrix:
</div>
</label>
<div class="col-2 system charted p-0 text-center">
<button type="button" class="btn" id="matrixBtn">Select File</button>
<input type="hidden" id="matrixFile" name="matrixFile">

View file

@ -98,7 +98,7 @@ div.charted b.inactive {
}
.highlighted {
color: var(--secondary-dark);
color: var(--background);
background: var(--main);
}

View file

@ -1,38 +1,38 @@
import type { Tail as TailType } from 'tail'
import type { autoScan, completeFsdJump, detailedScan, journalEntry, navRoute, planetScan } from "../@types/journalLines"
import type { Tail as TailType } from 'tail';
import type { autoScan, completeFsdJump, detailedScan, journalEntry, navRoute, planetScan } from "../@types/journalLines";
const EventEmitter = require('node:events')
import * as _ from 'lodash-es'
const path = require('node:path')
const { readFile } = require('node:fs/promises')
const reverseLineReader = require('reverse-line-reader')
const Tail = require('tail').Tail
const EventEmitter = require('node:events');
import * as _ from 'lodash-es';
const path = require('node:path');
const { readFile } = require('node:fs/promises');
const reverseLineReader = require('reverse-line-reader');
const Tail = require('tail').Tail;
import { System } from "./System"
import { Log } from "./Log"
import { Body } from "./Body"
import { System } from "./System";
import { Log } from "./Log";
import { Body } from "./Body";
export class Journal extends EventEmitter {
#path: string
testing: string
location: System
navRoute: System[]
#path: string;
testing: string;
location: System;
navRoute: System[];
constructor(journalPath: string) {
super()
super();
this.#path = journalPath
this.location = new System()
this.navRoute = []
this.#path = journalPath;
this.location = new System();
this.navRoute = [];
// Start ReverseLineReader chain here.
Log.write(`Journal initialized. Attempting to find current location.`)
this.#getLastFsdJump()
Log.write(`Journal initialized. Attempting to find current location.`);
this.#getLastFsdJump();
// -> IF no FSD Jump: this.#getLastLocation()
// --> this.#getScannedBodies()
this.testing = this.#path
this.testing = this.#path;
}
/* --------------------------------------------------------------------- #getLastFsdJump ---- */
@ -42,22 +42,23 @@ export class Journal extends EventEmitter {
#getLastFsdJump(): void {
reverseLineReader.eachLine(this.#path, (raw: string) => {
if (raw) { //skip blank line at end of file
const line: journalEntry = JSON.parse(raw)
const line: journalEntry = JSON.parse(raw);
if (line.event === 'FSDJump') {
this.location = new System((line as completeFsdJump))
Log.write(`Current location set to ${this.location.name}.`)
this.emit('ENTERED_NEW_SYSTEM')
return false
this.location = new System((line as completeFsdJump));
Log.write(`Current location set to ${this.location.name}.`);
this.emit('ENTERED_NEW_SYSTEM');
return false;
}
}
}).then(() => {
if (this.location.name === 'Unknown') {
Log.write('Unable to find last hyperspace jump. Searching for last known location.')
this.#getLastLocation()
Log
.write('Unable to find last hyperspace jump. Searching for last known location.');
this.#getLastLocation();
} else {
Log.write('Attempting to find scanned bodies in current system.')
this.#getScannedBodies()
Log.write('Attempting to find scanned bodies in current system.');
this.#getScannedBodies();
}
})
}
@ -69,27 +70,27 @@ export class Journal extends EventEmitter {
reverseLineReader.eachLine(this.#path, (raw: string, last: boolean) => {
// Extra check just to be sure.
if (this.location.name !== 'Unknown') {
return false
return false;
}
if (raw) {
const line: journalEntry = JSON.parse(raw)
const line: journalEntry = JSON.parse(raw);
if (line.event === 'Location') {
this.location = new System((line as completeFsdJump))
Log.write(`Current location set to ${this.location.name}.`)
this.emit('ENTERED_NEW_SYSTEM')
return false
this.location = new System((line as completeFsdJump));
Log.write(`Current location set to ${this.location.name}.`);
this.emit('ENTERED_NEW_SYSTEM');
return false;
} else if (last) {
Log.write('WARNING: Unable to find last known location.')
return false
Log.write('WARNING: Unable to find last known location.');
return false;
}
}
}).then(() => {
if (this.location.name !== 'Unknown') {
Log.write('Attempting to find scanned bodies in current system.')
this.#getScannedBodies()
Log.write('Attempting to find scanned bodies in current system.');
this.#getScannedBodies();
}
})
}
@ -102,26 +103,26 @@ export class Journal extends EventEmitter {
reverseLineReader.eachLine(this.#path, (raw: string) => {
if (raw) {
const line: journalEntry = JSON.parse(raw)
const line: journalEntry = JSON.parse(raw);
// Check if previous line was ScanType = Detailed, and handle that.
if (dssLine) {
if (line.event === 'SAAScanComplete') {
// This was a DSS, so add to list with DSS flag set to true.
this.location.bodies.push(new Body(dssLine, true))
this.location.bodies.push(new Body(dssLine, true));
} else {
// Else, check that the body hasn't already been added (by a DSS scan line).
const dupChecker = {'BodyName': dssLine.BodyName, 'BodyID': dssLine.BodyID}
const r = _.find(this.location.bodies, dupChecker)
const dupChecker = {'BodyName': dssLine.BodyName, 'BodyID': dssLine.BodyID};
const r = _.find(this.location.bodies, dupChecker);
if (r === undefined) {
// Body was not already logged, so add to list.
this.location.bodies.push(new Body(dssLine))
this.location.bodies.push(new Body(dssLine));
}
}
// Finally, clear the variable.
dssLine = null
dssLine = null;
}
// Now move on to evaluating the current line.
@ -130,10 +131,10 @@ export class Journal extends EventEmitter {
// the one immediately above for event = SAAScanComplete, which indicates this
// was a DSS.
if (line.ScanType === 'Detailed' && !('StarType' in line)) {
dssLine = (line as detailedScan)
dssLine = (line as detailedScan);
} else if ('StarType' in line) { // Save stars to bodies list.
this.location.bodies.push(new Body((line as autoScan|detailedScan)))
this.location.bodies.push(new Body((line as autoScan|detailedScan)));
} else if (line.ScanType === 'AutoScan') { // Save auto/discovery scan bodies.
// Check if planet, and then do the duplicate check (otherwise it's an
@ -142,33 +143,33 @@ export class Journal extends EventEmitter {
const dupChecker = {
'BodyName': (line as planetScan<'AutoScan'>).BodyName,
'BodyID': (line as planetScan<'AutoScan'>).BodyID,
}
const r = _.find(this.location.bodies, dupChecker)
};
const r = _.find(this.location.bodies, dupChecker);
if (r === undefined) {
this.location.bodies.push(new Body((line as autoScan)))
this.location.bodies.push(new Body((line as autoScan)));
}
} else { // Asteroids.
this.location.bodies.push(new Body((line as autoScan)))
this.location.bodies.push(new Body((line as autoScan)));
}
}
} else if (line.event === 'FSDJump') {
// Stop evaluating once we reach the beginning of current system entries.
return false
return false;
}
}
}).then(() => {
if (this.location.bodies.length > 0) {
Log.write('No scanned bodies found in current system.')
this.emit('BUILD_BODY_LIST')
Log.write('No scanned bodies found in current system.');
this.emit('BUILD_BODY_LIST');
} else {
Log.write('Scanned bodies found.')
Log.write('Scanned bodies found.');
}
Log.write('Checking for nav route.')
this.#getNavRoute()
Log.write('Checking for nav route.');
this.#getNavRoute();
})
}
@ -176,40 +177,40 @@ export class Journal extends EventEmitter {
async #getNavRoute(): Promise<void> {
this.navRoute = [] // Clear previous route, to catch overwritten routes.
let routeFile: string|null = null
let routeFile: string|null = null;
try {
const filePath: string = path.dirname(this.#path) + '/NavRoute.json'
routeFile = await readFile(filePath, {encoding: 'utf8'})
const filePath: string = path.dirname(this.#path) + '/NavRoute.json';
routeFile = await readFile(filePath, {encoding: 'utf8'});
} catch (err) {
Log.write(`Error reading nav route file: ${err.message}.`)
Log.write(`Error reading nav route file: ${err.message}.`);
}
if (routeFile) {
const route: navRoute = JSON.parse(routeFile)
const route: navRoute = JSON.parse(routeFile);
// system -> skip
// CURRENT -> push = true; skip
// system -> push
let push: boolean = false
let push: boolean = false;
route.Route.forEach((system) => {
if (!push && system.SystemAddress === this.location.SystemAddress) {
push = true
push = true;
}
if (push && system.SystemAddress !== this.location.SystemAddress) {
this.navRoute.push(new System(system))
this.navRoute.push(new System(system));
}
})
if (this.navRoute.length > 0) {
Log.write('Nav route set.')
Log.write('Nav route set.');
} else {
Log.write('No nav route found.')
Log.write('No nav route found.');
}
// Call this no matter what, so that cleared routes are properly dealt with.
this.emit('SET_NAV_ROUTE')
this.emit('SET_NAV_ROUTE');
}
}
@ -217,63 +218,63 @@ export class Journal extends EventEmitter {
// Watch the journal for changes.
watch(): void {
const tail: TailType = new Tail(this.#path, {useWatchFile: true})
const tail: TailType = new Tail(this.#path, {useWatchFile: true});
Log.write(`Watching ${path.basename(this.#path)}...`)
Log.write(`Watching ${path.basename(this.#path)}...`);
tail.on('line', (data) => data ? this.#parseLine(data) : undefined)
tail.on('error', (err) => Log.write(`Tail error in Journal.watch(): ${err}`))
tail.on('line', (data) => data ? this.#parseLine(data) : undefined);
tail.on('error', (err) => Log.write(`Tail error in Journal.watch(): ${err}`));
}
/* ------------------------------------------------------------------------ #parseLine() ---- */
// Parse and handle journal lines.
#parseLine(raw: string) {
const line: journalEntry = JSON.parse(raw)
let dssFlag: boolean = false
const line: journalEntry = JSON.parse(raw);
let dssFlag: boolean = false;
switch (line.event) {
// Hyperspace jump started (3.. 2.. 1..)
case 'StartJump': {
if ('JumpType' in line && line.JumpType === 'Hyperspace') {
this.emit('ENTERING_WITCH_SPACE')
this.emit('ENTERING_WITCH_SPACE');
}
break
break;
}
// CMDR jumped to new system, so update current location.
case 'FSDJump': {
this.#handleFsdJump((line as completeFsdJump))
break
this.#handleFsdJump((line as completeFsdJump));
break;
}
// CMDR completed DSS scan, so set flag for when next line processes and we want to
// figure out what kind of scan occurred.
case 'SAAScanComplete': {
dssFlag = true
break
dssFlag = true;
break;
}
// A scan occurred, so let's hand that info off to the appropriate function and then
// reset the DSS flag.
case 'Scan': {
this.#handleScanLine((line as autoScan|detailedScan), dssFlag)
dssFlag = false
break
this.#handleScanLine((line as autoScan|detailedScan), dssFlag);
dssFlag = false;
break;
}
// CMDR set a new nav route.
case 'NavRoute': {
this.#getNavRoute()
break
this.#getNavRoute();
break;
}
// CMDR cleared the nav route.
case 'NavRouteClear': {
this.navRoute = []
Log.write('Nav route cleared.')
this.emit('SET_NAV_ROUTE')
break
this.navRoute = [];
Log.write('Nav route cleared.');
this.emit('SET_NAV_ROUTE');
break;
}
}
}
@ -281,49 +282,49 @@ export class Journal extends EventEmitter {
/* ---------------------------------------------------------------------- #handleFsdJump ---- */
#handleFsdJump(line: completeFsdJump): void {
this.location = new System(line)
Log.write(`FSD Jump detected, current location updated to ${this.location.name}.`)
this.location = new System(line);
Log.write(`FSD Jump detected, current location updated to ${this.location.name}.`);
if (this.navRoute.length > 0) {
_.remove(this.navRoute, (system) => {
return system.SystemAddress === this.location.SystemAddress
return system.SystemAddress === this.location.SystemAddress;
})
}
this.emit('ENTERED_NEW_SYSTEM')
this.emit('ENTERED_NEW_SYSTEM');
}
/* --------------------------------------------------------------------- #handleScanLine ---- */
#handleScanLine(line: autoScan|detailedScan, DSS: boolean = false) {
const dupChecker = {'BodyName': line.BodyName, 'BodyID': line.BodyID}
let body: Body|null = null
const dupChecker = {'BodyName': line.BodyName, 'BodyID': line.BodyID};
let body: Body|null = null;
// If it's a DSS scan, then we should have already added the body to the list. But we'll
// check to make sure.
if (DSS) {
// Using findIndex() rather than find() so we can edit the body if found.
const bodyIndex: number = _.findIndex(this.location.bodies, dupChecker)
const bodyIndex: number = _.findIndex(this.location.bodies, dupChecker);
if (bodyIndex > -1) { // Body was found in list, so simply toggle the DSS flag.
this.location.bodies[bodyIndex].DSSDone = true
this.location.bodies[bodyIndex].DSSDone = true;
} else { // Body was missed on initial journal scan, so add it to the list.
body = new Body(line, true)
this.location.bodies.push(body)
body = new Body(line, true);
this.location.bodies.push(body);
}
} else { // Otherwise it's an FSS or auto scan, and needs to be added to the list.
// Probably overkill, but do a duplicate check just in case.
const r = _.find(this.location.bodies, dupChecker)
const r = _.find(this.location.bodies, dupChecker);
if (r === undefined) {
body = new Body(line)
this.location.bodies.push(body)
body = new Body(line);
this.location.bodies.push(body);
}
}
Log.write(`Scan detected. Body: ${line.BodyName}.`)
this.emit('BODY_SCANNED', body, DSS)
Log.write(`Scan detected. Body: ${line.BodyName}.`);
this.emit('BODY_SCANNED', body, DSS);
}
}

View file

@ -1,17 +1,22 @@
import { EliteMatrix } from "elite-matrix";
const EventEmitter = require('node:events');
const fs = require('node:fs/promises');
const { statSync, writeFileSync, readFileSync } = require('node:fs');
const ini = require('ini');
const os = require('node:os');
const path = require('node:path');
const { setTimeout } = require('node:timers/promises');
const xmlJS = require('xml-js');
import { EliteMatrix } from "elite-matrix";
import { Log } from "./Log";
interface settingsFile {
minValue: number,
maxDistance: number,
matrixFile: string,
}
export class Settings {
export class Settings extends EventEmitter {
static #instance: Settings;
#file: string;
@ -20,10 +25,12 @@ export class Settings {
minValue: number;
maxDistance: number;
#matrixFile?: string;
#matrixFile: null|string;
matrix?: EliteMatrix;
private constructor(isPackaged: boolean) {
super();
if (!isPackaged && os.platform() === 'linux') {
this.#file = '/mnt/c/Users/marle/ed-safari-settings.json';
} else {
@ -39,6 +46,7 @@ export class Settings {
const contents: string = JSON.stringify({
minValue: 500000,
maxDistance: 10000,
matrixFile: '',
});
writeFileSync(this.#file, contents);
@ -49,8 +57,12 @@ export class Settings {
const contents: settingsFile = JSON.parse(readFileSync(this.#file, { encoding: 'utf8' }));
this.minValue = contents.minValue;
this.maxDistance = contents.maxDistance;
this.#matrixFile = contents.matrixFile;
this.#writing = false;
if (this.#matrixFile) {
this.#setMatrix();
}
}
static get(isPackaged: boolean = false): Settings {
@ -74,6 +86,10 @@ export class Settings {
this.#writing = false;
Log.write('Settings saved!');
// Update Settings props.
await this.#read();
return true;
} catch (err) {
Log.write(err);
@ -83,4 +99,73 @@ export class Settings {
return false;
}
}
/* ------------------------------------------------------------------------------- #read ---- */
async #read(): Promise<boolean> {
try {
const file: string = await fs.readFile(this.#file, { encoding: 'utf8' });
const contents: settingsFile = JSON.parse(file);
this.minValue = contents.minValue;
this.maxDistance = contents.maxDistance;
this.#matrixFile = contents.matrixFile;
if (this.#matrixFile) {
await this.#setMatrix();
Log.write('Custom colors set!');
}
return true;
} catch (err) {
Log.write(err);
return false;
}
}
/* -------------------------------------------------------------------------- #setMatrix ---- */
async #setMatrix(): Promise<void> {
const file: string = await fs.readFile(this.#matrixFile, { encoding: 'utf8' });
let matrixRed: [number, number, number];
let matrixGreen: [number, number, number];
let matrixBlue: [number, number, number];
if (path.basename(this.#matrixFile) === 'GraphicsConfiguration.xml') {
const options = {
trim: true,
ignoreDeclaration: true,
ignoreAttributes: true,
compact: true,
textKey: '$'
};
const contents = xmlJS.xml2js(file, options);
let matrix = [
contents.GraphicsConfig.GUIColour.Default.MatrixRed.$,
contents.GraphicsConfig.GUIColour.Default.MatrixGreen.$,
contents.GraphicsConfig.GUIColour.Default.MatrixBlue.$,
];
matrix = matrix.map(v => v.replace(/\s/g, '').split(','));
matrixRed = matrix[0].length === 3 ? matrix[0] : [1,0,0];
matrixGreen = matrix[1].length === 3 ? matrix[1] : [0,1,0];
matrixBlue = matrix[2].length === 3 ? matrix[2] : [0,0,1];
this.matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
} else if (path.basename(this.#matrixFile) === 'XML-Profile.ini') {
const contents = (ini.parse(file)).constants;
matrixRed = [contents.x150, contents.y150, contents.z150];
matrixGreen = [contents.x151, contents.y151, contents.z151];
matrixBlue = [contents.x152, contents.y152, contents.z152];
this.matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
}
this.emit('CUSTOM_COLORS_SET');
}
}

View file

@ -9,6 +9,17 @@ export class UI {
return Intl.NumberFormat().format(Math.round(number));
}
/* --------------------------------------------------------------------------- setColors ---- */
static setColors(matrix) {
const body = $('body');
body.css('--main', matrix.filterColor('#F5A804'));
body.css('--accent-dark', matrix.filterColor('#000e5f'));
body.css('--accent-light', matrix.filterColor('#17cbd4'));
body.css('--secondary-light', matrix.filterColor('#EAA529'));
body.css('--secondary-dark', matrix.filterColor('#370C03'));
}
/* --------------------------------------------------------------------- enterWitchSpace ---- */
static enterWitchSpace() {

View file

@ -35,6 +35,12 @@ if (!journal) {
safari.watchJournalDir();
journal.watch();
/* ------------------------------------------------------------------------------ set colors ---- */
settings.on('CUSTOM_COLORS_SET', () => {
UI.setColors(settings.matrix);
});
/* -------------------------------------------------------------------- close window handler ---- */
$('#closeBtn').on('click', () => {
@ -130,17 +136,3 @@ edsm.on('SYSTEM_APPRAISED', (system) => {
UI.setValue(systemRow, system.estimatedValueMapped);
}
});
// const matrixRed = [1.2, 0.05, 0.07];
// const matrixGreen = [0.13, 1, 1.18];
// const matrixBlue = [0.4, 1.29, 2];
// const matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
// const hex = matrix.filterColor('#f5a804');
// const rgb = matrix.filterColor([245, 168, 4]);
// console.log(`rgb(${rgb})`)
// $('body').css('--main', `rgb(${rgb})`);
// $('body').css('--accent-dark', matrix.filterColor('#000e5f'));
// $('body').css('--accent-light', matrix.filterColor('#17cbd4'));
// $('body').css('--secondary-light', matrix.filterColor('#EAA529'));
// $('body').css('--secondary-dark', matrix.filterColor('#370C03'));

View file

@ -5,12 +5,23 @@ import './assets/ldom.min';
const { ipcRenderer } = require('electron');
const { setTimeout } = require('node:timers/promises');
const { basename } = require('node:path');
import { Settings } from './models/Settings';
import { UI } from './models/UI';
const settings = Settings.get();
if (settings.matrix) {
UI.setColors(settings.matrix);
}
/* ------------------------------------------------------------------------------ set colors ---- */
settings.on('CUSTOM_COLORS_SET', () => {
UI.setColors(settings.matrix);
});
/* -------------------------------------------------------------------- close window handler ---- */
$('#closeBtn').on('click', () => {
@ -61,6 +72,11 @@ $('form').on('submit', async function (event) {
errors = true;
}
const fileName = basename(data.matrixFile);
if (fileName !== 'XML-Profile.ini' || fileName !== 'GraphicsConfiguration.xml') {
UI.addFormError('#matrixFile', 'Invalid file.');
}
// TODO re-enable submit button if errors.
// If no errors, save.