feat(s3-columns): load full prefix path on initial load and add folder upload support
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-01-28 - 1.9.0 - feat(s3-columns)
|
||||||
|
load full prefix path on initial load and add folder upload support
|
||||||
|
|
||||||
|
- loadInitialColumn now loads all prefix path segments in parallel and pre-selects child prefixes so multi-column path is restored
|
||||||
|
- added getPathSegments helper and auto-scroll to show the rightmost column after load
|
||||||
|
- added separate hidden folder input (webkitdirectory) and folder upload flow; triggerFileUpload now accepts 'files' | 'folder'
|
||||||
|
- replaced generic 'Upload...' with 'Upload Files...' and added 'Upload Folder...' menu items
|
||||||
|
- updated updated() to react to currentPrefix changes and cleaned up folder input on disconnectedCallback
|
||||||
|
|
||||||
## 2026-01-28 - 1.8.1 - fix(cli)
|
## 2026-01-28 - 1.8.1 - fix(cli)
|
||||||
set executable permission on cli.js
|
set executable permission on cli.js
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tsview',
|
name: '@git.zone/tsview',
|
||||||
version: '1.8.1',
|
version: '1.9.0',
|
||||||
description: 'A CLI tool for viewing S3 and MongoDB data with a web UI'
|
description: 'A CLI tool for viewing S3 and MongoDB data with a web UI'
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tsview',
|
name: '@git.zone/tsview',
|
||||||
version: '1.8.1',
|
version: '1.9.0',
|
||||||
description: 'A CLI tool for viewing S3 and MongoDB data with a web UI'
|
description: 'A CLI tool for viewing S3 and MongoDB data with a web UI'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
private dragCounters: Map<number, number> = new Map();
|
private dragCounters: Map<number, number> = new Map();
|
||||||
private folderHoverTimer: ReturnType<typeof setTimeout> | null = null;
|
private folderHoverTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
private fileInputElement: HTMLInputElement | null = null;
|
private fileInputElement: HTMLInputElement | null = null;
|
||||||
|
private folderInputElement: HTMLInputElement | null = null;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@@ -380,10 +381,11 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
this.clearFolderHover();
|
this.clearFolderHover();
|
||||||
this.dragCounters.clear();
|
this.dragCounters.clear();
|
||||||
if (this.fileInputElement) { this.fileInputElement.remove(); this.fileInputElement = null; }
|
if (this.fileInputElement) { this.fileInputElement.remove(); this.fileInputElement = null; }
|
||||||
|
if (this.folderInputElement) { this.folderInputElement.remove(); this.folderInputElement = null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties: Map<string, unknown>) {
|
updated(changedProperties: Map<string, unknown>) {
|
||||||
if (changedProperties.has('bucketName')) {
|
if (changedProperties.has('bucketName') || changedProperties.has('currentPrefix')) {
|
||||||
this.loadInitialColumn();
|
this.loadInitialColumn();
|
||||||
} else if (changedProperties.has('refreshKey')) {
|
} else if (changedProperties.has('refreshKey')) {
|
||||||
this.refreshAllColumns();
|
this.refreshAllColumns();
|
||||||
@@ -393,16 +395,37 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
private async loadInitialColumn() {
|
private async loadInitialColumn() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const result = await apiService.listObjects(this.bucketName, this.currentPrefix, '/');
|
// Parse the path segments from currentPrefix
|
||||||
this.columns = [
|
// e.g., "folder1/folder2/folder3/" → ["folder1/", "folder1/folder2/", "folder1/folder2/folder3/"]
|
||||||
{
|
const pathSegments = this.getPathSegments(this.currentPrefix);
|
||||||
prefix: this.currentPrefix,
|
|
||||||
|
// Build all prefixes we need to load (including root)
|
||||||
|
const prefixesToLoad = ['', ...pathSegments];
|
||||||
|
|
||||||
|
// Load all columns in parallel
|
||||||
|
const columnResults = await Promise.all(
|
||||||
|
prefixesToLoad.map(prefix =>
|
||||||
|
apiService.listObjects(this.bucketName, prefix, '/')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build columns array with proper selections
|
||||||
|
this.columns = columnResults.map((result, index) => {
|
||||||
|
const prefix = prefixesToLoad[index];
|
||||||
|
// The selected item is the next prefix in the path (if any)
|
||||||
|
const selectedItem = index < pathSegments.length ? pathSegments[index] : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefix,
|
||||||
objects: result.objects,
|
objects: result.objects,
|
||||||
prefixes: result.prefixes,
|
prefixes: result.prefixes,
|
||||||
selectedItem: null,
|
selectedItem,
|
||||||
width: this.DEFAULT_COLUMN_WIDTH,
|
width: this.DEFAULT_COLUMN_WIDTH,
|
||||||
},
|
};
|
||||||
];
|
});
|
||||||
|
|
||||||
|
// Auto-scroll to show the rightmost column
|
||||||
|
this.updateComplete.then(() => this.scrollToEnd());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading objects:', err);
|
console.error('Error loading objects:', err);
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
@@ -410,6 +433,22 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to parse prefix into cumulative path segments
|
||||||
|
private getPathSegments(prefix: string): string[] {
|
||||||
|
if (!prefix) return [];
|
||||||
|
|
||||||
|
const parts = prefix.split('/').filter(p => p); // Remove empty strings
|
||||||
|
const segments: string[] = [];
|
||||||
|
let cumulative = '';
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
cumulative += part + '/';
|
||||||
|
segments.push(cumulative);
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
private async refreshAllColumns() {
|
private async refreshAllColumns() {
|
||||||
const updatedColumns = await Promise.all(
|
const updatedColumns = await Promise.all(
|
||||||
this.columns.map(async (col) => {
|
this.columns.map(async (col) => {
|
||||||
@@ -566,9 +605,14 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
action: async () => this.openCreateDialog('file', prefix),
|
action: async () => this.openCreateDialog('file', prefix),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Upload...',
|
name: 'Upload Files...',
|
||||||
iconName: 'lucide:upload',
|
iconName: 'lucide:file',
|
||||||
action: async () => this.triggerFileUpload(prefix),
|
action: async () => this.triggerFileUpload(prefix, 'files'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Upload Folder...',
|
||||||
|
iconName: 'lucide:folderUp',
|
||||||
|
action: async () => this.triggerFileUpload(prefix, 'folder'),
|
||||||
},
|
},
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{
|
{
|
||||||
@@ -654,9 +698,14 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
action: async () => this.openCreateDialog('file', prefix),
|
action: async () => this.openCreateDialog('file', prefix),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Upload...',
|
name: 'Upload Files...',
|
||||||
iconName: 'lucide:upload',
|
iconName: 'lucide:file',
|
||||||
action: async () => this.triggerFileUpload(prefix),
|
action: async () => this.triggerFileUpload(prefix, 'files'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Upload Folder...',
|
||||||
|
iconName: 'lucide:folderUp',
|
||||||
|
action: async () => this.triggerFileUpload(prefix, 'folder'),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -697,20 +746,27 @@ export class TsviewS3Columns extends DeesElement {
|
|||||||
|
|
||||||
// --- File upload helpers ---
|
// --- File upload helpers ---
|
||||||
|
|
||||||
private ensureFileInput(): HTMLInputElement {
|
private ensureFileInputs(): void {
|
||||||
if (!this.fileInputElement) {
|
if (!this.fileInputElement) {
|
||||||
this.fileInputElement = document.createElement('input');
|
this.fileInputElement = document.createElement('input');
|
||||||
this.fileInputElement.type = 'file';
|
this.fileInputElement.type = 'file';
|
||||||
this.fileInputElement.multiple = true;
|
this.fileInputElement.multiple = true;
|
||||||
(this.fileInputElement as any).webkitdirectory = true; // Enable folder selection
|
|
||||||
this.fileInputElement.style.display = 'none';
|
this.fileInputElement.style.display = 'none';
|
||||||
this.shadowRoot!.appendChild(this.fileInputElement);
|
this.shadowRoot!.appendChild(this.fileInputElement);
|
||||||
}
|
}
|
||||||
return this.fileInputElement;
|
if (!this.folderInputElement) {
|
||||||
|
this.folderInputElement = document.createElement('input');
|
||||||
|
this.folderInputElement.type = 'file';
|
||||||
|
this.folderInputElement.multiple = true;
|
||||||
|
(this.folderInputElement as any).webkitdirectory = true;
|
||||||
|
this.folderInputElement.style.display = 'none';
|
||||||
|
this.shadowRoot!.appendChild(this.folderInputElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private triggerFileUpload(targetPrefix: string) {
|
private triggerFileUpload(targetPrefix: string, type: 'files' | 'folder') {
|
||||||
const input = this.ensureFileInput();
|
this.ensureFileInputs();
|
||||||
|
const input = type === 'folder' ? this.folderInputElement! : this.fileInputElement!;
|
||||||
input.value = '';
|
input.value = '';
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
input.removeEventListener('change', handler);
|
input.removeEventListener('change', handler);
|
||||||
|
|||||||
Reference in New Issue
Block a user