diff --git a/src/app/Application.ts b/src/app/Application.ts index bd3979cab..a4e247862 100644 --- a/src/app/Application.ts +++ b/src/app/Application.ts @@ -126,7 +126,6 @@ class Application { this.mArgs = args; ipcMain.on('show-window', () => this.showMainWindow(args?.startMinimized)); - process.env['UV_THREADPOOL_SIZE'] = (os.cpus().length * 1.5).toString(); app.commandLine.appendSwitch('js-flags', `--max-old-space-size=${args.maxMemory || 4096}`); @@ -1079,7 +1078,7 @@ class Application { message: 'Your Vortex installation has been corrupted. ' + 'This could be the result of a virus or manual manipulation. ' + 'Vortex might still appear to work (partially) but we suggest ' - + 'you reinstall it.', + + 'you reinstall it. For more information please refer to Vortex\'s log files.', noLink: true, buttons: ['Quit', 'Ignore'], }) diff --git a/src/extensions/download_management/util/setDownloadGames.ts b/src/extensions/download_management/util/setDownloadGames.ts index 57704e0fd..f27d7387e 100644 --- a/src/extensions/download_management/util/setDownloadGames.ts +++ b/src/extensions/download_management/util/setDownloadGames.ts @@ -43,6 +43,7 @@ async function setDownloadGames( // game may be undefined if the download is recognized but it's for a // game Vortex doesn't support api.sendNotification({ + id: 'download-moved' + (game?.name ?? gameIds[0]), type: 'success', title: 'Download moved to game {{gameName}}', message: download.localPath, diff --git a/src/extensions/ini_prep/index.ts b/src/extensions/ini_prep/index.ts index 3d608b1e6..b297ddc4f 100644 --- a/src/extensions/ini_prep/index.ts +++ b/src/extensions/ini_prep/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import {IExtensionApi, IExtensionContext} from '../../types/IExtensionContext'; import { IProfile, IState } from '../../types/IState'; import { ITestResult } from '../../types/ITestResult'; @@ -175,8 +176,9 @@ function bakeSettings(t: TFunction, setdefault(enabledTweaks, getBaseFile(file), []) .push(path.join(tweaksPath, file)); }); + return Promise.resolve(); }) - .catch(err => undefined); + .catch(err => Promise.resolve(undefined)); }).then(() => Promise.mapSeries(baseFiles, iniFileName => { // starting with the .base file for each ini, re-bake the file by applying // the ini tweaks diff --git a/src/extensions/mod_management/util/testModReference.ts b/src/extensions/mod_management/util/testModReference.ts index 7ae5d45c4..fdb82702a 100644 --- a/src/extensions/mod_management/util/testModReference.ts +++ b/src/extensions/mod_management/util/testModReference.ts @@ -66,6 +66,7 @@ export function safeCoerce(input: string): string { } export function coerceToSemver(version: string): string { + version = version?.trim?.(); if (!version) { return undefined; } @@ -86,7 +87,10 @@ export function coerceToSemver(version: string): string { } } else { if (coerceableRE.test(version)) { - const coerced = semver.coerce(version); + // Remove leading 0's from the version segments as that's + // an illegal semantic versioning format/pattern + const sanitizedVersion = version.replace(/\b0+(\d)/g, '$1'); + const coerced = semver.coerce(sanitizedVersion); if (coerced) { return coerced.version } diff --git a/src/extensions/mod_management/views/ModList.tsx b/src/extensions/mod_management/views/ModList.tsx index 08ba7ee53..83527453b 100644 --- a/src/extensions/mod_management/views/ModList.tsx +++ b/src/extensions/mod_management/views/ModList.tsx @@ -1076,7 +1076,13 @@ class ModList extends ComponentEx { } private updateModGrouping(modsWithState) { - const modList = Object.keys(modsWithState).map(key => modsWithState[key]); + const modList = Object.keys(modsWithState).reduce((accum, key) => { + const mod = modsWithState[key]; + if (mod) { + accum.push(mod); + } + return accum; + }, []); const grouped = groupMods(modList, { groupBy: 'file', multipleEnabled: false }); const groupedMods = grouped.reduce((prev: { [id: string]: IModWithState[] }, value) => { diff --git a/src/extensions/nexus_integration/util/convertGameId.ts b/src/extensions/nexus_integration/util/convertGameId.ts index 445daeb47..a21953290 100644 --- a/src/extensions/nexus_integration/util/convertGameId.ts +++ b/src/extensions/nexus_integration/util/convertGameId.ts @@ -48,17 +48,25 @@ export function convertGameIdReverse(knownGames: IGameStored[], input: string): return undefined; } - const game = knownGames.find(iter => + const validGames = knownGames.filter(iter => iter.id === input.toLowerCase() || (iter.details !== undefined) && (iter.details.nexusPageId === input)); + + // We obviously prefer the exact match first. + const game = validGames.find(iter => iter.id === input.toLowerCase()); if (game !== undefined) { return game.id; } + // Alternatively - there may be a nexus page id match. + if (validGames.length > 0) { + return validGames[0].id; + } + return { skyrimspecialedition: 'skyrimse', newvegas: 'falloutnv', elderscrollsonline: 'teso', - }[input.toLowerCase()] || input; + }[input.toLowerCase()] || input.toLowerCase(); } /** diff --git a/src/extensions/symlink_activator_elevate/index.ts b/src/extensions/symlink_activator_elevate/index.ts index 7f271e26b..927f72ca2 100644 --- a/src/extensions/symlink_activator_elevate/index.ts +++ b/src/extensions/symlink_activator_elevate/index.ts @@ -1,8 +1,9 @@ +/* eslint-disable */ import { clearUIBlocker, setUIBlocker } from '../../actions'; import {IExtensionApi, IExtensionContext} from '../../types/IExtensionContext'; import { IGame } from '../../types/IGame'; import { IState } from '../../types/IState'; -import {ProcessCanceled, TemporaryError, UserCanceled} from '../../util/CustomErrors'; +import {ProcessCanceled, UserCanceled} from '../../util/CustomErrors'; import * as fs from '../../util/fs'; import { Normalize } from '../../util/getNormalizeFunc'; import getVortexPath from '../../util/getVortexPath'; @@ -101,7 +102,7 @@ class DeploymentMethod extends LinkingDeployment { private mOpenRequests: { [num: number]: { resolve: () => void, reject: (err: Error) => void } }; private mIPCServer: net.Server; private mDone: () => void; - private mWaitForUser: () => Promise; + // private mWaitForUser: () => Promise; private mOnReport: (report: string) => void; private mTmpFilePath: string; @@ -114,18 +115,18 @@ class DeploymentMethod extends LinkingDeployment { api); this.mElevatedClient = null; - this.mWaitForUser = () => new Promise((resolve, reject) => api.sendNotification({ - type: 'info', - message: 'Deployment requires elevation', - noDismiss: true, - actions: [{ - title: 'Elevate', - action: dismiss => { dismiss(); resolve(); }, - }, { - title: 'Cancel', - action: dismiss => { dismiss(); reject(new UserCanceled()); }, - }], - })); + // this.mWaitForUser = () => new Promise((resolve, reject) => api.sendNotification({ + // type: 'info', + // message: 'Deployment requires elevation', + // noDismiss: true, + // actions: [{ + // title: 'Elevate', + // action: dismiss => { dismiss(); resolve(); }, + // }, { + // title: 'Cancel', + // action: dismiss => { dismiss(); reject(new UserCanceled()); }, + // }], + // })); let lastReport: string; this.mOnReport = (report: string) => { @@ -173,11 +174,21 @@ class DeploymentMethod extends LinkingDeployment { } public userGate(): Promise { - const state: IState = this.api.store.getState(); - - return state.settings.workarounds.userSymlinks - ? Promise.resolve() - : this.mWaitForUser(); + // In the past, we used to block the user from deploying/purging his mods + // until he would give us consent to elevate permissions to do so. + // That is a redundant anti-pattern as the elevation UI itself will already inform the user + // of this requirement and give him the opportunity to cancel or proceed with the deployment! + // + // Additionally - blocking the deployment behind a collapsible notification is extremely bad UX + // as it is not necessarily obvious to the user that we require him to click on the notification. + // Finally, this will block the user from switching to other games while Vortex awaits for elevation + // causing the "Are you stuck?" overlay to appear and remain there, waiting for the user to click on an + // invisible notification button. + // + // I could add a Promise.race([this.waitForUser(), this.waitForElevation()]) to replace the mWaitForUser + // functor - but what's the point - if the user clicked deploy, he surely wants to elevate his instance + // as well. (And if not, he can always cancel the Windows API dialog!) + return Promise.resolve(); } public prepare(dataPath: string, clean: boolean, lastActivation: IDeployedFile[],