@jhits/plugin-images 0.0.6 → 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 +8 -8
- 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/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/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/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/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
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { Upload, Search, X, Image as ImageIcon, Check, Link as LinkIcon, Loader2, Grid3x3 } from 'lucide-react';
|
|
6
|
+
// Masonry Grid Component that maintains item positions
|
|
7
|
+
function MasonryGrid({ images, selectedImageId, onSelectImage, onClose, fixedColumns, }) {
|
|
8
|
+
const containerRef = useRef(null);
|
|
9
|
+
const [columnCount, setColumnCount] = useState(fixedColumns || 2);
|
|
10
|
+
// Calculate column count based on screen size (only if not fixed)
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (fixedColumns) {
|
|
13
|
+
setColumnCount(fixedColumns);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const updateColumnCount = () => {
|
|
17
|
+
const width = window.innerWidth;
|
|
18
|
+
let cols = 2;
|
|
19
|
+
if (width >= 1280)
|
|
20
|
+
cols = 6;
|
|
21
|
+
else if (width >= 1024)
|
|
22
|
+
cols = 5;
|
|
23
|
+
else if (width >= 768)
|
|
24
|
+
cols = 4;
|
|
25
|
+
else if (width >= 640)
|
|
26
|
+
cols = 3;
|
|
27
|
+
setColumnCount(cols);
|
|
28
|
+
};
|
|
29
|
+
updateColumnCount();
|
|
30
|
+
window.addEventListener('resize', updateColumnCount);
|
|
31
|
+
return () => window.removeEventListener('resize', updateColumnCount);
|
|
32
|
+
}, [fixedColumns]);
|
|
33
|
+
// Distribute images into columns using stable round-robin assignment
|
|
34
|
+
// This ensures items NEVER move once rendered - they always go to the same column
|
|
35
|
+
// based on their index, preventing reshuffling when new items are added
|
|
36
|
+
const columns = useMemo(() => {
|
|
37
|
+
const cols = Array.from({ length: columnCount }, () => []);
|
|
38
|
+
images.forEach((image, index) => {
|
|
39
|
+
// Round-robin: image at index N always goes to column (N % columnCount)
|
|
40
|
+
// This guarantees stable positions - items never move columns
|
|
41
|
+
const columnIndex = index % columnCount;
|
|
42
|
+
cols[columnIndex].push(image);
|
|
43
|
+
});
|
|
44
|
+
return cols;
|
|
45
|
+
}, [images, columnCount]);
|
|
46
|
+
return (_jsx("div", { ref: containerRef, className: "grid gap-4", style: {
|
|
47
|
+
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`,
|
|
48
|
+
}, children: columns.map((columnImages, columnIndex) => (_jsx("div", { className: "flex flex-col gap-4", children: columnImages.map((image, imageIndex) => {
|
|
49
|
+
var _a;
|
|
50
|
+
// Normalize both selectedImageId and image identifiers for matching
|
|
51
|
+
let normalizedSelectedId = selectedImageId;
|
|
52
|
+
let normalizedImageUrl = image.url;
|
|
53
|
+
let normalizedImageId = image.id;
|
|
54
|
+
let normalizedImageFilename = image.filename;
|
|
55
|
+
// Extract filename from selectedImageId if it's a URL
|
|
56
|
+
if (selectedImageId) {
|
|
57
|
+
if (selectedImageId.includes('/')) {
|
|
58
|
+
const urlParts = selectedImageId.split('/');
|
|
59
|
+
normalizedSelectedId = ((_a = urlParts[urlParts.length - 1]) === null || _a === void 0 ? void 0 : _a.split('?')[0]) || selectedImageId;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Normalize image URL (remove protocol, domain, query params)
|
|
63
|
+
if (image.url) {
|
|
64
|
+
// Remove protocol and domain if present
|
|
65
|
+
normalizedImageUrl = image.url.replace(/^https?:\/\/[^\/]+/, '');
|
|
66
|
+
// Remove query params
|
|
67
|
+
normalizedImageUrl = normalizedImageUrl.split('?')[0];
|
|
68
|
+
// Extract just the filename
|
|
69
|
+
const urlParts = normalizedImageUrl.split('/');
|
|
70
|
+
normalizedImageUrl = urlParts[urlParts.length - 1] || normalizedImageUrl;
|
|
71
|
+
}
|
|
72
|
+
// Check if image is selected by comparing all possible combinations
|
|
73
|
+
const isSelected = Boolean(selectedImageId && (
|
|
74
|
+
// Direct matches
|
|
75
|
+
selectedImageId === image.id ||
|
|
76
|
+
selectedImageId === image.url ||
|
|
77
|
+
selectedImageId === image.filename ||
|
|
78
|
+
// Normalized filename matches
|
|
79
|
+
normalizedSelectedId === image.id ||
|
|
80
|
+
normalizedSelectedId === image.filename ||
|
|
81
|
+
normalizedSelectedId === normalizedImageUrl ||
|
|
82
|
+
normalizedSelectedId === normalizedImageId ||
|
|
83
|
+
normalizedSelectedId === normalizedImageFilename ||
|
|
84
|
+
// URL contains/ends with matches
|
|
85
|
+
(image.url && selectedImageId && (image.url.includes(selectedImageId) ||
|
|
86
|
+
image.url.endsWith(selectedImageId) ||
|
|
87
|
+
selectedImageId.includes(image.url) ||
|
|
88
|
+
selectedImageId.endsWith(image.url) ||
|
|
89
|
+
(normalizedSelectedId && (image.url.includes(normalizedSelectedId) ||
|
|
90
|
+
image.url.endsWith(normalizedSelectedId))))) ||
|
|
91
|
+
// Filename matches
|
|
92
|
+
(image.filename && (image.filename === normalizedSelectedId ||
|
|
93
|
+
normalizedSelectedId === image.filename)) ||
|
|
94
|
+
// ID matches
|
|
95
|
+
(image.id && (image.id === normalizedSelectedId ||
|
|
96
|
+
normalizedSelectedId === image.id))));
|
|
97
|
+
// Use a unique key combining column index, image index, and image id
|
|
98
|
+
// This ensures uniqueness even if image.id is duplicated
|
|
99
|
+
const uniqueKey = `${columnIndex}-${imageIndex}-${image.id}-${image.url}`;
|
|
100
|
+
return (_jsx(ImageCard, { image: image, isSelected: isSelected, onSelect: () => {
|
|
101
|
+
onSelectImage(image);
|
|
102
|
+
onClose();
|
|
103
|
+
} }, uniqueKey));
|
|
104
|
+
}) }, columnIndex))) }));
|
|
105
|
+
}
|
|
106
|
+
// Image Card Component for Masonry Layout
|
|
107
|
+
function ImageCard({ image, isSelected, onSelect }) {
|
|
108
|
+
const [imageLoaded, setImageLoaded] = useState(false);
|
|
109
|
+
const [imageError, setImageError] = useState(false);
|
|
110
|
+
const imgRef = useRef(null);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
// Check if image is already loaded (cached)
|
|
113
|
+
const img = imgRef.current;
|
|
114
|
+
if (img) {
|
|
115
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
116
|
+
setImageLoaded(true);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Fallback: if image doesn't load within 5 seconds, show it anyway
|
|
120
|
+
const timeout = setTimeout(() => {
|
|
121
|
+
setImageLoaded(true);
|
|
122
|
+
}, 5000);
|
|
123
|
+
return () => clearTimeout(timeout);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, [image.url]);
|
|
127
|
+
const handleLoad = () => {
|
|
128
|
+
setImageLoaded(true);
|
|
129
|
+
setImageError(false);
|
|
130
|
+
};
|
|
131
|
+
const handleError = (e) => {
|
|
132
|
+
console.error('[ImageCard] Image failed to load:', image.url, e);
|
|
133
|
+
setImageError(true);
|
|
134
|
+
setImageLoaded(false);
|
|
135
|
+
};
|
|
136
|
+
return (_jsxs("button", { onClick: onSelect, className: `group relative w-full rounded-xl overflow-hidden bg-neutral-100 dark:bg-neutral-800 transition-all duration-200 active:scale-[0.98] shadow-sm hover:shadow-lg block ${isSelected ? 'ring-2 ring-primary ring-offset-1 ring-offset-white dark:ring-offset-neutral-900' : ''}`, children: [!imageError ? (_jsxs("div", { className: "relative w-full group/image", children: [_jsx("img", { ref: imgRef, src: image.url, alt: image.filename, onLoad: handleLoad, onError: handleError, className: `w-full h-auto object-cover transition-all duration-300 group-hover/image:scale-105 block ${!imageLoaded ? 'opacity-0' : 'opacity-100'}`, loading: "lazy" }), !imageLoaded && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-neutral-200 dark:bg-neutral-800 z-10", children: _jsx(Loader2, { size: 20, className: "text-neutral-400 animate-spin" }) }))] })) : (_jsxs("div", { className: "w-full aspect-square flex flex-col items-center justify-center bg-neutral-200 dark:bg-neutral-800 text-neutral-400", children: [_jsx(ImageIcon, { size: 24, className: "mb-2" }), _jsx("p", { className: "text-xs font-medium", children: "Failed to load" })] })), isSelected && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 border-2 border-primary rounded-xl pointer-events-none z-30 ring-2 ring-primary/20" }), _jsxs("div", { className: "absolute top-2 right-2 bg-primary text-white rounded-md px-2 py-1 shadow-md z-30 flex items-center gap-1.5 backdrop-blur-sm", children: [_jsx(Check, { size: 13, strokeWidth: 2.5, className: "flex-shrink-0" }), _jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wide", children: "Selected" })] }), _jsx("div", { className: "absolute inset-0 bg-primary/8 dark:bg-primary/12 rounded-xl pointer-events-none z-20" })] })), _jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-200 rounded-xl pointer-events-none" }), _jsx("div", { className: "absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/70 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200 rounded-b-xl pointer-events-none", children: _jsx("p", { className: "text-[10px] text-white font-semibold truncate leading-tight", children: image.filename }) })] }));
|
|
137
|
+
}
|
|
138
|
+
const ITEMS_PER_PAGE = 100; // Increased from 24 to load more images per request
|
|
139
|
+
export function ImageBrowserModal({ isOpen, onClose, onSelectImage, selectedImageId, darkMode = false, onUpload, uploading = false, }) {
|
|
140
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
141
|
+
const [externalUrl, setExternalUrl] = useState('');
|
|
142
|
+
const [images, setImages] = useState([]);
|
|
143
|
+
const [loading, setLoading] = useState(false);
|
|
144
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
145
|
+
const [page, setPage] = useState(1);
|
|
146
|
+
const [hasMore, setHasMore] = useState(true);
|
|
147
|
+
const [total, setTotal] = useState(0);
|
|
148
|
+
const [resolvedSelectedId, setResolvedSelectedId] = useState(selectedImageId);
|
|
149
|
+
const [internalUploading, setInternalUploading] = useState(false);
|
|
150
|
+
const [mounted, setMounted] = useState(false);
|
|
151
|
+
const [portalTarget, setPortalTarget] = useState(null);
|
|
152
|
+
// Handle SSR & portal target - ensure we only render portal on client
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
setMounted(true);
|
|
155
|
+
if (typeof document === 'undefined')
|
|
156
|
+
return;
|
|
157
|
+
// If we're inside the GlobalImageEditor overlay, portal into that instead of document.body
|
|
158
|
+
// This ensures the browser modal shares the same stacking context as the global editor
|
|
159
|
+
const editorContainer = document.querySelector('[data-image-editor="true"]');
|
|
160
|
+
if (editorContainer) {
|
|
161
|
+
setPortalTarget(editorContainer);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
setPortalTarget(document.body);
|
|
165
|
+
}
|
|
166
|
+
}, []);
|
|
167
|
+
// Debug: Log when selectedImageId prop changes
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
setResolvedSelectedId(selectedImageId);
|
|
170
|
+
}, [selectedImageId]);
|
|
171
|
+
// Load column count from localStorage, default to 3
|
|
172
|
+
const getStoredColumnCount = () => {
|
|
173
|
+
if (typeof window === 'undefined')
|
|
174
|
+
return 3;
|
|
175
|
+
try {
|
|
176
|
+
const stored = localStorage.getItem('plugin-images-column-count');
|
|
177
|
+
if (stored) {
|
|
178
|
+
const parsed = parseInt(stored, 10);
|
|
179
|
+
// Validate: must be between 3 and 9
|
|
180
|
+
if (!isNaN(parsed) && parsed >= 3 && parsed <= 9) {
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
// Silently fail - use default
|
|
187
|
+
}
|
|
188
|
+
return 3;
|
|
189
|
+
};
|
|
190
|
+
const [columnCount, setColumnCount] = useState(getStoredColumnCount);
|
|
191
|
+
// Save column count to localStorage whenever it changes
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (typeof window === 'undefined')
|
|
194
|
+
return;
|
|
195
|
+
try {
|
|
196
|
+
localStorage.setItem('plugin-images-column-count', columnCount.toString());
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
// Silently fail
|
|
200
|
+
}
|
|
201
|
+
}, [columnCount]);
|
|
202
|
+
const modalRef = useRef(null);
|
|
203
|
+
const scrollContainerRef = useRef(null);
|
|
204
|
+
const fileInputRef = useRef(null);
|
|
205
|
+
const searchTimeoutRef = useRef(null);
|
|
206
|
+
// Load images from API with pagination
|
|
207
|
+
const loadImages = useCallback(async (pageNum = 1, append = false) => {
|
|
208
|
+
if (append) {
|
|
209
|
+
setLoadingMore(true);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
setLoading(true);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const searchParam = searchQuery.trim() ? `&search=${encodeURIComponent(searchQuery.trim())}` : '';
|
|
216
|
+
const response = await fetch(`/api/plugin-images/list?page=${pageNum}&limit=${ITEMS_PER_PAGE}${searchParam}`);
|
|
217
|
+
if (response.ok) {
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
const newImages = (data.images || []).map((img) => {
|
|
220
|
+
const imageUrl = img.url || `/api/uploads/${img.filename}`;
|
|
221
|
+
// Ensure URL is absolute if it's a relative path
|
|
222
|
+
const finalUrl = imageUrl.startsWith('http') ? imageUrl : imageUrl.startsWith('/') ? imageUrl : `/${imageUrl}`;
|
|
223
|
+
return Object.assign(Object.assign({}, img), { url: finalUrl });
|
|
224
|
+
});
|
|
225
|
+
setTotal(data.total || 0);
|
|
226
|
+
if (append) {
|
|
227
|
+
setImages(prev => {
|
|
228
|
+
// Deduplicate: use a Map to track unique images by id
|
|
229
|
+
const imageMap = new Map();
|
|
230
|
+
// Add existing images
|
|
231
|
+
prev.forEach((img) => {
|
|
232
|
+
imageMap.set(img.id, img);
|
|
233
|
+
});
|
|
234
|
+
// Add new images (will overwrite duplicates)
|
|
235
|
+
newImages.forEach((img) => {
|
|
236
|
+
imageMap.set(img.id, img);
|
|
237
|
+
});
|
|
238
|
+
const updated = Array.from(imageMap.values());
|
|
239
|
+
// More images available if: we got a full page AND haven't loaded all images
|
|
240
|
+
setHasMore(newImages.length === ITEMS_PER_PAGE && updated.length < (data.total || 0));
|
|
241
|
+
return updated;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Deduplicate even for initial load
|
|
246
|
+
const imageMap = new Map();
|
|
247
|
+
newImages.forEach((img) => {
|
|
248
|
+
imageMap.set(img.id, img);
|
|
249
|
+
});
|
|
250
|
+
const deduplicated = Array.from(imageMap.values());
|
|
251
|
+
setImages(deduplicated);
|
|
252
|
+
// More images available if: we got a full page AND haven't loaded all images
|
|
253
|
+
setHasMore(deduplicated.length === ITEMS_PER_PAGE && deduplicated.length < (data.total || 0));
|
|
254
|
+
}
|
|
255
|
+
setPage(pageNum);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
console.error('[ImageBrowserModal] Failed to load images:', response.status);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
console.error('[ImageBrowserModal] Failed to load images:', error);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
setLoading(false);
|
|
266
|
+
setLoadingMore(false);
|
|
267
|
+
}
|
|
268
|
+
}, [searchQuery]);
|
|
269
|
+
// Load multiple pages in parallel for faster initial load
|
|
270
|
+
const loadInitialImages = useCallback(async () => {
|
|
271
|
+
setLoading(true);
|
|
272
|
+
try {
|
|
273
|
+
const searchParam = searchQuery.trim() ? `&search=${encodeURIComponent(searchQuery.trim())}` : '';
|
|
274
|
+
// Load first 2-3 pages in parallel to fill the screen quickly
|
|
275
|
+
const pagesToLoad = 3;
|
|
276
|
+
const requests = Array.from({ length: pagesToLoad }, (_, i) => fetch(`/api/plugin-images/list?page=${i + 1}&limit=${ITEMS_PER_PAGE}${searchParam}`));
|
|
277
|
+
const responses = await Promise.all(requests);
|
|
278
|
+
const results = await Promise.all(responses.map(r => r.ok ? r.json() : { images: [], total: 0 }));
|
|
279
|
+
// Combine all images
|
|
280
|
+
const allImages = [];
|
|
281
|
+
let totalCount = 0;
|
|
282
|
+
results.forEach((data, index) => {
|
|
283
|
+
if (data.images && data.images.length > 0) {
|
|
284
|
+
const pageImages = data.images.map((img) => {
|
|
285
|
+
const imageUrl = img.url || `/api/uploads/${img.filename}`;
|
|
286
|
+
const finalUrl = imageUrl.startsWith('http') ? imageUrl : imageUrl.startsWith('/') ? imageUrl : `/${imageUrl}`;
|
|
287
|
+
return Object.assign(Object.assign({}, img), { url: finalUrl });
|
|
288
|
+
});
|
|
289
|
+
allImages.push(...pageImages);
|
|
290
|
+
}
|
|
291
|
+
if (index === 0 && data.total) {
|
|
292
|
+
totalCount = data.total;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
// Deduplicate
|
|
296
|
+
const imageMap = new Map();
|
|
297
|
+
allImages.forEach((img) => {
|
|
298
|
+
imageMap.set(img.id, img);
|
|
299
|
+
});
|
|
300
|
+
const deduplicated = Array.from(imageMap.values());
|
|
301
|
+
setImages(deduplicated);
|
|
302
|
+
setTotal(totalCount);
|
|
303
|
+
setHasMore(deduplicated.length < totalCount);
|
|
304
|
+
setPage(pagesToLoad);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.error('[ImageBrowserModal] Error loading initial images:', error);
|
|
308
|
+
// Fallback to single page load
|
|
309
|
+
loadImages(1, false);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
setLoading(false);
|
|
313
|
+
}
|
|
314
|
+
}, [searchQuery, loadImages]);
|
|
315
|
+
// Load more images when scrolling near bottom
|
|
316
|
+
const handleScroll = useCallback(() => {
|
|
317
|
+
const container = scrollContainerRef.current;
|
|
318
|
+
if (!container || loadingMore || !hasMore || loading)
|
|
319
|
+
return;
|
|
320
|
+
const scrollTop = container.scrollTop;
|
|
321
|
+
const scrollHeight = container.scrollHeight;
|
|
322
|
+
const clientHeight = container.clientHeight;
|
|
323
|
+
// Calculate distance from bottom
|
|
324
|
+
const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
|
|
325
|
+
// Load more when within 200px of bottom
|
|
326
|
+
if (distanceFromBottom < 200) {
|
|
327
|
+
loadImages(page + 1, true);
|
|
328
|
+
}
|
|
329
|
+
}, [page, hasMore, loadingMore, loading, loadImages]);
|
|
330
|
+
// Check if viewport needs more images to fill the screen
|
|
331
|
+
const checkAndLoadMoreIfNeeded = useCallback(() => {
|
|
332
|
+
const container = scrollContainerRef.current;
|
|
333
|
+
if (!container || loading || loadingMore || !hasMore)
|
|
334
|
+
return;
|
|
335
|
+
// Check if content fills the viewport (with some margin)
|
|
336
|
+
const scrollHeight = container.scrollHeight;
|
|
337
|
+
const clientHeight = container.clientHeight;
|
|
338
|
+
const needsMoreContent = scrollHeight <= clientHeight + 100; // 100px margin
|
|
339
|
+
if (needsMoreContent) {
|
|
340
|
+
loadImages(page + 1, true);
|
|
341
|
+
}
|
|
342
|
+
}, [loading, loadingMore, hasMore, images.length, page, loadImages]);
|
|
343
|
+
// Auto-load more images when images change and viewport isn't filled
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (!isOpen || images.length === 0 || loading || loadingMore)
|
|
346
|
+
return;
|
|
347
|
+
// Wait for DOM to update after images are set and rendered
|
|
348
|
+
const timeoutId = setTimeout(() => {
|
|
349
|
+
checkAndLoadMoreIfNeeded();
|
|
350
|
+
}, 200); // Delay to allow images to render and layout to settle
|
|
351
|
+
return () => clearTimeout(timeoutId);
|
|
352
|
+
}, [images, isOpen, loading, loadingMore, checkAndLoadMoreIfNeeded]);
|
|
353
|
+
// Check again after loading completes
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
if (!isOpen || images.length === 0)
|
|
356
|
+
return;
|
|
357
|
+
if (!loading && !loadingMore) {
|
|
358
|
+
// Loading just finished, check if we need more
|
|
359
|
+
const timeoutId = setTimeout(() => {
|
|
360
|
+
checkAndLoadMoreIfNeeded();
|
|
361
|
+
}, 300); // Give images time to render
|
|
362
|
+
return () => clearTimeout(timeoutId);
|
|
363
|
+
}
|
|
364
|
+
}, [loading, loadingMore, isOpen, images.length, checkAndLoadMoreIfNeeded]);
|
|
365
|
+
// Also check when column count changes (more columns = need more images)
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
if (!isOpen || images.length === 0 || loading || loadingMore)
|
|
368
|
+
return;
|
|
369
|
+
const timeoutId = setTimeout(() => {
|
|
370
|
+
checkAndLoadMoreIfNeeded();
|
|
371
|
+
}, 200);
|
|
372
|
+
return () => clearTimeout(timeoutId);
|
|
373
|
+
}, [columnCount, isOpen, loading, loadingMore, checkAndLoadMoreIfNeeded]);
|
|
374
|
+
// Initial load and reset on open
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (isOpen) {
|
|
377
|
+
document.body.style.overflow = 'hidden';
|
|
378
|
+
setPage(1);
|
|
379
|
+
setImages([]);
|
|
380
|
+
setHasMore(true);
|
|
381
|
+
// Use parallel loading for faster initial load
|
|
382
|
+
loadInitialImages();
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
document.body.style.overflow = 'unset';
|
|
386
|
+
setSearchQuery('');
|
|
387
|
+
setImages([]);
|
|
388
|
+
setPage(1);
|
|
389
|
+
}
|
|
390
|
+
return () => { document.body.style.overflow = 'unset'; };
|
|
391
|
+
}, [isOpen, loadInitialImages, selectedImageId]);
|
|
392
|
+
// Debounced search
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
if (!isOpen)
|
|
395
|
+
return;
|
|
396
|
+
if (searchTimeoutRef.current) {
|
|
397
|
+
clearTimeout(searchTimeoutRef.current);
|
|
398
|
+
}
|
|
399
|
+
searchTimeoutRef.current = setTimeout(() => {
|
|
400
|
+
setPage(1);
|
|
401
|
+
setImages([]);
|
|
402
|
+
setHasMore(true);
|
|
403
|
+
// For search, use parallel loading if no search query (faster), otherwise single page
|
|
404
|
+
if (!searchQuery.trim()) {
|
|
405
|
+
loadInitialImages();
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
loadImages(1, false);
|
|
409
|
+
}
|
|
410
|
+
}, 300);
|
|
411
|
+
return () => {
|
|
412
|
+
if (searchTimeoutRef.current) {
|
|
413
|
+
clearTimeout(searchTimeoutRef.current);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}, [searchQuery, isOpen, loadImages, loadInitialImages]);
|
|
417
|
+
// Reload after upload
|
|
418
|
+
useEffect(() => {
|
|
419
|
+
if (!uploading && isOpen && images.length > 0) {
|
|
420
|
+
// Reload first page to show newly uploaded image
|
|
421
|
+
loadImages(1, false);
|
|
422
|
+
}
|
|
423
|
+
}, [uploading, isOpen, loadImages]);
|
|
424
|
+
// Attach scroll listener
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
const container = scrollContainerRef.current;
|
|
427
|
+
if (!container)
|
|
428
|
+
return;
|
|
429
|
+
container.addEventListener('scroll', handleScroll);
|
|
430
|
+
return () => container.removeEventListener('scroll', handleScroll);
|
|
431
|
+
}, [handleScroll]);
|
|
432
|
+
// Handle file upload (default handler if onUpload prop is not provided)
|
|
433
|
+
const handleFileUpload = async (file) => {
|
|
434
|
+
// If onUpload prop is provided, use it
|
|
435
|
+
if (onUpload) {
|
|
436
|
+
await onUpload(file);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
// Otherwise, handle upload internally
|
|
440
|
+
setInternalUploading(true);
|
|
441
|
+
try {
|
|
442
|
+
const formData = new FormData();
|
|
443
|
+
formData.append('file', file);
|
|
444
|
+
const response = await fetch('/api/plugin-images/upload', {
|
|
445
|
+
method: 'POST',
|
|
446
|
+
body: formData,
|
|
447
|
+
});
|
|
448
|
+
const data = await response.json();
|
|
449
|
+
if (data.success && data.image) {
|
|
450
|
+
// Reload images to show the newly uploaded image
|
|
451
|
+
await loadImages(1, false);
|
|
452
|
+
// Optionally select the newly uploaded image
|
|
453
|
+
// onSelectImage(data.image);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.error('[ImageBrowserModal] Upload failed:', data.error || 'Unknown error');
|
|
457
|
+
alert(data.error || 'Failed to upload image');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
console.error('[ImageBrowserModal] Upload error:', error);
|
|
462
|
+
alert('Failed to upload image');
|
|
463
|
+
}
|
|
464
|
+
finally {
|
|
465
|
+
setInternalUploading(false);
|
|
466
|
+
// Reset file input
|
|
467
|
+
if (fileInputRef.current) {
|
|
468
|
+
fileInputRef.current.value = '';
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
// 4. Handle External URL Logic
|
|
473
|
+
const handleUseExternalUrl = () => {
|
|
474
|
+
var _a;
|
|
475
|
+
const url = externalUrl.trim();
|
|
476
|
+
if (!url)
|
|
477
|
+
return;
|
|
478
|
+
const externalImage = {
|
|
479
|
+
id: `external-${Date.now()}`,
|
|
480
|
+
filename: ((_a = url.split('/').pop()) === null || _a === void 0 ? void 0 : _a.split('?')[0]) || 'external-image',
|
|
481
|
+
url: url,
|
|
482
|
+
size: 0,
|
|
483
|
+
mimeType: 'image/external',
|
|
484
|
+
uploadedAt: new Date().toISOString(),
|
|
485
|
+
};
|
|
486
|
+
onSelectImage(externalImage);
|
|
487
|
+
setExternalUrl('');
|
|
488
|
+
onClose();
|
|
489
|
+
};
|
|
490
|
+
if (!isOpen || !mounted || !portalTarget)
|
|
491
|
+
return null;
|
|
492
|
+
const modalContent = (_jsxs("div", { className: "fixed inset-0 z-[200] flex items-center justify-center p-4 animate-in fade-in duration-200", children: [_jsx("div", { className: "absolute inset-0 bg-neutral-950/70 backdrop-blur-md", onClick: onClose }), _jsxs("div", { ref: modalRef, className: `relative w-full max-w-6xl h-[90vh] bg-white dark:bg-neutral-900 rounded-3xl shadow-2xl flex flex-col overflow-hidden border border-neutral-200 dark:border-neutral-800 animate-in zoom-in-95 duration-300 ${darkMode ? 'dark' : ''}`, children: [_jsxs("div", { className: "px-6 py-5 border-b border-neutral-200 dark:border-neutral-800 bg-gradient-to-r from-neutral-50 to-white dark:from-neutral-900 dark:to-neutral-950 flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-black uppercase tracking-tight dark:text-white", children: "Media Library" }), total > 0 && (_jsxs("p", { className: "text-xs text-neutral-500 font-semibold uppercase tracking-wider mt-1", children: [total, " ", total === 1 ? 'image' : 'images'] }))] }), _jsx("button", { onClick: onClose, className: "p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-xl transition-all duration-200 hover:scale-110 active:scale-95", "aria-label": "Close", children: _jsx(X, { size: 20, className: "text-neutral-500 dark:text-neutral-300 hover:text-neutral-700 dark:hover:text-white transition-colors" }) })] }), _jsxs("div", { className: "px-6 py-4 bg-neutral-50/50 dark:bg-neutral-900/30 border-b border-neutral-200 dark:border-neutral-800 flex flex-col sm:flex-row gap-3", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { size: 16, className: "absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" }), _jsx("input", { type: "text", placeholder: "Search images...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full pl-10 pr-4 py-2.5 bg-white dark:bg-neutral-950 border border-neutral-200 dark:border-neutral-800 rounded-xl text-sm font-medium focus:ring-2 focus:ring-primary/30 focus:border-primary outline-none transition-all shadow-sm" })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center gap-3 bg-white dark:bg-neutral-950 border border-neutral-200 dark:border-neutral-800 rounded-xl px-4 py-2.5 shadow-sm", children: [_jsx(Grid3x3, { size: 16, className: "text-neutral-400 flex-shrink-0" }), _jsxs("div", { className: "flex items-center gap-3 min-w-[140px]", children: [_jsx("span", { className: "text-sm font-bold text-neutral-700 dark:text-neutral-300 min-w-[24px] text-right tabular-nums", children: columnCount }), _jsx("input", { type: "range", min: "3", max: "9", value: columnCount, onChange: (e) => setColumnCount(parseInt(e.target.value)), className: "flex-1 h-2 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-primary" })] }), _jsx("span", { className: "text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider", children: "cols" })] }), _jsxs("button", { onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: uploading || internalUploading, className: "flex items-center justify-center gap-2 px-5 py-2.5 bg-primary text-white rounded-xl font-semibold text-sm hover:bg-primary/90 disabled:opacity-50 transition-all duration-200 shadow-md hover:shadow-lg active:scale-[0.98]", children: [(uploading || internalUploading) ? _jsx(Loader2, { size: 16, className: "animate-spin" }) : _jsx(Upload, { size: 16 }), "Upload"] })] })] }), _jsx("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto p-6", style: { scrollbarWidth: 'thin' }, children: loading && images.length === 0 ? (_jsxs("div", { className: "h-full flex flex-col items-center justify-center space-y-4", children: [_jsx(Loader2, { size: 48, className: "text-primary animate-spin" }), _jsx("p", { className: "text-sm font-semibold text-neutral-500 uppercase tracking-wider", children: "Loading Library..." })] })) : images.length === 0 ? (_jsxs("div", { className: "h-full flex flex-col items-center justify-center text-center py-16", children: [_jsx("div", { className: "p-5 bg-gradient-to-br from-neutral-100 to-neutral-200 dark:from-neutral-800 dark:to-neutral-900 rounded-2xl mb-4 shadow-inner", children: _jsx(ImageIcon, { size: 36, className: "text-neutral-400" }) }), _jsx("h3", { className: "text-lg font-bold dark:text-white mb-2", children: "No images found" }), _jsx("p", { className: "text-sm text-neutral-500 max-w-sm", children: searchQuery.trim()
|
|
493
|
+
? 'Try a different search term or upload a new image.'
|
|
494
|
+
: 'Upload your first image to get started.' })] })) : (_jsxs(_Fragment, { children: [_jsx(MasonryGrid, { images: images, selectedImageId: resolvedSelectedId, onSelectImage: (image) => {
|
|
495
|
+
onSelectImage(image);
|
|
496
|
+
}, onClose: onClose, fixedColumns: columnCount }), loadingMore && (_jsx("div", { className: "flex justify-center items-center py-8", children: _jsx(Loader2, { size: 24, className: "text-primary animate-spin" }) })), !hasMore && images.length > 0 && (_jsx("div", { className: "text-center py-6", children: _jsxs("p", { className: "text-xs text-neutral-400 font-semibold uppercase tracking-wider", children: ["All ", total, " images loaded"] }) }))] })) }), _jsxs("div", { className: "px-6 py-4 bg-gradient-to-r from-neutral-50 to-white dark:from-neutral-900 dark:to-neutral-950 border-t border-neutral-200 dark:border-neutral-800 flex flex-col sm:flex-row items-center gap-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-neutral-400 min-w-max", children: [_jsx(LinkIcon, { size: 14 }), _jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider", children: "External URL" })] }), _jsxs("div", { className: "flex-1 flex w-full gap-2", children: [_jsx("input", { type: "text", placeholder: "Paste image URL here...", value: externalUrl, onChange: (e) => setExternalUrl(e.target.value), onKeyDown: (e) => e.key === 'Enter' && handleUseExternalUrl(), className: "flex-1 px-3 py-2 bg-white dark:bg-neutral-950 border border-neutral-200 dark:border-neutral-800 rounded-lg text-xs font-medium focus:ring-2 focus:ring-primary/30 focus:border-primary outline-none transition-all shadow-sm" }), _jsx("button", { onClick: handleUseExternalUrl, className: "px-4 py-2 bg-neutral-900 dark:bg-white dark:text-neutral-900 text-white rounded-lg text-xs font-semibold hover:opacity-90 transition-all duration-200 active:scale-[0.98]", children: "Import" })] })] }), _jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: (e) => {
|
|
497
|
+
var _a;
|
|
498
|
+
const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
499
|
+
if (file) {
|
|
500
|
+
handleFileUpload(file);
|
|
501
|
+
}
|
|
502
|
+
}, className: "hidden" })] })] }));
|
|
503
|
+
return createPortal(modalContent, portalTarget);
|
|
504
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ImageEditorProps {
|
|
3
|
+
imageUrl: string;
|
|
4
|
+
scale: number;
|
|
5
|
+
positionX: number;
|
|
6
|
+
positionY: number;
|
|
7
|
+
brightness?: number;
|
|
8
|
+
blur?: number;
|
|
9
|
+
onScaleChange: (scale: number) => void;
|
|
10
|
+
onPositionChange: (x: number, y: number) => void;
|
|
11
|
+
onBrightnessChange?: (brightness: number) => void;
|
|
12
|
+
onBlurChange?: (blur: number) => void;
|
|
13
|
+
aspectRatio?: string;
|
|
14
|
+
borderRadius?: string;
|
|
15
|
+
imageId?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ImageEditorHandle {
|
|
18
|
+
flushSave: () => Promise<{
|
|
19
|
+
scale: number;
|
|
20
|
+
positionX: number;
|
|
21
|
+
positionY: number;
|
|
22
|
+
brightness: number;
|
|
23
|
+
blur: number;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare const ImageEditor: React.ForwardRefExoticComponent<ImageEditorProps & React.RefAttributes<ImageEditorHandle>>;
|
|
27
|
+
//# sourceMappingURL=ImageEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageEditor.d.ts","sourceRoot":"","sources":["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"}
|