Files
frappe-lms/frontend/src/pages/Courses/CourseImportModal.vue
2026-04-03 13:18:11 +05:30

202 lines
5.2 KiB
Vue

<template>
<Dialog
v-model="show"
:options="{
title: __('Import Course from ZIP'),
}"
>
<template #body-content>
<div class="text-p-base">
<div
v-if="!zip"
@dragover.prevent
@drop.prevent="(e) => uploadFile(e)"
class="h-[120px] flex flex-col items-center justify-center bg-surface-gray-1 border border-dashed border-outline-gray-3 rounded-md"
>
<div v-if="!uploading" class="w-4/5 text-center">
<UploadCloud
class="size-6 stroke-1.5 text-ink-gray-6 mx-auto mb-2.5"
/>
<input
ref="fileInput"
type="file"
class="hidden"
accept=".zip"
@change="(e) => uploadFile(e)"
/>
<div class="leading-5 text-ink-gray-9">
{{ __('Drag and drop a ZIP file, or upload from your') }}
<span
@click="openFileSelector"
class="cursor-pointer font-semibold hover:underline"
>
{{ __('Device') }}
</span>
</div>
</div>
<div
v-else-if="uploading"
class="w-fit bg-surface-white border rounded-md p-2 my-4"
>
<div class="space-y-2">
<div class="font-medium">
{{ uploadingFile.name }}
</div>
<div class="text-ink-gray-6">
{{ convertToMB(uploaded) }} of {{ convertToMB(total) }}
</div>
</div>
<div class="w-full bg-surface-gray-1 h-1 rounded-full mt-3">
<div
class="bg-surface-gray-7 h-1 rounded-full transition-all duration-500 ease-in-out"
:style="`width: ${uploadProgress}%`"
></div>
</div>
</div>
</div>
<div
v-else-if="zip"
class="h-[120px] flex items-center justify-center bg-surface-gray-1 border border-dashed border-outline-gray-3 rounded-md"
>
<div
class="w-fit bg-surface-white border rounded-md p-2 flex items-center justify-between items-center space-x-4"
>
<div class="space-y-2">
<div class="font-medium leading-5 text-ink-gray-9">
{{ zip.file_name || zip.name }}
</div>
<div v-if="zip.file_size" class="text-ink-gray-6">
{{ convertToMB(zip.file_size) }}
</div>
</div>
<Trash2
class="size-4 stroke-1.5 text-ink-red-3 cursor-pointer"
@click="deleteFile"
/>
</div>
</div>
</div>
</template>
<template #actions>
<div class="flex justify-end">
<Button variant="solid" @click="importZip">
{{ __('Import') }}
</Button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Button, call, Dialog, FileUploadHandler, toast } from 'frappe-ui'
import { computed, ref } from 'vue'
import { Trash2, UploadCloud } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
const fileInput = ref<HTMLInputElement | null>(null)
const show = defineModel<boolean>({ required: true, default: false })
const zip = ref<any | null>(null)
const uploaded = ref(0)
const total = ref(0)
const uploading = ref(false)
const uploadingFile = ref<any | null>(null)
const router = useRouter()
const openFileSelector = () => {
fileInput.value?.click()
}
const uploadProgress = computed(() => {
if (total.value === 0) return 0
return Math.floor((uploaded.value / total.value) * 100)
})
const extractFile = (e: Event): File | null => {
const inputFiles = (e.target as HTMLInputElement)?.files
const dt = (e as DragEvent).dataTransfer?.files
return inputFiles?.[0] || dt?.[0] || null
}
const validateFile = (file: File) => {
const extension = file.name.split('.').pop()?.toLowerCase()
if (extension !== 'zip') {
toast.error('Please upload a valid ZIP file.')
console.error('Please upload a valid ZIP file.')
}
return extension
}
const uploadFile = (e: Event) => {
const file = extractFile(e)
if (!file) return
let fileType = validateFile(file)
if (fileType !== 'zip') return
uploadingFile.value = file
const uploader = new FileUploadHandler()
uploader.on('start', () => {
uploading.value = true
})
uploader.on('progress', (data: { uploaded: number; total: number }) => {
uploaded.value = data.uploaded
total.value = data.total
})
uploader.on('error', (error: any) => {
uploading.value = false
toast.error(__('File upload failed. Please try again.'))
console.error('File upload error:', error)
})
uploader.on('finish', () => {
uploading.value = false
})
uploader
.upload(file, {
private: 1,
})
.then((data: any) => {
zip.value = data
})
.catch((error: any) => {
console.error('File upload error:', error)
toast.error(__('File upload failed. Please try again.'))
uploading.value = false
uploadingFile.value = null
uploaded.value = 0
total.value = 0
})
}
const importZip = () => {
if (!zip.value) return
call('lms.lms.api.import_course_from_zip', {
zip_file_path: zip.value.file_url,
})
.then((data: any) => {
toast.success('Course imported successfully!')
show.value = false
deleteFile()
router.push({
name: 'CourseDetail',
params: { courseName: data },
})
})
.catch((error: any) => {
toast.error('Error importing course: ' + error.message)
console.error('Error importing course:', error)
})
}
const deleteFile = () => {
zip.value = null
}
const convertToMB = (bytes: number) => {
return (bytes / 1024 / 1024).toFixed(2) + ' MB'
}
</script>