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} |
-
-
-
-
width: |
{$form.photo.crop.face.size} |
@@ -227,14 +220,11 @@
-
-
+
+
@@ -247,10 +237,6 @@
y: |
{$form.photo.crop.image.y} |
-
-
-
-
width: |
{$form.photo.crop.image.width} |
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"