@jhits/plugin-images 0.0.13 → 0.0.15

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 (111) hide show
  1. package/dist/api/list/index.d.ts +18 -0
  2. package/dist/api/list/index.d.ts.map +1 -1
  3. package/dist/api/list/index.js +121 -20
  4. package/dist/api/router.d.ts.map +1 -1
  5. package/dist/api/router.js +7 -0
  6. package/dist/api/usage/route.d.ts +23 -0
  7. package/dist/api/usage/route.d.ts.map +1 -0
  8. package/dist/api/usage/route.js +238 -0
  9. package/dist/components/BackgroundImage.d.ts.map +1 -1
  10. package/dist/components/BackgroundImage.js +5 -17
  11. package/dist/components/GlobalImageEditor.d.ts.map +1 -1
  12. package/dist/components/GlobalImageEditor.js +9 -4
  13. package/dist/components/Image.d.ts +3 -6
  14. package/dist/components/Image.d.ts.map +1 -1
  15. package/dist/components/Image.js +103 -206
  16. package/dist/components/ImageEditor.d.ts.map +1 -1
  17. package/dist/components/ImageEditor.js +21 -125
  18. package/dist/components/ImagePicker.d.ts.map +1 -1
  19. package/dist/components/ImagePicker.js +6 -59
  20. package/dist/utils/fallback.d.ts +9 -4
  21. package/dist/utils/fallback.d.ts.map +1 -1
  22. package/dist/utils/fallback.js +40 -12
  23. package/dist/utils/transforms.d.ts.map +1 -1
  24. package/dist/utils/transforms.js +7 -10
  25. package/dist/views/ImageManager/components/CleanupLibraryModal.d.ts +12 -0
  26. package/dist/views/ImageManager/components/CleanupLibraryModal.d.ts.map +1 -0
  27. package/dist/views/ImageManager/components/CleanupLibraryModal.js +7 -0
  28. package/dist/views/ImageManager/components/DeleteImageModal.d.ts +15 -0
  29. package/dist/views/ImageManager/components/DeleteImageModal.d.ts.map +1 -0
  30. package/dist/views/ImageManager/components/DeleteImageModal.js +8 -0
  31. package/dist/views/ImageManager/components/ImageGrid.d.ts +12 -0
  32. package/dist/views/ImageManager/components/ImageGrid.d.ts.map +1 -0
  33. package/dist/views/ImageManager/components/ImageGrid.js +15 -0
  34. package/dist/views/ImageManager/components/ImageManagerHeader.d.ts +11 -0
  35. package/dist/views/ImageManager/components/ImageManagerHeader.d.ts.map +1 -0
  36. package/dist/views/ImageManager/components/ImageManagerHeader.js +6 -0
  37. package/dist/views/ImageManager/components/ImageManagerStats.d.ts +8 -0
  38. package/dist/views/ImageManager/components/ImageManagerStats.d.ts.map +1 -0
  39. package/dist/views/ImageManager/components/ImageManagerStats.js +6 -0
  40. package/dist/views/ImageManager/components/ImageManagerToolbar.d.ts +9 -0
  41. package/dist/views/ImageManager/components/ImageManagerToolbar.d.ts.map +1 -0
  42. package/dist/views/ImageManager/components/ImageManagerToolbar.js +10 -0
  43. package/dist/views/ImageManager/components/ImageTable.d.ts +13 -0
  44. package/dist/views/ImageManager/components/ImageTable.d.ts.map +1 -0
  45. package/dist/views/ImageManager/components/ImageTable.js +13 -0
  46. package/dist/views/ImageManager/types.d.ts +26 -0
  47. package/dist/views/ImageManager/types.d.ts.map +1 -0
  48. package/dist/views/ImageManager/types.js +1 -0
  49. package/dist/views/ImageManager.d.ts +1 -1
  50. package/dist/views/ImageManager.d.ts.map +1 -1
  51. package/dist/views/ImageManager.js +206 -2
  52. package/package.json +10 -9
  53. package/src/api/list/index.ts +147 -22
  54. package/src/api/router.ts +8 -0
  55. package/src/api/usage/route.ts +294 -0
  56. package/src/components/BackgroundImage.tsx +5 -15
  57. package/src/components/GlobalImageEditor.tsx +9 -4
  58. package/src/components/Image.tsx +128 -268
  59. package/src/components/ImageEditor.tsx +31 -193
  60. package/src/components/ImagePicker.tsx +22 -107
  61. package/src/utils/fallback.ts +46 -13
  62. package/src/utils/transforms.ts +9 -12
  63. package/src/views/ImageManager/components/CleanupLibraryModal.tsx +96 -0
  64. package/src/views/ImageManager/components/DeleteImageModal.tsx +144 -0
  65. package/src/views/ImageManager/components/ImageGrid.tsx +119 -0
  66. package/src/views/ImageManager/components/ImageManagerHeader.tsx +72 -0
  67. package/src/views/ImageManager/components/ImageManagerStats.tsx +60 -0
  68. package/src/views/ImageManager/components/ImageManagerToolbar.tsx +60 -0
  69. package/src/views/ImageManager/components/ImageTable.tsx +120 -0
  70. package/src/views/ImageManager/types.ts +27 -0
  71. package/src/views/ImageManager.tsx +307 -12
  72. package/src/components/BackgroundImage.d.ts +0 -11
  73. package/src/components/BackgroundImage.d.ts.map +0 -1
  74. package/src/components/GlobalImageEditor/config.d.ts +0 -9
  75. package/src/components/GlobalImageEditor/config.d.ts.map +0 -1
  76. package/src/components/GlobalImageEditor/eventHandlers.d.ts +0 -20
  77. package/src/components/GlobalImageEditor/eventHandlers.d.ts.map +0 -1
  78. package/src/components/GlobalImageEditor/imageDetection.d.ts +0 -16
  79. package/src/components/GlobalImageEditor/imageDetection.d.ts.map +0 -1
  80. package/src/components/GlobalImageEditor/imageSetup.d.ts +0 -9
  81. package/src/components/GlobalImageEditor/imageSetup.d.ts.map +0 -1
  82. package/src/components/GlobalImageEditor/saveLogic.d.ts +0 -26
  83. package/src/components/GlobalImageEditor/saveLogic.d.ts.map +0 -1
  84. package/src/components/GlobalImageEditor/stylingDetection.d.ts +0 -9
  85. package/src/components/GlobalImageEditor/stylingDetection.d.ts.map +0 -1
  86. package/src/components/GlobalImageEditor/transformParsing.d.ts +0 -16
  87. package/src/components/GlobalImageEditor/transformParsing.d.ts.map +0 -1
  88. package/src/components/GlobalImageEditor/types.d.ts +0 -36
  89. package/src/components/GlobalImageEditor/types.d.ts.map +0 -1
  90. package/src/components/GlobalImageEditor.d.ts +0 -8
  91. package/src/components/GlobalImageEditor.d.ts.map +0 -1
  92. package/src/components/Image.d.ts +0 -22
  93. package/src/components/Image.d.ts.map +0 -1
  94. package/src/components/ImageBrowserModal.d.ts +0 -13
  95. package/src/components/ImageBrowserModal.d.ts.map +0 -1
  96. package/src/components/ImageEditor.d.ts +0 -27
  97. package/src/components/ImageEditor.d.ts.map +0 -1
  98. package/src/components/ImagePicker.d.ts +0 -3
  99. package/src/components/ImagePicker.d.ts.map +0 -1
  100. package/src/components/ImagesPluginInit.d.ts +0 -24
  101. package/src/components/ImagesPluginInit.d.ts.map +0 -1
  102. package/src/hooks/useImagePicker.d.ts +0 -20
  103. package/src/hooks/useImagePicker.d.ts.map +0 -1
  104. package/src/types/index.d.ts +0 -80
  105. package/src/types/index.d.ts.map +0 -1
  106. package/src/utils/fallback.d.ts +0 -27
  107. package/src/utils/fallback.d.ts.map +0 -1
  108. package/src/utils/transforms.d.ts +0 -26
  109. package/src/utils/transforms.d.ts.map +0 -1
  110. package/src/views/ImageManager.d.ts +0 -10
  111. package/src/views/ImageManager.d.ts.map +0 -1
