elk/components/common/CommonCropImage.vue

110 lines
2.8 KiB
Vue
Raw Normal View History

<script lang="ts" setup>
import type { Boundaries } from 'vue-advanced-cropper'
import { Cropper } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'
export interface Props {
/** Images to be cropped */
modelValue?: File
/** Crop frame aspect ratio (width/height), default 1/1 */
stencilAspectRatio?: number
/** The ratio of the longest edge of the cut box to the length of the cut screen, default 0.9, not more than 1 */
stencilSizePercentage?: number
}
const props = withDefaults(defineProps<Props>(), {
stencilAspectRatio: 1 / 1,
stencilSizePercentage: 0.9,
})
const emits = defineEmits<{
(event: 'update:modelValue', value: File): void
}>()
const vmFile = useVModel(props, 'modelValue', emits, { passive: true })
const cropperDialog = ref(false)
const cropper = ref<InstanceType<typeof Cropper>>()
const cropperFlag = ref(false)
const cropperImage = reactive({
src: '',
type: 'image/jpg',
})
const stencilSize = ({ boundaries }: { boundaries: Boundaries }) => {
return {
width: boundaries.width * props.stencilSizePercentage,
height: boundaries.height * props.stencilSizePercentage,
}
}
watch(vmFile, (file, _, onCleanup) => {
let expired = false
onCleanup(() => expired = true)
if (file && !cropperFlag.value) {
cropperDialog.value = true
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (e) => {
if (expired)
return
cropperImage.src = e.target?.result as string
cropperImage.type = file.type
}
}
cropperFlag.value = false
})
const cropImage = () => {
if (cropper.value && vmFile.value) {
cropperFlag.value = true
cropperDialog.value = false
const { canvas } = cropper.value.getResult()
canvas?.toBlob((blob) => {
vmFile.value = new File([blob as any], `cropped${vmFile.value?.name}` as string, { type: blob?.type })
}, cropperImage.type)
}
}
</script>
<template>
<ModalDialog v-model="cropperDialog" :use-v-if="false" max-w-500px flex>
<div flex-1 w-0>
<div text-lg text-center my2 px3>
<h1>
{{ $t('action.edit') }}
</h1>
</div>
<div aspect-ratio-1>
<Cropper
ref="cropper"
class="overflow-hidden w-full h-full"
:src="cropperImage.src"
:resize-image="{
adjustStencil: false,
}"
:stencil-size="stencilSize"
:stencil-props="{
aspectRatio: props.stencilAspectRatio,
movable: false,
resizable: false,
handlers: {},
}"
image-restriction="stencil"
/>
</div>
<div m-4>
<button
btn-solid w-full rounded text-sm
@click="cropImage()"
>
{{ $t('action.confirm') }}
</button>
</div>
</div>
</ModalDialog>
</template>