Create a rotate 90 degrees clockwise button for each image (#666)

* Create a rotate 90 degrees clockwise button for each image

* Added more error handling and turned rotate function to async as recommended by coderabbitai
This commit is contained in:
EdWorth120
2025-05-04 06:28:03 -03:00
committed by GitHub
parent eb21da50e3
commit b89364d8c5

View File

@@ -75,6 +75,26 @@
<p>Delete photo</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
size="icon"
type="button"
variant="default"
@click.prevent="
async () => {
await rotateBase64Image90Deg(photo.fileBase64, index);
}
"
>
<MdiRotateClockwise />
<div class="sr-only">Rotate photo</div>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Rotate photo</p>
</TooltipContent>
</Tooltip>
<!-- TODO: re-enable when we have a way to set primary photos -->
<!-- <Tooltip>
<TooltipTrigger>
@@ -114,6 +134,7 @@
import MdiPackageVariant from "~icons/mdi/package-variant";
import MdiPackageVariantClosed from "~icons/mdi/package-variant-closed";
import MdiDelete from "~icons/mdi/delete";
import MdiRotateClockwise from "~icons/mdi/rotate-clockwise";
// import MdiStarOutline from "~icons/mdi/star-outline";
// import MdiStar from "~icons/mdi/star";
import { AttachmentTypes } from "~~/lib/api/types/non-generated";
@@ -294,4 +315,79 @@
navigateTo(`/item/${data.id}`);
}
}
function dataURLtoFile(dataURL: string, fileName: string) {
try {
const arr = dataURL.split(",");
const mimeMatch = arr[0].match(/:(.*?);/);
if (!mimeMatch || !mimeMatch[1]) {
throw new Error("Invalid data URL format");
}
const mime = mimeMatch[1];
// Validate mime type is an image
if (!mime.startsWith("image/")) {
throw new Error("Invalid mime type, expected image");
}
const bstr = atob(arr[arr.length - 1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], fileName, { type: mime });
} catch (error) {
console.error("Error converting data URL to file:", error);
// Return a fallback or rethrow based on your error handling strategy
throw error;
}
}
async function rotateBase64Image90Deg(base64Image: string, index: number) {
// Create an off-screen canvas
const offScreenCanvas = document.createElement("canvas");
const offScreenCanvasCtx = offScreenCanvas.getContext("2d");
if (!offScreenCanvasCtx) {
toast.error("Your browser doesn't support canvas operations");
return;
}
// Create an image
const img = new Image();
// Create a promise to handle the image loading
await new Promise<void>((resolve, reject) => {
img.onload = () => resolve();
img.onerror = () => reject(new Error("Failed to load image"));
img.src = base64Image;
}).catch(error => {
toast.error("Failed to rotate image: " + error.message);
});
// Set its dimensions to rotated size
offScreenCanvas.height = img.width;
offScreenCanvas.width = img.height;
// Rotate and draw source image into the off-screen canvas
offScreenCanvasCtx.rotate((90 * Math.PI) / 180);
offScreenCanvasCtx.translate(0, -offScreenCanvas.width);
offScreenCanvasCtx.drawImage(img, 0, 0);
const imageType = base64Image.match(/^data:(.+);base64/)?.[1] || "image/jpeg";
// Encode image to data-uri with base64
try {
form.photos[index].fileBase64 = offScreenCanvas.toDataURL(imageType, 100);
form.photos[index].file = dataURLtoFile(form.photos[index].fileBase64, form.photos[index].photoName);
} catch (error) {
toast.error("Failed to process rotated image");
console.error(error);
} finally {
// Clean up resources
offScreenCanvas.width = 0;
offScreenCanvas.height = 0;
}
}
</script>