javascript

examples

examples.js
/**
 * 21.5 Capstone Projects - Examples
 *
 * Complete implementation examples for capstone projects
 */

// ============================================================
// PROJECT 1: Task Management System Core
// ============================================================

/**
 * Event Bus - Central event management
 */
class EventBus {
  constructor() {
    this.events = new Map();
  }

  on(event, handler) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event).add(handler);
    return () => this.off(event, handler);
  }

  off(event, handler) {
    const handlers = this.events.get(event);
    if (handlers) {
      handlers.delete(handler);
    }
  }

  emit(event, data) {
    const handlers = this.events.get(event);
    if (handlers) {
      handlers.forEach((handler) => {
        try {
          handler(data);
        } catch (error) {
          console.error(`Error in event handler for ${event}:`, error);
        }
      });
    }
  }
}

/**
 * Store - State management with persistence
 */
class Store {
  constructor(name, initialState = {}) {
    this.name = name;
    this.state = this.loadState() || initialState;
    this.listeners = new Set();
  }

  loadState() {
    try {
      const saved = localStorage.getItem(`store_${this.name}`);
      return saved ? JSON.parse(saved) : null;
    } catch {
      return null;
    }
  }

  saveState() {
    try {
      localStorage.setItem(`store_${this.name}`, JSON.stringify(this.state));
    } catch (error) {
      console.error('Failed to save state:', error);
    }
  }

  getState() {
    return { ...this.state };
  }

  setState(updater) {
    const newState =
      typeof updater === 'function'
        ? updater(this.state)
        : { ...this.state, ...updater };

    this.state = newState;
    this.saveState();
    this.notify();
  }

