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;
|
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) {
|
@media (prefers-color-scheme: light) {
|
||||||
body {
|
body {
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
|
@ -742,6 +831,9 @@
|
||||||
<header>
|
<header>
|
||||||
<h1>claude-remote</h1>
|
<h1>claude-remote</h1>
|
||||||
<div style="display: flex; align-items: center; gap: 16px;">
|
<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>
|
<button class="settings-btn" onclick="openStyleModal()" title="Display Settings">⚙️</button>
|
||||||
<div class="status" id="status">
|
<div class="status" id="status">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
|
|
@ -750,6 +842,14 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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="container">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-title">Active Sessions</div>
|
<div class="section-title">Active Sessions</div>
|
||||||
|
|
@ -1145,12 +1245,29 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSessions() {
|
function renderSessions() {
|
||||||
|
// Update sessions count button text
|
||||||
|
updateSessionsCountButton();
|
||||||
|
|
||||||
if (state.sessions.size === 0) {
|
if (state.sessions.size === 0) {
|
||||||
$sessions.innerHTML = '<div class="empty-state">No active sessions</div>';
|
$sessions.innerHTML = '<div class="empty-state">No active sessions</div>';
|
||||||
return;
|
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 ${s.expanded ? 'expanded' : ''}" data-session="${s.id}">
|
||||||
<div class="session-header" onclick="toggleSession('${s.id}')">
|
<div class="session-header" onclick="toggleSession('${s.id}')">
|
||||||
<div class="session-info">
|
<div class="session-info">
|
||||||
|
|
@ -1723,6 +1840,81 @@
|
||||||
updateSettingsUI();
|
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
|
// Initialize
|
||||||
loadSettings();
|
loadSettings();
|
||||||
applySettings();
|
applySettings();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue