/** * 布局管理器 * 负责管理左右分栏布局和拖拽调整 */ export class LayoutManager { private container: HTMLElement; private leftPanel: HTMLElement; private rightPanel: HTMLElement; private splitter: HTMLElement; private isDragging: boolean = false; private startX: number = 0; private startLeftWidth: number = 0; constructor( container: HTMLElement, defaultRatio: number = 0.48, theme: string = "vs-dark", ) { this.container = container; // 创建布局 this.leftPanel = document.createElement("div"); this.splitter = document.createElement("div"); this.rightPanel = document.createElement("div"); this.initLayout(defaultRatio, theme); this.initResizable(); } /** * 初始化布局 */ private initLayout(ratio: number, theme: string): void { // 设置容器样式 this.container.style.display = "flex"; this.container.style.height = "100%"; this.container.style.position = "relative"; // Create a wrapper for the editor content const editorWrapper = document.createElement("div"); editorWrapper.className = "sce-editor-wrapper"; editorWrapper.style.display = "flex"; editorWrapper.style.height = "100%"; editorWrapper.style.width = "100%"; editorWrapper.style.overflow = "hidden"; // 左侧面板(试题区) this.leftPanel.className = "sce-question-panel"; this.leftPanel.style.width = `${ratio * 100}%`; this.leftPanel.style.minWidth = "200px"; this.leftPanel.style.overflow = "auto"; this.leftPanel.style.padding = "14px 6px 14px 14px"; this.leftPanel.style.boxSizing = "border-box"; // 分隔条 this.splitter.className = "sce-splitter"; this.splitter.style.width = "2px"; this.splitter.style.cursor = "col-resize"; this.splitter.style.flexShrink = "0"; this.splitter.style.userSelect = "none"; this.splitter.style.margin = "0 6px"; // Create a container for right side (will contain sce-editor-panel and terminal-panel) const rightContainer = document.createElement("div"); rightContainer.className = "sce-right-container"; rightContainer.style.flex = "1"; rightContainer.style.minWidth = "400px"; rightContainer.style.minHeight = "0"; rightContainer.style.display = "flex"; rightContainer.style.flexDirection = "column"; rightContainer.style.overflow = "hidden"; // 右侧面板(编辑器区 - 将与 terminal-panel 平级) this.rightPanel.className = "sce-editor-panel"; this.rightPanel.style.flex = "1"; this.rightPanel.style.minHeight = "0"; this.rightPanel.style.overflow = "hidden"; this.rightPanel.style.position = "relative"; this.rightPanel.style.display = "flex"; this.rightPanel.style.flexDirection = "column"; // 应用主题 this.setTheme(theme); // Add editor panel to right container rightContainer.appendChild(this.rightPanel); // 添加到 wrapper editorWrapper.appendChild(this.leftPanel); editorWrapper.appendChild(this.splitter); editorWrapper.appendChild(rightContainer); // 将 wrapper 添加到容器 this.container.appendChild(editorWrapper); } /** * 初始化拖拽功能 */ private initResizable(): void { this.splitter.addEventListener("mousedown", this.handleMouseDown); document.addEventListener("mousemove", this.handleMouseMove); document.addEventListener("mouseup", this.handleMouseUp); } /** * 鼠标按下 */ private handleMouseDown = (e: MouseEvent): void => { this.isDragging = true; this.startX = e.clientX; this.startLeftWidth = this.leftPanel.offsetWidth; // 添加拖拽样式 this.splitter.style.background = "#4CAF50"; document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; e.preventDefault(); }; /** * 鼠标移动 */ private handleMouseMove = (e: MouseEvent): void => { if (!this.isDragging) return; const deltaX = e.clientX - this.startX; const newLeftWidth = this.startLeftWidth + deltaX; const containerWidth = this.container.offsetWidth; // 限制最小和最大宽度 const minWidth = 200; const maxWidth = containerWidth - 400 - 8; // 右侧最小400px,分隔条8px if (newLeftWidth >= minWidth && newLeftWidth <= maxWidth) { const ratio = newLeftWidth / containerWidth; this.leftPanel.style.width = `${ratio * 100}%`; } }; /** * 鼠标释放 */ private handleMouseUp = (): void => { if (!this.isDragging) return; this.isDragging = false; this.splitter.style.background = "#f5f5f5"; document.body.style.cursor = ""; document.body.style.userSelect = ""; }; /** * 设置左侧内容 */ setLeftContent(content: string | HTMLElement): void { if (typeof content === "string") { this.leftPanel.innerHTML = content; this.leftPanel.style.overflow = "auto"; } else { this.leftPanel.innerHTML = ""; this.leftPanel.appendChild(content); if (content.classList.contains("sce-question-markdown")) { this.leftPanel.style.overflow = "hidden"; } else { this.leftPanel.style.overflow = "auto"; } } } /** * 获取右侧容器(用于放置编辑器) */ getRightContainer(): HTMLElement { return this.rightPanel; } /** * 显示/隐藏左侧面板 */ setQuestionPanelVisible(visible: boolean): void { if (visible) { this.leftPanel.style.display = "block"; this.splitter.style.display = "block"; } else { this.leftPanel.style.display = "none"; this.splitter.style.display = "none"; } } /** * 设置分割比例 */ setSplitRatio(ratio: number): void { this.leftPanel.style.width = `${ratio * 100}%`; } /** * 设置主题 */ setTheme(theme: string): void { const isDark = theme === "vs-dark"; if (isDark) { this.leftPanel.style.background = "#1e1e1e"; this.leftPanel.style.color = "#d4d4d4"; this.splitter.style.background = "#252526"; } else { this.leftPanel.style.background = "#ffffff"; this.leftPanel.style.color = "#333333"; this.splitter.style.background = "#f5f5f5"; } const editor = this.leftPanel.querySelector( ".sce-markdown-editor", ) as HTMLTextAreaElement | null; const preview = this.leftPanel.querySelector( ".sce-markdown-preview", ) as HTMLElement | null; const labels = this.leftPanel.querySelectorAll(".sce-markdown-label"); const tabs = this.leftPanel.querySelectorAll(".sce-markdown-tab"); if (editor) { editor.style.background = isDark ? "#1e1e1e" : "#ffffff"; editor.style.color = isDark ? "#d4d4d4" : "#333333"; editor.style.border = isDark ? "1px solid #333" : "1px solid #ddd"; } if (preview) { preview.style.background = isDark ? "#1b1b1b" : "#ffffff"; preview.style.color = isDark ? "#d4d4d4" : "#333333"; // preview.style.border = isDark ? "1px solid #333" : "1px solid #ddd"; } labels.forEach((label) => { (label as HTMLElement).style.color = isDark ? "#c5c5c5" : "#666666"; }); tabs.forEach((tab) => { const btn = tab as HTMLButtonElement; const isActive = btn.classList.contains("active"); btn.style.border = isDark ? "1px solid #3a3a3a" : "1px solid #ddd"; btn.style.background = isActive ? isDark ? "#2d2d30" : "#f3f3f3" : isDark ? "#1f1f1f" : "#ffffff"; btn.style.color = isDark ? "#d4d4d4" : "#333333"; btn.style.borderRadius = "6px"; btn.style.padding = "4px 10px"; btn.style.fontSize = "12px"; btn.style.cursor = "pointer"; }); } /** * 销毁 */ destroy(): void { document.removeEventListener("mousemove", this.handleMouseMove); document.removeEventListener("mouseup", this.handleMouseUp); this.container.innerHTML = ""; } }