layoutManager.js

10.20 KB
12/10/2025 04:23
JS
layoutManager.js
/**
 * Layout Manager Module
 * จัดการการแสดงผลและการจัดการโครงสร้าง DOM
 */
(function(global) {
  'use strict';

  /**
   * คลาส LayoutManager
   * ให้ความสามารถในการจัดการโครงสร้าง DOM และการแสดงผล
   */
  class LayoutManager {
    constructor(editor) {
      this.editor = editor;
      this.layoutTree = null;
      this.selectedElement = null;
      this.isResizing = false;
      this.resizeHandle = null;
      this.resizeStartX = 0;
      this.resizeStartY = 0;
      this.resizeStartWidth = 0;
      this.resizeStartHeight = 0;
    }

    /**
     * เริ่มต้นโมดูลจัดการเลย์เอาต์
     */
    init() {
      this.setupEventListeners();
      this.buildLayoutTree();

      if (this.editor.config.debug) {
        console.log('LayoutManager เริ่มต้นแล้ว');
      }
    }

    /**
     * ตั้งค่า event listeners
     */
    setupEventListeners() {
      // ฟังเหตุการณ์การเลือกอิลิเมนต์
      this.editor.on('element:selected', (data) => {
        this.selectElement(data.element);
      });

      // ฟังเหตุการณ์การเปลี่ยนแปลงโหมดตัวแก้ไข
      this.editor.on('editor:mode-changed', (data) => {
        if (data.mode === 'edit') {
          this.showLayoutPanel();
        } else {
          this.hideLayoutPanel();
        }
      });

      // ฟังเหตุการณ์การวางคอมโพเนนต์
      this.editor.on('dragdrop:component-dropped', () => {
        this.buildLayoutTree();
      });

      // ฟังเหตุการณ์การวางอิลิเมนต์
      this.editor.on('dragdrop:element-dropped', () => {
        this.buildLayoutTree();
      });
    }

    /**
     * สร้างต้นไม้เลย์เอาต์
     */
    buildLayoutTree() {
      this.layoutTree = document.getElementById('editor-layout-tree');
      if (!this.layoutTree) return;

      // ล้างต้นไม้เก่า
      this.layoutTree.innerHTML = '';

      // สร้างต้นไม้ใหม่
      this.buildLayoutTreeRecursive(this.editor.container, this.layoutTree, 0);
    }

    /**
     * สร้างต้นไม้เลย์เอาต์แบบเรียกซ้ำ
     */
    buildLayoutTreeRecursive(element, parent, level) {
      // สร้าง ID สำหรับอิลิเมนต์
      if (!element.getAttribute('data-element-id')) {
        element.setAttribute('data-element-id', 'element-' + Math.random().toString(36).substr(2, 9));
      }

      const elementId = element.getAttribute('data-element-id');

      // สร้างรายการต้นไม้
      const item = this.editor.domUtils.createElement('div', {
        'class': 'editor-layout-tree-item',
        'data-element-id': elementId,
        'style': `padding-left: ${level * 20}px`
      });

      // สร้างชื่อแท็ก
      const tagName = element.tagName.toLowerCase();
      const className = element.className ? '.' + element.className.split(' ').join('.') : '';
      const name = this.editor.domUtils.createElement('span', {
        'class': 'tree-element-name'
      }, `${tagName}${className}`);

      // สร้างไอคอน
      const icon = this.editor.domUtils.createElement('i', {
        'class': 'material-icons tree-icon'
      }, this.getElementIcon(element));

      // สร้างปุ่มดำเนินการ
      const actions = this.editor.domUtils.createElement('div', {
        'class': 'tree-actions'
      });

      const selectBtn = this.editor.domUtils.createElement('button', {
        'class': 'tree-action-btn',
        'title': 'เลือก'
      }, '<i class="material-icons">edit</i>');

      const visibilityBtn = this.editor.domUtils.createElement('button', {
        'class': 'tree-action-btn',
        'title': element.style.display === 'none' ? 'แสดง' : 'ซ่อน'
      }, `<i class="material-icons">${element.style.display === 'none' ? 'visibility_off' : 'visibility'}</i>`);

      const moveUpBtn = this.editor.domUtils.createElement('button', {
        'class': 'tree-action-btn',
        'title': 'ย้ายขึ้น'
      }, '<i class="material-icons">keyboard_arrow_up</i>');

      const moveDownBtn = this.editor.domUtils.createElement('button', {
        'class': 'tree-action-btn',
        'title': 'ย้ายลง'
      }, '<i class="material-icons">keyboard_arrow_down</i>');

      const deleteBtn = this.editor.domUtils.createElement('button', {
        'class': 'tree-action-btn',
        'title': 'ลบ'
      }, '<i class="material-icons">delete</i>');

      actions.appendChild(selectBtn);
      actions.appendChild(visibilityBtn);
      actions.appendChild(moveUpBtn);
      actions.appendChild(moveDownBtn);
      actions.appendChild(deleteBtn);

      // เพิ่มอิลิเมนต์ในรายการ
      item.appendChild(icon);
      item.appendChild(name);
      item.appendChild(actions);

      // เพิ่มรายการในพาเรนต์
      parent.appendChild(item);

      // เพิ่ม event listeners
      selectBtn.addEventListener('click', () => {
        this.selectElement(element);
      });

      visibilityBtn.addEventListener('click', () => {
        if (element.style.display === 'none') {
          element.style.display = '';
          visibilityBtn.innerHTML = '<i class="material-icons">visibility</i>';
        } else {
          element.style.display = 'none';
          visibilityBtn.innerHTML = '<i class="material-icons">visibility_off</i>';
        }
      });

      moveUpBtn.addEventListener('click', () => {
        this.moveElementUp(element);
      });

      moveDownBtn.addEventListener('click', () => {
        this.moveElementDown(element);
      });

      deleteBtn.addEventListener('click', () => {
        this.deleteElement(element);
      });

      // สร้างรายการย่อยสำหรับอิลิเมนต์ลูก
      const children = Array.from(element.children);
      if (children.length > 0) {
        const childrenContainer = this.editor.domUtils.createElement('div', {
          'class': 'tree-children'
        });

        children.forEach(child => {
          this.buildLayoutTreeRecursive(child, childrenContainer, level + 1);
        });

        parent.appendChild(childrenContainer);
      }
    }

    /**
     * รับไอคอนสำหรับอิลิเมนต์
     */
    getElementIcon(element) {
      const tagName = element.tagName.toLowerCase();

      switch (tagName) {
        case 'header':
          return 'header';
        case 'footer':
          return 'footer';
        case 'nav':
          return 'menu';
        case 'section':
          return 'view_module';
        case 'article':
          return 'article';
        case 'div':
          return 'crop_square';
        case 'img':
          return 'image';
        case 'p':
          return 'text_fields';
        case 'h1':
        case 'h2':
        case 'h3':
        case 'h4':
        case 'h5':
        case 'h6':
          return 'title';
        case 'ul':
        case 'ol':
          return 'list';
        case 'li':
          return 'label';
        case 'a':
          return 'link';
        case 'button':
          return 'touch_app';
        case 'form':
          return 'input';
        case 'input':
        case 'textarea':
        case 'select':
          return 'text_format';
        default:
          return 'code';
      }
    }

    /**
     * เลือกอิลิเมนต์
     */
    selectElement(element) {
      this.selectedElement = element;

      // ลบการเลือกก่อนหน้า
      const previousSelected = this.layoutTree.querySelectorAll('.tree-selected');
      previousSelected.forEach(el => {
        el.classList.remove('tree-selected');
      });

      // เลือกอิลิเมนต์ใหม่ในต้นไม้
      const treeElement = this.layoutTree.querySelector(`[data-element-id="${element.getAttribute('data-element-id') || ''}"]`);
      if (treeElement) {
        treeElement.classList.add('tree-selected');
      }

      // ส่งเหตุการณ์
      this.editor.emit('layout:element-selected', {element});
    }

    /**
     * แสดงแผงจัดการเลย์เอาต์
     */
    showLayoutPanel() {
      this.editor.uiManager.showPanel('layout');
      this.buildLayoutTree();
    }

    /**
     * ซ่อนแผงจัดการเลย์เอาต์
     */
    hideLayoutPanel() {
      this.editor.uiManager.hidePanel('layout');
    }

    /**
     * ย้ายอิลิเมนต์ขึ้น
     */
    moveElementUp(element) {
      const previousSibling = element.previousElementSibling;
      if (previousSibling) {
        element.parentNode.insertBefore(element, previousSibling);
        this.buildLayoutTree();
        this.editor.emit('layout:element-moved', {element, direction: 'up'});
      }
    }

    /**
     * ย้ายอิลิเมนต์ลง
     */
    moveElementDown(element) {
      const nextSibling = element.nextElementSibling;
      if (nextSibling) {
        element.parentNode.insertBefore(nextSibling, element);
        this.buildLayoutTree();
        this.editor.emit('layout:element-moved', {element, direction: 'down'});
      }
    }

    /**
     * ลบอิลิเมนต์
     */
    deleteElement(element) {
      if (confirm('คุณแน่ใจหรือไม่ว่าต้องการลบอิลิเมนต์นี้?')) {
        element.remove();
        this.buildLayoutTree();
        this.editor.emit('layout:element-deleted', {element});
      }
    }
  }

  // เปิดเผยคลาส LayoutManager ทั่วโลก
  global.LayoutManager = LayoutManager;

})(typeof window !== 'undefined' ? window : this);