Skip to content

Commit c37c40f

Browse files
committed
release: v0.2.1
- Persist settings in Electron (IPC + userData settings.json) with browser localStorage fallback - Fix Settings modal save wiring so changes actually apply - Respect global shortcuts master toggle and correctly map KeyA..KeyZ accelerators - Bump version to 0.2.1
1 parent 7ef7df8 commit c37c40f

File tree

7 files changed

+88
-15
lines changed

7 files changed

+88
-15
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "islamic-text-copier",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "A cross-platform Islamic Text Copier built with React and Electron",
55
"main": "src/main.js",
66
"homepage": "./",

src/components/App.js

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import './App.css';
1010
// Falls back gracefully for browser mode
1111
const electronAPI = window.electronAPI || null;
1212

13-
// Load settings from localStorage or use defaults
14-
const loadSettings = () => {
13+
// Load settings from localStorage or use defaults (browser fallback)
14+
const loadSettingsFromLocalStorage = () => {
1515
try {
1616
const saved = localStorage.getItem('itc-settings');
1717
if (saved) {
@@ -23,8 +23,8 @@ const loadSettings = () => {
2323
return defaultSettings;
2424
};
2525

26-
// Save settings to localStorage
27-
const saveSettings = (settings) => {
26+
// Save settings to localStorage (browser fallback)
27+
const saveSettingsToLocalStorage = (settings) => {
2828
try {
2929
localStorage.setItem('itc-settings', JSON.stringify(settings));
3030
} catch (err) {
@@ -35,10 +35,10 @@ const saveSettings = (settings) => {
3535
function App() {
3636
const [statusMessage, setStatusMessage] = useState('');
3737
const [meaningText, setMeaningText] = useState('');
38-
const [appVersion, setAppVersion] = useState('0.2.0');
38+
const [appVersion, setAppVersion] = useState('0.2.1');
3939
const [showDocs, setShowDocs] = useState(false);
4040
const [showSettings, setShowSettings] = useState(false);
41-
const [settings, setSettings] = useState(loadSettings);
41+
const [settings, setSettings] = useState(loadSettingsFromLocalStorage);
4242
const [globalNotification, setGlobalNotification] = useState('');
4343
const [updateStatus, setUpdateStatus] = useState('');
4444

@@ -59,9 +59,34 @@ function App() {
5959
fetchVersion();
6060
}, []);
6161

62+
// Load settings from Electron main process when available
63+
useEffect(() => {
64+
const loadElectronSettings = async () => {
65+
if (!electronAPI || !electronAPI.isElectron || !electronAPI.getSettings) return;
66+
67+
try {
68+
const result = await electronAPI.getSettings();
69+
if (result && result.success && result.settings) {
70+
setSettings(result.settings);
71+
}
72+
} catch (err) {
73+
console.error('Failed to load Electron settings:', err);
74+
}
75+
};
76+
77+
loadElectronSettings();
78+
}, []);
79+
6280
// Save settings whenever they change
6381
useEffect(() => {
64-
saveSettings(settings);
82+
if (electronAPI && electronAPI.isElectron && electronAPI.setSettings) {
83+
electronAPI.setSettings(settings).catch((err) => {
84+
console.error('Failed to save Electron settings:', err);
85+
});
86+
return;
87+
}
88+
89+
saveSettingsToLocalStorage(settings);
6590
}, [settings]);
6691

6792
// Apply theme from settings
@@ -77,6 +102,10 @@ function App() {
77102
// Unregister all first to clean up
78103
await electronAPI.unregisterAllGlobalShortcuts();
79104

105+
if (!settings.globalShortcutsEnabled) {
106+
return;
107+
}
108+
80109
// Register each shortcut that has global enabled
81110
for (const shortcut of settings.shortcuts) {
82111
if (shortcut.global) {
@@ -99,7 +128,7 @@ function App() {
99128
return () => {
100129
electronAPI.unregisterAllGlobalShortcuts();
101130
};
102-
}, [settings.shortcuts]);
131+
}, [settings.shortcuts, settings.globalShortcutsEnabled]);
103132

104133
// Listen for global shortcut triggered events
105134
useEffect(() => {
@@ -377,7 +406,7 @@ function App() {
377406
{showSettings && (
378407
<Settings
379408
settings={settings}
380-
onSettingsChange={handleSettingsChange}
409+
onSave={handleSettingsChange}
381410
onClose={closeSettings}
382411
/>
383412
)}

src/components/__tests__/App.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ describe('App Component', () => {
214214

215215
render(<App />);
216216

217-
// Should fallback to default version (0.2.0)
217+
// Should fallback to default version (0.2.1)
218218
await waitFor(() => {
219-
expect(screen.getByText(/Version 0\.2\.0/i)).toBeInTheDocument();
219+
expect(screen.getByText(/Version 0\.2\.1/i)).toBeInTheDocument();
220220
});
221221
});
222222

src/main.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { app, BrowserWindow, shell, ipcMain, globalShortcut, clipboard, dialog } = require('electron');
22
const path = require('path');
3+
const fs = require('fs');
34
const { autoUpdater } = require('electron-updater');
45

56
// Keep a global reference of the window object
@@ -172,6 +173,38 @@ ipcMain.handle('get-version', async () => {
172173
return app.getVersion(); // Returns version from package.json
173174
});
174175

176+
/**
177+
* Settings persistence (stored in userData)
178+
*/
179+
function getSettingsFilePath() {
180+
return path.join(app.getPath('userData'), 'settings.json');
181+
}
182+
183+
ipcMain.handle('get-settings', async () => {
184+
const settingsPath = getSettingsFilePath();
185+
try {
186+
const raw = await fs.promises.readFile(settingsPath, 'utf8');
187+
return { success: true, settings: JSON.parse(raw) };
188+
} catch (error) {
189+
if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
190+
return { success: true, settings: null };
191+
}
192+
console.error('Failed to read settings:', error);
193+
return { success: false, error: error.message, settings: null };
194+
}
195+
});
196+
197+
ipcMain.handle('set-settings', async (event, settings) => {
198+
const settingsPath = getSettingsFilePath();
199+
try {
200+
await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
201+
return { success: true };
202+
} catch (error) {
203+
console.error('Failed to write settings:', error);
204+
return { success: false, error: error.message };
205+
}
206+
});
207+
175208
/**
176209
* Convert a key code to an Electron accelerator string
177210
*/
@@ -181,9 +214,14 @@ function keyCodeToAccelerator(keyCode) {
181214
'Digit6': '6', 'Digit7': '7', 'Digit8': '8', 'Digit9': '9', 'Digit0': '0',
182215
'Minus': '-', 'Equal': '=', 'BracketLeft': '[', 'BracketRight': ']',
183216
'Semicolon': ';', 'Quote': "'", 'Comma': ',', 'Period': '.',
184-
'Slash': '/', 'Backslash': '\\'
217+
'Slash': '/', 'Backslash': '\\', 'Backquote': '`'
185218
};
186-
return keyMap[keyCode] || keyCode;
219+
220+
if (keyMap[keyCode]) return keyMap[keyCode];
221+
if (typeof keyCode === 'string' && keyCode.startsWith('Key') && keyCode.length === 4) {
222+
return keyCode.slice(3);
223+
}
224+
return keyCode;
187225
}
188226

189227
/**

src/preload.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
3131
*/
3232
getVersion: () => ipcRenderer.invoke('get-version'),
3333

34+
getSettings: () => ipcRenderer.invoke('get-settings'),
35+
36+
setSettings: (settings) => ipcRenderer.invoke('set-settings', settings),
37+
3438
/**
3539
* Register a global shortcut that works outside the app
3640
* @param {string} id - Unique identifier for the shortcut

src/setupTests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ global.window.electronAPI = {
1212
openExternal: jest.fn(),
1313
openPath: jest.fn(),
1414
getVersion: jest.fn().mockResolvedValue('1.0.0'),
15+
getSettings: jest.fn().mockResolvedValue({ success: true, settings: null }),
16+
setSettings: jest.fn().mockResolvedValue({ success: true }),
1517
isElectron: true,
1618
};
1719

version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
221
1+
0.2.1

0 commit comments

Comments
 (0)