  subscribe(listener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  notify() {
    this.listeners.forEach((listener) => listener(this.state));
  }
}

/**
 * Auth Module - Secure authentication
 */
const AuthModule = (() => {
  const store = new Store('auth', { user: null, token: null });
  const eventBus = new EventBus();

  // Token management
  const TOKEN_KEY = 'auth_token';

  function getToken() {
    return sessionStorage.getItem(TOKEN_KEY);
  }

  function setToken(token) {
    if (token) {
      sessionStorage.setItem(TOKEN_KEY, token);
    } else {
      sessionStorage.removeItem(TOKEN_KEY);
    }
  }

  // Password validation
  function validatePassword(password) {
    const errors = [];
    if (password.length < 8) errors.push('At least 8 characters');
    if (!/[A-Z]/.test(password)) errors.push('At least one uppercase');
    if (!/[a-z]/.test(password)) errors.push('At least one lowercase');
    if (!/[0-9]/.test(password)) errors.push('At least one number');
    if (!/[^A-Za-z0-9]/.test(password))
      errors.push('At least one special character');
    return { valid: errors.length === 0, errors };
  }

  // Email validation
  function validateEmail(email) {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(email);
  }

  // Simulated API calls
  async function login(email, password) {
    if (!validateEmail(email)) {
      throw new Error('Invalid email format');
    }

    // Simulate API call
    await new Promise((resolve) => setTimeout(resolve, 500));

    // In real app, this would be an API call
    const mockUser = { id: '1', email, name: email.split('@')[0] };
    const mockToken = btoa(
      JSON.stringify({ userId: mockUser.id, exp: Date.now() + 3600000 })
    );

    setToken(mockToken);
    store.setState({ user: mockUser, token: mockToken });
    eventBus.emit('auth:login', mockUser);

    return mockUser;
  }

  async function logout() {
    setToken(null);
    store.setState({ user: null, token: null });
    eventBus.emit('auth:logout');
  }

  async function register(email, password, name) {
    if (!validateEmail(email)) {
      throw new Error('Invalid email format');
    }

    const passwordValidation = validatePassword(password);
    if (!passwordValidation.valid) {
      throw new Error(
        `Password requirements: ${passwordValidation.errors.join(', ')}`
      );
    }

    // Simulate API call
    await new Promise((resolve) => setTimeout(resolve, 500));

    return { success: true, message: 'Registration successful' };
  }

  function isAuthenticated() {
    const token = getToken();
    if (!token) return false;

    try {
      const payload = JSON.parse(atob(token));
      return payload.exp > Date.now();
    } catch {
      return false;
    }
  }

  function getCurrentUser() {
    return store.getState().user;
  }

  return {
    login,
    logout,
    register,
    isAuthenticated,
    getCurrentUser,
    validatePassword,
    validateEmail,
    on: eventBus.on.bind(eventBus),
    subscribe: store.subscribe.bind(store),
  };
})();

/**
 * Task Module - Task management with offline support
 */
const TaskModule = (() => {
  const store = new Store('tasks', { tasks: [], filters: {} });
  const eventBus = new EventBus();

  // Generate unique ID
  function generateId() {
    return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // Sanitize task input
  function sanitizeTask(task) {
    return {
      id: task.id || generateId(),
      title: String(task.title || '')
        .slice(0, 200)
        .trim(),
      description: String(task.description || '')
        .slice(0, 2000)
        .trim(),
      status: ['todo', 'in-progress', 'done'].includes(task.status)
        ? task.status
        : 'todo',
      priority: ['low', 'medium', 'high'].includes(task.priority)
        ? task.priority
        : 'medium',
      dueDate: task.dueDate ? new Date(task.dueDate).toISOString() : null,
      labels: Array.isArray(task.labels)
        ? task.labels.slice(0, 10).map((l) => String(l).slice(0, 50))
        : [],
      assignee: task.assignee ? String(task.assignee).slice(0, 100) : null,
      createdAt: task.createdAt || new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };
  }

  // CRUD Operations
  function createTask(taskData) {
    const task = sanitizeTask(taskData);
    store.setState((state) => ({
      ...state,
      tasks: [...state.tasks, task],
    }));
    eventBus.emit('task:created', task);
    return task;
  }

  function updateTask(id, updates) {
    let updatedTask = null;
    store.setState((state) => ({
      ...state,
      tasks: state.tasks.map((task) => {
        if (task.id === id) {
          updatedTask = sanitizeTask({ ...task, ...updates, id });
          return updatedTask;
        }
        return task;
      }),
    }));
    if (updatedTask) {
      eventBus.emit('task:updated', updatedTask);
    }
    return updatedTask;
  }

  function deleteTask(id) {
    const task = getTask(id);
    if (task) {
      store.setState((state) => ({
        ...state,
        tasks: state.tasks.filter((t) => t.id !== id),
      }));
      eventBus.emit('task:deleted', task);
    }
    return task;
  }

  function getTask(id) {
    return store.getState().tasks.find((t) => t.id === id);
  }

  function getAllTasks() {
    return [...store.getState().tasks];
  }

  // Filtering and sorting
  function getTasks(options = {}) {
    let tasks = getAllTasks();

    // Apply filters
    if (options.status) {
      tasks = tasks.filter((t) => t.status === options.status);
    }
    if (options.priority) {
      tasks = tasks.filter((t) => t.priority === options.priority);
    }
    if (options.search) {
      const search = options.search.toLowerCase();
      tasks = tasks.filter(
        (t) =>
          t.title.toLowerCase().includes(search) ||
          t.description.toLowerCase().includes(search)
      );
    }
    if (options.labels && options.labels.length) {
      tasks = tasks.filter((t) =>
        options.labels.some((label) => t.labels.includes(label))
      );
    }

    // Apply sorting
    if (options.sortBy) {
      const sortOrder = options.sortOrder === 'desc' ? -1 : 1;
      tasks.sort((a, b) => {
        if (options.sortBy === 'dueDate') {
          return (
            sortOrder * (new Date(a.dueDate || 0) - new Date(b.dueDate || 0))
          );
        }
        if (options.sortBy === 'priority') {
          const priorityOrder = { high: 3, medium: 2, low: 1 };
          return (
            sortOrder * (priorityOrder[b.priority] - priorityOrder[a.priority])
          );
        }
        if (options.sortBy === 'createdAt') {
          return sortOrder * (new Date(b.createdAt) - new Date(a.createdAt));
        }
        return 0;
      });
    }

    return tasks;
  }

  return {
    createTask,
    updateTask,
    deleteTask,
    getTask,
    getAllTasks,
    getTasks,
    on: eventBus.on.bind(eventBus),
    subscribe: store.subscribe.bind(store),
  };
})();

// ============================================================
// PROJECT 2: E-Commerce Dashboard Components
// ============================================================

/**
 * Virtual List - Efficient rendering of large lists
 */
class VirtualList {
  constructor(container, options = {}) {
    this.container = container;
    this.itemHeight = options.itemHeight || 50;
    this.bufferSize = options.bufferSize || 5;
    this.items = [];
    this.renderedItems = new Map();

    this.viewport = document.createElement('div');
    this.viewport.style.cssText = 'overflow-y: auto; height: 100%;';

    this.content = document.createElement('div');
    this.content.style.position = 'relative';

    this.viewport.appendChild(this.content);
    this.container.appendChild(this.viewport);

    this.viewport.addEventListener('scroll', this.handleScroll.bind(this));
    this.renderItem =
      options.renderItem ||
      ((item) => {
        const el = document.createElement('div');
        el.textContent = JSON.stringify(item);
        return el;
      });
  }

  setItems(items) {
    this.items = items;
    this.content.style.height = `${items.length * this.itemHeight}px`;
    this.render();
  }

  handleScroll() {
    requestAnimationFrame(() => this.render());
  }

  render() {
    const scrollTop = this.viewport.scrollTop;
    const viewportHeight = this.viewport.clientHeight;

    const startIndex = Math.max(
      0,
      Math.floor(scrollTop / this.itemHeight) - this.bufferSize
    );
    const endIndex = Math.min(
      this.items.length,
      Math.ceil((scrollTop + viewportHeight) / this.itemHeight) +
        this.bufferSize
    );

    // Remove items outside viewport
    for (const [index, element] of this.renderedItems) {
      if (index < startIndex || index >= endIndex) {
        element.remove();
        this.renderedItems.delete(index);
      }
    }

    // Add items in viewport
    for (let i = startIndex; i < endIndex; i++) {
      if (!this.renderedItems.has(i)) {
        const element = this.renderItem(this.items[i], i);
        element.style.cssText = `position: absolute; top: ${
          i * this.itemHeight
        }px; width: 100%; height: ${this.itemHeight}px;`;
        this.content.appendChild(element);
        this.renderedItems.set(i, element);
      }
    }
  }

  destroy() {
    this.viewport.removeEventListener('scroll', this.handleScroll);
    this.container.innerHTML = '';
  }
}

/**
 * Chart Component - Simple chart rendering
 */
class SimpleChart {
  constructor(canvas, options = {}) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.options = {
      padding: 40,
      gridLines: 5,
      colors: ['#4A90D9', '#7BC67E', '#F5A623', '#D0021B', '#9013FE'],
      ...options,
    };
    this.data = [];
  }

  setData(data) {
    this.data = data;
    this.render();
  }

  clear() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  }

