mirror of
https://github.com/koel/koel
synced 2025-02-25 19:57:11 +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)" />
|
<UserAvatar v-if="profile.avatar" :user="profile" style="width: var(--w)" />
|
||||||
|
|
||||||
<div class="buttons">
|
<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" />
|
<Icon :icon="faUpload" />
|
||||||
</button>
|
</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" />
|
<Icon :icon="faRefresh" />
|
||||||
</button>
|
</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" />
|
<Icon :icon="faTimes" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="cropperSource" class="cropper-wrapper">
|
<ImageCropper v-if="cropperSource" :source="cropperSource" @cancel="onCancel" @crop="onCrop" />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -40,7 +26,6 @@ import {
|
||||||
faTimes,
|
faTimes,
|
||||||
faUpload
|
faUpload
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Cropper } from 'vue-advanced-cropper'
|
|
||||||
import 'vue-advanced-cropper/dist/style.css'
|
import 'vue-advanced-cropper/dist/style.css'
|
||||||
import { computed, ref, toRefs } from 'vue'
|
import { computed, ref, toRefs } from 'vue'
|
||||||
import { useFileDialog } from '@vueuse/core'
|
import { useFileDialog } from '@vueuse/core'
|
||||||
|
@ -49,16 +34,15 @@ import { useFileReader } from '@/composables'
|
||||||
import { gravatar } from '@/utils'
|
import { gravatar } from '@/utils'
|
||||||
|
|
||||||
import UserAvatar from '@/components/user/UserAvatar.vue'
|
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 props = defineProps<{ profile: Pick<User, 'name' | 'avatar'> }>()
|
||||||
const { profile } = toRefs(props)
|
const { profile } = toRefs(props)
|
||||||
|
|
||||||
const cropper = ref<typeof Cropper>()
|
const { open: openFileDialog, onChange, reset } = useFileDialog({
|
||||||
|
|
||||||
const { open: openFileDialog, onChange } = useFileDialog({
|
|
||||||
accept: 'image/*',
|
accept: 'image/*',
|
||||||
multiple: false
|
multiple: false,
|
||||||
|
reset: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const cropperSource = ref<string | null>(null)
|
const cropperSource = ref<string | null>(null)
|
||||||
|
@ -84,11 +68,12 @@ const resetAvatar = () => {
|
||||||
|
|
||||||
const avatarChanged = computed(() => profile.value.avatar !== userStore.current.avatar)
|
const avatarChanged = computed(() => profile.value.avatar !== userStore.current.avatar)
|
||||||
|
|
||||||
const crop = () => {
|
const onCrop = (result: string) => {
|
||||||
const { canvas } = cropper.value!.getResult()
|
profile.value.avatar = result
|
||||||
profile.value.avatar = canvas.toDataURL()
|
|
||||||
cropperSource.value = null
|
cropperSource.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onCancel = () => (cropperSource.value = null)
|
||||||
</script>
|
</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…
Add table
Reference in a new issue