Files
hatty/src/renderer/js/split-manager.js
T

141 lines
3.6 KiB
JavaScript

import { TerminalView } from './terminal-view.js';
export class TabSplitTree {
constructor(options = {}) {
this.container = options.container;
this.tabId = options.tabId;
this.onActiveTerminalChange = options.onActiveTerminalChange || (() => {});
this.onEmpty = options.onEmpty || (() => {});
this.onTitleChange = options.onTitleChange || (() => {});
this.terminals = new Map(); // id -> TerminalView
this.activeTerminal = null;
this.rootElement = document.createElement('div');
this.rootElement.className = 'split-container horizontal';
this.container.appendChild(this.rootElement);
// 创建初始窗格
this.addPane(this.rootElement);
}
addPane(parentElement, orientation = 'horizontal') {
const paneElement = document.createElement('div');
paneElement.className = 'split-pane';
parentElement.appendChild(paneElement);
const termView = new TerminalView({
container: paneElement,
onFocus: (term) => {
this.setActiveTerminal(term);
},
onTitleChange: (title) => {
if (this.activeTerminal === termView) {
this.onTitleChange(title);
}
},
onExit: () => {
this.closeTerminal(termView.id);
},
});
this.terminals.set(termView.id, {
termView,
paneElement,
parentElement,
});
paneElement.addEventListener('click', () => {
termView.focus();
});
this.setActiveTerminal(termView);
return termView;
}
splitCurrent(direction = 'horizontal') {
if (!this.activeTerminal) return;
const currentInfo = this.terminals.get(this.activeTerminal.id);
if (!currentInfo) return;
const currentPane = currentInfo.paneElement;
const parent = currentPane.parentElement;
// 创建一个新的 split-container 容器替换原有 pane,实现嵌套分屏
const splitWrapper = document.createElement('div');
splitWrapper.className = `split-container ${direction}`;
parent.replaceChild(splitWrapper, currentPane);
splitWrapper.appendChild(currentPane);
// 添加分割线
const divider = document.createElement('div');
divider.className = 'split-divider';
splitWrapper.appendChild(divider);
// 添加新窗格
const newTerm = this.addPane(splitWrapper, direction);
// 刷新自适应尺寸
setTimeout(() => {
this.fitAll();
newTerm.focus();
}, 50);
}
setActiveTerminal(termView) {
this.activeTerminal = termView;
// 清除所有聚焦样式
for (const info of this.terminals.values()) {
info.paneElement.classList.remove('focused');
}
const currentInfo = this.terminals.get(termView.id);
if (currentInfo) {
currentInfo.paneElement.classList.add('focused');
}
this.onActiveTerminalChange(termView);
this.onTitleChange(termView.title);
}
closeTerminal(termId) {
const info = this.terminals.get(termId);
if (!info) return;
info.termView.destroy();
const pane = info.paneElement;
const parent = pane.parentElement;
this.terminals.delete(termId);
pane.remove();
if (this.terminals.size === 0) {
this.onEmpty();
return;
}
// 焦点转移给另一个终端
const nextTerm = this.terminals.values().next().value.termView;
this.setActiveTerminal(nextTerm);
nextTerm.focus();
this.fitAll();
}
fitAll() {
for (const info of this.terminals.values()) {
info.termView.fit();
}
}
destroy() {
for (const info of this.terminals.values()) {
info.termView.destroy();
}
this.terminals.clear();
this.rootElement.remove();
}
}