mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.5 KiB
147 lines
4.5 KiB
<script setup lang="ts"> |
|
import { useAttachmentCell } from '../utils' |
|
|
|
const emits = defineEmits<{ |
|
'update:visible': [value: boolean] |
|
'upload': [fileList: File[]] |
|
}>() |
|
|
|
const { isLoading, startCamera: _startCamera, stopCamera: _stopCamera, videoStream, permissionGranted } = useAttachmentCell()! |
|
|
|
const capturedImage = ref<null | File>(null) |
|
const videoRef = ref<HTMLVideoElement | undefined>() |
|
const canvasRef = ref<HTMLCanvasElement | undefined>() |
|
|
|
const startCamera = async () => { |
|
try { |
|
await _startCamera() |
|
if (!videoRef.value || !videoStream.value) return |
|
videoRef.value.srcObject = videoStream.value |
|
} catch (error) {} |
|
} |
|
|
|
const stopCamera = () => { |
|
_stopCamera() |
|
if (videoRef.value) { |
|
videoRef.value.srcObject = null |
|
} |
|
} |
|
|
|
const retakeImage = () => { |
|
capturedImage.value = null |
|
startCamera() |
|
} |
|
|
|
const captureImage = () => { |
|
const video = videoRef.value |
|
const canvas = canvasRef.value |
|
|
|
if (!video || !canvas) return |
|
|
|
canvas.width = video.videoWidth |
|
canvas.height = video.videoHeight |
|
const context = canvas.getContext('2d') |
|
|
|
if (context) { |
|
canvas.style.display = 'block' |
|
context.translate(canvas.width, 0) |
|
context.scale(-1, 1) |
|
context.drawImage(video, 0, 0, canvas.width, canvas.height) |
|
canvas.toBlob((blob) => { |
|
if (!blob) return |
|
capturedImage.value = new File([blob], `${new Date().toDateString()}.png`, { type: 'image/png' }) |
|
}, 'image/png') |
|
stopCamera() |
|
} |
|
} |
|
|
|
const closeMenu = () => { |
|
emits('update:visible', false) |
|
} |
|
|
|
onMounted(() => { |
|
startCamera() |
|
}) |
|
|
|
onBeforeUnmount(() => { |
|
stopCamera() |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div class="w-full relative h-full"> |
|
<NcTooltip class="absolute top-3 right-2"> |
|
<NcButton type="text" class="!border-0" size="xsmall" @click="closeMenu"> |
|
<GeneralIcon icon="close" /> |
|
</NcButton> |
|
|
|
<template #title> {{ $t('general.close') }} </template> |
|
</NcTooltip> |
|
<div v-if="!permissionGranted" class="w-full h-full flex bg-gray-50 items-center justify-center"> |
|
<div |
|
class="flex flex-col hover:bg-white p-2 cursor-pointer rounded-md !transition-all transition-ease-in-out duration-300 gap-2 items-center justify-center" |
|
@click="startCamera" |
|
> |
|
<div class="p-5 bg-white rounded-md shadow-sm"> |
|
<mdi-camera class="text-4xl text-gray-800" /> |
|
</div> |
|
<h1 class="text-gray-800 font-semibold text-center text-xl"> |
|
{{ $t('labels.allowAccessToYourCamera') }} |
|
</h1> |
|
</div> |
|
</div> |
|
<div |
|
v-else |
|
:class="{ |
|
'py-8': !capturedImage, |
|
'pt-8 pb-2': capturedImage, |
|
}" |
|
class="w-full gap-3 h-full flex-col flex items-center justify-between" |
|
> |
|
<div v-show="!capturedImage" class="w-full gap-3 h-full flex-col flex items-center justify-between"> |
|
<video ref="videoRef" class="rounded-md" style="width: 400px" autoplay></video> |
|
|
|
<NcButton class="!rounded-full !px-0" @click="captureImage"> |
|
<mdi-camera class="text-xl" /> |
|
</NcButton> |
|
</div> |
|
|
|
<div v-show="capturedImage" class="flex group flex-col gap-1"> |
|
<canvas ref="canvasRef" class="rounded-md" style="width: 400px; display: none"></canvas> |
|
|
|
<div class="relative text-[12px] font-semibold text-gray-800 flex"> |
|
<div class="flex-auto truncate line-height-4"> |
|
{{ capturedImage?.name }} |
|
</div> |
|
<div class="flex-none hide-ui transition-all transition-ease-in-out !h-4 flex items-center bg-white"> |
|
<NcTooltip placement="bottom"> |
|
<template #title> {{ $t('title.removeFile') }} </template> |
|
<component :is="iconMap.delete" class="!text-red-500 cursor-pointer" @click="retakeImage" /> |
|
</NcTooltip> |
|
</div> |
|
</div> |
|
<div class="flex-none text-[10px] font-semibold text-gray-500"> |
|
{{ formatBytes(capturedImage?.size, 0) }} |
|
</div> |
|
</div> |
|
<div v-show="capturedImage" class="flex gap-2 pr-2 bottom-1 relative w-full items-center justify-end"> |
|
<NcButton :disabled="isLoading" type="secondary" size="small" @click="closeMenu"> |
|
{{ $t('labels.cancel') }} |
|
</NcButton> |
|
|
|
<NcButton :loading="isLoading" size="small" @click="emits('upload', [capturedImage] as File[])"> |
|
<template v-if="!isLoading"> {{ $t('labels.uploadImage') }} </template> |
|
|
|
<template v-else> {{ $t('labels.uploading') }} </template> |
|
</NcButton> |
|
</div> |
|
</div> |
|
</div> |
|
</template> |
|
|
|
<style scoped lang="scss"> |
|
video { |
|
-webkit-transform: scaleX(-1); |
|
transform: scaleX(-1); |
|
} |
|
</style>
|
|
|