  drawLine(data) {
    this.clear();
    const { width, height } = this.canvas;
    const { padding, colors } = this.options;
    const chartWidth = width - padding * 2;
    const chartHeight = height - padding * 2;

    if (data.length === 0) return;

    const maxValue = Math.max(...data.map((d) => d.value));
    const minValue = Math.min(...data.map((d) => d.value));
    const range = maxValue - minValue || 1;

    // Draw grid
    this.ctx.strokeStyle = '#eee';
    this.ctx.lineWidth = 1;
    for (let i = 0; i <= this.options.gridLines; i++) {
      const y = padding + (chartHeight / this.options.gridLines) * i;
      this.ctx.beginPath();
      this.ctx.moveTo(padding, y);
      this.ctx.lineTo(width - padding, y);
      this.ctx.stroke();
    }

    // Draw line
    this.ctx.strokeStyle = colors[0];
    this.ctx.lineWidth = 2;
    this.ctx.beginPath();

    data.forEach((point, i) => {
      const x = padding + (chartWidth / (data.length - 1)) * i;
      const y =
        padding +
        chartHeight -
        ((point.value - minValue) / range) * chartHeight;

      if (i === 0) {
        this.ctx.moveTo(x, y);
      } else {
        this.ctx.lineTo(x, y);
      }
    });

    this.ctx.stroke();

    // Draw points
    this.ctx.fillStyle = colors[0];
    data.forEach((point, i) => {
      const x = padding + (chartWidth / (data.length - 1)) * i;
      const y =
        padding +
        chartHeight -
        ((point.value - minValue) / range) * chartHeight;

      this.ctx.beginPath();
      this.ctx.arc(x, y, 4, 0, Math.PI * 2);
      this.ctx.fill();
    });
  }

