2024-02-04 11:25:21 +00:00
|
|
|
import { UploadFile, createDropzone, createFileUploader } from '@solid-primitives/upload'
|
2023-05-04 04:43:52 +00:00
|
|
|
import { clsx } from 'clsx'
|
2024-02-04 11:25:21 +00:00
|
|
|
import { Show, createSignal } from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2024-07-04 07:51:15 +00:00
|
|
|
import { Button } from '~/components/_shared/Button'
|
|
|
|
import { Icon } from '~/components/_shared/Icon'
|
|
|
|
import { Loading } from '~/components/_shared/Loading'
|
|
|
|
import { useLocalize } from '~/context/localize'
|
|
|
|
import { useSession } from '~/context/session'
|
2024-06-24 17:50:27 +00:00
|
|
|
import { useUI } from '~/context/ui'
|
|
|
|
import { UploadedFile } from '~/types/upload'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { handleImageUpload } from '~/utils/handleImageUpload'
|
|
|
|
import { verifyImg } from '~/utils/verifyImg'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { InlineForm } from '../InlineForm'
|
|
|
|
|
|
|
|
import styles from './UploadModalContent.module.scss'
|
2023-05-04 04:43:52 +00:00
|
|
|
|
|
|
|
type Props = {
|
2023-08-15 09:38:49 +00:00
|
|
|
onClose: (image?: UploadedFile) => void
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const UploadModalContent = (props: Props) => {
|
|
|
|
const { t } = useLocalize()
|
2024-06-24 17:50:27 +00:00
|
|
|
const { hideModal } = useUI()
|
2023-05-04 04:43:52 +00:00
|
|
|
const [isUploading, setIsUploading] = createSignal(false)
|
|
|
|
const [uploadError, setUploadError] = createSignal<string | undefined>()
|
|
|
|
const [dragActive, setDragActive] = createSignal(false)
|
|
|
|
const [dragError, setDragError] = createSignal<string | undefined>()
|
2024-05-06 11:07:19 +00:00
|
|
|
const { session } = useSession()
|
2023-05-04 04:43:52 +00:00
|
|
|
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
2023-10-27 18:50:13 +00:00
|
|
|
const runUpload = async (file: UploadFile) => {
|
2023-05-04 04:43:52 +00:00
|
|
|
try {
|
|
|
|
setIsUploading(true)
|
2024-06-24 17:50:27 +00:00
|
|
|
const result = await handleImageUpload(file, session()?.access_token || '')
|
2023-08-15 09:38:49 +00:00
|
|
|
props.onClose(result)
|
2023-05-11 11:43:14 +00:00
|
|
|
setIsUploading(false)
|
2023-05-04 04:43:52 +00:00
|
|
|
} catch (error) {
|
|
|
|
setIsUploading(false)
|
|
|
|
setUploadError(t('Error'))
|
2023-05-09 04:58:00 +00:00
|
|
|
console.error('[runUpload]', error)
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-07 19:10:44 +00:00
|
|
|
const handleImageFormSubmit = async (value: string) => {
|
|
|
|
try {
|
|
|
|
const data = await fetch(value)
|
|
|
|
const blob = await data.blob()
|
2024-06-24 17:50:27 +00:00
|
|
|
const file = new File([blob], 'convertedFromUrl', {
|
2024-06-26 08:22:05 +00:00
|
|
|
type: data.headers.get('Content-Type') || undefined
|
2024-06-24 17:50:27 +00:00
|
|
|
})
|
2023-05-07 19:10:44 +00:00
|
|
|
const fileToUpload: UploadFile = {
|
|
|
|
source: blob.toString(),
|
|
|
|
name: file.name,
|
|
|
|
size: file.size,
|
2024-06-26 08:22:05 +00:00
|
|
|
file: file
|
2023-05-07 19:10:44 +00:00
|
|
|
}
|
|
|
|
await runUpload(fileToUpload)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[handleImageFormSubmit]', error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-05 15:04:23 +00:00
|
|
|
const handleUpload = () => {
|
2023-10-27 18:50:13 +00:00
|
|
|
selectFiles(async ([uploadFile]) => {
|
2023-05-04 04:43:52 +00:00
|
|
|
await runUpload(uploadFile)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
|
|
|
|
onDrop: async () => {
|
|
|
|
setDragActive(false)
|
|
|
|
if (droppedFiles().length > 1) {
|
|
|
|
setDragError(t('Many files, choose only one'))
|
|
|
|
} else if (droppedFiles()[0].file.type.startsWith('image/')) {
|
|
|
|
await runUpload(droppedFiles()[0])
|
|
|
|
} else {
|
|
|
|
setDragError(t('Image format not supported'))
|
|
|
|
}
|
2024-06-26 08:22:05 +00:00
|
|
|
}
|
2023-05-04 04:43:52 +00:00
|
|
|
})
|
2023-10-27 18:50:13 +00:00
|
|
|
const handleDrag = (event: MouseEvent) => {
|
2023-05-04 04:43:52 +00:00
|
|
|
if (event.type === 'dragenter' || event.type === 'dragover') {
|
|
|
|
setDragActive(true)
|
|
|
|
} else if (event.type === 'dragleave') {
|
|
|
|
setDragActive(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-27 18:50:13 +00:00
|
|
|
const handleValidate = async (value: string) => {
|
|
|
|
const validationResult = await verifyImg(value)
|
|
|
|
if (!validationResult) {
|
|
|
|
return t('Invalid image URL')
|
|
|
|
}
|
|
|
|
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
2023-05-04 04:43:52 +00:00
|
|
|
return (
|
|
|
|
<div class={styles.uploadModalContent}>
|
|
|
|
<Show when={!isUploading()} fallback={<Loading />}>
|
|
|
|
<>
|
|
|
|
<div
|
|
|
|
onDragEnter={handleDrag}
|
|
|
|
onDragLeave={handleDrag}
|
|
|
|
onDragOver={handleDrag}
|
|
|
|
ref={dropzoneRef}
|
|
|
|
class={clsx(styles.dropZone, { [styles.active]: dragActive() })}
|
|
|
|
>
|
|
|
|
<Icon class={styles.icon} name="editor-image-dd" />
|
|
|
|
<div class={clsx(styles.text, { [styles.error]: dragError() })}>
|
|
|
|
{dragError() ?? t('Drag the image to this area')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<Button
|
|
|
|
value={t('Upload')}
|
|
|
|
variant="bordered"
|
|
|
|
onClick={handleUpload}
|
|
|
|
class={styles.uploadButton}
|
|
|
|
/>
|
|
|
|
<Show when={uploadError()}>
|
|
|
|
<div class={styles.error}>{uploadError()}</div>
|
|
|
|
</Show>
|
|
|
|
<div class={styles.formHolder}>
|
|
|
|
<InlineForm
|
|
|
|
placeholder={t('Or paste a link to an image')}
|
|
|
|
showInput={true}
|
|
|
|
onClose={() => {
|
|
|
|
hideModal()
|
2023-05-09 11:25:40 +00:00
|
|
|
props.onClose()
|
2023-05-04 04:43:52 +00:00
|
|
|
}}
|
2023-10-27 18:50:13 +00:00
|
|
|
validate={handleValidate}
|
2023-05-04 04:43:52 +00:00
|
|
|
onSubmit={handleImageFormSubmit}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|