@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.
- package/dist/api/fallback/route.d.ts +7 -0
- package/dist/api/fallback/route.d.ts.map +1 -0
- package/dist/api/fallback/route.js +65 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/list/index.d.ts +21 -0
- package/dist/api/list/index.d.ts.map +1 -0
- package/dist/api/list/index.js +80 -0
- package/dist/api/resolve/route.d.ts +39 -0
- package/dist/api/resolve/route.d.ts.map +1 -0
- package/dist/api/resolve/route.js +213 -0
- package/dist/api/router.d.ts +14 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +67 -0
- package/dist/api/upload/index.d.ts +20 -0
- package/dist/api/upload/index.d.ts.map +1 -0
- package/dist/api/upload/index.js +65 -0
- package/dist/api/uploads/[filename]/route.d.ts +21 -0
- package/dist/api/uploads/[filename]/route.d.ts.map +1 -0
- package/dist/api/uploads/[filename]/route.js +80 -0
- package/dist/api-server.d.ts +9 -0
- package/dist/api-server.d.ts.map +1 -0
- package/dist/api-server.js +9 -0
- package/dist/components/BackgroundImage.d.ts +11 -0
- package/dist/components/BackgroundImage.d.ts.map +1 -0
- package/dist/components/BackgroundImage.js +33 -0
- package/dist/components/GlobalImageEditor/config.d.ts +9 -0
- package/dist/components/GlobalImageEditor/config.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/config.js +17 -0
- package/dist/components/GlobalImageEditor/eventHandlers.d.ts +20 -0
- package/dist/components/GlobalImageEditor/eventHandlers.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/eventHandlers.js +210 -0
- package/dist/components/GlobalImageEditor/imageDetection.d.ts +16 -0
- package/dist/components/GlobalImageEditor/imageDetection.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/imageDetection.js +135 -0
- package/dist/components/GlobalImageEditor/imageSetup.d.ts +9 -0
- package/dist/components/GlobalImageEditor/imageSetup.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/imageSetup.js +260 -0
- package/dist/components/GlobalImageEditor/saveLogic.d.ts +26 -0
- package/dist/components/GlobalImageEditor/saveLogic.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/saveLogic.js +98 -0
- package/dist/components/GlobalImageEditor/stylingDetection.d.ts +9 -0
- package/dist/components/GlobalImageEditor/stylingDetection.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/stylingDetection.js +110 -0
- package/dist/components/GlobalImageEditor/transformParsing.d.ts +16 -0
- package/dist/components/GlobalImageEditor/transformParsing.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/transformParsing.js +68 -0
- package/dist/components/GlobalImageEditor/types.d.ts +36 -0
- package/dist/components/GlobalImageEditor/types.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor/types.js +4 -0
- package/dist/components/GlobalImageEditor.d.ts +8 -0
- package/dist/components/GlobalImageEditor.d.ts.map +1 -0
- package/dist/components/GlobalImageEditor.js +232 -0
- package/dist/components/Image.d.ts +22 -0
- package/dist/components/Image.d.ts.map +1 -0
- package/dist/components/Image.js +227 -0
- package/dist/components/ImageBrowserModal.d.ts +13 -0
- package/dist/components/ImageBrowserModal.d.ts.map +1 -0
- package/dist/components/ImageBrowserModal.js +507 -0
- package/dist/components/ImageEditor.d.ts +27 -0
- package/dist/components/ImageEditor.d.ts.map +1 -0
- package/dist/components/ImageEditor.js +172 -0
- package/dist/components/ImageEffectsPanel.d.ts +10 -0
- package/dist/components/ImageEffectsPanel.d.ts.map +1 -0
- package/dist/components/ImageEffectsPanel.js +11 -0
- package/dist/components/ImagePicker.d.ts +3 -0
- package/dist/components/ImagePicker.d.ts.map +1 -0
- package/dist/components/ImagePicker.js +142 -0
- package/dist/components/ImagesPluginInit.d.ts +24 -0
- package/dist/components/ImagesPluginInit.d.ts.map +1 -0
- package/dist/components/ImagesPluginInit.js +28 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +7 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +172 -0
- package/dist/hooks/useImagePicker.d.ts +20 -0
- package/dist/hooks/useImagePicker.d.ts.map +1 -0
- package/dist/hooks/useImagePicker.js +320 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.server.d.ts +11 -0
- package/dist/index.server.d.ts.map +1 -0
- package/dist/index.server.js +10 -0
- package/dist/init.d.ts +33 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +43 -0
- package/dist/types/index.d.ts +80 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/fallback.d.ts +27 -0
- package/dist/utils/fallback.d.ts.map +1 -0
- package/dist/utils/fallback.js +63 -0
- package/dist/utils/transforms.d.ts +26 -0
- package/dist/utils/transforms.d.ts.map +1 -0
- package/dist/utils/transforms.js +38 -0
- package/dist/views/ImageManager.d.ts +10 -0
- package/dist/views/ImageManager.d.ts.map +1 -0
- package/dist/views/ImageManager.js +9 -0
- package/package.json +26 -22
- package/src/assets/noimagefound.jpg +0 -0
- package/src/components/BackgroundImage.d.ts +11 -0
- package/src/components/BackgroundImage.d.ts.map +1 -0
- package/src/components/BackgroundImage.js +35 -0
- package/src/components/GlobalImageEditor/config.d.ts +9 -0
- package/src/components/GlobalImageEditor/config.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/config.js +18 -0
- package/src/components/GlobalImageEditor/eventHandlers.d.ts +20 -0
- package/src/components/GlobalImageEditor/eventHandlers.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/eventHandlers.js +206 -0
- package/src/components/GlobalImageEditor/imageDetection.d.ts +16 -0
- package/src/components/GlobalImageEditor/imageDetection.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/imageDetection.js +130 -0
- package/src/components/GlobalImageEditor/imageSetup.d.ts +9 -0
- package/src/components/GlobalImageEditor/imageSetup.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/imageSetup.js +261 -0
- package/src/components/GlobalImageEditor/saveLogic.d.ts +26 -0
- package/src/components/GlobalImageEditor/saveLogic.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/saveLogic.js +99 -0
- package/src/components/GlobalImageEditor/stylingDetection.d.ts +9 -0
- package/src/components/GlobalImageEditor/stylingDetection.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/stylingDetection.js +110 -0
- package/src/components/GlobalImageEditor/transformParsing.d.ts +16 -0
- package/src/components/GlobalImageEditor/transformParsing.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/transformParsing.js +68 -0
- package/src/components/GlobalImageEditor/types.d.ts +36 -0
- package/src/components/GlobalImageEditor/types.d.ts.map +1 -0
- package/src/components/GlobalImageEditor/types.js +4 -0
- package/src/components/GlobalImageEditor.d.ts +8 -0
- package/src/components/GlobalImageEditor.d.ts.map +1 -0
- package/src/components/GlobalImageEditor.js +227 -0
- package/src/components/GlobalImageEditor.tsx +2 -2
- package/src/components/Image.d.ts +22 -0
- package/src/components/Image.d.ts.map +1 -0
- package/src/components/Image.js +229 -0
- package/src/components/ImageBrowserModal.d.ts +13 -0
- package/src/components/ImageBrowserModal.d.ts.map +1 -0
- package/src/components/ImageBrowserModal.js +504 -0
- package/src/components/ImageBrowserModal.tsx +18 -5
- package/src/components/ImageEditor.d.ts +27 -0
- package/src/components/ImageEditor.d.ts.map +1 -0
- package/src/components/ImageEditor.js +173 -0
- package/src/components/ImagePicker.d.ts +3 -0
- package/src/components/ImagePicker.d.ts.map +1 -0
- package/src/components/ImagePicker.js +143 -0
- package/src/components/ImagePicker.tsx +53 -15
- package/src/components/ImagesPluginInit.d.ts +24 -0
- package/src/components/ImagesPluginInit.d.ts.map +1 -0
- package/src/components/ImagesPluginInit.js +28 -0
- package/src/hooks/useImagePicker.d.ts +20 -0
- package/src/hooks/useImagePicker.d.ts.map +1 -0
- package/src/hooks/useImagePicker.js +322 -0
- package/src/hooks/useImagePicker.ts +28 -6
- package/src/index.d.ts +23 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +28 -0
- package/src/init.d.ts +33 -0
- package/src/init.d.ts.map +1 -0
- package/src/init.js +43 -0
- package/src/types/index.d.ts +80 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/index.js +4 -0
- package/src/utils/fallback.d.ts +27 -0
- package/src/utils/fallback.d.ts.map +1 -0
- package/src/utils/fallback.js +63 -0
- package/src/utils/transforms.d.ts +26 -0
- package/src/utils/transforms.d.ts.map +1 -0
- package/src/utils/transforms.js +38 -0
- package/src/views/ImageManager.d.ts +10 -0
- package/src/views/ImageManager.d.ts.map +1 -0
- package/src/views/ImageManager.js +9 -0
- 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
|
-
}
|