  drawBar(data) {
    this.clear();
    const { width, height } = this.canvas;
    const { padding, colors } = this.options;
    const chartWidth = width - padding * 2;
    const chartHeight = height - padding * 2;

    if (data.length === 0) return;

    const maxValue = Math.max(...data.map((d) => d.value));
    const barWidth = (chartWidth / data.length) * 0.8;
    const gap = (chartWidth / data.length) * 0.2;

    data.forEach((point, i) => {
      const barHeight = (point.value / maxValue) * chartHeight;
      const x = padding + (chartWidth / data.length) * i + gap / 2;
      const y = padding + chartHeight - barHeight;

      this.ctx.fillStyle = colors[i % colors.length];
      this.ctx.fillRect(x, y, barWidth, barHeight);

      // Label
      this.ctx.fillStyle = '#333';
      this.ctx.font = '12px sans-serif';
      this.ctx.textAlign = 'center';
      this.ctx.fillText(point.label || '', x + barWidth / 2, height - 10);
    });
  }
}

/**
 * Dashboard Analytics
 */
class DashboardAnalytics {
  constructor() {
    this.data = new Map();
    this.cache = new Map();
    this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
  }

  async fetchMetrics(timeRange = '7d') {
    const cacheKey = `metrics_${timeRange}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.data;
    }

    // Simulate API call
    await new Promise((resolve) => setTimeout(resolve, 300));

    const metrics = {
      revenue: { value: 42350, change: 12.5 },
      orders: { value: 1234, change: 8.2 },
      customers: { value: 5678, change: 15.3 },
      conversionRate: { value: 3.42, change: -2.1 },
    };

    this.cache.set(cacheKey, { data: metrics, timestamp: Date.now() });
    return metrics;
  }

  async fetchSalesData(timeRange = '7d') {
    const cacheKey = `sales_${timeRange}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.data;
    }

    // Simulate API call
    await new Promise((resolve) => setTimeout(resolve, 300));

    const days = timeRange === '7d' ? 7 : timeRange === '30d' ? 30 : 90;
    const data = Array.from({ length: days }, (_, i) => ({
      date: new Date(Date.now() - (days - i - 1) * 86400000)
        .toISOString()
        .split('T')[0],
      value: Math.floor(Math.random() * 5000) + 1000,
      orders: Math.floor(Math.random() * 100) + 20,
    }));

    this.cache.set(cacheKey, { data, timestamp: Date.now() });
    return data;
  }

  clearCache() {
    this.cache.clear();
  }
}

// ============================================================
// PROJECT 3: Real-Time Chat Core
// ============================================================

/**
 * Message Encryption (simplified for demo)
 */
class MessageEncryption {
  constructor() {
    this.encoder = new TextEncoder();
    this.decoder = new TextDecoder();
  }

  async generateKeyPair() {
    // In real app, use Web Crypto API
    const keyPair = {
      publicKey: this.generateRandomKey(),
      privateKey: this.generateRandomKey(),
    };
    return keyPair;
  }

