mirror of
https://github.com/koel/koel
synced 2024-11-27 22:40:26 +00:00
feat: extract image cropper to own component
This commit is contained in:
parent
e106bff23d
commit
9238ecfd44
2 changed files with 95 additions and 27 deletions
|
@ -3,34 +3,20 @@
|
|||
<UserAvatar v-if="profile.avatar" :user="profile" style="width: var(--w)" />
|
||||
|
||||
<div class="buttons">
|
||||
<button class="upload" type="button" title="Pick a new avatar" @click.prevent="openFileDialog">
|
||||
<button class="upload" title="Pick a new avatar" type="button" @click.prevent="openFileDialog">
|
||||
<Icon :icon="faUpload" />
|
||||
</button>
|
||||
|
||||
<button v-if="avatarChanged" type="button" class="reset" title="Reset avatar" @click.prevent="resetAvatar">
|
||||
<button v-if="avatarChanged" class="reset" title="Reset avatar" type="button" @click.prevent="resetAvatar">
|
||||
<Icon :icon="faRefresh" />
|
||||
</button>
|
||||
|
||||
<button v-else class="remove" type="button" title="Remove avatar" @click.prevent="removeAvatar">
|
||||
<button v-else class="remove" title="Remove avatar" type="button" @click.prevent="removeAvatar">
|
||||
<Icon :icon="faTimes" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="cropperSource" class="cropper-wrapper">
|
||||
<div>
|
||||
<Cropper
|
||||
ref="cropper"
|
||||
:src="cropperSource"
|
||||
:stencil-props="{ aspectRatio: 1 }"
|
||||
:min-height="192"
|
||||
:max-height="480"
|
||||
/>
|
||||
<div class="controls">
|
||||
<Btn type="button" green @click.prevent="crop">Crop</Btn>
|
||||
<Btn type="button" red @click.prevent="cropperSource = null">Cancel</Btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ImageCropper v-if="cropperSource" :source="cropperSource" @cancel="onCancel" @crop="onCrop" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -40,7 +26,6 @@ import {
|
|||
faTimes,
|
||||
faUpload
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { Cropper } from 'vue-advanced-cropper'
|
||||
import 'vue-advanced-cropper/dist/style.css'
|
||||
import { computed, ref, toRefs } from 'vue'
|
||||
import { useFileDialog } from '@vueuse/core'
|
||||
|
@ -49,16 +34,15 @@ import { useFileReader } from '@/composables'
|
|||
import { gravatar } from '@/utils'
|
||||
|
||||
import UserAvatar from '@/components/user/UserAvatar.vue'
|
||||
import Btn from '@/components/ui/Btn.vue'
|
||||
import ImageCropper from '@/components/utils/ImageCropper.vue'
|
||||
|
||||
const props = defineProps<{ profile: Pick<User, 'name' | 'avatar'> }>()
|
||||
const { profile } = toRefs(props)
|
||||
|
||||
const cropper = ref<typeof Cropper>()
|
||||
|
||||
const { open: openFileDialog, onChange } = useFileDialog({
|
||||
const { open: openFileDialog, onChange, reset } = useFileDialog({
|
||||
accept: 'image/*',
|
||||
multiple: false
|
||||
multiple: false,
|
||||
reset: true
|
||||
})
|
||||
|
||||
const cropperSource = ref<string | null>(null)
|
||||
|
@ -84,11 +68,12 @@ const resetAvatar = () => {
|
|||
|
||||
const avatarChanged = computed(() => profile.value.avatar !== userStore.current.avatar)
|
||||
|
||||
const crop = () => {
|
||||
const { canvas } = cropper.value!.getResult()
|
||||
profile.value.avatar = canvas.toDataURL()
|
||||
const onCrop = (result: string) => {
|
||||
profile.value.avatar = result
|
||||
cropperSource.value = null
|
||||
}
|
||||
|
||||
const onCancel = () => (cropperSource.value = null)
|
||||
</script>
|
||||
|
||||
|
||||
|
|
83
resources/assets/js/components/utils/ImageCropper.vue
Normal file
83
resources/assets/js/components/utils/ImageCropper.vue
Normal file
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class="cropper-wrapper">
|
||||
<div>
|
||||
<Cropper
|
||||
ref="cropper"
|
||||
:src="source"
|
||||
:stencil-props="{ aspectRatio: 1 }"
|
||||
:min-width="config.minWidth"
|
||||
:max-width="config.maxWidth"
|
||||
/>
|
||||
<div class="controls">
|
||||
<Btn type="button" green @click.prevent="crop">Crop</Btn>
|
||||
<Btn type="button" red @click.prevent="cancel">Cancel</Btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, toRefs } from 'vue'
|
||||
import { Cropper } from 'vue-advanced-cropper'
|
||||
import 'vue-advanced-cropper/dist/style.css'
|
||||
|
||||
import Btn from '@/components/ui/Btn.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
source?: string | null
|
||||
config?: {
|
||||
minWidth: number
|
||||
maxWidth: number
|
||||
}
|
||||
}>(), {
|
||||
source: null,
|
||||
config: () => ({
|
||||
minWidth: 192,
|
||||
maxWidth: 480
|
||||
})
|
||||
})
|
||||
|
||||
const { source, config } = toRefs(props)
|
||||
const cropper = ref<typeof Cropper>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'crop', result: string): void
|
||||
(e: 'cancel'): void
|
||||
}>()
|
||||
|
||||
const crop = () => emits('crop', cropper.value?.getResult().canvas.toDataURL())
|
||||
const cancel = () => emits('cancel')
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cropper-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 99;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: fixed;
|
||||
right: 1.5rem;
|
||||
top: 1.5rem;
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue