feat(s3): add rename support for files and folders in S3 UI columns and keys
This commit is contained in:
@@ -106,6 +106,22 @@ export class TsviewS3Columns extends DeesElement {
|
||||
@state()
|
||||
private accessor movePickerLoading: boolean = false;
|
||||
|
||||
// Rename dialog state
|
||||
@state()
|
||||
private accessor showRenameDialog: boolean = false;
|
||||
|
||||
@state()
|
||||
private accessor renameSource: { key: string; isFolder: boolean } | null = null;
|
||||
|
||||
@state()
|
||||
private accessor renameName: string = '';
|
||||
|
||||
@state()
|
||||
private accessor renameInProgress: boolean = false;
|
||||
|
||||
@state()
|
||||
private accessor renameError: string | null = null;
|
||||
|
||||
// Internal drag state
|
||||
@state()
|
||||
private accessor draggedItem: { key: string; isFolder: boolean } | null = null;
|
||||
@@ -757,6 +773,11 @@ export class TsviewS3Columns extends DeesElement {
|
||||
await navigator.clipboard.writeText(prefix);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Rename',
|
||||
iconName: 'lucide:pencil',
|
||||
action: async () => this.openRenameDialog(prefix, true),
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
name: 'Move to...',
|
||||
@@ -833,6 +854,11 @@ export class TsviewS3Columns extends DeesElement {
|
||||
await navigator.clipboard.writeText(key);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Rename',
|
||||
iconName: 'lucide:pencil',
|
||||
action: async () => this.openRenameDialog(key, false),
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
name: 'Move to...',
|
||||
@@ -1277,6 +1303,129 @@ export class TsviewS3Columns extends DeesElement {
|
||||
this.moveInProgress = false;
|
||||
}
|
||||
|
||||
// --- Rename dialog methods ---
|
||||
|
||||
private openRenameDialog(key: string, isFolder: boolean) {
|
||||
this.renameSource = { key, isFolder };
|
||||
this.renameName = getFileName(key);
|
||||
this.renameError = null;
|
||||
this.showRenameDialog = true;
|
||||
|
||||
// Auto-focus and smart selection
|
||||
this.updateComplete.then(() => {
|
||||
const input = this.shadowRoot?.querySelector('.rename-dialog-input') as HTMLInputElement;
|
||||
if (input) {
|
||||
input.focus();
|
||||
if (!isFolder) {
|
||||
const lastDot = this.renameName.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
input.setSelectionRange(0, lastDot);
|
||||
} else {
|
||||
input.select();
|
||||
}
|
||||
} else {
|
||||
input.select();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async executeRename() {
|
||||
if (!this.renameSource || !this.renameName.trim()) return;
|
||||
|
||||
const newName = this.renameName.trim();
|
||||
const currentName = getFileName(this.renameSource.key);
|
||||
|
||||
if (newName === currentName) {
|
||||
this.renameError = 'Name is the same as current';
|
||||
return;
|
||||
}
|
||||
if (!newName) {
|
||||
this.renameError = 'Name cannot be empty';
|
||||
return;
|
||||
}
|
||||
if (newName.includes('/')) {
|
||||
this.renameError = 'Name cannot contain "/"';
|
||||
return;
|
||||
}
|
||||
|
||||
this.renameInProgress = true;
|
||||
this.renameError = null;
|
||||
|
||||
try {
|
||||
const parentPrefix = getParentPrefix(this.renameSource.key);
|
||||
const newKey = parentPrefix + newName + (this.renameSource.isFolder ? '/' : '');
|
||||
|
||||
let result: { success: boolean; error?: string };
|
||||
if (this.renameSource.isFolder) {
|
||||
result = await apiService.movePrefix(this.bucketName, this.renameSource.key, newKey);
|
||||
} else {
|
||||
result = await apiService.moveObject(this.bucketName, this.renameSource.key, newKey);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
this.closeRenameDialog();
|
||||
await this.refreshAllColumns();
|
||||
} else {
|
||||
this.renameError = result.error || 'Rename failed';
|
||||
}
|
||||
} catch (err) {
|
||||
this.renameError = `Error: ${err}`;
|
||||
}
|
||||
this.renameInProgress = false;
|
||||
}
|
||||
|
||||
private closeRenameDialog() {
|
||||
this.showRenameDialog = false;
|
||||
this.renameSource = null;
|
||||
this.renameName = '';
|
||||
this.renameError = null;
|
||||
this.renameInProgress = false;
|
||||
}
|
||||
|
||||
private renderRenameDialog() {
|
||||
if (!this.showRenameDialog || !this.renameSource) return '';
|
||||
|
||||
const isFolder = this.renameSource.isFolder;
|
||||
const title = isFolder ? 'Rename Folder' : 'Rename File';
|
||||
|
||||
return html`
|
||||
<div class="dialog-overlay" @click=${() => this.closeRenameDialog()}>
|
||||
<div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
|
||||
<div class="dialog-title">${title}</div>
|
||||
<div class="dialog-location">
|
||||
Location: ${this.bucketName}/${getParentPrefix(this.renameSource.key)}
|
||||
</div>
|
||||
${this.renameError ? html`<div class="move-error">${this.renameError}</div>` : ''}
|
||||
<input
|
||||
type="text"
|
||||
class="dialog-input rename-dialog-input"
|
||||
placeholder=${isFolder ? 'folder-name' : 'filename.ext'}
|
||||
.value=${this.renameName}
|
||||
@input=${(e: InputEvent) => {
|
||||
this.renameName = (e.target as HTMLInputElement).value;
|
||||
this.renameError = null;
|
||||
}}
|
||||
@keydown=${(e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') this.executeRename();
|
||||
if (e.key === 'Escape') this.closeRenameDialog();
|
||||
}}
|
||||
/>
|
||||
<div class="dialog-actions">
|
||||
<button class="dialog-btn dialog-btn-cancel"
|
||||
@click=${() => this.closeRenameDialog()}
|
||||
?disabled=${this.renameInProgress}>Cancel</button>
|
||||
<button class="dialog-btn dialog-btn-create"
|
||||
@click=${() => this.executeRename()}
|
||||
?disabled=${this.renameInProgress || !this.renameName.trim()}>
|
||||
${this.renameInProgress ? 'Renaming...' : 'Rename'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderMoveDialog() {
|
||||
if (!this.showMoveDialog || !this.moveSource) return '';
|
||||
|
||||
@@ -1462,6 +1611,7 @@ export class TsviewS3Columns extends DeesElement {
|
||||
${this.renderCreateDialog()}
|
||||
${this.renderMoveDialog()}
|
||||
${this.renderMovePickerDialog()}
|
||||
${this.renderRenameDialog()}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user