pixedi 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +65 -60
  2. package/README.npm.md +80 -0
  3. package/dist/lib/index.js +1602 -0
  4. package/dist/lib/index.umd.js +28 -0
  5. package/package.json +9 -4
  6. package/.github/workflows/deploy.yml +0 -54
  7. package/.prettierr +0 -9
  8. package/.storybook/main.ts +0 -16
  9. package/.storybook/preview.ts +0 -53
  10. package/.vscode/extensions.json +0 -6
  11. package/eslint.config.js +0 -22
  12. package/index.html +0 -13
  13. package/pnpm-workspace.yaml +0 -2
  14. package/public/favicon.svg +0 -1
  15. package/src/app/App.tsx +0 -10
  16. package/src/app/entries/index.ts +0 -145
  17. package/src/app/providers/RouterProvider.tsx +0 -6
  18. package/src/app/providers/index.ts +0 -1
  19. package/src/app/router/AppRouter.tsx +0 -12
  20. package/src/app/router/index.ts +0 -0
  21. package/src/app/styles/index.css +0 -4
  22. package/src/features/gallery/GalleryPage.tsx +0 -33
  23. package/src/features/gallery/ImageEditPage.tsx +0 -37
  24. package/src/features/gallery/index.ts +0 -2
  25. package/src/main.tsx +0 -10
  26. package/src/shared/components/ImageCard/ImageCard.tsx +0 -54
  27. package/src/shared/components/ImageCard/index.ts +0 -1
  28. package/src/shared/components/ImageEditor/ImageEditor.tsx +0 -48
  29. package/src/shared/components/ImageEditor/assets/icons.tsx +0 -114
  30. package/src/shared/components/ImageEditor/constants.ts +0 -118
  31. package/src/shared/components/ImageEditor/crop/Crop.tsx +0 -60
  32. package/src/shared/components/ImageEditor/crop/CropBorders.tsx +0 -28
  33. package/src/shared/components/ImageEditor/crop/CropLines.tsx +0 -8
  34. package/src/shared/components/ImageEditor/crop/CropPointer.tsx +0 -21
  35. package/src/shared/components/ImageEditor/crop/CropPointers.tsx +0 -44
  36. package/src/shared/components/ImageEditor/crop/CropTools.tsx +0 -138
  37. package/src/shared/components/ImageEditor/crop/crop.css +0 -180
  38. package/src/shared/components/ImageEditor/crop/index.ts +0 -8
  39. package/src/shared/components/ImageEditor/crop/useCropInteraction.ts +0 -230
  40. package/src/shared/components/ImageEditor/eventBus.ts +0 -8
  41. package/src/shared/components/ImageEditor/frame/Frame.tsx +0 -48
  42. package/src/shared/components/ImageEditor/frame/frame.css +0 -40
  43. package/src/shared/components/ImageEditor/frame/index.ts +0 -1
  44. package/src/shared/components/ImageEditor/header/Header.tsx +0 -81
  45. package/src/shared/components/ImageEditor/header/header.css +0 -27
  46. package/src/shared/components/ImageEditor/header/index.ts +0 -1
  47. package/src/shared/components/ImageEditor/hooks/index.ts +0 -5
  48. package/src/shared/components/ImageEditor/hooks/useAbove.tsx +0 -18
  49. package/src/shared/components/ImageEditor/hooks/useBellow.tsx +0 -18
  50. package/src/shared/components/ImageEditor/hooks/useImageLoader.tsx +0 -110
  51. package/src/shared/components/ImageEditor/hooks/useImageProcessor.test.tsx +0 -147
  52. package/src/shared/components/ImageEditor/hooks/useImageProcessor.ts +0 -225
  53. package/src/shared/components/ImageEditor/hooks/useMobile.tsx +0 -33
  54. package/src/shared/components/ImageEditor/index.css +0 -145
  55. package/src/shared/components/ImageEditor/index.ts +0 -1
  56. package/src/shared/components/ImageEditor/provider/ImageEditorProvider.test.tsx +0 -126
  57. package/src/shared/components/ImageEditor/provider/ImageEditorProvider.tsx +0 -107
  58. package/src/shared/components/ImageEditor/provider/StoreContext.tsx +0 -20
  59. package/src/shared/components/ImageEditor/provider/initialState.ts +0 -30
  60. package/src/shared/components/ImageEditor/provider/useImageEditorContext.tsx +0 -12
  61. package/src/shared/components/ImageEditor/resize/ResizeTools.tsx +0 -183
  62. package/src/shared/components/ImageEditor/resize/index.ts +0 -1
  63. package/src/shared/components/ImageEditor/resize/resize.css +0 -56
  64. package/src/shared/components/ImageEditor/sidebar/Sidebar.tsx +0 -54
  65. package/src/shared/components/ImageEditor/sidebar/SidebarCrop.tsx +0 -57
  66. package/src/shared/components/ImageEditor/sidebar/SidebarPresets.tsx +0 -54
  67. package/src/shared/components/ImageEditor/sidebar/SidebarResize.tsx +0 -26
  68. package/src/shared/components/ImageEditor/sidebar/index.ts +0 -1
  69. package/src/shared/components/ImageEditor/sidebar/sidebar.css +0 -101
  70. package/src/shared/components/ImageEditor/types.ts +0 -59
  71. package/src/shared/components/ImageEditor/ui/button/Button.stories.tsx +0 -42
  72. package/src/shared/components/ImageEditor/ui/button/Button.tsx +0 -21
  73. package/src/shared/components/ImageEditor/ui/button/button.css +0 -69
  74. package/src/shared/components/ImageEditor/ui/button-crop/ButtonCrop.stories.tsx +0 -26
  75. package/src/shared/components/ImageEditor/ui/button-crop/ButtonCrop.tsx +0 -29
  76. package/src/shared/components/ImageEditor/ui/button-crop/button-crop.css +0 -38
  77. package/src/shared/components/ImageEditor/ui/index.ts +0 -6
  78. package/src/shared/components/ImageEditor/ui/input/Input.stories.tsx +0 -45
  79. package/src/shared/components/ImageEditor/ui/input/Input.tsx +0 -20
  80. package/src/shared/components/ImageEditor/ui/input/input.css +0 -48
  81. package/src/shared/components/ImageEditor/ui/input-pixel/InputPixel.stories.tsx +0 -33
  82. package/src/shared/components/ImageEditor/ui/input-pixel/InputPixel.tsx +0 -23
  83. package/src/shared/components/ImageEditor/ui/input-pixel/input-pixel.css +0 -24
  84. package/src/shared/components/ImageEditor/ui/select/Select.stories.tsx +0 -67
  85. package/src/shared/components/ImageEditor/ui/select/Select.tsx +0 -161
  86. package/src/shared/components/ImageEditor/ui/select/select.css +0 -147
  87. package/src/shared/components/ImageEditor/ui/separator/Separator.stories.tsx +0 -37
  88. package/src/shared/components/ImageEditor/ui/separator/Separator.tsx +0 -27
  89. package/src/shared/components/ImageEditor/ui/separator/separator.css +0 -19
  90. package/src/shared/components/ImageEditor/utils.test.ts +0 -136
  91. package/src/shared/components/ImageEditor/utils.ts +0 -497
  92. package/src/shared/components/ui/index.ts +0 -1
  93. package/src/shared/components/ui/preloader.tsx +0 -78
  94. package/src/shared/config/index.ts +0 -3
  95. package/src/shared/config/routes.ts +0 -4
  96. package/src/shared/features/gallery/GalleryPage.tsx +0 -33
  97. package/src/shared/features/gallery/ImageEditPage.tsx +0 -37
  98. package/src/shared/features/gallery/index.ts +0 -2
  99. package/src/shared/hooks/index.ts +0 -1
  100. package/src/shared/hooks/useImagePreload.tsx +0 -49
  101. package/src/shared/types/index.ts +0 -11
  102. package/src/test/setup.ts +0 -14
  103. package/src/virtual-css-injected-by-js.d.ts +0 -8
  104. package/src/widget.tsx +0 -119
  105. package/tsconfig.app.json +0 -29
  106. package/tsconfig.json +0 -12
  107. package/tsconfig.lib.json +0 -18
  108. package/tsconfig.node.json +0 -23
  109. package/vite-env.d.ts +0 -1
  110. package/vite.config.ts +0 -24
  111. package/vite.lib.config.ts +0 -33
  112. package/vite.widget.config.ts +0 -39
  113. package/wrangler.json +0 -5
