diff --git a/DEVELOP.md b/DEVELOP.md index 0d3c2a98..7178b6b0 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -64,13 +64,13 @@ This package provides a number of test cases: See if translation files match keys -`# npm run langtest` +`# npm run lang_test` ### Fix translations If translation files are mismatched, this will fix it (english is master) -`# npm run langfix` +`# npm run lang_fix` ### Check Dependencies diff --git a/lib/single-mod-checker.js b/lib/modCheckLib.js similarity index 57% rename from lib/single-mod-checker.js rename to lib/modCheckLib.js index 89445b30..e7199be2 100644 --- a/lib/single-mod-checker.js +++ b/lib/modCheckLib.js @@ -9,12 +9,474 @@ const fs = require('fs') const path = require('path') const admZip = require('adm-zip') -//const unzip = require('unzip-stream') const glob = require('glob') const xml2js = require('xml2js') const crypto = require('crypto') const { decodeDXT, parseDDS } = require('./ddsLibrary') -const PNG = require('pngjs').PNG +const JPEG = require('jpeg-js') + + +class modFileCollection { + #ignoreList = [ + '^npm-debug\\.log$', + '^\\..*\\.swp$', + '^Thumbs\\.db$', + '^thumbs\\.db$', + '^ehthumbs\\.db$', + '^Desktop\\.ini$', + '^desktop\\.ini$', + '@eaDir$', + ] + #junkRegex = new RegExp(this.#ignoreList.join('|')) + + #bindConflict = {} + #map_CollectionToFolder = {} + #map_CollectionToFolderRelative = {} + #map_CollectionToFullName = {} + #map_CollectionToName = {} + #map_FolderContents = {} + #map_FolderToCollection = {} + #map_ModUUIDToShortName = {} + #set_Collections = new Set() + + #modHubList = { 'mods' : {}, 'last' : [] } + #modHubVersion = {} + + #list_allMods = {} + #list_newMods = new Set() + + #log = null + #loadingWindow = {} + #localeFunction = null + #map_CollectionNotes = null + #modCache = {} + #modCacheStore = {} + + #userHome = '' + #useSyncSafeMode = false + #skipCache = false + + #scanPromise = [] + + constructor(logger, notes, cache, homeDir, loadingWindow, locale, skipCache = false) { + this.#modCache = cache + this.#log = logger + this.#map_CollectionNotes = notes + this.#userHome = homeDir + this.#skipCache = skipCache + this.#loadingWindow = loadingWindow + this.#localeFunction = locale + } + + mapFolderToCollection(folder) { + return this.#map_FolderToCollection[folder] || null + } + mapCollectionToFolder(collectKey) { + return this.#map_CollectionToFolder[collectKey] + } + mapCollectionToName(collectKey) { + return this.#map_CollectionToName[collectKey] + } + mapCollectionToFullName(collectKey) { + return this.#map_CollectionToFullName[collectKey] + } + + set newCollectionOrder(newSetOrder) { this.#set_Collections = newSetOrder } + + removeCollection(collectKey) { + const thisRealPath = this.#map_CollectionToFolder[collectKey] + + this.#set_Collections.delete(collectKey) + + delete this.#map_FolderToCollection[thisRealPath] + delete this.#list_allMods[collectKey] + delete this.#map_FolderContents[collectKey] + delete this.#map_CollectionToFolder[collectKey] + delete this.#map_CollectionToFolderRelative[collectKey] + delete this.#map_CollectionToName[collectKey] + delete this.#map_CollectionToFullName[collectKey] + } + + get bindConflict() { return this.#bindConflict } + get collections() { return this.#set_Collections } + + getModListFromCollection(collectKey) { + return Object.values(this.#list_allMods[collectKey].mods) + } + getModCollection(collectKey) { + return this.#list_allMods[collectKey] + } + + get modHubList() { return this.#modHubList } + set modHubList(newList) { this.#modHubList = newList } + get modHubVersion() { return this.#modHubVersion } + set modHubVersion(newList) { this.#modHubVersion = newList } + + modHubFullRecord(thisMod, asArray = true) { + /* Return [ID, Version, Recent] */ + const modHubID = this.modHubModRecord(thisMod) + + if ( asArray ) { + return [ + modHubID, + this.modHubVersionModHubId(modHubID), + this.#modHubList.last.includes(modHubID) + ] + } + + return { + id : modHubID, + version : this.modHubVersionModHubId(modHubID), + recent : this.#modHubList.last.includes(modHubID), + } + } + modHubModRecord(thisMod) { + return this.#modHubList.mods[thisMod.fileDetail.shortName] || null + } + modHubModUUID(modUUID) { + return this.#modHubList.mods[this.#map_ModUUIDToShortName[modUUID]] || null + } + modHubVersionUUID(modUUID) { + return this.#modHubVersion[this.modHubModUUID(modUUID)] || null + } + modHubVersionModRecord(thisMod) { + return this.#modHubVersion[this.modHubModRecord(thisMod)] || null + } + modHubVersionModHubId(modHubID) { + return this.#modHubVersion[modHubID] || null + } + + modColUUIDToRecord(ID) { + const idParts = ID.split('--') + return this.#list_allMods?.[idParts[0]]?.mods?.[idParts[1]] || null + } + + modColUUIDsToRecords(IDs) { + const theseMods = [] + IDs.forEach((thisColUUID) => { theseMods.push(this.modColUUIDToRecord(thisColUUID)) }) + return theseMods + } + + modColAndUUID(collectKey, modKey) { + return this.#list_allMods[collectKey].mods[modKey] + } + + clearAll() { + this.#scanPromise = [] + this.#map_FolderContents = {} + this.#map_CollectionToFolder = {} + this.#map_CollectionToFolderRelative = {} + this.#map_FolderToCollection = {} + this.#map_CollectionToName = {} + this.#map_CollectionToFullName = {} + this.#map_ModUUIDToShortName = {} + this.#list_allMods = {} + this.#list_newMods = new Set() + this.#set_Collections.clear() + } + + set syncSafe(mode) { this.#useSyncSafeMode = mode } + + addCollection(folder) { + const goodFolderContents = [] + const thisFolderKey = this.#getMD5Hash(folder, 'col_') + const thisRealPath = path.normalize(folder) + const thisShortName = path.basename(thisRealPath) + const thisFullName = this.#populateFullName(thisFolderKey, thisShortName) + + try { + const folderContents = fs.readdirSync(thisRealPath, {withFileTypes : true}) + + folderContents.forEach((thisFile) => { + if ( !this.#junkRegex.test(thisFile.name) ) { goodFolderContents.push(thisFile) } + }) + } catch (e) { + this.#log.log.danger(`Couldn't read folder: ${thisRealPath} :: ${e}`, 'collection-reader') + return null + } + + this.#set_Collections.add(thisFolderKey) + this.#list_allMods[thisFolderKey] = { + alphaSort : [], + dependSet : new Set(), + folderSize : 0, + fullName : thisFullName, + mods : {}, + modSet : new Set(), + name : thisShortName, + } + + this.#map_FolderContents [thisFolderKey] = goodFolderContents + this.#map_CollectionToFolder [thisFolderKey] = thisRealPath + this.#map_CollectionToFolderRelative [thisFolderKey] = this.#toHomeDir(thisRealPath) + this.#map_FolderToCollection [thisRealPath] = thisFolderKey + this.#map_CollectionToName [thisFolderKey] = thisShortName + this.#map_CollectionToFullName [thisFolderKey] = thisFullName + + return { + collectKey : thisFolderKey, + fileCount : goodFolderContents.length, + } + } + + async processMods() { + this.#modCacheStore = this.#modCache.store + + for ( const collectKey of this.#set_Collections ) { + for ( const thisFile of this.#map_FolderContents[collectKey] ) { + this.#scanPromise.push(this.#addMod(collectKey, thisFile)) + } + } + + Promise.all(this.#scanPromise).then(() => { + this.#modCache.store = this.#modCacheStore + this.#doAlphaSort() + this.#processBindConflicts() + }) + } + + get processPromise() { return Promise.all(this.#scanPromise) } + + #doAlphaSort() { + this.#set_Collections.forEach((collectKey) => { + this.#list_allMods[collectKey].alphaSort.sort() + }) + } + + #cacheHit(thisFileStats) { + if ( thisFileStats.folder || this.#skipCache || thisFileStats.hashString === null ) { return false } + + return this.#modCacheStore[thisFileStats.hashString] ?? false + } + + #fileStats(collectKey, thisFile) { + const folderLoc = this.#map_CollectionToFolder[collectKey] + const fullPath = path.join(folderLoc, thisFile.name) + const fileStats = { + isFolder : null, + date : null, + birthDate : null, + size : null, + error : false, + hashString : null, + fullPath : fullPath, + } + + try { + if ( thisFile.isSymbolicLink() ) { + const thisSymLink = fs.readlinkSync(fullPath) + const thisSymLinkStat = fs.lstatSync(path.join(folderLoc, thisSymLink)) + + fileStats.isFolder = thisSymLinkStat.isDirectory() + fileStats.date = thisSymLinkStat.ctime + fileStats.birthDate = thisSymLinkStat.birthtime + + if ( !fileStats.isFolder ) { fileStats.size = thisSymLinkStat.size } + } else { + const theseStats = fs.statSync(fullPath) + + fileStats.isFolder = thisFile.isDirectory() + fileStats.date = theseStats.ctime + fileStats.birthDate = theseStats.birthtime + + if ( !fileStats.isFolder ) { fileStats.size = theseStats.size } + } + + if ( fileStats.isFolder ) { + let bytes = 0 + glob.sync('**', { cwd : path.join(fullPath) }).forEach((file) => { + try { + const stats = fs.statSync(path.join(fullPath, file)) + if ( stats.isFile() ) { bytes += stats.size } + } catch { /* Do Nothing if we can't read it. */ } + }) + fileStats.size = bytes + } else { + fileStats.hashString = this.#getMD5Hash(`${thisFile.name}-${fileStats.size}-${(this.#useSyncSafeMode)?fileStats.birthDate.toISOString():fileStats.date.toISOString()}`) + } + } catch (e) { + this.#log.log.warning(`Unable to stat file ${thisFile.name} in ${folderLoc} : ${e}`, 'file-stat') + fileStats.isFolder = false + fileStats.size = 0 + fileStats.date = new Date(1969, 1, 1, 0, 0, 0, 0) + fileStats.birthDate = new Date(1969, 1, 1, 0, 0, 0, 0) + fileStats.error = true + } + return fileStats + } + + #addModToData(collectKey, modRecord) { + const thisModRecord = { ...modRecord } + + thisModRecord.currentCollection = collectKey + thisModRecord.colUUID = `${collectKey}--${thisModRecord.uuid}` + thisModRecord.modHub = this.modHubFullRecord(thisModRecord, false) + + this.#map_ModUUIDToShortName[thisModRecord.uuid] = thisModRecord.fileDetail.shortName + this.#list_allMods[collectKey].mods[thisModRecord.uuid] = thisModRecord + this.#list_allMods[collectKey].folderSize += thisModRecord.fileDetail.fileSize + this.#list_allMods[collectKey].modSet.add(thisModRecord.uuid) + this.#list_allMods[collectKey].dependSet.add(thisModRecord.fileDetail.shortName) + this.#list_allMods[collectKey].alphaSort.push(`${thisModRecord.fileDetail.shortName}::${thisModRecord.uuid}`) + } + + #processBindConflicts() { + this.#bindConflict = {} + + this.#set_Collections.forEach((collectKey) => { + this.#bindConflict[collectKey] = {} + + const collectionBinds = {} + + this.#list_allMods[collectKey].modSet.forEach((modUUID) => { + const thisMod = this.#list_allMods[collectKey].mods[modUUID] + + Object.keys(thisMod.modDesc.binds).forEach((actName) => { + thisMod.modDesc.binds[actName].forEach((keyCombo) => { + if ( keyCombo === '' ) { return } + + const safeCat = thisMod.modDesc.actions[actName] || 'UNKNOWN' + const thisCombo = `${safeCat}--${keyCombo}` + + collectionBinds[thisCombo] ??= [] + collectionBinds[thisCombo].push(thisMod.fileDetail.shortName) + }) + }) + }) + + Object.keys(collectionBinds).forEach((keyCombo) => { + if ( collectionBinds[keyCombo].length > 1 ) { + collectionBinds[keyCombo].forEach((modName) => { + this.#bindConflict[collectKey][modName] ??= {} + this.#bindConflict[collectKey][modName][keyCombo] = collectionBinds[keyCombo].filter((w) => w !== modName) + if ( this.#bindConflict[collectKey][modName][keyCombo].length === 0 ) { + delete this.#bindConflict[collectKey][modName][keyCombo] + } + }) + } + }) + }) + + Object.keys(this.#bindConflict).forEach((collectKey) => { + Object.keys(this.#bindConflict[collectKey]).forEach((modName) => { + if ( Object.keys(this.#bindConflict[collectKey][modName]).length === 0 ) { + delete this.#bindConflict[collectKey][modName] + } + }) + }) + } + + #addMod(collectKey, thisFile) { + return new Promise((resolve) => { + let isDone = false + const thisFileStats = this.#fileStats(collectKey, thisFile) + + // Check cache + const modInCache = this.#cacheHit(thisFileStats) + if ( modInCache ) { + this.#log.log.debug(`Adding mod FROM cache: ${modInCache.fileDetail.shortName}`, `mod-${modInCache.uuid}`) + this.#addModToData(collectKey, modInCache) + this.#loadingWindow.count() + isDone = true + resolve(true) + } + + if ( !isDone && !thisFileStats.isFolder && !thisFile.name.endsWith('.zip') ) { + const thisModRecord = new notModFileChecker( + thisFileStats.fullPath, + false, + thisFileStats.size, + thisFileStats.date, + this.#log + ) + this.#addModToData(collectKey, thisModRecord) + this.#loadingWindow.count() + isDone = true + resolve(true) + } + + if ( !isDone) { + try { + const thisModRecord = new modFileChecker( + thisFileStats.fullPath, + thisFileStats.isFolder, + thisFileStats.size, + (this.#useSyncSafeMode) ? thisFileStats.birthDate : thisFileStats.date, + thisFileStats.hashString, + this.#log, + this.#localeFunction + ) + + thisModRecord.doTests().then(() => { + const thisModRecordStore = thisModRecord.storable + + this.#addModToData(collectKey, thisModRecordStore) + + if ( thisFileStats.hashString !== null ) { + this.#log.log.info('Adding mod to cache', `mod-${thisModRecordStore.uuid}`) + this.#list_newMods.add(thisFileStats.hashString) + this.#modCacheStore[thisFileStats.hashString] = thisModRecordStore + } + }) + } catch (e) { + this.#log.log.danger(`Couldn't process file: ${thisFileStats.fullPath} :: ${e}`, 'collection-reader') + const thisModRecord = new notModFileChecker( + thisFileStats.fullPath, + false, + thisFileStats.size, + thisFileStats.date, + this.#log + ) + this.#addModToData(collectKey, thisModRecord) + } finally { + this.#loadingWindow.count() + resolve(true) + } + } + }) + } + + async toRenderer(extra = null) { + this.#log.log.debug('Collection Render Return Called', 'collection-reader') + + return Promise.all(this.#scanPromise).then(() => { + this.#log.log.debug('Collection Render Return Firing', 'collection-reader') + + return { + currentLocale : this.#localeFunction(), + opts : extra, + bindConflict : this.#bindConflict, + modList : this.#list_allMods, + set_Collections : this.#set_Collections, + collectionToFolder : this.#map_CollectionToFolder, + collectionToFolderRelative : this.#map_CollectionToFolderRelative, + folderToCollection : this.#map_FolderToCollection, + collectionToName : this.#map_CollectionToName, + collectionToFullName : this.#map_CollectionToFullName, + collectionNotes : this.#map_CollectionNotes.store, + newMods : this.#list_newMods, + modHub : { + list : this.#modHubList, + version : this.#modHubVersion, + }, + } + }) + } + + #toHomeDir(folder) { + return folder.replaceAll(this.#userHome, '~') + } + + #populateFullName(collectKey, shortName) { + const tagLine = this.#map_CollectionNotes.get(`${collectKey}.notes_tagline`, null) + + return `${shortName}${tagLine === null ? '' : ` [${tagLine}]`}` + } + + #getMD5Hash(text, prefix = '') { + return `${prefix}${crypto.createHash('md5').update(text).digest('hex')}` + } +} class modFileChecker { #maxFilesType = { grle : 10, png : 128, txt : 2, pdf : 1 } @@ -153,42 +615,41 @@ class modFileChecker { #log = null #logUUID = null - constructor( filePath, isFolder, size, date, log = null, locale = null ) { + constructor( filePath, isFolder, size, date, md5Pre = null, log = null, locale = null ) { this.fileDetail.fullPath = filePath this.fileDetail.isFolder = isFolder this.fileDetail.fileSize = size this.fileDetail.fileDate = date.toISOString() - this.uuid = crypto.createHash('md5').update(filePath).digest('hex') - this.#locale = locale this.#log = log - this.#logUUID = `mod-${this.uuid}` + + this.md5Sum = md5Pre this.fileDetail.shortName = path.parse(this.fileDetail.fullPath).name - this.#log.log.info(`Adding Mod File: ${this.fileDetail.shortName}`, this.#logUUID) - this.#failFlags.folder_needs_zip = this.fileDetail.isFolder + } + async doTests() { + this.uuid = crypto.createHash('md5').update(this.fileDetail.fullPath).digest('hex') + this.#logUUID = `mod-${this.uuid}` + this.#log.log.info(`Adding Mod File: ${this.fileDetail.shortName}`, this.#logUUID) + if ( ! this.#isFileNameBad() ) { if ( ! this.fileDetail.isFolder ) { - const hashString = `${path.basename(this.fileDetail.fullPath)}-${this.fileDetail.fileSize}-${this.fileDetail.fileDate}` - this.md5Sum = crypto.createHash('md5').update(hashString).digest('hex') + this.#testZip().then(() => { + this.#doneTest() + }) - this.#testZip() - - if ( this.#failFlags.no_modDesc ) { this.md5Sum = null } - } else { - this.#testFolder() + this.#testFolder().then(() => { + this.#doneTest() + }) } + } else { + this.#doneTest() } - - this.populateL10n() - this.badgeArray = this.#getBadges() - this.issues = this.#populateIssues() - this.currentLocal = this.#locale() } get debugDump() { @@ -457,8 +918,14 @@ class modFileChecker { return true } + #doneTest() { + this.populateL10n() + this.badgeArray = this.#getBadges() + this.issues = this.#populateIssues() + this.currentLocal = this.#locale() + } - #testZip() { + async #testZip() { let zipFile = null let zipEntries = null @@ -511,11 +978,12 @@ class modFileChecker { this.#log.log.notice(`Caught icon fail: ${e}`, this.#logUUID) } + if ( this.#failFlags.no_modDesc ) { this.md5Sum = null } + zipFile = null } - - #testFolder() { + async #testFolder() { if ( ! fs.existsSync(path.join(this.fileDetail.fullPath, 'modDesc.xml')) ) { this.#failFlags.no_modDesc = true return false @@ -560,7 +1028,6 @@ class modFileChecker { } } - #nestedXMLProperty (propertyPath, passedObj = false) { if (!propertyPath) { return false } @@ -672,16 +1139,15 @@ class modFileChecker { // convert the DXT texture to an Uint8Array containing RGBA data const rgbaData = decodeDXT(imageDataView, imageWidth, imageHeight, ddsData.format) - // make a new PNG image of same width and height, pipe in raw RGBA data - const pngData = new PNG({ width : imageWidth, height : imageHeight }) - - pngData.data = rgbaData + // convert to JPEG + const jpgData = JPEG.encode({ + width : imageWidth, + height : imageHeight, + data : rgbaData, + }, 70) try { - // Dump out PNG, base64 encode it. - const pngBuffer = PNG.sync.write(pngData) - - this.modDesc.iconImageCache = `data:image/png;base64, ${pngBuffer.toString('base64')}` + this.modDesc.iconImageCache = `data:image/jpeg;base64, ${jpgData.data.toString('base64')}` } catch { this.modDesc.iconImageCache = null return false @@ -764,6 +1230,7 @@ class notModFileChecker { } module.exports = { + modFileCollection : modFileCollection, modFileChecker : modFileChecker, notModFileChecker : notModFileChecker, } diff --git a/modAssist_main.js b/modAssist_main.js index f7be1837..109e4f1b 100644 --- a/modAssist_main.js +++ b/modAssist_main.js @@ -31,43 +31,26 @@ log.log.info(` - Electron Version: ${process.versions.electron}`) log.log.info(` - Chrome Version: ${process.versions.chrome}`) - -process.on('uncaughtException', (err, origin) => { +function handleUnhandled(type, err, origin) { const rightNow = new Date() fs.appendFileSync( crashLog, - `Exception Timestamp : ${rightNow.toISOString()}\n\nCaught exception: ${err}\n\nException origin: ${origin}\n\n${err.stack}` + `${type} Timestamp : ${rightNow.toISOString()}\n\nCaught ${type}: ${err}\n\nOrigin: ${origin}\n\n${err.stack}` ) if ( !isNetworkError(err) ) { dialog.showMessageBoxSync(null, { - title : 'Uncaught Error - Quitting', - message : `Caught exception: ${err}\n\nException origin: ${origin}\n\n${err.stack}\n\n\nCan't Continue, exiting now!\n\nTo send file, please see ${crashLog}`, + title : `Uncaught ${type} - Quitting`, + message : `Caught ${type}: ${err}\n\nOrigin: ${origin}\n\n${err.stack}\n\n\nCan't Continue, exiting now!\n\nTo send file, please see ${crashLog}`, type : 'error', }) - gameLogFile.close() + if ( gameLogFile ) { gameLogFile.close() } app.quit() } else { - log.log.debug(`Network error: ${err}`, 'net-error-exception') + log.log.debug(`Network error: ${err}`, `net-error-${type}`) } -}) -process.on('unhandledRejection', (err, origin) => { - const rightNow = new Date() - fs.appendFileSync( - crashLog, - `Rejection Timestamp : ${rightNow.toISOString()}\n\nCaught rejection: ${err}\n\nRejection origin: ${origin}\n\n${err.stack}` - ) - if ( !isNetworkError(err) ) { - dialog.showMessageBoxSync(null, { - title : 'Uncaught Error - Quitting', - message : `Caught rejection: ${err}\n\nRejection origin: ${origin}\n\n${err.stack}\n\n\nCan't Continue, exiting now!\n\nTo send file, please see ${crashLog}`, - type : 'error', - }) - gameLogFile.close() - app.quit() - } else { - log.log.debug(`Network error: ${err}`, 'net-error-rejection') - } -}) +} +process.on('uncaughtException', (err, origin) => { handleUnhandled('exception', err, origin) }) +process.on('unhandledRejection', (err, origin) => { handleUnhandled('rejection', err, origin) }) const translator = require('./lib/translate.js') const myTranslator = new translator.translator(translator.getSystemLocale()) @@ -106,7 +89,7 @@ if ( process.platform === 'win32' && app.isPackaged && gotTheLock && !isPortable dialog.showMessageBox(windows.main, dialogOpts).then((returnValue) => { if (returnValue.response === 0) { if ( tray ) { tray.destroy() } - gameLogFile.close() + if ( gameLogFile ) { gameLogFile.close() } Object.keys(windows).forEach((thisWin) => { if ( thisWin !== 'main' && windows[thisWin] !== null ) { windows[thisWin].destroy() @@ -124,10 +107,7 @@ if ( process.platform === 'win32' && app.isPackaged && gotTheLock && !isPortable }, ( 30 * 60 * 1000)) } -const glob = require('glob') -const fxml = require('fast-xml-parser') -const crypto = require('crypto') - +const fxml = require('fast-xml-parser') const userHome = require('os').homedir() const pathRender = path.join(app.getAppPath(), 'renderer') const pathPreload = path.join(pathRender, 'preload') @@ -151,14 +131,10 @@ const gameGuesses = [ 'C:\\Program Files (x86)\\Steam\\steamapps\\common\\Farming Simulator 22' ] const pathGuesses = [ + path.join(app.getPath('documents'), 'My Games', 'FarmingSimulator2022'), path.join(userHome, 'OneDrive', 'Documents', 'My Games', 'FarmingSimulator2022'), path.join(userHome, 'Documents', 'My Games', 'FarmingSimulator2022') ] -try { - const winUtil = require('windows') - const userFolder = winUtil.registry('HKEY_CURRENT_USER/SOFTWARE/Microsoft/Windows/CurrentVersion/Explorer/User Shell Folders').Personal.value - pathGuesses.unshift(path.join(userFolder, 'My Games', 'FarmingSimulator2022')) -} catch { /* do nothing */ } gameGuesses.forEach((testPath) => { if ( fs.existsSync(path.join(testPath, gameExeName)) ) { @@ -173,7 +149,7 @@ pathGuesses.forEach((testPath) => { } }) -const { modFileChecker, notModFileChecker } = require('./lib/single-mod-checker.js') +const { modFileCollection } = require('./lib/modCheckLib.js') const winDef = (w, h) => { return { x : { type : 'number', default : -1 }, @@ -234,32 +210,24 @@ const mcStore = new Store({schema : settingsSchema, migrations : settingsMig, cl const maCache = new Store({name : 'mod_cache', clearInvalidConfig : true}) const modNote = new Store({name : 'col_notes', clearInvalidConfig : true}) -const newModsList = [] - -let modFolders = new Set() -let modFoldersMap = {} -let modList = {} -let bindConflict = {} -let countTotal = 0 -let countMods = 0 -let modHubList = {'mods' : {}, 'last' : []} -let modHubVersion = {} -let lastFolderLoc = null -let lastGameSettings = {} - +const modCollect = new modFileCollection( + log, + modNote, + maCache, + app.getPath('home'), + { + hide : loadingWindow_hide, + count : loadingWindow_current, + }, + myTranslator.deferCurrentLocale, + skipCache +) -const ignoreList = [ - '^npm-debug\\.log$', - '^\\..*\\.swp$', - '^Thumbs\\.db$', - '^thumbs\\.db$', - '^ehthumbs\\.db$', - '^Desktop\\.ini$', - '^desktop\\.ini$', - '@eaDir$', -] +const loadWindowCount = { total : 0, current : 0} -const junkRegex = new RegExp(ignoreList.join('|')) +let modFolders = new Set() +let lastFolderLoc = null +let lastGameSettings = {} let tray = null const windows = { @@ -332,14 +300,14 @@ mcStore.set('cache_version', app.getVersion()) function debugDangerCallback() { - if ( windows.main !== null ) { - windows.main.webContents.send('fromMain_debugLogDanger') - } + if ( windows.main !== null ) { windows.main.webContents.send('fromMain_debugLogDanger') } } + /* _ _ ____ _ _ ____ _____ _ _ ___ ( \/\/ )(_ _)( \( )( _ \ ( _ )( \/\/ )/ __) ) ( _)(_ ) ( )(_) ) )(_)( ) ( \__ \ (__/\__)(____)(_)\_)(____/ (_____)(__/\__)(___/ */ + function destroyAndFocus(winName) { windows[winName] = null if ( windows.main !== null ) { windows.main.focus() } @@ -408,7 +376,7 @@ function createSubWindow(winName, {noSelect = true, show = true, parent = null, event.preventDefault() } if ( input.alt && input.control && input.code === 'KeyD' ) { - createDebugWindow() + createNamedWindow('debug') event.preventDefault() } }) @@ -524,7 +492,7 @@ function createMainWindow () { event.preventDefault() } if ( input.alt && input.control && input.code === 'KeyD' ) { - createDebugWindow() + createNamedWindow('debug') event.preventDefault() } }) @@ -535,245 +503,151 @@ function createMainWindow () { }) } -function createConfirmFav(mods, destinations) { - if ( mods.length < 1 ) { return } - if ( windows.confirm ) { windows.confirm.focus(); return } - - windows.confirm = createSubWindow('confirm', { parent : 'main', preload : 'confirmMulti', fixed : true }) - - windows.confirm.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_confirmList', mods, destinations, modList) - }) - - windows.confirm.loadFile(path.join(pathRender, 'confirm-multi.html')) - - windows.confirm.on('closed', () => { destroyAndFocus('confirm') }) -} +function createNamedWindow(winName, windowArgs) { + const subWinDef = subWindows[winName] + const thisWindow = subWinDef.winName -function createConfirmWindow(type, modRecords, origList) { - if ( modRecords.length < 1 ) { return } - if ( windows.confirm ) { windows.confirm.focus(); return } - - const file_HTML = `confirm-file${type.charAt(0).toUpperCase()}${type.slice(1)}.html` - const file_JS = `confirm${type.charAt(0).toUpperCase()}${type.slice(1)}` - const collection = origList[0].split('--')[0] - - windows.confirm = createSubWindow('confirm', { parent : 'main', preload : file_JS, fixed : true }) - - windows.confirm.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_confirmList', { - records : modRecords, - list : modList, - foldersMap : modFoldersMap, - collection : collection, - }) - }) - - windows.confirm.loadFile(path.join(pathRender, file_HTML)) - - windows.confirm.on('closed', () => { destroyAndFocus('confirm') }) -} - -function createChangeLogWindow() { - if ( windows.change ) { - windows.change.focus() + if ( windows[thisWindow] ) { + windows[thisWindow].focus() + if ( subWinDef.refocusCallback ) { subWinDef.callback(windowArgs) } return } - windows.change = createSubWindow('change', { parent : 'main', fixed : true, preload : 'aChangelogWindow' }) + windows[thisWindow] = createSubWindow(subWinDef.winName, subWinDef.subWindowArgs) - windows.change.loadFile(path.join(pathRender, 'a_changelog.html')) - windows.change.on('closed', () => { destroyAndFocus('change') }) -} + windows[thisWindow].webContents.on('did-finish-load', async () => { + subWinDef.callback(windowArgs) -function createFolderWindow() { - if ( windows.folder ) { - windows.folder.focus() - windows.folder.webContents.send('fromMain_getFolders', modList) - return - } - - windows.folder = createSubWindow('folder', { parent : 'main', preload : 'folderWindow' }) - - windows.folder.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_getFolders', modList) - }) - - windows.folder.loadFile(path.join(pathRender, 'folders.html')) - windows.folder.on('closed', () => { destroyAndFocus('folder'); processModFolders() }) -} - -function createDetailWindow(thisModRecord) { - if ( thisModRecord === null ) { return } - const modhubRecord = modRecordToModHub(thisModRecord) - - if ( windows.detail ) { - windows.detail.focus() - windows.detail.webContents.send('fromMain_modRecord', thisModRecord, modhubRecord, bindConflict, myTranslator.currentLocale) - return - } - - windows.detail = createSubWindow('detail', { parent : 'main', preload : 'detailWindow' }) - - windows.detail.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_modRecord', thisModRecord, modhubRecord, bindConflict, myTranslator.currentLocale) - if ( devDebug ) { windows.detail.webContents.openDevTools() } - }) - - windows.detail.loadFile(path.join(pathRender, 'detail.html')) - windows.detail.on('closed', () => { destroyAndFocus('detail') }) - - windows.detail.webContents.setWindowOpenHandler(({ url }) => { - shell.openExternal(url) - return { action : 'deny' } - }) -} - -function createFindWindow() { - if ( windows.find ) { - windows.find.focus() - windows.find.webContents.send('fromMain_modRecords', modList) - return - } - - windows.find = createSubWindow('find', { preload : 'findWindow' }) - - windows.find.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_modRecords', modList) - if ( devDebug ) { windows.find.webContents.openDevTools() } - }) - - windows.find.loadFile(path.join(pathRender, 'find.html')) - windows.find.on('closed', () => { destroyAndFocus('find') }) -} - -function createDebugWindow() { - if ( windows.debug ) { - windows.debug.focus() - windows.debug.webContents.send('fromMain_debugLog', log.htmlLog) - return - } - - windows.debug = createSubWindow('debug', { preload : 'debugWindow' }) - - windows.debug.webContents.on('did-finish-load', (event) => { - event.sender.send('fromMain_debugLog', log.htmlLog) - }) - - windows.debug.loadFile(path.join(app.getAppPath(), 'renderer', 'debug.html')) - windows.debug.on('closed', () => { destroyAndFocus('debug') }) -} -function createGameLogWindow() { - if ( windows.gamelog ) { - windows.gamelog.focus() - readGameLog() - return - } - - windows.gamelog = createSubWindow('gamelog', { preload : 'gamelogWindow' }) - - windows.gamelog.webContents.on('did-finish-load', () => { - readGameLog() - if ( devDebug ) { windows.gamelog.webContents.openDevTools() } - }) - - windows.gamelog.loadFile(path.join(app.getAppPath(), 'renderer', 'gamelog.html')) - windows.gamelog.on('closed', () => { destroyAndFocus('gamelog') }) -} - -function createPrefsWindow() { - if ( windows.prefs ) { - windows.prefs.focus() - windows.prefs.webContents.send( 'fromMain_allSettings', mcStore.store, devControls ) - return - } - - windows.prefs = createSubWindow('prefs', { parent : 'main', preload : 'prefsWindow', title : myTranslator.syncStringLookup('user_pref_title_main') }) - - windows.prefs.webContents.on('did-finish-load', (event) => { - event.sender.send( 'fromMain_allSettings', mcStore.store, devControls ) - }) - - windows.prefs.loadFile(path.join(pathRender, 'prefs.html')) - windows.prefs.on('closed', () => { destroyAndFocus('prefs') }) - - windows.prefs.webContents.setWindowOpenHandler(({ url }) => { - shell.openExternal(url) - return { action : 'deny' } + if ( devDebug && subWindowDev.has(subWinDef.winName) ) { + windows[thisWindow].webContents.openDevTools() + } }) -} - -function createSavegameWindow(collection) { - if ( windows.save ) { - windows.save.focus() - windows.save.webContents.send('fromMain_collectionName', collection, modList) - return - } - windows.save = createSubWindow('save', { preload : 'savegameWindow' }) + windows[thisWindow].loadFile(path.join(pathRender, subWinDef.HTMLFile)) - windows.save.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_collectionName', collection, modList) - if ( devDebug ) { windows.save.webContents.openDevTools() } + windows[thisWindow].on('closed', () => { + destroyAndFocus(subWinDef.winName) + if ( typeof subWinDef.extraCloseFunc === 'function' ) { + subWinDef.extraCloseFunc() + } + }) - windows.save.loadFile(path.join(pathRender, 'savegame.html')) - windows.save.on('closed', () => { destroyAndFocus('save') }) -} - -function createNotesWindow(collection) { - if ( windows.notes ) { - windows.notes.focus() - windows.notes.webContents.send('fromMain_collectionName', collection, modList[collection].name, modNote.store, lastGameSettings) - return + if ( subWinDef.handleURLinWin ) { + windows[thisWindow].webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url) + return { action : 'deny' } + }) } - - windows.notes = createSubWindow('notes', { parent : 'main', preload : 'notesWindow' }) - - windows.notes.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_collectionName', collection, modList[collection].name, modNote.store, lastGameSettings) - if ( devDebug ) { windows.notes.webContents.openDevTools() } - }) - - windows.notes.loadFile(path.join(pathRender, 'notes.html')) - windows.notes.on('closed', () => { destroyAndFocus('notes'); processModFolders() }) } -function createResolveWindow(modSet, shortName) { - if ( windows.resolve ) { - windows.resolve.webContents.send('fromMain_modSet', modSet, shortName) - windows.resolve.focus() - return - } - - windows.resolve = createSubWindow('resolve', { parent : 'version', preload : 'resolveWindow', fixed : true }) - - windows.resolve.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_modSet', modSet, shortName) - if ( devDebug ) { windows.resolve.webContents.openDevTools() } - }) - - windows.resolve.loadFile(path.join(pathRender, 'resolve.html')) - windows.resolve.on('closed', () => { destroyAndFocus('resolve') }) +const subWindowDev = new Set(['save', 'find', 'detail', 'notes', 'version', 'resolve']) +const subWindows = { + confirmFav : { + winName : 'confirm', + HTMLFile : 'confirm-multi.html', + subWindowArgs : { parent : 'main', preload : 'confirmMulti', fixed : true }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_confirmList', 'confirm', false) }, + }, + confirmCopy : { + winName : 'confirm', + HTMLFile : 'confirm-fileCopy.html', + subWindowArgs : { parent : 'main', preload : 'confirmCopy', fixed : true }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_confirmList', 'confirm', false) }, + }, + confirmMove : { + winName : 'confirm', + HTMLFile : 'confirm-fileMove.html', + subWindowArgs : { parent : 'main', preload : 'confirmMove', fixed : true }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_confirmList', 'confirm', false) }, + }, + confirmDelete : { + winName : 'confirm', + HTMLFile : 'confirm-fileDelete.html', + subWindowArgs : { parent : 'main', preload : 'confirmDelete', fixed : true }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_confirmList', 'confirm', false) }, + }, + change : { + winName : 'change', + HTMLFile : 'a_changelog.html', + subWindowArgs : { parent : 'main', fixed : true, preload : 'aChangelogWindow' }, + callback : () => { return }, + }, + folder : { + winName : 'folder', + HTMLFile : 'folders.html', + subWindowArgs : { parent : 'main', preload : 'folderWindow' }, + callback : () => { sendModList({}, 'fromMain_getFolders', 'folder', false ) }, + refocusCallback : true, + extraCloseFunc : () => { processModFolders() }, + }, + debug : { + winName : 'debug', + HTMLFile : 'debug.html', + subWindowArgs : { preload : 'debugWindow' }, + callback : () => { windows.debug.webContents.send('fromMain_debugLog', log.htmlLog) }, + refocusCallback : true, + }, + gamelog : { + winName : 'gamelog', + HTMLFile : 'gamelog.html', + subWindowArgs : { preload : 'gamelogWindow' }, + callback : () => { readGameLog() }, + refocusCallback : true, + }, + prefs : { + winName : 'prefs', + HTMLFile : 'prefs.html', + subWindowArgs : { parent : 'main', preload : 'prefsWindow' }, + callback : () => { windows.prefs.webContents.send( 'fromMain_allSettings', mcStore.store, devControls ) }, + refocusCallback : true, + handleURLinWin : true, + }, + detail : { + winName : 'detail', + HTMLFile : 'detail.html', + subWindowArgs : { parent : 'main', preload : 'detailWindow' }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_modRecord', 'detail', false) }, + refocusCallback : true, + handleURLinWin : true, + }, + find : { + winName : 'find', + HTMLFile : 'find.html', + subWindowArgs : { preload : 'findWindow' }, + callback : () => { sendModList({}, 'fromMain_modRecords', 'find', false ) }, + refocusCallback : true, + }, + notes : { + winName : 'notes', + HTMLFile : 'notes.html', + subWindowArgs : { parent : 'main', preload : 'notesWindow' }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_collectionName', 'notes', false ) }, + refocusCallback : true, + }, + version : { + winName : 'version', + HTMLFile : 'versions.html', + subWindowArgs : { parent : 'main', preload : 'versionWindow' }, + callback : () => { sendModList({}, 'fromMain_modList', 'version', false ) }, + refocusCallback : true, + }, + resolve : { + winName : 'resolve', + HTMLFile : 'resolve.html', + subWindowArgs : { parent : 'version', preload : 'resolveWindow', fixed : true }, + callback : (windowArgs) => { windows.resolve.webContents.send('fromMain_modSet', windowArgs.modSet, windowArgs.shortName) }, + refocusCallback : true, + }, + save : { + winName : 'save', + HTMLFile : 'savegame.html', + subWindowArgs : { preload : 'savegameWindow' }, + callback : (windowArgs) => { sendModList(windowArgs, 'fromMain_collectionName', 'save', false ) }, + refocusCallback : true, + }, } -function createVersionWindow() { - if ( windows.version ) { - windows.version.webContents.send('fromMain_modList', modList) - windows.version.focus() - return - } - - windows.version = createSubWindow('version', { parent : 'main', preload : 'versionWindow' }) - - windows.version.webContents.on('did-finish-load', async (event) => { - event.sender.send('fromMain_modList', modList) - if ( devDebug ) { windows.version.webContents.openDevTools() } - }) - - windows.version.loadFile(path.join(pathRender, 'versions.html')) - windows.version.on('closed', () => { destroyAndFocus('version') }) -} function loadingWindow_open(l10n) { const newCenter = getRealCenter('load') @@ -796,19 +670,18 @@ function loadingWindow_open(l10n) { return } } -function loadingWindow_total(amount, reset = false, inMB = false) { - countTotal = ( reset ) ? amount : amount + countTotal +function loadingWindow_doCount(whichCount, amount, reset, inMB) { + loadWindowCount[whichCount] = ( reset ) ? amount : amount + loadWindowCount[whichCount] if ( ! windows.load.isDestroyed() ) { - windows.load.webContents.send('fromMain_loadingTotal', countTotal, inMB) + windows.load.webContents.send(`fromMain_loading_${whichCount}`, loadWindowCount[whichCount], inMB) } } +function loadingWindow_total(amount, reset = false, inMB = false) { + loadingWindow_doCount('total', amount, reset, inMB) +} function loadingWindow_current(amount = 1, reset = false, inMB = false) { - countMods = ( reset ) ? amount : amount + countMods - - if ( ! windows.load.isDestroyed() ) { - windows.load.webContents.send('fromMain_loadingCurrent', countMods, inMB) - } + loadingWindow_doCount('current', amount, reset, inMB) } function loadingWindow_hide(time = 1250) { setTimeout(() => { @@ -823,16 +696,7 @@ function loadingWindow_noCount() { } } -function isNetworkError(errorObject) { - return errorObject.message.startsWith('net::ERR_')// || - // errorObject.message === 'net::ERR_INTERNET_DISCONNECTED' || - // errorObject.message === 'net::ERR_PROXY_CONNECTION_FAILED' || - // errorObject.message === 'net::ERR_CONNECTION_RESET' || - // errorObject.message === 'net::ERR_CONNECTION_CLOSE' || - // errorObject.message === 'net::ERR_NAME_NOT_RESOLVED' || - // errorObject.message === 'net::ERR_CONNECTION_TIMED_OUT' || - // errorObject.message === 'net::ERR_SSL_PROTOCOL_ERROR' -} +function isNetworkError(errorObject) { return errorObject.message.startsWith('net::ERR_') } /* ____ ____ ___ (_ _)( _ \ / __) @@ -845,23 +709,25 @@ ipcMain.on('toMain_populateClipboard', (event, text) => { clipboard.writeText(te ipcMain.on('toMain_makeInactive', () => { parseSettings({ disable : true }) }) ipcMain.on('toMain_makeActive', (event, newList) => { parseSettings({ - newFolder : modFoldersMap[newList], + newFolder : modCollect.mapCollectionToFolder(newList), userName : modNote.get(`${newList}.notes_username`, null), password : modNote.get(`${newList}.notes_password`, null), serverName : modNote.get(`${newList}.notes_server`, null), }) }) + ipcMain.on('toMain_openMods', (event, mods) => { - const thisCollectionFolder = modFoldersMap[mods[0].split('--')[0]] - const thisMod = modIdToRecord(mods[0]) + const thisCollectionFolder = modCollect.mapCollectionToFolder(mods[0].split('--')[0]) + const thisMod = modCollect.modColUUIDToRecord(mods[0]) if ( thisMod !== null ) { shell.showItemInFolder(path.join(thisCollectionFolder, path.basename(thisMod.fileDetail.fullPath))) } }) + ipcMain.on('toMain_openHub', (event, mods) => { - const thisMod = modIdToRecord(mods[0]) - const thisModId = modHubList.mods[thisMod.fileDetail.shortName] || null + const thisMod = modCollect.modColUUIDToRecord(mods[0]) + const thisModId = thisMod.modHub.id if ( thisModId !== null ) { shell.openExternal(`https://www.farming-simulator.com/mod.php?mod_id=${thisModId}`) @@ -869,38 +735,64 @@ ipcMain.on('toMain_openHub', (event, mods) => { }) ipcMain.on('toMain_copyFavorites', () => { - const favCols = [] - const sourceFiles = [] - let destCols = Object.keys(modList) + const sourceCollections = [] + const destinationCollections = [] + const sourceFiles = [] + + modCollect.collections.forEach((collectKey) => { + const isFavorite = modNote.get(`${collectKey}.notes_favorite`, false) - Object.keys(modNote.store).forEach((collection) => { - if ( modNote.get(`${collection}.notes_favorite`, false) === true ) { favCols.push(collection) } + if ( isFavorite ) { + sourceCollections.push(collectKey) + } else { + destinationCollections.push(collectKey) + } }) - favCols.forEach((collection) => { - destCols = destCols.filter((item) => item !== collection ) - modList[collection].mods.forEach((thisMod) => { - sourceFiles.push([ - thisMod.fileDetail.fullPath, - collection, - thisMod.fileDetail.shortName, - thisMod.l10n.title - ]) + sourceCollections.forEach((collectKey) => { + const thisCollection = modCollect.getModCollection(collectKey) + thisCollection.modSet.forEach((modKey) => { + sourceFiles.push({ + fullPath : thisCollection.mods[modKey].fileDetail.fullPath, + collectKey : collectKey, + shortName : thisCollection.mods[modKey].fileDetail.shortName, + title : thisCollection.mods[modKey].l10n.title, + }) }) }) - createConfirmFav(sourceFiles, destCols) + if ( sourceFiles.length > 0 ) { + //createConfirmFav({ + createNamedWindow( + 'confirmFav', + { + sourceFiles : sourceFiles, + destinations : destinationCollections, + sources : sourceCollections, + } + ) + } }) -ipcMain.on('toMain_deleteMods', (event, mods) => { createConfirmWindow('delete', modIdsToRecords(mods), mods) }) -ipcMain.on('toMain_moveMods', (event, mods) => { createConfirmWindow('move', modIdsToRecords(mods), mods) }) -ipcMain.on('toMain_copyMods', (event, mods) => { createConfirmWindow('copy', modIdsToRecords(mods), mods) }) + +function handleCopyMoveDelete(windowName, modIDS, modRecords = null) { + if ( modIDS.length > 0 ) { + createNamedWindow(windowName, { + records : ( modRecords === null ) ? modCollect.modColUUIDsToRecords(modIDS) : modRecords, + originCollectKey : modIDS[0].split('--')[0], + }) + } +} + +ipcMain.on('toMain_deleteMods', (event, mods) => { handleCopyMoveDelete('confirmDelete', mods) }) +ipcMain.on('toMain_moveMods', (event, mods) => { handleCopyMoveDelete('confirmMove', mods) }) +ipcMain.on('toMain_copyMods', (event, mods) => { handleCopyMoveDelete('confirmCopy', mods) }) ipcMain.on('toMain_realFileDelete', (event, fileMap) => { fileOperation('delete', fileMap) }) ipcMain.on('toMain_realFileMove', (event, fileMap) => { fileOperation('move', fileMap) }) ipcMain.on('toMain_realFileCopy', (event, fileMap) => { fileOperation('copy', fileMap) }) ipcMain.on('toMain_realFileVerCP', (event, fileMap) => { fileOperation('copy', fileMap, 'resolve') setTimeout(() => { - windows.version.webContents.send('fromMain_modList', modList) + sendModList({}, 'fromMain_modList', 'version', false ) }, 1500) }) /** END: File operation buttons */ @@ -934,93 +826,77 @@ ipcMain.on('toMain_addFolder', () => { log.log.danger(`Could not read specified add folder : ${unknownError}`, 'folder-opts') }) }) -ipcMain.on('toMain_editFolders', () => { createFolderWindow() }) +ipcMain.on('toMain_editFolders', () => { createNamedWindow('folder') }) ipcMain.on('toMain_openFolder', (event, folder) => { shell.openPath(folder) }) ipcMain.on('toMain_refreshFolders', () => { foldersDirty = true; processModFolders() }) ipcMain.on('toMain_removeFolder', (event, folder) => { if ( modFolders.delete(folder) ) { log.log.notice(`Folder removed from list ${folder}`, 'folder-opts') mcStore.set('modFolders', Array.from(modFolders)) - Object.keys(modList).forEach((collection) => { - if ( modList[collection].fullPath === folder ) { delete modList[collection] } - }) - Object.keys(modFoldersMap).forEach((collection) => { - if ( modFoldersMap[collection] === folder ) { delete modFoldersMap[collection]} - }) - windows.folder.webContents.send('fromMain_getFolders', modList) - foldersDirty = true + const collectKey = modCollect.mapFolderToCollection(folder) + + modCollect.removeCollection(collectKey) + + sendModList({}, 'fromMain_getFolders', 'folder', false ) + + foldersDirty = true } else { log.log.warning(`Folder NOT removed from list ${folder}`, 'folder-opts') } }) - ipcMain.on('toMain_reorderFolder', (event, from, to) => { - const newOrder = Array.from(modFolders) - const item = newOrder.splice(from, 1)[0] - newOrder.splice(to, 0, item) - - const reorder_modList = {} - const reorder_modFoldersMap = {} + const newOrder = Array.from(modFolders) + const newSetOrder = new Set() + const item = newOrder.splice(from, 1)[0] + newOrder.splice(to, 0, item) newOrder.forEach((path) => { - Object.keys(modFoldersMap).forEach((collection) => { - if ( modFoldersMap[collection] === path ) { - reorder_modFoldersMap[collection] = modFoldersMap[collection] - } - }) - Object.keys(modList).forEach((collection) => { - if ( modList[collection].fullPath === path ) { - reorder_modList[collection] = modList[collection] - } - }) + newSetOrder.add(modCollect.mapFolderToCollection(path)) }) - modFolders = new Set(newOrder) - modList = reorder_modList - modFoldersMap = reorder_modFoldersMap + modFolders = new Set(newOrder) + modCollect.newCollectionOrder = newSetOrder mcStore.set('modFolders', Array.from(modFolders)) - windows.folder.webContents.send('fromMain_getFolders', modList) + sendModList({}, 'fromMain_getFolders', 'folder', false ) foldersDirty = true }) -/** END: Folder Window Operation */ - - ipcMain.on('toMain_reorderFolderAlpha', () => { const newOrder = [] const collator = new Intl.Collator() - Object.keys(modList).forEach((collection) => { - newOrder.push({name : modList[collection].name, collection : collection}) + modCollect.collections.forEach((collectKey) => { + newOrder.push({ + path : modCollect.mapCollectionToFolder(collectKey), + name : modCollect.mapCollectionToName(collectKey), + collectKey : collectKey, + }) }) newOrder.sort((a, b) => collator.compare(a.name, b.name) || - collator.compare(a.collection, b.collection) + collator.compare(a.collectKey, b.collectKey) ) const newModFolders = new Set() - const newModList = {} - const newModFoldersMap = {} + const newModSetOrder = new Set() - newOrder.forEach((order) => { - newModFolders.add(modFoldersMap[order.collection]) - newModList[order.collection] = modList[order.collection] - newModFoldersMap[order.collection] = modFoldersMap[order.collection] + newOrder.forEach((orderPart) => { + newModFolders.add(orderPart.path) + newModSetOrder.add(orderPart.collectKey) }) - modFolders = newModFolders - modList = newModList - modFoldersMap = newModFoldersMap + modFolders = newModFolders + modCollect.newCollectionOrder = newModSetOrder mcStore.set('modFolders', Array.from(modFolders)) - windows.folder.webContents.send('fromMain_getFolders', modList) + sendModList({}, 'fromMain_getFolders', 'folder', false ) foldersDirty = true }) - +/** END: Folder Window Operation */ /** Logging Operation */ ipcMain.on('toMain_log', (event, level, process, text) => { log.log[level](text, process) }) @@ -1043,11 +919,9 @@ ipcMain.on('toMain_langList_send', (event) => { event.sender.send('fromMain_langList_return', langList, myTranslator.deferCurrentLocale()) }) }) - ipcMain.on('toMain_getText_sync', (event, text) => { event.returnValue = myTranslator.syncStringLookup(text) }) - ipcMain.on('toMain_getText_send', (event, l10nSet) => { l10nSet.forEach((l10nEntry) => { if ( l10nEntry === 'app_version' ) { @@ -1080,24 +954,28 @@ ipcMain.on('toMain_getText_send', (event, l10nSet) => { /** Detail window operation */ -ipcMain.on('toMain_openModDetail', (event, thisMod) => { createDetailWindow(modIdToRecord(thisMod)) }) -ipcMain.on('toMain_showChangelog', () => { createChangeLogWindow() } ) +ipcMain.on('toMain_openModDetail', (event, thisMod) => { createNamedWindow('detail', {selected : modCollect.modColUUIDToRecord(thisMod) }) }) /** END: Detail window operation */ +/** Changelog window operation */ +ipcMain.on('toMain_showChangelog', () => { createNamedWindow('change') } ) +/** END: Changelog window operation */ + +/** Main window context menus */ ipcMain.on('toMain_modContextMenu', async (event, modID) => { - const thisMod = modIdToRecord(modID) - const thisModId = modHubList.mods[thisMod.fileDetail.shortName] || null + const thisMod = modCollect.modColUUIDToRecord(modID) const template = [ { label : thisMod.fileDetail.shortName}, { type : 'separator' }, { label : myTranslator.syncStringLookup('context_mod_detail'), click : () => { - createDetailWindow(thisMod) + createNamedWindow('detail', {selected : thisMod}) + //createDetailWindow({selected : thisMod}) }}, { type : 'separator' }, { label : myTranslator.syncStringLookup('open_folder'), click : () => { - const thisCollectionFolder = modFoldersMap[modID.split('--')[0]] + const thisCollectionFolder = modCollect.mapCollectionToFolder(modID.split('--')[0]) if ( thisMod !== null ) { shell.showItemInFolder(path.join(thisCollectionFolder, path.basename(thisMod.fileDetail.fullPath))) @@ -1105,44 +983,42 @@ ipcMain.on('toMain_modContextMenu', async (event, modID) => { }} ] - if ( thisModId !== null ) { + if ( thisMod.modHub.id !== null ) { template.push({ label : myTranslator.syncStringLookup('open_hub'), click : () => { - shell.openExternal(`https://www.farming-simulator.com/mod.php?mod_id=${thisModId}`) + shell.openExternal(`https://www.farming-simulator.com/mod.php?mod_id=${thisMod.modHub.id}`) }}) } template.push({ type : 'separator' }) template.push({ label : myTranslator.syncStringLookup('copy_to_list'), click : () => { - createConfirmWindow('copy', [thisMod], [modID]) + handleCopyMoveDelete('confirmCopy', [modID], [thisMod]) }}) template.push({ label : myTranslator.syncStringLookup('move_to_list'), click : () => { - createConfirmWindow('move', [thisMod], [modID]) + handleCopyMoveDelete('confirmMove', [modID], [thisMod]) }}) template.push({ label : myTranslator.syncStringLookup('remove_from_list'), click : () => { - createConfirmWindow('delete', [thisMod], [modID]) + handleCopyMoveDelete('confirmDelete', [modID], [thisMod]) }}) const menu = Menu.buildFromTemplate(template) menu.popup(BrowserWindow.fromWebContents(event.sender)) }) - ipcMain.on('toMain_mainContextMenu', async (event, collection) => { - const tagLine = modNote.get(`${collection}.notes_tagline`, null) - const subLabel = `${modList[collection].name}${tagLine === null ? '' : ` :: ${tagLine}`}` + const subLabel = modCollect.mapCollectionToFullName(collection) const template = [ { label : myTranslator.syncStringLookup('context_main_title').padEnd(subLabel.length, ' '), sublabel : subLabel }, - { type : 'separator' }, + { type : 'separator' }, { label : myTranslator.syncStringLookup('list-active'), click : () => { parseSettings({ - newFolder : modFoldersMap[collection], + newFolder : modCollect.mapCollectionToFolder(collection), userName : modNote.get(`${collection}.notes_username`, null), password : modNote.get(`${collection}.notes_password`, null), serverName : modNote.get(`${collection}.notes_server`, null), }) }}, - { type : 'separator' }, + { type : 'separator' }, { label : myTranslator.syncStringLookup('open_folder'), click : () => { - shell.openPath(modFoldersMap[collection]) + shell.openPath(modCollect.mapCollectionToFolder(collection)) }} ] @@ -1168,12 +1044,13 @@ ipcMain.on('toMain_mainContextMenu', async (event, collection) => { const menu = Menu.buildFromTemplate(template) menu.popup(BrowserWindow.fromWebContents(event.sender)) }) +/** END: Main window context menus */ -/** Debug window operation */ -ipcMain.on('toMain_openGameLog', () => { createGameLogWindow() }) +/** Game log window operation */ +ipcMain.on('toMain_openGameLog', () => { createNamedWindow('gamelog') }) ipcMain.on('toMain_openGameLogFolder', () => { shell.showItemInFolder(path.join(path.dirname(gameSettings), 'log.txt')) }) -ipcMain.on('toMain_getGameLog', () => { readGameLog() }) +ipcMain.on('toMain_getGameLog', () => { readGameLog() }) function readGameLog() { if ( windows.gamelog === null ) { return } @@ -1185,10 +1062,10 @@ function readGameLog() { log.log.warning(`Could not read game log file: ${e}`, 'game-log') } } -/** END: Debug window operation */ +/** END: Game log window operation */ /** Debug window operation */ -ipcMain.on('toMain_openDebugLog', () => { createDebugWindow() }) +ipcMain.on('toMain_openDebugLog', () => { createNamedWindow('debug') }) ipcMain.on('toMain_openDebugFolder', () => { shell.showItemInFolder(log.pathToLog) }) ipcMain.on('toMain_getDebugLog', (event) => { event.sender.send('fromMain_debugLog', log.htmlLog) }) /** END: Debug window operation */ @@ -1213,13 +1090,11 @@ function gameLauncher() { log.log.warning('Game path not set or invalid!', 'game-launcher') } } - ipcMain.on('toMain_startFarmSim', () => { gameLauncher() }) /** END: game launcher */ /** Find window operation */ -ipcMain.on('toMain_openFind', () => { createFindWindow() }) - +ipcMain.on('toMain_openFind', () => { createNamedWindow('find') }) ipcMain.on('toMain_findContextMenu', async (event, thisMod) => { const template = [ { label : myTranslator.syncStringLookup('select_in_main'), sublabel : thisMod.name }, @@ -1241,12 +1116,7 @@ ipcMain.on('toMain_findContextMenu', async (event, thisMod) => { /** END : Find window operation*/ /** Preferences window operation */ -ipcMain.on('toMain_getCollDesc', (event, collection) => { - const tagLine = modNote.get(`${collection}.notes_tagline`, null) - - event.returnValue = ( tagLine !== null ) ? ` [${tagLine}]` : '' -}) -ipcMain.on('toMain_openPrefs', () => { createPrefsWindow() }) +ipcMain.on('toMain_openPrefs', () => { createNamedWindow('prefs') }) ipcMain.on('toMain_getPref', (event, name) => { event.returnValue = mcStore.get(name) }) ipcMain.on('toMain_setPref', (event, name, value) => { if ( name === 'dev_mode' ) { @@ -1272,6 +1142,11 @@ ipcMain.on('toMain_resetWindows', () => { windows.main.center() windows.prefs.center() }) +ipcMain.on('toMain_clearCacheFile', () => { + maCache.clear() + foldersDirty = true + processModFolders() +}) ipcMain.on('toMain_cleanCacheFile', (event) => { const localStore = maCache.store const md5Set = new Set() @@ -1280,15 +1155,25 @@ ipcMain.on('toMain_cleanCacheFile', (event) => { Object.keys(localStore).forEach((md5) => { md5Set.add(md5) }) - Object.keys(modList).forEach((collection) => { - modList[collection].mods.forEach((mod) => { md5Set.delete(mod.md5Sum) }) + modCollect.collections.forEach((collectKey) => { + Object.values(modCollect.getModCollection(collectKey).mods).forEach((mod) => { + md5Set.delete(mod.md5Sum) + }) }) + loadingWindow_total(md5Set.size, true) + loadingWindow_current(0, true) + setTimeout(() => { loadingWindow_total(md5Set.size, true) loadingWindow_current(0, true) - md5Set.forEach((md5) => { maCache.delete(md5); loadingWindow_current() }) + md5Set.forEach((md5) => { + delete localStore[md5] + loadingWindow_current() + }) + + maCache.store = localStore loadingWindow_hide(1500) event.sender.send('fromMain_l10n_refresh') @@ -1337,8 +1222,13 @@ ipcMain.on('toMain_setGamePath', (event) => { /** Notes Operation */ -ipcMain.on('toMain_openNotes', (event, collection) => { createNotesWindow(collection) }) -ipcMain.on('toMain_setNote', (event, id, value, collection) => { +ipcMain.on('toMain_openNotes', (event, collectKey) => { + createNamedWindow('notes', { + collectKey : collectKey, + lastGameSettings : lastGameSettings, + }) +}) +ipcMain.on('toMain_setNote', (event, id, value, collectKey) => { const dirtyActions = [ 'notes_website', 'notes_websiteDL', @@ -1349,14 +1239,16 @@ ipcMain.on('toMain_setNote', (event, id, value, collection) => { if ( dirtyActions.includes(id) ) { foldersDirty = true } if ( value === '' ) { - modNote.delete(`${collection}.${id}`) + modNote.delete(`${collectKey}.${id}`) } else { - modNote.set(`${collection}.${id}`, value) + modNote.set(`${collectKey}.${id}`, value) } - createNotesWindow(collection) + createNamedWindow('notes', { + collectKey : collectKey, + lastGameSettings : lastGameSettings, + }) }) - /** END: Notes Operation */ /** Download operation */ @@ -1367,28 +1259,18 @@ ipcMain.on('toMain_downloadList', (event, collection) => { if ( thisSite === null || !thisDoDL ) { return } - dialog.showMessageBoxSync(windows.main, { - title : myTranslator.syncStringLookup('download_title'), - message : `${myTranslator.syncStringLookup('download_started')} :: ${modList[collection].name}\n${myTranslator.syncStringLookup('download_finished')}`, - type : 'info', - }) + doDialogBox('main', { titleL10n : 'download_title', message : `${myTranslator.syncStringLookup('download_started')} :: ${modCollect.mapCollectionToName(collection)}\n${myTranslator.syncStringLookup('download_finished')}` }) log.log.info(`Downloading Collection : ${collection}`, 'mod-download') log.log.info(`Download Link : ${thisLink}`, 'mod-download') - - const dlReq = net.request(thisLink) dlReq.on('response', (response) => { log.log.info(`Got download: ${response.statusCode}`, 'mod-download') if ( response.statusCode < 200 || response.statusCode >= 400 ) { - dialog.showMessageBoxSync(windows.main, { - title : myTranslator.syncStringLookup('download_title'), - message : `${myTranslator.syncStringLookup('download_failed')} :: ${modList[collection].name}`, - type : 'error', - }) + doDialogBox('main', { type : 'error', titleL10n : 'download_title', message : `${myTranslator.syncStringLookup('download_failed')} :: ${modCollect.mapCollectionToName(collection)}` }) } else { loadingWindow_open('download') @@ -1431,7 +1313,7 @@ ipcMain.on('toMain_downloadList', (event, collection) => { processModFolders() }) - zipReadStream.pipe(unzip.Extract({ path : modList[collection].fullPath })) + zipReadStream.pipe(unzip.Extract({ path : modCollect.mapCollectionToFolder(collection) })) } catch (e) { log.log.warning(`Download failed : (${response.statusCode}) ${e}`, 'mod-download') loadingWindow_hide() @@ -1442,53 +1324,53 @@ ipcMain.on('toMain_downloadList', (event, collection) => { dlReq.on('error', (error) => { log.log.warning(`Network error : ${error}`, 'mod-download'); loadingWindow_hide() }) dlReq.end() }) - /** END: download operation */ /** Export operation */ +const csvRow = (entries) => entries.map((entry) => `"${entry.replaceAll('"', '""')}"`).join(',') + ipcMain.on('toMain_exportList', (event, collection) => { const csvTable = [] - csvTable.push('"Mod","Title","Version","Author","ModHub","Link"') - modList[collection].mods.forEach((mod) => { - const modHubID = modHubList.mods[mod.fileDetail.shortName] || null + csvTable.push(csvRow(['Mod', 'Title', 'Version', 'Author', 'ModHub', 'Link'])) + + modCollect.getModListFromCollection(collection).forEach((mod) => { + const modHubID = mod.modHub.id const modHubLink = ( modHubID !== null ) ? `https://www.farming-simulator.com/mod.php?mod_id=${modHubID}` : '' const modHubYesNo = ( modHubID !== null ) ? 'yes' : 'no' - csvTable.push(`"${mod.fileDetail.shortName}.zip","${mod.l10n.title.replaceAll('"', '\'')}","${mod.modDesc.version}","${mod.modDesc.author.replaceAll('"', '\'')}","${modHubYesNo}","${modHubLink}"`) + csvTable.push(csvRow([ + `${mod.fileDetail.shortName}.zip`, + mod.l10n.title, + mod.modDesc.version, + mod.modDesc.author, + modHubYesNo, + modHubLink + ])) }) dialog.showSaveDialog(windows.main, { - defaultPath : path.join(app.getPath('desktop'), `${modList[collection].name}.csv`), - filters : [ - { name : 'CSV', extensions : ['csv'] }, - ], + defaultPath : path.join(app.getPath('desktop'), `${modCollect.mapCollectionToName(collection)}.csv`), + filters : [{ name : 'CSV', extensions : ['csv'] }], }).then(async (result) => { if ( result.canceled ) { log.log.debug('Save CSV Cancelled', 'csv-export') } else { try { fs.writeFileSync(result.filePath, csvTable.join('\n')) - dialog.showMessageBoxSync(windows.main, { - message : myTranslator.syncStringLookup('save_csv_worked'), - type : 'info', - }) + doDialogBox('main', { messageL10n : 'save_csv_worked' }) } catch (err) { log.log.warning(`Could not save csv file : ${err}`, 'csv-export') - dialog.showMessageBoxSync(windows.main, { - message : myTranslator.syncStringLookup('save_csv_failed'), - type : 'warning', - }) + doDialogBox('main', { type : 'warning', messageL10n : 'save_csv_failed' }) } } }).catch((unknownError) => { log.log.warning(`Could not save csv file : ${unknownError}`, 'csv-export') }) }) - ipcMain.on('toMain_exportZip', (event, selectedMods) => { const filePaths = [] - modIdsToRecords(selectedMods).forEach((mod) => { + modCollect.modColUUIDsToRecords(selectedMods).forEach((mod) => { filePaths.push([mod.fileDetail.shortName, mod.fileDetail.fullPath]) }) @@ -1518,10 +1400,7 @@ ipcMain.on('toMain_exportZip', (event, selectedMods) => { loadingWindow_hide() log.log.warning(`Could not create zip file : ${err}`, 'zip-export') setTimeout(() => { - dialog.showMessageBoxSync(windows.main, { - message : myTranslator.syncStringLookup('save_zip_failed'), - type : 'warning', - }) + doDialogBox('main', { type : 'warning', messageL10n : 'save_zip_failed' }) }, 1500) }) @@ -1544,10 +1423,7 @@ ipcMain.on('toMain_exportZip', (event, selectedMods) => { log.log.warning(`Could not create zip file : ${err}`, 'zip-export') loadingWindow_hide() setTimeout(() => { - dialog.showMessageBoxSync(windows.main, { - message : myTranslator.syncStringLookup('save_zip_failed'), - type : 'warning', - }) + doDialogBox('main', { type : 'warning', messageL10n : 'save_zip_failed' }) }, 1500) } } @@ -1558,7 +1434,7 @@ ipcMain.on('toMain_exportZip', (event, selectedMods) => { /** END: Export operation */ /** Savegame window operation */ -ipcMain.on('toMain_openSave', (event, collection) => { createSavegameWindow(collection) }) +ipcMain.on('toMain_openSave', (event, collection) => { createNamedWindow('save', { collectKey : collection }) }) ipcMain.on('toMain_selectInMain', (event, selectList) => { windows.main.focus() windows.main.webContents.send('fromMain_selectOnly', selectList) @@ -1579,7 +1455,8 @@ function openSaveGame(zipMode = false) { if ( !result.canceled ) { try { const thisSavegame = new saveFileChecker(result.filePaths[0], !zipMode, log) - windows.save.webContents.send('fromMain_saveInfo', modList, thisSavegame, modHubList) + + sendModList({ thisSaveGame : thisSavegame }, 'fromMain_saveInfo', 'save', false ) } catch (e) { log.log.danger(`Load failed: ${e}`, 'savegame') } @@ -1592,77 +1469,67 @@ function openSaveGame(zipMode = false) { /** Version window operation */ -ipcMain.on('toMain_versionCheck', () => { createVersionWindow() }) -ipcMain.on('toMain_refreshVersions', (event) => { event.sender.send('fromMain_modList', modList) } ) +ipcMain.on('toMain_versionCheck', () => { createNamedWindow('version') }) +ipcMain.on('toMain_refreshVersions', () => { sendModList({}, 'fromMain_modList', 'version', false ) } ) ipcMain.on('toMain_versionResolve', (event, shortName) => { const modSet = [] - Object.keys(modList).forEach((collection) => { - modList[collection].mods.forEach((mod) => { + + modCollect.collections.forEach((collectKey) => { + modCollect.getModCollection(collectKey).modSet.forEach((modKey) => { + const mod = modCollect.modColAndUUID(collectKey, modKey) + if ( mod.fileDetail.shortName === shortName && !mod.fileDetail.isFolder ) { - modSet.push([collection, mod.modDesc.version, mod, modList[collection].name]) + modSet.push({ + collectKey : collectKey, + version : mod.modDesc.version, + modRecord : mod, + collectName : modCollect.mapCollectionToName(collectKey), + }) } }) }) - createResolveWindow(modSet, shortName) + createNamedWindow('resolve', { + modSet : modSet, + shortName : shortName, + }) }) /** END: Version window operation */ /** Utility & Convenience Functions */ ipcMain.on('toMain_closeSubWindow', (event, thisWin) => { windows[thisWin].close() }) -ipcMain.on('toMain_homeDirRevamp', (event, thisPath) => { event.returnValue = thisPath.replaceAll(userHome, '~') }) -function refreshClientModList() { - windows.main.webContents.send( - 'fromMain_modList', +function sendModList(extraArgs = {}, eventName = 'fromMain_modList', toWindow = 'main', closeLoader = true) { + modCollect.toRenderer(extraArgs).then((modCollection) => { + windows[toWindow].webContents.send(eventName, modCollection) + + if ( toWindow === 'main' && windows.version && windows.version.isVisible() ) { + windows.version.webContents.send(eventName, modCollection) + } + if ( closeLoader ) { loadingWindow_hide(1500) } + }) +} + +function refreshClientModList(closeLoader = true) { + sendModList( { currentLocale : myTranslator.deferCurrentLocale(), - modList : modList, l10n : { disable : myTranslator.syncStringLookup('override_disabled'), unknown : myTranslator.syncStringLookup('override_unknown'), }, activeCollection : overrideIndex, - foldersMap : modFoldersMap, - newMods : newModsList, - modHub : { - list : modHubList, - version : modHubVersion, - }, - bindConflict : bindConflict, - notes : modNote.store, - } + }, + 'fromMain_modList', + 'main', + closeLoader ) } -function modRecordToModHub(mod) { - const modId = modHubList.mods[mod.fileDetail.shortName] || null - return [modId, (modHubVersion[modId] || null), modHubList.last.includes(modId)] -} -function modIdToRecord(id) { - const idParts = id.split('--') - let foundMod = null - let foundCol = null - - modList[idParts[0]].mods.forEach((mod) => { - if ( foundMod === null && mod.uuid === idParts[1] ) { - foundMod = mod - foundCol = idParts[0] - } - }) - foundMod.currentCollection = foundCol - return foundMod -} - -function modIdsToRecords(mods) { - const theseMods = [] - mods.forEach((inMod) => { theseMods.push(modIdToRecord(inMod)) }) - return theseMods -} /** END: Utility & Convenience Functions */ - +/** Business Functions */ function parseGameXML(devMode = null) { const gameXMLFile = gameSettings.replace('gameSettings.xml', 'game.xml') @@ -1708,7 +1575,6 @@ function parseGameXML(devMode = null) { parseGameXML(null) } } -/** Business Functions */ function parseSettings({disable = null, newFolder = null, userName = null, serverName = null, password = null } = {}) { if ( ! gameSettings.endsWith('.xml') ) { log.log.danger(`Game settings is not an xml file ${gameSettings}, fixing`, 'game-settings') @@ -1759,10 +1625,7 @@ function parseSettings({disable = null, newFolder = null, userName = null, serve if ( overrideActive === 'false' || overrideActive === false ) { overrideIndex = '0' } else { - overrideIndex = '999' - Object.keys(modFoldersMap).forEach((cleanName) => { - if ( modFoldersMap[cleanName] === overrideFolder ) { overrideIndex = cleanName } - }) + overrideIndex = modCollect.mapFolderToCollection(overrideFolder) || '999' } if ( disable !== null || newFolder !== null || userName !== null || password !== null || serverName !== null ) { @@ -1813,7 +1676,6 @@ function parseSettings({disable = null, newFolder = null, userName = null, serve parseSettings() refreshClientModList() - loadingWindow_hide(1500) } } @@ -1832,16 +1694,14 @@ function fileOperation(type, fileMap, srcWindow = 'confirm') { } }, 250) } - - function fileOperation_post(type, fileMap) { const fullPathMap = [] fileMap.forEach((file) => { const thisFileName = path.basename(file[2]) fullPathMap.push([ - path.join(modFoldersMap[file[1]], thisFileName), // source - path.join(modFoldersMap[file[0]], thisFileName), // dest + path.join(modCollect.mapCollectionToFolder(file[1]), thisFileName), // source + path.join(modCollect.mapCollectionToFolder(file[0]), thisFileName), // dest ]) }) @@ -1879,67 +1739,10 @@ function fileOperation_post(type, fileMap) { }) processModFolders() - if ( windows.version && windows.version.isVisible() ) { - windows.version.webContents.send('fromMain_modList', modList) - } - -} - -function fileGetStats(folder, thisFile) { - let isFolder = null - let date = null - let b_date = null - let size = null - let error = false - - try { - if ( thisFile.isSymbolicLink() ) { - const thisSymLink = fs.readlinkSync(path.join(folder, thisFile.name)) - const thisSymLinkStat = fs.lstatSync(path.join(folder, thisSymLink)) - isFolder = thisSymLinkStat.isDirectory() - date = thisSymLinkStat.ctime - b_date = thisSymLinkStat.birthtime - - if ( !isFolder ) { size = thisSymLinkStat.size } - } else { - isFolder = thisFile.isDirectory() - } - - if ( ! thisFile.isSymbolicLink() ) { - const theseStats = fs.statSync(path.join(folder, thisFile.name)) - if ( !isFolder ) { size = theseStats.size } - date = theseStats.ctime - b_date = theseStats.birthtime - - } - if ( isFolder ) { - let bytes = 0 - glob.sync('**', { cwd : path.join(folder, thisFile.name) }).forEach((file) => { - try { - const stats = fs.statSync(path.join(folder, thisFile.name, file)) - if ( stats.isFile() ) { bytes += stats.size } - } catch { /* Do Nothing if we can't read it. */ } - }) - size = bytes - } - } catch (e) { - log.log.warning(`Unable to stat file ${thisFile.name} in ${folder} : ${e}`, 'file-stat') - isFolder = false - size = 0 - date = new Date(1969, 1, 1, 0, 0, 0, 0) - error = true - } - return { - folder : isFolder, - size : size, - date : date, - b_date : b_date, - error : error, - } } let loadingWait = null -async function processModFolders(newFolder) { +async function processModFolders() { if ( !foldersDirty ) { loadingWindow_hide(); return } loadingWindow_open('mods', 'main') @@ -1949,206 +1752,53 @@ async function processModFolders(newFolder) { loadingWait = setInterval(() => { if ( windows.load.isVisible() ) { clearInterval(loadingWait) - const localStore = maCache.store - const processPromises = processModFolders_post(newFolder, localStore) - - Promise.all(processPromises).then(() => { - maCache.store = localStore - foldersDirty = false - - processModBindConflict() - processModFolders_post_after() - }) + processModFoldersOnDisk() } }, 250) } - -function processModFolders_post(newFolder = false, localStore) { - const useOneDrive = mcStore.get('use_one_drive', false) - const readingPromises = [] - if ( newFolder === false ) { modList = {}; modFoldersMap = {}} +function processModFoldersOnDisk() { + modCollect.syncSafe = mcStore.get('use_one_drive', false) + modCollect.clearAll() // Cleaner for no-longer existing folders, count contents of others modFolders.forEach((folder) => { - if ( ! fs.existsSync(folder) ) { - modFolders.delete(folder) - } else { - try { - const folderSize = fs.readdirSync(folder, {withFileTypes : true}) - loadingWindow_total(folderSize.length) - } catch (e) { - log.log.danger(`Couldn't count folder: ${folder} :: ${e}`, 'folder-reader') - } - } + if ( ! fs.existsSync(folder) ) { modFolders.delete(folder) } }) mcStore.set('modFolders', Array.from(modFolders)) modFolders.forEach((folder) => { - const cleanName = `col_${crypto.createHash('md5').update(folder).digest('hex')}` - const shortName = path.basename(folder) - - if ( folder === newFolder || newFolder === false ) { - modFoldersMap[cleanName] = folder - modList[cleanName] = { name : shortName, fullPath : folder, mods : [] } - - try { - const folderContents = fs.readdirSync(folder, {withFileTypes : true}) - - folderContents.forEach((thisFile) => { - if ( junkRegex.test(thisFile.name) ) { - loadingWindow_current() - return - } - - const thisFileStats = fileGetStats(folder, thisFile) + const thisCollectionStats = modCollect.addCollection(folder) - if ( thisFileStats.error ) { - loadingWindow_current() - return - } - - readingPromises.push( - new Promise((resolve) => { - setTimeout(() => { - processModFileSingleton(folder, thisFile, localStore, cleanName, thisFileStats, useOneDrive) - resolve(true) - }, 10) - }) - ) - }) - - } catch (e) { - log.log.danger(`Couldn't process folder: ${folder} :: ${e}`, 'folder-reader') - } - } + loadingWindow_total(thisCollectionStats.fileCount) }) - return readingPromises -} - -async function processModFileSingleton (folder, thisFile, localStore, cleanName, thisFileStats, useOneDrive) { - if ( !thisFileStats.folder && !skipCache ) { - const hashString = `${thisFile.name}-${thisFileStats.size}-${(useOneDrive)?thisFileStats.b_date.toISOString():thisFileStats.date.toISOString()}` - const thisMD5Sum = crypto.createHash('md5').update(hashString).digest('hex') - - if ( typeof localStore[thisMD5Sum] !== 'undefined') { - modList[cleanName].mods.push(localStore[thisMD5Sum]) - log.log.debug(`Adding mod FROM cache: ${localStore[thisMD5Sum].fileDetail.shortName}`, `mod-${localStore[thisMD5Sum].uuid}`) - loadingWindow_current() - return - } - } + modCollect.processMods() - if ( !thisFileStats.folder && !thisFile.name.endsWith('.zip') ) { - modList[cleanName].mods.push(new notModFileChecker( - path.join(folder, thisFile.name), - false, - thisFileStats.size, - thisFileStats.date, - log - )) - loadingWindow_current() - return - } - - try { - const thisModDetail = new modFileChecker( - path.join(folder, thisFile.name), - thisFileStats.folder, - thisFileStats.size, - (useOneDrive) ? thisFileStats.b_date : thisFileStats.date, - log, - myTranslator.deferCurrentLocale - ) - const storable = thisModDetail.storable - modList[cleanName].mods.push(thisModDetail) + modCollect.processPromise.then(() => { + parseSettings() + parseGameXML() + refreshClientModList() - if ( thisModDetail.md5Sum !== null ) { - log.log.info('Adding mod to cache', `mod-${thisModDetail.uuid}`) - newModsList.push(thisModDetail.md5Sum) - localStore[thisModDetail.md5Sum] = storable + if ( mcStore.get('rel_notes') !== app.getVersion() ) { + mcStore.set('rel_notes', app.getVersion() ) + log.log.info('New version detected, show changelog') + createNamedWindow('change') } - } catch (e) { - log.log.danger(`Couldn't process file: ${thisFile.name} :: ${e}`, 'folder-reader') - modList[cleanName].mods.push(new notModFileChecker( - path.join(folder, thisFile.name), - false, - thisFileStats.size, - thisFileStats.date, - log - )) - } - - loadingWindow_current() - -} - -function processModBindConflict() { - bindConflict = {} - - Object.keys(modList).forEach((collection) => { - bindConflict[collection] = {} - const collectionBinds = {} - - modList[collection].mods.forEach((thisMod) => { - Object.keys(thisMod.modDesc.binds).forEach((actName) => { - thisMod.modDesc.binds[actName].forEach((keyCombo) => { - if ( keyCombo === '' ) { return } - - const safeCat = thisMod.modDesc.actions[actName] || 'UNKNOWN' - const thisCombo = `${safeCat}--${keyCombo}` - - collectionBinds[thisCombo] ??= [] - collectionBinds[thisCombo].push(thisMod.fileDetail.shortName) - }) - }) - }) - Object.keys(collectionBinds).forEach((keyCombo) => { - if ( collectionBinds[keyCombo].length > 1 ) { - collectionBinds[keyCombo].forEach((modName) => { - bindConflict[collection][modName] ??= {} - bindConflict[collection][modName][keyCombo] = collectionBinds[keyCombo].filter((w) => w !== modName) - if ( bindConflict[collection][modName][keyCombo].length === 0 ) { - delete bindConflict[collection][modName][keyCombo] - } - }) - } - }) - Object.keys(bindConflict).forEach((collection) => { - Object.keys(bindConflict[collection]).forEach((modName) => { - if ( Object.keys(bindConflict[collection][modName]).length === 0 ) { - delete bindConflict[collection][modName] - } - }) - }) }) } -function processModFolders_post_after() { - parseSettings() - parseGameXML() - refreshClientModList() - loadingWindow_hide() - - if ( mcStore.get('rel_notes') !== app.getVersion() ) { - mcStore.set('rel_notes', app.getVersion() ) - log.log.info('New version detected, show changelog') - createChangeLogWindow() - } -} - function loadSaveFile(filename) { try { - const rawData = fs.readFileSync(path.join(app.getPath('userData'), filename)) + const rawData = fs.readFileSync(path.join(app.getPath('userData'), filename)) const jsonData = JSON.parse(rawData) switch (filename) { case 'modHubData.json' : - modHubList = jsonData + modCollect.modHubList = jsonData break case 'modHubVersion.json' : - modHubVersion = jsonData + modCollect.modHubVersion = jsonData break default : break @@ -2182,6 +1832,18 @@ function dlSaveFile(url, filename) { } /** END: Business Functions */ +function doDialogBox(attachTo, {type = 'info', message = null, messageL10n = null, title = null, titleL10n = null }) { + const attachWin = ( attachTo === null ) ? null : windows[attachTo] + + const thisTitle = ( title !== null ) ? title : myTranslator.syncStringLookup(( titleL10n === null ) ? 'app_name' : titleL10n) + const thisMessage = ( message !== null ) ? message : myTranslator.syncStringLookup(messageL10n) + + dialog.showMessageBoxSync(attachWin, { + title : thisTitle, + message : thisMessage, + type : type, + }) +} app.whenReady().then(() => { diff --git a/package.json b/package.json index f5bb0e8f..153a55a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fsg-mod-assistant", - "version": "1.10.2", + "version": "1.99.0", "description": "FSG Farm Sim Mod Assistant", "main": "modAssist_main.js", "homepage": "https://github.com/FSGModding/FSG_Mod_Assistant#readme", @@ -19,8 +19,8 @@ "pack": "electron-builder --dir", "dist": "electron-builder", "test": "node ./test/mod-reader-test.js && node ./test/save-reader-test.js && node ./test/translate-check.js", - "langtest": "node ./test/translate-check.js", - "langfix": "node ./test/translate-fix.js", + "lang_test": "node ./test/translate-check.js", + "lang_fix": "node ./test/translate-fix.js", "depends": "node ./test/outdated-deps.js" }, "devDependencies": { @@ -37,10 +37,9 @@ "electron-updater": "^5.2.1", "fast-xml-parser": "^4.0.11", "glob": "^8.0.3", - "pngjs": "^6.0.0", + "jpeg-js": "^0.4.4", "semver": "^7.3.8", "unzip-stream": "^0.3.1", - "windows": "^0.1.2", "xml2js": "^0.4.23", "yargs": "^17.5.1" }, diff --git a/renderer/a_changelog.html b/renderer/a_changelog.html index 9f67aa34..a6c3afd4 100644 --- a/renderer/a_changelog.html +++ b/renderer/a_changelog.html @@ -42,66 +42,81 @@

