From 04ad357555ff5c457f977b83b42ed5ce81bc8890 Mon Sep 17 00:00:00 2001 From: Jeremiasz Major Date: Mon, 16 Dec 2024 15:12:16 +0100 Subject: [PATCH] replace croppr with custom cropper --- package.json | 1 - resources/css/style.css | 1 - resources/js/Components/Cropper.svelte | 60 ------ .../js/Components/Cropper/Cropper.svelte | 193 ++++++++++++++++++ resources/js/Components/Cropper/box.ts | 99 +++++++++ .../Artists/Components/Form/PhotoForm.svelte | 54 ++--- .../Pages/Artists/Components/Form/helpers.ts | 19 ++ resources/js/helpers/clamp.ts | 3 + resources/js/helpers/cropper.ts | 32 --- tsconfig.json | 2 +- yarn.lock | 5 - 11 files changed, 335 insertions(+), 134 deletions(-) delete mode 100644 resources/js/Components/Cropper.svelte create mode 100644 resources/js/Components/Cropper/Cropper.svelte create mode 100644 resources/js/Components/Cropper/box.ts create mode 100644 resources/js/Pages/Artists/Components/Form/helpers.ts create mode 100644 resources/js/helpers/clamp.ts delete mode 100644 resources/js/helpers/cropper.ts diff --git a/package.json b/package.json index 8615cae4..670c1d97 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "dependencies": { "@inertiajs/svelte": "2.0.0-beta.3", "@tailwindcss/forms": "^0.5.9", - "croppr": "^2.3", "esm-env": "^1.2", "pretty-bytes": "^6.1", "svelte": "^5.7", diff --git a/resources/css/style.css b/resources/css/style.css index 37d4ae4c..db414121 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -1,4 +1,3 @@ -@import 'croppr/src/css/croppr.css'; @import './base.css'; @import './headers.css'; @import './placeholders.css'; diff --git a/resources/js/Components/Cropper.svelte b/resources/js/Components/Cropper.svelte deleted file mode 100644 index 2f512bdc..00000000 --- a/resources/js/Components/Cropper.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - diff --git a/resources/js/Components/Cropper/Cropper.svelte b/resources/js/Components/Cropper/Cropper.svelte new file mode 100644 index 00000000..35bd925d --- /dev/null +++ b/resources/js/Components/Cropper/Cropper.svelte @@ -0,0 +1,193 @@ + + + + + + +
+ +
+
+ {#each handles as handle} + {@const { position: [x, y], cursor } = handle} + +
onmousedown(e, handle)} + style:cursor="{cursor}-resize" + style:left="{scaled(crop.x + (crop.width * x))}px" + style:top="{scaled(crop.y + (crop.height * y))}px" + >
+ {/each} +
+ + onmousedown(e, 'anchor')} + class="cropped" + style:clip="rect({scaled(crop.y)}px, {scaled(crop.x + crop.width)}px, {scaled(crop.y + crop.height)}px, {scaled(crop.x)}px)" + > +
+ + diff --git a/resources/js/Components/Cropper/box.ts b/resources/js/Components/Cropper/box.ts new file mode 100644 index 00000000..bc4f8c41 --- /dev/null +++ b/resources/js/Components/Cropper/box.ts @@ -0,0 +1,99 @@ +export interface Box { + x: number; + y: number; + width: number; + height: number; +} + +type Coordinates = [number, number]; + +export function round(box: Box): Box { + return { + x: Math.round(box.x), + y: Math.round(box.y), + width: Math.round(box.width), + height: Math.round(box.height), + }; +} + +function resize( + box: Box, + newWidth: number, + newHeight: number, + origin: Coordinates, +): Box { + const fromX = box.x + (box.width * origin[0]); + const fromY = box.y + (box.height * origin[1]); + + return { + x: fromX - (newWidth * origin[0]), + y: fromY - (newHeight * origin[1]), + width: newWidth, + height: newHeight, + }; +} + +function scale(box: Box, factor: number, origin: Coordinates): Box { + const newWidth = box.width * factor; + const newHeight = box.height * factor; + + return resize(box, newWidth, newHeight, origin); +} + +function getAbsolutePoint(box: Box, [x, y]: Coordinates): Coordinates { + return [ + box.x + (box.width * x), + box.y + (box.height * y), + ]; +} + +export function constrainToRatio( + box: Box, + ratio: number, + origin: Coordinates, + grow: 'width' | 'height', +): Box { + return grow === 'width' + ? resize(box, box.height / ratio, box.height, origin) + : resize(box, box.width, box.width * ratio, origin); +} + +export function constrainToBoundary( + box: Box, + boundaryWidth: number, + boundaryHeight: number, + origin: Coordinates, +): Box { + const [originX, originY] = getAbsolutePoint(box, origin); + const maxIfLeft = originX; + const maxIfTop = originY; + const maxIfRight = boundaryWidth - originX; + const maxIfBottom = boundaryHeight - originY; + + let maxWidth: number; + if (origin[0] > 0.5) { + maxWidth = maxIfLeft; + } else if (origin[0] < 0.5) { + maxWidth = maxIfRight; + } else { + maxWidth = Math.min(maxIfLeft, maxIfRight) * 2; + } + let maxHeight: number; + if (origin[1] > 0.5) { + maxHeight = maxIfTop; + } else if (origin[1] < 0.5) { + maxHeight = maxIfBottom; + } else { + maxHeight = Math.min(maxIfTop, maxIfBottom) * 2; + } + + if (box.width > maxWidth) { + return scale(box, maxWidth / box.width, origin); + } + + if (box.height > maxHeight) { + return scale(box, maxHeight / box.height, origin); + } + + return box; +} diff --git a/resources/js/Pages/Artists/Components/Form/PhotoForm.svelte b/resources/js/Pages/Artists/Components/Form/PhotoForm.svelte index 118b97a1..47d29513 100644 --- a/resources/js/Pages/Artists/Components/Form/PhotoForm.svelte +++ b/resources/js/Pages/Artists/Components/Form/PhotoForm.svelte @@ -1,11 +1,11 @@ @@ -183,22 +190,12 @@ {#if activePicker.type !== 'remove' && previewUrl && showCropper} -
-
+
+
@@ -211,10 +208,6 @@ - -
y: {$form.photo.crop.face.y}
- - @@ -227,14 +220,11 @@
width: {$form.photo.crop.face.size}
-
-
+
+
@@ -247,10 +237,6 @@ - -
y: {$form.photo.crop.image.y}
- - diff --git a/resources/js/Pages/Artists/Components/Form/helpers.ts b/resources/js/Pages/Artists/Components/Form/helpers.ts new file mode 100644 index 00000000..a1737a25 --- /dev/null +++ b/resources/js/Pages/Artists/Components/Form/helpers.ts @@ -0,0 +1,19 @@ +import type { ArtistFaceCrop } from '@/types/artists'; +import type { Box } from '@/Components/Cropper/box'; + +export function faceCropToBox(crop: ArtistFaceCrop): Box { + return { + x: crop.x, + y: crop.y, + width: crop.size, + height: crop.size, + }; +} + +export function boxToFaceCrop(box: Box): ArtistFaceCrop { + return { + x: box.x, + y: box.y, + size: box.width, + }; +} diff --git a/resources/js/helpers/clamp.ts b/resources/js/helpers/clamp.ts new file mode 100644 index 00000000..9465da56 --- /dev/null +++ b/resources/js/helpers/clamp.ts @@ -0,0 +1,3 @@ +export default function clamp(min: number, number: number, max: number) { + return Math.min(Math.max(min, number), max); +} diff --git a/resources/js/helpers/cropper.ts b/resources/js/helpers/cropper.ts deleted file mode 100644 index 2ddb65fd..00000000 --- a/resources/js/helpers/cropper.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Croppr from 'croppr'; - -export default class Cropper extends Croppr { - getScaleFactors() { - // @ts-expect-error this is not typed - const el = this.imageEl as HTMLImageElement; - - const [actualWidth, actualHeight] = [el.naturalWidth, el.naturalHeight]; - - const { width: elementWidth, height: elementHeight } = el.getBoundingClientRect(); - - return [actualWidth / elementWidth, actualHeight / elementHeight]; - } - - resizeToScaled(width: number, height: number) { - const [factorX, factorY] = this.getScaleFactors(); - - return this.resizeTo( - Math.round(width / factorX), - Math.round(height / factorY), - ); - } - - moveToScaled(x: number, y: number) { - const [factorX, factorY] = this.getScaleFactors(); - - return this.moveTo( - Math.round(x / factorX), - Math.round(y / factorY), - ); - } -} diff --git a/tsconfig.json b/tsconfig.json index b7c9fb8b..de9a9e9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "types": ["node", "vite/client"], "allowJs": true, "checkJs": true, - "skipLibCheck": true, // error in croppr + "skipLibCheck": true, }, "include": [ "resources/js/**/*.ts", diff --git a/yarn.lock b/yarn.lock index bacecb22..b0f8999a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -886,11 +886,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -croppr@^2.3: - version "2.3.1" - resolved "https://registry.yarnpkg.com/croppr/-/croppr-2.3.1.tgz#d279e006531240fa8ebf2681e4127ae7c42b074e" - integrity sha512-0rvTl4VmR3I4AahjJPF1u9IlT7ckvjIcgaLnUjYaY+UZsP9oxlVYZWYDuqM3SVCQiaI7DXMjR7wOEYT+mydOFg== - cross-spawn@^7.0.0, cross-spawn@^7.0.5: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
width: {$form.photo.crop.image.width}