mirror of
https://github.com/frappe/lms.git
synced 2026-04-19 22:52:29 +03:00
202 lines
5.2 KiB
Vue
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>
|