-

1.10.3

-
    -
  • ADD: Right-click Context menus on collections and mods in the main window
  • -
  • ADD: Game log analyzer
  • -
  • CHANGE: Rework filter bar to include number of selected mods
  • -
  • CHANGE: Much better scrollbar in main window, includes selected mods in an overview
  • -
  • ADD: Dialog popup when the selected collection in the game and mod assistant do not match
  • -
  • ADD: Misc artwork on some dialogs
  • +

    What's New?

    +
      +
    • Complete rewrite of the scanning library. Roughly 300% faster and requires less than half the storage the old one did.
    • +
    • NEW FEATURE Monitor and filter the game log from Mod Assistant
    • +
    • NOTICE Consider clearing your mod cache if upgrading from a previous version. It will take a few minutes, but should improve performance. There is a "Clear" button in user preferences.
    -

    1.10.0 (since 1.9.0)

    -
      -
    • Rework mod filtering to a unified interface
    • -
    • FIX: Add "select active" mods to savegame compare
    • -
    • FIX: Cache cleaning could happen "invisibly", add a timeout
    • -
    • FIX: Uncaught error type on updater
    • -
    • FIX: More data sanitization
    • -
    • ADD: CrowdIn Link with the rest of the language settings in user preferences
    • -
    • FIX: Edge cases for the translator failing and not populating buttons and icons
    • -
    • CHANGE: Active collection icon is now green with a white check
    • -
    • ADD: Tooltips for folder management buttons
    • -
    • FIX: Loading / Status window should only be on top of other app windows, not *all* windows
    • -
    -

    1.9.0 (since 1.8.0)

    -
      -
    • ADD: Export to ZIP file option
    • -
    • ADD: Button to copy web admin password per-collection
    • -
    • FIX: Visual updates to main interface
    • -
    • FIX: Human readable dates in details page
    • -
    • UPDATE: Refactor DDS libraries
    • -
    • UPDATE: Change Icon Library to bootstrap-icons
    • -
    -

    1.8.0 (since 1.5.0)

    -
      -
    • UPDATE: update all Dependencies
    • -
    • BIG FIX: Fix autoupdater. v1.6.x has been removed totally. v1.7.0 was the fix
    • -
    • ADD: pt.json Localization (thanks RagagelesOficial)
    • -
    • FIX: Count all mods in all folders prior to testing scan
    • -
    • ADD: Tag Line / Description for each collection
    • -
    • ADD: Scrollbar in main window (custom)
    • -
    • ADD: Right click menu on "Find All" screen to select a specific instance of a mod in main window
    • -
    • ADD: Progress meter on loading screens, use MB instead of Bytes where applicable
    • -
    • FIX: Mod Download now fails intelligently
    • -
    • ADD: Some sanity checking on Details/Notes for each folder (website URL structure, user name length, admin password length)
    • -
    • FIX: Some more fixes for the update process (unusual exception)
    • -
    • ADD: CTRL+A / CTRL+SHIFT+A to mass toggle file overwrite for copy/move mods
    • -
    • FIX: Keyboard shortcuts for non-Latin keyboards
    • -
    • FIX: Language select preference was unclear
    • -
    • ADD: Selected mod count in main window
    • -
    • ADD: Mod category count in savegame comparison
    • -
    -

    1.5.0 (since 1.4.0)

    -
      -
    • ADD: When multiple mods are selected, ALT+Clicking a mod will re-enable the "Find on ModHub" and "Show in Explorer" buttons
    • -
    • FIX: When changing language, change in every open window
    • -
    • FIX: Move the language button to preferences instead
    • -
    • FIX: If the modHubData.json file is broken, don't break the app
    • -
    • L10N: Split "filter / limit to" Heading for better context
    • -
    • FIX: Odd crash when updating
    • -
    • ADD: Ignore CSV files when selecting from savegame lists
    • -
    • ADD: Load Mod Dependencies, warn when not met.
    • + + +

      Added Features by Major Version

      +
        +
      • + 2.0.0 +
          +
        • Re-write of mod scanner to improve performance
        • +
        • Sync safe scan mode for OneDrive and Google Drive mod collections
        • +
        • Right click context menus in main window
        • +
        • Game log file viewer
        • +
        • New filter UI display
        • +
        • Sanity checker when starting game
        • +
        • Custom scrollbar and background
        • +
        +
      • +
      • + 1.9.0 +
          +
        • Export multiple mods to ZIP file
        • +
        • Refactor of icon reader to be faster
        • +
        +
      • +
      • + 1.8.0 +
          +
        • Progress meter for loading screens
        • +
        • Collection description detail
        • +
        • Track mod dependencies
        • +
        +
      • +
      • + 1.5.0 +
          +
        • Track mod dependencies
        • +
        +
      • +
      • + 1.4.0 +
          +
        • Alphabetical collection sort
        • +
        • Allow selecting mod sets from savegame comparison
        • +
        • Add support for Giants LED (collectors edition)
        • +
        +
      • +
      • + 1.3.0 +
          +
        • Add "Search All Collections" window
        • +
        • Remember window placement and size
        • +
        +
      • +
      • + 1.2.0 +
          +
        • Download all mods from dedicated servers
        • +
        • Add favorite collections
        • +
        +
      • +
      • + 1.1.0 +
          +
        • Collection re-ordering now possible
        • +
        • Export collections to CSV list
        • +
        • Track notes and details per collection
        • +
        • Detect key binding conflicts
        • +
        • Toggle in-game Development Controls / Console
        • +
        +
diff --git a/renderer/prefs.html b/renderer/prefs.html index d72ea3fe..ebdc1e07 100644 --- a/renderer/prefs.html +++ b/renderer/prefs.html @@ -119,6 +119,12 @@
+
+

+
+
+
+
diff --git a/renderer/preload/preload-confirmCopy.js b/renderer/preload/preload-confirmCopy.js index 5c2d2c87..dfd0e9f0 100644 --- a/renderer/preload/preload-confirmCopy.js +++ b/renderer/preload/preload-confirmCopy.js @@ -37,9 +37,7 @@ contextBridge.exposeInMainWorld( contextBridge.exposeInMainWorld( 'mods', { - getCollDesc : (coll) => { return ipcRenderer.sendSync('toMain_getCollDesc', coll) }, closeWindow : ( ) => { ipcRenderer.send('toMain_closeSubWindow', 'confirm') }, - homeDirMap : ( path ) => { return ipcRenderer.sendSync('toMain_homeDirRevamp', path) }, realCopyFile : ( fileMap ) => { ipcRenderer.send('toMain_realFileCopy', fileMap) }, receive : ( channel, func ) => { const validChannels = [ diff --git a/renderer/preload/preload-confirmMove.js b/renderer/preload/preload-confirmMove.js index dbb064c1..5c740a3d 100644 --- a/renderer/preload/preload-confirmMove.js +++ b/renderer/preload/preload-confirmMove.js @@ -37,9 +37,7 @@ contextBridge.exposeInMainWorld( contextBridge.exposeInMainWorld( 'mods', { - getCollDesc : (coll) => { return ipcRenderer.sendSync('toMain_getCollDesc', coll) }, closeWindow : ( ) => { ipcRenderer.send('toMain_closeSubWindow', 'confirm') }, - homeDirMap : ( path ) => { return ipcRenderer.sendSync('toMain_homeDirRevamp', path) }, realMoveFile : ( fileMap ) => { ipcRenderer.send('toMain_realFileMove', fileMap) }, receive : ( channel, func ) => { const validChannels = [ diff --git a/renderer/preload/preload-confirmMulti.js b/renderer/preload/preload-confirmMulti.js index bd9c99ba..f25c3e0e 100644 --- a/renderer/preload/preload-confirmMulti.js +++ b/renderer/preload/preload-confirmMulti.js @@ -37,9 +37,8 @@ contextBridge.exposeInMainWorld( contextBridge.exposeInMainWorld( 'mods', { - closeWindow : ( ) => { ipcRenderer.send('toMain_closeSubWindow', 'confirm') }, - homeDirMap : ( path ) => { return ipcRenderer.sendSync('toMain_homeDirRevamp', path) }, - realCopyFile : ( fileMap ) => { ipcRenderer.send('toMain_realFileCopy', fileMap) }, + closeWindow : ( ) => { ipcRenderer.send('toMain_closeSubWindow', 'confirm') }, + realCopyFile : ( fileMap ) => { ipcRenderer.send('toMain_realFileCopy', fileMap) }, receive : ( channel, func ) => { const validChannels = [ 'fromMain_confirmList', diff --git a/renderer/preload/preload-folderWindow.js b/renderer/preload/preload-folderWindow.js index bbe08951..fafa092f 100644 --- a/renderer/preload/preload-folderWindow.js +++ b/renderer/preload/preload-folderWindow.js @@ -39,7 +39,6 @@ contextBridge.exposeInMainWorld( contextBridge.exposeInMainWorld( 'mods', { closeWindow : ( ) => { ipcRenderer.send('toMain_closeSubWindow', 'folder') }, - homeDirMap : ( path ) => { return ipcRenderer.sendSync('toMain_homeDirRevamp', path) }, removeFolder : ( folder ) => { ipcRenderer.send('toMain_removeFolder', folder) }, openFolder : ( folder ) => { ipcRenderer.send('toMain_openFolder', folder) }, reorderFolder : ( from, to ) => { ipcRenderer.send('toMain_reorderFolder', from, to) }, diff --git a/renderer/preload/preload-loadingWindow.js b/renderer/preload/preload-loadingWindow.js index ae79fb6e..96879420 100644 --- a/renderer/preload/preload-loadingWindow.js +++ b/renderer/preload/preload-loadingWindow.js @@ -24,7 +24,7 @@ ipcRenderer.on('fromMain_loadingNoCount', () => { document.getElementById('statusProgBar').classList.add('d-none') }) -ipcRenderer.on('fromMain_loadingTotal', (event, count, inMB = false) => { +ipcRenderer.on('fromMain_loading_total', (event, count, inMB = false) => { const thisCount = inMB ? toMB(count) : count const thisElement = document.getElementById('statusTotal') lastTotal = ( count < 1 ) ? 1 : count @@ -32,7 +32,7 @@ ipcRenderer.on('fromMain_loadingTotal', (event, count, inMB = false) => { if ( thisElement !== null ) { thisElement.innerHTML = thisCount } }) -ipcRenderer.on('fromMain_loadingCurrent', (event, count, inMB = false) => { +ipcRenderer.on('fromMain_loading_current', (event, count, inMB = false) => { const thisCount = inMB ? toMB(count, false) : count const thisElement = document.getElementById('statusCurrent') const thisProg = document.getElementById('statusProgBarInner') diff --git a/renderer/preload/preload-mainWindow.js b/renderer/preload/preload-mainWindow.js index 37dc952f..55a711d6 100644 --- a/renderer/preload/preload-mainWindow.js +++ b/renderer/preload/preload-mainWindow.js @@ -39,7 +39,6 @@ contextBridge.exposeInMainWorld( contextBridge.exposeInMainWorld( 'mods', { - getCollDesc : (coll) => { return ipcRenderer.sendSync('toMain_getCollDesc', coll) }, startFarmSim : () => { ipcRenderer.send('toMain_startFarmSim') }, openPreferences : () => { ipcRenderer.send('toMain_openPrefs') }, openFindAll : () => { ipcRenderer.send('toMain_openFind') }, @@ -77,6 +76,7 @@ contextBridge.exposeInMainWorld( 'fromMain_selectNoneOpen', 'fromMain_selectInvertOpen', 'fromMain_selectOnly', + 'fromMain_selectOnlyFilter', 'fromMain_debugLogDanger', ] diff --git a/renderer/preload/preload-prefsWindow.js b/renderer/preload/preload-prefsWindow.js index 46a5c3d4..073eaef7 100644 --- a/renderer/preload/preload-prefsWindow.js +++ b/renderer/preload/preload-prefsWindow.js @@ -42,6 +42,7 @@ contextBridge.exposeInMainWorld( 'mods', { closeWindow : () => { ipcRenderer.send('toMain_closeSubWindow', 'prefs') }, cleanCache : () => { ipcRenderer.send('toMain_cleanCacheFile') }, + clearCache : () => { ipcRenderer.send('toMain_clearCacheFile') }, resetWindows : () => { ipcRenderer.send('toMain_resetWindows') }, setGamePath : () => { ipcRenderer.send('toMain_setGamePath') }, setPrefFile : () => { ipcRenderer.send('toMain_setPrefFile') }, diff --git a/renderer/renderJS/assist_ui.js b/renderer/renderJS/assist_ui.js index 16110317..39da3530 100644 --- a/renderer/renderJS/assist_ui.js +++ b/renderer/renderJS/assist_ui.js @@ -80,8 +80,8 @@ window.mods.receive('fromMain_selectOnly', (selectList) => { window.mods.receive('fromMain_selectOnlyFilter', (selectMod, filterText) => { const tableID = `${selectMod.split('--')[0]}_mods` - const checkList = [selectMod.split('--')[1]] - + const checkList = [`${selectMod}__checkbox`] + select_lib.close_all(tableID) select_lib.click_only(tableID, checkList) select_lib.filter(tableID, filterText) @@ -89,14 +89,13 @@ window.mods.receive('fromMain_selectOnlyFilter', (selectMod, filterText) => { let lastLocale = 'en' -let lastQuickLists = {} let searchStringMap = {} let searchTagMap = {} let lastList = null let fullList = {} -window.mods.receive('fromMain_modList', (opts) => { - lastQuickLists = {} + +window.mods.receive('fromMain_modList', (modCollect) => { searchStringMap = {} searchTagMap = { broken : [], @@ -110,70 +109,58 @@ window.mods.receive('fromMain_modList', (opts) => { update : [], nonmh : [], } - lastLocale = opts.currentLocale + lastLocale = modCollect.opts.currentLocale - fsgUtil.byId('lang-style-div').setAttribute('class', opts.currentLocale) + fsgUtil.byId('lang-style-div').setAttribute('class', modCollect.opts.currentLocale) const lastOpenAcc = document.querySelector('.accordion-collapse.show') const lastOpenID = (lastOpenAcc !== null) ? lastOpenAcc.id : null const lastOpenQ = (lastOpenAcc !== null) ? fsgUtil.byId('filter_input').value : '' const scrollStart = window.scrollY - const selectedList = ( opts.activeCollection !== '999' && opts.activeCollection !== '0') ? `collection--${opts.activeCollection}` : opts.activeCollection const modTable = [] const optList = [] const scrollTable = [] - - lastList = selectedList + + /* List selection */ + lastList = ( modCollect.opts.activeCollection !== '999' && modCollect.opts.activeCollection !== '0') ? `collection--${modCollect.opts.activeCollection}` : modCollect.opts.activeCollection fullList = {} - optList.push(fsgUtil.buildSelectOpt('0', `--${opts.l10n.disable}--`, selectedList, true)) - fullList[0] = `--${opts.l10n.disable}--` - - Object.keys(opts.modList).forEach((collection) => { - const modRows = [] - const scrollRows = [] - let sizeOfFolder = 0 + fullList[0] = `--${modCollect.opts.l10n.disable}--` + optList.push(fsgUtil.buildSelectOpt('0', `--${modCollect.opts.l10n.disable}--`, lastList, true)) + + modCollect.set_Collections.forEach((collectKey) => { + fullList[`collection--${collectKey}`] = modCollect.modList[collectKey].fullName + optList.push(fsgUtil.buildSelectOpt(`collection--${collectKey}`, modCollect.modList[collectKey].fullName, lastList, false, modCollect.collectionToFolder[collectKey])) + + }) + + fullList[999] = `--${modCollect.opts.l10n.unknown}--` + optList.push(fsgUtil.buildSelectOpt('999', `--${modCollect.opts.l10n.unknown}--`, lastList, true)) - opts.modList[collection].mods.forEach((thisMod) => { + fsgUtil.byId('collectionSelect').innerHTML = optList.join('') + /* END : List selection */ + + + modCollect.set_Collections.forEach((collectKey) => { + const thisCollection = modCollect.modList[collectKey] + const collectNotes = modCollect.collectionNotes?.[collectKey] + const modRows = [] + const scrollRows = [] + const sizeOfFolder = thisCollection.folderSize + + thisCollection.alphaSort.forEach((modKey) => { try { - const displayBadges = thisMod.badgeArray || [] - const modId = opts.modHub.list.mods[thisMod.fileDetail.shortName] || null - const modVer = opts.modHub.version[modId] || null - const modColUUID = `${collection}--${thisMod.uuid}` - - sizeOfFolder += thisMod.fileDetail.fileSize - - if ( Object.keys(thisMod.modDesc.binds).length > 0 ) { - if ( typeof opts.bindConflict[collection][thisMod.fileDetail.shortName] !== 'undefined' ) { - displayBadges.push('keys_bad') - } else { - displayBadges.push('keys_ok') - } - } - if ( modVer !== null && thisMod.modDesc.version !== modVer) { - displayBadges.push('update') - } - if ( opts.newMods.includes(thisMod.md5Sum) && !thisMod.canNotUse ) { - displayBadges.push('new') - } - if ( modId !== null && opts.modHub.list.last.includes(modId) ) { - displayBadges.push('recent') - } - if ( modId === null ) { - displayBadges.push('nonmh') - } - - if ( displayBadges.includes('broken') && displayBadges.includes('notmod') ) { - const brokenIdx = displayBadges.indexOf('broken') - displayBadges.splice(brokenIdx, brokenIdx !== -1 ? 1 : 0) - } - - if ( ! metDepend(thisMod.modDesc.depend, collection, opts.modList[collection].mods) ) { - displayBadges.unshift('depend') - } - - searchStringMap[modColUUID] = [ + const thisMod = thisCollection.mods[modKey.split('::')[1]] + const displayBadges = doBadgeSet( + thisMod.badgeArray, + thisMod, + thisCollection, + modCollect.newMods, + modCollect.bindConflict?.[collectKey] + ) + + searchStringMap[thisMod.colUUID] = [ thisMod.fileDetail.shortName, thisMod.l10n.title, thisMod.modDesc.author @@ -181,16 +168,16 @@ window.mods.receive('fromMain_modList', (opts) => { displayBadges.forEach((badge) => { if ( typeof searchTagMap?.[badge]?.push === 'function' ) { - searchTagMap[badge].push(modColUUID) + searchTagMap[badge].push(thisMod.colUUID) } }) - scrollRows.push(`
`) + scrollRows.push(fsgUtil.buildScrollMod(collectKey, thisMod.colUUID)) modRows.push(makeModRow( - modColUUID, + thisMod.colUUID, thisMod, displayBadges, - modId + thisMod.modHub.id )) } catch (e) { @@ -199,32 +186,26 @@ window.mods.receive('fromMain_modList', (opts) => { }) modTable.push(makeModCollection( - collection, - `${opts.modList[collection].name} [${fsgUtil.bytesToHR(sizeOfFolder, opts.currentLocale)}]`, + collectKey, + `${thisCollection.name} [${fsgUtil.bytesToHR(sizeOfFolder, lastLocale)}]`, modRows, - fsgUtil.notesDefault(opts.notes, collection, 'notes_website'), - fsgUtil.notesDefault(opts.notes, collection, 'notes_websiteDL', false), - fsgUtil.notesDefault(opts.notes, collection, 'notes_tagline'), - fsgUtil.notesDefault(opts.notes, collection, 'notes_admin'), - opts.modList[collection].mods.length + fsgUtil.notesDefault(collectNotes, 'notes_website'), + fsgUtil.notesDefault(collectNotes, 'notes_websiteDL', false), + fsgUtil.notesDefault(collectNotes, 'notes_tagline'), + fsgUtil.notesDefault(collectNotes, 'notes_admin'), + thisCollection.dependSet.size )) - scrollTable.push(`
${scrollRows.join('')}`) - const selectCollName = `${opts.modList[collection].name}${window.mods.getCollDesc(collection)}` - - optList.push(fsgUtil.buildSelectOpt(`collection--${collection}`, selectCollName, selectedList, false, opts.foldersMap[collection])) - fullList[`collection--${collection}`] = selectCollName - + scrollTable.push(fsgUtil.buildScrollCollect(collectKey, scrollRows)) }) - optList.push(fsgUtil.buildSelectOpt('999', `--${opts.l10n.unknown}--`, selectedList, true)) - fullList[999] = `--${opts.l10n.unknown}--` - fsgUtil.byId('collectionSelect').innerHTML = optList.join('') + fsgUtil.byId('mod-collections').innerHTML = modTable.join('') fsgUtil.byId('scroll-bar-fake').innerHTML = scrollTable.join('') - Object.keys(opts.notes).forEach((collection) => { - const thisFav = opts?.notes[collection]?.notes_favorite || false + modCollect.set_Collections.forEach((collectKey) => { + const thisFav = fsgUtil.notesDefault(modCollect.collectionNotes?.[collectKey], 'notes_favorite', false) + if ( thisFav ) { - const favFolder = document.querySelector(`[data-bs-target="#${collection}_mods"] svg`) + const favFolder = document.querySelector(`[data-bs-target="#${collectKey}_mods"] svg`) if ( favFolder !== null ) { favFolder.innerHTML += '' @@ -232,7 +213,7 @@ window.mods.receive('fromMain_modList', (opts) => { } }) - const activeFolder = document.querySelector(`[data-bs-target="#${opts.activeCollection}_mods"] svg`) + const activeFolder = document.querySelector(`[data-bs-target="#${modCollect.opts.activeCollection}_mods"] svg`) if ( activeFolder !== null ) { let currentInner = activeFolder.innerHTML @@ -254,31 +235,56 @@ window.mods.receive('fromMain_modList', (opts) => { select_lib.filter(lastOpenID, lastOpenQ) } window.scrollTo(0, scrollStart) - } catch { /* nope */ } + } catch { + // Don't Care + } select_lib.filter() processL10N() }) -function metDepend(depends, collection, collectionMods) { - if ( typeof depends === 'undefined' || depends.length === 0 ) { return true } +function doBadgeSet(originalBadges, thisMod, thisCollection, newMods, bindConflicts) { + const theseBadges = originalBadges || [] + + if ( Object.keys(thisMod.modDesc.binds).length > 0 ) { + theseBadges.push(typeof bindConflicts[thisMod.fileDetail.shortName] !== 'undefined' ? 'keys_bad' : 'keys_ok') + } + + if ( thisMod.modHub.version !== null && thisMod.modDesc.version !== thisMod.modHub.version) { + theseBadges.push('update') + } - if ( typeof lastQuickLists[collection] === 'undefined' ) { - lastQuickLists[collection] = new Set() - collectionMods.forEach((mod) => { - lastQuickLists[collection].add(mod.fileDetail.shortName) + if ( newMods.has(thisMod.md5Sum) && !thisMod.canNotUse ) { + theseBadges.push('new') + } + + if ( thisMod.modHub.recent ) { + theseBadges.push('recent') + } + + if ( thisMod.modHub.id === null ) { + theseBadges.push('nonmh') + } + + if ( theseBadges.includes('broken') && theseBadges.includes('notmod') ) { + const brokenIdx = theseBadges.indexOf('broken') + theseBadges.splice(brokenIdx, brokenIdx !== -1 ? 1 : 0) + } + + if ( typeof thisMod.modDesc.depend !== 'undefined' && thisMod.modDesc.depend.length > 0 ) { + let hasAllDeps = true + + thisMod.modDesc.depend.forEach((thisDep) => { + if ( ! thisCollection.dependSet.has(thisDep) ) { + hasAllDeps = false + return + } }) + if ( !hasAllDeps ) { theseBadges.unshift('depend')} } - let hasAllDeps = true - depends.forEach((thisDep) => { - if ( ! lastQuickLists[collection].has(thisDep) ) { - hasAllDeps = false - return - } - }) - return hasAllDeps + return Array.from(new Set(theseBadges)) } function clientMakeListInactive() { diff --git a/renderer/renderJS/confirm_copy_move_ui.js b/renderer/renderJS/confirm_copy_move_ui.js index 2ad0499e..7c0a9ce7 100644 --- a/renderer/renderJS/confirm_copy_move_ui.js +++ b/renderer/renderJS/confirm_copy_move_ui.js @@ -31,7 +31,8 @@ window.l10n.receive('fromMain_getText_return', (data) => { window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -let lastRec = null +let lastModCollect = null +let lastFolderRelative = null window.mods.receive('fromMain_subWindowSelectAll', () => { fsgUtil.query('[type="checkbox"]').forEach((element) => { element.checked = true }) @@ -40,24 +41,20 @@ window.mods.receive('fromMain_subWindowSelectNone', () => { fsgUtil.query('[type="checkbox"]').forEach((element) => { element.checked = false }) }) -window.mods.receive('fromMain_confirmList', (confList) => { - const selectOpts = [] - - selectOpts.push(['...', 0]) +window.mods.receive('fromMain_confirmList', (modCollect) => { + lastModCollect = modCollect + lastFolderRelative = modCollect.collectionToFolderRelative[modCollect.opts.originCollectKey] + + const selectHTML = [] - lastRec = confList + selectHTML.push('') - Object.keys(confList.foldersMap).forEach((safeName) => { - if ( safeName !== confList.collection ) { - const humanName = `${fsgUtil.basename(confList.foldersMap[safeName])}${window.mods.getCollDesc(safeName)}` - selectOpts.push([humanName, safeName]) + modCollect.set_Collections.forEach((collectKey) => { + if ( collectKey !== modCollect.opts.originCollectKey ) { + selectHTML.push(``) } }) - const selectHTML = [] - selectOpts.forEach((opt) => { - selectHTML.push(``) - }) fsgUtil.byId('select_destination').innerHTML = selectHTML.join('') updateConfirmList() @@ -67,27 +64,27 @@ function updateConfirmList() { const confirmHTML = [] const selectedDest = fsgUtil.byId('select_destination').value - lastRec.records.forEach((mod) => { - const printPath = window.mods.homeDirMap(`${lastRec.foldersMap[lastRec.collection]}\\${fsgUtil.basename(mod.fileDetail.fullPath)}`) + lastModCollect.opts.records.forEach((thisMod) => { + const printPath = `${lastFolderRelative}\\${fsgUtil.basename(thisMod.fileDetail.fullPath)}` confirmHTML.push(`
- +
-

${mod.fileDetail.shortName} ${fsgUtil.escapeSpecial(mod.l10n.title)}

+

${thisMod.fileDetail.shortName} ${fsgUtil.escapeSpecial(thisMod.l10n.title)}

${printPath}

`) if ( selectedDest === '0' ) { confirmHTML.push(`
${getText('no_destination_selected')}
`) - } else if ( findConflict(selectedDest, mod.fileDetail.shortName, mod.fileDetail.isFolder) ) { + } else if ( findConflict(selectedDest, thisMod.fileDetail.shortName, thisMod.fileDetail.isFolder) ) { confirmHTML.push(`
${getText('destination_full')}
- - + +
`) @@ -95,7 +92,7 @@ function updateConfirmList() { confirmHTML.push(`
${getText('destination_clear')}
- `) + `) } confirmHTML.push('
') @@ -105,55 +102,44 @@ function updateConfirmList() { processL10N() } -function findConflict(collection, shortName, folder) { +function findConflict(collectKey, shortName, folder) { let foundConf = false - lastRec.list[collection].mods.forEach((mod) => { - if ( !foundConf && shortName === mod.fileDetail.shortName && folder === mod.fileDetail.isFolder ) { + lastModCollect.modList[collectKey].modSet.forEach((modKey) => { + const thisMod = lastModCollect.modList[collectKey].mods[modKey] + + if ( !foundConf && shortName === thisMod.fileDetail.shortName && folder === thisMod.fileDetail.isFolder ) { foundConf = true } }) return foundConf } -function clientDoCopy() { +function getSelectedMods() { const destination = fsgUtil.byId('select_destination').value if ( destination === '0' ) { return false } const fileMap = [] - lastRec.records.forEach((mod) => { + lastModCollect.opts.records.forEach((mod) => { const includeMeElement = fsgUtil.byId(mod.uuid) if ( includeMeElement.getAttribute('type') === 'checkbox' && includeMeElement.checked === true ) { - fileMap.push([destination, lastRec.collection, mod.fileDetail.fullPath]) + fileMap.push([destination, lastModCollect.opts.originCollectKey, mod.fileDetail.fullPath]) } if ( includeMeElement.getAttribute('type') === 'hidden' && includeMeElement.value ) { - fileMap.push([destination, lastRec.collection, mod.fileDetail.fullPath]) + fileMap.push([destination, lastModCollect.opts.originCollectKey, mod.fileDetail.fullPath]) } }) - window.mods.realCopyFile(fileMap) + return fileMap } +function clientDoCopy() { + window.mods.realCopyFile(getSelectedMods()) +} -function clientDoMove() { - const destination = fsgUtil.byId('select_destination').value - - if ( destination === '0' ) { return false } - - const fileMap = [] - - lastRec.records.forEach((mod) => { - const includeMeElement = fsgUtil.byId(mod.uuid) - if ( includeMeElement.getAttribute('type') === 'checkbox' && includeMeElement.checked === true ) { - fileMap.push([destination, lastRec.collection, mod.fileDetail.fullPath]) - } - if ( includeMeElement.getAttribute('type') === 'hidden' && includeMeElement.value ) { - fileMap.push([destination, lastRec.collection, mod.fileDetail.fullPath]) - } - }) - - window.mods.realMoveFile(fileMap) +function clientDoMove() { + window.mods.realMoveFile(getSelectedMods()) } diff --git a/renderer/renderJS/confirm_delete_ui.js b/renderer/renderJS/confirm_delete_ui.js index 42ce6fd3..9411f1d4 100644 --- a/renderer/renderJS/confirm_delete_ui.js +++ b/renderer/renderJS/confirm_delete_ui.js @@ -30,17 +30,20 @@ window.l10n.receive('fromMain_getText_return', (data) => { }) window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -let lastRec = null +let lastRec = null +let lastFolderRelative = null -window.mods.receive('fromMain_confirmList', (confList) => { - lastRec = confList +window.mods.receive('fromMain_confirmList', (modCollect) => { + lastRec = modCollect.opts + lastFolderRelative = modCollect.collectionToFolderRelative[modCollect.opts.originCollectKey] const confirmHTML = [] - confList.records.forEach((mod) => { - const printPath = window.mods.homeDirMap(`${confList.foldersMap[confList.collection]}\\${fsgUtil.basename(mod.fileDetail.fullPath)}`) + modCollect.opts.records.forEach((thisMod) => { + const printPath = `${lastFolderRelative}\\${fsgUtil.basename(thisMod.fileDetail.fullPath)}` + confirmHTML.push('
') - confirmHTML.push(`

${mod.fileDetail.shortName}

`) + confirmHTML.push(`

${thisMod.fileDetail.shortName}

`) confirmHTML.push(`

${printPath}

`) confirmHTML.push('') }) @@ -52,8 +55,8 @@ window.mods.receive('fromMain_confirmList', (confList) => { function clientDeleteButton() { const fileMap = [] - lastRec.records.forEach((mod) => { - fileMap.push([lastRec.collection, lastRec.collection, mod.fileDetail.fullPath]) + lastRec.records.forEach((thisMod) => { + fileMap.push([lastRec.originCollectKey, lastRec.originCollectKey, thisMod.fileDetail.fullPath]) }) window.mods.realDeleteFile(fileMap) diff --git a/renderer/renderJS/confirm_multi_ui.js b/renderer/renderJS/confirm_multi_ui.js index bc7a20b3..72d62573 100644 --- a/renderer/renderJS/confirm_multi_ui.js +++ b/renderer/renderJS/confirm_multi_ui.js @@ -32,27 +32,31 @@ window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) let lastSourceMods = null -window.mods.receive('fromMain_confirmList', (mods, destinations, modList) => { - lastSourceMods = mods +window.mods.receive('fromMain_confirmList', (modCollect) => { + lastSourceMods = modCollect.opts.sourceFiles const destChecks = [] const confRows = [] - destinations.forEach((collection) => { - destChecks.push(makeCheck(collection, modList[collection].name, window.mods.homeDirMap(modList[collection].fullPath))) + modCollect.opts.destinations.forEach((collectKey) => { + destChecks.push(makeCheck( + collectKey, + modCollect.collectionToName[collectKey], + modCollect.collectionToFolderRelative[collectKey] + )) }) - mods.forEach((mod) => { - confRows.push(makeRow(mod)) + modCollect.opts.sourceFiles.forEach((source) => { + confRows.push(makeRow(source)) }) - fsgUtil.byId('dest_list').innerHTML = destChecks.join('') + fsgUtil.byId('dest_list').innerHTML = destChecks.join('') fsgUtil.byId('confirm_list').innerHTML = confRows.join('') processL10N() }) -const makeRow = (row) => `${row[2]}${row[3]}` +const makeRow = (row) => `${row.shortName}${row.title}` const makeCheck = (id, name, dir) => `
@@ -64,9 +68,9 @@ function clientDoCopy() { const realDestinations = fsgUtil.query(':checked') const fileMap = [] - lastSourceMods.forEach((mod) => { + lastSourceMods.forEach((source) => { realDestinations.forEach((realDest) => { - fileMap.push([realDest.id, mod[1], mod[0]]) + fileMap.push([realDest.id, source.collectKey, source.fullPath]) }) }) diff --git a/renderer/renderJS/detail_ui.js b/renderer/renderJS/detail_ui.js index 37ce5dce..36d61f6c 100644 --- a/renderer/renderJS/detail_ui.js +++ b/renderer/renderJS/detail_ui.js @@ -40,13 +40,14 @@ window.l10n.receive('fromMain_getText_return_title', (data) => { window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -window.mods.receive('fromMain_modRecord', (modRecord, modhubRecord, bindConflict, thisLocale) => { - const mhVer = ( modhubRecord[1] !== null ) ? modhubRecord[1] : `${getText(modhubRecord[0] === null ? 'mh_norecord' : 'mh_unknown' )}` - const modDate = new Date(Date.parse(modRecord.fileDetail.fileDate)) +window.mods.receive('fromMain_modRecord', (modCollect) => { + const modRecord = modCollect.opts.selected + const mhVer = ( modRecord.modHub.id !== null ) ? modRecord.modHub.version : `${getText(modRecord.modHub.id === null ? 'mh_norecord' : 'mh_unknown' )}` + const modDate = new Date(Date.parse(modRecord.fileDetail.fileDate)) const idMap = { filesize : fsgUtil.bytesToHR(modRecord.fileDetail.fileSize, modRecord.currentLocale), - file_date : modDate.toLocaleString(thisLocale, {timeZoneName : 'short'}), + file_date : modDate.toLocaleString(modCollect.currentLocale, {timeZoneName : 'short'}), title : (( modRecord.l10n.title !== null && modRecord.l10n.title !== 'n/a' ) ? fsgUtil.escapeSpecial(modRecord.l10n.title) : modRecord.fileDetail.shortName), mod_location : modRecord.fileDetail.fullPath, mod_author : fsgUtil.escapeSpecial(modRecord.modDesc.author), @@ -72,13 +73,13 @@ window.mods.receive('fromMain_modRecord', (modRecord, modhubRecord, bindConflict const keyBinds = [] Object.keys(modRecord.modDesc.binds).forEach((action) => { const thisBinds = [] - modRecord.modDesc.binds[action].forEach((keyCombo) => { thisBinds.push(clientGetKeyMapSimple(keyCombo, thisLocale))}) + modRecord.modDesc.binds[action].forEach((keyCombo) => { thisBinds.push(clientGetKeyMapSimple(keyCombo, modCollect.currentLocale))}) keyBinds.push(`${action} :: ${thisBinds.join(' / ')}`) }) fsgUtil.byId('keyBinds').innerHTML = ( keyBinds.length > 0 ) ? keyBinds.join('\n') : getText('detail_key_none') - const bindingIssue = bindConflict[modRecord.currentCollection][modRecord.fileDetail.shortName] + const bindingIssue = modCollect.bindConflict[modRecord.currentCollection][modRecord.fileDetail.shortName] const bindingIssueTest = typeof bindingIssue !== 'undefined' if ( modRecord.issues.length < 1 && !bindingIssueTest ) { @@ -96,7 +97,7 @@ window.mods.receive('fromMain_modRecord', (modRecord, modhubRecord, bindConflict if ( bindingIssueTest ) { Object.keys(bindingIssue).forEach((keyCombo) => { - const actualKey = clientGetKeyMap(keyCombo, thisLocale) + const actualKey = clientGetKeyMap(keyCombo, modCollect.currentLocale) const confList = bindingIssue[keyCombo].join(', ') const issueText = `${getText('bind_conflict')} : ${actualKey} :: ${confList}` problems.push(`${checkX(0, false)}${issueText}`) @@ -109,24 +110,24 @@ window.mods.receive('fromMain_modRecord', (modRecord, modhubRecord, bindConflict const displayBadges = modRecord.badgeArray || [] if ( Object.keys(modRecord.modDesc.binds).length > 0 ) { - if ( typeof bindConflict[modRecord.currentCollection][modRecord.fileDetail.shortName] !== 'undefined' ) { + if ( typeof modCollect.bindConflict[modRecord.currentCollection][modRecord.fileDetail.shortName] !== 'undefined' ) { displayBadges.push('keys_bad') } else { displayBadges.push('keys_ok') } } - if ( modhubRecord[0] !== null && modhubRecord[1] !== null && modRecord.modDesc.version !== modhubRecord[1]) { + if ( modRecord.modHub.id !== null && modRecord.modHub.version !== null && modRecord.modDesc.version !== modRecord.modHub.version ) { displayBadges.push('update') } - if ( modhubRecord[2] ) { + if ( modRecord.modHub.recent ) { displayBadges.push('recent') } - if ( modhubRecord[0] === null ) { + if ( modRecord.modHub.id === null ) { displayBadges.push('nonmh') fsgUtil.byId('modhub_link').classList.add('d-none') } else { - const modhubLink = `https://www.farming-simulator.com/mod.php?mod_id=${modhubRecord[0]}` + const modhubLink = `https://www.farming-simulator.com/mod.php?mod_id=${modRecord.modHub.id}` fsgUtil.byId('modhub_link').innerHTML = `${modhubLink}` } diff --git a/renderer/renderJS/find_ui.js b/renderer/renderJS/find_ui.js index 2f23eefc..0de3d7be 100644 --- a/renderer/renderJS/find_ui.js +++ b/renderer/renderJS/find_ui.js @@ -51,11 +51,11 @@ window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) let fullList = {} let fullListSort = [] -window.mods.receive('fromMain_modRecords', (modList) => { +window.mods.receive('fromMain_modRecords', (modCollect) => { fullList = {} - Object.keys(modList).forEach((collection) => { - - modList[collection].mods.forEach((mod) => { + modCollect.set_Collections.forEach((collectKey) => { + modCollect.modList[collectKey].modSet.forEach((modKey) => { + const mod = modCollect.modList[collectKey].mods[modKey] if ( ! mod.canNotUse ) { fullList[mod.fileDetail.shortName] ??= { name : mod.fileDetail.shortName, @@ -66,8 +66,8 @@ window.mods.receive('fromMain_modRecords', (modList) => { } fullList[mod.fileDetail.shortName].collect.push({ version : fsgUtil.escapeSpecial(mod.modDesc.version), - name : modList[collection].name, - fullId : `${collection}--${mod.uuid}`, + name : modCollect.collectionToName[collectKey], + fullId : `${collectKey}--${mod.uuid}`, }) } }) diff --git a/renderer/renderJS/folder_ui.js b/renderer/renderJS/folder_ui.js index 974f432a..81472033 100644 --- a/renderer/renderJS/folder_ui.js +++ b/renderer/renderJS/folder_ui.js @@ -29,6 +29,7 @@ function clientGetL10NEntries() { window.l10n.receive('fromMain_getText_return', (data) => { fsgUtil.query(`l10n[name="${data[0]}"]`).forEach((item) => { item.innerHTML = data[1] }) }) + window.l10n.receive('fromMain_getText_return_title', (data) => { fsgUtil.query(`l10n[name="${data[0]}"]`).forEach((item) => { const buttonItem = item.closest('button') @@ -40,13 +41,19 @@ window.l10n.receive('fromMain_getText_return_title', (data) => { }) window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -window.mods.receive('fromMain_getFolders', (modList) => { - let folderNum = 0 +window.mods.receive('fromMain_getFolders', (modCollect) => { + let folderNum = 0 const localFolderList = [] - const lastFolder = Object.keys(modList).length - 1 - - Object.keys(modList).forEach((list) => { - localFolderList.push(makeFolderLine(modList[list].fullPath, modList[list].name, folderNum, lastFolder)) + const lastFolder = modCollect.set_Collections.size - 1 + + modCollect.set_Collections.forEach((collectKey) => { + localFolderList.push(makeFolderLine( + modCollect.collectionToFolder[collectKey], + modCollect.collectionToFolderRelative[collectKey], + modCollect.collectionToFullName[collectKey], + folderNum, + lastFolder + )) folderNum++ }) @@ -73,7 +80,7 @@ function dnBtn(num, last, disable) { return `${moveBtn('', num, num+1, disable)}${moveBtn('', num, last, disable)}` } -function makeFolderLine(path, name, num, last) { +function makeFolderLine(path, relPath, name, num, last) { return `
@@ -94,7 +101,7 @@ function makeFolderLine(path, name, num, last) {
- ${window.mods.homeDirMap(path)} + ${relPath}
diff --git a/renderer/renderJS/fsg_util.js b/renderer/renderJS/fsg_util.js index 1a2cb7a1..38e50a97 100644 --- a/renderer/renderJS/fsg_util.js +++ b/renderer/renderJS/fsg_util.js @@ -40,6 +40,12 @@ const fsgUtil = { buildSelectOpt : (value, text, selected, disabled = false, title = null) => { return `` }, + buildScrollCollect : (collectKey, scrollRows) => { + return `
${scrollRows.join('')}` + }, + buildScrollMod : (collectKey, modUUID) => { + return `
` + }, getAttribNullError : (element, attrib) => { const attribValue = element.getAttribute(attrib) @@ -85,8 +91,10 @@ const fsgUtil = { icon : 'data:image/png;base64,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' }, - notesDefault : (notes, collection, key, defaultValue = null) => { - const thisValue = notes?.[collection]?.[key] + notesDefault : (collectonNotes, key, defaultValue = null) => { + if ( typeof collectonNotes === 'undefined' ) { return defaultValue } + + const thisValue = collectonNotes?.[key] return ( typeof thisValue === 'undefined' || thisValue === '' ) ? defaultValue : thisValue }, diff --git a/renderer/renderJS/notes_ui.js b/renderer/renderJS/notes_ui.js index b592d4a5..4dc60871 100644 --- a/renderer/renderJS/notes_ui.js +++ b/renderer/renderJS/notes_ui.js @@ -31,16 +31,17 @@ window.l10n.receive('fromMain_getText_return', (data) => { }) window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -window.mods.receive('fromMain_collectionName', (collection, collectionName, allNotes, lastGameSettings) => { - thisCollection = collection - fsgUtil.byId('collection_name').innerHTML = collectionName +window.mods.receive('fromMain_collectionName', (modCollect) => { + thisCollection = modCollect.opts.collectKey + + fsgUtil.byId('collection_name').innerHTML = modCollect.collectionToName[thisCollection] fsgUtil.query('input').forEach((element) => { let thisValue = '' - let thisPlaceholder = lastGameSettings[element.id.replace('notes_', '')] - if ( typeof allNotes[collection] !== 'undefined' ) { - thisValue = allNotes[collection][element.id] + let thisPlaceholder = modCollect.opts.lastGameSettings[element.id.replace('notes_', '')] + if ( typeof modCollect.collectionNotes[thisCollection] !== 'undefined' ) { + thisValue = modCollect.collectionNotes[thisCollection][element.id] thisPlaceholder = ( typeof thisValue !== 'undefined' ) ? '' : thisPlaceholder } if ( element.getAttribute('type') === 'checkbox' ) { @@ -53,8 +54,8 @@ window.mods.receive('fromMain_collectionName', (collection, collectionName, allN clientCheckValid(element.id) }) - if ( typeof allNotes[collection] !== 'undefined' ) { - fsgUtil.byId('notes_notes').innerHTML = allNotes[collection].notes_notes || '' + if ( typeof modCollect.collectionNotes[thisCollection] !== 'undefined' ) { + fsgUtil.byId('notes_notes').innerHTML = modCollect.collectionNotes[thisCollection].notes_notes || '' } processL10N() diff --git a/renderer/renderJS/resolve_ui.js b/renderer/renderJS/resolve_ui.js index dd3248c0..2b602235 100644 --- a/renderer/renderJS/resolve_ui.js +++ b/renderer/renderJS/resolve_ui.js @@ -14,7 +14,6 @@ | |_| |_| -- || | |__|______|______||__|__| */ -let badVersionString = false let cacheShortName = null let cacheCollection = null @@ -48,69 +47,84 @@ window.mods.receive('fromMain_subWindowSelectNone', () => { }) window.mods.receive('fromMain_modSet', (modSet, shortName) => { - let version = [0, '0.0.0.0', null] + let latestVersion = { vString : null, vParts : [], collectKey : null } const modHTML = [] fsgUtil.byId('modName').innerHTML = shortName cacheShortName = shortName modSet.forEach((mod) => { - version = compareVersion(version, mod[1], mod[0]) + latestVersion = compareVersion(latestVersion, mod.version, mod.collectKey) }) - if ( badVersionString ) { - fsgUtil.byId('newVersion').innerHTML = 'ERROR - NON-NUMERIC VERSION FOUND' - fsgUtil.byId('copyButton').classList.add('disabled') - } else { - fsgUtil.byId('newVersion').innerHTML = version[1] - fsgUtil.byId('copyButton').classList.remove('disabled') - cacheCollection = version[2] - } + fsgUtil.byId('newVersion').innerHTML = latestVersion.vString + fsgUtil.byId('copyButton').classList.remove('disabled') + cacheCollection = latestVersion.collectKey modSet.forEach((mod) => { - modHTML.push(makeLine(mod, version)) + modHTML.push(makeLine(mod, latestVersion)) }) fsgUtil.byId('modSet').innerHTML = modHTML.join('') processL10N() }) -function compareVersion(versionArray, thisVersion, collection) { - const verParts = thisVersion.split('.').reverse() - let thisVersionInt = 0 +function compareVersion(latestVersion, thisVersion, collectKey) { + const latestVersionRet = latestVersion + const thisVersionParts = thisVersion.split('.') + + if ( latestVersion.vString === null ) { + latestVersionRet.collectKey = collectKey + latestVersionRet.vString = thisVersion + latestVersionRet.vParts = thisVersionParts + return latestVersionRet + } + + if ( latestVersion.vString === latestVersion ) { + return latestVersionRet + } - for ( let i = 0; i < verParts.length; i++ ) { - thisVersionInt += verParts[i] * Math.pow(10, i) - if ( isNaN(thisVersionInt) ) { badVersionString = true } + if ( latestVersion.vParts.length !== thisVersionParts.length ) { + // Different number of parts, string compare. + if ( thisVersion > latestVersion.vString ) { + latestVersionRet.collectKey = collectKey + latestVersionRet.vString = thisVersion + latestVersionRet.vParts = thisVersionParts + } + return latestVersionRet } - if ( thisVersionInt > versionArray[0] ) { - return [thisVersionInt, thisVersion, collection] - + for ( let i = 0; i < latestVersion.vParts.length; i++ ) { + if ( latestVersion.vParts[i] < thisVersionParts[i] ) { + latestVersionRet.collectKey = collectKey + latestVersionRet.vString = thisVersion + latestVersionRet.vParts = thisVersionParts + return latestVersionRet + } } - return versionArray + return latestVersionRet } function makeLine(mod, version) { - if ( mod[1] === version[1] ) { //same + if ( mod.version === version.vString ) { //same return `
  • -
    ${mod[2].fileDetail.shortName}
    -
    ${fsgUtil.escapeSpecial(mod[2].l10n.title)}
    -
    ${getText('destination')} ${mod[3]} :: ${getText('version_same')}
    +
    ${mod.modRecord.fileDetail.shortName}
    +
    ${fsgUtil.escapeSpecial(mod.modRecord.l10n.title)}
    +
    ${getText('destination')} ${mod.collectName} :: ${getText('version_same')}
  • ` } return `
  • -
    ${mod[2].fileDetail.shortName} ${mod[1]}
    -
    ${fsgUtil.escapeSpecial(mod[2].l10n.title)}
    -
    ${getText('destination')} ${mod[3]}
    +
    ${mod.modRecord.fileDetail.shortName} ${mod.version}
    +
    ${fsgUtil.escapeSpecial(mod.modRecord.l10n.title)}
    +
    ${getText('destination')} ${mod.collectName}
    - +
  • ` } @@ -122,6 +136,7 @@ function clientDoCopy() { fileMap.push([thisCheck.value, cacheCollection, `${cacheShortName}.zip`]) }) + window.mods.realCopyFile(fileMap) } diff --git a/renderer/renderJS/savegame_ui.js b/renderer/renderJS/savegame_ui.js index a422d0c2..99175081 100644 --- a/renderer/renderJS/savegame_ui.js +++ b/renderer/renderJS/savegame_ui.js @@ -61,19 +61,20 @@ window.l10n.receive('fromMain_getText_return_title', (data) => { window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -window.mods.receive('fromMain_collectionName', (collection, modList) => { - thisCollection = collection +window.mods.receive('fromMain_collectionName', (modCollect) => { + thisCollection = modCollect.opts.collectKey - fsgUtil.byId('collection_name').innerHTML = modList[collection].name - fsgUtil.byId('collection_location').innerHTML = modList[collection].fullPath + fsgUtil.byId('collection_name').innerHTML = modCollect.collectionToName[thisCollection] + fsgUtil.byId('collection_location').innerHTML = modCollect.collectionToFolderRelative[thisCollection] processL10N() }) -window.mods.receive('fromMain_saveInfo', (modList, savegame, modHubList) => { - const fullModSet = new Set() - const haveModSet = {} - const modSetHTML = [] +window.mods.receive('fromMain_saveInfo', (modCollect) => { + const savegame = modCollect.opts.thisSaveGame + const fullModSet = new Set() + const haveModSet = {} + const modSetHTML = [] selectList.inactive = [] selectList.unused = [] @@ -103,7 +104,8 @@ window.mods.receive('fromMain_saveInfo', (modList, savegame, modHubList) => { `) } - modList[thisCollection].mods.forEach((thisMod) => { + + Object.values(modCollect.modList[thisCollection].mods).forEach((thisMod) => { haveModSet[thisMod.fileDetail.shortName] = thisMod fullModSet.add(thisMod.fileDetail.shortName) }) @@ -122,7 +124,7 @@ window.mods.receive('fromMain_saveInfo', (modList, savegame, modHubList) => { isDLC : false, usedBy : null, versionMismatch : false, - isModHub : typeof modHubList.mods[thisMod] !== 'undefined', + isModHub : typeof modCollect.modHub.list.mods[thisMod] !== 'undefined', } if ( thisMod.startsWith('pdlc_')) { diff --git a/renderer/renderJS/version_ui.js b/renderer/renderJS/version_ui.js index b9dc1ae8..aded0a77 100644 --- a/renderer/renderJS/version_ui.js +++ b/renderer/renderJS/version_ui.js @@ -37,16 +37,17 @@ window.l10n.receive('fromMain_getText_return_title', (data) => { window.l10n.receive('fromMain_l10n_refresh', () => { processL10N() }) -window.mods.receive('fromMain_modList', (modList) => { +window.mods.receive('fromMain_modList', (modCollect) => { const nameIconMap = {} const collectionMap = {} const nameTitleMap = {} const versionList = {} const versionListNoMatch = {} - Object.keys(modList).forEach((collection) => { - collectionMap[collection] = modList[collection].name - modList[collection].mods.forEach((mod) => { + modCollect.set_Collections.forEach((collectKey) => { + collectionMap[collectKey] = modCollect.collectionToName[collectKey] + modCollect.modList[collectKey].modSet.forEach((modKey) => { + const mod = modCollect.modList[collectKey].mods[modKey] const modName = mod.fileDetail.shortName const modVer = mod.modDesc.version @@ -54,7 +55,7 @@ window.mods.receive('fromMain_modList', (modList) => { nameTitleMap[modName] ??= fsgUtil.escapeSpecial(mod.l10n.title) nameIconMap[modName] ??= mod.modDesc.iconImageCache versionList[modName] ??= [] - versionList[modName].push([collection, modVer]) + versionList[modName].push([collectKey, modVer]) } }) }) diff --git a/test/mod-reader-test.js b/test/mod-reader-test.js index c8ff7714..fd8db1bc 100644 --- a/test/mod-reader-test.js +++ b/test/mod-reader-test.js @@ -8,52 +8,62 @@ const path = require('path') const fs = require('fs') +const os = require('os') const testPath = path.join(__dirname, 'testMods') -const { ma_logger } = require('../lib/ma-logger.js') -const { modFileChecker } = require('../lib/single-mod-checker.js') +const { ma_logger } = require('../lib/ma-logger.js') +const { modFileCollection } = require('../lib/modCheckLib.js') + -const logger = new ma_logger('multi-test') console.log('FSG Mod Assistant : Test Mod Reader') console.log('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n') let exitCode = 0 -let folderContents = [] + +const logger = new ma_logger('multi-test') +const modCollect = new modFileCollection( + logger, + { get : () => { return '' }, store : {} }, + { store : {} }, + os.homedir(), + { + hide : () => { return }, + count : () => { return }, + }, + () => { return 'en'}, + true +) try { - folderContents = fs.readdirSync(testPath, {withFileTypes : true}) + fs.readdirSync(testPath, {withFileTypes : true}) } catch (e) { console.log(`Couldn't open test folder :: ${e}`) exitCode = 1 process.exit(exitCode) } -folderContents.forEach((thisFile) => { - console.log(`${thisFile.name} :: BEGIN`) - try { - const thisMod = new modFileChecker( - path.join(testPath, thisFile.name), - thisFile.isDirectory(), - 0, - new Date(1970, 1, 1, 0, 0, 0, 0), - logger, - () => { return 'en'} - ) - console.log(` Giants HASH : ${thisMod.giantsHash}`) - console.log(` Issues : ${thisMod.issues.join(', ')}`) - console.log(` Badges : ${thisMod.badgeArray.join(', ')}`) - console.log(` Extra : ${thisMod.fileDetail.extraFiles.join(', ')}`) - } catch (e) { - console.log(` Unable to read ${thisFile} :: ${e}`) - console.log(e.stack) - exitCode = 1 - } - console.log(`${thisFile.name} :: END\n`) -}) - -console.log('\n\nLogger:') -console.log(logger.textLog) - -console.log(`\n\nExiting with code ${exitCode}\n`) -process.exit(exitCode) +console.log('-=-=-=-=-=-=-=-=-=-=-=-=-') +console.log('TEST COLLECTION :: BEGIN') +console.log('-=-=-=-=-=-=-=-=-=-=-=-=-') + +const thisCollectionStats = modCollect.addCollection(testPath) + +console.log(` File Count: ${thisCollectionStats.fileCount}\n`) + +modCollect.processMods() + +modCollect.processPromise.then(() => { + console.log('\n-=-=-=-=-=-=-=-=-=-=-=-=-') + console.log('TEST COLLECTION :: FINISHED') + console.log('-=-=-=-=-=-=-=-=-=-=-=-=-\n') + modCollect.collections.forEach((collectKey) => { + modCollect.getModListFromCollection(collectKey).forEach((thisMod) => { + console.log(`Short Name : ${thisMod.fileDetail.shortName}`) + console.log(` Issues : ${thisMod.issues.join(', ')}`) + console.log(` Badges : ${thisMod.badgeArray.join(', ')}`) + console.log(` Extra : ${thisMod.fileDetail.extraFiles.join(', ')}`) + }) + }) + console.log(`\n\nExiting with code ${exitCode}\n`) +}) \ No newline at end of file diff --git a/test/single_read_test.js b/test/single_read_test.js deleted file mode 100644 index 1b46a62e..00000000 --- a/test/single_read_test.js +++ /dev/null @@ -1,35 +0,0 @@ -/* _______ __ _______ __ __ - | | |.-----.--| | _ |.-----.-----.|__|.-----.| |_ - | || _ | _ | ||__ --|__ --|| ||__ --|| _| - |__|_|__||_____|_____|___|___||_____|_____||__||_____||____| - (c) 2022-present FSG Modding. MIT License. */ - -// Test Program - -const { ma_logger } = require('../lib/ma-logger.js') -const { modFileChecker } = require('../lib/single-mod-checker.js') - -const logger = new ma_logger('single-test') - -console.log('FSG Mod Assistant : Test Mod Reader') -console.log('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n') - -if ( process.argv.length < 3 ) { - console.log('File not provided') - process.exit(1) -} -const thisModFile = process.argv[2] - -console.log(` --Testing: ${thisModFile}`) - -const thisMod = new modFileChecker( - thisModFile, - false, - 0, - new Date(1970, 1, 1, 0, 0, 0, 0), - logger, - () => { return 'en'} -) - -console.log(thisMod.debugDump) - diff --git a/translations/de.json b/translations/de.json index 3c4d2755..6ab27ab5 100644 --- a/translations/de.json +++ b/translations/de.json @@ -302,5 +302,7 @@ "context_main_copy": "Kopieren", "context_mod_detail": "Details anzeigen", "user_pref_title_use_one_drive": "OneDrive-Synchronisierungsmodus verwenden", - "user_pref_blurb_use_one_drive": "Schalten Sie diesen Modus ein, wenn Ihre Mod-Sammlung mit OneDrive synchronisiert ist, Sie mehrere Computer verwenden und Mod Assistant Ihre gesamte Sammlung bei jeder Aktualisierung von Grund auf neu scannt." + "user_pref_blurb_use_one_drive": "Schalten Sie diesen Modus ein, wenn Ihre Mod-Sammlung mit OneDrive synchronisiert ist, Sie mehrere Computer verwenden und Mod Assistant Ihre gesamte Sammlung bei jeder Aktualisierung von Grund auf neu scannt.", + "user_pref_blurb_clear_cache": "Mit dieser Schaltfläche können Sie die Cache-Datei von Mod Assist löschen und neu erstellen. Seien Sie vorsichtig, es kann eine Weile dauern, bis der Vorgang abgeschlossen ist", + "user_pref_button_clear_cache": "Cache löschen [VORSICHT!]" } \ No newline at end of file diff --git a/translations/en.json b/translations/en.json index e0d5e417..bb024a3b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -302,5 +302,7 @@ "context_main_copy" : "Copy", "context_mod_detail" : "Show Details", "user_pref_title_use_one_drive" : "Use OneDrive Sync Mode", - "user_pref_blurb_use_one_drive" : "Turn this mode on if your mod collection is synced with OneDrive, you use multiple computers, and Mod Assistant re-scans from scratch your entire collection on every refresh" + "user_pref_blurb_use_one_drive" : "Turn this mode on if your mod collection is synced with OneDrive, you use multiple computers, and Mod Assistant re-scans from scratch your entire collection on every refresh", + "user_pref_blurb_clear_cache" : "This button allows you to clear and rebuild Mod Assist's cache file. Use with care, it may take a while to complete", + "user_pref_button_clear_cache" : "Clear Cache [BE CAREFUL!]" } \ No newline at end of file diff --git a/translations/es.json b/translations/es.json index d146f50d..2f043737 100644 --- a/translations/es.json +++ b/translations/es.json @@ -302,5 +302,7 @@ "context_main_copy": "Copia", "context_mod_detail": "Mostrar detalles", "user_pref_title_use_one_drive": "Usar el modo de sincronización de OneDrive", - "user_pref_blurb_use_one_drive": "Activa este modo si tu colección de mods está sincronizada con OneDrive, utilizas varios ordenadores y Mod Assistant vuelve a escanear desde cero toda tu colección en cada actualización" + "user_pref_blurb_use_one_drive": "Activa este modo si tu colección de mods está sincronizada con OneDrive, utilizas varios ordenadores y Mod Assistant vuelve a escanear desde cero toda tu colección en cada actualización", + "user_pref_blurb_clear_cache": "Este botón permite borrar y reconstruir el archivo de caché de Mod Assist. Utilízalo con cuidado, puede tardar un poco en completarse", + "user_pref_button_clear_cache": "Borrar caché [¡CUIDADO!]" } \ No newline at end of file diff --git a/translations/fr.json b/translations/fr.json index a3ec1c04..20fefeb4 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -302,5 +302,7 @@ "context_main_copy": "Copie", "context_mod_detail": "Afficher les détails", "user_pref_title_use_one_drive": "Utiliser le mode de synchronisation OneDrive", - "user_pref_blurb_use_one_drive": "Activez ce mode si votre collection de mods est synchronisée avec OneDrive, vous utilisez plusieurs ordinateurs, et l'assistant de mod re-scanne à partir de zéro toute votre collection à chaque rafraîchissement" + "user_pref_blurb_use_one_drive": "Activez ce mode si votre collection de mods est synchronisée avec OneDrive, vous utilisez plusieurs ordinateurs, et l'assistant de mod re-scanne à partir de zéro toute votre collection à chaque rafraîchissement", + "user_pref_blurb_clear_cache": "Ce bouton vous permet d'effacer et de reconstruire le fichier cache de Mod Assist. Utilisez-le avec précaution, il peut prendre un certain temps avant d'être terminé", + "user_pref_button_clear_cache": "Effacer le cache [ATTENTION !]" } \ No newline at end of file diff --git a/translations/nl.json b/translations/nl.json index 10a787c3..bf9446ab 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -302,5 +302,7 @@ "context_main_copy": "Kopie", "context_mod_detail": "Toon details", "user_pref_title_use_one_drive": "Gebruik OneDrive Sync Mode", - "user_pref_blurb_use_one_drive": "Zet deze modus aan als je modcollectie is gesynchroniseerd met OneDrive, je meerdere computers gebruikt en Mod Assistant bij elke verversing je hele collectie opnieuw scant." + "user_pref_blurb_use_one_drive": "Zet deze modus aan als je modcollectie is gesynchroniseerd met OneDrive, je meerdere computers gebruikt en Mod Assistant bij elke verversing je hele collectie opnieuw scant.", + "user_pref_blurb_clear_cache": "Met deze knop kun je het cache-bestand van Mod Assist wissen en opnieuw opbouwen. Voorzichtig gebruiken, het kan even duren voordat het klaar is.", + "user_pref_button_clear_cache": "Cache wissen [WEES VOORZICHTIG.!]." } \ No newline at end of file diff --git a/translations/pl.json b/translations/pl.json index 93cb92f0..fcd9428b 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -302,5 +302,7 @@ "context_main_copy": "Kopiuj", "context_mod_detail": "Pokaż szczegóły", "user_pref_title_use_one_drive": "Użyj trybu synchronizacji OneDrive", - "user_pref_blurb_use_one_drive": "Włącz ten tryb, jeśli twój folder z modami jest synchronizowany z OneDrive lub jeśli używasz wielu komputerów, a Mod Asystent ponownie skanuje od zera cały twój folder przy każdym odświeżeniu" -} + "user_pref_blurb_use_one_drive": "Włącz ten tryb, jeśli twój folder z modami jest synchronizowany z OneDrive lub jeśli używasz wielu komputerów, a Mod Asystent ponownie skanuje od zera cały twój folder przy każdym odświeżeniu", + "user_pref_blurb_clear_cache": "Ten przycisk pozwala na wyczyszczenie i odbudowanie pliku cache Mod Assist. Używaj z ostrożnością, ukończenie może zająć chwilę", + "user_pref_button_clear_cache": "Wyczyść pamięć podręczną [BĄDŹ OSTROŻNY!]" +} \ No newline at end of file diff --git a/translations/pt.json b/translations/pt.json index 531c2d5e..9ff193e2 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -302,5 +302,7 @@ "context_main_copy": "Cópia", "context_mod_detail": "Mostrar detalhes", "user_pref_title_use_one_drive": "Use o modo OneDrive Sync", - "user_pref_blurb_use_one_drive": "Ligue este modo se a sua colecção de mod está sincronizada com a OneDrive, utiliza vários computadores, e o Mod Assistant digitaliza novamente de raiz toda a sua colecção em cada actualização" + "user_pref_blurb_use_one_drive": "Ligue este modo se a sua colecção de mod está sincronizada com a OneDrive, utiliza vários computadores, e o Mod Assistant digitaliza novamente de raiz toda a sua colecção em cada actualização", + "user_pref_blurb_clear_cache": "Este botão permite-lhe limpar e reconstruir o ficheiro de cache do Mod Assist. Use com cuidado, pode demorar algum tempo a completar", + "user_pref_button_clear_cache": "Cache claro [Sê cuidadoso!]" } \ No newline at end of file diff --git a/translations/ru.json b/translations/ru.json index dd17c80c..1b447fde 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -302,5 +302,7 @@ "context_main_copy": "Копировать", "context_mod_detail": "Показать подробности", "user_pref_title_use_one_drive": "Использовать режим синхронизации OneDrive", - "user_pref_blurb_use_one_drive": "Включите этот режим, если ваша коллекция модов синхронизирована с OneDrive, вы используете несколько компьютеров и Mod Assistant повторно сканирует всю вашу коллекцию с нуля при каждом обновлении" + "user_pref_blurb_use_one_drive": "Включите этот режим, если ваша коллекция модов синхронизирована с OneDrive, вы используете несколько компьютеров и Mod Assistant повторно сканирует всю вашу коллекцию с нуля при каждом обновлении", + "user_pref_blurb_clear_cache": "Эта кнопка позволяет очистить и восстановить файл кэша Mod Assist. Используйте с осторожностью, это может занять некоторое время", + "user_pref_button_clear_cache": "Очистить кэш [ОСТОРОЖНО!]" } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4147de75..1264c322 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1853,18 +1853,10 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" -jasmine-core@~3.99.0: - version "3.99.1" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.99.1.tgz#5bfa4b2d76618868bfac4c8ff08bb26fffa4120d" - integrity sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg== - -jasmine@^3.1.0: - version "3.99.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.99.0.tgz#7cc7aeda7ade2d57694fc818a374f778cbb4ea62" - integrity sha512-YIThBuHzaIIcjxeuLmPD40SjxkEcc8i//sGMDKCgkRMVgIwRJf5qyExtlJpQeh7pkeoBSOe6lQEdg+/9uKg9mw== - dependencies: - glob "^7.1.6" - jasmine-core "~3.99.0" +jpeg-js@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== js-sdsl@^4.1.4: version "4.3.0" @@ -2305,11 +2297,6 @@ plist@^3.0.1, plist@^3.0.4: base64-js "^1.5.1" xmlbuilder "^15.1.1" -pngjs@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" - integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -2791,13 +2778,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -windows@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/windows/-/windows-0.1.2.tgz#adff4ff04f55cbb25761c3666b81558569ff1654" - integrity sha512-u4R5VsMedbhaRwF4I05P8doeuD3mD48J1clcqqV3zEVlXtXaTK70+wApZNwEipAXV/nPKZRopW3ic6tqNC2fcQ== - dependencies: - jasmine "^3.1.0" - word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"