Files
frontend/src/lib/components/ImageCropModal.svelte

89 lines
2.4 KiB
Svelte

<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let open = false;
export let imageSrc = '';
const dispatch = createEventDispatcher<{
crop: Blob;
cancel: void;
}>();
let cropperInstance: import('cropperjs').default | null = null;
let isProcessing = false;
// Svelte action — only runs in the browser, so dynamic import is safe here
function initCropper(node: HTMLImageElement) {
import('cropperjs').then(({ default: Cropper }) => {
cropperInstance = new Cropper(node, {
aspectRatio: 1,
viewMode: 0, // no restriction — canvas can move freely
dragMode: 'move', // drag moves the image, not the crop box
cropBoxMovable: false,
cropBoxResizable: false,
toggleDragModeOnDblclick: false,
autoCropArea: 0.8, // crop box = 80% so image extends beyond it and can be dragged
});
});
return {
destroy() {
cropperInstance?.destroy();
cropperInstance = null;
}
};
}
function handleApply() {
if (!cropperInstance) return;
isProcessing = true;
const canvas = cropperInstance.getCroppedCanvas({ width: 500, height: 500 });
canvas.toBlob(
(blob) => {
if (blob) dispatch('crop', blob);
isProcessing = false;
},
'image/jpeg',
0.92
);
}
function handleCancel() {
dispatch('cancel');
}
</script>
{#if open}
<div class="modal modal-open z-50">
<div class="modal-box max-w-2xl w-full">
<h3 class="font-bold text-lg mb-1">Crop Logo</h3>
<p class="text-sm text-base-content/50 mb-4">Drag to reposition &bull; Scroll or pinch to zoom</p>
<div class="w-full h-80 bg-base-300 rounded-lg overflow-hidden">
<img
src={imageSrc}
alt="Crop preview"
use:initCropper
class="block max-w-full"
/>
</div>
<div class="modal-action mt-6">
<button class="btn btn-ghost" on:click={handleCancel} disabled={isProcessing}>
Cancel
</button>
<button class="btn btn-primary" on:click={handleApply} disabled={isProcessing}>
{#if isProcessing}
<span class="loading loading-spinner loading-sm"></span>
Processing...
{:else}
Apply Crop
{/if}
</button>
</div>
</div>
<div class="modal-backdrop bg-black/60" on:click={handleCancel} role="presentation"></div>
</div>
{/if}