feat: start implementing image cropping when uploading pfp
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful

still need to fix the selection to within the canvas boundries, and fix theming
This commit is contained in:
JustTemmie 2025-07-04 08:04:50 +02:00
parent 873f1c81a9
commit 3c4965c06f
Signed by: justtemmie
SSH key fingerprint: SHA256:nBO+OwpTkd8LYhe38PIqdxmDvkIg9Vw2EbrRZM97dkU
4 changed files with 208 additions and 1 deletions

61
components/CropPopup.vue Normal file
View file

@ -0,0 +1,61 @@
<template>
<div class="crop-popup">
<div class="crop-container">
<img ref="image" :src="imageSrc" alt="Picture">
</div>
<div class="image-preview"></div>
<Button text="Crop" :callback="cropImage"></Button>
<Button text="Cancel" :callback="closePopup"></Button>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import Cropper from 'cropperjs';
const props = defineProps({
imageSrc: String,
onCrop: Function,
onClose: Function,
});
const image = ref<HTMLImageElement | null>(null);
const cropper = ref<Cropper | null>(null);
watch(image, (newValue) => {
if (newValue) {
cropper.value = new Cropper(newValue)
const selection = cropper.value.getCropperSelection()
if (selection) {
selection.precise = true
selection.aspectRatio = 1
selection.initialCoverage = 1
}
}
});
async function cropImage() {
if (cropper) {
cropper.value?.element
const selection = cropper.value?.getCropperSelection();
if (selection) {
const canvas = await selection.$toCanvas({width: 256, height: 256})
console.log(canvas)
canvas.toBlob((blob) => {
if (blob && props.onCrop) {
props.onCrop(blob);
}
});
}
}
}
function closePopup() {
if (props.onClose) {
props.onClose();
}
}
</script>
<style scoped>
</style>

View file

@ -21,12 +21,23 @@
</div>
<UserPopup v-if="user" :user="user" id="profile-popup"></UserPopup>
<CropPopup
v-if="isCropPopupVisible"
:imageSrc="cropImageSrc"
:onCrop="handleCrop"
:onClose="closeCropPopup"
/>
</div>
</div>
<div id="crop-container">
</div>
</template>
<script lang="ts" setup>
import Button from '~/components/Button.vue';
import CropPopup from '~/components/CropPopup.vue';
import type { UserResponse } from '~/types/interfaces';
const { fetchUser } = useAuth();
@ -38,6 +49,8 @@ if (!user) {
}
let newPfpFile: File;
const isCropPopupVisible = ref(false); // State to manage the visibility of the CropPopup
const cropImageSrc = ref(''); // State to hold the image source for cropping
async function saveChanges() {
if (!user) return;
@ -93,7 +106,8 @@ async function changeAvatar() {
const reader = new FileReader();
reader.addEventListener("load", () => {
if (reader.result && typeof reader.result === 'string') {
user.avatar = reader.result;
cropImageSrc.value = reader.result;
isCropPopupVisible.value = true;
}
});
reader.readAsDataURL(file);
@ -102,6 +116,27 @@ async function changeAvatar() {
input.click()
}
function handleCrop(blob: Blob) {
if (!user) return;
newPfpFile = new File([blob], 'avatar.png', { type: 'image/png' })
const reader = new FileReader();
reader.addEventListener("load", () => {
if (reader.result && typeof reader.result === 'string') {
user.avatar = reader.result;
}
});
reader.readAsDataURL(newPfpFile)
closeCropPopup()
}
function closeCropPopup() {
isCropPopupVisible.value = false
}
</script>
<style scoped>