Add session filtering (focus mode) to dashboard

This commit is contained in:
Jared Miller 2026-01-28 15:13:05 -05:00
parent 057966469d
commit 68dce06e47
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -664,6 +664,95 @@
font-weight: 600;
}
.popover {
position: fixed;
top: 60px;
right: 200px;
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1500;
min-width: 300px;
max-width: 400px;
}
.popover-content {
padding: 0;
}
.popover-header {
padding: 12px 16px;
border-bottom: 1px solid #3a3a3a;
font-size: 14px;
font-weight: 600;
color: #e0e0e0;
}
#session-filter-list {
max-height: 400px;
overflow-y: auto;
}
.session-filter-item {
padding: 12px 16px;
border-bottom: 1px solid #3a3a3a;
cursor: pointer;
transition: background 0.2s;
}
.session-filter-item:last-child {
border-bottom: none;
}
.session-filter-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.session-filter-item.selected {
background: rgba(255, 152, 0, 0.1);
border-left: 3px solid #ff9800;
padding-left: 13px;
}
.session-filter-command {
font-weight: 600;
font-size: 13px;
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.session-filter-cwd {
font-size: 11px;
color: #888;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.clear-filter-btn {
width: 100%;
padding: 12px 16px;
border: none;
border-top: 1px solid #3a3a3a;
background: #1a1a1a;
color: #e0e0e0;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.clear-filter-btn:hover {
background: rgba(255, 255, 255, 0.05);
}
.clear-filter-btn:active {
opacity: 0.7;
}
@media (prefers-color-scheme: light) {
body {
background: #f5f5f5;
@ -742,6 +831,9 @@
<header>
<h1>claude-remote</h1>
<div style="display: flex; align-items: center; gap: 16px;">
<button class="settings-btn" id="sessions-filter-btn" onclick="openSessionFilter()" title="Session Filter" style="font-size: 14px; font-weight: 500; min-width: auto; padding: 8px 12px;">
<span id="sessions-count-text">0 sessions</span>
</button>
<button class="settings-btn" onclick="openStyleModal()" title="Display Settings">⚙️</button>
<div class="status" id="status">
<span class="status-dot"></span>
@ -750,6 +842,14 @@
</div>
</header>
<div id="session-filter-popover" class="popover" style="display: none;">
<div class="popover-content">
<div class="popover-header">Select Session</div>
<div id="session-filter-list"></div>
<button class="clear-filter-btn" onclick="clearSessionFilter()">Show All Sessions</button>
</div>
</div>
<div class="container">
<div class="section">
<div class="section-title">Active Sessions</div>
@ -1145,12 +1245,29 @@
}
function renderSessions() {
// Update sessions count button text
updateSessionsCountButton();
if (state.sessions.size === 0) {
$sessions.innerHTML = '<div class="empty-state">No active sessions</div>';
return;
}
$sessions.innerHTML = Array.from(state.sessions.values()).map(s => `
// Filter sessions if focusSessionId is set
let sessionsToRender = Array.from(state.sessions.values());
if (state.settings.focusSessionId !== null) {
const focusSession = state.sessions.get(state.settings.focusSessionId);
if (focusSession) {
sessionsToRender = [focusSession];
} else {
// Filtered session no longer exists, clear filter
state.settings.focusSessionId = null;
saveSettings();
updateSessionsCountButton();
}
}
$sessions.innerHTML = sessionsToRender.map(s => `
<div class="session ${s.expanded ? 'expanded' : ''}" data-session="${s.id}">
<div class="session-header" onclick="toggleSession('${s.id}')">
<div class="session-info">
@ -1723,6 +1840,81 @@
updateSettingsUI();
};
// Session filtering
function updateSessionsCountButton() {
const $countText = document.getElementById('sessions-count-text');
const total = state.sessions.size;
if (state.settings.focusSessionId !== null) {
$countText.innerHTML = `Viewing 1 of ${total} <span style="margin-left: 4px;">×</span>`;
} else {
const label = total === 1 ? 'session' : 'sessions';
$countText.textContent = `${total} ${label}`;
}
}
window.openSessionFilter = () => {
renderSessionFilterList();
const $popover = document.getElementById('session-filter-popover');
$popover.style.display = 'block';
// Close on outside click
setTimeout(() => {
document.addEventListener('click', closeSessionFilterOnOutsideClick);
}, 0);
};
window.closeSessionFilter = () => {
const $popover = document.getElementById('session-filter-popover');
$popover.style.display = 'none';
document.removeEventListener('click', closeSessionFilterOnOutsideClick);
};
function closeSessionFilterOnOutsideClick(event) {
const $popover = document.getElementById('session-filter-popover');
const $filterBtn = document.getElementById('sessions-filter-btn');
if (!$popover.contains(event.target) && !$filterBtn.contains(event.target)) {
closeSessionFilter();
}
}
window.setSessionFilter = (sessionId) => {
state.settings.focusSessionId = Number(sessionId);
saveSettings();
closeSessionFilter();
renderSessions();
};
window.clearSessionFilter = () => {
state.settings.focusSessionId = null;
saveSettings();
closeSessionFilter();
renderSessions();
};
function renderSessionFilterList() {
const $list = document.getElementById('session-filter-list');
if (state.sessions.size === 0) {
$list.innerHTML = '<div style="padding: 16px; text-align: center; color: #888;">No active sessions</div>';
return;
}
$list.innerHTML = Array.from(state.sessions.values()).map(s => {
const isSelected = state.settings.focusSessionId === s.id;
return `
<div class="session-filter-item ${isSelected ? 'selected' : ''}" onclick="setSessionFilter(${s.id})">
<div class="session-filter-command">
<span>${escapeHtml(s.command || 'claude')}</span>
${renderStateBadge(s.state || 'ready')}
</div>
<div class="session-filter-cwd">${escapeHtml(s.cwd || '~')}</div>
</div>
`;
}).join('');
}
// Initialize
loadSettings();
applySettings();