feat: add resizable columns and horizontal scrolling
- Columns can be resized by dragging the border between them - Column widths persist during navigation (150px min, 500px max) - Container scrolls horizontally when columns exceed available space - Auto-scrolls to show newly opened columns - Resize handle highlights on hover/active state
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ interface IColumn {
|
|||||||
objects: IS3Object[];
|
objects: IS3Object[];
|
||||||
prefixes: string[];
|
prefixes: string[];
|
||||||
selectedItem: string | null;
|
selectedItem: string | null;
|
||||||
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('tsview-s3-columns')
|
@customElement('tsview-s3-columns')
|
||||||
@@ -24,6 +25,11 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
@state()
|
@state()
|
||||||
private accessor loading: boolean = false;
|
private accessor loading: boolean = false;
|
||||||
|
|
||||||
|
private resizing: { columnIndex: number; startX: number; startWidth: number } | null = null;
|
||||||
|
private readonly DEFAULT_COLUMN_WIDTH = 250;
|
||||||
|
private readonly MIN_COLUMN_WIDTH = 150;
|
||||||
|
private readonly MAX_COLUMN_WIDTH = 500;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
@@ -31,25 +37,57 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-container {
|
.columns-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 100%;
|
min-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-wrapper {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
min-width: 220px;
|
|
||||||
max-width: 280px;
|
|
||||||
border-right: 1px solid #333;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column:last-child {
|
.resize-handle {
|
||||||
border-right: none;
|
width: 5px;
|
||||||
|
height: 100%;
|
||||||
|
background: transparent;
|
||||||
|
cursor: col-resize;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 2px;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle:hover::after,
|
||||||
|
.resize-handle.active::after {
|
||||||
|
background: #6366f1;
|
||||||
|
width: 2px;
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-wrapper:last-child .resize-handle {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-header {
|
.column-header {
|
||||||
@@ -151,6 +189,7 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
objects: result.objects,
|
objects: result.objects,
|
||||||
prefixes: result.prefixes,
|
prefixes: result.prefixes,
|
||||||
selectedItem: null,
|
selectedItem: null,
|
||||||
|
width: this.DEFAULT_COLUMN_WIDTH,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -182,8 +221,11 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
objects: result.objects,
|
objects: result.objects,
|
||||||
prefixes: result.prefixes,
|
prefixes: result.prefixes,
|
||||||
selectedItem: null,
|
selectedItem: null,
|
||||||
|
width: this.DEFAULT_COLUMN_WIDTH,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
// Auto-scroll to show the new column
|
||||||
|
this.updateComplete.then(() => this.scrollToEnd());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading folder:', err);
|
console.error('Error loading folder:', err);
|
||||||
}
|
}
|
||||||
@@ -192,6 +234,48 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
// The navigate event is only for breadcrumb sync, not for column navigation
|
// The navigate event is only for breadcrumb sync, not for column navigation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scrollToEnd() {
|
||||||
|
this.scrollLeft = this.scrollWidth - this.clientWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startResize(e: MouseEvent, columnIndex: number) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.resizing = {
|
||||||
|
columnIndex,
|
||||||
|
startX: e.clientX,
|
||||||
|
startWidth: this.columns[columnIndex].width,
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', this.handleResize);
|
||||||
|
document.addEventListener('mouseup', this.stopResize);
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleResize = (e: MouseEvent) => {
|
||||||
|
if (!this.resizing) return;
|
||||||
|
|
||||||
|
const delta = e.clientX - this.resizing.startX;
|
||||||
|
const newWidth = Math.min(
|
||||||
|
this.MAX_COLUMN_WIDTH,
|
||||||
|
Math.max(this.MIN_COLUMN_WIDTH, this.resizing.startWidth + delta)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.columns = this.columns.map((col, i) => {
|
||||||
|
if (i === this.resizing!.columnIndex) {
|
||||||
|
return { ...col, width: newWidth };
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private stopResize = () => {
|
||||||
|
this.resizing = null;
|
||||||
|
document.removeEventListener('mousemove', this.handleResize);
|
||||||
|
document.removeEventListener('mouseup', this.stopResize);
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
};
|
||||||
|
|
||||||
private selectFile(columnIndex: number, key: string) {
|
private selectFile(columnIndex: number, key: string) {
|
||||||
// Update selection
|
// Update selection
|
||||||
this.columns = this.columns.map((col, i) => {
|
this.columns = this.columns.map((col, i) => {
|
||||||
@@ -240,7 +324,19 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="columns-container">
|
<div class="columns-container">
|
||||||
${this.columns.map((column, index) => this.renderColumn(column, index))}
|
${this.columns.map((column, index) => this.renderColumnWrapper(column, index))}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderColumnWrapper(column: IColumn, index: number) {
|
||||||
|
return html`
|
||||||
|
<div class="column-wrapper">
|
||||||
|
${this.renderColumn(column, index)}
|
||||||
|
<div
|
||||||
|
class="resize-handle ${this.resizing?.columnIndex === index ? 'active' : ''}"
|
||||||
|
@mousedown=${(e: MouseEvent) => this.startResize(e, index)}
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -251,7 +347,7 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
: this.bucketName;
|
: this.bucketName;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="column">
|
<div class="column" style="width: ${column.width}px">
|
||||||
<div class="column-header" title=${column.prefix || this.bucketName}>
|
<div class="column-header" title=${column.prefix || this.bucketName}>
|
||||||
${headerName}
|
${headerName}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user