Skip to content

Commit

Permalink
feat: file upload via camera
Browse files Browse the repository at this point in the history
  • Loading branch information
shariquerik committed Oct 14, 2024
1 parent 998abc2 commit 7afdf97
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 16 deletions.
42 changes: 38 additions & 4 deletions frontend/src/components/FilesUploader/FilesUploader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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>
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand Down
76 changes: 67 additions & 9 deletions frontend/src/components/FilesUploader/FilesUploaderArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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'
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -330,5 +384,9 @@ defineExpose({
showFileBrowser,
showWebLink,
webLink,
showCamera,
cameraImage,
captureImage,
uploadViaCamera,
})
</script>
6 changes: 3 additions & 3 deletions frontend/src/components/FilesUploader/filesUploaderHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
interface UploadOptions {
file?: File
fileObj?: File
private?: boolean
fileUrl?: string
folder?: string
Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 7afdf97

Please sign in to comment.