mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-01 12:36:43 +00:00
153 lines
4.8 KiB
Vue
153 lines
4.8 KiB
Vue
<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 { isMobileMode } = useGlobal()
|
|
|
|
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="isMobileMode ? 'small' : '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-nc-bg-gray-extralight items-center justify-center">
|
|
<div
|
|
class="flex flex-col hover:bg-nc-bg-default 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-nc-bg-default rounded-md shadow-sm">
|
|
<mdi-camera class="text-4xl text-nc-content-gray" />
|
|
</div>
|
|
<h1 class="text-nc-content-gray 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 border border-nc-border-red"
|
|
>
|
|
<video ref="videoRef" class="rounded-md w-full aspect-video max-w-md flex-1 object-contain" 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">
|
|
<canvas ref="canvasRef" class="mb-2 rounded-md w-full aspect-video max-w-md flex-1 object-contain"></canvas>
|
|
|
|
<div class="relative text-[12px] font-semibold text-nc-content-gray 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-nc-bg-default">
|
|
<NcTooltip placement="bottom">
|
|
<template #title> {{ $t('title.removeFile') }} </template>
|
|
<component :is="iconMap.delete" class="!text-nc-content-red-medium cursor-pointer" @click="retakeImage" />
|
|
</NcTooltip>
|
|
</div>
|
|
</div>
|
|
<div class="flex-none text-[10px] font-semibold text-nc-content-gray-muted">
|
|
{{ 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>
|