feat(dashboard): unify icons with Lucide Icons library

- Introduce Lucide Icons via CDN for consistent SVG icons
- Replace emoji icons with Lucide SVG icons in sidebar navigation
- Fix Sessions/Explorer icon confusion (📁/📂 → history/folder-tree)
- Update top bar icons (logo, theme toggle, search, refresh)
- Update stats section icons with colored Lucide icons
- Add icon animations support (animate-spin for loading states)
- Update Explorer view with Lucide folder/file icons
- Support dark/light theme icon adaptation

Icon mapping:
- Explorer: folder-tree (was 📂)
- Sessions: history (was 📁)
- Overview: bar-chart-3
- Active: play-circle
- Archived: archive
- Lite Plan: file-edit
- Lite Fix: wrench
- MCP Servers: plug
- Hooks: webhook
This commit is contained in:
catlog22
2025-12-08 22:58:42 +08:00
parent 818d9f3f5d
commit 5f31c9ad7e
9 changed files with 2882 additions and 27 deletions

View File

@@ -0,0 +1,219 @@
// ==========================================
// GLOBAL NOTIFICATION SYSTEM
// ==========================================
// Floating notification panel accessible from any view
/**
* Initialize global notification panel
*/
function initGlobalNotifications() {
// Create FAB and panel if not exists
if (!document.getElementById('globalNotificationFab')) {
const fabHtml = `
<div class="global-notif-fab" id="globalNotificationFab" onclick="toggleGlobalNotifications()" title="Notifications">
<span class="fab-icon">🔔</span>
<span class="fab-badge" id="globalNotifBadge">0</span>
</div>
<div class="global-notif-panel" id="globalNotificationPanel">
<div class="global-notif-header">
<span class="global-notif-title">🔔 Notifications</span>
<button class="global-notif-close" onclick="toggleGlobalNotifications()">×</button>
</div>
<div class="global-notif-actions">
<button class="notif-action-btn" onclick="clearGlobalNotifications()">
<span>🗑️</span> Clear All
</button>
</div>
<div class="global-notif-list" id="globalNotificationList">
<div class="global-notif-empty">
<span>No notifications</span>
<p>System events and task updates will appear here</p>
</div>
</div>
</div>
`;
const container = document.createElement('div');
container.id = 'globalNotificationContainer';
container.innerHTML = fabHtml;
document.body.appendChild(container);
}
renderGlobalNotifications();
}
/**
* Toggle notification panel visibility
*/
function toggleGlobalNotifications() {
isNotificationPanelVisible = !isNotificationPanelVisible;
const panel = document.getElementById('globalNotificationPanel');
const fab = document.getElementById('globalNotificationFab');
if (panel && fab) {
if (isNotificationPanelVisible) {
panel.classList.add('show');
fab.classList.add('active');
} else {
panel.classList.remove('show');
fab.classList.remove('active');
}
}
}
/**
* Add a global notification
* @param {string} type - 'info', 'success', 'warning', 'error'
* @param {string} message - Main notification message
* @param {string} details - Optional details
* @param {string} source - Optional source identifier (e.g., 'explorer', 'mcp')
*/
function addGlobalNotification(type, message, details = null, source = null) {
const notification = {
id: Date.now(),
type,
message,
details,
source,
timestamp: new Date().toISOString(),
read: false
};
globalNotificationQueue.unshift(notification);
// Keep only last 100 notifications
if (globalNotificationQueue.length > 100) {
globalNotificationQueue = globalNotificationQueue.slice(0, 100);
}
renderGlobalNotifications();
updateGlobalNotifBadge();
// Show toast for important notifications
if (type === 'error' || type === 'success') {
showNotificationToast(notification);
}
}
/**
* Show a brief toast notification
*/
function showNotificationToast(notification) {
const typeIcon = {
'info': '',
'success': '✅',
'warning': '⚠️',
'error': '❌'
}[notification.type] || '';
// Remove existing toast
const existing = document.querySelector('.notif-toast');
if (existing) existing.remove();
const toast = document.createElement('div');
toast.className = `notif-toast type-${notification.type}`;
toast.innerHTML = `
<span class="toast-icon">${typeIcon}</span>
<span class="toast-message">${escapeHtml(notification.message)}</span>
`;
document.body.appendChild(toast);
// Animate in
requestAnimationFrame(() => toast.classList.add('show'));
// Auto-remove
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
/**
* Render notification list
*/
function renderGlobalNotifications() {
const listEl = document.getElementById('globalNotificationList');
if (!listEl) return;
if (globalNotificationQueue.length === 0) {
listEl.innerHTML = `
<div class="global-notif-empty">
<span>No notifications</span>
<p>System events and task updates will appear here</p>
</div>
`;
return;
}
listEl.innerHTML = globalNotificationQueue.map(notif => {
const typeIcon = {
'info': '',
'success': '✅',
'warning': '⚠️',
'error': '❌'
}[notif.type] || '';
const time = formatNotifTime(notif.timestamp);
const sourceLabel = notif.source ? `<span class="notif-source">${notif.source}</span>` : '';
return `
<div class="global-notif-item type-${notif.type} ${notif.read ? 'read' : ''}" data-id="${notif.id}">
<div class="notif-item-header">
<span class="notif-icon">${typeIcon}</span>
<span class="notif-message">${escapeHtml(notif.message)}</span>
${sourceLabel}
</div>
${notif.details ? `<div class="notif-details">${escapeHtml(notif.details)}</div>` : ''}
<div class="notif-meta">
<span class="notif-time">${time}</span>
</div>
</div>
`;
}).join('');
}
/**
* Format notification time
*/
function formatNotifTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) return 'Just now';
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
return date.toLocaleDateString();
}
/**
* Update notification badge
*/
function updateGlobalNotifBadge() {
const badge = document.getElementById('globalNotifBadge');
if (badge) {
const unreadCount = globalNotificationQueue.filter(n => !n.read).length;
badge.textContent = unreadCount;
badge.style.display = unreadCount > 0 ? 'flex' : 'none';
}
}
/**
* Clear all notifications
*/
function clearGlobalNotifications() {
globalNotificationQueue = [];
renderGlobalNotifications();
updateGlobalNotifBadge();
}
/**
* Mark all as read
*/
function markAllNotificationsRead() {
globalNotificationQueue.forEach(n => n.read = true);
renderGlobalNotifications();
updateGlobalNotifBadge();
}