@@ -1,138 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
- import { Button, Separator } from "../ui";
3
- import type { CropRect } from "../types";
4
- import { useImageEditorContext } from "../provider/useImageEditorContext";
5
- import { eventBus } from "../eventBus";
6
- import { getInitalCrop } from "../utils";
7
- import { useImageProcessor } from "../hooks";
8
- import { Check, Loader, X } from "../assets/icons";
9
-
10
- export const CropTools = () => {
11
- const {
12
- setCurrentAction,
13
- getCurrentAction,
14
- getLastHistoryItem,
15
- addToHistory,
16
- setSidebar,
17
- } = useImageEditorContext();
18
- const { crop, resize } = useImageProcessor();
19
- const [loading, setLoading] = useState(false);
20
- const action = getCurrentAction();
21
- const { base64, width, height } = getLastHistoryItem();
22
-
23
- const leftRef = useRef<HTMLDivElement>(null);
24
- const topRef = useRef<HTMLDivElement>(null);
25
- const widthRef = useRef<HTMLDivElement>(null);
26
- const heightRef = useRef<HTMLDivElement>(null);
27
- const cropRectRef = useRef<CropRect | null>(null);
28
-
29
- const handleClose = () => {
30
- setCurrentAction(null);
31
- setSidebar(true);
32
- };
33
-
34
- useEffect(() => {
35
- if (action?.name !== "crop") {
36
- return;
37
- }
38
- const { x, y, w, h } = getInitalCrop(action.args.ratio, width, height);
39
- const xPx = Math.round((x / 100) * width);
40
- const yPx = Math.round((y / 100) * height);
41
- const wPx = Math.round((w / 100) * width);
42
- const hPx = Math.round((h / 100) * height);
43
-
44
- if (leftRef.current) leftRef.current.textContent = xPx.toString();
45
- if (topRef.current) topRef.current.textContent = yPx.toString();
46
- if (widthRef.current) widthRef.current.textContent = wPx.toString();
47
- if (heightRef.current) heightRef.current.textContent = hPx.toString();
48
- cropRectRef.current = { x: xPx, y: yPx, w: wPx, h: hPx };
49
-
50
- const onCropUpdate = (event: Event) => {
51
- const customEvent = event as CustomEvent<CropRect>;
52
- const { x, y, w, h } = customEvent.detail;
53
- cropRectRef.current = customEvent.detail;
54
- if (leftRef.current) leftRef.current.textContent = x.toString();
55
- if (topRef.current) topRef.current.textContent = y.toString();
56
- if (widthRef.current) widthRef.current.textContent = w.toString();
57
- if (heightRef.current) heightRef.current.textContent = h.toString();
58
- };
59
-
60
- eventBus.addEventListener("crop-update", onCropUpdate);
61
-
62
- return () => {
63
- eventBus.removeEventListener("crop-update", onCropUpdate);
64
- };
65
- }, [action, width, height]);
66
-
67
- const handleSave = async () => {
68
- if (!action || action.name !== "crop") {
69
- return;
70
- }
71
- try {
72
- setLoading(true);
73
- if (!cropRectRef.current) {
74
- return;
75
- }
76
- const { x, y, w, h } = cropRectRef.current;
77
- const { preset } = action.args;
78
- let processedBase64 = await crop(base64, x, y, w, h);
79
-
80
- if (preset) {
81
- const { w, h } = preset;
82
- processedBase64 = await resize(processedBase64, w, h);
83
- }
84
-
85
- addToHistory({
86
- name: "Crop",
87
- base64: processedBase64,
88
- width: preset ? preset.w : cropRectRef.current!.w,
89
- height: preset ? preset.h : cropRectRef.current!.h,
90
- });
91
- setSidebar(true);
92
- } catch (error) {
93
- console.error("Failed to process image:", error);
94
- } finally {
95
- setLoading(false);
96
- }
97
- };
98
-
99
- return (
100
- <div className="crop-tools">
101
- <div className="crop-tools-info">
102
- <div className="crop-tools-info-label">left</div>
103
- <div className="crop-tools-info-value semibold" ref={leftRef} />
104
- </div>
105
- <div className="crop-tools-info">
106
- <div className="crop-tools-info-label">top</div>
107
- <div className="crop-tools-info-value semibold" ref={topRef} />
108
- </div>
109
- <div className="crop-tools-info">
110
- <div className="crop-tools-info-label">width</div>
111
- <div className="crop-tools-info-value semibold" ref={widthRef} />
112
- </div>
113
- <div className="crop-tools-info">
114
- <div className="crop-tools-info-label">height</div>
115
- <div className="crop-tools-info-value semibold" ref={heightRef} />
116
- </div>
117
- <Separator orientation="vertical" />
118
- <Button
119
- variant="ghost"
120
- className="text-green"
121
- style={{ height: "36px", width: "36px" }}
122
- disabled={loading}
123
- onClick={handleSave}
124
- >
125
- {loading ? <Loader /> : <Check />}
126
- </Button>
127
- <Separator orientation="vertical" />
128
- <Button
129
- variant="ghost"
130
- style={{ height: "36px", width: "36px" }}
131
- disabled={loading}
132
- onClick={handleClose}
133
- >
134
- <X className="text-red" />
135
- </Button>
136
- </div>
137
- );
138
- };
@@ -1,180 +0,0 @@
1
- .crop-image {
2
- position: absolute;
3
- inset: 0;
4
- width: 100%;
5
- height: 100%;
6
- }
7
-
8
- .crop-box {
9
- position: absolute;
10
- cursor: move;
11
- }
12
-
13
- .crop-tools {
14
- position: absolute;
15
- bottom: 24px;
16
- left: 50%;
17
- transform: translateX(-50%);
18
- background-color: var(--background);
19
- display: flex;
20
- height: 36px;
21
- padding-left: 12px;
22
- align-items: center;
23
- border-radius: 4px;
24
- box-sizing: border-box;
25
- border: 1px solid var(--border);
26
- box-shadow:
27
- 0 4px 6px -1px rgba(0, 0, 0, 0.1),
28
- 0 2px 4px -1px rgba(0, 0, 0, 0.06);
29
- }
30
-
31
- .crop-tools-info {
32
- display: inline-block;
33
- min-width: 54px;
34
- margin-top: -2px;
35
- }
36
-
37
- .crop-tools-info-label {
38
- font-size: 12px;
39
- line-height: 12px;
40
- color: var(--muted-foreground);
41
- }
42
-
43
- .crop-tools-info-value {
44
- font-size: 14px;
45
- line-height: 14px;
46
- }
47
-
48
- .crop-b {
49
- position: absolute;
50
- background-color: var(--accent-blue);
51
- }
52
- .crop-b-t {
53
- top: 0;
54
- left: 0;
55
- width: 100%;
56
- height: 2px;
57
- cursor: ns-resize;
58
- }
59
- .crop-b-r {
60
- top: 0;
61
- right: 0;
62
- width: 2px;
63
- height: 100%;
64
- cursor: ew-resize;
65
- }
66
- .crop-b-b {
67
- bottom: 0;
68
- left: 0;
69
- width: 100%;
70
- height: 2px;
71
- cursor: ns-resize;
72
- transform: translateY(1px);
73
- }
74
- .crop-b-l {
75
- top: 0;
76
- left: -1px;
77
- width: 2px;
78
- height: 100%;
79
- cursor: ew-resize;
80
- transform: translateX(1px);
81
- }
82
-
83
- .crop-l-box {
84
- position: absolute;
85
- inset: 2px;
86
- pointer-events: none;
87
- }
88
- .crop-l {
89
- position: absolute;
90
- border: 1px dashed var(--color-white);
91
- opacity: 0.5;
92
- }
93
- .crop-l-v {
94
- width: 33%;
95
- height: 100%;
96
- top: 0;
97
- left: 33%;
98
- border-top: 0;
99
- border-bottom: 0;
100
- }
101
- .crop-l-h {
102
- width: 100%;
103
- height: 33%;
104
- top: 33%;
105
- left: 0;
106
- border-right: 0;
107
- border-left: 0;
108
- }
109
-
110
- .crop-ps {
111
- position: absolute;
112
- box-sizing: border-box;
113
- width: 12px;
114
- height: 12px;
115
- border: 1px solid var(--accent-blue);
116
- background-color: var(--overlay-bg-white);
117
- border-radius: 50%;
118
- }
119
- .crop-ps-tl {
120
- top: -5px;
121
- left: -5px;
122
- cursor: nwse-resize;
123
- }
124
- .crop-ps-t {
125
- top: -5px;
126
- left: 50%;
127
- transform: translateX(-50%);
128
- cursor: ns-resize;
129
- }
130
- .crop-ps-tr {
131
- top: -5px;
132
- right: -5px;
133
- cursor: nesw-resize;
134
- }
135
- .crop-ps-r {
136
- top: 50%;
137
- right: -5px;
138
- transform: translateY(-50%);
139
- cursor: ew-resize;
140
- }
141
- .crop-ps-br {
142
- bottom: -5px;
143
- right: -5px;
144
- cursor: nwse-resize;
145
- }
146
- .crop-ps-b {
147
- bottom: -5px;
148
- left: 50%;
149
- transform: translateX(-50%);
150
- cursor: ns-resize;
151
- }
152
- .crop-ps-bl {
153
- bottom: -5px;
154
- left: -5px;
155
- cursor: nesw-resize;
156
- }
157
- .crop-ps-l {
158
- top: 50%;
159
- left: -5px;
160
- transform: translateY(-50%);
161
- cursor: ew-resize;
162
- }
163
-
164
- .crop-p-b {
165
- position: absolute;
166
- inset: 0;
167
- box-sizing: border-box;
168
- border: 2px solid var(--accent-blue);
169
- }
170
- .crop-p-p {
171
- position: absolute;
172
- width: 32px;
173
- height: 32px;
174
- right: -16px;
175
- bottom: -16px;
176
- background-color: var(--overlay-bg-white);
177
- border-radius: 50%;
178
- border: 3px solid var(--accent-blue);
179
- box-sizing: border-box;
180
- }
@@ -1,8 +0,0 @@
1
- export { Crop } from "./Crop";
2
- import { CropTools } from "./CropTools";
3
- export { CropBorders } from "./CropBorders";
4
- export { CropLines } from "./CropLines";
5
- export { CropPointers } from "./CropPointers";
6
- export { useCropInteraction } from "./useCropInteraction";
7
- export { CropPointer } from "./CropPointer";
8
- export { CropTools };
@@ -1,230 +0,0 @@
1
- import { useEffect, useRef } from "react";
2
- import { useImageEditorContext } from "../provider/useImageEditorContext";
3
- import { getCropPoints } from "../utils";
4
- import type { CropRect, Direction } from "../types";
5
- import { emitCropUpdate } from "../eventBus";
6
- import { useMobile } from "../hooks";
7
-
8
- function clamp(value: number, min: number, max: number): number {
9
- return Math.min(Math.max(value, min), max);
10
- }
11
-
12
- function pct(px: number, total: number): number {
13
- return (px / total) * 100;
14
- }
15
-
16
- function applyRect(
17
- el: HTMLDivElement,
18
- xPct: number,
19
- yPct: number,
20
- wPct: number,
21
- hPct: number,
22
- ): void {
23
- el.style.left = `${xPct}%`;
24
- el.style.top = `${yPct}%`;
25
- el.style.width = `${wPct}%`;
26
- el.style.height = `${hPct}%`;
27
- }
28
-
29
- function applyClipPath(
30
- img: HTMLImageElement,
31
- xPct: number,
32
- yPct: number,
33
- wPct: number,
34
- hPct: number,
35
- ): void {
36
- img.style.clipPath = `xywh(${xPct}% ${yPct}% ${wPct}% ${hPct}%)`;
37
- }
38
-
39
- type UseCropInteractionArgs = {
40
- imgRef: React.RefObject<HTMLImageElement | null>;
41
- cropRef: React.RefObject<HTMLDivElement | null>;
42
- };
43
-
44
- export const useCropInteraction = ({
45
- imgRef,
46
- cropRef,
47
- }: UseCropInteractionArgs) => {
48
- const { getCurrentAction, getLastHistoryItem } = useImageEditorContext();
49
- const mobile = useMobile();
50
-
51
- const startPointRef = useRef<{ x: number; y: number } | null>(null);
52
- const directionRef = useRef<Direction | "">("");
53
- const posRef = useRef<{ xPct: number; yPct: number }>({ xPct: 0, yPct: 0 });
54
- const cropRectRef = useRef<CropRect>({ x: 0, y: 0, w: 0, h: 0 });
55
-
56
- const handleCropStart = (
57
- e: React.MouseEvent | React.TouchEvent,
58
- type: Direction,
59
- cursor?: string,
60
- ) => {
61
- if (!cropRef.current) return;
62
-
63
- e.stopPropagation();
64
-
65
- cropRectRef.current = {
66
- x: cropRef.current.offsetLeft,
67
- y: cropRef.current.offsetTop,
68
- w: cropRef.current.offsetWidth,
69
- h: cropRef.current.offsetHeight,
70
- };
71
-
72
- const clientX = "clientX" in e ? e.clientX : e.touches[0].clientX;
73
- const clientY = "clientY" in e ? e.clientY : e.touches[0].clientY;
74
- startPointRef.current = { x: clientX, y: clientY };
75
- directionRef.current = type;
76
-
77
- if (!mobile) {
78
- cropRef.current.style.cursor = `${cursor}-resize`;
79
- document.body.style.cursor = `${cursor}-resize`;
80
- }
81
- };
82
-
83
- useEffect(() => {
84
- const handleDragStart = (e: MouseEvent | TouchEvent) => {
85
- if (cropRef.current) {
86
- const parent = cropRef.current.parentElement;
87
- if (parent) {
88
- posRef.current = {
89
- xPct: pct(cropRef.current.offsetLeft, parent.offsetWidth),
90
- yPct: pct(cropRef.current.offsetTop, parent.offsetHeight),
91
- };
92
- }
93
- }
94
- const clientX = "clientX" in e ? e.clientX : e.touches[0].clientX;
95
- const clientY = "clientY" in e ? e.clientY : e.touches[0].clientY;
96
- startPointRef.current = { x: clientX, y: clientY };
97
- document.body.style.cursor = "move";
98
- };
99
-
100
- const handleMove = (e: MouseEvent | TouchEvent) => {
101
- if (!imgRef.current || !cropRef.current || !startPointRef.current) return;
102
-
103
- const elCrop = cropRef.current;
104
- const elImage = imgRef.current;
105
- const parent = elCrop.parentElement;
106
- if (!parent) return;
107
-
108
- const parentW = parent.offsetWidth;
109
- const parentH = parent.offsetHeight;
110
-
111
- if (directionRef.current) {
112
- handleResize(e, elCrop, elImage, parentW, parentH);
113
- return;
114
- }
115
-
116
- handleDrag(e, elCrop, elImage, parentW, parentH);
117
- };
118
-
119
- const handleResize = (
120
- e: MouseEvent | TouchEvent,
121
- elCrop: HTMLDivElement,
122
- elImage: HTMLImageElement,
123
- parentW: number,
124
- parentH: number,
125
- ) => {
126
- const action = getCurrentAction();
127
- if (!action || action.name !== "crop" || !startPointRef.current) return;
128
-
129
- const { isFree, ratio } = action.args;
130
- const clientX = "clientX" in e ? e.clientX : e.touches[0].clientX;
131
- const clientY = "clientY" in e ? e.clientY : e.touches[0].clientY;
132
- const { x, y, w, h } = getCropPoints(
133
- directionRef.current as Direction,
134
- isFree,
135
- ratio,
136
- startPointRef.current.x,
137
- startPointRef.current.y,
138
- clientX,
139
- clientY,
140
- parentW,
141
- parentH,
142
- cropRectRef.current,
143
- elCrop,
144
- );
145
-
146
- const xPct = pct(x, parentW);
147
- const yPct = pct(y, parentH);
148
- const wPct = pct(w, parentW);
149
- const hPct = pct(h, parentH);
150
-
151
- applyRect(elCrop, xPct, yPct, wPct, hPct);
152
- applyClipPath(elImage, xPct, yPct, wPct, hPct);
153
-
154
- const lastHistoryItem = getLastHistoryItem();
155
- if (!lastHistoryItem) return;
156
- const { width: imgW, height: imgH } = lastHistoryItem;
157
- emitCropUpdate({
158
- x: Math.round((xPct / 100) * imgW),
159
- y: Math.round((yPct / 100) * imgH),
160
- w: Math.round((wPct / 100) * imgW),
161
- h: Math.round((hPct / 100) * imgH),
162
- });
163
- };
164
-
165
- const handleDrag = (
166
- e: MouseEvent | TouchEvent,
167
- elCrop: HTMLDivElement,
168
- elImage: HTMLImageElement,
169
- parentW: number,
170
- parentH: number,
171
- ) => {
172
- if (!startPointRef.current) return;
173
- console.log("HandleDrag");
174
- const frmWidth = elCrop.offsetWidth;
175
- const frmHeight = elCrop.offsetHeight;
176
-
177
- const clientX = "clientX" in e ? e.clientX : e.touches[0].clientX;
178
- const clientY = "clientY" in e ? e.clientY : e.touches[0].clientY;
179
- const dxPct = pct(clientX - startPointRef.current.x, parentW);
180
- const dyPct = pct(clientY - startPointRef.current.y, parentH);
181
- startPointRef.current = { x: clientX, y: clientY };
182
-
183
- const maxXPct = pct(parentW - frmWidth, parentW);
184
- const maxYPct = pct(parentH - frmHeight, parentH);
185
-
186
- const leftPct = clamp(posRef.current.xPct + dxPct, 0, maxXPct);
187
- const topPct = clamp(posRef.current.yPct + dyPct, 0, maxYPct);
188
- const wPct = pct(frmWidth, parentW);
189
- const hPct = pct(frmHeight, parentH);
190
-
191
- posRef.current = { xPct: leftPct, yPct: topPct };
192
-
193
- applyRect(elCrop, leftPct, topPct, wPct, hPct);
194
- applyClipPath(elImage, leftPct, topPct, wPct, hPct);
195
-
196
- const { width: imgW, height: imgH } = getLastHistoryItem();
197
- emitCropUpdate({
198
- x: Math.round((leftPct / 100) * imgW),
199
- y: Math.round((topPct / 100) * imgH),
200
- w: Math.round((wPct / 100) * imgW),
201
- h: Math.round((hPct / 100) * imgH),
202
- });
203
- };
204
-
205
- const handleMoveEnd = () => {
206
- if (!cropRef.current) return;
207
-
208
- startPointRef.current = null;
209
- directionRef.current = "";
210
- cropRef.current.style.cursor = "move";
211
- document.body.style.cursor = "auto";
212
- };
213
-
214
- if (!cropRef.current) return;
215
-
216
- const controller = new AbortController();
217
- const { signal } = controller;
218
-
219
- cropRef.current.addEventListener("mousedown", handleDragStart, { signal });
220
- cropRef.current.addEventListener("touchstart", handleDragStart, { signal });
221
- document.addEventListener("mousemove", handleMove, { signal });
222
- document.addEventListener("touchmove", handleMove, { signal });
223
- document.addEventListener("mouseup", handleMoveEnd, { signal });
224
- document.addEventListener("touchend", handleMoveEnd, { signal });
225
-
226
- return () => controller.abort();
227
- }, [getCurrentAction, getLastHistoryItem, imgRef, cropRef]);
228
-
229
- return { handleCropStart };
230
- };
@@ -1,8 +0,0 @@
1
- import type { CropRect } from "./types";
2
-
3
- export const eventBus = new EventTarget();
4
-
5
- export const emitCropUpdate = (detail: CropRect) => {
6
- const event = new CustomEvent<CropRect>("crop-update", { detail });
7
- eventBus.dispatchEvent(event);
8
- };
@@ -1,48 +0,0 @@
1
- import { useRef } from "react";
2
- import { useImageEditorContext } from "../provider/useImageEditorContext";
3
- import { Crop } from "../crop";
4
- import { CropTools } from "../crop";
5
- import "./frame.css";
6
- import { ResizeTools } from "../resize";
7
-
8
- export const Frame = () => {
9
- const { getLastHistoryItem, currentAction } = useImageEditorContext();
10
- const frameRef = useRef<HTMLDivElement>(null);
11
- const imageRef = useRef<HTMLImageElement>(null);
12
- const { width, height, base64 } = getLastHistoryItem();
13
-
14
- const isCrop = currentAction?.name === "crop";
15
- const isResize = currentAction?.name === "resize";
16
- const isFade = isCrop || isResize;
17
-
18
- const handleResizing = (scale: number) => {
19
- if (frameRef.current) {
20
- frameRef.current.style.transform = `scale(${scale / 100})`;
21
- }
22
- };
23
-
24
- const frameClassName = `frame ${isFade ? "mask" : ""}`;
25
- const imageClassName = `frame-image ${isFade ? "frame-image--faded" : ""}`;
26
-
27
- return (
28
- <div className={frameClassName}>
29
- <div
30
- ref={frameRef}
31
- style={{
32
- aspectRatio: `${width} / ${height}`,
33
- }}
34
- className="frame-preview"
35
- >
36
- <img
37
- ref={imageRef}
38
- src={base64}
39
- alt="Image"
40
- className={imageClassName}
41
- />
42
- {isCrop && <Crop key={currentAction?.args?.id} />}
43
- </div>
44
- {isResize && <ResizeTools onResizing={handleResizing} />}
45
- {isCrop && <CropTools />}
46
- </div>
47
- );
48
- };
@@ -1,40 +0,0 @@
1
- .frame {
2
- display: flex;
3
- width: 100%;
4
- height: 100%;
5
- min-height: 0;
6
- position: relative;
7
- overflow: hidden;
8
- justify-content: center;
9
- align-items: center;
10
- padding: 16px;
11
- box-sizing: border-box;
12
- }
13
-
14
- .frame--faded {
15
- background-color: rgba(0, 0, 0, 0.5);
16
- }
17
-
18
- .frame-preview {
19
- position: relative;
20
- max-width: 100%;
21
- max-height: 100%;
22
- width: auto;
23
- height: auto;
24
- min-height: 0;
25
- min-width: 0;
26
- flex-shrink: 1;
27
- display: block;
28
- user-select: none;
29
- transition: transform 0.2s ease;
30
- }
31
-
32
- .frame-image {
33
- width: 100%;
34
- height: 100%;
35
- object-fit: contain;
36
- }
37
-
38
- .frame-image--faded {
39
- opacity: 0.4;
40
- }
@@ -1 +0,0 @@
1
- export { Frame } from "./Frame";