feat: file upload via camera
This commit is contained in:
parent
998abc2cd8
commit
7afdf97697
@ -16,7 +16,7 @@
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
v-if="files.length"
|
||||
variant="subtle"
|
||||
@ -25,14 +25,28 @@
|
||||
@click="removeAllFiles"
|
||||
/>
|
||||
<Button
|
||||
v-if="filesUploaderArea?.showWebLink"
|
||||
v-if="
|
||||
filesUploaderArea?.showWebLink || filesUploaderArea?.showCamera
|
||||
"
|
||||
:label="__('Back to file upload')"
|
||||
@click="filesUploaderArea.showWebLink = false"
|
||||
@click="
|
||||
() => {
|
||||
filesUploaderArea.showWebLink = false
|
||||
filesUploaderArea.showCamera = false
|
||||
filesUploaderArea.webLink = null
|
||||
filesUploaderArea.cameraImage = null
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="arrow-left" class="size-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
v-if="filesUploaderArea?.cameraImage"
|
||||
:label="__('Retake')"
|
||||
@click="filesUploaderArea.cameraImage = null"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
@ -50,12 +64,29 @@
|
||||
@click="setAllPrivate"
|
||||
/>
|
||||
<Button
|
||||
v-if="!filesUploaderArea?.showCamera"
|
||||
variant="solid"
|
||||
:label="__('Attach')"
|
||||
:loading="fileUploadStarted"
|
||||
:disabled="disableAttachButton"
|
||||
@click="attachFiles"
|
||||
/>
|
||||
<Button
|
||||
v-if="
|
||||
filesUploaderArea?.showCamera && filesUploaderArea?.cameraImage
|
||||
"
|
||||
variant="solid"
|
||||
:label="__('Upload')"
|
||||
@click="() => filesUploaderArea.uploadViaCamera()"
|
||||
/>
|
||||
<Button
|
||||
v-if="
|
||||
filesUploaderArea?.showCamera && !filesUploaderArea?.cameraImage
|
||||
"
|
||||
variant="solid"
|
||||
:label="__('Capture')"
|
||||
@click="() => filesUploaderArea.captureImage()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -105,6 +136,9 @@ function removeAllFiles() {
|
||||
}
|
||||
|
||||
const disableAttachButton = computed(() => {
|
||||
if (filesUploaderArea.value?.showCamera) {
|
||||
return !filesUploaderArea.value.cameraImage
|
||||
}
|
||||
if (filesUploaderArea.value?.showWebLink) {
|
||||
return !filesUploaderArea.value.webLink
|
||||
}
|
||||
@ -141,7 +175,7 @@ const fileUploadStarted = ref(false)
|
||||
|
||||
function attachFile(file, i) {
|
||||
const args = {
|
||||
file: file?.fileObj || {},
|
||||
fileObj: file.fileObj || {},
|
||||
type: file.type,
|
||||
private: file.private,
|
||||
fileUrl: file.fileUrl,
|
||||
|
||||
@ -2,6 +2,15 @@
|
||||
<div v-if="showWebLink">
|
||||
<TextInput v-model="webLink" placeholder="https://example.com" />
|
||||
</div>
|
||||
<div v-else-if="showCamera">
|
||||
<video v-show="!cameraImage" ref="video" class="rounded" autoplay></video>
|
||||
<canvas
|
||||
v-show="cameraImage"
|
||||
ref="canvas"
|
||||
class="rounded"
|
||||
style="width: -webkit-fill-available"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center gap-4 rounded-lg border border-dashed min-h-64 text-gray-600"
|
||||
@ -38,7 +47,7 @@
|
||||
<div class="mt-1">{{ __('Link') }}</div>
|
||||
</div>
|
||||
<div v-if="allowTakePhoto">
|
||||
<Button icon="camera" size="md" @click="captureImage" />
|
||||
<Button icon="camera" size="md" @click="startCamera" />
|
||||
<div class="mt-1">{{ __('Camera') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -117,7 +126,7 @@
|
||||
import FileTextIcon from '@/components/Icons/FileTextIcon.vue'
|
||||
import FileAudioIcon from '@/components/Icons/FileAudioIcon.vue'
|
||||
import FileVideoIcon from '@/components/Icons/FileVideoIcon.vue'
|
||||
import { createToast } from '@/utils'
|
||||
import { createToast, dateFormat } from '@/utils'
|
||||
import { FormControl, CircularProgressBar, createResource } from 'frappe-ui'
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
@ -138,13 +147,17 @@ const fileInput = ref(null)
|
||||
const isDragging = ref(false)
|
||||
const showWebLink = ref(false)
|
||||
const showFileBrowser = ref(false)
|
||||
const showCamera = ref(false)
|
||||
|
||||
const webLink = ref('')
|
||||
const cameraImage = ref(null)
|
||||
|
||||
const allowMultiple = ref(props.options.allowMultiple == false ? false : true)
|
||||
const disableFileBrowser = ref(props.options.disableFileBrowser || false)
|
||||
const allowWebLink = ref(props.options.allowWebLink == false ? false : true)
|
||||
const allowTakePhoto = ref(props.options.allowTakePhoto || false)
|
||||
const allowTakePhoto = ref(
|
||||
props.options.allowTakePhoto || window.navigator.mediaDevices || false,
|
||||
)
|
||||
const restrictions = ref(props.options.restrictions || {})
|
||||
const makeAttachmentsPublic = ref(props.options.makeAttachmentsPublic || false)
|
||||
|
||||
@ -186,15 +199,56 @@ function onFileInput(event) {
|
||||
addFiles(fileInput.value.files)
|
||||
}
|
||||
|
||||
const video = ref(null)
|
||||
const facingMode = ref('environment')
|
||||
|
||||
async function startCamera() {
|
||||
showCamera.value = true
|
||||
|
||||
let stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
facingMode: facingMode.value,
|
||||
},
|
||||
audio: false,
|
||||
})
|
||||
video.value.srcObject = stream
|
||||
}
|
||||
|
||||
const canvas = ref(null)
|
||||
|
||||
function captureImage() {
|
||||
//
|
||||
const width = video.value.videoWidth
|
||||
const height = video.value.videoHeight
|
||||
|
||||
canvas.value.width = width
|
||||
canvas.value.height = height
|
||||
|
||||
canvas.value.getContext('2d').drawImage(video.value, 0, 0, width, height)
|
||||
|
||||
cameraImage.value = canvas.value.toDataURL('image/png')
|
||||
}
|
||||
|
||||
function uploadViaCamera() {
|
||||
const nowDatetime = dateFormat(new Date(), 'YYYY_MM_DD_HH_mm_ss')
|
||||
let filename = `capture_${nowDatetime}.png`
|
||||
urlToFile(cameraImage.value, filename, 'image/png').then((file) => {
|
||||
addFiles([file])
|
||||
showCamera.value = false
|
||||
cameraImage.value = null
|
||||
})
|
||||
}
|
||||
|
||||
function urlToFile(url, filename, mime_type) {
|
||||
return fetch(url)
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => new File([buffer], filename, { type: mime_type }))
|
||||
}
|
||||
|
||||
function addFiles(fileArray) {
|
||||
let _files = Array.from(fileArray)
|
||||
.filter(checkRestrictions)
|
||||
.map((file, i) => {
|
||||
let isImage = file.type.startsWith('image')
|
||||
let isImage = file.type?.startsWith('image')
|
||||
let sizeKb = file.size / 1024
|
||||
return {
|
||||
index: i,
|
||||
@ -203,7 +257,7 @@ function addFiles(fileArray) {
|
||||
cropperFile: file,
|
||||
cropBoxData: null,
|
||||
type: file.type,
|
||||
optimize: sizeKb > 200 && isImage && !file.type.includes('svg'),
|
||||
optimize: sizeKb > 200 && isImage && !file.type?.includes('svg'),
|
||||
name: file.name,
|
||||
doc: null,
|
||||
progress: 0,
|
||||
@ -314,13 +368,13 @@ function convertSize(size) {
|
||||
size /= 1024
|
||||
unitIndex++
|
||||
}
|
||||
return `${size.toFixed(2)} ${units[unitIndex]}`
|
||||
return `${size?.toFixed(2)} ${units[unitIndex]}`
|
||||
}
|
||||
|
||||
function fileIcon(type) {
|
||||
if (type.startsWith('audio')) {
|
||||
if (type?.startsWith('audio')) {
|
||||
return FileAudioIcon
|
||||
} else if (type.startsWith('video')) {
|
||||
} else if (type?.startsWith('video')) {
|
||||
return FileVideoIcon
|
||||
}
|
||||
return FileTextIcon
|
||||
@ -330,5 +384,9 @@ defineExpose({
|
||||
showFileBrowser,
|
||||
showWebLink,
|
||||
webLink,
|
||||
showCamera,
|
||||
cameraImage,
|
||||
captureImage,
|
||||
uploadViaCamera,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
interface UploadOptions {
|
||||
file?: File
|
||||
fileObj?: File
|
||||
private?: boolean
|
||||
fileUrl?: string
|
||||
folder?: string
|
||||
@ -99,8 +99,8 @@ class FilesUploadHandler {
|
||||
|
||||
let formData = new FormData()
|
||||
|
||||
if (options.file && file?.name) {
|
||||
formData.append('file', options.file, file.name)
|
||||
if (options.fileObj && file?.name) {
|
||||
formData.append('file', options.fileObj, file.name)
|
||||
}
|
||||
formData.append('is_private', options.private || false ? '1' : '0')
|
||||
formData.append('folder', options.folder || 'Home')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user