mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 21:49:42 +02:00
feat: add settings gear icon with app settings modal
- Add gear icon button in header-right for app settings - Create App Settings modal with fields for: - Default CLAUDE.md template path - Default working directory - Settings persist to ~/.claudeman/settings.json - Add GET/PUT /api/settings endpoints - Keep "+" tab for creating new sessions (useful UX) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+70
-1
@@ -477,7 +477,7 @@ class ClaudemanApp {
|
||||
`;
|
||||
}
|
||||
|
||||
// Add the "+" button
|
||||
// Add the "+" button at the end of session tabs
|
||||
html += `
|
||||
<div class="session-tab new-tab" onclick="app.createNewSession()" title="New Session (Ctrl+N)">
|
||||
<span>+</span>
|
||||
@@ -1298,6 +1298,74 @@ class ClaudemanApp {
|
||||
});
|
||||
}
|
||||
|
||||
// ========== App Settings Modal ==========
|
||||
|
||||
openAppSettings() {
|
||||
// Load current settings
|
||||
const settings = this.loadAppSettingsFromStorage();
|
||||
document.getElementById('appSettingsClaudeMdPath').value = settings.defaultClaudeMdPath || '';
|
||||
document.getElementById('appSettingsDefaultDir').value = settings.defaultWorkingDir || '';
|
||||
document.getElementById('appSettingsModal').classList.add('active');
|
||||
}
|
||||
|
||||
closeAppSettings() {
|
||||
document.getElementById('appSettingsModal').classList.remove('active');
|
||||
}
|
||||
|
||||
async saveAppSettings() {
|
||||
const settings = {
|
||||
defaultClaudeMdPath: document.getElementById('appSettingsClaudeMdPath').value.trim(),
|
||||
defaultWorkingDir: document.getElementById('appSettingsDefaultDir').value.trim(),
|
||||
};
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(settings));
|
||||
|
||||
// Also save to server
|
||||
try {
|
||||
await fetch('/api/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
this.showToast('Settings saved', 'success');
|
||||
} catch (err) {
|
||||
// Server save failed but localStorage succeeded
|
||||
this.showToast('Settings saved locally', 'warning');
|
||||
}
|
||||
|
||||
this.closeAppSettings();
|
||||
}
|
||||
|
||||
loadAppSettingsFromStorage() {
|
||||
try {
|
||||
const saved = localStorage.getItem('claudeman-app-settings');
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load app settings:', err);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async loadAppSettingsFromServer() {
|
||||
try {
|
||||
const res = await fetch('/api/settings');
|
||||
if (res.ok) {
|
||||
const settings = await res.json();
|
||||
// Merge with localStorage (server takes precedence)
|
||||
const localSettings = this.loadAppSettingsFromStorage();
|
||||
const merged = { ...localSettings, ...settings };
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(merged));
|
||||
return merged;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load settings from server:', err);
|
||||
}
|
||||
return this.loadAppSettingsFromStorage();
|
||||
}
|
||||
|
||||
// ========== Help Modal ==========
|
||||
|
||||
showHelp() {
|
||||
@@ -1310,6 +1378,7 @@ class ClaudemanApp {
|
||||
|
||||
closeAllPanels() {
|
||||
this.closeSessionOptions();
|
||||
this.closeAppSettings();
|
||||
document.getElementById('respawnPanel').classList.remove('open');
|
||||
document.getElementById('newSessionPanel').classList.remove('open');
|
||||
document.getElementById('taskPanel').classList.remove('open');
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<span class="status-text">Connecting</span>
|
||||
</div>
|
||||
<div class="header-tokens" id="headerTokens" title="Total tokens across all sessions">0 tokens</div>
|
||||
<button class="btn-icon-header" onclick="app.openAppSettings()" title="App Settings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -272,6 +273,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Settings Modal -->
|
||||
<div class="modal" id="appSettingsModal">
|
||||
<div class="modal-backdrop" onclick="app.closeAppSettings()"></div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>App Settings</h3>
|
||||
<button class="modal-close" onclick="app.closeAppSettings()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-row">
|
||||
<label>Default CLAUDE.md Template Path</label>
|
||||
<input type="text" id="appSettingsClaudeMdPath" placeholder="/path/to/CLAUDE.md template">
|
||||
<span class="form-hint">Used when creating new cases. Leave empty for built-in template.</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Default Working Directory</label>
|
||||
<input type="text" id="appSettingsDefaultDir" placeholder="~/projects">
|
||||
<span class="form-hint">Default directory for new sessions.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn-toolbar" onclick="app.closeAppSettings()">Cancel</button>
|
||||
<button class="btn-toolbar btn-primary" onclick="app.saveAppSettings()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Button -->
|
||||
<button class="help-btn" onclick="app.showHelp()" title="Keyboard shortcuts (Ctrl+?)">?</button>
|
||||
</div>
|
||||
|
||||
@@ -187,6 +187,27 @@ body {
|
||||
font-family: 'SF Mono', Monaco, monospace;
|
||||
}
|
||||
|
||||
.btn-icon-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-dim);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-icon-header:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* Session tab gear icon */
|
||||
.session-tab .tab-gear {
|
||||
opacity: 0;
|
||||
|
||||
+31
-1
@@ -2,7 +2,7 @@ import Fastify, { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
|
||||
import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { Session, ClaudeMessage, type BackgroundTask } from '../session.js';
|
||||
@@ -650,6 +650,36 @@ export class WebServer extends EventEmitter {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
});
|
||||
|
||||
// ============ App Settings Endpoints ============
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
|
||||
this.app.get('/api/settings', async () => {
|
||||
try {
|
||||
if (existsSync(settingsPath)) {
|
||||
const content = readFileSync(settingsPath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to read settings:', err);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
this.app.put('/api/settings', async (req) => {
|
||||
const settings = req.body as { defaultClaudeMdPath?: string; defaultWorkingDir?: string };
|
||||
|
||||
try {
|
||||
const dir = dirname(settingsPath);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return { error: (err as Error).message };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupSessionListeners(session: Session): void {
|
||||
|
||||
Reference in New Issue
Block a user