View File

@@ -96,6 +96,8 @@ function initNavigation() {
renderMcpManager();
} else if (currentView === 'project-overview') {
renderProjectOverview();
} else if (currentView === 'explorer') {
renderExplorer();
}
});
});
@@ -112,6 +114,8 @@ function updateContentTitle() {
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 === 'liteTasks') {
const names = { 'lite-plan': 'Lite Plan Sessions', 'lite-fix': 'Lite Fix Sessions' };
titleEl.textContent = names[currentLiteType] || 'Lite Tasks';

View File

@@ -6,6 +6,7 @@ function initTheme() {
const saved = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', saved);
updateThemeIcon(saved);
updateHljsTheme(saved);
document.getElementById('themeToggle').addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
@@ -13,9 +14,36 @@ function initTheme() {
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
updateThemeIcon(next);
updateHljsTheme(next);
});
}
function updateThemeIcon(theme) {
document.getElementById('themeToggle').textContent = theme === 'light' ? '🌙' : '☀️';
const darkIcon = document.querySelector('.theme-icon-dark');
const lightIcon = document.querySelector('.theme-icon-light');
if (darkIcon && lightIcon) {
if (theme === 'light') {
darkIcon.classList.remove('hidden');
lightIcon.classList.add('hidden');
} else {
darkIcon.classList.add('hidden');
lightIcon.classList.remove('hidden');
}
}
}
function updateHljsTheme(theme) {
// Toggle highlight.js theme stylesheets
const darkTheme = document.getElementById('hljs-theme-dark');
const lightTheme = document.getElementById('hljs-theme-light');
if (darkTheme && lightTheme) {
if (theme === 'dark') {
darkTheme.disabled = false;
lightTheme.disabled = true;
} else {
darkTheme.disabled = true;
lightTheme.disabled = false;
}
}
}