Merge pull request #12 from built-by-as/unread-indicator-appearance
Unread indicator appearance
This commit is contained in:
commit
67f5162c3a
68
renderer.ts
68
renderer.ts
|
|
@ -213,6 +213,9 @@ let mcpServers: McpServer[] = [];
|
||||||
let mcpPollerActive = false;
|
let mcpPollerActive = false;
|
||||||
let terminalSettings: TerminalSettings = { ...DEFAULT_SETTINGS };
|
let terminalSettings: TerminalSettings = { ...DEFAULT_SETTINGS };
|
||||||
|
|
||||||
|
// Track activity timers for each session
|
||||||
|
const activityTimers = new Map<string, NodeJS.Timeout>();
|
||||||
|
|
||||||
function createTerminalUI(sessionId: string) {
|
function createTerminalUI(sessionId: string) {
|
||||||
const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"];
|
const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"];
|
||||||
|
|
||||||
|
|
@ -457,6 +460,7 @@ function addTab(sessionId: string, name: string) {
|
||||||
tab.id = `tab-${sessionId}`;
|
tab.id = `tab-${sessionId}`;
|
||||||
tab.className = "tab";
|
tab.className = "tab";
|
||||||
tab.innerHTML = `
|
tab.innerHTML = `
|
||||||
|
<span class="unread-indicator"></span>
|
||||||
<span class="tab-name">${name}</span>
|
<span class="tab-name">${name}</span>
|
||||||
<button class="tab-close-btn" data-id="${sessionId}">×</button>
|
<button class="tab-close-btn" data-id="${sessionId}">×</button>
|
||||||
`;
|
`;
|
||||||
|
|
@ -498,6 +502,49 @@ function clearUnreadStatus(sessionId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markSessionActivity(sessionId: string) {
|
||||||
|
const session = sessions.get(sessionId);
|
||||||
|
if (!session) return;
|
||||||
|
|
||||||
|
// Add activity indicator to tab
|
||||||
|
const tab = document.getElementById(`tab-${sessionId}`);
|
||||||
|
if (tab) {
|
||||||
|
tab.classList.add("activity");
|
||||||
|
tab.classList.remove("unread");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any existing timer
|
||||||
|
const existingTimer = activityTimers.get(sessionId);
|
||||||
|
if (existingTimer) {
|
||||||
|
clearTimeout(existingTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a new timer to remove activity after 1 second of no output
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
clearActivityStatus(sessionId);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
activityTimers.set(sessionId, timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearActivityStatus(sessionId: string) {
|
||||||
|
const session = sessions.get(sessionId);
|
||||||
|
if (!session) return;
|
||||||
|
|
||||||
|
// Remove activity indicator from tab, but keep unread if it's set
|
||||||
|
const tab = document.getElementById(`tab-${sessionId}`);
|
||||||
|
if (tab) {
|
||||||
|
tab.classList.remove("activity");
|
||||||
|
// If there's no unread status, transition to unread after activity ends
|
||||||
|
if (!tab.classList.contains("unread") && activeSessionId !== sessionId) {
|
||||||
|
tab.classList.add("unread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the timer
|
||||||
|
activityTimers.delete(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
function switchToSession(sessionId: string) {
|
function switchToSession(sessionId: string) {
|
||||||
// Hide all sessions
|
// Hide all sessions
|
||||||
sessions.forEach((session, id) => {
|
sessions.forEach((session, id) => {
|
||||||
|
|
@ -529,8 +576,9 @@ function switchToSession(sessionId: string) {
|
||||||
// Load MCP servers for this session
|
// Load MCP servers for this session
|
||||||
loadMcpServers();
|
loadMcpServers();
|
||||||
|
|
||||||
// Clear unread status when switching to this session
|
// Clear unread and activity status when switching to this session
|
||||||
clearUnreadStatus(sessionId);
|
clearUnreadStatus(sessionId);
|
||||||
|
clearActivityStatus(sessionId);
|
||||||
|
|
||||||
// Focus and resize
|
// Focus and resize
|
||||||
session.terminal.focus();
|
session.terminal.focus();
|
||||||
|
|
@ -633,11 +681,25 @@ ipcRenderer.on("session-output", (_event, sessionId: string, data: string) => {
|
||||||
|
|
||||||
session.terminal.write(filteredData);
|
session.terminal.write(filteredData);
|
||||||
|
|
||||||
// Only mark as unread if this is not the active session
|
// Only mark as unread/activity if this is not the active session
|
||||||
if (activeSessionId !== sessionId && session.hasActivePty) {
|
if (activeSessionId !== sessionId && session.hasActivePty) {
|
||||||
|
// Show activity spinner while output is coming in
|
||||||
|
markSessionActivity(sessionId);
|
||||||
|
|
||||||
// Check if Claude session is ready for input
|
// Check if Claude session is ready for input
|
||||||
if (isClaudeSessionReady(filteredData)) {
|
if (isClaudeSessionReady(filteredData)) {
|
||||||
markSessionAsUnread(sessionId);
|
// Clear activity timer and set unread
|
||||||
|
const existingTimer = activityTimers.get(sessionId);
|
||||||
|
if (existingTimer) {
|
||||||
|
clearTimeout(existingTimer);
|
||||||
|
activityTimers.delete(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tab = document.getElementById(`tab-${sessionId}`);
|
||||||
|
if (tab) {
|
||||||
|
tab.classList.remove("activity");
|
||||||
|
tab.classList.add("unread");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
styles.css
26
styles.css
|
|
@ -113,15 +113,25 @@
|
||||||
@apply bg-gray-800 border-b-2 border-blue-500;
|
@apply bg-gray-800 border-b-2 border-blue-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab.unread .tab-name::after {
|
.unread-indicator {
|
||||||
content: '';
|
display: none;
|
||||||
display: inline-block;
|
width: 10px;
|
||||||
width: 6px;
|
height: 10px;
|
||||||
height: 6px;
|
|
||||||
background-color: #3b82f6;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-left: 6px;
|
margin-right: 6px;
|
||||||
vertical-align: middle;
|
}
|
||||||
|
|
||||||
|
.tab.unread .unread-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.activity .unread-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
border: 2px solid #eab308;
|
||||||
|
border-top-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-name {
|
.tab-name {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue