Files
FileCodeBoxFronted/src/composables/useSendFlow.ts
T
2026-06-03 02:38:29 +08:00

340 lines
10 KiB
TypeScript

import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAlertStore } from '@/stores/alertStore'
import { useAdminStore } from '@/stores/adminStore'
import { useConfigStore } from '@/stores/configStore'
import { useFileDataStore } from '@/stores/fileData'
import type { SendType, SentFileRecord } from '@/types'
import { getClipboardFile, insertTextAtSelection } from '@/utils/clipboard-paste'
import { getErrorMessage } from '@/utils/common'
import { getStorageUnit } from '@/utils/convert'
import { calculateFileHash } from '@/utils/file-processing'
import { buildSentRecord, isExpirationWithinLimit } from '@/utils/send-record'
import { createSentRecordActions } from '@/utils/sent-record-actions'
import { useSendSubmit } from './useSendSubmit'
export function useSendFlow() {
const { t } = useI18n()
const alertStore = useAlertStore()
const adminStore = useAdminStore()
const configStore = useConfigStore()
const fileDataStore = useFileDataStore()
const config = computed(() => configStore.config)
const sendType = ref<SendType>('file')
const selectedFile = ref<File | null>(null)
const selectedFiles = ref<File[]>([])
const textContent = ref('')
const expirationMethod = ref(config.value.expireStyle[0] || 'day')
const expirationValue = ref('1')
const uploadProgress = ref(0)
const showDrawer = ref(false)
const selectedRecord = ref<SentFileRecord | null>(null)
const isSubmitting = ref(false)
const fileHash = ref('')
const sendRecords = computed(() => fileDataStore.shareData)
const uploadDescription = computed(() =>
t('send.uploadArea.descriptionWithLimit', {
size: getStorageUnit(config.value.uploadSize)
})
)
const expirationOptions = computed(() =>
config.value.expireStyle.map((value) => ({
value,
label: getUnit(value)
}))
)
watch(
() => config.value.expireStyle,
(expireStyle) => {
if (expireStyle.length > 0 && !expireStyle.includes(expirationMethod.value)) {
expirationMethod.value = expireStyle[0]
}
},
{ immediate: true }
)
const notifyCopyResult = (message: string, type: 'success' | 'error') => {
alertStore.showAlert(message, type)
}
const sentRecordActions = createSentRecordActions(notifyCopyResult)
const { resetPresignUpload, submitFile, submitText } = useSendSubmit({
getMaxFileSize: () => configStore.uploadSizeLimit,
notify: (message, type) => alertStore.showAlert(message, type),
translate: t,
onProgress: (progress) => {
uploadProgress.value = progress
},
onHashCalculated: (hash) => {
fileHash.value = hash
}
})
const checkOpenUpload = () => {
if (config.value.openUpload === 0 && !adminStore.hasToken) {
alertStore.showAlert(t('send.messages.guestUploadDisabled'), 'error')
return false
}
return true
}
const checkFileSize = (file: File) => {
if (file.size > config.value.uploadSize) {
alertStore.showAlert(
t('send.messages.fileSizeExceeded', { size: getStorageUnit(config.value.uploadSize) }),
'error'
)
selectedFile.value = null
return false
}
return true
}
const checkExpirationTime = (method: string, value: string): boolean =>
isExpirationWithinLimit(method, value, config.value.max_save_seconds || 0)
const checkUpload = () => {
if (!selectedFile.value) return false
if (!checkOpenUpload()) return false
if (!checkFileSize(selectedFile.value)) return false
if (!checkExpirationTime(expirationMethod.value, expirationValue.value)) return false
return true
}
const handleFileSelected = async (file: File) => {
selectedFile.value = file
selectedFiles.value = []
if (!checkOpenUpload()) return
if (!checkFileSize(file)) return
fileHash.value = await calculateFileHash(file)
}
const handleFilesSelected = async (files: File[]) => {
if (!checkOpenUpload()) return
selectedFiles.value = files
selectedFile.value = null
fileHash.value = ''
}
const handleFileDrop = async (event: DragEvent) => {
if (!event.dataTransfer?.files || event.dataTransfer.files.length === 0) return
const files = Array.from(event.dataTransfer.files)
if (files.length === 1) {
const file = files[0]
selectedFile.value = file
selectedFiles.value = []
if (!checkUpload()) return
fileHash.value = await calculateFileHash(file)
} else {
if (!checkOpenUpload()) return
selectedFiles.value = files
selectedFile.value = null
fileHash.value = ''
}
}
const handlePaste = async (event: ClipboardEvent) => {
const items = event.clipboardData?.items
if (!items) return
const file = getClipboardFile(items)
if (file) {
if (file.size === 0) {
alertStore.showAlert(t('send.messages.emptyFileError'), 'error')
return
}
selectedFile.value = file
if (!checkUpload()) return
try {
fileHash.value = await calculateFileHash(file)
alertStore.showAlert(
t('send.messages.fileAddedFromClipboard', { filename: file.name }),
'success'
)
} catch (err) {
alertStore.showAlert(t('send.messages.fileProcessingFailed'), 'error')
console.error('File hash calculation failed:', err)
}
return
}
const textItem = items[0]
if (!textItem) return
sendType.value = 'text'
textItem.getAsString((str: string) => {
const trimmedStr = str.trim()
if (!trimmedStr) return
const textareaElement = document.getElementById('text-content') as HTMLTextAreaElement
if (!textareaElement) {
textContent.value += trimmedStr
return
}
const insertion = insertTextAtSelection({
text: textContent.value,
insertText: trimmedStr,
selectionStart: textareaElement.selectionStart,
selectionEnd: textareaElement.selectionEnd
})
textContent.value = insertion.value
setTimeout(() => {
textareaElement.setSelectionRange(insertion.cursor, insertion.cursor)
textareaElement.focus()
}, 0)
})
}
const getUnit = (value: string = expirationMethod.value) => {
switch (value) {
case 'day':
return t('send.expiration.units.days')
case 'hour':
return t('send.expiration.units.hours')
case 'minute':
return t('send.expiration.units.minutes')
case 'count':
return t('send.expiration.units.times')
case 'forever':
return t('send.expiration.units.forever')
default:
return ''
}
}
const handleSubmit = async () => {
if (isSubmitting.value) return
isSubmitting.value = true
try {
if (sendType.value === 'file' && !selectedFile.value && selectedFiles.value.length === 0) {
alertStore.showAlert(t('send.messages.selectFile'), 'error')
return
}
if (sendType.value === 'text' && !textContent.value.trim()) {
alertStore.showAlert(t('send.messages.enterText'), 'error')
return
}
if (!checkOpenUpload()) {
return
}
if (expirationMethod.value !== 'forever' && !expirationValue.value) {
alertStore.showAlert(t('send.messages.enterExpirationValue'), 'error')
return
}
if (!checkExpirationTime(expirationMethod.value, expirationValue.value)) {
const maxDays = Math.floor(config.value.max_save_seconds / 86400)
alertStore.showAlert(t('send.messages.expirationTooLong', { days: maxDays }), 'error')
return
}
const expireValue = expirationValue.value ? parseInt(expirationValue.value) : 1
let response
if (sendType.value === 'file') {
response = await submitFile({
selectedFile: selectedFile.value,
selectedFiles: selectedFiles.value,
expireValue,
expireStyle: expirationMethod.value,
enableChunk: Boolean(config.value.enableChunk),
validateFileSize: checkFileSize
})
} else {
response = await submitText({
text: textContent.value,
expireValue,
expireStyle: expirationMethod.value
})
}
if (!response) return
if (response?.code === 200) {
const newRecord = buildSentRecord({
response,
sendType: sendType.value,
textContent: textContent.value,
selectedFile: selectedFile.value,
selectedFiles: selectedFiles.value,
expirationMethod: expirationMethod.value,
expirationValue: expirationValue.value,
translate: t,
getUnit
})
fileDataStore.addShareDataRecord(newRecord)
alertStore.showAlert(
t('send.messages.sendSuccess', { code: newRecord.retrieveCode }),
'success'
)
selectedFile.value = null
selectedFiles.value = []
textContent.value = ''
uploadProgress.value = 0
resetPresignUpload()
selectedRecord.value = newRecord
await sentRecordActions.copyLink(newRecord)
} else {
throw new Error(t('send.messages.serverError'))
}
} catch (error: unknown) {
alertStore.showAlert(getErrorMessage(error, t('send.messages.sendFailed')), 'error')
} finally {
uploadProgress.value = 0
isSubmitting.value = false
}
}
const toggleDrawer = () => {
showDrawer.value = !showDrawer.value
}
const viewDetails = (record: SentFileRecord) => {
selectedRecord.value = record
}
const closeDetails = () => {
selectedRecord.value = null
}
const deleteRecord = (id: number) => {
const index = fileDataStore.shareData.findIndex((record) => record.id === id)
if (index !== -1) {
fileDataStore.deleteShareData(index)
}
}
return {
config,
sendType,
selectedFile,
selectedFiles,
textContent,
expirationMethod,
expirationValue,
uploadProgress,
showDrawer,
selectedRecord,
isSubmitting,
sendRecords,
uploadDescription,
expirationOptions,
closeDetails,
deleteRecord,
copySentRecordCode: sentRecordActions.copyCode,
copySentRecordLink: sentRecordActions.copyLink,
copySentRecordWgetCommand: sentRecordActions.copyWgetCommand,
getQRCodeValue: sentRecordActions.getQRCodeValue,
getUnit,
handleFileDrop,
handleFileSelected,
handleFilesSelected,
handlePaste,
handleSubmit,
toggleDrawer,
viewDetails
}
}