From 7d881fc9e6e235976354fa6ef8eca53d114d3007 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 23 Apr 2025 13:17:10 -0400 Subject: [PATCH] feature/IO-3205-Paint-Scale-Integrations: init --- src/main/ipc/ipcMainConfig.ts | 90 ++++++--- src/main/ipc/ipcMainHandler.settings.ts | 175 ++++++++++++++++-- .../Settings.PaintScaleInputPaths.tsx | 157 ++++++++++++++++ .../Settings.PaintScaleOutputPaths.tsx | 157 ++++++++++++++++ .../src/components/Settings/Settings.tsx | 55 +++--- src/util/ipcTypes.json | 10 +- src/util/translations/en-US/renderer.json | 9 +- 7 files changed, 580 insertions(+), 73 deletions(-) create mode 100644 src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx create mode 100644 src/renderer/src/components/Settings/Settings.PaintScaleOutputPaths.tsx diff --git a/src/main/ipc/ipcMainConfig.ts b/src/main/ipc/ipcMainConfig.ts index 7a6f3ad..e9273bc 100644 --- a/src/main/ipc/ipcMainConfig.ts +++ b/src/main/ipc/ipcMainConfig.ts @@ -1,3 +1,4 @@ +// main/ipcMainConfig.ts import { app, ipcMain } from "electron"; import log from "electron-log/main"; import { autoUpdater } from "electron-updater"; @@ -16,6 +17,12 @@ import { SettingsWatcherPollingSet, SettingEmsOutFilePathSet, SettingEmsOutFilePathGet, + SettingsPaintScaleInputConfigsGet, + SettingsPaintScaleInputConfigsSet, + SettingsPaintScaleInputPathSet, + SettingsPaintScaleOutputConfigsGet, + SettingsPaintScaleOutputConfigsSet, + SettingsPaintScaleOutputPathSet, } from "./ipcMainHandler.settings"; import { ipcMainHandleAuthStateChanged, @@ -24,23 +31,19 @@ import { // Log all IPC messages and their payloads const logIpcMessages = (): void => { - // Get all message types from ipcTypes.toMain Object.keys(ipcTypes.toMain).forEach((key) => { const messageType = ipcTypes.toMain[key]; - - // Wrap the original handler with our logging - const originalHandler = ipcMain.listeners(messageType)[0]; + const originalHandler = ipcMain.listeners(messageType)?.[0]; if (originalHandler) { ipcMain.removeAllListeners(messageType); } ipcMain.on(messageType, (event, payload) => { log.info( - `%c[IPC Main]%c${messageType}`, - "color: red", - "color: green", - payload, + `%c[IPC Main]%c${messageType}`, + "color: red", + "color: green", + payload, ); - // Call original handler if it existed if (originalHandler) { originalHandler(event, payload); } @@ -49,20 +52,19 @@ const logIpcMessages = (): void => { }; ipcMain.on(ipcTypes.toMain.test, () => - console.log("** Verify that ipcMain is loaded and working."), + console.log("** Verify that ipcMain is loaded and working."), ); -//Auth handler +// Auth handler ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged); ipcMain.on(ipcTypes.toMain.user.resetPassword, ipMainHandleResetPassword); -//Add debug handlers if in development +// Add debug handlers if in development if (import.meta.env.DEV) { log.debug("[IPC Debug Functions] Adding Debug Handlers"); ipcMain.on(ipcTypes.toMain.debug.decodeEstimate, async (): Promise => { const relativeEmsFilepath = `_reference/ems/MPI_1/3698420.ENV`; - // Get the app's root directory and create an absolute path const rootDir = app.getAppPath(); const absoluteFilepath = path.join(rootDir, relativeEmsFilepath); @@ -76,44 +78,72 @@ if (import.meta.env.DEV) { }); } -//Settings Handlers +// Settings Handlers ipcMain.handle( - ipcTypes.toMain.settings.filepaths.get, - SettingsWatchedFilePathsGet, + ipcTypes.toMain.settings.filepaths.get, + SettingsWatchedFilePathsGet, ); ipcMain.handle( - ipcTypes.toMain.settings.filepaths.add, - SettingsWatchedFilePathsAdd, + ipcTypes.toMain.settings.filepaths.add, + SettingsWatchedFilePathsAdd, ); ipcMain.handle( - ipcTypes.toMain.settings.filepaths.remove, - SettingsWatchedFilePathsRemove, + ipcTypes.toMain.settings.filepaths.remove, + SettingsWatchedFilePathsRemove, ); ipcMain.handle( - ipcTypes.toMain.settings.watcher.getpolling, - SettingsWatcherPollingGet, + ipcTypes.toMain.settings.watcher.getpolling, + SettingsWatcherPollingGet, ); ipcMain.handle( - ipcTypes.toMain.settings.watcher.setpolling, - SettingsWatcherPollingSet, + ipcTypes.toMain.settings.watcher.setpolling, + SettingsWatcherPollingSet, ); ipcMain.handle(ipcTypes.toMain.settings.getPpcFilePath, SettingsPpcFilePathGet); ipcMain.handle(ipcTypes.toMain.settings.setPpcFilePath, SettingsPpcFilePathSet); ipcMain.handle( - ipcTypes.toMain.settings.getEmsOutFilePath, - SettingEmsOutFilePathGet, + ipcTypes.toMain.settings.getEmsOutFilePath, + SettingEmsOutFilePathGet, ); ipcMain.handle( - ipcTypes.toMain.settings.setEmsOutFilePath, - SettingEmsOutFilePathSet, + ipcTypes.toMain.settings.setEmsOutFilePath, + SettingEmsOutFilePathSet, +); + +// Paint Scale Input Settings Handlers +ipcMain.handle( + ipcTypes.toMain.settings.paintScale.getInputConfigs, + SettingsPaintScaleInputConfigsGet, +); +ipcMain.handle( + ipcTypes.toMain.settings.paintScale.setInputConfigs, + SettingsPaintScaleInputConfigsSet, +); +ipcMain.handle( + ipcTypes.toMain.settings.paintScale.setInputPath, + SettingsPaintScaleInputPathSet, +); + +// Paint Scale Output Settings Handlers +ipcMain.handle( + ipcTypes.toMain.settings.paintScale.getOutputConfigs, + SettingsPaintScaleOutputConfigsGet, +); +ipcMain.handle( + ipcTypes.toMain.settings.paintScale.setOutputConfigs, + SettingsPaintScaleOutputConfigsSet, +); +ipcMain.handle( + ipcTypes.toMain.settings.paintScale.setOutputPath, + SettingsPaintScaleOutputPathSet, ); ipcMain.handle(ipcTypes.toMain.user.getActiveShop, () => { return store.get("app.bodyshop.shopname"); }); -//Watcher Handlers +// Watcher Handlers ipcMain.on(ipcTypes.toMain.watcher.start, () => { StartWatcher(); }); @@ -127,4 +157,4 @@ ipcMain.on(ipcTypes.toMain.updates.download, () => { autoUpdater.downloadUpdate(); }); -logIpcMessages(); +logIpcMessages(); \ No newline at end of file diff --git a/src/main/ipc/ipcMainHandler.settings.ts b/src/main/ipc/ipcMainHandler.settings.ts index a066d79..dbb3bb8 100644 --- a/src/main/ipc/ipcMainHandler.settings.ts +++ b/src/main/ipc/ipcMainHandler.settings.ts @@ -1,9 +1,31 @@ -import {dialog, IpcMainInvokeEvent} from "electron"; +// main/ipcMainHandler.settings.ts +import { dialog, IpcMainInvokeEvent } from "electron"; import log from "electron-log/main"; import _ from "lodash"; import Store from "../store/store"; -import {getMainWindow} from "../util/toRenderer"; -import {addWatcherPath, removeWatcherPath, StartWatcher, StopWatcher,} from "../watcher/watcher"; +import { getMainWindow } from "../util/toRenderer"; +import { + addWatcherPath, + removeWatcherPath, + StartWatcher, + StopWatcher, +} from "../watcher/watcher"; + +interface PaintScaleConfig { + id: string; + path: string | null; + type: string; +} + +// Initialize paint scale input configs in store if not set +if (!Store.get("settings.paintScaleInputConfigs")) { + Store.set("settings.paintScaleInputConfigs", []); +} + +// Initialize paint scale output configs in store if not set +if (!Store.get("settings.paintScaleOutputConfigs")) { + Store.set("settings.paintScaleOutputConfigs", []); +} const SettingsWatchedFilePathsAdd = async (): Promise => { const mainWindow = getMainWindow(); @@ -17,21 +39,22 @@ const SettingsWatchedFilePathsAdd = async (): Promise => { if (!result.canceled) { Store.set( - "settings.filepaths", - _.union(result.filePaths, Store.get("settings.filepaths")), + "settings.filepaths", + _.union(result.filePaths, Store.get("settings.filepaths")), ); addWatcherPath(result.filePaths); } return Store.get("settings.filepaths"); }; + const SettingsWatchedFilePathsRemove = async ( - _event: IpcMainInvokeEvent, - path: string, + _event: IpcMainInvokeEvent, + path: string, ): Promise => { Store.set( - "settings.filepaths", - _.without(Store.get("settings.filepaths"), path), + "settings.filepaths", + _.without(Store.get("settings.filepaths"), path), ); removeWatcherPath(path); return Store.get("settings.filepaths"); @@ -46,15 +69,16 @@ const SettingsWatcherPollingGet = async (): Promise<{ interval: number; }> => { const pollingEnabled: { enabled: boolean; interval: number } = - Store.get("settings.polling"); + Store.get("settings.polling"); return { enabled: pollingEnabled.enabled, interval: pollingEnabled.interval }; }; + const SettingsWatcherPollingSet = async ( - _event: IpcMainInvokeEvent, - pollingSettings: { - enabled: boolean; - interval: number; - }, + _event: IpcMainInvokeEvent, + pollingSettings: { + enabled: boolean; + interval: number; + }, ): Promise<{ enabled: boolean; interval: number; @@ -63,15 +87,16 @@ const SettingsWatcherPollingSet = async ( const { enabled, interval } = pollingSettings; Store.set("settings.polling", { enabled, interval }); - //Restart the watcher with these new settings. await StopWatcher(); await StartWatcher(); return { enabled, interval }; }; + const SettingsPpcFilePathGet = async (): Promise => { return Store.get("settings.ppcFilePath"); }; + const SettingsPpcFilePathSet = async (): Promise => { const mainWindow = getMainWindow(); if (!mainWindow) { @@ -83,14 +108,16 @@ const SettingsPpcFilePathSet = async (): Promise => { }); if (!result.canceled) { - Store.set("settings.ppcFilePath", result.filePaths[0]); //There should only ever be on directory that was selected. + Store.set("settings.ppcFilePath", result.filePaths[0]); } return (Store.get("settings.ppcFilePath") as string) || ""; }; + const SettingEmsOutFilePathGet = async (): Promise => { return Store.get("settings.emsOutFilePath"); }; + const SettingEmsOutFilePathSet = async (): Promise => { const mainWindow = getMainWindow(); if (!mainWindow) { @@ -102,12 +129,116 @@ const SettingEmsOutFilePathSet = async (): Promise => { }); if (!result.canceled) { - Store.set("settings.emsOutFilePath", result.filePaths[0]); //There should only ever be on directory that was selected. + Store.set("settings.emsOutFilePath", result.filePaths[0]); } return (Store.get("settings.emsOutFilePath") as string) || ""; }; +const SettingsPaintScaleInputConfigsGet = async ( + _event: IpcMainInvokeEvent, +): Promise => { + try { + const configs = Store.get("settings.paintScaleInputConfigs") as PaintScaleConfig[]; + log.debug("Retrieved paint scale input configs:", configs); + return configs || []; + } catch (error) { + log.error("Error getting paint scale input configs:", error); + throw error; + } +}; + +const SettingsPaintScaleInputConfigsSet = async ( + _event: IpcMainInvokeEvent, + configs: PaintScaleConfig[], +): Promise => { + try { + Store.set("settings.paintScaleInputConfigs", configs); + log.debug("Saved paint scale input configs:", configs); + return true; + } catch (error) { + log.error("Error setting paint scale input configs:", error); + throw error; + } +}; + +const SettingsPaintScaleInputPathSet = async ( + _event: IpcMainInvokeEvent, +): Promise => { + try { + const mainWindow = getMainWindow(); + if (!mainWindow) { + log.error("No main window found when trying to open dialog"); + return null; + } + const result = await dialog.showOpenDialog(mainWindow, { + properties: ["openDirectory"], + }); + if (result.canceled) { + log.debug("Paint scale input path selection canceled"); + return null; + } + const path = result.filePaths[0]; + log.debug("Selected paint scale input path:", path); + return path; + } catch (error) { + log.error("Error setting paint scale input path:", error); + throw error; + } +}; + +const SettingsPaintScaleOutputConfigsGet = async ( + _event: IpcMainInvokeEvent, +): Promise => { + try { + const configs = Store.get("settings.paintScaleOutputConfigs") as PaintScaleConfig[]; + log.debug("Retrieved paint scale output configs:", configs); + return configs || []; + } catch (error) { + log.error("Error getting paint scale output configs:", error); + throw error; + } +}; + +const SettingsPaintScaleOutputConfigsSet = async ( + _event: IpcMainInvokeEvent, + configs: PaintScaleConfig[], +): Promise => { + try { + Store.set("settings.paintScaleOutputConfigs", configs); + log.debug("Saved paint scale output configs:", configs); + return true; + } catch (error) { + log.error("Error setting paint scale output configs:", error); + throw error; + } +}; + +const SettingsPaintScaleOutputPathSet = async ( + _event: IpcMainInvokeEvent, +): Promise => { + try { + const mainWindow = getMainWindow(); + if (!mainWindow) { + log.error("No main window found when trying to open dialog"); + return null; + } + const result = await dialog.showOpenDialog(mainWindow, { + properties: ["openDirectory"], + }); + if (result.canceled) { + log.debug("Paint scale output path selection canceled"); + return null; + } + const path = result.filePaths[0]; + log.debug("Selected paint scale output path:", path); + return path; + } catch (error) { + log.error("Error setting paint scale output path:", error); + throw error; + } +}; + export { SettingsPpcFilePathGet, SettingsPpcFilePathSet, @@ -118,4 +249,10 @@ export { SettingsWatcherPollingSet, SettingEmsOutFilePathGet, SettingEmsOutFilePathSet, -}; + SettingsPaintScaleInputConfigsGet, + SettingsPaintScaleInputConfigsSet, + SettingsPaintScaleInputPathSet, + SettingsPaintScaleOutputConfigsGet, + SettingsPaintScaleOutputConfigsSet, + SettingsPaintScaleOutputPathSet, +}; \ No newline at end of file diff --git a/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx b/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx new file mode 100644 index 0000000..03bac0d --- /dev/null +++ b/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx @@ -0,0 +1,157 @@ +// renderer/Settings.PaintScaleInputPaths.tsx +import { FolderOpenFilled } from "@ant-design/icons"; +import { Button, Card, Input, Select, Space, Table } from "antd"; +import { FC, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ipcTypes from "../../../../util/ipcTypes.json"; + +interface PaintScaleConfig { + id: string; + path: string | null; + type: PaintScaleType; +} + +enum PaintScaleType { + PPG = "PPG", + SHERWIN = "SHERWIN", + AKZO = "AKZO", +} + +const paintScaleTypeOptions = Object.values(PaintScaleType).map((type) => ({ + value: type, + label: type, +})); + +const SettingsPaintScaleInputPaths: FC = () => { + const { t } = useTranslation(); + const [paintScaleConfigs, setPaintScaleConfigs] = useState([]); + + // Load paint scale input configs from store on mount + useEffect(() => { + window.electron.ipcRenderer + .invoke(ipcTypes.toMain.settings.paintScale.getInputConfigs) + .then((configs: PaintScaleConfig[]) => { + setPaintScaleConfigs(configs || []); + }) + .catch((error) => { + console.error("Failed to load paint scale input configs:", error); + }); + }, []); + + // Handle adding a new paint scale config + const handleAddConfig = () => { + const newConfig: PaintScaleConfig = { + id: Date.now().toString(), + path: null, + type: PaintScaleType.PPG, + }; + const updatedConfigs = [...paintScaleConfigs, newConfig]; + setPaintScaleConfigs(updatedConfigs); + saveConfigs(updatedConfigs); + }; + + // Handle removing a config + const handleRemoveConfig = (id: string) => { + const updatedConfigs = paintScaleConfigs.filter((config) => config.id !== id); + setPaintScaleConfigs(updatedConfigs); + saveConfigs(updatedConfigs); + }; + + // Handle path selection + const handlePathChange = (id: string) => { + window.electron.ipcRenderer + .invoke(ipcTypes.toMain.settings.paintScale.setInputPath, id) + .then((path: string | null) => { + if (path) { + const updatedConfigs = paintScaleConfigs.map((config) => + config.id === id ? { ...config, path } : config, + ); + setPaintScaleConfigs(updatedConfigs); + saveConfigs(updatedConfigs); + } + }) + .catch((error) => { + console.error("Failed to set paint scale input path:", error); + }); + }; + + // Handle type change + const handleTypeChange = (id: string, type: PaintScaleType) => { + const updatedConfigs = paintScaleConfigs.map((config) => + config.id === id ? { ...config, type } : config, + ); + setPaintScaleConfigs(updatedConfigs); + saveConfigs(updatedConfigs); + }; + + // Save configs to store + const saveConfigs = (configs: PaintScaleConfig[]) => { + window.electron.ipcRenderer + .invoke(ipcTypes.toMain.settings.paintScale.setInputConfigs, configs) + .catch((error) => { + console.error("Failed to save paint scale input configs:", error); + }); + }; + + // Table columns for paint scale configs + const columns = [ + { + title: t("settings.labels.paintScalePath"), + dataIndex: "path", + key: "path", + render: (path: string | null, record: PaintScaleConfig) => ( + + +