forked from Mirrors/elk
110 lines
2.8 KiB
Vue
110 lines
2.8 KiB
Vue
<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 | null
|
|
/** 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 emit = defineEmits<{
|
|
(event: 'update:modelValue', value: File): void
|
|
}>()
|
|
|
|
const vmFile = useVModel(props, 'modelValue', emit, { 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>
|