@jhits/plugin-images 0.0.5 → 0.0.7

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 (175) hide show
  1. package/dist/api/fallback/route.d.ts +7 -0
  2. package/dist/api/fallback/route.d.ts.map +1 -0
  3. package/dist/api/fallback/route.js +65 -0
  4. package/dist/api/index.d.ts +9 -0
  5. package/dist/api/index.d.ts.map +1 -0
  6. package/dist/api/index.js +8 -0
  7. package/dist/api/list/index.d.ts +21 -0
  8. package/dist/api/list/index.d.ts.map +1 -0
  9. package/dist/api/list/index.js +80 -0
  10. package/dist/api/resolve/route.d.ts +39 -0
  11. package/dist/api/resolve/route.d.ts.map +1 -0
  12. package/dist/api/resolve/route.js +213 -0
  13. package/dist/api/router.d.ts +14 -0
  14. package/dist/api/router.d.ts.map +1 -0
  15. package/dist/api/router.js +67 -0
  16. package/dist/api/upload/index.d.ts +20 -0
  17. package/dist/api/upload/index.d.ts.map +1 -0
  18. package/dist/api/upload/index.js +65 -0
  19. package/dist/api/uploads/[filename]/route.d.ts +21 -0
  20. package/dist/api/uploads/[filename]/route.d.ts.map +1 -0
  21. package/dist/api/uploads/[filename]/route.js +80 -0
  22. package/dist/api-server.d.ts +9 -0
  23. package/dist/api-server.d.ts.map +1 -0
  24. package/dist/api-server.js +9 -0
  25. package/dist/components/BackgroundImage.d.ts +11 -0
  26. package/dist/components/BackgroundImage.d.ts.map +1 -0
  27. package/dist/components/BackgroundImage.js +33 -0
  28. package/dist/components/GlobalImageEditor/config.d.ts +9 -0
  29. package/dist/components/GlobalImageEditor/config.d.ts.map +1 -0
  30. package/dist/components/GlobalImageEditor/config.js +17 -0
  31. package/dist/components/GlobalImageEditor/eventHandlers.d.ts +20 -0
  32. package/dist/components/GlobalImageEditor/eventHandlers.d.ts.map +1 -0
  33. package/dist/components/GlobalImageEditor/eventHandlers.js +210 -0
  34. package/dist/components/GlobalImageEditor/imageDetection.d.ts +16 -0
  35. package/dist/components/GlobalImageEditor/imageDetection.d.ts.map +1 -0
  36. package/dist/components/GlobalImageEditor/imageDetection.js +135 -0
  37. package/dist/components/GlobalImageEditor/imageSetup.d.ts +9 -0
  38. package/dist/components/GlobalImageEditor/imageSetup.d.ts.map +1 -0
  39. package/dist/components/GlobalImageEditor/imageSetup.js +260 -0
  40. package/dist/components/GlobalImageEditor/saveLogic.d.ts +26 -0
  41. package/dist/components/GlobalImageEditor/saveLogic.d.ts.map +1 -0
  42. package/dist/components/GlobalImageEditor/saveLogic.js +98 -0
  43. package/dist/components/GlobalImageEditor/stylingDetection.d.ts +9 -0
  44. package/dist/components/GlobalImageEditor/stylingDetection.d.ts.map +1 -0
  45. package/dist/components/GlobalImageEditor/stylingDetection.js +110 -0
  46. package/dist/components/GlobalImageEditor/transformParsing.d.ts +16 -0
  47. package/dist/components/GlobalImageEditor/transformParsing.d.ts.map +1 -0
  48. package/dist/components/GlobalImageEditor/transformParsing.js +68 -0
  49. package/dist/components/GlobalImageEditor/types.d.ts +36 -0
  50. package/dist/components/GlobalImageEditor/types.d.ts.map +1 -0
  51. package/dist/components/GlobalImageEditor/types.js +4 -0
  52. package/dist/components/GlobalImageEditor.d.ts +8 -0
  53. package/dist/components/GlobalImageEditor.d.ts.map +1 -0
  54. package/dist/components/GlobalImageEditor.js +232 -0
  55. package/dist/components/Image.d.ts +22 -0
  56. package/dist/components/Image.d.ts.map +1 -0
  57. package/dist/components/Image.js +227 -0
  58. package/dist/components/ImageBrowserModal.d.ts +13 -0
  59. package/dist/components/ImageBrowserModal.d.ts.map +1 -0
  60. package/dist/components/ImageBrowserModal.js +507 -0
  61. package/dist/components/ImageEditor.d.ts +27 -0
  62. package/dist/components/ImageEditor.d.ts.map +1 -0
  63. package/dist/components/ImageEditor.js +172 -0
  64. package/dist/components/ImageEffectsPanel.d.ts +10 -0
  65. package/dist/components/ImageEffectsPanel.d.ts.map +1 -0
  66. package/dist/components/ImageEffectsPanel.js +11 -0
  67. package/dist/components/ImagePicker.d.ts +3 -0
  68. package/dist/components/ImagePicker.d.ts.map +1 -0
  69. package/dist/components/ImagePicker.js +142 -0
  70. package/dist/components/ImagesPluginInit.d.ts +24 -0
  71. package/dist/components/ImagesPluginInit.d.ts.map +1 -0
  72. package/dist/components/ImagesPluginInit.js +28 -0
  73. package/dist/components/index.d.ts +9 -0
  74. package/dist/components/index.d.ts.map +1 -0
  75. package/dist/components/index.js +7 -0
  76. package/dist/config.d.ts +14 -0
  77. package/dist/config.d.ts.map +1 -0
  78. package/dist/config.js +172 -0
  79. package/dist/hooks/useImagePicker.d.ts +20 -0
  80. package/dist/hooks/useImagePicker.d.ts.map +1 -0
  81. package/dist/hooks/useImagePicker.js +320 -0
  82. package/dist/index.d.ts +23 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +28 -0
  85. package/dist/index.server.d.ts +11 -0
  86. package/dist/index.server.d.ts.map +1 -0
  87. package/dist/index.server.js +10 -0
  88. package/dist/init.d.ts +33 -0
  89. package/dist/init.d.ts.map +1 -0
  90. package/dist/init.js +43 -0
  91. package/dist/types/index.d.ts +80 -0
  92. package/dist/types/index.d.ts.map +1 -0
  93. package/dist/types/index.js +4 -0
  94. package/dist/utils/fallback.d.ts +27 -0
  95. package/dist/utils/fallback.d.ts.map +1 -0
  96. package/dist/utils/fallback.js +63 -0
  97. package/dist/utils/transforms.d.ts +26 -0
  98. package/dist/utils/transforms.d.ts.map +1 -0
  99. package/dist/utils/transforms.js +38 -0
  100. package/dist/views/ImageManager.d.ts +10 -0
  101. package/dist/views/ImageManager.d.ts.map +1 -0
  102. package/dist/views/ImageManager.js +9 -0
  103. package/package.json +26 -22
  104. package/src/assets/noimagefound.jpg +0 -0
  105. package/src/components/BackgroundImage.d.ts +11 -0
  106. package/src/components/BackgroundImage.d.ts.map +1 -0
  107. package/src/components/BackgroundImage.js +35 -0
  108. package/src/components/GlobalImageEditor/config.d.ts +9 -0
  109. package/src/components/GlobalImageEditor/config.d.ts.map +1 -0
  110. package/src/components/GlobalImageEditor/config.js +18 -0
  111. package/src/components/GlobalImageEditor/eventHandlers.d.ts +20 -0
  112. package/src/components/GlobalImageEditor/eventHandlers.d.ts.map +1 -0
  113. package/src/components/GlobalImageEditor/eventHandlers.js +206 -0
  114. package/src/components/GlobalImageEditor/imageDetection.d.ts +16 -0
  115. package/src/components/GlobalImageEditor/imageDetection.d.ts.map +1 -0
  116. package/src/components/GlobalImageEditor/imageDetection.js +130 -0
  117. package/src/components/GlobalImageEditor/imageSetup.d.ts +9 -0
  118. package/src/components/GlobalImageEditor/imageSetup.d.ts.map +1 -0
  119. package/src/components/GlobalImageEditor/imageSetup.js +261 -0
  120. package/src/components/GlobalImageEditor/saveLogic.d.ts +26 -0
  121. package/src/components/GlobalImageEditor/saveLogic.d.ts.map +1 -0
  122. package/src/components/GlobalImageEditor/saveLogic.js +99 -0
  123. package/src/components/GlobalImageEditor/stylingDetection.d.ts +9 -0
  124. package/src/components/GlobalImageEditor/stylingDetection.d.ts.map +1 -0
  125. package/src/components/GlobalImageEditor/stylingDetection.js +110 -0
  126. package/src/components/GlobalImageEditor/transformParsing.d.ts +16 -0
  127. package/src/components/GlobalImageEditor/transformParsing.d.ts.map +1 -0
  128. package/src/components/GlobalImageEditor/transformParsing.js +68 -0
  129. package/src/components/GlobalImageEditor/types.d.ts +36 -0
  130. package/src/components/GlobalImageEditor/types.d.ts.map +1 -0
  131. package/src/components/GlobalImageEditor/types.js +4 -0
  132. package/src/components/GlobalImageEditor.d.ts +8 -0
  133. package/src/components/GlobalImageEditor.d.ts.map +1 -0
  134. package/src/components/GlobalImageEditor.js +227 -0
  135. package/src/components/GlobalImageEditor.tsx +2 -2
  136. package/src/components/Image.d.ts +22 -0
  137. package/src/components/Image.d.ts.map +1 -0
  138. package/src/components/Image.js +229 -0
  139. package/src/components/ImageBrowserModal.d.ts +13 -0
  140. package/src/components/ImageBrowserModal.d.ts.map +1 -0
  141. package/src/components/ImageBrowserModal.js +504 -0
  142. package/src/components/ImageBrowserModal.tsx +18 -5
  143. package/src/components/ImageEditor.d.ts +27 -0
  144. package/src/components/ImageEditor.d.ts.map +1 -0
  145. package/src/components/ImageEditor.js +173 -0
  146. package/src/components/ImagePicker.d.ts +3 -0
  147. package/src/components/ImagePicker.d.ts.map +1 -0
  148. package/src/components/ImagePicker.js +143 -0
  149. package/src/components/ImagePicker.tsx +53 -15
  150. package/src/components/ImagesPluginInit.d.ts +24 -0
  151. package/src/components/ImagesPluginInit.d.ts.map +1 -0
  152. package/src/components/ImagesPluginInit.js +28 -0
  153. package/src/hooks/useImagePicker.d.ts +20 -0
  154. package/src/hooks/useImagePicker.d.ts.map +1 -0
  155. package/src/hooks/useImagePicker.js +322 -0
  156. package/src/hooks/useImagePicker.ts +28 -6
  157. package/src/index.d.ts +23 -0
  158. package/src/index.d.ts.map +1 -0
  159. package/src/index.js +28 -0
  160. package/src/init.d.ts +33 -0
  161. package/src/init.d.ts.map +1 -0
  162. package/src/init.js +43 -0
  163. package/src/types/index.d.ts +80 -0
  164. package/src/types/index.d.ts.map +1 -0
  165. package/src/types/index.js +4 -0
  166. package/src/utils/fallback.d.ts +27 -0
  167. package/src/utils/fallback.d.ts.map +1 -0
  168. package/src/utils/fallback.js +63 -0
  169. package/src/utils/transforms.d.ts +26 -0
  170. package/src/utils/transforms.d.ts.map +1 -0
  171. package/src/utils/transforms.js +38 -0
  172. package/src/views/ImageManager.d.ts +10 -0
  173. package/src/views/ImageManager.d.ts.map +1 -0
  174. package/src/views/ImageManager.js +9 -0
  175. package/src/components/GlobalImageEditor/GlobalImageEditor.tsx +0 -374
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Fallback image utility
3
+ * Provides a default fallback image URL when images fail to load or are invalid
4
+ * Also handles URL construction for image filenames
5
+ */
6
+ /**
7
+ * Returns the URL for the fallback "image not found" image
8
+ * Served from the plugin's API route
9
+ */
10
+ export function getFallbackImageUrl() {
11
+ return '/api/plugin-images/fallback';
12
+ }
13
+ /**
14
+ * Constructs the full image URL from a filename or URL
15
+ * - If it's already a full URL (http://, https://, or starts with /), returns as-is
16
+ * - If it's a filename, constructs `/api/uploads/${filename}`
17
+ */
18
+ export function constructImageUrl(src) {
19
+ if (!src || typeof src !== 'string') {
20
+ return null;
21
+ }
22
+ // If it's already a full URL (absolute or relative), return as-is
23
+ if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('/')) {
24
+ return src;
25
+ }
26
+ // Otherwise, it's a filename - construct the API URL
27
+ return `/api/uploads/${src}`;
28
+ }
29
+ /**
30
+ * Validates if a URL is valid and can be used with Next.js Image component
31
+ */
32
+ export function isValidImageUrl(url) {
33
+ if (!url || typeof url !== 'string') {
34
+ return false;
35
+ }
36
+ // Check if it's a valid URL format
37
+ try {
38
+ // For relative URLs (starting with /), they're valid
39
+ if (url.startsWith('/')) {
40
+ return true;
41
+ }
42
+ // For absolute URLs, validate the URL format
43
+ new URL(url);
44
+ return true;
45
+ }
46
+ catch (_a) {
47
+ return false;
48
+ }
49
+ }
50
+ /**
51
+ * Gets a safe image URL with automatic URL construction and fallback
52
+ * - Constructs the full URL if src is a filename
53
+ * - Falls back to the plugin's fallback image if invalid or missing
54
+ */
55
+ export function getSafeImageUrl(src) {
56
+ // First, construct the URL if it's a filename
57
+ const constructedUrl = constructImageUrl(src);
58
+ // Then validate and return, or fallback
59
+ if (isValidImageUrl(constructedUrl)) {
60
+ return constructedUrl;
61
+ }
62
+ return getFallbackImageUrl();
63
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Image Transform Utilities
3
+ */
4
+ export interface ImageTransformOptions {
5
+ scale: number;
6
+ positionX: number;
7
+ positionY: number;
8
+ baseScale?: number;
9
+ }
10
+ /**
11
+ * Calculates the CSS transform string
12
+ * Order: Center -> Scale -> Translate (Offset)
13
+ *
14
+ * Position values are stored as percentages of the CONTAINER, not the image.
15
+ * This makes them consistent across different container sizes.
16
+ *
17
+ * IMPORTANT: The image uses width: auto; height: auto with minWidth: 100%; minHeight: 100%
18
+ * This allows the image to maintain its natural aspect ratio while ensuring it covers the container.
19
+ * baseScale handles the "fitting" to ensure the image is large enough to fill the container.
20
+ *
21
+ * Applying SCALE before POSITION makes the position relative to the visual size you see,
22
+ * not the original raw file size. This ensures consistent positioning across different scales.
23
+ */
24
+ export declare function getImageTransform(options: ImageTransformOptions, needsCentering?: boolean, caller?: string): string;
25
+ export declare function getImageFilter(brightness?: number, blur?: number): string | undefined;
26
+ //# sourceMappingURL=transforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["transforms.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC7B,OAAO,EAAE,qBAAqB,EAC9B,cAAc,GAAE,OAAe,EAC/B,MAAM,CAAC,EAAE,MAAM,GAChB,MAAM,CAmBR;AAED,wBAAgB,cAAc,CAAC,UAAU,GAAE,MAAY,EAAE,IAAI,GAAE,MAAU,GAAG,MAAM,GAAG,SAAS,CAG7F"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Image Transform Utilities
3
+ */
4
+ /**
5
+ * Calculates the CSS transform string
6
+ * Order: Center -> Scale -> Translate (Offset)
7
+ *
8
+ * Position values are stored as percentages of the CONTAINER, not the image.
9
+ * This makes them consistent across different container sizes.
10
+ *
11
+ * IMPORTANT: The image uses width: auto; height: auto with minWidth: 100%; minHeight: 100%
12
+ * This allows the image to maintain its natural aspect ratio while ensuring it covers the container.
13
+ * baseScale handles the "fitting" to ensure the image is large enough to fill the container.
14
+ *
15
+ * Applying SCALE before POSITION makes the position relative to the visual size you see,
16
+ * not the original raw file size. This ensures consistent positioning across different scales.
17
+ */
18
+ export function getImageTransform(options, needsCentering = false, caller) {
19
+ const { scale, positionX, positionY, baseScale = 1 } = options;
20
+ const totalScale = baseScale * scale;
21
+ // 1. Center the image (if using top:50% left:50%)
22
+ const center = needsCentering ? 'translate(-50%, -50%)' : '';
23
+ // 2. Apply the scaling (base x zoom) FIRST
24
+ // This ensures the position offset is relative to the scaled visual size
25
+ const zoom = `scale(${totalScale})`;
26
+ // 3. Apply the offset (positionX/Y) AFTER scaling
27
+ // Position values are stored as percentage of CONTAINER
28
+ // Since scale is applied first, the translate is relative to the scaled visual size
29
+ const offset = `translate(${positionX}%, ${positionY}%)`;
30
+ // Combining them: Center first, then scale, then move
31
+ // Order matters: scale before translate ensures position is relative to visual size
32
+ return `${center} ${zoom} ${offset}`.trim();
33
+ }
34
+ export function getImageFilter(brightness = 100, blur = 0) {
35
+ if (brightness === 100 && blur === 0)
36
+ return undefined;
37
+ return `brightness(${brightness}%) blur(${blur}px)`;
38
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Image Manager View
3
+ * Main view for managing uploaded images
4
+ */
5
+ export interface ImageManagerViewProps {
6
+ siteId: string;
7
+ locale: string;
8
+ }
9
+ export declare function ImageManagerView({ siteId, locale }: ImageManagerViewProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=ImageManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageManager.d.ts","sourceRoot":"","sources":["ImageManager.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,qBAAqB,2CAczE"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Image Manager View
3
+ * Main view for managing uploaded images
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ export function ImageManagerView({ siteId, locale }) {
8
+ return (_jsx("div", { className: "min-h-screen bg-dashboard-bg p-8", children: _jsxs("div", { className: "max-w-7xl mx-auto", children: [_jsx("h1", { className: "text-4xl font-black uppercase tracking-tighter text-dashboard-text mb-8", children: "Image Manager" }), _jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 mb-8", children: "This plugin provides image upload and management functionality. Use the ImagePicker component in other plugins to select images." })] }) }));
9
+ }
@@ -1,374 +0,0 @@
1
- /**
2
- * Global Image Editor Component
3
- * Allows clicking on any image in the client app to edit it (admin/dev only)
4
- *
5
- * Reads configuration from window.__JHITS_PLUGIN_PROPS__['plugin-images']
6
- */
7
-
8
- 'use client';
9
-
10
- import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
11
- import { X, Edit2 } from 'lucide-react';
12
- import { ImagePicker } from '../ImagePicker';
13
- import type { ImageMetadata } from '../../types';
14
- import type { SelectedImage, PendingTransform } from './types';
15
- import { getPluginConfig } from './config';
16
- import { parseImageData } from './imageDetection';
17
- import { setupImageHandlers } from './imageSetup';
18
- import { saveTransformToAPI, flushPendingSave, getFilename, normalizePosition } from './saveLogic';
19
- import { handleImageChange, handleBrightnessChange, handleBlurChange } from './eventHandlers';
20
-
21
- export function GlobalImageEditor() {
22
- // Configuration
23
- const config = useMemo(() => getPluginConfig(), []);
24
-
25
- // State
26
- const [selectedImage, setSelectedImage] = useState<SelectedImage | null>(null);
27
- const [isOpen, setIsOpen] = useState(false);
28
- const [userRole, setUserRole] = useState<string | null>(null);
29
- const [isLoading, setIsLoading] = useState(true);
30
-
31
- // Refs for save debouncing
32
- const saveTransformTimeoutRef = useRef<NodeJS.Timeout | null>(null);
33
- const pendingTransformRef = useRef<PendingTransform | null>(null);
34
-
35
- // Check if user is admin/dev
36
- useEffect(() => {
37
- const checkUser = async () => {
38
- try {
39
- const res = await fetch('/api/me');
40
- const data = await res.json();
41
- if (data.loggedIn && (data.user?.role === 'admin' || data.user?.role === 'dev')) {
42
- setUserRole(data.user.role);
43
- }
44
- } catch (error) {
45
- console.error('Failed to check user role:', error);
46
- } finally {
47
- setIsLoading(false);
48
- }
49
- };
50
- checkUser();
51
- }, []);
52
-
53
- // Listen for open-image-editor custom event from Image/BackgroundImage components
54
- useEffect(() => {
55
- if (!userRole) return;
56
-
57
- const handleOpenEditor = async (e: CustomEvent) => {
58
- const { id, currentBrightness, currentBlur } = e.detail || {};
59
- if (!id) return;
60
-
61
- console.log('[GlobalImageEditor] open-image-editor event received for id:', id);
62
-
63
- // Find the element by data-image-id or data-background-image-id
64
- let element = document.querySelector(`[data-image-id="${id}"], [data-background-image-id="${id}"]`) as HTMLElement;
65
-
66
- if (!element) {
67
- element = document.querySelector(`[data-background-image-component="true"][data-background-image-id="${id}"]`) as HTMLElement;
68
- }
69
-
70
- if (!element) {
71
- console.error('[GlobalImageEditor] Element not found for id:', id);
72
- return;
73
- }
74
-
75
- // Extract semantic ID
76
- const semanticId = element.getAttribute('data-image-id') ||
77
- element.getAttribute('data-background-image-id') ||
78
- id;
79
-
80
- // Parse image data
81
- const imageData = parseImageData(element, semanticId, currentBrightness, currentBlur);
82
-
83
- if (!imageData) {
84
- console.error('[GlobalImageEditor] Failed to parse image data');
85
- return;
86
- }
87
-
88
- // Store semantic ID on element if not already set
89
- if (imageData.isBackground && !element.hasAttribute('data-background-image-id')) {
90
- element.setAttribute('data-background-image-id', semanticId);
91
- } else if (!imageData.isBackground && !element.hasAttribute('data-image-id')) {
92
- element.setAttribute('data-image-id', semanticId);
93
- }
94
-
95
- setSelectedImage(imageData);
96
- setIsOpen(true);
97
- };
98
-
99
- window.addEventListener('open-image-editor', handleOpenEditor as unknown as EventListener);
100
-
101
- return () => {
102
- window.removeEventListener('open-image-editor', handleOpenEditor as unknown as EventListener);
103
- };
104
- }, [userRole]);
105
-
106
- // Add click handlers to all images
107
- useEffect(() => {
108
- if (isLoading || !userRole) {
109
- console.log('[GlobalImageEditor] Skipping image handlers - isLoading:', isLoading, 'userRole:', userRole);
110
- return;
111
- }
112
-
113
- console.log('[GlobalImageEditor] Setting up image handlers for role:', userRole);
114
-
115
- const cleanup = setupImageHandlers((imageData) => {
116
- setSelectedImage(imageData);
117
- setIsOpen(true);
118
- });
119
-
120
- // Setup immediately and also after a short delay to catch dynamically loaded images
121
- const timeoutId = setTimeout(() => {
122
- const cleanup2 = setupImageHandlers((imageData) => {
123
- setSelectedImage(imageData);
124
- setIsOpen(true);
125
- });
126
- // Note: This creates a new cleanup, but the first one will handle the initial setup
127
- }, 500);
128
-
129
- return () => {
130
- clearTimeout(timeoutId);
131
- cleanup();
132
- };
133
- }, [isLoading, userRole]);
134
-
135
- // Save image transform (scale/position) to API with debouncing
136
- const saveImageTransformHandler = useCallback(async (
137
- scale: number,
138
- positionX: number,
139
- positionY: number,
140
- immediate: boolean = false,
141
- brightnessOverride?: number,
142
- blurOverride?: number
143
- ) => {
144
- if (!selectedImage) return;
145
-
146
- const { element } = selectedImage;
147
- const semanticId = element.getAttribute('data-image-id') ||
148
- element.getAttribute('data-background-image-id');
149
-
150
- if (!semanticId) return;
151
-
152
- const filename = await getFilename(semanticId, selectedImage);
153
- if (!filename) {
154
- console.error('[GlobalImageEditor] getFilename returned null for semanticId:', semanticId);
155
- return;
156
- }
157
- console.log('[GlobalImageEditor] getFilename returned:', filename, 'for semanticId:', semanticId);
158
-
159
- // Use override values if provided, otherwise use selectedImage values
160
- // IMPORTANT: Overrides contain the latest values from the editor
161
- const finalBrightness = brightnessOverride !== undefined ? brightnessOverride : (selectedImage?.brightness ?? 100);
162
- const finalBlur = blurOverride !== undefined ? blurOverride : (selectedImage?.blur ?? 0);
163
-
164
- // Store pending transform
165
- pendingTransformRef.current = {
166
- scale,
167
- positionX,
168
- positionY,
169
- semanticId,
170
- filename
171
- };
172
-
173
- // Clear existing timeout
174
- if (saveTransformTimeoutRef.current) {
175
- clearTimeout(saveTransformTimeoutRef.current);
176
- saveTransformTimeoutRef.current = null;
177
- }
178
-
179
- // If immediate save is requested (e.g., when "Done" is clicked), save right away
180
- if (immediate) {
181
- const normalizedPositionX = normalizePosition(positionX);
182
- const normalizedPositionY = normalizePosition(positionY);
183
-
184
- console.log('[GlobalImageEditor] Immediate save to API:', { semanticId, filename, scale, normalizedPositionX, normalizedPositionY, finalBrightness, finalBlur });
185
- await saveTransformToAPI(
186
- semanticId,
187
- filename,
188
- scale,
189
- normalizedPositionX,
190
- normalizedPositionY,
191
- finalBrightness,
192
- finalBlur
193
- );
194
- pendingTransformRef.current = null;
195
- return;
196
- }
197
-
198
- // Debounce: save after 300ms of no changes
199
- saveTransformTimeoutRef.current = setTimeout(async () => {
200
- const pending = pendingTransformRef.current;
201
- if (pending && selectedImage) {
202
- await flushPendingSave(pending, selectedImage);
203
- }
204
- }, 300);
205
- }, [selectedImage]);
206
-
207
- // Cleanup timeout on unmount
208
- useEffect(() => {
209
- return () => {
210
- if (saveTransformTimeoutRef.current) {
211
- clearTimeout(saveTransformTimeoutRef.current);
212
- }
213
- };
214
- }, []);
215
-
216
- // Event handlers
217
- const handleImageChangeWrapper = useCallback(async (image: ImageMetadata | null) => {
218
- if (!selectedImage) return;
219
- await handleImageChange(image, selectedImage, handleClose);
220
- }, [selectedImage]);
221
-
222
- const handleBrightnessChangeWrapper = useCallback(async (brightness: number) => {
223
- if (!selectedImage) return;
224
- // Don't save immediately when in editor - will be saved when "Done" is pressed via onEditorSave
225
- await handleBrightnessChange(brightness, selectedImage, setSelectedImage, false);
226
- }, [selectedImage]);
227
-
228
- const handleBlurChangeWrapper = useCallback(async (blur: number) => {
229
- if (!selectedImage) return;
230
- // Don't save immediately when in editor - will be saved when "Done" is pressed via onEditorSave
231
- await handleBlurChange(blur, selectedImage, setSelectedImage, false);
232
- }, [selectedImage]);
233
-
234
- const handleEditorSave = useCallback(async (
235
- finalScale: number,
236
- finalPositionX: number,
237
- finalPositionY: number,
238
- finalBrightness?: number,
239
- finalBlur?: number
240
- ) => {
241
- console.log('[GlobalImageEditor] handleEditorSave callback created/updated');
242
- console.log('[GlobalImageEditor] handleEditorSave called:', { finalScale, finalPositionX, finalPositionY, finalBrightness, finalBlur, hasSelectedImage: !!selectedImage });
243
- if (selectedImage) {
244
- // Use provided values if they're not undefined, otherwise fall back to selectedImage values
245
- // Note: 0 is a valid value for blur, so we check for undefined explicitly
246
- const brightness = finalBrightness !== undefined ? finalBrightness : selectedImage.brightness;
247
- const blur = finalBlur !== undefined ? finalBlur : selectedImage.blur;
248
-
249
- const updated = {
250
- ...selectedImage,
251
- scale: finalScale,
252
- positionX: finalPositionX,
253
- positionY: finalPositionY,
254
- brightness,
255
- blur,
256
- };
257
- setSelectedImage(updated);
258
-
259
- console.log('[GlobalImageEditor] Calling saveImageTransformHandler with:', { finalScale, finalPositionX, finalPositionY, brightness, blur, immediate: true });
260
- // Only save once when editor closes with all final values
261
- // ImagePicker prevents duplicate calls by not calling individual handlers when onEditorSave is provided
262
- await saveImageTransformHandler(
263
- finalScale,
264
- finalPositionX,
265
- finalPositionY,
266
- true,
267
- brightness,
268
- blur
269
- );
270
- } else {
271
- console.error('[GlobalImageEditor] handleEditorSave called but selectedImage is null!');
272
- }
273
- }, [selectedImage, saveImageTransformHandler]);
274
-
275
- const handleClose = useCallback(() => {
276
- setIsOpen(false);
277
- setSelectedImage(null);
278
- }, []);
279
-
280
- // Don't render if disabled or user is not admin/dev
281
- if (!config.enabled || isLoading || !userRole) {
282
- return null;
283
- }
284
-
285
- if (!isOpen || !selectedImage) {
286
- return null;
287
- }
288
-
289
- return (
290
- <div
291
- className={`fixed inset-0 z-[9999] flex items-center justify-center ${config.overlayClassName || 'bg-black/50 backdrop-blur-sm'}`}
292
- data-image-editor="true"
293
- onClick={(e) => {
294
- if (e.target === e.currentTarget) {
295
- handleClose();
296
- }
297
- }}
298
- >
299
- <div
300
- className={`relative bg-white dark:bg-neutral-900 rounded-2xl shadow-2xl max-w-7xl w-full mx-4 max-h-[90vh] flex flex-col ${config.className || ''}`}
301
- onClick={(e) => e.stopPropagation()}
302
- >
303
- {/* Header */}
304
- <div className="flex items-center justify-between p-6 border-b dark:border-neutral-800">
305
- <h2 className="text-2xl font-bold dark:text-white">Edit Image</h2>
306
- <button
307
- onClick={handleClose}
308
- className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors"
309
- aria-label="Close editor"
310
- >
311
- <X className="w-5 h-5" />
312
- </button>
313
- </div>
314
-
315
- {/* Content */}
316
- <div className="p-6">
317
- <ImagePicker
318
- value={selectedImage.element.getAttribute('data-image-id') || selectedImage.element.getAttribute('data-background-image-id') || selectedImage.originalSrc}
319
- onChange={handleImageChangeWrapper}
320
- brightness={selectedImage.brightness}
321
- blur={selectedImage.blur}
322
- scale={selectedImage.scale}
323
- positionX={selectedImage.positionX}
324
- positionY={selectedImage.positionY}
325
- onBrightnessChange={handleBrightnessChangeWrapper}
326
- onBlurChange={handleBlurChangeWrapper}
327
- onEditorSave={handleEditorSave}
328
- onScaleChange={async (newScale) => {
329
- console.log('[GlobalImageEditor] onScaleChange called:', newScale);
330
- if (selectedImage) {
331
- const updated = { ...selectedImage, scale: newScale };
332
- setSelectedImage(updated);
333
- await saveImageTransformHandler(newScale, updated.positionX, updated.positionY, false);
334
- }
335
- }}
336
- onPositionXChange={async (newPositionX) => {
337
- console.log('[GlobalImageEditor] onPositionXChange called:', newPositionX);
338
- if (selectedImage) {
339
- const updated = { ...selectedImage, positionX: newPositionX };
340
- setSelectedImage(updated);
341
- await saveImageTransformHandler(updated.scale, newPositionX, updated.positionY, false);
342
- }
343
- }}
344
- onPositionYChange={async (newPositionY) => {
345
- console.log('[GlobalImageEditor] onPositionYChange called:', newPositionY);
346
- if (selectedImage) {
347
- const updated = { ...selectedImage, positionY: newPositionY };
348
- setSelectedImage(updated);
349
- // If onEditorSave wasn't provided, this might be called when editor closes
350
- // In that case, we should save immediately with all current values including brightness/blur
351
- // We'll save immediately to ensure the final state is persisted
352
- await saveImageTransformHandler(
353
- updated.scale,
354
- updated.positionX,
355
- newPositionY,
356
- true, // Save immediately - this is likely the final callback when editor closes
357
- updated.brightness,
358
- updated.blur
359
- );
360
- }
361
- }}
362
- onEditorSave={handleEditorSave}
363
- showEffects={true}
364
- darkMode={false}
365
- aspectRatio={selectedImage.aspectRatio}
366
- borderRadius={selectedImage.borderRadius}
367
- objectFit={selectedImage.objectFit}
368
- objectPosition={selectedImage.objectPosition}
369
- />
370
- </div>
371
- </div>
372
- </div>
373
- );
374
- }