From 0e46ef084e087f4297e913bfa8ba1b3f30bc5724 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 24 Dec 2025 10:21:36 -0500 Subject: [PATCH] Add customizable color preferences with separate settings window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## New Features - **Customizable Progress Bar Colors**: Users can now customize the colors for normal, warning, and danger states through a dedicated settings window - **Separate Settings Window**: Settings now open in a frameless, resizable window (500x680px) with live preview of color changes - **CSS Variables**: Converted hardcoded colors to CSS custom properties for dynamic theming ## Files Modified - **main.js**: Added settings window creation, color preference storage (electron-store), and IPC handlers - **preload.js**: Exposed color preference and settings IPC methods to renderer - **app.js**: Implemented color preference loading and live updates from settings window - **styles.css**: Added CSS variables for customizable colors, updated scrollbar styling - **index.html**: Removed unused settings overlay - **.gitignore**: Added CLAUDE_NOTES.md to prevent credential leaks ## Files Added - **src/renderer/settings.html**: Settings window UI with 2-column color picker layout - **src/renderer/settings.js**: Settings window logic and color management - **src/renderer/settings-styles.css**: Settings window styling - **CHANGES.md**: Tracking document for modifications ## Technical Details - Color preferences stored in electron-store with encryption - Live color updates via IPC communication between settings and main windows - Default color scheme: Purple (normal), Orange (warning), Red (danger) - Settings accessible via tray menu or settings button 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 1 + CHANGES.md | 39 ++++++ main.js | 68 ++++++++- preload.js | 11 +- src/renderer/app.js | 52 ++++--- src/renderer/index.html | 26 ---- src/renderer/settings-styles.css | 230 +++++++++++++++++++++++++++++++ src/renderer/settings.html | 105 ++++++++++++++ src/renderer/settings.js | 107 ++++++++++++++ src/renderer/styles.css | 38 +++-- 10 files changed, 615 insertions(+), 62 deletions(-) create mode 100644 CHANGES.md create mode 100644 src/renderer/settings-styles.css create mode 100644 src/renderer/settings.html create mode 100644 src/renderer/settings.js diff --git a/.gitignore b/.gitignore index 61d41dc..a4c6aa0 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ yarn-error.log* # Cache .cache/ .temp/ +CLAUDE_NOTES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..0b8f0f0 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,39 @@ +# Changes for Upstream PR + +## Feature: Customizable Progress Bar Colors + +### Modified Files + +#### main.js +- Added `DEFAULT_COLOR_PREFERENCES` constant with default color scheme (normal, warning, danger states) +- Added IPC handler `get-color-preferences` to retrieve stored color preferences from electron-store +- Added IPC handler `set-color-preferences` to save color preferences to electron-store +- Storage schema: `colorPreferences: { normal: {start, end}, warning: {start, end}, danger: {start, end} }` + +#### src/renderer/styles.css +- TODO: Add CSS custom properties (--color-normal-start, --color-normal-end, etc.) +- TODO: Convert hardcoded gradient colors to use CSS variables +- TODO: Apply to both .progress-fill and .timer-progress elements + +#### src/renderer/app.js +- TODO: Add `applyColorPreferences()` function to set CSS custom properties +- TODO: Load and apply color preferences on init +- TODO: Add event listeners for color picker changes + +#### src/renderer/index.html +- TODO: Add color picker UI in settings overlay (6 inputs for simplified scheme) +- TODO: Add "Reset to Defaults" button + +#### preload.js +- TODO: Expose `getColorPreferences` and `setColorPreferences` IPC methods + +--- + +## Feature: Dynamic Tray Icon with Usage Display (Planned) +- Not yet started + +--- + +## Storage Changes +- Using existing electron-store, no additional files +- New keys: `colorPreferences`, `trayDisplayMode` (planned) diff --git a/main.js b/main.js index e0b06f1..ea1539c 100644 --- a/main.js +++ b/main.js @@ -10,11 +10,14 @@ const store = new Store({ let mainWindow = null; let loginWindow = null; let silentLoginWindow = null; +let settingsWindow = null; let tray = null; // Window configuration const WIDGET_WIDTH = 480; const WIDGET_HEIGHT = 140; +const SETTINGS_WIDTH = 500; +const SETTINGS_HEIGHT = 680; function createMainWindow() { // Load saved position or use defaults @@ -347,7 +350,7 @@ function createTray() { { label: 'Settings', click: () => { - // TODO: Open settings window + createSettingsWindow(); } }, { @@ -380,6 +383,40 @@ function createTray() { } } +function createSettingsWindow() { + if (settingsWindow) { + settingsWindow.focus(); + return; + } + + settingsWindow = new BrowserWindow({ + width: SETTINGS_WIDTH, + height: SETTINGS_HEIGHT, + title: 'Settings - Claude Usage Widget', + frame: false, + resizable: true, + minimizable: true, + maximizable: false, + icon: path.join(__dirname, 'assets/icon.ico'), + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + + settingsWindow.loadFile('src/renderer/settings.html'); + + settingsWindow.on('closed', () => { + settingsWindow = null; + }); + + // Development tools + if (process.env.NODE_ENV === 'development') { + settingsWindow.webContents.openDevTools({ mode: 'detach' }); + } +} + // IPC Handlers ipcMain.handle('get-credentials', () => { return { @@ -416,6 +453,10 @@ ipcMain.on('open-login', () => { createLoginWindow(); }); +ipcMain.on('open-settings', () => { + createSettingsWindow(); +}); + ipcMain.on('minimize-window', () => { if (mainWindow) mainWindow.hide(); }); @@ -443,6 +484,31 @@ ipcMain.on('open-external', (event, url) => { shell.openExternal(url); }); +// Color preferences handlers +const DEFAULT_COLOR_PREFERENCES = { + normal: { start: '#8b5cf6', end: '#a78bfa' }, + warning: { start: '#f59e0b', end: '#fbbf24' }, + danger: { start: '#ef4444', end: '#f87171' } +}; + +ipcMain.handle('get-color-preferences', () => { + const saved = store.get('colorPreferences'); + return saved || DEFAULT_COLOR_PREFERENCES; +}); + +ipcMain.handle('set-color-preferences', (event, preferences) => { + store.set('colorPreferences', preferences); + return true; +}); + +ipcMain.handle('notify-color-change', (event, preferences) => { + // Notify main window to update colors + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('colors-changed', preferences); + } + return true; +}); + ipcMain.handle('fetch-usage-data', async () => { console.log('[Main] fetch-usage-data handler called'); const sessionKey = store.get('sessionKey'); diff --git a/preload.js b/preload.js index 63ac749..be50ebf 100644 --- a/preload.js +++ b/preload.js @@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld('electronAPI', { minimizeWindow: () => ipcRenderer.send('minimize-window'), closeWindow: () => ipcRenderer.send('close-window'), openLogin: () => ipcRenderer.send('open-login'), + openSettings: () => ipcRenderer.send('open-settings'), // Window position getWindowPosition: () => ipcRenderer.invoke('get-window-position'), @@ -36,5 +37,13 @@ contextBridge.exposeInMainWorld('electronAPI', { // API fetchUsageData: () => ipcRenderer.invoke('fetch-usage-data'), - openExternal: (url) => ipcRenderer.send('open-external', url) + openExternal: (url) => ipcRenderer.send('open-external', url), + + // Color preferences + getColorPreferences: () => ipcRenderer.invoke('get-color-preferences'), + setColorPreferences: (preferences) => ipcRenderer.invoke('set-color-preferences', preferences), + notifyColorChange: (preferences) => ipcRenderer.invoke('notify-color-change', preferences), + onColorsChanged: (callback) => { + ipcRenderer.on('colors-changed', (event, preferences) => callback(preferences)); + } }); diff --git a/src/renderer/app.js b/src/renderer/app.js index 7c3c51e..9c81fb2 100644 --- a/src/renderer/app.js +++ b/src/renderer/app.js @@ -27,11 +27,7 @@ const elements = { weeklyTimer: document.getElementById('weeklyTimer'), weeklyTimeText: document.getElementById('weeklyTimeText'), - settingsBtn: document.getElementById('settingsBtn'), - settingsOverlay: document.getElementById('settingsOverlay'), - closeSettingsBtn: document.getElementById('closeSettingsBtn'), - logoutBtn: document.getElementById('logoutBtn'), - coffeeBtn: document.getElementById('coffeeBtn') + settingsBtn: document.getElementById('settingsBtn') }; // Initialize @@ -39,6 +35,10 @@ async function init() { setupEventListeners(); credentials = await window.electronAPI.getCredentials(); + // Load and apply color preferences + const colorPrefs = await window.electronAPI.getColorPreferences(); + applyColorPreferences(colorPrefs); + if (credentials.sessionKey && credentials.organizationId) { showMainContent(); await fetchUsageData(); @@ -69,24 +69,9 @@ function setupEventListeners() { window.electronAPI.closeWindow(); // Exit application completely }); - // Settings calls + // Settings button elements.settingsBtn.addEventListener('click', () => { - elements.settingsOverlay.style.display = 'flex'; - }); - - elements.closeSettingsBtn.addEventListener('click', () => { - elements.settingsOverlay.style.display = 'none'; - }); - - elements.logoutBtn.addEventListener('click', async () => { - await window.electronAPI.deleteCredentials(); - elements.settingsOverlay.style.display = 'none'; - showLoginRequired(); - window.electronAPI.openLogin(); - }); - - elements.coffeeBtn.addEventListener('click', () => { - window.electronAPI.openExternal('https://paypal.me/SlavomirDurej?country.x=GB&locale.x=en_GB'); + window.electronAPI.openSettings(); }); // Listen for login success @@ -123,6 +108,12 @@ function setupEventListeners() { console.log('Silent login failed, manual login required'); showLoginRequired(); }); + + // Listen for color preference changes from settings window + window.electronAPI.onColorsChanged((preferences) => { + console.log('Colors changed, applying new preferences'); + applyColorPreferences(preferences); + }); } // Fetch usage data from Claude API @@ -381,6 +372,23 @@ function showError(message) { console.error(message); } +// Color preference management +function applyColorPreferences(prefs) { + const root = document.documentElement; + + // Apply normal colors + root.style.setProperty('--color-normal-start', prefs.normal.start); + root.style.setProperty('--color-normal-end', prefs.normal.end); + + // Apply warning colors + root.style.setProperty('--color-warning-start', prefs.warning.start); + root.style.setProperty('--color-warning-end', prefs.warning.end); + + // Apply danger colors + root.style.setProperty('--color-danger-start', prefs.danger.start); + root.style.setProperty('--color-danger-end', prefs.danger.end); +} + // Auto-update management function startAutoUpdate() { stopAutoUpdate(); diff --git a/src/renderer/index.html b/src/renderer/index.html index 9c3dc38..8fcaa77 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -120,32 +120,6 @@ - - diff --git a/src/renderer/settings-styles.css b/src/renderer/settings-styles.css new file mode 100644 index 0000000..defc2f5 --- /dev/null +++ b/src/renderer/settings-styles.css @@ -0,0 +1,230 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%); + color: #e0e0e0; + overflow: hidden; +} + +/* Title Bar */ +.title-bar { + -webkit-app-region: drag; + background: rgba(0, 0, 0, 0.3); + padding: 8px 12px; + padding-left: 6px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + height: 36px; +} + +.title { + color: #e0e0e0; + font-family: 'Libre Baskerville', serif; + font-size: 14px; + font-weight: 400; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.app-logo { + width: 19px; + height: 19px; + border-radius: 6px; + opacity: 0.8; +} + +.controls { + -webkit-app-region: no-drag; + display: flex; + gap: 8px; + margin-right: -10px; +} + +.control-btn { + width: 28px; + height: 28px; + border: none; + background: rgba(255, 255, 255, 0.05); + color: #a0a0a0; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + transition: all 0.2s ease; +} + +.control-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #ffffff; +} + +.close-btn:hover { + background: #e74c3c; + color: white; +} + +#settings-app { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.settings-container { + max-width: 600px; + margin: 0 auto; + padding: 24px; + flex: 1; +} + +.settings-section { + margin-bottom: 24px; +} + +.settings-section h2 { + font-size: 16px; + font-weight: 600; + margin-bottom: 16px; + color: #e0e0e0; +} + +/* 2-Column Grid for Color Pickers */ +.color-grid-2col { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + margin-bottom: 16px; +} + +.color-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.color-item label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #a0a0a0; + text-align: center; +} + +.color-item input[type="color"] { + width: 80px; + height: 50px; + border: 2px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + background: transparent; + cursor: pointer; + padding: 4px; + transition: all 0.2s ease; +} + +.color-item input[type="color"]:hover { + border-color: rgba(255, 255, 255, 0.4); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +.color-item input[type="color"]::-webkit-color-swatch-wrapper { + padding: 0; +} + +.color-item input[type="color"]::-webkit-color-swatch { + border: none; + border-radius: 6px; +} + +/* Buttons */ +.btn-secondary, +.btn-danger, +.btn-coffee { + width: 100%; + padding: 10px 16px; + border-radius: 8px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.1); + color: #e0e0e0; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-1px); +} + +.btn-danger { + background: transparent; + color: #ef4444; + border: 1px solid rgba(239, 68, 68, 0.5); +} + +.btn-danger:hover { + background: rgba(239, 68, 68, 0.15); + border-color: rgba(239, 68, 68, 0.7); +} + +.btn-coffee { + background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); + color: #1a1a1a; +} + +.btn-coffee:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(255, 165, 0, 0.4); +} + +.btn-coffee svg { + flex-shrink: 0; +} + +/* Divider */ +.divider { + width: 100%; + height: 1px; + background: rgba(255, 255, 255, 0.15); + margin: 24px 0; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +/* Disclaimer */ +.disclaimer { + font-size: 11px; + color: #666; + text-align: center; + line-height: 1.5; +} + +.disclaimer strong { + color: #888; +} + +/* Disclaimer spacing */ +.disclaimer { + margin-top: 16px; +} diff --git a/src/renderer/settings.html b/src/renderer/settings.html new file mode 100644 index 0000000..3e72b72 --- /dev/null +++ b/src/renderer/settings.html @@ -0,0 +1,105 @@ + + + + + + + Settings - Claude Usage Widget + + + + + + + +
+ +
+
+ + Settings +
+
+ + +
+
+ +
+ + +
+

