Files
Claude-Code-Workflow/ccw/src/templates/dashboard-js/components/navigation.js
catlog22 766a8d2145 feat: Enhance global notifications with localStorage persistence and clear functionality
feat: Implement generic modal functions for better UI consistency

feat: Update navigation titles for CLI Tools view

feat: Add JSON formatting for notification details in CLI execution

feat: Introduce localStorage handling for global notification queue

feat: Expand CLI Manager view to include CCW installations with carousel

feat: Add CCW installation management with modal for user interaction

fix: Improve event delegation for explorer tree item interactions

refactor: Clean up CLI Tools section in dashboard HTML

feat: Add functionality to delete CLI execution history by ID
2025-12-11 21:18:28 +08:00

248 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Navigation and Routing
// Manages navigation events, active state, content title updates, search, and path selector
// Path Selector
function initPathSelector() {
const btn = document.getElementById('pathButton');
const menu = document.getElementById('pathMenu');
const recentContainer = document.getElementById('recentPaths');
// Render recent paths
if (recentPaths && recentPaths.length > 0) {
recentPaths.forEach(path => {
const item = document.createElement('div');
item.className = 'path-item' + (path === projectPath ? ' active' : '');
item.dataset.path = path;
// Path text
const pathText = document.createElement('span');
pathText.className = 'path-text';
pathText.textContent = path;
pathText.addEventListener('click', () => selectPath(path));
item.appendChild(pathText);
// Delete button (only for non-current paths)
if (path !== projectPath) {
const deleteBtn = document.createElement('button');
deleteBtn.className = 'path-delete-btn';
deleteBtn.innerHTML = '×';
deleteBtn.title = 'Remove from recent';
deleteBtn.addEventListener('click', async (e) => {
e.stopPropagation();
await removeRecentPathFromList(path);
});
item.appendChild(deleteBtn);
}
recentContainer.appendChild(item);
});
}
btn.addEventListener('click', (e) => {
e.stopPropagation();
menu.classList.toggle('hidden');
});
document.addEventListener('click', () => {
menu.classList.add('hidden');
});
document.getElementById('browsePath').addEventListener('click', async () => {
await browseForFolder();
});
}
// Navigation
function initNavigation() {
document.querySelectorAll('.nav-item[data-filter]').forEach(item => {
item.addEventListener('click', () => {
setActiveNavItem(item);
currentFilter = item.dataset.filter;
currentLiteType = null;
currentView = 'sessions';
currentSessionDetailKey = null;
updateContentTitle();
showStatsAndSearch();
renderSessions();
});
});
// Lite Tasks Navigation
document.querySelectorAll('.nav-item[data-lite]').forEach(item => {
item.addEventListener('click', () => {
setActiveNavItem(item);
currentLiteType = item.dataset.lite;
currentFilter = null;
currentView = 'liteTasks';
currentSessionDetailKey = null;
updateContentTitle();
showStatsAndSearch();
renderLiteTasks();
});
});
// View Navigation (Project Overview, MCP Manager, etc.)
document.querySelectorAll('.nav-item[data-view]').forEach(item => {
item.addEventListener('click', () => {
setActiveNavItem(item);
currentView = item.dataset.view;
currentFilter = null;
currentLiteType = null;
currentSessionDetailKey = null;
updateContentTitle();
// Route to appropriate view
if (currentView === 'mcp-manager') {
renderMcpManager();
} else if (currentView === 'project-overview') {
renderProjectOverview();
} else if (currentView === 'explorer') {
renderExplorer();
}
});
});
}
function setActiveNavItem(item) {
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
}
function updateContentTitle() {
const titleEl = document.getElementById('contentTitle');
if (currentView === 'project-overview') {
titleEl.textContent = 'Project Overview';
} else if (currentView === 'mcp-manager') {
titleEl.textContent = 'MCP Server Management';
} else if (currentView === 'explorer') {
titleEl.textContent = 'File Explorer';
} else if (currentView === 'cli-manager') {
titleEl.textContent = 'CLI Tools & CCW';
} else if (currentView === 'liteTasks') {
const names = { 'lite-plan': 'Lite Plan Sessions', 'lite-fix': 'Lite Fix Sessions' };
titleEl.textContent = names[currentLiteType] || 'Lite Tasks';
} else if (currentView === 'sessionDetail') {
titleEl.textContent = 'Session Detail';
} else if (currentView === 'liteTaskDetail') {
titleEl.textContent = 'Lite Task Detail';
} else {
const names = { 'all': 'All Sessions', 'active': 'Active Sessions', 'archived': 'Archived Sessions' };
titleEl.textContent = names[currentFilter] || 'Sessions';
}
}
// Search
function initSearch() {
const input = document.getElementById('searchInput');
input.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
document.querySelectorAll('.session-card').forEach(card => {
const text = card.textContent.toLowerCase();
card.style.display = text.includes(query) ? '' : 'none';
});
});
}
// Refresh Workspace
function initRefreshButton() {
const btn = document.getElementById('refreshWorkspace');
if (btn) {
btn.addEventListener('click', refreshWorkspace);
}
}
async function refreshWorkspace() {
const btn = document.getElementById('refreshWorkspace');
// Add spinning animation
btn.classList.add('refreshing');
btn.disabled = true;
try {
if (window.SERVER_MODE) {
// Reload data from server
const data = await loadDashboardData(projectPath);
if (data) {
// Clear and repopulate stores
Object.keys(sessionDataStore).forEach(k => delete sessionDataStore[k]);
Object.keys(liteTaskDataStore).forEach(k => delete liteTaskDataStore[k]);
// Populate stores
[...(data.activeSessions || []), ...(data.archivedSessions || [])].forEach(s => {
const sessionKey = `session-${s.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
sessionDataStore[sessionKey] = s;
});
[...(data.liteTasks?.litePlan || []), ...(data.liteTasks?.liteFix || [])].forEach(s => {
const sessionKey = `lite-${s.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
liteTaskDataStore[sessionKey] = s;
});
// Update global data
window.workflowData = data;
// Update sidebar counts
updateSidebarCounts(data);
// Re-render current view
if (currentView === 'sessions') {
renderSessions();
} else if (currentView === 'liteTasks') {
renderLiteTasks();
} else if (currentView === 'sessionDetail' && currentSessionDetailKey) {
showSessionDetailPage(currentSessionDetailKey);
} else if (currentView === 'liteTaskDetail' && currentSessionDetailKey) {
showLiteTaskDetailPage(currentSessionDetailKey);
} else if (currentView === 'project-overview') {
renderProjectOverview();
}
showRefreshToast('Workspace refreshed', 'success');
}
} else {
// Non-server mode: just reload page
window.location.reload();
}
} catch (error) {
console.error('Refresh failed:', error);
showRefreshToast('Refresh failed: ' + error.message, 'error');
} finally {
btn.classList.remove('refreshing');
btn.disabled = false;
}
}
function updateSidebarCounts(data) {
// Update session counts
const activeCount = document.querySelector('.nav-item[data-filter="active"] .nav-count');
const archivedCount = document.querySelector('.nav-item[data-filter="archived"] .nav-count');
const allCount = document.querySelector('.nav-item[data-filter="all"] .nav-count');
if (activeCount) activeCount.textContent = data.activeSessions?.length || 0;
if (archivedCount) archivedCount.textContent = data.archivedSessions?.length || 0;
if (allCount) allCount.textContent = (data.activeSessions?.length || 0) + (data.archivedSessions?.length || 0);
// Update lite task counts
const litePlanCount = document.querySelector('.nav-item[data-lite="lite-plan"] .nav-count');
const liteFixCount = document.querySelector('.nav-item[data-lite="lite-fix"] .nav-count');
if (litePlanCount) litePlanCount.textContent = data.liteTasks?.litePlan?.length || 0;
if (liteFixCount) liteFixCount.textContent = data.liteTasks?.liteFix?.length || 0;
}
function showRefreshToast(message, type) {
// Remove existing toast
const existing = document.querySelector('.status-toast');
if (existing) existing.remove();
const toast = document.createElement('div');
toast.className = `status-toast ${type}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('fade-out');
setTimeout(() => toast.remove(), 300);
}, 2000);
}