diff --git a/gcs/electron/fla.js b/gcs/electron/fla.ts similarity index 78% rename from gcs/electron/fla.js rename to gcs/electron/fla.ts index f1c7e5829..4faaa0693 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.ts @@ -2,43 +2,105 @@ This file contains the logic for parsing different types of log files on the main electron process. */ +import { WebContents } from "electron" import fs from "fs" import readline from "readline" -import createRecentLogsManager from "../settings/recentLogManager" +import createRecentLogsManager from "./utils/recentLogManager" import { - clearUnitCache, buildDefaultMessageFilters, - calculateMeanValues, calcGPSOffset, + calculateMeanValues, + clearUnitCache, convertTimeUStoUTC, expandBATMessages, expandESCMessages, + getUnit, processFlightModes, sortObjectByKeys, - getUnit, -} from "./utils/fla-utils" +} from "./utils/flaUtils" + +// Type definitions +interface FormatMessage { + length: number + name: string + type: number + format: string + fields: string[] + units?: string + multiplier?: string +} + +interface MessageObject { + name: string + type?: number + TimeUS?: number + [key: string]: string | number | undefined +} + +interface Messages { + [messageName: string]: + | MessageObject[] + | { [key: string]: FormatMessage } + | { [key: string]: string } + | string + | null +} + +interface ParseResult { + success: boolean + error?: string + summary?: LogSummary +} + +interface LogSummary { + formatMessages: { [key: string]: FormatMessage } + utcAvailable: boolean + logEvents: MessageObject[] + flightModeMessages: MessageObject[] + logType: string + messageFilters: Record + messageMeans: Record< + string, + { mean: string; max: string; min: string } + > | null + aircraftType: string | null +} + +interface Dataset { + label: string + yAxisID: string + x: Float64Array + y: Float32Array +} + +type LogType = "dataflash" | "fgcs_telemetry" | "mp_telemetry" | null const UPDATE_THROTTLE_MS = 100 // Update every 100ms const recentLogsManager = createRecentLogsManager() -let logData = null -let defaultMessageFilters = {} - -async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) { +let logData: Messages | null = null +let defaultMessageFilters: Record = {} + +async function parseDataflashLogFile( + rl: readline.Interface, + fileStream: fs.ReadStream, + fileSize: number, + webContents: WebContents, +): Promise { // https://ardupilot.org/copter/docs/logmessages.html // https://github.com/ArduPilot/ardupilot/tree/master/libraries/AP_Logger return new Promise((resolve, reject) => { const stringTypes = new Set(["n", "N", "Z", "M"]) - let aircraftType = null + let aircraftType: string | null = null let lastUpdateTime = 0 - const formatMessages = {} - const messages = {} - const units = {} + const formatMessages: { [key: string]: FormatMessage } = {} + const messages: Messages = {} + const units: { [key: string]: string } = {} - rl.on("line", (line) => { + rl.on("line", (line: string) => { // Skip empty lines early if (!line || line.length < 3) return @@ -116,7 +178,7 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) { messages[messageName] = [] } - const messageObj = { + const messageObj: MessageObject = { name: messageName, type: formatMessage.type, } @@ -144,7 +206,7 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) { } } - messages[messageName].push(messageObj) + ;(messages[messageName] as MessageObject[]).push(messageObj) } } @@ -168,7 +230,7 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) { resolve(messages) }) - rl.on("error", (err) => { + rl.on("error", (err: Error) => { console.error("Error reading log file:", err) reject(err) }) @@ -176,18 +238,18 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) { } async function parseFgcsTelemetryLogFile( - rl, - fileStream, - fileSize, - webContents, -) { - const formatMessages = {} - const messages = {} + rl: readline.Interface, + fileStream: fs.ReadStream, + fileSize: number, + webContents: WebContents, +): Promise { + const formatMessages: { [key: string]: FormatMessage } = {} + const messages: Messages = {} let lastUpdateTime = 0 return new Promise((resolve, reject) => { - rl.on("line", (line) => { + rl.on("line", (line: string) => { if (!line || line.length < 5 || line.includes("==")) { return } @@ -210,7 +272,7 @@ async function parseFgcsTelemetryLogFile( // Get field names and cache format if (!formatMessages[messageName]) { - const fields = [] + const fields: string[] = [] for (let i = 0; i < messageData.length; i++) { const keyVal = messageData[i]?.trim() if (keyVal) { @@ -220,10 +282,16 @@ async function parseFgcsTelemetryLogFile( } } } - formatMessages[messageName] = { fields } + formatMessages[messageName] = { + fields, + length: 0, + name: messageName, + type: 0, + format: "", + } } - const messageObj = { + const messageObj: MessageObject = { TimeUS: Math.round(timestamp * 1000), // Use round instead of parseInt for better precision name: messageName, } @@ -247,7 +315,7 @@ async function parseFgcsTelemetryLogFile( messages[messageName] = [] } - messages[messageName].push(messageObj) + ;(messages[messageName] as MessageObject[]).push(messageObj) const now = Date.now() if (now - lastUpdateTime > UPDATE_THROTTLE_MS) { @@ -267,18 +335,18 @@ async function parseFgcsTelemetryLogFile( resolve(messages) }) - rl.on("error", (err) => { + rl.on("error", (err: Error) => { console.error("Error reading log file:", err) reject(err) }) }) } -function determineLogFileType(filePath, firstLine) { +function determineLogFileType(filePath: string, firstLine: string): LogType { const reFileExtension = /(?:\.([^.]+))?$/ // https://stackoverflow.com/a/680982 - const ext = reFileExtension.exec(filePath)[1] + const ext = reFileExtension.exec(filePath)?.[1] - const extensionToTypeMap = { + const extensionToTypeMap: { [key: string]: LogType } = { log: "dataflash", ftlog: "fgcs_telemetry", // exclude txt from map as txt's are ambiguous @@ -304,21 +372,21 @@ function determineLogFileType(filePath, firstLine) { } // New function to get recent files -export function getRecentFiles() { +export function getRecentFiles(): string[] { return recentLogsManager.getRecentLogs() } // New function to clear recent files -export function clearRecentFiles() { +export function clearRecentFiles(): void { recentLogsManager.clearRecentLogs() } -async function getFirstLine(pathToFile) { +async function getFirstLine(pathToFile: string): Promise { // https://stackoverflow.com/a/60193465/23139916 const readable = fs.createReadStream(pathToFile) const reader = readline.createInterface({ input: readable }) - const line = await new Promise((resolve) => { - reader.on("line", (line) => { + const line = await new Promise((resolve) => { + reader.on("line", (line: string) => { reader.close() resolve(line) }) @@ -328,10 +396,14 @@ async function getFirstLine(pathToFile) { } // function to process and save the log file data -function processAndSaveLogData(loadedLogMessages, logType) { +function processAndSaveLogData( + loadedLogMessages: Messages, + logType: string, +): LogSummary { clearUnitCache() // Clear cache when loading new file - const aircraftType = loadedLogMessages.aircraftType - delete loadedLogMessages.aircraftType + const aircraftType = + (loadedLogMessages["aircraftType"] as string | null) || null + delete loadedLogMessages["aircraftType"] const initialFilters = buildDefaultMessageFilters(loadedLogMessages) @@ -351,7 +423,7 @@ function processAndSaveLogData(loadedLogMessages, logType) { // Convert TimeUS to TimeUTC if GPS data is available let finalMessages = { ...expandedMessages } - let gpsOffset = null + let gpsOffset: number | null = null let utcAvailable = false if (finalMessages.GPS && logType === "dataflash") { gpsOffset = calcGPSOffset(finalMessages) @@ -375,7 +447,7 @@ function processAndSaveLogData(loadedLogMessages, logType) { return { formatMessages: finalFormats, utcAvailable, - logEvents: finalMessages["EV"] || [], + logEvents: (finalMessages["EV"] as MessageObject[]) || [], flightModeMessages, logType, messageFilters: defaultMessageFilters, @@ -384,7 +456,10 @@ function processAndSaveLogData(loadedLogMessages, logType) { } } -export default async function openFile(event, filePath) { +export default async function openFile( + event: { sender: WebContents }, + filePath: string | null, +): Promise { if (filePath == null) { return { success: false, error: "No file path provided" } } @@ -410,7 +485,7 @@ export default async function openFile(event, filePath) { crlfDelay: Infinity, }) - let messages = null + let messages: Messages | null = null if (logType === "dataflash") { messages = await parseDataflashLogFile( @@ -446,14 +521,19 @@ export default async function openFile(event, filePath) { } else { return { success: false, error: "Failed to parse log file" } } - } catch (err) { + } catch (err: unknown) { console.error("Error parsing log file:", err) - return { success: false, error: err.message || "Unknown parsing error" } + const errorMessage = + err instanceof Error ? err.message : "Unknown parsing error" + return { success: false, error: errorMessage } } } // on-demand retrieval of messages -export async function getMessages(_event, requestedMessages) { +export async function getMessages( + _event: unknown, + requestedMessages: string[], +): Promise { // each requestedMessage should be of the form `${requestedMessageName}/${requestedFieldName}` // like ['ARM/ArmState', 'ARSP/Airspeed'] @@ -472,9 +552,10 @@ export async function getMessages(_event, requestedMessages) { return [] } - const formatMessages = logData.format || {} - const units = logData.units || {} - const datasets = [] + const formatMessages = + (logData.format as { [key: string]: FormatMessage }) || {} + const units = (logData.units as { [key: string]: string }) || {} + const datasets: Dataset[] = [] // Loop through the list of requested messages and transform each of them for ( @@ -494,7 +575,7 @@ export async function getMessages(_event, requestedMessages) { const fmt = formatMessages[categoryName] const hasField = fmt?.fields.includes(fieldName) - const series = logData[categoryName] + const series = logData[categoryName] as MessageObject[] if (!hasField || !Array.isArray(series) || series.length === 0) { // Skip unknown or unavailable labels diff --git a/gcs/electron/main.ts b/gcs/electron/main.ts index 463062d48..076e784c1 100644 --- a/gcs/electron/main.ts +++ b/gcs/electron/main.ts @@ -16,12 +16,7 @@ import fs from "node:fs" import path from "node:path" import packageInfo from "../package.json" -import openFile, { - clearRecentFiles, - getRecentFiles, - getMessages, - // @ts-expect-error - no types available -} from "./fla" +import openFile, { clearRecentFiles, getMessages, getRecentFiles } from "./fla" import registerAboutIPC, { destroyAboutWindow, openAboutPopout, @@ -42,15 +37,7 @@ import registerVibeStatusIPC, { } from "./modules/vibeStatusWindow" import registerVideoIPC, { destroyVideoWindow } from "./modules/videoWindow" import { readParamsFile } from "./utils/paramsFile" -// The built directory structure -// -// ├─┬─┬ dist -// │ │ └── index.html -// │ │ -// │ ├─┬ dist-electron -// │ │ ├── main.js -// │ │ └── preload.js -// │ + process.env.DIST = path.join(__dirname, "../dist") process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST diff --git a/gcs/electron/utils/fla-utils.js b/gcs/electron/utils/flaUtils.ts similarity index 54% rename from gcs/electron/utils/fla-utils.js rename to gcs/electron/utils/flaUtils.ts index 47a034f96..f016e527d 100644 --- a/gcs/electron/utils/fla-utils.js +++ b/gcs/electron/utils/flaUtils.ts @@ -1,4 +1,58 @@ -const ignoredMessages = [ +// Type definitions for fla-utils +interface MessageObject { + name: string + type?: number + TimeUS?: number + Instance?: number + Inst?: number + [key: string]: string | number | undefined +} + +interface FormatMessage { + length: number + name: string + type: number + format: string + fields: string[] + units?: string + multiplier?: string +} + +interface LoadedLogMessages { + [messageName: string]: + | MessageObject[] + | { [key: string]: FormatMessage } + | { [key: string]: string } + | string + | null +} + +interface FilterState { + [messageName: string]: { [fieldName: string]: boolean } +} + +interface FieldStats { + min: number + max: number + sum: number + count: number +} + +interface MeanValues { + [fieldKey: string]: { + mean: string + max: string + min: string + } +} + +interface ExpandResult { + updatedMessages: LoadedLogMessages + updatedFilters: FilterState + updatedFormats: { [key: string]: FormatMessage } +} + +const ignoredMessages: string[] = [ "ERR", "EV", "MSG", @@ -9,9 +63,19 @@ const ignoredMessages = [ "format", "aircraftType", ] -const ignoredKeys = ["TimeUS", "function", "source", "result", "time_boot_ms"] +const ignoredKeys: string[] = [ + "TimeUS", + "function", + "source", + "result", + "time_boot_ms", +] -export function gpsToUTC(gpsWeek, gms, leapSeconds = 18) { +export function gpsToUTC( + gpsWeek: number, + gms: number, + leapSeconds: number = 18, +): Date { // GPS epoch starts at 1980-01-06 00:00:00 UTC const gpsEpoch = new Date(Date.UTC(1980, 0, 6)) @@ -30,12 +94,14 @@ export function gpsToUTC(gpsWeek, gms, leapSeconds = 18) { * Structure: * { "MESSAGE_TYPE/FIELD_NAME": { mean: value, min: value, max: value }, ... } */ -export function calculateMeanValues(loadedLogMessages) { +export function calculateMeanValues( + loadedLogMessages: LoadedLogMessages, +): MeanValues | null { if (!loadedLogMessages) return null // Cache Set for O(1) lookups const ignoredMessagesSet = new Set(ignoredMessages) - const means = {} + const means: MeanValues = {} // Process data directly without building intermediate arrays Object.keys(loadedLogMessages) @@ -44,7 +110,7 @@ export function calculateMeanValues(loadedLogMessages) { const messageData = loadedLogMessages[key] if (!Array.isArray(messageData) || messageData.length === 0) return - const fieldStats = {} + const fieldStats: { [fieldKey: string]: FieldStats } = {} for (let i = 0; i < messageData.length; i++) { const message = messageData[i] @@ -99,12 +165,17 @@ export function calculateMeanValues(loadedLogMessages) { * // "AHR2": { "Roll": false, "Pitch": false, "Yaw": false } * // } */ -export function buildDefaultMessageFilters(loadedLogMessages) { - const logMessageFilterDefaultState = {} +export function buildDefaultMessageFilters( + loadedLogMessages: LoadedLogMessages, +): FilterState { + const logMessageFilterDefaultState: FilterState = {} // Cache keys and create Sets for O(1) lookups const messageKeys = Object.keys(loadedLogMessages) - const formatKeys = Object.keys(loadedLogMessages["format"]) + const formatData = loadedLogMessages["format"] as + | { [key: string]: FormatMessage } + | undefined + const formatKeys = Object.keys(formatData || {}) const ignoredMessagesSet = new Set(ignoredMessages) const ignoredKeysSet = new Set(ignoredKeys) @@ -113,24 +184,34 @@ export function buildDefaultMessageFilters(loadedLogMessages) { .filter((key) => messageKeys.includes(key) && !ignoredMessagesSet.has(key)) .sort() .forEach((key) => { - const fieldsState = {} + const fieldsState: { [fieldName: string]: boolean } = {} // Set all field states to false if they're not ignored - loadedLogMessages["format"][key].fields.forEach((field) => { - if (!ignoredKeysSet.has(field)) { - fieldsState[field] = false - } - }) + const formatMessage = formatData?.[key] + if (formatMessage?.fields) { + formatMessage.fields.forEach((field: string) => { + if (!ignoredKeysSet.has(field)) { + fieldsState[field] = false + } + }) + } logMessageFilterDefaultState[key] = fieldsState }) return logMessageFilterDefaultState } -export function processFlightModes(logType, loadedLogMessages) { +export function processFlightModes( + logType: string, + loadedLogMessages: LoadedLogMessages, +): MessageObject[] { if (logType === "dataflash") { - return loadedLogMessages.MODE + const modeData = loadedLogMessages["MODE"] + return Array.isArray(modeData) ? modeData : [] } else if (logType === "fgcs_telemetry") { - return getHeartbeatMessages(loadedLogMessages.HEARTBEAT) + const heartbeatData = loadedLogMessages["HEARTBEAT"] + return getHeartbeatMessages( + Array.isArray(heartbeatData) ? heartbeatData : [], + ) } else { return [] } @@ -140,8 +221,10 @@ export function processFlightModes(logType, loadedLogMessages) { * For fgcs_telemetry logs: * Extracts heartbeat messages where mode changes occur */ -export function getHeartbeatMessages(heartbeatMessages) { - const modeMessages = [] +export function getHeartbeatMessages( + heartbeatMessages: MessageObject[], +): MessageObject[] { + const modeMessages: MessageObject[] = [] for (let i = 0; i < heartbeatMessages.length; i++) { const msg = heartbeatMessages[i] if (modeMessages.length === 0 || i === heartbeatMessages.length - 1) { @@ -160,15 +243,18 @@ export function getHeartbeatMessages(heartbeatMessages) { /** * Calculates GPS offset for UTC conversion */ -export function calcGPSOffset(loadedLogMessages) { - if (!loadedLogMessages["GPS"] || !loadedLogMessages["GPS"][0]) { +export function calcGPSOffset( + loadedLogMessages: LoadedLogMessages, +): number | null { + const gpsData = loadedLogMessages["GPS"] + if (!Array.isArray(gpsData) || !gpsData[0]) { return null } - const messageObj = loadedLogMessages["GPS"][0] + const messageObj = gpsData[0] if (messageObj.GWk !== undefined && messageObj.GMS !== undefined) { - const utcTime = gpsToUTC(messageObj.GWk, messageObj.GMS) - const offset = utcTime.getTime() - messageObj.TimeUS / 1000 + const utcTime = gpsToUTC(messageObj.GWk as number, messageObj.GMS as number) + const offset = utcTime.getTime() - (messageObj.TimeUS as number) / 1000 return offset } @@ -178,17 +264,23 @@ export function calcGPSOffset(loadedLogMessages) { /** * Converts TimeUS to UTC for all messages */ -export function convertTimeUStoUTC(logMessages, gpsOffset) { +export function convertTimeUStoUTC( + logMessages: LoadedLogMessages, + gpsOffset: number, +): LoadedLogMessages { // This still takes some time for some reason - const convertedMessages = { ...logMessages } + const convertedMessages: LoadedLogMessages = { ...logMessages } Object.keys(convertedMessages) .filter((key) => key !== "format" && key !== "units") .forEach((key) => { - convertedMessages[key] = convertedMessages[key].map((message) => ({ - ...message, - TimeUS: message.TimeUS / 1000 + gpsOffset, - })) + const messages = convertedMessages[key] + if (Array.isArray(messages)) { + convertedMessages[key] = messages.map((message: MessageObject) => ({ + ...message, + TimeUS: (message.TimeUS as number) / 1000 + gpsOffset, + })) + } }) return convertedMessages @@ -197,10 +289,12 @@ export function convertTimeUStoUTC(logMessages, gpsOffset) { /** * Sorts object keys alphabetically */ -export function sortObjectByKeys(obj) { +export function sortObjectByKeys(obj: { [key: string]: T }): { + [key: string]: T +} { const result = Object.keys(obj) .sort() - .reduce((acc, key) => { + .reduce((acc: { [key: string]: T }, key: string) => { acc[key] = obj[key] return acc }, {}) @@ -211,23 +305,38 @@ export function sortObjectByKeys(obj) { /** * Expands ESC messages into separate arrays based on Instance */ -export function expandESCMessages(logMessages, filterState) { +export function expandESCMessages( + logMessages: LoadedLogMessages, + filterState: FilterState, +): ExpandResult { const escData = logMessages["ESC"] - if (!escData?.length) { + if (!Array.isArray(escData) || !escData.length) { + const formatData = logMessages["format"] return { updatedMessages: logMessages, updatedFilters: filterState, - updatedFormats: logMessages["format"], + updatedFormats: + typeof formatData === "object" && + formatData !== null && + !Array.isArray(formatData) + ? (formatData as { [key: string]: FormatMessage }) + : {}, } } - const updatedMessages = { ...logMessages } - const updatedFilters = { ...filterState } - const updatedFormats = { ...logMessages["format"] } - - escData.forEach((escMessage) => { - const escName = `ESC${escMessage["Instance"] + 1}` - const newEscData = { + const updatedMessages: LoadedLogMessages = { ...logMessages } + const updatedFilters: FilterState = { ...filterState } + const formatData = logMessages["format"] + const updatedFormats: { [key: string]: FormatMessage } = + typeof formatData === "object" && + formatData !== null && + !Array.isArray(formatData) + ? { ...(formatData as { [key: string]: FormatMessage }) } + : {} + + escData.forEach((escMessage: MessageObject) => { + const escName = `ESC${(escMessage["Instance"] as number) + 1}` + const newEscData: MessageObject = { ...escMessage, name: escName, } @@ -241,7 +350,10 @@ export function expandESCMessages(logMessages, filterState) { } } - updatedMessages[escName].push(newEscData) + const targetArray = updatedMessages[escName] + if (Array.isArray(targetArray)) { + targetArray.push(newEscData) + } }) delete updatedMessages["ESC"] @@ -257,8 +369,13 @@ export function expandESCMessages(logMessages, filterState) { /** * Expands BAT messages into separate arrays based on Instance */ -export function expandBATMessages(logMessages, filterState, formatsWithESC) { - if (!logMessages["BAT"]) { +export function expandBATMessages( + logMessages: LoadedLogMessages, + filterState: FilterState, + formatsWithESC: { [key: string]: FormatMessage }, +): ExpandResult { + const batData = logMessages["BAT"] + if (!Array.isArray(batData)) { return { updatedMessages: logMessages, updatedFilters: filterState, @@ -266,13 +383,13 @@ export function expandBATMessages(logMessages, filterState, formatsWithESC) { } } - const updatedMessages = { ...logMessages } - const updatedFilters = { ...filterState } - const updatedFormats = { ...formatsWithESC } + const updatedMessages: LoadedLogMessages = { ...logMessages } + const updatedFilters: FilterState = { ...filterState } + const updatedFormats: { [key: string]: FormatMessage } = { ...formatsWithESC } - logMessages["BAT"].forEach((battData) => { + batData.forEach((battData: MessageObject) => { const instanceValue = battData["Instance"] ?? battData["Inst"] - const battName = `BAT${(instanceValue ?? 0) + 1}` + const battName = `BAT${((instanceValue as number) ?? 0) + 1}` if (!updatedMessages[battName]) { updatedMessages[battName] = [] @@ -283,10 +400,13 @@ export function expandBATMessages(logMessages, filterState, formatsWithESC) { } } - updatedMessages[battName].push({ - ...battData, - name: battName, - }) + const targetArray = updatedMessages[battName] + if (Array.isArray(targetArray)) { + targetArray.push({ + ...battData, + name: battName, + }) + } }) delete updatedMessages["BAT"] @@ -300,17 +420,22 @@ export function expandBATMessages(logMessages, filterState, formatsWithESC) { } // Memoization cache for getUnit function -const unitCache = new Map() +const unitCache = new Map() -export function clearUnitCache() { +export function clearUnitCache(): void { unitCache.clear() } -export function getUnit(messageName, fieldName, formatMessages, units) { +export function getUnit( + messageName: string, + fieldName: string, + formatMessages: { [key: string]: FormatMessage }, + units: { [key: string]: string }, +): string { // Create cache key const cacheKey = `${messageName}/${fieldName}` if (unitCache.has(cacheKey)) { - return unitCache.get(cacheKey) + return unitCache.get(cacheKey) as string } // TODO: Find out why this is here diff --git a/gcs/electron/utils/recentLogManager.ts b/gcs/electron/utils/recentLogManager.ts new file mode 100644 index 000000000..519d3adc5 --- /dev/null +++ b/gcs/electron/utils/recentLogManager.ts @@ -0,0 +1,71 @@ +import { app } from "electron" +import * as fs from "fs" +import * as path from "path" + +export default function createRecentLogsManager(maxRecentLogs: number = 10) { + // JSON file to hold paths to recently opened logs + const recentLogsPath: string = path.join( + app.getPath("userData"), + "recentLogs.json", + ) + + function loadRecentLogs(): string[] { + try { + if (fs.existsSync(recentLogsPath)) { + const data = fs.readFileSync(recentLogsPath, "utf8") + const parsed = JSON.parse(data) + // Ensure we return an array of strings + return Array.isArray(parsed) ? parsed : [] + } + return [] + } catch (error) { + console.error("Error loading recent files:", error) + return [] + } + } + + let recentLogs: string[] = loadRecentLogs() + + function saveRecentLogs(): void { + try { + fs.writeFileSync(recentLogsPath, JSON.stringify(recentLogs), "utf8") + } catch (error) { + console.error("Error saving recent files:", error) + } + } + + return { + addRecentLog(filePath: string): void { + // Remove the file if it already exists in the list + recentLogs = recentLogs.filter((file: string) => file !== filePath) + + // Add the file to the beginning of the list + recentLogs.unshift(filePath) + + // Trim the list if it exceeds the maximum allowed + if (recentLogs.length > maxRecentLogs) { + recentLogs = recentLogs.slice(0, maxRecentLogs) + } + saveRecentLogs() + }, + + getRecentLogs(): string[] { + // Filter out files that no longer exist + const existingFiles: string[] = recentLogs.filter((file: string) => + fs.existsSync(file), + ) + + // Update the list if files were removed + if (existingFiles.length !== recentLogs.length) { + recentLogs = existingFiles + saveRecentLogs() + } + return existingFiles + }, + + clearRecentLogs(): void { + recentLogs = [] + saveRecentLogs() + }, + } +} diff --git a/gcs/settings/recentLogManager.js b/gcs/settings/recentLogManager.js deleted file mode 100644 index bfbdedd18..000000000 --- a/gcs/settings/recentLogManager.js +++ /dev/null @@ -1,64 +0,0 @@ -import fs from 'fs' -import path from 'path' -import { app } from 'electron' - -export default function createRecentLogsManager(maxRecentLogs = 10) { - // JSON file to hold paths to recently opened logs - const recentLogsPath = path.join(app.getPath('userData'), 'recentLogs.json') - - function loadRecentLogs() { - try { - if (fs.existsSync(recentLogsPath)) { - const data = fs.readFileSync(recentLogsPath, 'utf8') - return JSON.parse(data) - } - return [] - } catch (error) { - console.error('Error loading recent files:', error) - return [] - } - } - - let recentLogs = loadRecentLogs() - - function saveRecentLogs() { - try { - fs.writeFileSync(recentLogsPath, JSON.stringify(recentLogs), 'utf8') - } catch (error) { - console.error('Error saving recent files:', error) - } - } - - return { - addRecentLog(filePath) { - // Remove the file if it already exists in the list - recentLogs = recentLogs.filter((file) => file !== filePath) - - // Add the file to the beginning of the list - recentLogs.unshift(filePath) - - // Trim the list if it exceeds the maximum allowed - if (recentLogs.length > maxRecentLogs) { - recentLogs = recentLogs.slice(0, maxRecentLogs) - } - saveRecentLogs() - }, - - getRecentLogs() { - // Filter out files that no longer exist - const existingFiles = recentLogs.filter((file) => fs.existsSync(file)) - - // Update the list if files were removed - if (existingFiles.length !== recentLogs.length) { - recentLogs = existingFiles - saveRecentLogs() - } - return existingFiles - }, - - clearRecentLogs() { - recentLogs = [] - saveRecentLogs() - }, - } -} diff --git a/gcs/tsconfig.json b/gcs/tsconfig.json index f295895d1..d8f5fcae0 100644 --- a/gcs/tsconfig.json +++ b/gcs/tsconfig.json @@ -20,6 +20,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "electron", "settings/recentLogManager.js", "../graphs"], + "include": ["src", "electron"], "references": [{ "path": "./tsconfig.node.json" }] }