@@ -1,227 +1,124 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import NextImage from 'next/image';
4
- import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
4
+ import { useState, useEffect, useCallback } from 'react';
5
5
  import { Edit2, Loader2 } from 'lucide-react';
6
- import { getImageTransform, getImageFilter } from '../utils/transforms';
7
- import { getFallbackImageUrl } from '../utils/fallback';
8
- export function Image({ id, alt, width, height, className = '', fill = false, sizes, priority = false, objectFit = 'cover', objectPosition = 'center', style, editable = true, ...props // Using rest for override props
9
- }) {
10
- // 1. State management (Local only, used if props are undefined)
11
- const [apiData, setApiData] = useState({
12
- filename: null,
13
- brightness: 100,
14
- blur: 0,
15
- scale: 1.0,
16
- positionX: 0,
17
- positionY: 0,
6
+ export let authCache = null;
7
+ let authFetching = null;
8
+ export async function checkAuthOnce() {
9
+ if (authCache !== null)
10
+ return authCache;
11
+ if (authFetching)
12
+ return authFetching;
13
+ authFetching = fetch('/api/me')
14
+ .then(res => res.json())
15
+ .then(data => {
16
+ const isAuth = data.loggedIn && ['admin', 'dev'].includes(data.user?.role);
17
+ authCache = isAuth;
18
+ return isAuth;
19
+ })
20
+ .catch(() => {
21
+ authCache = false;
22
+ return false;
18
23
  });
24
+ return authFetching;
25
+ }
26
+ export function Image({ id, alt, width, height, className = '', fill = false, sizes, priority = false, objectFit = 'cover', objectPosition = 'center', style, editable = true, }) {
27
+ const [resolvedFilename, setResolvedFilename] = useState(null);
19
28
  const [isResolving, setIsResolving] = useState(true);
20
- const [baseScale, setBaseScale] = useState(1);
29
+ const [brightness, setBrightness] = useState(100);
30
+ const [blur, setBlur] = useState(0);
21
31
  const [isAuthenticated, setIsAuthenticated] = useState(false);
22
- const [imageError, setImageError] = useState(false);
23
- const imageRef = useRef(null);
24
- const containerRef = useRef(null);
25
- // 2. Derive "Effective" values (Prop > Local State)
26
- const effective = useMemo(() => ({
27
- brightness: props.brightness ?? apiData.brightness,
28
- blur: props.blur ?? apiData.blur,
29
- scale: props.scale ?? apiData.scale,
30
- positionX: props.positionX ?? apiData.positionX,
31
- positionY: props.positionY ?? apiData.positionY,
32
- filename: apiData.filename || id
33
- }), [props, apiData, id]);
34
- // 3. Optimized Image Resolution
35
- const fetchImageMetadata = useCallback(async (targetId) => {
36
- const isLikelyPath = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(targetId) || /^\d+-/.test(targetId);
37
- if (isLikelyPath) {
38
- setApiData(prev => ({ ...prev, filename: targetId }));
39
- setIsResolving(false);
40
- return;
41
- }
42
- try {
43
- const res = await fetch(`/api/plugin-images/resolve?id=${encodeURIComponent(targetId)}`);
44
- if (!res.ok)
45
- throw new Error();
46
- const data = await res.json();
47
- const apiPositionX = data.positionX ?? 0;
48
- const apiPositionY = data.positionY ?? 0;
49
- const apiScale = data.scale ?? 1.0;
50
- setApiData({
51
- filename: data.filename || targetId,
52
- brightness: data.brightness ?? 100,
53
- blur: data.blur ?? 0,
54
- scale: apiScale,
55
- positionX: apiPositionX,
56
- positionY: apiPositionY,
57
- });
58
- // Reset error state when resolution succeeds (in case it was set during pre-check)
59
- setImageError(false);
32
+ const [authChecked, setAuthChecked] = useState(false);
33
+ // Check authentication status if editable is true (default)
34
+ useEffect(() => {
35
+ if (editable && !authChecked) {
36
+ const checkAuth = async () => {
37
+ try {
38
+ const res = await fetch('/api/me');
39
+ const data = await res.json();
40
+ if (data.loggedIn && (data.user?.role === 'admin' || data.user?.role === 'dev')) {
41
+ setIsAuthenticated(true);
42
+ }
43
+ }
44
+ catch (error) {
45
+ // User is not authenticated or API call failed
46
+ setIsAuthenticated(false);
47
+ }
48
+ finally {
49
+ setAuthChecked(true);
50
+ }
51
+ };
52
+ checkAuth();
60
53
  }
61
- catch {
62
- setApiData(prev => ({ ...prev, filename: targetId }));
54
+ else if (!editable) {
55
+ // If explicitly set to false, skip auth check
56
+ setAuthChecked(true);
63
57
  }
64
- finally {
58
+ }, [editable, authChecked]);
59
+ // Only allow editing if editable is true AND user is authenticated
60
+ const canEdit = editable && isAuthenticated;
61
+ const resolveImage = useCallback(() => {
62
+ const looksLikeFilename = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(id) || /^\d+-/.test(id);
63
+ if (looksLikeFilename) {
64
+ setResolvedFilename(id);
65
65
  setIsResolving(false);
66
- }
67
- }, []);
68
- // 4. Auth Check (Single mount only)
69
- useEffect(() => {
70
- if (!editable)
71
66
  return;
72
- fetch('/api/me')
73
- .then(res => res.json())
67
+ }
68
+ fetch(`/api/plugin-images/resolve?id=${encodeURIComponent(id)}`)
69
+ .then(res => res.ok ? res.json() : { filename: id, brightness: 100, blur: 0 })
74
70
  .then(data => {
75
- if (data.loggedIn && ['admin', 'dev'].includes(data.user?.role)) {
76
- setIsAuthenticated(true);
77
- }
71
+ setResolvedFilename(data.filename || id);
72
+ setBrightness(data.brightness ?? 100);
73
+ setBlur(data.blur ?? 0);
74
+ setIsResolving(false);
78
75
  })
79
- .catch(() => setIsAuthenticated(false));
80
- }, [editable]);
81
- // 5. Stable Event Listener (Prevents closure staleness)
82
- // Only fetch from API if props are not provided (parent controls the values)
83
- const hasProps = props.scale !== undefined || props.positionX !== undefined || props.positionY !== undefined || props.brightness !== undefined || props.blur !== undefined;
76
+ .catch(() => {
77
+ setResolvedFilename(id);
78
+ setBrightness(100);
79
+ setBlur(0);
80
+ setIsResolving(false);
81
+ });
82
+ }, [id]);
84
83
  useEffect(() => {
85
- fetchImageMetadata(id);
86
- // Only listen to events if props are not provided
87
- if (hasProps)
88
- return;
89
- const handleUpdate = (e) => {
84
+ resolveImage();
85
+ const handleMappingUpdate = (e) => {
90
86
  if (e.detail?.id === id) {
91
- // Only update if the event contains new data
92
- const eventData = e.detail;
93
- if (eventData.scale !== undefined || eventData.positionX !== undefined || eventData.positionY !== undefined || eventData.brightness !== undefined || eventData.blur !== undefined) {
94
- // Update local state directly from event to avoid unnecessary API call
95
- setApiData(prev => ({
96
- ...prev,
97
- brightness: eventData.brightness ?? prev.brightness,
98
- blur: eventData.blur ?? prev.blur,
99
- scale: eventData.scale ?? prev.scale,
100
- positionX: eventData.positionX ?? prev.positionX,
101
- positionY: eventData.positionY ?? prev.positionY,
102
- }));
103
- }
87
+ setIsResolving(true);
88
+ if (e.detail.brightness !== undefined)
89
+ setBrightness(e.detail.brightness);
90
+ if (e.detail.blur !== undefined)
91
+ setBlur(e.detail.blur);
92
+ setTimeout(() => resolveImage(), 100);
104
93
  }
105
94
  };
106
- window.addEventListener('image-mapping-updated', handleUpdate);
107
- return () => window.removeEventListener('image-mapping-updated', handleUpdate);
108
- }, [id, fetchImageMetadata, hasProps]);
109
- // 6. Layout & Transforms
110
- const calculateBaseScale = useCallback(() => {
111
- if (!imageRef.current || !containerRef.current)
112
- return;
113
- const containerWidth = containerRef.current.offsetWidth;
114
- const containerHeight = containerRef.current.offsetHeight;
115
- // Ensure container has dimensions before calculating
116
- if (containerWidth === 0 || containerHeight === 0) {
117
- // Try to get dimensions from computed styles or parent
118
- const parentWidth = containerRef.current.parentElement?.clientWidth;
119
- const parentHeight = containerRef.current.parentElement?.clientHeight;
120
- if (parentWidth && parentHeight && imageRef.current.naturalWidth > 0) {
121
- const widthRatio = parentWidth / imageRef.current.naturalWidth;
122
- const heightRatio = parentHeight / imageRef.current.naturalHeight;
123
- const calculatedBaseScale = Math.max(widthRatio, heightRatio);
124
- setBaseScale(calculatedBaseScale);
125
- }
126
- return;
127
- }
128
- if (imageRef.current.naturalWidth === 0 || imageRef.current.naturalHeight === 0)
129
- return;
130
- const widthRatio = containerWidth / imageRef.current.naturalWidth;
131
- const heightRatio = containerHeight / imageRef.current.naturalHeight;
132
- const calculatedBaseScale = Math.max(widthRatio, heightRatio);
133
- setBaseScale(calculatedBaseScale);
134
- }, []);
135
- // Only construct image URL if we have a valid filename (not a semantic ID)
136
- // Check if filename looks like an actual file (has extension or starts with timestamp)
137
- const filename = effective.filename;
138
- const hasFileExtension = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(filename);
139
- const looksLikeTimestamp = /^\d+-/.test(filename);
140
- const isValidFilename = hasFileExtension || looksLikeTimestamp;
141
- // Don't construct URL until resolution is complete AND we have a valid filename
142
- const shouldConstructUrl = !isResolving && isValidFilename;
143
- const baseSrc = shouldConstructUrl ? `/api/uploads/${encodeURIComponent(filename)}` : null;
144
- const src = imageError ? getFallbackImageUrl() : (baseSrc || getFallbackImageUrl());
95
+ window.addEventListener('image-mapping-updated', handleMappingUpdate);
96
+ return () => window.removeEventListener('image-mapping-updated', handleMappingUpdate);
97
+ }, [id, resolveImage]);
98
+ const src = resolvedFilename
99
+ ? `/api/uploads/${encodeURIComponent(resolvedFilename)}`
100
+ : `/api/uploads/${encodeURIComponent(id)}`;
101
+ // Logic: If the user passes 'w-full' or 'h-full', or explicit 'fill',
102
+ // we must use Next.js fill mode to make object-fit work.
145
103
  const shouldFill = fill || className.includes('w-full') || className.includes('h-full');
146
- const hasTransforms = effective.scale !== 1 || effective.positionX !== 0 || effective.positionY !== 0;
147
- // Pre-check image URL to detect errors early (only for valid filenames after resolution)
148
- useEffect(() => {
149
- // Don't check if:
150
- // 1. Already in error state
151
- // 2. Still resolving
152
- // 3. No valid base src
153
- // 4. Base src is the fallback URL
154
- if (imageError || isResolving || !baseSrc || baseSrc === getFallbackImageUrl() || !isValidFilename)
155
- return;
156
- const testImg = document.createElement('img');
157
- const handleError = () => {
158
- setImageError(true);
159
- };
160
- const handleLoad = () => {
161
- setImageError(false);
162
- };
163
- testImg.onerror = handleError;
164
- testImg.onload = handleLoad;
165
- testImg.src = baseSrc;
166
- return () => {
167
- testImg.onerror = null;
168
- testImg.onload = null;
169
- };
170
- }, [baseSrc, imageError, isResolving, isValidFilename]);
171
- // Recalculate baseScale when container is resized
172
- useEffect(() => {
173
- if (!hasTransforms || !shouldFill || !containerRef.current)
174
- return;
175
- const resizeObserver = new ResizeObserver(() => {
176
- calculateBaseScale();
177
- });
178
- resizeObserver.observe(containerRef.current);
179
- return () => resizeObserver.disconnect();
180
- }, [hasTransforms, shouldFill, calculateBaseScale]);
181
- const transformStyle = hasTransforms && baseScale > 0 ? (() => {
182
- return getImageTransform({
183
- scale: effective.scale,
184
- positionX: effective.positionX,
185
- positionY: effective.positionY,
186
- baseScale
187
- }, true);
188
- })() : undefined;
189
- const filterStyle = getImageFilter(effective.brightness, effective.blur);
190
- return (_jsxs("div", { ref: containerRef, className: `group/image relative overflow-hidden transition-all duration-300 ${className}`, style: {
104
+ // Default sizes for fill images when not provided - use unoptimized since these are API routes
105
+ const imageSizes = shouldFill ? (sizes || undefined) : sizes;
106
+ const filterStyle = brightness !== 100 || blur !== 0
107
+ ? `brightness(${brightness}%) blur(${blur}px)`
108
+ : undefined;
109
+ const handleEditClick = (e) => {
110
+ e.preventDefault();
111
+ e.stopPropagation();
112
+ window.dispatchEvent(new CustomEvent('open-image-editor', {
113
+ detail: { id, currentBrightness: brightness, currentBlur: blur }
114
+ }));
115
+ };
116
+ return (_jsxs("div", { className: `group/image relative overflow-hidden transition-all duration-300 ${className}`, style: {
191
117
  ...style,
192
- filter: filterStyle || style?.filter,
193
- // Ensure container has explicit positioning context
194
118
  position: 'relative',
195
- }, "data-image-id": id, children: [isResolving && (_jsx("div", { className: "absolute inset-0 z-20 flex items-center justify-center bg-neutral-100 dark:bg-neutral-800 animate-pulse", children: _jsx(Loader2, { className: "w-5 h-5 animate-spin text-neutral-400" }) })), hasTransforms && shouldFill ? (baseSrc ? (_jsx("img", { ref: imageRef, src: src, alt: alt, loading: priority ? 'eager' : 'lazy', onLoad: calculateBaseScale, onError: () => setImageError(true), className: "absolute max-w-none select-none", style: {
196
- top: '50%',
197
- left: '50%',
198
- width: 'auto', // Allow image to maintain natural aspect ratio
199
- height: 'auto', // Allow image to maintain natural aspect ratio
200
- minWidth: '100%', // Ensure image covers container width
201
- minHeight: '100%', // Ensure image covers container height
202
- transform: baseScale > 0 && transformStyle
203
- ? transformStyle
204
- : 'translate(-50%, -50%)',
205
- transformOrigin: 'center center',
206
- position: 'absolute',
207
- }, draggable: false })) : null) : imageError || !baseSrc ? (
208
- // Fallback to regular img tag when error occurs or no valid filename yet
209
- _jsx("img", { src: getFallbackImageUrl(), alt: alt, loading: priority ? 'eager' : 'lazy', className: `transition-all duration-500 ${editable ? 'group-hover/image:scale-105' : ''}`, style: {
210
- objectFit,
211
- objectPosition,
212
- width: shouldFill ? '100%' : width,
213
- height: shouldFill ? '100%' : height,
214
- ...(transformStyle ? { transform: transformStyle } : {}),
215
- ...style,
216
- } })) : (_jsx(NextImage, { src: src, alt: alt, width: !shouldFill ? width : undefined, height: !shouldFill ? height : undefined, fill: shouldFill, sizes: sizes || (shouldFill ? "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" : undefined), priority: priority, loading: priority ? 'eager' : undefined, className: `transition-all duration-500 ${editable ? 'group-hover/image:scale-105' : ''}`, style: {
217
- objectFit,
218
- objectPosition,
219
- ...(transformStyle ? { transform: transformStyle } : {}),
220
- ...style,
221
- } })), editable && isAuthenticated && (_jsx("button", { onClick: (e) => {
222
- e.preventDefault();
223
- window.dispatchEvent(new CustomEvent('open-image-editor', {
224
- detail: { id, currentBrightness: effective.brightness, currentBlur: effective.blur }
225
- }));
226
- }, className: "absolute inset-0 z-30 flex items-center justify-center opacity-0 group-hover/image:opacity-100 transition-all duration-300 bg-neutral-900/40 backdrop-blur-[2px]", children: _jsxs("div", { className: "flex items-center gap-2 px-4 py-2 bg-white dark:bg-neutral-800 rounded-full shadow-xl", children: [_jsx(Edit2, { size: 14, className: "text-primary" }), _jsx("span", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Edit" })] }) }))] }));
119
+ filter: filterStyle || style?.filter,
120
+ }, "data-image-id": id, children: [isResolving && (_jsx("div", { className: "absolute inset-0 z-20 flex items-center justify-center bg-neutral-100 dark:bg-neutral-800 animate-pulse", children: _jsx(Loader2, { className: "w-5 h-5 animate-spin text-neutral-400" }) })), _jsx(NextImage, { src: src, alt: alt, width: !shouldFill ? width : undefined, height: !shouldFill ? height : undefined, fill: shouldFill, sizes: imageSizes, unoptimized: true, priority: priority, className: `transition-all duration-500 ease-in-out ${editable ? 'group-hover/image:scale-105' : ''}`, style: {
121
+ objectFit: objectFit,
122
+ objectPosition: objectPosition,
123
+ } }), canEdit && (_jsxs("div", { onClick: handleEditClick, className: "absolute inset-0 z-30 flex items-center justify-center opacity-0 group-hover/image:opacity-100 transition-all duration-300 cursor-pointer", children: [_jsx("div", { className: "absolute inset-0 bg-neutral-900/40 dark:bg-neutral-900/60 backdrop-blur-[2px]" }), _jsxs("div", { className: "relative flex items-center gap-2 px-4 py-2.5 bg-neutral-100 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 rounded-full shadow-2xl scale-90 group-hover/image:scale-100 transition-all duration-300 border border-neutral-300 dark:border-neutral-700", children: [_jsx(Edit2, { size: 14, strokeWidth: 2.5, className: "text-primary" }), _jsx("span", { className: "text-[10px] font-black uppercase tracking-widest", children: "Edit" })] })] }))] }));
227
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ImageEditor.d.ts","sourceRoot":"","sources":["../../src/components/ImageEditor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA6F,MAAM,OAAO,CAAC;AAIlH,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvH;AAED,eAAO,MAAM,WAAW,4FAuStB,CAAC"}
1
+ {"version":3,"file":"ImageEditor.d.ts","sourceRoot":"","sources":["../../src/components/ImageEditor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA6F,MAAM,OAAO,CAAC;AAIlH,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvH;AAED,eAAO,MAAM,WAAW,4FAqItB,CAAC"}
@@ -1,172 +1,68 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect, useCallback, useImperativeHandle, forwardRef, useMemo } from 'react';
4
- import { ZoomIn, RotateCcw, Maximize2, Sun, Droplets } from 'lucide-react';
5
- import { getImageTransform, getImageFilter } from '../utils/transforms';
3
+ import { useState, useRef, useEffect, useImperativeHandle, forwardRef, useMemo } from 'react';
4
+ import { RotateCcw, Maximize2 } from 'lucide-react';
5
+ import { getImageFilter } from '../utils/transforms';
6
6
  export const ImageEditor = forwardRef(({ imageUrl, scale, positionX, positionY, brightness = 100, blur = 0, onScaleChange, onPositionChange, onBrightnessChange, onBlurChange, aspectRatio = '16/9', borderRadius = 'rounded-xl', imageId, }, ref) => {
7
7
  const containerRef = useRef(null);
8
- const imageRef = useRef(null);
9
8
  const [isDragging, setIsDragging] = useState(false);
10
9
  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
11
10
  const [dragStartPosition, setDragStartPosition] = useState({ x: 0, y: 0 });
12
- const [baseScale, setBaseScale] = useState(0);
13
- // Local state for brightness and blur (only applied on save)
14
11
  const [localBrightness, setLocalBrightness] = useState(brightness);
15
12
  const [localBlur, setLocalBlur] = useState(blur);
16
- // Keep internal values in a ref to avoid stale closure issues during the final save
17
- const currentValues = useRef({ scale, positionX, positionY, brightness, blur });
18
- useEffect(() => {
19
- currentValues.current = { scale, positionX, positionY, brightness: localBrightness, blur: localBlur };
20
- }, [scale, positionX, positionY, localBrightness, localBlur]);
21
- // Reset local values when props change (when editor opens with new values)
22
13
  useEffect(() => {
23
14
  setLocalBrightness(brightness);
24
15
  setLocalBlur(blur);
25
16
  }, [brightness, blur]);
26
- // EXPOSE TO PARENT: Return current values so Parent can handle the DB Save
27
17
  useImperativeHandle(ref, () => ({
28
18
  flushSave: async () => {
29
- // Read the latest values directly from props/state (they're in the dependency array, so they're current)
30
- // The ref is kept in sync via useEffect, but read directly from props to avoid any timing issues
31
- const latestScale = scale;
32
- const latestPositionX = positionX;
33
- const latestPositionY = positionY;
34
- const latestBrightness = localBrightness;
35
- const latestBlur = localBlur;
36
- // Update the ref to ensure it's in sync (for any other code that might read it)
37
- currentValues.current = {
38
- scale: latestScale,
39
- positionX: latestPositionX,
40
- positionY: latestPositionY,
41
- brightness: latestBrightness,
42
- blur: latestBlur
43
- };
44
- // DON'T call onBrightnessChange/onBlurChange here - they trigger saves
45
- // The parent (via onEditorSave) will handle saving all values together
46
- // This prevents duplicate saves
47
19
  return {
48
- scale: Math.max(0.1, Math.min(5.0, latestScale)),
49
- positionX: latestPositionX,
50
- positionY: latestPositionY,
51
- brightness: latestBrightness,
52
- blur: latestBlur
20
+ scale: Math.max(0.1, Math.min(5.0, scale)),
21
+ positionX: positionX,
22
+ positionY: positionY,
23
+ brightness: localBrightness,
24
+ blur: localBlur
53
25
  };
54
26
  }
55
27
  }), [scale, positionX, positionY, localBrightness, localBlur]);
56
- const getClampedPosition = useCallback((x, y, currentScale) => {
57
- if (!containerRef.current || !imageRef.current || baseScale === 0)
58
- return { x, y };
59
- const totalScale = baseScale * currentScale;
60
- const containerWidth = containerRef.current.offsetWidth;
61
- const containerHeight = containerRef.current.offsetHeight;
62
- // With minWidth/minHeight, the image maintains its natural aspect ratio
63
- // but is scaled to cover the container. The scaled dimensions are based on natural size.
64
- const scaledWidth = imageRef.current.naturalWidth * totalScale;
65
- const scaledHeight = imageRef.current.naturalHeight * totalScale;
66
- const overflowX = Math.max(0, (scaledWidth - containerWidth) / 2);
67
- const overflowY = Math.max(0, (scaledHeight - containerHeight) / 2);
68
- // Convert overflow limits to percentage of CONTAINER (not natural size)
69
- // Position values are container-relative, so limits must be too
70
- const limitX = containerWidth > 0 ? (overflowX / containerWidth) * 100 : 0;
71
- const limitY = containerHeight > 0 ? (overflowY / containerHeight) * 100 : 0;
72
- return { x: Math.max(-limitX, Math.min(limitX, x)), y: Math.max(-limitY, Math.min(limitY, y)) };
73
- }, [baseScale]);
74
28
  const handleScaleChange = (newScale) => {
75
- const s = Math.max(0.1, Math.min(5.0, newScale));
76
- onScaleChange(s);
77
- const clamped = getClampedPosition(positionX, positionY, s);
78
- onPositionChange(clamped.x, clamped.y);
29
+ onScaleChange(Math.max(0.1, Math.min(5.0, newScale)));
79
30
  };
80
- const calculateBaseScale = useCallback(() => {
81
- if (!imageRef.current || !containerRef.current)
82
- return;
83
- const img = imageRef.current;
84
- const container = containerRef.current;
85
- const containerWidth = container.offsetWidth;
86
- const containerHeight = container.offsetHeight;
87
- // Ensure container has dimensions before calculating
88
- if (containerWidth === 0 || containerHeight === 0) {
89
- // Try to get dimensions from computed styles or parent
90
- const parentWidth = container.parentElement?.clientWidth;
91
- const parentHeight = container.parentElement?.clientHeight;
92
- if (parentWidth && parentHeight && img.naturalWidth > 0) {
93
- const widthRatio = parentWidth / img.naturalWidth;
94
- const heightRatio = parentHeight / img.naturalHeight;
95
- const calculatedBaseScale = Math.max(widthRatio, heightRatio);
96
- setBaseScale(calculatedBaseScale);
97
- }
98
- return;
99
- }
100
- if (img.naturalWidth === 0 || img.naturalHeight === 0)
101
- return;
102
- setBaseScale(Math.max(containerWidth / img.naturalWidth, containerHeight / img.naturalHeight));
103
- }, []);
104
- useEffect(() => {
105
- if (!containerRef.current)
106
- return;
107
- const obs = new ResizeObserver(calculateBaseScale);
108
- obs.observe(containerRef.current);
109
- return () => obs.disconnect();
110
- }, [calculateBaseScale]);
111
31
  useEffect(() => {
112
32
  if (!isDragging)
113
33
  return;
114
34
  const move = (e) => {
115
- if (!containerRef.current || !imageRef.current || baseScale === 0)
35
+ if (!containerRef.current)
116
36
  return;
117
- const rect = containerRef.current.getBoundingClientRect();
118
- // Calculate movement in screen pixels
119
37
  const mouseDeltaX = e.clientX - dragStart.x;
120
38
  const mouseDeltaY = e.clientY - dragStart.y;
121
- // Convert screen pixel movement to percentage of CONTAINER
122
- // Since the image is now width: 100%; height: 100%, percentages are container-relative
123
- // This makes position values consistent across different container sizes
124
- const dx = containerRef.current && containerRef.current.offsetWidth > 0
125
- ? (mouseDeltaX / containerRef.current.offsetWidth) * 100
126
- : 0;
127
- const dy = containerRef.current && containerRef.current.offsetHeight > 0
128
- ? (mouseDeltaY / containerRef.current.offsetHeight) * 100
129
- : 0;
130
- const newPositionX = dragStartPosition.x + dx;
131
- const newPositionY = dragStartPosition.y + dy;
132
- const clamped = getClampedPosition(newPositionX, newPositionY, scale);
133
- onPositionChange(clamped.x, clamped.y);
39
+ const dx = (mouseDeltaX / containerRef.current.offsetWidth) * 100;
40
+ const dy = (mouseDeltaY / containerRef.current.offsetHeight) * 100;
41
+ onPositionChange(dragStartPosition.x + dx, dragStartPosition.y + dy);
134
42
  };
135
43
  const up = () => setIsDragging(false);
136
44
  window.addEventListener('mousemove', move);
137
45
  window.addEventListener('mouseup', up);
138
46
  return () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
139
- }, [isDragging, dragStart, dragStartPosition, scale, baseScale, getClampedPosition, onPositionChange]);
47
+ }, [isDragging, dragStart, dragStartPosition, onPositionChange]);
140
48
  const aspectValue = useMemo(() => {
141
49
  if (aspectRatio === 'auto')
142
50
  return undefined;
143
51
  const [w, h] = aspectRatio.split('/').map(Number);
144
52
  return w / h;
145
53
  }, [aspectRatio]);
146
- return (_jsxs("div", { className: "flex flex-col lg:flex-row gap-6 h-full", children: [_jsx("div", { className: "flex-1 bg-neutral-50 dark:bg-neutral-950 rounded-2xl p-4 lg:p-6 flex items-center justify-center border border-neutral-300 dark:border-neutral-700 relative group/canvas min-w-0", children: _jsx("div", { ref: containerRef, className: `relative shadow-2xl overflow-hidden ${borderRadius} bg-neutral-100 dark:bg-neutral-900 w-full max-w-full border border-neutral-200 dark:border-neutral-800`, style: { aspectRatio: aspectValue, height: aspectValue && aspectValue < 1 ? '400px' : 'auto', width: aspectValue && aspectValue < 1 ? 'auto' : '100%', cursor: isDragging ? 'grabbing' : 'grab' }, onMouseDown: (e) => {
54
+ return (_jsxs("div", { className: "flex flex-col lg:flex-row gap-6 h-full", children: [_jsx("div", { className: "flex-1 bg-neutral-50 dark:bg-neutral-950 rounded-2xl p-4 lg:p-6 flex items-center justify-center border border-neutral-300 dark:border-neutral-700 relative group/canvas min-w-0", children: _jsx("div", { ref: containerRef, className: `relative shadow-2xl overflow-hidden ${borderRadius} bg-neutral-100 dark:bg-neutral-900 w-full max-w-full border border-neutral-200 dark:border-neutral-800`, style: {
55
+ aspectRatio: aspectValue,
56
+ cursor: isDragging ? 'grabbing' : 'grab'
57
+ }, onMouseDown: (e) => {
147
58
  setIsDragging(true);
148
59
  setDragStart({ x: e.clientX, y: e.clientY });
149
- // Convert stored position to visual percentage for dragging
150
- // stored = visual, so we can use positionX directly as visual percentage
151
60
  setDragStartPosition({ x: positionX, y: positionY });
152
61
  e.preventDefault();
153
- }, children: _jsx("img", { ref: imageRef, src: imageUrl, alt: "Editor", onLoad: calculateBaseScale, className: "absolute max-w-none select-none", style: {
154
- top: '50%',
155
- left: '50%',
156
- width: 'auto', // Allow image to maintain natural aspect ratio
157
- height: 'auto', // Allow image to maintain natural aspect ratio
158
- minWidth: '100%', // Ensure image covers container width
159
- minHeight: '100%', // Ensure image covers container height
62
+ }, children: _jsx("img", { src: imageUrl, alt: "Editor", className: "w-full h-full object-cover select-none pointer-events-none", style: {
160
63
  filter: getImageFilter(localBrightness, localBlur),
161
- transform: baseScale > 0 ? getImageTransform({ scale, positionX, positionY, baseScale }, true) : 'translate(-50%, -50%)',
64
+ transform: `translate(${positionX}%, ${positionY}%) scale(${scale})`,
162
65
  transformOrigin: 'center center'
163
- }, draggable: false }) }) }), _jsx("div", { className: "w-full lg:w-80 space-y-4", children: _jsxs("div", { className: "p-6 bg-white dark:bg-neutral-900 rounded-3xl border border-neutral-200 dark:border-neutral-800 shadow-lg dark:shadow-neutral-950/50 space-y-6", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsxs("label", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 flex items-center gap-2", children: [_jsx(ZoomIn, { size: 14, className: "text-neutral-600 dark:text-neutral-300" }), " Zoom Level"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-xs font-mono font-bold text-primary dark:text-primary", children: [Math.round(scale * 100), "%"] }), _jsx("button", { onClick: () => handleScaleChange(1), className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors", title: "Reset zoom", children: _jsx(RotateCcw, { size: 12, className: "text-neutral-500 dark:text-neutral-400" }) })] })] }), _jsx("input", { type: "range", min: "0.1", max: "5", step: "0.01", value: scale, onChange: (e) => handleScaleChange(parseFloat(e.target.value)), className: "w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] }), onBrightnessChange && (_jsxs("div", { children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsxs("label", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 flex items-center gap-2", children: [_jsx(Sun, { size: 14, className: "text-neutral-600 dark:text-neutral-300" }), " Brightness"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-xs font-mono font-bold text-primary dark:text-primary", children: [Math.round(localBrightness), "%"] }), _jsx("button", { onClick: () => setLocalBrightness(100), className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors", title: "Reset brightness", children: _jsx(RotateCcw, { size: 12, className: "text-neutral-500 dark:text-neutral-400" }) })] })] }), _jsx("input", { type: "range", min: "0", max: "200", step: "1", value: localBrightness, onChange: (e) => setLocalBrightness(parseInt(e.target.value)), className: "w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] })), onBlurChange && (_jsxs("div", { children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsxs("label", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 flex items-center gap-2", children: [_jsx(Droplets, { size: 14, className: "text-neutral-600 dark:text-neutral-300" }), " Blur"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-xs font-mono font-bold text-primary dark:text-primary", children: [localBlur, "px"] }), _jsx("button", { onClick: () => setLocalBlur(0), className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors", title: "Reset blur", children: _jsx(RotateCcw, { size: 12, className: "text-neutral-500 dark:text-neutral-400" }) })] })] }), _jsx("input", { type: "range", min: "0", max: "20", step: "0.5", value: localBlur, onChange: (e) => setLocalBlur(parseFloat(e.target.value)), className: "w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] })), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("button", { onClick: () => onPositionChange(0, 0), className: "flex flex-col items-center gap-2 p-4 bg-neutral-50 dark:bg-neutral-800 rounded-2xl hover:bg-primary/10 dark:hover:bg-primary/20 transition-all border border-transparent hover:border-primary/20 dark:hover:border-primary/30", children: [_jsx(Maximize2, { size: 16, className: "text-neutral-700 dark:text-neutral-300" }), _jsx("span", { className: "text-[9px] font-black uppercase tracking-widest text-neutral-600 dark:text-neutral-400", children: "Center" })] }), _jsxs("button", { onClick: () => {
164
- handleScaleChange(1);
165
- if (onBrightnessChange)
166
- setLocalBrightness(100);
167
- if (onBlurChange)
168
- setLocalBlur(0);
169
- onPositionChange(0, 0);
170
- }, className: "flex flex-col items-center gap-2 p-4 bg-neutral-50 dark:bg-neutral-800 rounded-2xl hover:bg-primary/10 dark:hover:bg-primary/20 transition-all border border-transparent hover:border-primary/20 dark:hover:border-primary/30", children: [_jsx(RotateCcw, { size: 16, className: "text-neutral-700 dark:text-neutral-300" }), _jsx("span", { className: "text-[9px] font-black uppercase tracking-widest text-neutral-600 dark:text-neutral-400", children: "Reset All" })] })] })] }) })] }));
66
+ } }) }) }), _jsx("div", { className: "w-full lg:w-80 space-y-4", children: _jsxs("div", { className: "p-6 bg-white dark:bg-neutral-900 rounded-3xl border border-neutral-200 dark:border-neutral-800 shadow-lg dark:shadow-neutral-950/50 space-y-6", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("label", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 flex items-center gap-2", children: "Zoom Level" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-xs font-mono font-bold text-primary", children: [Math.round(scale * 100), "%"] }), _jsx("button", { onClick: () => handleScaleChange(1), className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg", children: _jsx(RotateCcw, { size: 12 }) })] })] }), _jsx("input", { type: "range", min: "0.1", max: "5", step: "0.01", value: scale, onChange: (e) => handleScaleChange(parseFloat(e.target.value)), className: "w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] }), onBrightnessChange && (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500 mb-4 block", children: "Brightness" }), _jsx("input", { type: "range", min: "0", max: "200", step: "1", value: localBrightness, onChange: (e) => setLocalBrightness(parseInt(e.target.value)), className: "w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] })), onBlurChange && (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500 mb-4 block", children: "Blur" }), _jsx("input", { type: "range", min: "0", max: "20", step: "0.5", value: localBlur, onChange: (e) => setLocalBlur(parseFloat(e.target.value)), className: "w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] })), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("button", { onClick: () => onPositionChange(0, 0), className: "flex flex-col items-center gap-2 p-4 bg-neutral-50 dark:bg-neutral-800 rounded-2xl border border-transparent hover:border-primary/20", children: [_jsx(Maximize2, { size: 16 }), _jsx("span", { className: "text-[9px] font-black uppercase tracking-widest", children: "Center" })] }), _jsxs("button", { onClick: () => { handleScaleChange(1); setLocalBrightness(100); setLocalBlur(0); onPositionChange(0, 0); }, className: "flex flex-col items-center gap-2 p-4 bg-neutral-50 dark:bg-neutral-800 rounded-2xl border border-transparent hover:border-primary/20", children: [_jsx(RotateCcw, { size: 16 }), _jsx("span", { className: "text-[9px] font-black uppercase tracking-widest", children: "Reset" })] })] })] }) })] }));
171
67
  });
172
68
  ImageEditor.displayName = 'ImageEditor';
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePicker.d.ts","sourceRoot":"","sources":["../../src/components/ImagePicker.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAQjD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EAAE,QAAQ,EAAE,UAAgB,EAAE,IAAQ,EAAE,KAAW,EAAE,SAAa,EAAE,SAAa,EACtF,WAAoB,EAAE,YAA2B,EAAE,kBAAkB,EAAE,YAAY,EACnF,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,cAAsB,GAC5F,EAAE,gBAAgB,2CAuPlB"}
1
+ {"version":3,"file":"ImagePicker.d.ts","sourceRoot":"","sources":["../../src/components/ImagePicker.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAOjD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EAAE,QAAQ,EAAE,UAAgB,EAAE,IAAQ,EAAE,KAAW,EAAE,SAAa,EAAE,SAAa,EACtF,WAAoB,EAAE,YAA2B,EAAE,kBAAkB,EAAE,YAAY,EACnF,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,cAAsB,GAC5F,EAAE,gBAAgB,2CAmKlB"}