diff --git a/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.html b/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.html index cb3014bd..f6fd51e1 100644 --- a/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.html +++ b/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.html @@ -1,9 +1,11 @@
@if (clientId() && agentId()) { @@ -88,6 +90,14 @@
Ver }
+ +
+
@if (visibleTabs().length > 0) { diff --git a/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.scss b/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.scss index d96e543b..9b268e66 100644 --- a/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.scss +++ b/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.scss @@ -25,7 +25,8 @@ .file-tree-sidebar { flex: 0 0 300px; - min-width: 0; + min-width: 150px; + max-width: 600px; min-height: 0; overflow: hidden; background-color: var(--bs-body-bg); @@ -53,6 +54,34 @@ } } +.resize-handle { + flex: 0 0 4px; + cursor: col-resize; + background-color: transparent; + position: relative; + z-index: 10; + transition: background-color 0.15s ease-in-out; + + &::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -2px; + right: -2px; + z-index: 10; + } + + &:hover, + &:active { + background-color: var(--bs-primary); + } + + @media (max-width: 767.98px) { + display: none !important; + } +} + .file-tree-wrapper { min-height: 0; overflow: hidden; diff --git a/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.ts b/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.ts index deca1ec6..ee531129 100644 --- a/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.ts +++ b/libs/domains/framework/frontend/feature-agent-console/src/lib/file-editor/file-editor.component.ts @@ -8,6 +8,7 @@ import { ElementRef, inject, input, + NgZone, OnDestroy, output, signal, @@ -57,11 +58,13 @@ export class FileEditorComponent implements OnDestroy, AfterViewInit { private readonly destroyRef = inject(DestroyRef); private readonly actions$ = inject(Actions); private readonly location = inject(Location); + private readonly ngZone = inject(NgZone); @ViewChild('tabsContainer', { static: false }) tabsContainerRef?: ElementRef; @ViewChild('tabsWrapper', { static: false }) tabsWrapperRef?: ElementRef; @ViewChild('fileUpdateModal', { static: false }) fileUpdateModalRef?: ElementRef; @ViewChild('saveOverrideModal', { static: false }) saveOverrideModalRef?: ElementRef; + @ViewChild('fileTreeSidebar', { static: false }) fileTreeSidebarRef?: ElementRef; // Inputs clientId = input.required(); @@ -99,6 +102,15 @@ export class FileEditorComponent implements OnDestroy, AfterViewInit { // Track files we just saved to ignore our own notifications: filePath -> timestamp when saved private readonly recentlySavedFiles = signal>(new Map()); + // Sidebar resize state + private readonly SIDEBAR_MIN_WIDTH = 150; + private readonly SIDEBAR_MAX_WIDTH = 600; + private readonly SIDEBAR_DEFAULT_WIDTH = 300; + readonly sidebarWidth = signal(300); + private isResizing = false; + private readonly boundOnResizeMove = this.onResizeMove.bind(this); + private readonly boundOnResizeEnd = this.onResizeEnd.bind(this); + // Autosave debounce subject private readonly autosaveTrigger$ = new Subject(); @@ -1217,6 +1229,57 @@ export class FileEditorComponent implements OnDestroy, AfterViewInit { } } + /** Start sidebar resize on mousedown/touchstart */ + onResizeStart(event: MouseEvent | TouchEvent): void { + event.preventDefault(); + this.isResizing = true; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + + // Run outside Angular zone for performance during drag + this.ngZone.runOutsideAngular(() => { + document.addEventListener('mousemove', this.boundOnResizeMove); + document.addEventListener('mouseup', this.boundOnResizeEnd); + document.addEventListener('touchmove', this.boundOnResizeMove); + document.addEventListener('touchend', this.boundOnResizeEnd); + }); + } + + private onResizeMove(event: MouseEvent | TouchEvent): void { + if (!this.isResizing) return; + + const sidebarEl = this.fileTreeSidebarRef?.nativeElement; + if (!sidebarEl) return; + + const containerRect = sidebarEl.parentElement?.getBoundingClientRect(); + if (!containerRect) return; + + const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX; + let newWidth = clientX - containerRect.left; + newWidth = Math.max(this.SIDEBAR_MIN_WIDTH, Math.min(this.SIDEBAR_MAX_WIDTH, newWidth)); + + // Apply width directly on the element for smooth dragging + sidebarEl.style.flexBasis = `${newWidth}px`; + this.ngZone.run(() => { + this.sidebarWidth.set(newWidth); + }); + } + + private onResizeEnd(): void { + if (!this.isResizing) return; + this.isResizing = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + + document.removeEventListener('mousemove', this.boundOnResizeMove); + document.removeEventListener('mouseup', this.boundOnResizeEnd); + document.removeEventListener('touchmove', this.boundOnResizeMove); + document.removeEventListener('touchend', this.boundOnResizeEnd); + + // Recalculate tabs after resize + this.recalculateTabs(); + } + ngOnDestroy(): void { // Clear all open tabs when component is destroyed if (this.clientId() && this.agentId()) { @@ -1226,5 +1289,10 @@ export class FileEditorComponent implements OnDestroy, AfterViewInit { if (this.resizeObserver) { this.resizeObserver.disconnect(); } + // Clean up resize listeners in case component is destroyed mid-drag + document.removeEventListener('mousemove', this.boundOnResizeMove); + document.removeEventListener('mouseup', this.boundOnResizeEnd); + document.removeEventListener('touchmove', this.boundOnResizeMove); + document.removeEventListener('touchend', this.boundOnResizeEnd); } }