import { Editor } from '../../editor/Editor.js'; import { EditorPanel, EditorPanelDefinition, FileEditorPanel, FileEditorPanelDefinition, implementsInterface } from '../../editor/editor-panel.js'; import { ContextMenuDirectory, ContextMenuEntry, ContextMenuSeparator } from '../context-menu/context-menu.js'; import { showConfirmDialog } from '../confirm-dialog/confirm-dialog.js'; interface TabEntry { id: string; label: string; panelType: string; element: HTMLElement; factory: () => HTMLElement; } export { EditorPanel }; class TabContainer extends HTMLElement { tabs: TabEntry[] = []; activeId: string | null = null; uid = Math.random().toString(36).slice(2); connectedCallback(): void { if (!this.id) this.id = 'tc-' + this.uid; this.classList.add('tab-container'); this.innerHTML = `
`; this.querySelector('.tc-menu')!.addEventListener('click', (e: Event) => { this.openMenu(e as MouseEvent); }); this.addEventListener('dragover', (e: DragEvent) => { if (e.dataTransfer?.types.includes('application/editor-tab')) { e.preventDefault(); this.classList.add('tc-drop-target'); } }); this.addEventListener('dragleave', () => this.classList.remove('tc-drop-target')); this.addEventListener('drop', (e: DragEvent) => { e.preventDefault(); this.classList.remove('tc-drop-target'); const raw = e.dataTransfer?.getData('application/editor-tab'); if (!raw) return; const { tabId, sourceContainerId } = JSON.parse(raw) as { tabId: string; sourceContainerId: string }; if (sourceContainerId === this.id) return; const source = document.getElementById(sourceContainerId) as TabContainer | null; if (!source) return; const tab = source.extractTab(tabId); if (tab) this.receiveTab(tab); }); this.addEventListener( 'panel:label-change', ( e: Event ) => { const panel = e.target as HTMLElement; const tab = this.tabs.find( t => t.element === panel ); if ( tab ) { tab.label = ( e as CustomEvent ).detail.label; this.renderBar(); } } ); Editor.get().onDocumentDirty.addListener( () => this.renderBar() ); Editor.get().onDocumentSaved.addListener( () => this.renderBar() ); } openMenu(e: MouseEvent): void { const btn = e.currentTarget as HTMLElement; const rect = btn.getBoundingClientRect(); const root = new ContextMenuDirectory(null); const addDir = new ContextMenuDirectory(root, 'Add'); const panelTypes = [ { label: 'Page Editor', panelType: 'page-editor', tag: 'page-editor-panel' }, { label: 'Code Editor', panelType: 'code-panel', tag: 'code-panel' }, { label: 'File Tree', panelType: 'file-tree', tag: 'file-tree-panel' }, { label: 'Rojo Chat', panelType: 'rojo-chat', tag: 'rojo-chat-panel' }, { label: 'Rojo Settings', panelType: 'rojo-settings', tag: 'rojo-settings-panel' }, ]; for (const pt of panelTypes) { addDir.add(new ContextMenuEntry(addDir, pt.label, () => { this.dispatchEvent(new CustomEvent('tab-container:add-panel', { bubbles: true, detail: { containerId: this.id, panelType: pt.panelType, tag: pt.tag, label: pt.label }, })); })); } root.add(addDir); root.add(new ContextMenuEntry(root, 'Duplicate', () => this.duplicateActive())); const splitDir = new ContextMenuDirectory(root, 'Split'); splitDir.add(new ContextMenuEntry(splitDir, '↔ Horizontally', () => { this.dispatchEvent(new CustomEvent('tab-container:split', { bubbles: true, detail: { containerId: this.id, direction: 'horizontal' }, })); })); splitDir.add(new ContextMenuEntry(splitDir, '↕ Vertically', () => { this.dispatchEvent(new CustomEvent('tab-container:split', { bubbles: true, detail: { containerId: this.id, direction: 'vertical' }, })); })); root.add(splitDir); const panelCount = this.closest('.es-panel')?.querySelectorAll('tab-container').length ?? 1; if (panelCount > 1) { root.add(new ContextMenuEntry(root, 'Close Container', () => this.closeContainer())); } const activeTab = this.tabs.find(t => t.id === this.activeId); if (activeTab) { const panel = activeTab.element as EditorPanel; if (typeof panel.addContextMenuEntries === 'function') { root.add(new ContextMenuSeparator(root)); panel.addContextMenuEntries(root); } } root.add(new ContextMenuSeparator(root)); root.add(new ContextMenuEntry(root, 'Close', () => this.closeActive())); root.show(rect.left, rect.bottom); } async closeContainer(): Promise