diff --git a/README.md b/README.md index b1456a0..deb949a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,44 @@ # Claude Usage Widget -A beautiful, standalone Windows desktop widget that displays your Claude.ai usage statistics in real-time. +A beautiful, highly customizable Windows desktop widget that displays your Claude.ai usage statistics in real-time with system tray integration. -![Claude Usage Widget](assets/claude-usage-screenshot.jpg) +**[SCREENSHOT: Main widget showing session and weekly usage]** ## Features -- 🎯 **Real-time Usage Tracking** - Monitor both session and weekly usage limits -- 📊 **Visual Progress Bars** - Clean, gradient progress indicators -- ⏱️ **Countdown Timers** - Circular timers showing time until reset -- 🔄 **Auto-refresh** - Updates every 5 minutes automatically -- 🎨 **Modern UI** - Sleek, draggable widget with dark theme -- 🎨 **Customizable Colors** - Personalize progress bar colors for each usage level -- ⚙️ **Settings Panel** - Easy-to-use settings window for customization -- 🔒 **Secure** - Encrypted credential storage +### Core Features +- 🎯 **Real-time Usage Tracking** - Monitor both 5-hour session and 7-day weekly usage limits +- 📊 **Visual Progress Bars** - Clean, gradient progress indicators with customizable colors +- ⏱️ **Countdown Timers** - Circular timers showing time until session/weekly reset +- 🔄 **Configurable Auto-refresh** - Adjustable update interval (10-300 seconds) +- 🎨 **Fully Customizable UI** - Toggle any element on/off, customize all colors and theme - 📍 **Always on Top** - Stays visible across all workspaces -- 💾 **System Tray** - Minimizes to tray for easy access +- 🔒 **Secure** - Encrypted credential storage using electron-store + +### System Tray Integration +- 💾 **Live Tray Icon** - Real-time usage percentage displayed in system tray +- 🎨 **Customizable Tray Colors** - Independent color schemes for tray icon +- ⚙️ **Tray Update Interval** - Configurable refresh rate (10-300 seconds) +- 📊 **Switchable Metrics** - Display either session or weekly usage in tray +- 🔢 **Toggle Percentage Text** - Show/hide percentage number on tray icon + +### Advanced Customization +- 🎨 **Theme Editor** - Customize background gradients, text colors, borders, and title bar +- 🖼️ **Element Visibility Controls** - Show/hide any UI element independently: + - Section labels (Current Session / Weekly Limit) + - Progress bars + - Percentage text + - Countdown circles + - Time remaining text +- 📏 **Dynamic Resizing** - Window automatically adjusts to fit visible elements +- 💎 **Static or Gradient Colors** - Choose between static colors or gradient progressions +- 🎯 **Color Coding by Usage** - Different colors for normal (0-74%), warning (75-89%), and danger (90-100%) + +### Application Settings +- 🚀 **Start on System Boot** - Automatically launch when Windows starts +- 🔽 **Start Minimized** - Begin in system tray without showing window +- 📌 **Close to Tray** - Minimize to tray instead of exiting application +- 💾 **Persistent Window Position** - Remembers and restores window location ## Installation @@ -54,71 +77,189 @@ The installer will be created in the `dist/` folder. ### First Launch 1. Launch the widget -2. Click "Login to Claude" when prompted -3. A browser window will open - login to your Claude.ai account -4. The widget will automatically capture your session -5. Usage data will start displaying immediately +2. The app will attempt to auto-login using stored credentials +3. If no credentials exist, click "Login to Claude" +4. A browser window will open - login to your Claude.ai account +5. The widget will automatically capture your session +6. Usage data will start displaying immediately + +**[SCREENSHOT: Login screen]** ### Widget Controls -- **Drag** - Click and drag the title bar to move the widget -- **Refresh** - Click the refresh icon to update data immediately -- **Minimize** - Click the minus icon to hide to system tray -- **Close** - Click the X to minimize to tray (doesn't exit) +**Title Bar:** +- **Drag** - Click and drag anywhere on the title bar to move the widget +- **Settings Icon** - Open the comprehensive settings panel +- **Refresh Icon** - Update usage data immediately +- **Minimize** - Hide to system tray +- **Close** - Minimize to tray or exit (depending on settings) -### System Tray Menu +**[SCREENSHOT: Widget with controls highlighted]** -Right-click the tray icon for: -- Show/Hide widget -- Refresh usage data -- Re-login (if session expires) -- Settings - Open customization panel -- Exit application +### System Tray + +**Tray Icon:** +- Displays real-time usage percentage +- Color changes based on usage level (normal/warning/danger) +- Switches between session and weekly usage (configurable in settings) +- Updates independently with configurable interval + +**Tray Menu (Right-click):** +- **Show/Hide Widget** - Toggle main window visibility +- **Refresh Usage** - Update data immediately +- **Re-login** - Clear credentials and login again +- **Settings** - Open settings panel +- **Exit** - Quit application completely + +**[SCREENSHOT: System tray icon and menu]** + +## Settings + +Access settings by clicking the gear icon on the widget or via the system tray menu. + +### Main Window Settings + +#### Colors +Customize progress bar colors with gradient or static options: + +**Static Color Mode:** +- Enable "Use Static Color" toggle +- Choose a single color for the progress bar (ignores usage levels) + +**Gradient Mode (Default):** +- **Normal** (0-74% usage) - Default: Purple gradient (#8b5cf6 → #a78bfa) +- **Warning** (75-89% usage) - Default: Orange gradient (#f59e0b → #fbbf24) +- **Danger** (90-100% usage) - Default: Red gradient (#ef4444 → #f87171) + +Each level has independent start and end colors for smooth gradients. + +**[SCREENSHOT: Color customization panel]** + +#### Theme +Customize the entire widget appearance: +- **Background Gradient** - Start and end colors +- **Text Colors** - Primary and secondary text +- **Title Bar** - Background color and opacity (0-100%) +- **Borders** - Border color and opacity (0-100%) + +**[SCREENSHOT: Theme customization panel]** + +#### Update Interval +- Range: 10-300 seconds (default: 30 seconds) +- Controls how often the main widget refreshes usage data +- Lower values = more current data, higher API usage +- Slider with real-time value display + +#### Current Session Elements +Control visibility of each element in the Current Session section: +- **Show Current Session** - Master toggle for entire section +- **Show Label Text** - "CURRENT SESSION" label +- **Show Progress Bar** - Usage progress bar +- **Show Percentage Text** - Usage percentage number +- **Show Countdown Circle** - Circular timer graphic +- **Show Time Text** - Time remaining until reset + +**[SCREENSHOT: Element visibility controls]** + +#### Weekly Limit Elements +Identical controls for the Weekly Limit section: +- Master toggle to show/hide entire section +- Individual toggles for label, bar, percentage, circle, and time + +**Example Configurations:** +- Minimal: Just progress bars (hide everything else) +- Data-rich: All elements visible (default) +- Custom: Any combination you prefer + +### Tray Icon Settings + +#### Display Mode +Choose which metric to display in the system tray: +- **Session Usage** (5-hour limit) +- **Weekly Usage** (7-day limit) + +#### Update Interval +- Range: 10-300 seconds (default: 30 seconds) +- Independent from main widget update interval +- Tray icon updates on its own schedule + +#### Show Percentage Text +- Toggle percentage number on/off in tray icon +- When disabled, shows only the colored ring + +#### Colors +Separate color customization for tray icon: +- Static or gradient mode (same as main widget) +- Independent color schemes +- Normal/Warning/Danger levels + +**[SCREENSHOT: Tray icon settings]** + +### Application Settings + +#### Start on System Boot +- Automatically launch widget when Windows starts +- Registers with Windows startup items +- Disable to manually start the widget + +#### Start Minimized +- Launch hidden in system tray +- Widget won't show window on startup +- Access via tray icon + +#### Close to Tray +- When enabled: Clicking X minimizes to tray +- When disabled: Clicking X exits application +- Minimize button always goes to tray + +**[SCREENSHOT: Application settings]** + +### Account + +**Log Out** - Clears stored credentials and returns to login screen ## Understanding the Display -### Current Session -- **Progress Bar** - Shows usage from 0-100% -- **Timer** - Time remaining until 5-hour session resets +### Current Session (5-Hour Limit) +- **Progress Bar** - Visual representation of usage (0-100%) +- **Percentage** - Exact usage percentage +- **Countdown Circle** - Circular timer showing progress toward reset +- **Time Remaining** - Hours and minutes until session resets (e.g., "2h 34m") - **Color Coding**: - Purple: Normal usage (0-74%) - Orange: High usage (75-89%) - Red: Critical usage (90-100%) -### Weekly Limit -- **Progress Bar** - Shows weekly usage from 0-100% -- **Timer** - Time remaining until weekly reset (Wednesdays 7:00 AM) -- **Same color coding** as session usage +### Weekly Limit (7-Day Rolling Window) +- **Progress Bar** - Weekly usage from 0-100% +- **Percentage** - Exact weekly usage percentage +- **Countdown Circle** - Progress toward weekly reset +- **Time Remaining** - Days/hours until weekly reset (e.g., "3d 5h") +- Same color coding as session usage -## Customization +**[SCREENSHOT: Usage display explanation with labels]** -### Color Preferences +## Window Behavior -Customize the progress bar colors to match your preferences: +### Dynamic Resizing +- Window automatically resizes based on visible elements +- Shows only what you've enabled in settings +- Maintains minimum size for readability +- Cannot be manually resized smaller than content -1. Right-click the system tray icon -2. Select "Settings" -3. Use the color pickers to customize each usage level: - - **Normal** (0-74% usage) - Default: Purple gradient - - **Warning** (75-89% usage) - Default: Orange gradient - - **Danger** (90-100% usage) - Default: Red gradient -4. Changes apply instantly to the main widget -5. Click "Reset to Defaults" to restore original colors +### Manual Resizing +- Drag edges to resize window +- Constraints: 320-600px width, 96-180px height +- Content stays centered at all sizes -![Settings Window - Color Customization](assets/settings-screenshot.png) +### Position Persistence +- Window position saved automatically when moved +- Restored on next launch +- Per-display awareness -### Auto-start on Windows Boot +## Keyboard Shortcuts -1. Press `Win + R` -2. Type `shell:startup` and press Enter -3. Create a shortcut to the widget executable in this folder - -### Custom Refresh Interval - -Edit `src/renderer/app.js`: -```javascript -const UPDATE_INTERVAL = 5 * 60 * 1000; // Change to your preference (in milliseconds) -``` +Currently, the widget does not have keyboard shortcuts. This may be added in a future version. ## Troubleshooting @@ -126,16 +267,31 @@ const UPDATE_INTERVAL = 5 * 60 * 1000; // Change to your preference (in millisec - Your Claude.ai session may have expired - Click "Login to Claude" to re-authenticate - Check that you're logging into the correct account +- Verify Claude.ai is accessible in your region ### Widget not updating - Check your internet connection - Click the refresh button manually -- Ensure Claude.ai is accessible in your region -- Try re-logging in from the system tray menu +- Ensure Claude.ai API is accessible +- Try increasing update interval if experiencing rate limits +- Re-login from system tray menu -### Widget position not saving -- Window position is now saved automatically when you drag it -- Position will be restored when you restart the app +### Tray icon not showing/updating +- Check that "Show Percentage Text" is enabled to see text +- Verify update interval isn't too long +- Try changing display mode (session ↔ weekly) +- Restart the application + +### Window too small/elements cut off +- Check element visibility settings +- Enable more elements to expand window +- Window automatically sizes to fit enabled elements +- Reset to defaults if layout seems broken + +### Auto-start not working +- Verify "Start on System Boot" is enabled in settings +- Check Windows Task Manager > Startup tab +- May require administrator rights on some systems ### Build errors ```bash @@ -146,10 +302,11 @@ npm install ## Privacy & Security -- Your session credentials are stored **locally only** using encrypted storage -- No data is sent to any third-party servers -- The widget only communicates with Claude.ai official API -- Session cookies are stored using Electron's secure storage +- **Local Storage Only** - Your session credentials are stored locally using encrypted electron-store +- **No Third-Party Servers** - No data is sent to any servers except Claude.ai official API +- **Official API Only** - Widget only communicates with Claude.ai API endpoints +- **Secure Cookies** - Session cookies stored using Electron's secure storage +- **Open Source** - All code is publicly available for audit ## Technical Details @@ -157,11 +314,14 @@ npm install - Electron 28.0.0 - Pure JavaScript (no framework overhead) - Native Node.js APIs -- electron-store for secure storage +- electron-store for encrypted secure storage +- axios for HTTP requests +- Canvas API for tray icon generation -**API Endpoint:** +**API Endpoints:** ``` https://claude.ai/api/organizations/{org_id}/usage +https://claude.ai/api/organizations ``` **Storage Location:** @@ -169,37 +329,152 @@ https://claude.ai/api/organizations/{org_id}/usage %APPDATA%/claude-usage-widget/config.json (encrypted) ``` +**Configuration Keys:** +- `sessionKey` - Encrypted Claude.ai session token +- `organizationId` - Your Claude organization UUID +- `colorPreferences` - Custom color settings +- `traySettings` - Tray icon configuration +- `themeSettings` - Widget theme preferences +- `appSettings` - Application behavior settings +- `uiVisibility` - Element visibility toggles +- `windowPosition` - Saved window location + +## Project Structure + +``` +claude-usage-widget/ +├── main.js # Electron main process +├── preload.js # IPC bridge (security) +├── package.json # Dependencies and scripts +├── assets/ +│ ├── icon.ico # Application icon +│ └── logo.png # Widget logo +└── src/ + └── renderer/ + ├── index.html # Main widget UI + ├── app.js # Widget logic + ├── styles.css # Widget styles + ├── settings.html # Settings panel UI + ├── settings.js # Settings logic + └── settings-styles.css # Settings styles +``` + +## Version History + +### v1.4.2 (Current) +- ✨ Added comprehensive UI element visibility controls +- ✨ Added system tray icon with live usage display +- ✨ Added customizable tray icon colors and update interval +- ✨ Added application settings (start on boot, start minimized, close to tray) +- ✨ Added theme customization (backgrounds, text colors, borders, opacity) +- ✨ Added configurable update intervals for UI and tray +- ✨ Added static color mode for progress bars +- ✨ Added dynamic window resizing based on visible elements +- 🎨 Improved settings organization with collapsible sections +- 🎨 Consistent typography and spacing throughout settings +- 🐛 Fixed element centering and responsive layout +- 🐛 Fixed timer container visibility handling + +### v1.4.1 +- Added color customization +- Added settings panel +- Improved window position persistence + +### v1.4.0 +- Added custom color themes +- Added settings window +- Remember window position + +### v1.3.0 +- Initial public release +- Real-time usage tracking +- System tray support +- Auto-refresh functionality + ## Roadmap +### Planned Features - [ ] macOS support - [ ] Linux support -- [x] Custom color themes - [ ] Notification alerts at usage thresholds -- [x] Remember window position -- [x] Settings panel - [ ] Usage history graphs +- [ ] Export usage data (CSV/JSON) - [ ] Multiple account support - [ ] Keyboard shortcuts +- [ ] Mini mode (ultra-compact view) +- [ ] Desktop widget mode (frameless, always-on-desktop) +- [ ] Custom notification sounds + +### Completed +- [x] Custom color themes +- [x] Remember window position +- [x] Settings panel +- [x] System tray integration +- [x] Tray icon with live usage +- [x] Element visibility controls +- [x] Theme customization +- [x] Configurable update intervals +- [x] Auto-start on boot +- [x] Static color mode ## Contributing -Contributions are welcome! Please feel free to submit a Pull Request. +Contributions are welcome! Here's how you can help: + +1. **Fork the repository** +2. **Create a feature branch** (`git checkout -b feature/amazing-feature`) +3. **Commit your changes** (`git commit -m 'Add some amazing feature'`) +4. **Push to the branch** (`git push origin feature/amazing-feature`) +5. **Open a Pull Request** + +### Development Guidelines +- Follow existing code style +- Test thoroughly before submitting PR +- Update README if adding features +- Add comments for complex logic ## License -MIT License - feel free to use and modify as needed. +MIT License - feel free to use, modify, and distribute as needed. + +See [LICENSE](LICENSE) file for full details. ## Disclaimer -This is an unofficial tool and is not affiliated with or endorsed by Anthropic. Use at your own discretion. +**This is an unofficial tool and is not affiliated with, endorsed by, or connected to Anthropic or Claude.ai in any way.** + +- Use at your own risk and discretion +- No warranties or guarantees provided +- Not responsible for any issues arising from use +- May break if Claude.ai changes their API +- Respect Anthropic's Terms of Service ## Support If you encounter issues: -1. Check the [Issues](issues) page -2. Create a new issue with details about your problem -3. Include your OS version and any error messages + +1. **Check existing issues** - [Issues page](../../issues) +2. **Create a new issue** with: + - Your Windows version + - Widget version + - Steps to reproduce + - Error messages (if any) + - Screenshots (if relevant) + +**Before reporting:** +- Try restarting the widget +- Try re-logging in +- Check if Claude.ai is accessible in browser +- Verify you have the latest version + +## Acknowledgments + +- Built for the Claude.ai community +- Inspired by the need for easy usage monitoring +- Thanks to all contributors and users --- -Made with ❤️ for the Claude.ai community +**Made with ❤️ for the Claude.ai community** + +**Star ⭐ this repo if you find it useful!** diff --git a/main.js b/main.js index f1d260e..6ea471f 100644 --- a/main.js +++ b/main.js @@ -31,10 +31,14 @@ function createMainWindow() { const windowOptions = { width: WIDGET_WIDTH, height: WIDGET_HEIGHT, + minWidth: 320, + maxWidth: 600, + minHeight: 96, + maxHeight: 180, frame: false, transparent: true, alwaysOnTop: true, - resizable: false, + resizable: true, skipTaskbar: false, icon: path.join(__dirname, 'assets/icon.ico'), webPreferences: { @@ -579,7 +583,12 @@ ipcMain.on('minimize-window', () => { }); ipcMain.on('close-window', () => { - app.quit(); + const appSettings = store.get('appSettings', { closeToTray: false }); + if (appSettings.closeToTray) { + mainWindow.hide(); + } else { + app.quit(); + } }); ipcMain.handle('get-window-position', () => { @@ -597,6 +606,19 @@ ipcMain.handle('set-window-position', (event, { x, y }) => { return false; }); +ipcMain.on('set-window-height', (event, height) => { + if (mainWindow) { + const currentBounds = mainWindow.getBounds(); + mainWindow.setSize(currentBounds.width, Math.round(height)); + } +}); + +ipcMain.on('set-window-size', (event, { width, height }) => { + if (mainWindow) { + mainWindow.setSize(Math.round(width), Math.round(height)); + } +}); + ipcMain.on('open-external', (event, url) => { shell.openExternal(url); }); @@ -735,13 +757,84 @@ ipcMain.handle('notify-theme-change', (event, theme) => { return true; }); +// App settings +ipcMain.handle('get-app-settings', () => { + return store.get('appSettings', { + startOnBoot: false, + startMinimized: false, + closeToTray: false, + uiUpdateInterval: 30 // 30 seconds + }); +}); + +ipcMain.handle('set-app-settings', (event, settings) => { + const currentSettings = store.get('appSettings', {}); + const newSettings = { ...currentSettings, ...settings }; + store.set('appSettings', newSettings); + + // Apply auto-launch setting + if (settings.hasOwnProperty('startOnBoot')) { + app.setLoginItemSettings({ + openAtLogin: settings.startOnBoot, + path: process.execPath + }); + } + + return true; +}); + +// UI visibility settings +ipcMain.handle('get-ui-visibility', () => { + return store.get('uiVisibility', { + showSessionSection: true, + showWeeklySection: true, + sessionShowLabel: true, + sessionShowBar: true, + sessionShowPercentage: true, + sessionShowCircle: true, + sessionShowTime: true, + weeklyShowLabel: true, + weeklyShowBar: true, + weeklyShowPercentage: true, + weeklyShowCircle: true, + weeklyShowTime: true + }); +}); + +ipcMain.handle('set-ui-visibility', (event, visibility) => { + store.set('uiVisibility', visibility); + // Notify main window to update visibility + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('ui-visibility-changed', visibility); + } + return true; +}); + // App lifecycle app.whenReady().then(() => { + // Apply startup settings + const appSettings = store.get('appSettings', { + startOnBoot: false, + startMinimized: false, + closeToTray: false + }); + + // Set auto-launch + app.setLoginItemSettings({ + openAtLogin: appSettings.startOnBoot, + path: process.execPath + }); + createMainWindow(); createTray(); createIconGeneratorWindow(); startTrayUpdateTimer(); + // Start minimized if enabled + if (appSettings.startMinimized) { + mainWindow.hide(); + } + // Check if we have credentials // const hasCredentials = store.get('sessionKey') && store.get('organizationId'); // if (!hasCredentials) { diff --git a/package.json b/package.json index 2d87625..8715437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-usage-widget", - "version": "1.4.0", + "version": "1.4.2", "description": "Desktop widget for Claude.ai usage monitoring", "main": "main.js", "scripts": { diff --git a/preload.js b/preload.js index 5993823..3c07fce 100644 --- a/preload.js +++ b/preload.js @@ -17,6 +17,8 @@ contextBridge.exposeInMainWorld('electronAPI', { // Window position getWindowPosition: () => ipcRenderer.invoke('get-window-position'), setWindowPosition: (position) => ipcRenderer.invoke('set-window-position', position), + setWindowHeight: (height) => ipcRenderer.send('set-window-height', height), + setWindowSize: (size) => ipcRenderer.send('set-window-size', size), // Event listeners onLoginSuccess: (callback) => { @@ -60,5 +62,16 @@ contextBridge.exposeInMainWorld('electronAPI', { notifyThemeChange: (theme) => ipcRenderer.invoke('notify-theme-change', theme), onThemeChanged: (callback) => { ipcRenderer.on('theme-changed', (event, theme) => callback(theme)); + }, + + // App settings + getAppSettings: () => ipcRenderer.invoke('get-app-settings'), + setAppSettings: (settings) => ipcRenderer.invoke('set-app-settings', settings), + + // UI visibility settings + getUIVisibility: () => ipcRenderer.invoke('get-ui-visibility'), + setUIVisibility: (visibility) => ipcRenderer.invoke('set-ui-visibility', visibility), + onUIVisibilityChanged: (callback) => { + ipcRenderer.on('ui-visibility-changed', (event, visibility) => callback(visibility)); } }); diff --git a/src/renderer/app.js b/src/renderer/app.js index e0a585f..d860366 100644 --- a/src/renderer/app.js +++ b/src/renderer/app.js @@ -43,6 +43,10 @@ async function init() { const themeSettings = await window.electronAPI.getThemeSettings(); applyThemePreferences(themeSettings); + // Load and apply UI visibility settings + const uiVisibility = await window.electronAPI.getUIVisibility(); + applyUIVisibility(uiVisibility); + if (credentials.sessionKey && credentials.organizationId) { showMainContent(); await fetchUsageData(); @@ -124,6 +128,12 @@ function setupEventListeners() { console.log('Theme changed, applying new theme'); applyThemePreferences(theme); }); + + // Listen for UI visibility changes from settings window + window.electronAPI.onUIVisibilityChanged((visibility) => { + console.log('UI visibility changed, applying new settings'); + applyUIVisibility(visibility); + }); } // Fetch usage data from Claude API @@ -434,12 +444,164 @@ function applyThemePreferences(theme) { } } +// UI visibility management +function applyUIVisibility(visibility) { + const usageSections = document.querySelectorAll('.usage-section'); + if (usageSections.length < 2) return; + + const sessionSection = usageSections[0]; + const weeklySection = usageSections[1]; + + // Default to true if not explicitly set to false + const showSessionSection = visibility.showSessionSection !== false; + const showWeeklySection = visibility.showWeeklySection !== false; + const sessionShowLabel = visibility.sessionShowLabel !== false; + const sessionShowBar = visibility.sessionShowBar !== false; + const sessionShowPercentage = visibility.sessionShowPercentage !== false; + const sessionShowCircle = visibility.sessionShowCircle !== false; + const sessionShowTime = visibility.sessionShowTime !== false; + const weeklyShowLabel = visibility.weeklyShowLabel !== false; + const weeklyShowBar = visibility.weeklyShowBar !== false; + const weeklyShowPercentage = visibility.weeklyShowPercentage !== false; + const weeklyShowCircle = visibility.weeklyShowCircle !== false; + const weeklyShowTime = visibility.weeklyShowTime !== false; + + // Session elements + const sessionLabel = sessionSection.querySelector('.usage-label'); + const sessionBar = sessionSection.querySelector('.progress-bar'); + const sessionPercentage = sessionSection.querySelector('.usage-percentage'); + const sessionTimerContainer = sessionSection.querySelector('.timer-container'); + const sessionTimerSVG = sessionSection.querySelector('.mini-timer'); + const sessionTimeText = sessionSection.querySelector('.timer-text'); + + if (sessionLabel) sessionLabel.style.display = sessionShowLabel ? 'block' : 'none'; + if (sessionBar) sessionBar.style.display = sessionShowBar ? 'block' : 'none'; + if (sessionPercentage) sessionPercentage.style.display = sessionShowPercentage ? 'block' : 'none'; + if (sessionTimerSVG) sessionTimerSVG.style.display = sessionShowCircle ? 'block' : 'none'; + if (sessionTimeText) sessionTimeText.style.display = sessionShowTime ? 'block' : 'none'; + + // Hide timer container if both circle and time are hidden + if (sessionTimerContainer) { + sessionTimerContainer.style.display = (sessionShowCircle || sessionShowTime) ? 'flex' : 'none'; + } + + // Weekly elements + const weeklyLabel = weeklySection.querySelector('.usage-label'); + const weeklyBar = weeklySection.querySelector('.progress-bar'); + const weeklyPercentage = weeklySection.querySelector('.usage-percentage'); + const weeklyTimerContainer = weeklySection.querySelector('.timer-container'); + const weeklyTimerSVG = weeklySection.querySelector('.mini-timer'); + const weeklyTimeText = weeklySection.querySelector('.timer-text'); + + if (weeklyLabel) weeklyLabel.style.display = weeklyShowLabel ? 'block' : 'none'; + if (weeklyBar) weeklyBar.style.display = weeklyShowBar ? 'block' : 'none'; + if (weeklyPercentage) weeklyPercentage.style.display = weeklyShowPercentage ? 'block' : 'none'; + if (weeklyTimerSVG) weeklyTimerSVG.style.display = weeklyShowCircle ? 'block' : 'none'; + if (weeklyTimeText) weeklyTimeText.style.display = weeklyShowTime ? 'block' : 'none'; + + // Hide timer container if both circle and time are hidden + if (weeklyTimerContainer) { + weeklyTimerContainer.style.display = (weeklyShowCircle || weeklyShowTime) ? 'flex' : 'none'; + } + + // Show/hide entire sections + if (sessionSection) sessionSection.style.display = showSessionSection ? 'flex' : 'none'; + if (weeklySection) weeklySection.style.display = showWeeklySection ? 'flex' : 'none'; + + // Adjust window height based on visible content + adjustWindowHeight(); +} + +// Adjust window size based on visible content +function adjustWindowHeight() { + const usageSections = document.querySelectorAll('.usage-section'); + let visibleSections = 0; + let maxWidthNeeded = 0; + + usageSections.forEach(section => { + if (section.style.display !== 'none') { + visibleSections++; + + // Calculate width needed for this section + let sectionWidth = 60; // Base padding (30px left + 30px right for safety) + + // Check what's visible in this section + const label = section.querySelector('.usage-label'); + const bar = section.querySelector('.progress-bar'); + const percentage = section.querySelector('.usage-percentage'); + const timerContainer = section.querySelector('.timer-container'); + + // Count visible elements and calculate width + let visibleElements = 0; + + if (label && label.style.display !== 'none') { + sectionWidth += 120; // Label width + buffer + visibleElements++; + } + if (bar && bar.style.display !== 'none') { + sectionWidth += 150; // Minimum readable bar width + visibleElements++; + } + if (percentage && percentage.style.display !== 'none') { + sectionWidth += 55; // Percentage width + visibleElements++; + } + if (timerContainer) { + const timerSVG = timerContainer.querySelector('.mini-timer'); + const timeText = timerContainer.querySelector('.timer-text'); + if ((timerSVG && timerSVG.style.display !== 'none') || + (timeText && timeText.style.display !== 'none')) { + sectionWidth += 100; // Timer container width + visibleElements++; + } + } + + // Add gaps between elements (16px each) + if (visibleElements > 1) { + sectionWidth += (visibleElements - 1) * 16; + } + + // Ensure minimum width even if only one element visible + sectionWidth = Math.max(sectionWidth, 200); + + maxWidthNeeded = Math.max(maxWidthNeeded, sectionWidth); + } + }); + + // Calculate height: title bar (36px) + padding + sections + const titleBarHeight = 36; + const contentPadding = 40; // 20px top + 20px bottom + const sectionHeight = 40; + const newHeight = titleBarHeight + contentPadding + (visibleSections * sectionHeight); + + // Title bar needs space for "Claude Usage" + buttons + // Minimum: logo (19px) + gap (8px) + text (~110px) + buttons (4 * 28px + 3 * 8px gaps) = ~280px + const minTitleBarWidth = 300; + + // Ensure we have content width or use minimum + const contentWidth = Math.max(maxWidthNeeded, 200); + + // Final dimensions - use the larger of content width or title bar width + const finalWidth = Math.max(contentWidth, minTitleBarWidth); + const finalHeight = Math.max(newHeight, titleBarHeight + 60); + + // Clamp to window constraints + const clampedWidth = Math.min(Math.max(finalWidth, 320), 600); + const clampedHeight = Math.min(Math.max(finalHeight, 96), 180); + + window.electronAPI.setWindowSize({ width: clampedWidth, height: clampedHeight }); +} + // Auto-update management -function startAutoUpdate() { +async function startAutoUpdate() { stopAutoUpdate(); + // Load update interval from settings + const appSettings = await window.electronAPI.getAppSettings(); + const intervalMs = (appSettings.uiUpdateInterval || 300) * 1000; // Convert to milliseconds + updateInterval = setInterval(() => { fetchUsageData(); - }, UPDATE_INTERVAL); + }, intervalMs); } function stopAutoUpdate() { diff --git a/src/renderer/settings-styles.css b/src/renderer/settings-styles.css index b628c18..aaec6a6 100644 --- a/src/renderer/settings-styles.css +++ b/src/renderer/settings-styles.css @@ -95,10 +95,11 @@ body { } .settings-section h2 { - font-size: 16px; - font-weight: 600; - margin-bottom: 16px; + font-size: 20px; + font-weight: 700; + margin-bottom: 20px; color: #e0e0e0; + letter-spacing: 0.3px; } /* 2-Column Grid for Color Pickers */ @@ -233,7 +234,11 @@ body { /* Setting Groups */ .setting-group { - margin-bottom: 25px; + margin-bottom: 24px; +} + +.setting-group:last-child { + margin-bottom: 0; } .setting-group h3 { @@ -308,6 +313,7 @@ body { display: flex; flex-direction: column; gap: 10px; + margin-top: 12px; margin-bottom: 20px; } @@ -436,7 +442,17 @@ body { /* Collapsible Sections */ .collapsible-section { - margin-bottom: 16px; + margin-top: 20px; + margin-bottom: 20px; +} + +.collapsible-section:first-child { + margin-top: 0; +} + +/* Add extra space when a setting-group follows a collapsible-section */ +.collapsible-section + .setting-group { + margin-top: 28px; } .collapsible-header { @@ -487,6 +503,22 @@ body { padding-top: 16px; } +/* Setting Item Spacing */ +.setting-item { + margin-bottom: 12px; +} + +.setting-item:last-child { + margin-bottom: 0; +} + +.setting-item .setting-description { + margin: 4px 0 0 60px; + font-size: 11px; + color: rgba(255, 255, 255, 0.5); + line-height: 1.4; +} + /* Theme Grid */ .theme-grid { display: grid; diff --git a/src/renderer/settings.html b/src/renderer/settings.html index 2b0e60d..327588c 100644 --- a/src/renderer/settings.html +++ b/src/renderer/settings.html @@ -149,6 +149,139 @@ + + +
+