  generateRandomKey() {
    return Array.from({ length: 32 }, () =>
      Math.floor(Math.random() * 256)
        .toString(16)
        .padStart(2, '0')
    ).join('');
  }

  async encrypt(message, key) {
    // Simplified XOR encryption for demo (use AES in production)
    const messageBytes = this.encoder.encode(message);
    const keyBytes = this.encoder.encode(key);
    const encrypted = new Uint8Array(messageBytes.length);

    for (let i = 0; i < messageBytes.length; i++) {
      encrypted[i] = messageBytes[i] ^ keyBytes[i % keyBytes.length];
    }

    return btoa(String.fromCharCode(...encrypted));
  }

  async decrypt(encryptedMessage, key) {
    const encrypted = new Uint8Array(
      atob(encryptedMessage)
        .split('')
        .map((c) => c.charCodeAt(0))
    );
    const keyBytes = this.encoder.encode(key);
    const decrypted = new Uint8Array(encrypted.length);

    for (let i = 0; i < encrypted.length; i++) {
      decrypted[i] = encrypted[i] ^ keyBytes[i % keyBytes.length];
    }

    return this.decoder.decode(decrypted);
  }
}

/**
 * Chat Room Manager
 */
class ChatRoom {
  constructor(id, options = {}) {
    this.id = id;
    this.name = options.name || `Room ${id}`;
    this.messages = [];
    this.participants = new Map();
    this.typing = new Set();
    this.eventBus = new EventBus();
    this.encryption = new MessageEncryption();
    this.maxMessages = options.maxMessages || 1000;
  }

  join(user) {
    this.participants.set(user.id, {
      ...user,
      joinedAt: new Date(),
      status: 'online',
    });
    this.eventBus.emit('participant:joined', user);
  }

  leave(userId) {
    const user = this.participants.get(userId);
    if (user) {
      this.participants.delete(userId);
      this.eventBus.emit('participant:left', user);
    }
  }

  async sendMessage(userId, content, options = {}) {
    const sender = this.participants.get(userId);
    if (!sender) {
      throw new Error('User not in room');
    }

    // Sanitize content
    const sanitizedContent = this.sanitizeMessage(content);

    const message = {
      id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      roomId: this.id,
      senderId: userId,
      senderName: sender.name,
      content: sanitizedContent,
      type: options.type || 'text',
      timestamp: new Date().toISOString(),
      status: 'sent',
      reactions: [],
      replyTo: options.replyTo || null,
    };

    // Add to messages (with limit)
    this.messages.push(message);
    if (this.messages.length > this.maxMessages) {
      this.messages = this.messages.slice(-this.maxMessages);
    }

    this.eventBus.emit('message:sent', message);

    // Simulate delivery
    setTimeout(() => {
      message.status = 'delivered';
      this.eventBus.emit('message:delivered', message);
    }, 100);

    return message;
  }

  sanitizeMessage(content) {
    // Basic XSS prevention
    return String(content)
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;')
      .slice(0, 5000); // Limit length
  }

  setTyping(userId, isTyping) {
    if (isTyping) {
      this.typing.add(userId);
    } else {
      this.typing.delete(userId);
    }
    this.eventBus.emit('typing:changed', Array.from(this.typing));
  }

  getTypingUsers() {
    return Array.from(this.typing)
      .map((id) => this.participants.get(id))
      .filter(Boolean);
  }

  addReaction(messageId, userId, reaction) {
    const message = this.messages.find((m) => m.id === messageId);
    if (message) {
      const existing = message.reactions.findIndex(
        (r) => r.userId === userId && r.reaction === reaction
      );

      if (existing > -1) {
        message.reactions.splice(existing, 1);
      } else {
        message.reactions.push({
          userId,
          reaction,
          timestamp: new Date().toISOString(),
        });
      }

      this.eventBus.emit('message:reaction', {
        messageId,
        reactions: message.reactions,
      });
    }
  }