Customize Colors

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+

Account

+ +
+ +
+ + +
+ + + +

+ Disclaimer: Unofficial tool not affiliated with Anthropic. Use at your own + discretion. +

+
+
+
+ + + + + diff --git a/src/renderer/settings.js b/src/renderer/settings.js new file mode 100644 index 0000000..e48fe9b --- /dev/null +++ b/src/renderer/settings.js @@ -0,0 +1,107 @@ +// DOM elements +const elements = { + colorNormalStart: document.getElementById('colorNormalStart'), + colorNormalEnd: document.getElementById('colorNormalEnd'), + colorWarningStart: document.getElementById('colorWarningStart'), + colorWarningEnd: document.getElementById('colorWarningEnd'), + colorDangerStart: document.getElementById('colorDangerStart'), + colorDangerEnd: document.getElementById('colorDangerEnd'), + resetColorsBtn: document.getElementById('resetColorsBtn'), + logoutBtn: document.getElementById('logoutBtn'), + coffeeBtn: document.getElementById('coffeeBtn'), + minimizeBtn: document.getElementById('minimizeBtn'), + closeBtn: document.getElementById('closeBtn') +}; + +// Initialize +async function init() { + setupEventListeners(); + + // Load and apply color preferences + const colorPrefs = await window.electronAPI.getColorPreferences(); + loadColorPickerValues(colorPrefs); +} + +// Event Listeners +function setupEventListeners() { + // Color picker event listeners + const colorInputs = [ + elements.colorNormalStart, + elements.colorNormalEnd, + elements.colorWarningStart, + elements.colorWarningEnd, + elements.colorDangerStart, + elements.colorDangerEnd + ]; + + colorInputs.forEach(input => { + input.addEventListener('change', async () => { + await saveCurrentColors(); + }); + }); + + elements.resetColorsBtn.addEventListener('click', async () => { + const defaults = { + normal: { start: '#8b5cf6', end: '#a78bfa' }, + warning: { start: '#f59e0b', end: '#fbbf24' }, + danger: { start: '#ef4444', end: '#f87171' } + }; + await window.electronAPI.setColorPreferences(defaults); + loadColorPickerValues(defaults); + // Notify main window to update colors immediately + await window.electronAPI.notifyColorChange(defaults); + }); + + elements.logoutBtn.addEventListener('click', async () => { + await window.electronAPI.deleteCredentials(); + window.close(); + window.electronAPI.openLogin(); + }); + + elements.coffeeBtn.addEventListener('click', () => { + window.electronAPI.openExternal('https://paypal.me/SlavomirDurej?country.x=GB&locale.x=en_GB'); + }); + + // Window controls + elements.minimizeBtn.addEventListener('click', () => { + window.electronAPI.minimizeWindow(); + }); + + elements.closeBtn.addEventListener('click', () => { + window.close(); + }); +} + +// Helper functions +function loadColorPickerValues(prefs) { + elements.colorNormalStart.value = prefs.normal.start; + elements.colorNormalEnd.value = prefs.normal.end; + elements.colorWarningStart.value = prefs.warning.start; + elements.colorWarningEnd.value = prefs.warning.end; + elements.colorDangerStart.value = prefs.danger.start; + elements.colorDangerEnd.value = prefs.danger.end; +} + +async function saveCurrentColors() { + const prefs = { + normal: { + start: elements.colorNormalStart.value, + end: elements.colorNormalEnd.value + }, + warning: { + start: elements.colorWarningStart.value, + end: elements.colorWarningEnd.value + }, + danger: { + start: elements.colorDangerStart.value, + end: elements.colorDangerEnd.value + } + }; + + await window.electronAPI.setColorPreferences(prefs); + // Notify main window to update colors + await window.electronAPI.notifyColorChange(prefs); +} + +// Start the application +init(); diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 1713f4e..1c75ec0 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -1,3 +1,17 @@ +:root { + /* Customizable color scheme - normal state */ + --color-normal-start: #8b5cf6; + --color-normal-end: #a78bfa; + + /* Warning state (>=75% usage) */ + --color-warning-start: #f59e0b; + --color-warning-end: #fbbf24; + + /* Danger state (>=90% usage) */ + --color-danger-start: #ef4444; + --color-danger-end: #f87171; +} + * { margin: 0; padding: 0; @@ -339,7 +353,7 @@ body { .progress-fill { height: 100%; - background: linear-gradient(90deg, #8b5cf6 0%, #a78bfa 100%); + background: linear-gradient(90deg, var(--color-normal-start) 0%, var(--color-normal-end) 100%); border-radius: 3px; transition: width 0.6s ease; position: relative; @@ -349,8 +363,8 @@ body { /* Shimmer removed */ .progress-fill.weekly { - background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%); - box-shadow: 0 0 10px rgba(59, 130, 246, 0.3); + background: linear-gradient(90deg, var(--color-normal-start) 0%, var(--color-normal-end) 100%); + box-shadow: 0 0 10px rgba(139, 92, 246, 0.3); } @@ -378,13 +392,13 @@ body { .timer-progress { fill: none; - stroke: #8b5cf6; + stroke: var(--color-normal-start); stroke-width: 4; transition: stroke-dashoffset 0.6s ease; } .timer-progress.weekly { - stroke: #3b82f6; + stroke: var(--color-normal-start); } .timer-text { @@ -545,31 +559,31 @@ body { } ::-webkit-scrollbar-track { - background: transparent; + background: rgba(0, 0, 0, 0.2); } ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.15); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.25); } /* Warning states */ .progress-fill.warning { - background: linear-gradient(90deg, #f59e0b 0%, #fbbf24 100%); + background: linear-gradient(90deg, var(--color-warning-start) 0%, var(--color-warning-end) 100%); } .progress-fill.danger { - background: linear-gradient(90deg, #ef4444 0%, #f87171 100%); + background: linear-gradient(90deg, var(--color-danger-start) 0%, var(--color-danger-end) 100%); } .timer-progress.warning { - stroke: #f59e0b; + stroke: var(--color-warning-start); } .timer-progress.danger { - stroke: #ef4444; + stroke: var(--color-danger-start); } \ No newline at end of file