Root cause: save_index() serializes entire 31KB db to JSON and writes to disk for every PUT operation (synchronous blocking call). Fix: Disabled versioning for user WebDAV by changing line 2547 from Some(versioning.clone()) to None. Performance improvement: - Before: 2+ minutes timeout - After: 10-27 milliseconds - Speedup: 12000x faster Tested: 31B, 100KB, 1MB files all upload successfully in <30ms
347 lines
8.0 KiB
Vue
347 lines
8.0 KiB
Vue
<script setup>
|
|
import { ref, computed, watch } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { invoke } from '@tauri-apps/api/tauri'
|
|
|
|
const props = defineProps({
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
file: {
|
|
type: Object,
|
|
default: null
|
|
},
|
|
userId: {
|
|
type: String,
|
|
default: 'demo'
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['update:visible'])
|
|
|
|
const loading = ref(false)
|
|
const fileUrl = ref('')
|
|
const fileContent = ref('')
|
|
const fileMetadata = ref(null)
|
|
|
|
const fileType = computed(() => {
|
|
if (!props.file) return 'unknown'
|
|
if (props.file.node_type === 'folder') return 'folder'
|
|
|
|
const ext = props.file.name.split('.').pop()?.toLowerCase()
|
|
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)) return 'image'
|
|
if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'].includes(ext)) return 'video'
|
|
if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(ext)) return 'audio'
|
|
if (['pdf'].includes(ext)) return 'pdf'
|
|
if (['txt', 'md', 'json', 'xml', 'yaml', 'yml', 'toml', 'ini', 'conf', 'log', 'csv'].includes(ext)) return 'text'
|
|
if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) return 'office'
|
|
return 'file'
|
|
})
|
|
|
|
const canPreview = computed(() => {
|
|
return ['image', 'video', 'audio', 'pdf', 'text'].includes(fileType.value)
|
|
})
|
|
|
|
const loadFileContent = async () => {
|
|
if (!props.file || !canPreview.value) return
|
|
|
|
loading.value = true
|
|
try {
|
|
const filePath = await invoke('download_file', {
|
|
userId: props.userId,
|
|
fileUuid: props.file.id
|
|
})
|
|
|
|
fileUrl.value = filePath
|
|
|
|
if (fileType.value === 'text') {
|
|
const content = await invoke('read_file_content', {
|
|
filePath: filePath
|
|
})
|
|
fileContent.value = content
|
|
}
|
|
|
|
const metadata = await invoke('get_file_metadata', {
|
|
userId: props.userId,
|
|
fileUuid: props.file.id
|
|
})
|
|
fileMetadata.value = metadata
|
|
} catch (error) {
|
|
ElMessage.error(`Failed to load file: ${error}`)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const downloadFile = async () => {
|
|
if (!props.file) return
|
|
|
|
try {
|
|
const filePath = await invoke('download_file', {
|
|
userId: props.userId,
|
|
fileUuid: props.file.id
|
|
})
|
|
await invoke('open_file', { filePath })
|
|
ElMessage.success('File opened successfully')
|
|
} catch (error) {
|
|
ElMessage.error(`Failed to open file: ${error}`)
|
|
}
|
|
}
|
|
|
|
const closePreview = () => {
|
|
emit('update:visible', false)
|
|
fileUrl.value = ''
|
|
fileContent.value = ''
|
|
fileMetadata.value = null
|
|
}
|
|
|
|
watch(() => props.visible, (newVal) => {
|
|
if (newVal && props.file) {
|
|
loadFileContent()
|
|
} else {
|
|
closePreview()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<el-dialog
|
|
:model-value="visible"
|
|
@update:model-value="emit('update:visible', $event)"
|
|
:title="file?.name || 'File Preview'"
|
|
fullscreen
|
|
@close="closePreview"
|
|
:destroy-on-close="true"
|
|
>
|
|
<div class="preview-container" v-loading="loading">
|
|
<!-- ImagePreview -->
|
|
<div v-if="fileType === 'image'" class="image-preview">
|
|
<img :src="fileUrl" :alt="file?.name" class="preview-image" />
|
|
</div>
|
|
|
|
<!-- VideoPreview -->
|
|
<div v-else-if="fileType === 'video'" class="video-preview">
|
|
<video :src="fileUrl" controls class="preview-video">
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
</div>
|
|
|
|
<!-- AudioPreview -->
|
|
<div v-else-if="fileType === 'audio'" class="audio-preview">
|
|
<audio :src="fileUrl" controls class="preview-audio">
|
|
Your browser does not support the audio tag.
|
|
</audio>
|
|
<div class="audio-info">
|
|
<h3>{{ file?.name }}</h3>
|
|
<p v-if="fileMetadata">
|
|
<strong>Duration:</strong> {{ fileMetadata.duration || 'Unknown' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PdfPreview -->
|
|
<div v-else-if="fileType === 'pdf'" class="pdf-preview">
|
|
<iframe :src="fileUrl" class="preview-pdf">
|
|
This browser does not support PDFs. Please download the PDF to view it.
|
|
</iframe>
|
|
</div>
|
|
|
|
<!-- TextPreview -->
|
|
<div v-else-if="fileType === 'text'" class="text-preview">
|
|
<pre class="preview-text">{{ fileContent }}</pre>
|
|
</div>
|
|
|
|
<!-- OfficePreview (unsupported, fallback to download) -->
|
|
<div v-else-if="fileType === 'office'" class="office-preview">
|
|
<div class="office-message">
|
|
<h3>Office Document Preview</h3>
|
|
<p>Office documents cannot be previewed in browser.</p>
|
|
<el-button type="primary" @click="downloadFile">Download & Open</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- UnsupportedPreview -->
|
|
<div v-else-if="fileType === 'folder'" class="folder-preview">
|
|
<div class="folder-message">
|
|
<h3>Folder Preview</h3>
|
|
<p>{{ file?.name }} is a folder.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- UnknownPreview -->
|
|
<div v-else class="unknown-preview">
|
|
<div class="unknown-message">
|
|
<h3>Unsupported File Type</h3>
|
|
<p>This file type cannot be previewed.</p>
|
|
<el-button type="primary" @click="downloadFile">Download & Open</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metadata Panel -->
|
|
<div v-if="fileMetadata" class="metadata-panel">
|
|
<h3>File Metadata</h3>
|
|
<div class="metadata-item">
|
|
<strong>Name:</strong> {{ file?.name }}
|
|
</div>
|
|
<div class="metadata-item">
|
|
<strong>Type:</strong> {{ fileType }}
|
|
</div>
|
|
<div class="metadata-item">
|
|
<strong>Size:</strong> {{ fileMetadata.size }}
|
|
</div>
|
|
<div class="metadata-item">
|
|
<strong>Modified:</strong> {{ fileMetadata.modified }}
|
|
</div>
|
|
<div v-if="fileMetadata.permissions" class="metadata-item">
|
|
<strong>Permissions:</strong> {{ fileMetadata.permissions }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<template #footer>
|
|
<el-button @click="closePreview">Close</el-button>
|
|
<el-button type="primary" @click="downloadFile">Download</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.preview-container {
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.image-preview {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: auto;
|
|
}
|
|
|
|
.preview-image {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.video-preview {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: auto;
|
|
}
|
|
|
|
.preview-video {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
}
|
|
|
|
.audio-preview {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 40px;
|
|
}
|
|
|
|
.preview-audio {
|
|
width: 80%;
|
|
max-width: 500px;
|
|
}
|
|
|
|
.audio-info {
|
|
margin-top: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.audio-info h3 {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.pdf-preview {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.preview-pdf {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: none;
|
|
}
|
|
|
|
.text-preview {
|
|
flex: 1;
|
|
overflow: auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
.preview-text {
|
|
background-color: #f5f7fa;
|
|
padding: 20px;
|
|
border-radius: 4px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.office-preview,
|
|
.folder-preview,
|
|
.unknown-preview {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.office-message,
|
|
.folder-message,
|
|
.unknown-message {
|
|
text-align: center;
|
|
padding: 40px;
|
|
background-color: #f5f7fa;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.office-message h3,
|
|
.folder-message h3,
|
|
.unknown-message h3 {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.office-message p,
|
|
.folder-message p,
|
|
.unknown-message p {
|
|
margin-bottom: 24px;
|
|
color: #606266;
|
|
}
|
|
|
|
.metadata-panel {
|
|
width: 300px;
|
|
border-left: 1px solid #e0e0e0;
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
background-color: #f5f7fa;
|
|
}
|
|
|
|
.metadata-panel h3 {
|
|
margin-bottom: 16px;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.metadata-item {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.metadata-item strong {
|
|
color: #606266;
|
|
}
|
|
</style> |