Add file rename functionality and improve preview handling
- Implement file/folder rename feature with dialog and API endpoint - Add rename button to file browser with keyboard shortcuts - Switch PDF preview from object to embed tag for better compatibility - Fix CORS issues by fetching preview files as blobs with object URLs - Add proper cleanup for object URLs to prevent memory leaks - Add renameObject utility function for MinIO operations
This commit is contained in:
@@ -118,6 +118,15 @@
|
||||
<v-icon>mdi-download</v-icon>
|
||||
<v-tooltip activator="parent" location="top">Download</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
size="small"
|
||||
@click.stop="confirmRename(item)"
|
||||
>
|
||||
<v-icon>mdi-rename-box</v-icon>
|
||||
<v-tooltip activator="parent" location="top">Rename</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
@@ -222,6 +231,41 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Rename Dialog -->
|
||||
<v-dialog v-model="renameDialog" max-width="400">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<v-icon class="mr-2">mdi-rename-box</v-icon>
|
||||
Rename {{ fileToRename?.isFolder ? 'Folder' : 'File' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="newName"
|
||||
:label="fileToRename?.isFolder ? 'New folder name' : 'New file name (without extension)'"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
autofocus
|
||||
@keyup.enter="renameFile"
|
||||
:hint="fileToRename?.isFolder ? '' : `Extension will be preserved (.${fileToRename?.extension})`"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="renameDialog = false">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
@click="renameFile"
|
||||
:loading="renaming"
|
||||
:disabled="!newName || newName === getDisplayNameForRename()"
|
||||
>
|
||||
Rename
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- File Preview Dialog -->
|
||||
<FilePreviewModal
|
||||
v-model="previewDialog"
|
||||
@@ -255,16 +299,20 @@ const searchQuery = ref('');
|
||||
const loading = ref(false);
|
||||
const uploading = ref(false);
|
||||
const deleting = ref(false);
|
||||
const renaming = ref(false);
|
||||
const creatingFolder = ref(false);
|
||||
const uploadDialog = ref(false);
|
||||
const deleteDialog = ref(false);
|
||||
const newFolderDialog = ref(false);
|
||||
const previewDialog = ref(false);
|
||||
const renameDialog = ref(false);
|
||||
const fileToDelete = ref<FileItem | null>(null);
|
||||
const fileToPreview = ref<FileItem | null>(null);
|
||||
const fileToRename = ref<FileItem | null>(null);
|
||||
const downloadingFiles = ref<Record<string, boolean>>({});
|
||||
const currentPath = ref('');
|
||||
const newFolderName = ref('');
|
||||
const newName = ref('');
|
||||
|
||||
// Table headers
|
||||
const headers = [
|
||||
@@ -461,6 +509,59 @@ const deleteFile = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Confirm rename
|
||||
const confirmRename = (file: FileItem) => {
|
||||
fileToRename.value = file;
|
||||
// Set initial value to current display name without extension
|
||||
if (file.isFolder) {
|
||||
newName.value = file.displayName;
|
||||
} else {
|
||||
// Remove extension from display name
|
||||
const extIndex = file.displayName.lastIndexOf('.');
|
||||
newName.value = extIndex > -1 ? file.displayName.substring(0, extIndex) : file.displayName;
|
||||
}
|
||||
renameDialog.value = true;
|
||||
};
|
||||
|
||||
// Get display name for rename comparison
|
||||
const getDisplayNameForRename = () => {
|
||||
if (!fileToRename.value) return '';
|
||||
if (fileToRename.value.isFolder) {
|
||||
return fileToRename.value.displayName;
|
||||
} else {
|
||||
// Remove extension from display name
|
||||
const extIndex = fileToRename.value.displayName.lastIndexOf('.');
|
||||
return extIndex > -1 ? fileToRename.value.displayName.substring(0, extIndex) : fileToRename.value.displayName;
|
||||
}
|
||||
};
|
||||
|
||||
// Rename file
|
||||
const renameFile = async () => {
|
||||
if (!fileToRename.value || !newName.value) return;
|
||||
|
||||
renaming.value = true;
|
||||
try {
|
||||
await $fetch('/api/files/rename', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
oldName: fileToRename.value.name,
|
||||
newName: newName.value,
|
||||
isFolder: fileToRename.value.isFolder,
|
||||
},
|
||||
});
|
||||
|
||||
toast.success(fileToRename.value.isFolder ? 'Folder renamed successfully' : 'File renamed successfully');
|
||||
renameDialog.value = false;
|
||||
await loadFiles();
|
||||
} catch (error: any) {
|
||||
toast.error(error.data?.statusMessage || 'Failed to rename');
|
||||
} finally {
|
||||
renaming.value = false;
|
||||
fileToRename.value = null;
|
||||
newName.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
// Helpers
|
||||
const formatDate = (date: string) => {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
|
||||
Reference in New Issue
Block a user