Add session filtering (focus mode) to dashboard
This commit is contained in:
parent
057966469d
commit
68dce06e47
1 changed files with 193 additions and 1 deletions
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue