Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<div class="file-editor-container d-flex h-100">
<!-- File Tree Sidebar with Git Manager below -->
<div
#fileTreeSidebar
class="file-tree-sidebar border-end d-flex flex-column"
[class.d-none]="!fileTreeVisible() && !gitManagerVisible()"
[class.mobile-overlay]="fileTreeVisible() || gitManagerVisible()"
[style.flex-basis.px]="sidebarWidth()"
>
@if (clientId() && agentId()) {
<!-- File Tree Header with Back Button (mobile only, hidden when only git manager is visible) -->
Expand Down Expand Up @@ -88,6 +90,14 @@ <h5 class="mb-0 fw-semibold" i18n="@@featureFileEditor-versionControlHeader">Ver
}
</div>

<!-- Resize Handle -->
<div
class="resize-handle"
[class.d-none]="!fileTreeVisible() && !gitManagerVisible()"
(mousedown)="onResizeStart($event)"
(touchstart)="onResizeStart($event)"
></div>

<!-- Editor Area -->
<div class="editor-area flex-grow-1 d-flex flex-column">
@if (visibleTabs().length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ElementRef,
inject,
input,
NgZone,
OnDestroy,
output,
signal,
Expand Down Expand Up @@ -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<HTMLDivElement>;
@ViewChild('tabsWrapper', { static: false }) tabsWrapperRef?: ElementRef<HTMLDivElement>;
@ViewChild('fileUpdateModal', { static: false }) fileUpdateModalRef?: ElementRef<HTMLDivElement>;
@ViewChild('saveOverrideModal', { static: false }) saveOverrideModalRef?: ElementRef<HTMLDivElement>;
@ViewChild('fileTreeSidebar', { static: false }) fileTreeSidebarRef?: ElementRef<HTMLDivElement>;

// Inputs
clientId = input.required<string>();
Expand Down Expand Up @@ -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<Map<string, number>>(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<number>(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<void>();

Expand Down Expand Up @@ -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()) {
Expand All @@ -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);
}
}
Loading