  getMessages(options = {}) {
    let messages = [...this.messages];

    if (options.before) {
      const beforeIndex = messages.findIndex((m) => m.id === options.before);
      if (beforeIndex > 0) {
        messages = messages.slice(
          Math.max(0, beforeIndex - (options.limit || 50)),
          beforeIndex
        );
      }
    } else if (options.limit) {
      messages = messages.slice(-options.limit);
    }

    return messages;
  }

  on(event, handler) {
    return this.eventBus.on(event, handler);
  }
}

/**
 * Chat Manager - Manages multiple rooms
 */
class ChatManager {
  constructor() {
    this.rooms = new Map();
    this.users = new Map();
    this.eventBus = new EventBus();
  }

  createRoom(id, options = {}) {
    if (this.rooms.has(id)) {
      throw new Error(`Room ${id} already exists`);
    }

    const room = new ChatRoom(id, options);
    this.rooms.set(id, room);
    this.eventBus.emit('room:created', room);

    return room;
  }

  getRoom(id) {
    return this.rooms.get(id);
  }

  deleteRoom(id) {
    const room = this.rooms.get(id);
    if (room) {
      this.rooms.delete(id);
      this.eventBus.emit('room:deleted', { id });
    }
  }

  registerUser(user) {
    this.users.set(user.id, {
      ...user,
      status: 'online',
      lastSeen: new Date(),
    });
  }

  setUserStatus(userId, status) {
    const user = this.users.get(userId);
    if (user) {
      user.status = status;
      user.lastSeen = new Date();
      this.eventBus.emit('user:status', { userId, status });
    }
  }

  on(event, handler) {
    return this.eventBus.on(event, handler);
  }
}

// ============================================================
// PROJECT 4: Code Playground Core
// ============================================================

/**
 * Code Sandbox - Safe code execution
 */
class CodeSandbox {
  constructor(options = {}) {
    this.timeout = options.timeout || 5000;
    this.maxLogs = options.maxLogs || 100;
    this.iframe = null;
    this.logs = [];
  }

  createSandbox() {
    // Create sandboxed iframe
    this.iframe = document.createElement('iframe');
    this.iframe.sandbox = 'allow-scripts';
    this.iframe.style.cssText = 'display: none;';
    document.body.appendChild(this.iframe);

    return this.iframe;
  }

  async execute(code, options = {}) {
    this.logs = [];

    return new Promise((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        this.destroy();
        reject(new Error('Execution timeout'));
      }, this.timeout);

      try {
        const sandbox = this.createSandbox();
        const contentWindow = sandbox.contentWindow;

        // Override console
        const wrappedCode = `
                    (function() {
                        const logs = [];
                        const originalConsole = console;
                        
                        window.console = {
                            log: (...args) => {
                                logs.push({ type: 'log', args: args.map(String) });
                                if (logs.length > ${this.maxLogs}) logs.shift();
                            },
                            error: (...args) => {
                                logs.push({ type: 'error', args: args.map(String) });
                            },
                            warn: (...args) => {
                                logs.push({ type: 'warn', args: args.map(String) });
                            }
                        };
                        
                        try {
                            ${code}
                            window.parent.postMessage({ type: 'success', logs }, '*');
                        } catch (error) {
                            window.parent.postMessage({ 
                                type: 'error', 
                                error: error.message,
                                logs 
                            }, '*');
                        }
                    })();
                `;

        // Listen for results
        const handleMessage = (event) => {
          if (event.source === contentWindow) {
            clearTimeout(timeoutId);
            window.removeEventListener('message', handleMessage);

            this.logs = event.data.logs || [];
            this.destroy();

            if (event.data.type === 'success') {
              resolve({ logs: this.logs });
            } else {
              reject(new Error(event.data.error));
            }
          }
        };

        window.addEventListener('message', handleMessage);

        // Execute code
        const script = contentWindow.document.createElement('script');
        script.textContent = wrappedCode;
        contentWindow.document.body.appendChild(script);
      } catch (error) {
        clearTimeout(timeoutId);
        this.destroy();
        reject(error);
      }
    });
  }

  destroy() {
    if (this.iframe && this.iframe.parentNode) {
      this.iframe.parentNode.removeChild(this.iframe);
    }
    this.iframe = null;
  }
}

