Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
755
admin-ui/js/components/layout/ds-shell.js
Normal file
755
admin-ui/js/components/layout/ds-shell.js
Normal file
@@ -0,0 +1,755 @@
|
||||
/**
|
||||
* ds-shell.js
|
||||
* Main shell component - provides IDE-style grid layout
|
||||
* MVP2: Integrated with AdminStore and ProjectStore for settings and project management
|
||||
*/
|
||||
|
||||
import './ds-activity-bar.js';
|
||||
import './ds-panel.js';
|
||||
import './ds-project-selector.js';
|
||||
import './ds-ai-chat-sidebar.js';
|
||||
import '../admin/ds-user-settings.js'; // Import settings component for direct instantiation
|
||||
import '../ds-notification-center.js'; // Notification center component
|
||||
import router from '../../core/router.js'; // Import Router for new architecture
|
||||
import layoutManager from '../../core/layout-manager.js';
|
||||
import toolBridge from '../../services/tool-bridge.js';
|
||||
import contextStore from '../../stores/context-store.js';
|
||||
import notificationService from '../../services/notification-service.js';
|
||||
import { useAdminStore } from '../../stores/admin-store.js';
|
||||
import { useProjectStore } from '../../stores/project-store.js';
|
||||
import { useUserStore } from '../../stores/user-store.js';
|
||||
import '../../config/component-registry.js'; // Ensure all panel components are loaded
|
||||
import { authReady } from '../../utils/demo-auth-init.js'; // Auth initialization promise
|
||||
|
||||
class DSShell extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.currentTeam = 'ui'; // Default team
|
||||
this.currentWorkdesk = null;
|
||||
this.browserInitialized = false;
|
||||
this.currentView = 'workdesk'; // Can be 'workdesk' or 'settings'
|
||||
|
||||
// MVP2: Initialize stores
|
||||
this.adminStore = useAdminStore();
|
||||
this.projectStore = useProjectStore();
|
||||
this.userStore = useUserStore();
|
||||
|
||||
// Bind event handlers to avoid memory leaks
|
||||
this.handleHashChangeBound = this.handleHashChange.bind(this);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
// Render UI immediately (non-blocking)
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Initialize layout manager
|
||||
layoutManager.init(this);
|
||||
|
||||
// Initialize Router (NEW - Phase 1 Architecture)
|
||||
router.init();
|
||||
|
||||
// Wait for authentication to complete before making API calls
|
||||
console.log('[DSShell] Waiting for authentication...');
|
||||
const authResult = await authReady;
|
||||
console.log('[DSShell] Authentication complete:', authResult);
|
||||
|
||||
// MVP2: Initialize store subscriptions (now safe to make API calls)
|
||||
this.initializeStoreSubscriptions();
|
||||
|
||||
// Initialize notification service
|
||||
notificationService.init();
|
||||
|
||||
// Set initial active link
|
||||
this.updateActiveLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup when component is removed from DOM (prevents memory leaks)
|
||||
*/
|
||||
disconnectedCallback() {
|
||||
// Remove event listener to prevent memory leak
|
||||
window.removeEventListener('hashchange', this.handleHashChangeBound);
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Setup store subscriptions to keep context in sync
|
||||
*/
|
||||
initializeStoreSubscriptions() {
|
||||
// Subscribe to admin settings changes
|
||||
this.adminStore.subscribe(() => {
|
||||
const settings = this.adminStore.getState();
|
||||
contextStore.updateAdminSettings({
|
||||
hostname: settings.hostname,
|
||||
port: settings.port,
|
||||
isRemote: settings.isRemote,
|
||||
dssSetupType: settings.dssSetupType
|
||||
});
|
||||
console.log('[DSShell] Admin settings updated:', settings);
|
||||
});
|
||||
|
||||
// Subscribe to project changes
|
||||
this.projectStore.subscribe(() => {
|
||||
const currentProject = this.projectStore.getCurrentProject();
|
||||
if (currentProject) {
|
||||
contextStore.setCurrentProject(currentProject);
|
||||
console.log('[DSShell] Project context updated:', currentProject);
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial project context
|
||||
const currentProject = this.projectStore.getCurrentProject();
|
||||
if (currentProject) {
|
||||
contextStore.setCurrentProject(currentProject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize browser automation (required for DevTools components)
|
||||
*/
|
||||
async initializeBrowser() {
|
||||
if (this.browserInitialized) {
|
||||
console.log('[DSShell] Browser already initialized');
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('[DSShell] Browser init temporarily disabled - not critical for development');
|
||||
this.browserInitialized = true; // Mark as initialized to skip
|
||||
return true;
|
||||
|
||||
/* DISABLED - MCP browser tools not available yet
|
||||
try {
|
||||
await toolBridge.executeTool('browser_init', {
|
||||
mode: 'remote',
|
||||
url: window.location.origin
|
||||
});
|
||||
|
||||
this.browserInitialized = true;
|
||||
console.log('[DSShell] Browser automation initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[DSShell] Failed to initialize browser:', error);
|
||||
this.browserInitialized = false;
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<ds-sidebar>
|
||||
<div class="sidebar-header" style="display: flex; flex-direction: column; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--vscode-border);">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 24px; font-weight: 700;">⬡</span>
|
||||
<div>
|
||||
<div style="font-size: 13px; font-weight: 700; color: var(--vscode-text);">DSS</div>
|
||||
<div style="font-size: 10px; color: var(--vscode-text-dim); line-height: 1.2;">Design System Studio</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NEW: Feature Module Navigation -->
|
||||
<div class="sidebar-content">
|
||||
<nav class="module-nav" style="display: flex; flex-direction: column; gap: 4px; padding-top: 12px;">
|
||||
<a href="#projects" class="nav-item" data-path="projects" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; color: var(--vscode-text-dim); text-decoration: none; border-radius: 4px; transition: all 0.1s; font-size: 13px;">
|
||||
<span style="font-size: 16px;">📁</span> Projects
|
||||
</a>
|
||||
<a href="#config" class="nav-item" data-path="config" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; color: var(--vscode-text-dim); text-decoration: none; border-radius: 4px; transition: all 0.1s; font-size: 13px;">
|
||||
<span style="font-size: 16px;">⚙️</span> Configuration
|
||||
</a>
|
||||
<a href="#components" class="nav-item" data-path="components" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; color: var(--vscode-text-dim); text-decoration: none; border-radius: 4px; transition: all 0.1s; font-size: 13px;">
|
||||
<span style="font-size: 16px;">🧩</span> Components
|
||||
</a>
|
||||
<a href="#translations" class="nav-item" data-path="translations" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; color: var(--vscode-text-dim); text-decoration: none; border-radius: 4px; transition: all 0.1s; font-size: 13px;">
|
||||
<span style="font-size: 16px;">🔄</span> Translations
|
||||
</a>
|
||||
<a href="#discovery" class="nav-item" data-path="discovery" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; color: var(--vscode-text-dim); text-decoration: none; border-radius: 4px; transition: all 0.1s; font-size: 13px;">
|
||||
<span style="font-size: 16px;">🔍</span> Discovery
|
||||
</a>
|
||||
<div style="height: 1px; background: var(--vscode-border); margin: 8px 0;"></div>
|
||||
<a href="#admin" class="nav-item" data-path="admin" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; color: var(--vscode-text-dim); text-decoration: none; border-radius: 4px; transition: all 0.1s; font-size: 13px;">
|
||||
<span style="font-size: 16px;">👤</span> Admin
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</ds-sidebar>
|
||||
|
||||
<ds-stage>
|
||||
<div class="stage-header" style="display: flex; justify-content: space-between; align-items: center; padding: 0 16px; border-bottom: 1px solid var(--vscode-border); background: var(--vscode-bg); min-height: 44px;">
|
||||
<div class="stage-header-left" style="display: flex; align-items: center; gap: 12px;">
|
||||
<!-- Hamburger Menu (Mobile) -->
|
||||
<button id="hamburger-menu" class="hamburger-menu" style="display: none; padding: 6px 8px; background: transparent; border: none; color: var(--vscode-text-dim); cursor: pointer; font-size: 20px;" aria-label="Toggle sidebar">☰</button>
|
||||
|
||||
<!-- NEW: Project Selector -->
|
||||
<ds-project-selector></ds-project-selector>
|
||||
</div>
|
||||
<div class="stage-header-right" id="stage-actions" style="display: flex; align-items: center; gap: 8px;">
|
||||
<!-- Action buttons will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-content">
|
||||
<div id="stage-workdesk-content" style="height: 100%; overflow: auto;">
|
||||
<!-- Dynamic Module Content via Router -->
|
||||
</div>
|
||||
</div>
|
||||
</ds-stage>
|
||||
|
||||
<ds-ai-chat-sidebar></ds-ai-chat-sidebar>
|
||||
`;
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Setup hamburger menu for mobile
|
||||
this.setupMobileMenu();
|
||||
|
||||
// Setup navigation highlight for new module nav
|
||||
this.setupNavigationHighlight();
|
||||
|
||||
// Populate stage-header-right with action buttons
|
||||
const stageActions = this.querySelector('#stage-actions');
|
||||
if (stageActions && stageActions.children.length === 0) {
|
||||
stageActions.innerHTML = `
|
||||
<button id="chat-toggle-btn" aria-label="Toggle AI Chat sidebar" aria-pressed="false" style="
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-text-dim);
|
||||
cursor: pointer;
|
||||
padding: 6px 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.1s;
|
||||
" title="Toggle Chat (💬)">💬</button>
|
||||
|
||||
<button id="advanced-mode-btn" aria-label="Toggle Advanced Mode" aria-pressed="false" style="
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-text-dim);
|
||||
cursor: pointer;
|
||||
padding: 6px 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.1s;
|
||||
" title="Advanced Mode (🔧)">🔧</button>
|
||||
|
||||
<div style="position: relative;">
|
||||
<button id="notification-toggle-btn" aria-label="Notifications" style="
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-text-dim);
|
||||
cursor: pointer;
|
||||
padding: 6px 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.1s;
|
||||
position: relative;
|
||||
" title="Notifications (🔔)">🔔
|
||||
<span id="notification-indicator" style="
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--vscode-accent);
|
||||
border-radius: 50%;
|
||||
display: none;
|
||||
"></span>
|
||||
</button>
|
||||
<ds-notification-center></ds-notification-center>
|
||||
</div>
|
||||
|
||||
<button id="settings-btn" aria-label="Open Settings" style="
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-text-dim);
|
||||
cursor: pointer;
|
||||
padding: 6px 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.1s;
|
||||
" title="Settings (⚙️)">⚙️</button>
|
||||
`;
|
||||
|
||||
// Add event listeners to stage-header action buttons
|
||||
const chatToggleBtn = this.querySelector('#chat-toggle-btn');
|
||||
if (chatToggleBtn) {
|
||||
chatToggleBtn.addEventListener('click', () => {
|
||||
const chatSidebar = this.querySelector('ds-ai-chat-sidebar');
|
||||
if (chatSidebar && chatSidebar.toggleCollapse) {
|
||||
chatSidebar.toggleCollapse();
|
||||
const pressed = chatSidebar.isCollapsed ? 'false' : 'true';
|
||||
chatToggleBtn.setAttribute('aria-pressed', pressed);
|
||||
}
|
||||
});
|
||||
chatToggleBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
chatToggleBtn.click();
|
||||
} else if (e.key === 'Escape') {
|
||||
const chatSidebar = this.querySelector('ds-ai-chat-sidebar');
|
||||
if (chatSidebar && !chatSidebar.isCollapsed) {
|
||||
chatToggleBtn.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
chatToggleBtn.addEventListener('mouseenter', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text)';
|
||||
e.target.style.background = 'var(--vscode-selection)';
|
||||
});
|
||||
chatToggleBtn.addEventListener('mouseleave', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text-dim)';
|
||||
e.target.style.background = 'transparent';
|
||||
});
|
||||
}
|
||||
|
||||
const advancedModeBtn = this.querySelector('#advanced-mode-btn');
|
||||
if (advancedModeBtn) {
|
||||
advancedModeBtn.addEventListener('click', () => {
|
||||
this.toggleAdvancedMode();
|
||||
});
|
||||
advancedModeBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
advancedModeBtn.click();
|
||||
}
|
||||
});
|
||||
advancedModeBtn.addEventListener('mouseenter', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text)';
|
||||
e.target.style.background = 'var(--vscode-selection)';
|
||||
});
|
||||
advancedModeBtn.addEventListener('mouseleave', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text-dim)';
|
||||
e.target.style.background = 'transparent';
|
||||
});
|
||||
}
|
||||
|
||||
const settingsBtn = this.querySelector('#settings-btn');
|
||||
if (settingsBtn) {
|
||||
settingsBtn.addEventListener('click', () => {
|
||||
this.openSettings();
|
||||
});
|
||||
settingsBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
settingsBtn.click();
|
||||
}
|
||||
});
|
||||
settingsBtn.addEventListener('mouseenter', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text)';
|
||||
e.target.style.background = 'var(--vscode-selection)';
|
||||
});
|
||||
settingsBtn.addEventListener('mouseleave', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text-dim)';
|
||||
e.target.style.background = 'transparent';
|
||||
});
|
||||
}
|
||||
|
||||
// Notification Center integration
|
||||
const notificationToggleBtn = this.querySelector('#notification-toggle-btn');
|
||||
const notificationCenter = this.querySelector('ds-notification-center');
|
||||
const notificationIndicator = this.querySelector('#notification-indicator');
|
||||
|
||||
if (notificationToggleBtn && notificationCenter) {
|
||||
// Toggle notification panel
|
||||
notificationToggleBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = notificationCenter.hasAttribute('open');
|
||||
if (isOpen) {
|
||||
notificationCenter.removeAttribute('open');
|
||||
} else {
|
||||
notificationCenter.setAttribute('open', '');
|
||||
}
|
||||
});
|
||||
|
||||
// Close when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!notificationCenter.contains(e.target) && !notificationToggleBtn.contains(e.target)) {
|
||||
notificationCenter.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
|
||||
// Update unread indicator
|
||||
notificationService.addEventListener('unread-count-changed', (e) => {
|
||||
const { count } = e.detail;
|
||||
if (notificationIndicator) {
|
||||
notificationIndicator.style.display = count > 0 ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle notification actions (navigation)
|
||||
notificationCenter.addEventListener('notification-action', (e) => {
|
||||
const { event, payload } = e.detail;
|
||||
console.log('[DSShell] Notification action:', event, payload);
|
||||
|
||||
// Handle navigation events
|
||||
if (event.startsWith('navigate:')) {
|
||||
const page = event.replace('navigate:', '');
|
||||
// Route to the appropriate page
|
||||
// This would integrate with your routing system
|
||||
console.log('[DSShell] Navigate to:', page, payload);
|
||||
}
|
||||
});
|
||||
|
||||
// Hover effects
|
||||
notificationToggleBtn.addEventListener('mouseenter', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text)';
|
||||
e.target.style.background = 'var(--vscode-selection)';
|
||||
});
|
||||
notificationToggleBtn.addEventListener('mouseleave', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text-dim)';
|
||||
e.target.style.background = 'transparent';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add team button event listeners
|
||||
const teamBtns = this.querySelectorAll('.team-btn');
|
||||
teamBtns.forEach((btn, index) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const teamId = e.target.dataset.team;
|
||||
this.switchTeam(teamId);
|
||||
});
|
||||
|
||||
// Keyboard navigation (Arrow keys)
|
||||
btn.addEventListener('keydown', (e) => {
|
||||
let nextBtn = null;
|
||||
if (e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
nextBtn = teamBtns[(index + 1) % teamBtns.length];
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
nextBtn = teamBtns[(index - 1 + teamBtns.length) % teamBtns.length];
|
||||
}
|
||||
if (nextBtn) {
|
||||
nextBtn.focus();
|
||||
nextBtn.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Hover effects
|
||||
btn.addEventListener('mouseenter', (e) => {
|
||||
e.target.style.color = 'var(--vscode-text)';
|
||||
e.target.style.background = 'var(--vscode-selection)';
|
||||
});
|
||||
|
||||
btn.addEventListener('mouseleave', (e) => {
|
||||
// Keep accent color if this is the active team
|
||||
if (e.target.classList.contains('active')) {
|
||||
e.target.style.color = 'var(--vscode-accent)';
|
||||
e.target.style.background = 'var(--vscode-selection)';
|
||||
} else {
|
||||
e.target.style.color = 'var(--vscode-text-dim)';
|
||||
e.target.style.background = 'transparent';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Set initial active team button
|
||||
this.updateTeamButtonStates();
|
||||
}
|
||||
|
||||
updateTeamButtonStates() {
|
||||
const teamBtns = this.querySelectorAll('.team-btn');
|
||||
teamBtns.forEach(btn => {
|
||||
if (btn.dataset.team === this.currentTeam) {
|
||||
btn.classList.add('active');
|
||||
btn.setAttribute('aria-selected', 'true');
|
||||
btn.style.color = 'var(--vscode-accent)';
|
||||
btn.style.background = 'var(--vscode-selection)';
|
||||
btn.style.borderColor = 'var(--vscode-accent)';
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
btn.setAttribute('aria-selected', 'false');
|
||||
btn.style.color = 'var(--vscode-text-dim)';
|
||||
btn.style.background = 'transparent';
|
||||
btn.style.borderColor = 'transparent';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupMobileMenu() {
|
||||
const hamburgerBtn = this.querySelector('#hamburger-menu');
|
||||
const sidebar = this.querySelector('ds-sidebar');
|
||||
|
||||
if (hamburgerBtn) {
|
||||
hamburgerBtn.addEventListener('click', () => {
|
||||
if (sidebar) {
|
||||
const isOpen = sidebar.classList.contains('mobile-open');
|
||||
if (isOpen) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
hamburgerBtn.setAttribute('aria-expanded', 'false');
|
||||
} else {
|
||||
sidebar.classList.add('mobile-open');
|
||||
hamburgerBtn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hamburgerBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
hamburgerBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close sidebar when clicking on a team button (mobile)
|
||||
const teamBtns = this.querySelectorAll('.team-btn');
|
||||
teamBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
if (sidebar && window.innerWidth <= 768) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
if (hamburgerBtn) {
|
||||
hamburgerBtn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Show/hide hamburger menu based on screen size
|
||||
const updateMenuVisibility = () => {
|
||||
if (hamburgerBtn) {
|
||||
if (window.innerWidth <= 768) {
|
||||
hamburgerBtn.style.display = 'flex';
|
||||
} else {
|
||||
hamburgerBtn.style.display = 'none';
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateMenuVisibility();
|
||||
window.addEventListener('resize', updateMenuVisibility);
|
||||
}
|
||||
|
||||
toggleAdvancedMode() {
|
||||
// Get activity bar for advanced mode state (or create local tracking)
|
||||
const activityBar = this.querySelector('ds-activity-bar');
|
||||
let advancedMode = false;
|
||||
|
||||
if (activityBar && activityBar.advancedMode !== undefined) {
|
||||
advancedMode = !activityBar.advancedMode;
|
||||
activityBar.advancedMode = advancedMode;
|
||||
activityBar.saveAdvancedMode();
|
||||
} else {
|
||||
// Fallback: use localStorage directly
|
||||
advancedMode = localStorage.getItem('dss-advanced-mode') !== 'true';
|
||||
localStorage.setItem('dss-advanced-mode', advancedMode.toString());
|
||||
}
|
||||
|
||||
this.onAdvancedModeChange(advancedMode);
|
||||
|
||||
// Update button appearance and accessibility state
|
||||
const advancedModeBtn = this.querySelector('#advanced-mode-btn');
|
||||
if (advancedModeBtn) {
|
||||
advancedModeBtn.setAttribute('aria-pressed', advancedMode.toString());
|
||||
advancedModeBtn.style.color = advancedMode ? 'var(--vscode-accent)' : 'var(--vscode-text-dim)';
|
||||
}
|
||||
}
|
||||
|
||||
onAdvancedModeChange(advancedMode) {
|
||||
console.log(`Advanced mode: ${advancedMode ? 'ON' : 'OFF'}`);
|
||||
|
||||
// Reconfigure panel with new advanced mode setting
|
||||
const panel = this.querySelector('ds-panel');
|
||||
if (panel) {
|
||||
panel.configure(this.currentTeam, advancedMode);
|
||||
}
|
||||
}
|
||||
|
||||
async switchTeam(teamId) {
|
||||
console.log(`Switching to team: ${teamId}`);
|
||||
this.currentTeam = teamId;
|
||||
|
||||
// Persist team selection to userStore
|
||||
this.userStore.updatePreferences({ lastTeam: teamId });
|
||||
|
||||
// Update team button states
|
||||
this.updateTeamButtonStates();
|
||||
|
||||
// Update stage title
|
||||
const stageTitle = this.querySelector('#stage-title');
|
||||
if (stageTitle) {
|
||||
stageTitle.textContent = `${teamId.toUpperCase()} Workdesk`;
|
||||
}
|
||||
|
||||
// Apply admin-mode class for full-page layout
|
||||
if (teamId === 'admin') {
|
||||
this.classList.add('admin-mode');
|
||||
|
||||
// Initialize browser automation for admin team (needed for DevTools components)
|
||||
this.initializeBrowser().catch(error => {
|
||||
console.warn('[DSShell] Browser initialization failed (non-blocking):', error.message);
|
||||
});
|
||||
} else {
|
||||
this.classList.remove('admin-mode');
|
||||
}
|
||||
|
||||
// Configure panel for this team
|
||||
const panel = this.querySelector('ds-panel');
|
||||
const activityBar = this.querySelector('ds-activity-bar');
|
||||
if (panel) {
|
||||
// Get advancedMode from activity bar
|
||||
const advancedMode = activityBar?.advancedMode || false;
|
||||
panel.configure(teamId, advancedMode);
|
||||
}
|
||||
|
||||
// Use layout manager to switch workdesk
|
||||
try {
|
||||
this.currentWorkdesk = await layoutManager.switchWorkdesk(teamId);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load workdesk for team ${teamId}:`, error);
|
||||
|
||||
// Show error in stage
|
||||
const stageContent = this.querySelector('#stage-workdesk-content');
|
||||
if (stageContent) {
|
||||
stageContent.innerHTML = `
|
||||
<div style="text-align: center; padding: 48px; color: #f48771;">
|
||||
<h2>Failed to load ${teamId.toUpperCase()} Workdesk</h2>
|
||||
<p style="margin-top: 16px;">Error: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open user settings view
|
||||
*/
|
||||
async openSettings() {
|
||||
this.currentView = 'settings';
|
||||
|
||||
const stageContent = this.querySelector('#stage-workdesk-content');
|
||||
const stageTitle = this.querySelector('#stage-title');
|
||||
|
||||
if (stageTitle) {
|
||||
stageTitle.textContent = '⚙️ Settings';
|
||||
}
|
||||
|
||||
if (stageContent) {
|
||||
// Clear existing content
|
||||
stageContent.innerHTML = '';
|
||||
|
||||
// Create and append user settings component
|
||||
const settingsComponent = document.createElement('ds-user-settings');
|
||||
stageContent.appendChild(settingsComponent);
|
||||
}
|
||||
|
||||
// Hide sidebar and minimize panel for full-width settings
|
||||
const sidebar = this.querySelector('ds-sidebar');
|
||||
const panel = this.querySelector('ds-panel');
|
||||
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('collapsed');
|
||||
}
|
||||
if (panel) {
|
||||
panel.classList.add('collapsed');
|
||||
}
|
||||
|
||||
console.log('[DSShell] Settings view opened');
|
||||
}
|
||||
|
||||
/**
|
||||
* Close settings view and return to workdesk
|
||||
*/
|
||||
closeSettings() {
|
||||
if (this.currentView === 'settings') {
|
||||
this.currentView = 'workdesk';
|
||||
|
||||
// Restore sidebar and panel
|
||||
const sidebar = this.querySelector('ds-sidebar');
|
||||
const panel = this.querySelector('ds-panel');
|
||||
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('collapsed');
|
||||
}
|
||||
if (panel) {
|
||||
panel.classList.remove('collapsed');
|
||||
}
|
||||
|
||||
// Reload current team's workdesk
|
||||
this.switchTeam(this.currentTeam);
|
||||
}
|
||||
}
|
||||
|
||||
setupNavigationHighlight() {
|
||||
// Use requestAnimationFrame to ensure DOM is ready (fixes race condition)
|
||||
requestAnimationFrame(() => {
|
||||
const navItems = this.querySelectorAll('.nav-item');
|
||||
|
||||
if (navItems.length === 0) {
|
||||
console.warn('[DSShell] No nav items found for highlight setup');
|
||||
return;
|
||||
}
|
||||
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('mouseenter', (e) => {
|
||||
if (!e.target.classList.contains('active')) {
|
||||
e.target.style.background = 'var(--vscode-list-hoverBackground, rgba(255,255,255,0.1))';
|
||||
e.target.style.color = 'var(--vscode-text)';
|
||||
}
|
||||
});
|
||||
item.addEventListener('mouseleave', (e) => {
|
||||
if (!e.target.classList.contains('active')) {
|
||||
e.target.style.background = 'transparent';
|
||||
e.target.style.color = 'var(--vscode-text-dim)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Use bound handler to enable proper cleanup (fixes memory leak)
|
||||
window.addEventListener('hashchange', this.handleHashChangeBound);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle hash change events (bound in constructor for proper cleanup)
|
||||
*/
|
||||
handleHashChange() {
|
||||
this.updateActiveLink();
|
||||
}
|
||||
|
||||
updateActiveLink(path) {
|
||||
const currentPath = path || (window.location.hash.replace('#', '') || 'projects');
|
||||
const navItems = this.querySelectorAll('.nav-item');
|
||||
|
||||
navItems.forEach(item => {
|
||||
const itemPath = item.dataset.path;
|
||||
if (itemPath === currentPath) {
|
||||
item.classList.add('active');
|
||||
item.style.background = 'var(--vscode-list-activeSelectionBackground, var(--vscode-selection))';
|
||||
item.style.color = 'var(--vscode-list-activeSelectionForeground, var(--vscode-accent))';
|
||||
item.style.fontWeight = '500';
|
||||
} else {
|
||||
item.classList.remove('active');
|
||||
item.style.background = 'transparent';
|
||||
item.style.color = 'var(--vscode-text-dim)';
|
||||
item.style.fontWeight = 'normal';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Getters for workdesk components to access
|
||||
get sidebarContent() {
|
||||
return this.querySelector('#sidebar-workdesk-content');
|
||||
}
|
||||
|
||||
get stageContent() {
|
||||
return this.querySelector('#stage-workdesk-content');
|
||||
}
|
||||
|
||||
get stageActions() {
|
||||
return this.querySelector('#stage-actions');
|
||||
}
|
||||
}
|
||||
|
||||
// Define custom element
|
||||
customElements.define('ds-shell', DSShell);
|
||||
|
||||
// Also define the sidebar and stage as custom elements for CSS targeting
|
||||
class DSSidebar extends HTMLElement {}
|
||||
class DSStage extends HTMLElement {}
|
||||
|
||||
customElements.define('ds-sidebar', DSSidebar);
|
||||
customElements.define('ds-stage', DSStage);
|
||||
Reference in New Issue
Block a user