universal_table/app/assets/javascripts/mind_map/utils/custom.toolbar.js

267 lines
8.3 KiB
JavaScript

import { util } from '../jsmind/jsmind.util.js'
import { getRelativePosition } from './custom.util.js'
import { PALETTE_COLORS } from './custom.config.js'
const TOOLBAR_ID = 'jsmind-toolbar'
/**
* jsMind 工具列管理
* jsMind Toolbar Manager
*/
export class JsmindToolbar {
/**
* 建構工具列
* Constructor for toolbar
* @param {Object} jm - jsMind 實例 (jsMind instance)
* @param {Object} options - jsMind 實例 (options)
*/
constructor(jm, options) {
this.jm = jm
this.container = document.getElementById(jm.options.container)
this.toolbarNodeId = null
this.toolbar = null
this.bgColorPalette = null
this.strokeColorPalette = null
this.textColorPalette = null
this.options = options
this.init()
}
/**
* 初始化工具列事件
* Initialize toolbar events
*/
init() {
// 監聽節點選取事件
// Listen for node selection events
this.jm.add_event_listener((e, f, g) => {
// 忽略非選擇節點事件
// Ignore non-selection events
if (e !== 4) return
const node = this.jm.get_selected_node()
if (!node || node.id === this.toolbarNodeId) return
this.toolbarNodeId = node.id
if (!this.toolbar) {
this.createToolbar()
}
this.moveToolbar(node)
})
// 確保不會重複綁定點擊事件
// Ensure click event is not bound multiple times
this.container.removeEventListener('click', this.onClickOutside)
this.container.addEventListener('click', this.onClickOutside.bind(this))
}
/**
* 處理點擊事件來隱藏工具列
* Handle click event to hide toolbar
* @param {Event} e - 事件對象 (Event object)
*/
onClickOutside(e) {
const clickedNode = e.target.tagName === 'JMNODE'
const clickedToolbar = e.target.closest(`#${TOOLBAR_ID}`)
if (!clickedNode && !clickedToolbar && this.toolbar) {
this.hideToolbar()
}
}
/**
* 建立工具列 UI
* Create toolbar UI
*/
createToolbar() {
this.toolbar = document.createElement('div')
this.toolbar.id = TOOLBAR_ID
// 建立工具列按鈕
// Create toolbar buttons
const buttons = [
{ id: 'toolbar-add-child-btn', text: this.options.text.addNode, onClick: this.handleAddChild.bind(this) },
{ id: 'toolbar-delete-btn', text: this.options.text.deleteNode, onClick: this.handleDelete.bind(this) },
{
id: 'toolbar-stroke-color-btn',
text: this.options.text.strokeColor,
onClick: this.handleStrokeColor.bind(this),
},
{
id: 'toolbar-bg-color-btn',
text: this.options.text.bgColor,
onClick: this.handleBgColor.bind(this),
},
{
id: 'toolbar-text-color-btn',
text: this.options.text.textColor,
onClick: this.handleTextColor.bind(this),
},
]
buttons.forEach((button) => {
const btn = document.createElement('button')
btn.id = button.id
btn.innerText = button.text
btn.onclick = button.onClick
this.toolbar.appendChild(btn)
// 附加顏色選單
// Append color palettes to corresponding buttons
if (button.id === 'toolbar-bg-color-btn') {
this.bgColorPalette = this.createColorPalette(
(color) => this.setNodeStyle('background-color', color),
btn
)
}
if (button.id === 'toolbar-stroke-color-btn') {
this.strokeColorPalette = this.createColorPalette(
(color) => this.setNodeStyle('leading-line-color', color),
btn
)
}
if (button.id === 'toolbar-text-color-btn') {
this.textColorPalette = this.createColorPalette(
(color) => this.setNodeStyle('foreground-color', color),
btn
)
}
})
this.container.appendChild(this.toolbar)
}
/**
* 移動工具列至選中節點
* Move the toolbar to the selected node
*/
moveToolbar(node) {
const nodeElement = node._data.view.element
if (!nodeElement) return
const { left, top } = getRelativePosition(nodeElement, this.container)
this.toolbar.style.left = `${left}px`
this.toolbar.style.top = `${top - 40}px`
this.toolbar.style.display = 'block'
// 根節點則隱藏刪除與線條顏色按鈕
// Hide delete & stroke color buttons if the node is root
const deleteBtn = this.toolbar.querySelector('#toolbar-delete-btn')
const strokeColorBtn = this.toolbar.querySelector('#toolbar-stroke-color-btn')
if (node.id === 'root') {
deleteBtn.style.display = 'none'
strokeColorBtn.style.display = 'none'
} else {
deleteBtn.style.display = 'inline-block'
strokeColorBtn.style.display = 'inline-block'
}
}
/**
* 隱藏工具列
* Hide the toolbar
*/
hideToolbar() {
this.toolbar.style.display = 'none'
this.bgColorPalette.style.display = 'none'
this.strokeColorPalette.style.display = 'none'
this.textColorPalette.style.display = 'none'
this.toolbarNodeId = null
}
/**
* 建立顏色選單
* Create color palette
*/
createColorPalette(onSelect, button) {
const colorPalette = document.createElement('div')
colorPalette.classList.add('toolbar-color-palette')
colorPalette.style.display = 'none'
PALETTE_COLORS.forEach((color) => {
const colorBox = document.createElement('div')
colorBox.classList.add('toolbar-color-palette-box')
colorBox.style.backgroundColor = color
colorBox.onclick = () => {
onSelect(color)
colorPalette.style.display = 'none'
}
colorPalette.appendChild(colorBox)
})
button.appendChild(colorPalette)
return colorPalette
}
/**
* 設定節點樣式
* Set node style
*/
setNodeStyle(style, color) {
if (!this.toolbarNodeId) return
const node = this.jm.get_node(this.toolbarNodeId)
if (!node) return
node.data[style] = color
if (style === 'leading-line-color') this.jm.view.show_lines()
else this.jm.view.restore_selected_node_custom_style(node)
}
/**
* 處理新增節點事件
* Handle add child node event
*/
handleAddChild(e) {
e.preventDefault();
e.stopPropagation()
if (!this.toolbarNodeId) return
const node = this.jm.get_node(this.toolbarNodeId)
if (!node) return
const newNode = this.jm.add_node(node, util.uuid.newid(), 'NewNode')
this.jm.select_node(newNode)
}
/**
* 處理刪除節點事件
* Handle delete node event
*/
handleDelete(e) {
e.preventDefault();
e.stopPropagation()
if (!this.toolbarNodeId) return
const node = this.jm.get_node(this.toolbarNodeId)
if (!node) return
this.jm.remove_node(node)
this.hideToolbar()
}
/**
* 處理其他樣式設定事件
* Handle style setting event
*/
handleStrokeColor(e) {
e.preventDefault();
e.stopPropagation()
this.toggleColorPalette(this.strokeColorPalette)
}
handleBgColor(e) {
e.preventDefault();
e.stopPropagation()
this.toggleColorPalette(this.bgColorPalette)
}
handleTextColor(e) {
e.preventDefault();
e.stopPropagation()
this.toggleColorPalette(this.textColorPalette)
}
/**
* 顯示或隱藏顏色選單
* Toggle color palette display
*/
toggleColorPalette(palette) {
;[this.bgColorPalette, this.strokeColorPalette, this.textColorPalette].forEach((p) => {
if (p !== palette) p.style.display = 'none'
})
palette.style.display = palette.style.display === 'block' ? 'none' : 'block'
}
}