From 0a3d840dee72771598a2d5fdd19a5ab5fd2699c6 Mon Sep 17 00:00:00 2001 From: Timon Home Date: Thu, 26 Mar 2026 12:55:09 +0100 Subject: [PATCH] experimental: Saving correclty --- package.json | 2 +- .../components/profiles/ConfigTab.tsx | 14 +++++--- .../components/settings/SettingsTab.tsx | 36 +++++++++++-------- src/renderer/index.html | 2 +- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index b57c18c..5f03d9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "java-runner-client", - "version": "2.1.5", + "version": "2.1.6", "description": "Run and manage Java processes with profiles, console I/O, and system tray support", "main": "dist/main/main.js", "scripts": { diff --git a/src/renderer/components/profiles/ConfigTab.tsx b/src/renderer/components/profiles/ConfigTab.tsx index af0a65b..b6a8b19 100644 --- a/src/renderer/components/profiles/ConfigTab.tsx +++ b/src/renderer/components/profiles/ConfigTab.tsx @@ -24,6 +24,8 @@ export function ConfigTab() { const { activeProfile, saveProfile, isRunning, startProcess, stopProcess } = useApp(); const [draft, setDraft] = useState(null); + // savedSnapshot holds what was last persisted — used for dirty detection + const [savedSnapshot, setSavedSnapshot] = useState(null); const [saved, setSaved] = useState(false); const [section, setSection] = useState
('general'); const [pendingArg, setPendingArg] = useState(false); @@ -32,23 +34,25 @@ export function ConfigTab() { useEffect(() => { if (activeProfile) { setDraft({ ...activeProfile }); + setSavedSnapshot({ ...activeProfile }); setSaved(false); setPendingArg(false); } }, [activeProfile?.id]); const isDirty = useMemo(() => { - if (!draft || !activeProfile) return false; - return JSON.stringify(draft) !== JSON.stringify(activeProfile); - }, [draft, activeProfile]); + if (!draft || !savedSnapshot) return false; + return JSON.stringify(draft) !== JSON.stringify(savedSnapshot); + }, [draft, savedSnapshot]); const handleSave = useCallback(async () => { if (!draft) return; await saveProfile(draft); - activeProfile && Object.assign(activeProfile, draft); + // Update the saved snapshot so dirty detection resets correctly + setSavedSnapshot({ ...draft }); setSaved(true); setTimeout(() => setSaved(false), 1800); - }, [draft, saveProfile, activeProfile]); + }, [draft, saveProfile]); const requestSectionChange = useCallback( (next: Section) => { diff --git a/src/renderer/components/settings/SettingsTab.tsx b/src/renderer/components/settings/SettingsTab.tsx index 161ee64..26a6244 100644 --- a/src/renderer/components/settings/SettingsTab.tsx +++ b/src/renderer/components/settings/SettingsTab.tsx @@ -1,43 +1,51 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; import { useApp } from '../../AppProvider'; import { Button } from '../common/Button'; import { Toggle } from '../common/Toggle'; import { VersionChecker } from './VersionChecker'; import { REST_API_CONFIG } from '../../../main/shared/config/API.config'; import { version } from '../../../../package.json'; -import { AppSettings, JRCEnvironment } from '../../../main/shared/types/App.types'; +import { AppSettings } from '../../../main/shared/types/App.types'; export function SettingsTab() { const { state, saveSettings } = useApp(); const [draft, setDraft] = useState(null); const [saved, setSaved] = useState(false); + // Track whether the draft was initialized so we don't overwrite user edits + const initializedRef = useRef(false); const set = (patch: Partial) => { setSaved(false); setDraft((prev) => (prev ? { ...prev, ...patch } : prev)); }; + // Initialize draft from store once on mount, then only sync fields the user + // hasn't touched (devModeEnabled) when the env changes externally. useEffect(() => { if (!state.settings) return; + if (!initializedRef.current) { + setDraft(state.settings); + initializedRef.current = true; + return; + } + // Only sync devModeEnabled from outside — everything else is user-controlled setDraft((prev) => { if (!prev) return state.settings; - return { ...state.settings, ...prev, devModeEnabled: prev.devModeEnabled }; + if (prev.devModeEnabled === state.settings!.devModeEnabled) return prev; + return { ...prev, devModeEnabled: state.settings!.devModeEnabled }; }); }, [state.settings]); + // Listen for dev-mode toggled externally (via the DevModeGate shortcut) useEffect(() => { - const listener = async (e: JRCEnvironment) => { - setSaved(false); + const unsub = window.env.onChange((env) => { setDraft((prev) => { - if (!prev || prev.devModeEnabled === e.devMode) return prev; - return { ...prev, devModeEnabled: e.devMode }; + if (!prev || prev.devModeEnabled === env.devMode) return prev; + return { ...prev, devModeEnabled: env.devMode }; }); - if (state.settings && state.settings.devModeEnabled !== e.devMode) { - await saveSettings({ ...state.settings, devModeEnabled: e.devMode }); - } - }; - window.env.onChange(listener); - }, [state.settings, saveSettings]); + }); + return unsub; + }, []); const isDirty = useMemo(() => { if (!draft || !state.settings) return false; @@ -60,7 +68,7 @@ export function SettingsTab() { {saved ? 'Settings saved' : 'Unsaved changes'} - diff --git a/src/renderer/index.html b/src/renderer/index.html index 7e74d02..c6ded00 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -9,7 +9,7 @@ default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; - img-src 'self' data:; + img-src 'self' data: https://avatars.githubusercontent.com; font-src 'self'; connect-src 'self' http://127.0.0.1:* http://localhost:*; "