/**
 * Code Editor State
 */
class CodeEditorState {
  constructor() {
    this.files = new Map();
    this.history = [];
    this.historyIndex = -1;
    this.maxHistory = 100;
  }

  setFile(name, content) {
    const previous = this.files.get(name);
    this.files.set(name, content);

    // Add to history
    this.history = this.history.slice(0, this.historyIndex + 1);
    this.history.push({ name, previous, current: content });
    if (this.history.length > this.maxHistory) {
      this.history.shift();
    }
    this.historyIndex = this.history.length - 1;
  }

  getFile(name) {
    return this.files.get(name);
  }

  deleteFile(name) {
    const previous = this.files.get(name);
    this.files.delete(name);
    this.history.push({ name, previous, current: null, deleted: true });
    this.historyIndex = this.history.length - 1;
  }

  undo() {
    if (this.historyIndex < 0) return false;

    const entry = this.history[this.historyIndex];
    if (entry.deleted) {
      this.files.set(entry.name, entry.previous);
    } else if (entry.previous === undefined) {
      this.files.delete(entry.name);
    } else {
      this.files.set(entry.name, entry.previous);
    }

    this.historyIndex--;
    return true;
  }

  redo() {
    if (this.historyIndex >= this.history.length - 1) return false;

    this.historyIndex++;
    const entry = this.history[this.historyIndex];

    if (entry.deleted) {
      this.files.delete(entry.name);
    } else {
      this.files.set(entry.name, entry.current);
    }

    return true;
  }

  getAllFiles() {
    return Object.fromEntries(this.files);
  }

  toJSON() {
    return {
      files: Object.fromEntries(this.files),
    };
  }

  fromJSON(data) {
    this.files = new Map(Object.entries(data.files || {}));
    this.history = [];
    this.historyIndex = -1;
  }
}

// ============================================================
// DEMO & TESTING
// ============================================================

console.log('=== Capstone Project Examples ===');

// Demo Task Module
console.log('\n--- Task Management Demo ---');
const task1 = TaskModule.createTask({
  title: 'Complete JavaScript course',
  description: 'Finish all 21 modules',
  priority: 'high',
  labels: ['learning', 'coding'],
});
console.log('Created task:', task1.title);

const task2 = TaskModule.createTask({
  title: 'Build portfolio project',
  priority: 'medium',
  status: 'in-progress',
});
console.log('Created task:', task2.title);

console.log('All tasks:', TaskModule.getAllTasks().length);
console.log('High priority:', TaskModule.getTasks({ priority: 'high' }).length);

// Demo Dashboard Analytics
console.log('\n--- Dashboard Analytics Demo ---');
const analytics = new DashboardAnalytics();

// Demo Chat Manager
console.log('\n--- Chat System Demo ---');
const chatManager = new ChatManager();
const room = chatManager.createRoom('general', { name: 'General Chat' });

room.join({ id: 'user1', name: 'Alice' });
room.join({ id: 'user2', name: 'Bob' });

room.sendMessage('user1', 'Hello everyone!');
room.sendMessage('user2', 'Hi Alice!');

console.log('Room participants:', room.participants.size);
console.log('Messages:', room.getMessages().length);

// Demo Code Sandbox
console.log('\n--- Code Sandbox Demo ---');
console.log('CodeSandbox ready for browser execution');

// Export for use
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    EventBus,
    Store,
    AuthModule,
    TaskModule,
    VirtualList,
    SimpleChart,
    DashboardAnalytics,
    MessageEncryption,
    ChatRoom,
    ChatManager,
    CodeSandbox,
    CodeEditorState,
  };
}
Examples - JavaScript Tutorial | DeepML