import {
computed, defineComponent,
type PropType, ref,
} from 'vue'
import type ApiInterface from '../../types/api-interface'
import { ArkUiConstants } from '../../composables/use-ark-ui'
import { type FileData } from './upload-area-types'
import './assets/styles.scss'
export const useUploadFile = async (params: { api?: ApiInterface, endpoint: string, file: File }) => {
const api = params.api || ArkUiConstants.api
const endpoint = params.endpoint || '/file/'
if (!api) {
throw new Error('API is not defined')
}
try {
const data = new FormData()
data.append('file', params.file)
return (await api.post(endpoint, data)).data
} catch (e) {
throw new Error('File upload error')
}
}
/**
* Кнопка для загрузки файлов. Имеет настраиваемый размер.
*
* Дизайн
*
*/
export default defineComponent({
props: {
/**
* Настройка доступности файла, даже если компонент заблокирован
*/
isFileAvailable: {
type: Boolean,
default: false,
},
/**
* Состояние лоадера в кнопке
*/
isLoading: {
type: Boolean,
default: false,
},
/**
* Объект ApiInterface.
*/
api: {
type: Object as PropType,
required: true,
},
/**
* Строка - конечная точка API (по умолчанию '/file/').
*/
endpoint: {
type: String,
default: '/file/',
},
/**
* Загружаемый файл.
*/
file: {
type: [Object, null] as PropType,
default: null,
},
/**
* Массив строк с допустимыми форматами файлов.
* По умолчанию ['.jpg', '.png', '.pdf'].
*/
formats: {
type: Array as PropType,
default: () => ['.jpg', '.png', '.pdf'],
},
/**
* Максимальный вес файла \
* Указывается в килобайтах \
* 1024 = 1мб
*/
maxFileSize: {
type: Number,
default: undefined,
},
/**
* Функция, вызываемая при загрузке файла.
*/
onUpload: {
type: Function as PropType<(file: FileData|null) => void>,
required: true,
},
/**
* Позволяет назначить пользовательскую логику удаления, включая сам запрос
*/
onDelete: {
type: Function as PropType<(file: FileData | null) => void>,
default: undefined,
},
/**
* Размер кнопки, по умолчанию 'M'.
*/
size: {
type: String,
default: 'M',
},
/**
* Флаг отключена ли кнопка.
*/
isDisabled: {
type: Boolean,
default: false,
},
/**
* Кастомная ширина кнопки в px.
*/
width: {
type: String,
default: null,
},
label: {
type: String,
default: 'Загрузить файл',
},
},
setup(props) {
const isLoading = ref(false)
const errorText = ref(undefined)
const isError = computed(() => typeof errorText.value !== 'undefined')
const filenameWithoutExtension = computed(() => {
if (!props.file) return undefined
const { name, extension } = props.file
const lastDotIndex = name.lastIndexOf('.')
if (lastDotIndex !== -1) {
const maybeExtension = name.substring(lastDotIndex + 1)
if (maybeExtension === extension) {
return name.substring(0, lastDotIndex)
}
}
return name
})
const upload = (value: FileData | null) => {
if (props.onUpload) {
props.onUpload(value)
}
}
const onChange = (e: Event) => {
const { files } = e.target as HTMLInputElement
if (files && files.length) {
isLoading.value = true
if (props.maxFileSize) {
const kb = files[0].size / 1024
if (kb > props.maxFileSize) {
errorText.value = `Ошибка загрузки, максимальный размер файла превышает ${formatFileSize(props.maxFileSize)}`
isLoading.value = false
upload(null)
return
}
}
useUploadFile({ file: files[0], api: props.api, endpoint: props.endpoint })
.then(upload)
.catch((e) => {
errorText.value = 'Ошибка загрузки'
upload(null)
throw new Error(e)
})
.finally(() => {
isLoading.value = false
})
}
}
const removeFile = () => {
if (props.file) {
if (props.onDelete) props.onDelete(props.file)
else props.api.delete(props.endpoint + props.file.id)
}
if (props.onUpload) props.onUpload(null)
upload(null)
}
const reloadFile = () => {
if (props.onUpload) props.onUpload(null)
errorText.value = undefined
upload(null)
}
function formatFileSize(kb: number) {
if (kb < 1024) return `${kb}кб`
let i = -1
const units = ['мб', 'гб', 'тб']
do {
kb /= 1024
i++
} while (kb >= 1024)
return `${Math.floor(kb)}${units[i]}`
}
return () => (
<>
{ props.maxFileSize && !isError.value && !props.file ? Максимальный размер файла: {formatFileSize(props.maxFileSize)}
: '' }
>
)
},
})