- Create InsertrControlPanel class for unified UI management - Separate business logic from presentation layer - Remove DOM manipulation from auth.js and editor.js - Add comprehensive CSS for status indicators and editing effects - Implement consistent kebab-case file naming - Add event-driven communication between core and UI layers UI Architecture: - Unified control panel with status indicator and action buttons - Color-coded status dots (gray/blue/green for visitor/auth/editing) - Professional editing hover effects with tooltips - Responsive design for mobile devices - Proper z-index and accessibility management Business Logic: - Pure auth.js focused on authentication state and OAuth flows - Pure editor.js focused on content editing workflow - Event emitters for state changes between modules - Clean separation of concerns and testable architecture
371 lines
12 KiB
JavaScript
371 lines
12 KiB
JavaScript
/**
|
|
* InsertrControlPanel - Unified UI Controller
|
|
* Handles all presentation layer concerns for the insertr system
|
|
*
|
|
* Architecture:
|
|
* - Pure presentation layer - no business logic
|
|
* - Event-driven communication with core modules
|
|
* - Unified control panel design
|
|
* - Responsive and accessible
|
|
*/
|
|
export class InsertrControlPanel {
|
|
constructor(auth, editor, apiClient, options = {}) {
|
|
this.auth = auth;
|
|
this.editor = editor;
|
|
this.apiClient = apiClient;
|
|
this.options = options;
|
|
|
|
// UI state
|
|
this.elements = {};
|
|
this.isInitialized = false;
|
|
|
|
// Bind methods for event listeners
|
|
this.handleAuthToggle = this.handleAuthToggle.bind(this);
|
|
this.handleEditToggle = this.handleEditToggle.bind(this);
|
|
this.handleEnhanceClick = this.handleEnhanceClick.bind(this);
|
|
}
|
|
|
|
/**
|
|
* Initialize the control panel (called after DOM is ready)
|
|
*/
|
|
init() {
|
|
if (this.isInitialized) return;
|
|
|
|
console.log('🎨 Initializing Insertr Control Panel');
|
|
this.createControlPanel();
|
|
this.setupEventListeners();
|
|
this.updateVisualState();
|
|
this.isInitialized = true;
|
|
}
|
|
|
|
/**
|
|
* Create the unified control panel structure
|
|
*/
|
|
createControlPanel() {
|
|
// Check if control panel already exists
|
|
if (document.getElementById('insertr-control-panel')) {
|
|
this.cacheElements();
|
|
return;
|
|
}
|
|
|
|
const controlPanelHtml = `
|
|
<div id="insertr-control-panel" class="insertr-control-panel">
|
|
<!-- Status Indicator Section -->
|
|
<div id="insertr-status-section" class="insertr-status-section">
|
|
<div id="insertr-status-indicator" class="insertr-status-indicator">
|
|
<span class="insertr-status-text">Visitor Mode</span>
|
|
<span class="insertr-status-dot insertr-status-visitor"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons Section -->
|
|
<div id="insertr-action-section" class="insertr-action-section">
|
|
<button id="insertr-enhance-btn" class="insertr-action-btn insertr-enhance-btn"
|
|
style="display: none;"
|
|
title="Enhance files with latest content">
|
|
🔄 Enhance
|
|
</button>
|
|
<button id="insertr-edit-toggle" class="insertr-action-btn insertr-edit-btn"
|
|
style="display: none;">
|
|
Edit Mode: Off
|
|
</button>
|
|
<button id="insertr-auth-toggle" class="insertr-action-btn insertr-auth-btn">
|
|
Login as Client
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add control panel to page
|
|
document.body.insertAdjacentHTML('beforeend', controlPanelHtml);
|
|
this.cacheElements();
|
|
|
|
console.log('📱 Control panel created');
|
|
}
|
|
|
|
/**
|
|
* Cache DOM elements for performance
|
|
*/
|
|
cacheElements() {
|
|
this.elements = {
|
|
controlPanel: document.getElementById('insertr-control-panel'),
|
|
statusSection: document.getElementById('insertr-status-section'),
|
|
statusIndicator: document.getElementById('insertr-status-indicator'),
|
|
statusText: document.querySelector('.insertr-status-text'),
|
|
statusDot: document.querySelector('.insertr-status-dot'),
|
|
actionSection: document.getElementById('insertr-action-section'),
|
|
authToggle: document.getElementById('insertr-auth-toggle'),
|
|
editToggle: document.getElementById('insertr-edit-toggle'),
|
|
enhanceBtn: document.getElementById('insertr-enhance-btn')
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Setup event listeners for all UI interactions
|
|
*/
|
|
setupEventListeners() {
|
|
if (this.elements.authToggle) {
|
|
this.elements.authToggle.addEventListener('click', this.handleAuthToggle);
|
|
}
|
|
|
|
if (this.elements.editToggle) {
|
|
this.elements.editToggle.addEventListener('click', this.handleEditToggle);
|
|
}
|
|
|
|
if (this.elements.enhanceBtn) {
|
|
this.elements.enhanceBtn.addEventListener('click', this.handleEnhanceClick);
|
|
}
|
|
|
|
// Listen for auth state changes
|
|
if (this.auth && typeof this.auth.on === 'function') {
|
|
this.auth.on('stateChange', () => this.updateVisualState());
|
|
}
|
|
|
|
// Listen for editor state changes
|
|
if (this.editor && typeof this.editor.on === 'function') {
|
|
this.editor.on('modeChange', () => this.updateVisualState());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle authentication toggle click
|
|
*/
|
|
handleAuthToggle() {
|
|
if (this.auth && typeof this.auth.toggleAuthentication === 'function') {
|
|
this.auth.toggleAuthentication();
|
|
this.updateVisualState();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle edit mode toggle click
|
|
*/
|
|
handleEditToggle() {
|
|
if (this.auth && typeof this.auth.toggleEditMode === 'function') {
|
|
this.auth.toggleEditMode();
|
|
this.updateVisualState();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle enhance button click
|
|
*/
|
|
async handleEnhanceClick() {
|
|
if (!this.elements.enhanceBtn) return;
|
|
|
|
const enhanceBtn = this.elements.enhanceBtn;
|
|
const siteId = window.insertrConfig?.siteId || this.options.siteId || 'demo';
|
|
|
|
try {
|
|
// Show loading state
|
|
enhanceBtn.textContent = '⏳ Enhancing...';
|
|
enhanceBtn.disabled = true;
|
|
|
|
// Smart server detection for enhance API
|
|
const isDevelopment = window.location.hostname === 'localhost' ||
|
|
window.location.hostname === '127.0.0.1';
|
|
const enhanceUrl = isDevelopment
|
|
? `http://localhost:8080/api/enhance?site_id=${siteId}`
|
|
: `/api/enhance?site_id=${siteId}`;
|
|
|
|
// Call enhance API
|
|
const response = await fetch(enhanceUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${this.auth?.getCurrentUser()?.token || 'mock-token'}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Enhancement failed: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('✅ Files enhanced successfully:', result);
|
|
|
|
// Show success state briefly
|
|
enhanceBtn.textContent = '✅ Enhanced!';
|
|
|
|
// Reset button after success
|
|
setTimeout(() => {
|
|
enhanceBtn.textContent = '🔄 Enhance';
|
|
enhanceBtn.disabled = false;
|
|
}, 2000);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Enhancement failed:', error);
|
|
enhanceBtn.textContent = '❌ Failed';
|
|
|
|
// Reset button after error
|
|
setTimeout(() => {
|
|
enhanceBtn.textContent = '🔄 Enhance';
|
|
enhanceBtn.disabled = false;
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update all visual elements based on current state
|
|
*/
|
|
updateVisualState() {
|
|
if (!this.isInitialized || !this.elements.statusText) return;
|
|
|
|
const isAuthenticated = this.auth ? this.auth.isAuthenticated() : false;
|
|
const isEditMode = this.auth ? this.auth.isEditMode() : false;
|
|
|
|
// Update status indicator
|
|
this.updateStatusIndicator(isAuthenticated, isEditMode);
|
|
|
|
// Update button states
|
|
this.updateButtonStates(isAuthenticated, isEditMode);
|
|
|
|
// Update body classes for global styling
|
|
this.updateBodyClasses(isAuthenticated, isEditMode);
|
|
}
|
|
|
|
/**
|
|
* Update status indicator text and visual state
|
|
*/
|
|
updateStatusIndicator(isAuthenticated, isEditMode) {
|
|
const { statusText, statusDot } = this.elements;
|
|
|
|
if (!statusText || !statusDot) return;
|
|
|
|
if (!isAuthenticated) {
|
|
statusText.textContent = 'Visitor Mode';
|
|
statusDot.className = 'insertr-status-dot insertr-status-visitor';
|
|
} else if (isEditMode) {
|
|
statusText.textContent = 'Editing';
|
|
statusDot.className = 'insertr-status-dot insertr-status-editing';
|
|
} else {
|
|
statusText.textContent = 'Authenticated';
|
|
statusDot.className = 'insertr-status-dot insertr-status-authenticated';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update button text, visibility, and states
|
|
*/
|
|
updateButtonStates(isAuthenticated, isEditMode) {
|
|
const { authToggle, editToggle, enhanceBtn } = this.elements;
|
|
|
|
// Update auth toggle button
|
|
if (authToggle) {
|
|
authToggle.textContent = isAuthenticated ? 'Logout' : 'Login as Client';
|
|
authToggle.className = `insertr-action-btn insertr-auth-btn ${
|
|
isAuthenticated ? 'insertr-authenticated' : ''
|
|
}`;
|
|
}
|
|
|
|
// Update edit mode toggle button
|
|
if (editToggle) {
|
|
editToggle.style.display = isAuthenticated ? 'inline-block' : 'none';
|
|
editToggle.textContent = `Edit Mode: ${isEditMode ? 'On' : 'Off'}`;
|
|
editToggle.className = `insertr-action-btn insertr-edit-btn ${
|
|
isEditMode ? 'insertr-edit-active' : ''
|
|
}`;
|
|
}
|
|
|
|
// Update enhance button (dev-mode only)
|
|
if (enhanceBtn) {
|
|
enhanceBtn.style.display = isAuthenticated ? 'inline-block' : 'none';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update body CSS classes for global styling hooks
|
|
*/
|
|
updateBodyClasses(isAuthenticated, isEditMode) {
|
|
document.body.classList.toggle('insertr-authenticated', isAuthenticated);
|
|
document.body.classList.toggle('insertr-edit-mode', isEditMode);
|
|
}
|
|
|
|
/**
|
|
* Add editing indicators to content elements
|
|
*/
|
|
addEditingIndicators() {
|
|
if (!this.editor || !this.editor.core) return;
|
|
|
|
const elements = this.editor.core.getAllElements();
|
|
console.log(`🎨 Adding editing indicators to ${elements.length} elements`);
|
|
|
|
elements.forEach(meta => {
|
|
this.initializeElementVisuals(meta.element);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize visual editing indicators for a single element
|
|
*/
|
|
initializeElementVisuals(element) {
|
|
// Add visual indicators
|
|
element.style.cursor = 'pointer';
|
|
element.style.position = 'relative';
|
|
|
|
// Add hover effects
|
|
element.addEventListener('mouseenter', () => {
|
|
if (this.auth && this.auth.isAuthenticated() && this.auth.isEditMode()) {
|
|
element.classList.add('insertr-editing-hover');
|
|
}
|
|
});
|
|
|
|
element.addEventListener('mouseleave', () => {
|
|
element.classList.remove('insertr-editing-hover');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove editing indicators (when edit mode is disabled)
|
|
*/
|
|
removeEditingIndicators() {
|
|
const elements = document.querySelectorAll('.insertr-editing-hover');
|
|
elements.forEach(element => {
|
|
element.classList.remove('insertr-editing-hover');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show status message to user
|
|
*/
|
|
showStatusMessage(message, type = 'info', duration = 3000) {
|
|
// Remove existing status message
|
|
const existing = document.getElementById('insertr-status-message');
|
|
if (existing) {
|
|
existing.remove();
|
|
}
|
|
|
|
const messageHtml = `
|
|
<div id="insertr-status-message" class="insertr-status-message ${type}">
|
|
<p class="insertr-status-text">${message}</p>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', messageHtml);
|
|
|
|
// Auto-remove after duration
|
|
setTimeout(() => {
|
|
const messageEl = document.getElementById('insertr-status-message');
|
|
if (messageEl) {
|
|
messageEl.remove();
|
|
}
|
|
}, duration);
|
|
}
|
|
|
|
/**
|
|
* Destroy the control panel (cleanup)
|
|
*/
|
|
destroy() {
|
|
if (this.elements.controlPanel) {
|
|
this.elements.controlPanel.remove();
|
|
}
|
|
|
|
this.elements = {};
|
|
this.isInitialized = false;
|
|
|
|
// Remove body classes
|
|
document.body.classList.remove('insertr-authenticated', 'insertr-edit-mode');
|
|
|
|
console.log('🗑️ Control panel destroyed');
|
|
}
|
|
} |