Update Interval

+

How often to refresh usage data (in seconds)

+ +
+ +
+ 30 seconds +
+
+
+ + +
+ +
+
+ +

Hide entire Current Session section

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

Hide entire Weekly Limit section

+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
@@ -282,26 +415,49 @@
- +
- +

Application

- -

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

+
+

Start on System Boot

+

Automatically launch when Windows starts

+ + +
+ +
+

Start Minimized

+

Start hidden in system tray

+ + +
+ +
+

Close to Tray

+

Minimize to tray instead of closing

+ + +
+ + +

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

diff --git a/src/renderer/settings.js b/src/renderer/settings.js index f4b73d6..4f684d0 100644 --- a/src/renderer/settings.js +++ b/src/renderer/settings.js @@ -8,7 +8,6 @@ const elements = { colorDangerEnd: document.getElementById('colorDangerEnd'), resetColorsBtn: document.getElementById('resetColorsBtn'), logoutBtn: document.getElementById('logoutBtn'), - coffeeBtn: document.getElementById('coffeeBtn'), minimizeBtn: document.getElementById('minimizeBtn'), closeBtn: document.getElementById('closeBtn'), // Tray icon elements @@ -45,7 +44,28 @@ const elements = { borderColor: document.getElementById('borderColor'), borderOpacity: document.getElementById('borderOpacity'), borderOpacityValue: document.getElementById('borderOpacityValue'), - resetThemeBtn: document.getElementById('resetThemeBtn') + resetThemeBtn: document.getElementById('resetThemeBtn'), + // App settings + startOnBoot: document.getElementById('startOnBoot'), + startMinimized: document.getElementById('startMinimized'), + closeToTray: document.getElementById('closeToTray'), + uiUpdateInterval: document.getElementById('uiUpdateInterval'), + uiUpdateIntervalValue: document.getElementById('uiUpdateIntervalValue'), + // Section visibility + showSessionSection: document.getElementById('showSessionSection'), + showWeeklySection: document.getElementById('showWeeklySection'), + // Session element visibility + sessionShowLabel: document.getElementById('sessionShowLabel'), + sessionShowBar: document.getElementById('sessionShowBar'), + sessionShowPercentage: document.getElementById('sessionShowPercentage'), + sessionShowCircle: document.getElementById('sessionShowCircle'), + sessionShowTime: document.getElementById('sessionShowTime'), + // Weekly element visibility + weeklyShowLabel: document.getElementById('weeklyShowLabel'), + weeklyShowBar: document.getElementById('weeklyShowBar'), + weeklyShowPercentage: document.getElementById('weeklyShowPercentage'), + weeklyShowCircle: document.getElementById('weeklyShowCircle'), + weeklyShowTime: document.getElementById('weeklyShowTime') }; // Initialize @@ -65,6 +85,14 @@ async function init() { const interval = await window.electronAPI.getTrayUpdateInterval(); loadTrayUpdateInterval(interval); + // Load app settings + const appSettings = await window.electronAPI.getAppSettings(); + loadAppSettings(appSettings); + + // Load UI visibility settings + const uiVisibility = await window.electronAPI.getUIVisibility(); + loadUIVisibility(uiVisibility); + // Load theme settings await loadThemeSettings(); @@ -114,10 +142,6 @@ function setupEventListeners() { 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(); @@ -220,6 +244,35 @@ function setupEventListeners() { // Reset theme button elements.resetThemeBtn.addEventListener('click', resetTheme); + + // App settings event listeners + elements.startOnBoot.addEventListener('change', saveAppSettings); + elements.startMinimized.addEventListener('change', saveAppSettings); + elements.closeToTray.addEventListener('change', saveAppSettings); + + elements.uiUpdateInterval.addEventListener('input', () => { + elements.uiUpdateIntervalValue.textContent = elements.uiUpdateInterval.value; + }); + + elements.uiUpdateInterval.addEventListener('change', saveAppSettings); + + // Section visibility event listeners + elements.showSessionSection.addEventListener('change', saveUIVisibility); + elements.showWeeklySection.addEventListener('change', saveUIVisibility); + + // Session element visibility event listeners + elements.sessionShowLabel.addEventListener('change', saveUIVisibility); + elements.sessionShowBar.addEventListener('change', saveUIVisibility); + elements.sessionShowPercentage.addEventListener('change', saveUIVisibility); + elements.sessionShowCircle.addEventListener('change', saveUIVisibility); + elements.sessionShowTime.addEventListener('change', saveUIVisibility); + + // Weekly element visibility event listeners + elements.weeklyShowLabel.addEventListener('change', saveUIVisibility); + elements.weeklyShowBar.addEventListener('change', saveUIVisibility); + elements.weeklyShowPercentage.addEventListener('change', saveUIVisibility); + elements.weeklyShowCircle.addEventListener('change', saveUIVisibility); + elements.weeklyShowTime.addEventListener('change', saveUIVisibility); } // Helper functions @@ -463,6 +516,69 @@ function applyThemeToSettings(theme) { }); } +// App settings functions +function loadAppSettings(settings) { + elements.startOnBoot.checked = settings.startOnBoot || false; + elements.startMinimized.checked = settings.startMinimized || false; + elements.closeToTray.checked = settings.closeToTray || false; + elements.uiUpdateInterval.value = settings.uiUpdateInterval || 30; + elements.uiUpdateIntervalValue.textContent = settings.uiUpdateInterval || 30; +} + +async function saveAppSettings() { + const settings = { + startOnBoot: elements.startOnBoot.checked, + startMinimized: elements.startMinimized.checked, + closeToTray: elements.closeToTray.checked, + uiUpdateInterval: parseInt(elements.uiUpdateInterval.value) + }; + + await window.electronAPI.setAppSettings(settings); +} + +// UI visibility functions +function loadUIVisibility(visibility) { + // Section visibility + elements.showSessionSection.checked = visibility.showSessionSection !== false; + elements.showWeeklySection.checked = visibility.showWeeklySection !== false; + + // Session elements + elements.sessionShowLabel.checked = visibility.sessionShowLabel !== false; + elements.sessionShowBar.checked = visibility.sessionShowBar !== false; + elements.sessionShowPercentage.checked = visibility.sessionShowPercentage !== false; + elements.sessionShowCircle.checked = visibility.sessionShowCircle !== false; + elements.sessionShowTime.checked = visibility.sessionShowTime !== false; + + // Weekly elements + elements.weeklyShowLabel.checked = visibility.weeklyShowLabel !== false; + elements.weeklyShowBar.checked = visibility.weeklyShowBar !== false; + elements.weeklyShowPercentage.checked = visibility.weeklyShowPercentage !== false; + elements.weeklyShowCircle.checked = visibility.weeklyShowCircle !== false; + elements.weeklyShowTime.checked = visibility.weeklyShowTime !== false; +} + +async function saveUIVisibility() { + const visibility = { + // Section visibility + showSessionSection: elements.showSessionSection.checked, + showWeeklySection: elements.showWeeklySection.checked, + // Session elements + sessionShowLabel: elements.sessionShowLabel.checked, + sessionShowBar: elements.sessionShowBar.checked, + sessionShowPercentage: elements.sessionShowPercentage.checked, + sessionShowCircle: elements.sessionShowCircle.checked, + sessionShowTime: elements.sessionShowTime.checked, + // Weekly elements + weeklyShowLabel: elements.weeklyShowLabel.checked, + weeklyShowBar: elements.weeklyShowBar.checked, + weeklyShowPercentage: elements.weeklyShowPercentage.checked, + weeklyShowCircle: elements.weeklyShowCircle.checked, + weeklyShowTime: elements.weeklyShowTime.checked + }; + + await window.electronAPI.setUIVisibility(visibility); +} + // Setup collapsible sections function setupCollapsibleSections() { const collapsibleHeaders = document.querySelectorAll('.collapsible-header'); diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 1c75ec0..3bef819 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -289,17 +289,25 @@ body { /* Main Content */ .content { padding: 20px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + box-sizing: border-box; } .usage-section { - display: grid; - grid-template-columns: 125px 1fr 45px 100px; - /* Fixed width labels for alignment */ + display: flex; align-items: center; - gap: 12px; - margin-bottom: 2px; - padding: 0 16px; - height: 32px; + justify-content: center; + gap: 16px; + margin-bottom: 8px; + padding: 0 20px; + min-height: 32px; + width: 100%; + max-width: 100%; + box-sizing: border-box; } .usage-section:last-of-type { @@ -331,6 +339,8 @@ body { text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; + flex-shrink: 0; + min-width: 110px; } .usage-percentage { @@ -338,6 +348,8 @@ body { font-size: 13px; font-weight: 700; text-align: right; + flex-shrink: 0; + min-width: 40px; } /* Progress Bar */ @@ -347,7 +359,7 @@ body { border-radius: 3px; overflow: hidden; margin: 0; - /* Remove margin as it is in grid */ + flex: 1; min-width: 80px; } @@ -372,12 +384,10 @@ body { .timer-container { display: flex; align-items: center; - justify-content: space-between; - /* Text on left, chart on right */ + justify-content: flex-end; gap: 8px; - /* space between text and pie chart */ - padding-left: 8px; - /* Alignment adjustment */ + flex-shrink: 0; + min-width: 90px; } .mini-timer {