@jhits/plugin-images 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/api/list/index.d.ts +18 -0
  2. package/dist/api/list/index.d.ts.map +1 -1
  3. package/dist/api/list/index.js +121 -20
  4. package/dist/api/router.d.ts.map +1 -1
  5. package/dist/api/router.js +7 -0
  6. package/dist/api/usage/route.d.ts +23 -0
  7. package/dist/api/usage/route.d.ts.map +1 -0
  8. package/dist/api/usage/route.js +238 -0
  9. package/dist/components/BackgroundImage.d.ts.map +1 -1
  10. package/dist/components/BackgroundImage.js +5 -17
  11. package/dist/components/GlobalImageEditor.d.ts.map +1 -1
  12. package/dist/components/GlobalImageEditor.js +9 -4
  13. package/dist/components/Image.d.ts +3 -6
  14. package/dist/components/Image.d.ts.map +1 -1
  15. package/dist/components/Image.js +103 -206
  16. package/dist/components/ImageEditor.d.ts.map +1 -1
  17. package/dist/components/ImageEditor.js +21 -125
  18. package/dist/components/ImagePicker.d.ts.map +1 -1
  19. package/dist/components/ImagePicker.js +6 -59
  20. package/dist/utils/fallback.d.ts +9 -4
  21. package/dist/utils/fallback.d.ts.map +1 -1
  22. package/dist/utils/fallback.js +40 -12
  23. package/dist/utils/transforms.d.ts.map +1 -1
  24. package/dist/utils/transforms.js +7 -10
  25. package/dist/views/ImageManager/components/CleanupLibraryModal.d.ts +12 -0
  26. package/dist/views/ImageManager/components/CleanupLibraryModal.d.ts.map +1 -0
  27. package/dist/views/ImageManager/components/CleanupLibraryModal.js +7 -0
  28. package/dist/views/ImageManager/components/DeleteImageModal.d.ts +15 -0
  29. package/dist/views/ImageManager/components/DeleteImageModal.d.ts.map +1 -0
  30. package/dist/views/ImageManager/components/DeleteImageModal.js +8 -0
  31. package/dist/views/ImageManager/components/ImageGrid.d.ts +12 -0
  32. package/dist/views/ImageManager/components/ImageGrid.d.ts.map +1 -0
  33. package/dist/views/ImageManager/components/ImageGrid.js +15 -0
  34. package/dist/views/ImageManager/components/ImageManagerHeader.d.ts +11 -0
  35. package/dist/views/ImageManager/components/ImageManagerHeader.d.ts.map +1 -0
  36. package/dist/views/ImageManager/components/ImageManagerHeader.js +6 -0
  37. package/dist/views/ImageManager/components/ImageManagerStats.d.ts +8 -0
  38. package/dist/views/ImageManager/components/ImageManagerStats.d.ts.map +1 -0
  39. package/dist/views/ImageManager/components/ImageManagerStats.js +6 -0
  40. package/dist/views/ImageManager/components/ImageManagerToolbar.d.ts +9 -0
  41. package/dist/views/ImageManager/components/ImageManagerToolbar.d.ts.map +1 -0
  42. package/dist/views/ImageManager/components/ImageManagerToolbar.js +10 -0
  43. package/dist/views/ImageManager/components/ImageTable.d.ts +13 -0
  44. package/dist/views/ImageManager/components/ImageTable.d.ts.map +1 -0
  45. package/dist/views/ImageManager/components/ImageTable.js +13 -0
  46. package/dist/views/ImageManager/types.d.ts +26 -0
  47. package/dist/views/ImageManager/types.d.ts.map +1 -0
  48. package/dist/views/ImageManager/types.js +1 -0
  49. package/dist/views/ImageManager.d.ts +1 -1
  50. package/dist/views/ImageManager.d.ts.map +1 -1
  51. package/dist/views/ImageManager.js +206 -2
  52. package/package.json +10 -9
  53. package/src/api/list/index.ts +147 -22
  54. package/src/api/router.ts +8 -0
  55. package/src/api/usage/route.ts +294 -0
  56. package/src/components/BackgroundImage.tsx +5 -15
  57. package/src/components/GlobalImageEditor.tsx +9 -4
  58. package/src/components/Image.tsx +128 -268
  59. package/src/components/ImageEditor.tsx +31 -193
  60. package/src/components/ImagePicker.tsx +22 -107
  61. package/src/utils/fallback.ts +46 -13
  62. package/src/utils/transforms.ts +9 -12
  63. package/src/views/ImageManager/components/CleanupLibraryModal.tsx +96 -0
  64. package/src/views/ImageManager/components/DeleteImageModal.tsx +144 -0
  65. package/src/views/ImageManager/components/ImageGrid.tsx +119 -0
  66. package/src/views/ImageManager/components/ImageManagerHeader.tsx +72 -0
  67. package/src/views/ImageManager/components/ImageManagerStats.tsx +60 -0
  68. package/src/views/ImageManager/components/ImageManagerToolbar.tsx +60 -0
  69. package/src/views/ImageManager/components/ImageTable.tsx +120 -0
  70. package/src/views/ImageManager/types.ts +27 -0
  71. package/src/views/ImageManager.tsx +307 -12
  72. package/src/components/BackgroundImage.d.ts +0 -11
  73. package/src/components/BackgroundImage.d.ts.map +0 -1
  74. package/src/components/GlobalImageEditor/config.d.ts +0 -9
  75. package/src/components/GlobalImageEditor/config.d.ts.map +0 -1
  76. package/src/components/GlobalImageEditor/eventHandlers.d.ts +0 -20
  77. package/src/components/GlobalImageEditor/eventHandlers.d.ts.map +0 -1
  78. package/src/components/GlobalImageEditor/imageDetection.d.ts +0 -16
  79. package/src/components/GlobalImageEditor/imageDetection.d.ts.map +0 -1
  80. package/src/components/GlobalImageEditor/imageSetup.d.ts +0 -9
  81. package/src/components/GlobalImageEditor/imageSetup.d.ts.map +0 -1
  82. package/src/components/GlobalImageEditor/saveLogic.d.ts +0 -26
  83. package/src/components/GlobalImageEditor/saveLogic.d.ts.map +0 -1
  84. package/src/components/GlobalImageEditor/stylingDetection.d.ts +0 -9
  85. package/src/components/GlobalImageEditor/stylingDetection.d.ts.map +0 -1
  86. package/src/components/GlobalImageEditor/transformParsing.d.ts +0 -16
  87. package/src/components/GlobalImageEditor/transformParsing.d.ts.map +0 -1
  88. package/src/components/GlobalImageEditor/types.d.ts +0 -36
  89. package/src/components/GlobalImageEditor/types.d.ts.map +0 -1
  90. package/src/components/GlobalImageEditor.d.ts +0 -8
  91. package/src/components/GlobalImageEditor.d.ts.map +0 -1
  92. package/src/components/Image.d.ts +0 -22
  93. package/src/components/Image.d.ts.map +0 -1
  94. package/src/components/ImageBrowserModal.d.ts +0 -13
  95. package/src/components/ImageBrowserModal.d.ts.map +0 -1
  96. package/src/components/ImageEditor.d.ts +0 -27
  97. package/src/components/ImageEditor.d.ts.map +0 -1
  98. package/src/components/ImagePicker.d.ts +0 -3
  99. package/src/components/ImagePicker.d.ts.map +0 -1
  100. package/src/components/ImagesPluginInit.d.ts +0 -24
  101. package/src/components/ImagesPluginInit.d.ts.map +0 -1
  102. package/src/hooks/useImagePicker.d.ts +0 -20
  103. package/src/hooks/useImagePicker.d.ts.map +0 -1
  104. package/src/types/index.d.ts +0 -80
  105. package/src/types/index.d.ts.map +0 -1
  106. package/src/utils/fallback.d.ts +0 -27
  107. package/src/utils/fallback.d.ts.map +0 -1
  108. package/src/utils/transforms.d.ts +0 -26
  109. package/src/utils/transforms.d.ts.map +0 -1
  110. package/src/views/ImageManager.d.ts +0 -10
  111. package/src/views/ImageManager.d.ts.map +0 -1
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Search, Grid3x3, List } from 'lucide-react';
5
+
6
+ interface ImageManagerToolbarProps {
7
+ search: string;
8
+ setSearch: (value: string) => void;
9
+ viewMode: 'grid' | 'list';
10
+ setViewMode: (mode: 'grid' | 'list') => void;
11
+ }
12
+
13
+ export function ImageManagerToolbar({
14
+ search,
15
+ setSearch,
16
+ viewMode,
17
+ setViewMode
18
+ }: ImageManagerToolbarProps) {
19
+ return (
20
+ <div className="px-4">
21
+ <div className="flex flex-col sm:flex-row items-center gap-4 p-2 bg-dashboard-card/30 backdrop-blur-xl border border-dashboard-border/40 rounded-2xl shadow-sm">
22
+ <div className="relative flex-1 group w-full">
23
+ <Search className="absolute left-4 top-1/2 -translate-y-1/2 text-dashboard-text-secondary/40 group-focus-within:text-primary transition-colors duration-300" size={18} />
24
+ <input
25
+ type="text"
26
+ placeholder="Search assets by filename..."
27
+ value={search}
28
+ onChange={(e) => setSearch(e.target.value)}
29
+ className="w-full pl-12 pr-6 py-3 bg-transparent border-none outline-none text-sm font-semibold text-dashboard-text placeholder:text-dashboard-text-secondary/30"
30
+ />
31
+ </div>
32
+
33
+ <div className="flex items-center gap-1.5 p-1.5 bg-dashboard-bg/50 rounded-xl border border-dashboard-border/40">
34
+ <button
35
+ onClick={() => setViewMode('grid')}
36
+ className={`p-2.5 rounded-lg transition-all ${
37
+ viewMode === 'grid'
38
+ ? 'bg-primary text-white shadow-lg shadow-primary/20'
39
+ : 'text-dashboard-text-secondary hover:text-primary hover:bg-primary/5'
40
+ }`}
41
+ title="Grid View"
42
+ >
43
+ <Grid3x3 size={18} />
44
+ </button>
45
+ <button
46
+ onClick={() => setViewMode('list')}
47
+ className={`p-2.5 rounded-lg transition-all ${
48
+ viewMode === 'list'
49
+ ? 'bg-primary text-white shadow-lg shadow-primary/20'
50
+ : 'text-dashboard-text-secondary hover:text-primary hover:bg-primary/5'
51
+ }`}
52
+ title="List View"
53
+ >
54
+ <List size={18} />
55
+ </button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,120 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import Image from 'next/image';
5
+ import { Eye, Trash2, Activity, Circle, CheckCircle2 } from 'lucide-react';
6
+ import { ImageMetadata } from '../types';
7
+
8
+ interface ImageTableProps {
9
+ images: ImageMetadata[];
10
+ isImageInUse: (filename: string) => boolean;
11
+ getImageUsage: (filename: string) => any[];
12
+ formatFileSize: (bytes: number) => string;
13
+ formatDate: (date: string) => string;
14
+ onViewFull: (url: string) => void;
15
+ onDelete: (image: ImageMetadata) => void;
16
+ }
17
+
18
+ export function ImageTable({
19
+ images,
20
+ isImageInUse,
21
+ getImageUsage,
22
+ formatFileSize,
23
+ formatDate,
24
+ onViewFull,
25
+ onDelete
26
+ }: ImageTableProps) {
27
+ return (
28
+ <div className="bg-dashboard-card/40 rounded-2xl overflow-hidden border border-dashboard-border/40 shadow-sm">
29
+ <table className="w-full text-left border-collapse">
30
+ <thead>
31
+ <tr className="text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-widest border-b border-dashboard-border/40 bg-dashboard-bg/20">
32
+ <th className="px-8 py-5">Asset Reference</th>
33
+ <th className="px-8 py-5">File Size</th>
34
+ <th className="px-8 py-5">Uploaded</th>
35
+ <th className="px-8 py-5">Usage Status</th>
36
+ <th className="px-8 py-5 text-right">Actions</th>
37
+ </tr>
38
+ </thead>
39
+ <tbody className="divide-y divide-dashboard-border/30">
40
+ {images.map((image) => {
41
+ const usage = getImageUsage(image.filename);
42
+ const inUse = isImageInUse(image.filename);
43
+
44
+ return (
45
+ <tr key={image.id} className="group hover:bg-primary/[0.03] transition-colors">
46
+ <td className="px-8 py-4">
47
+ <div className="flex items-center gap-4">
48
+ <div className={`size-12 relative bg-dashboard-bg/50 rounded-xl overflow-hidden border transition-all ${
49
+ inUse ? 'border-primary/30' : 'border-dashboard-border/60'
50
+ }`}>
51
+ <Image
52
+ src={image.url}
53
+ alt={image.filename}
54
+ fill
55
+ className="object-cover"
56
+ unoptimized
57
+ />
58
+ </div>
59
+ <div className="min-w-0">
60
+ <p className="font-bold text-dashboard-text text-sm tracking-tight group-hover:text-primary transition-colors truncate max-w-[250px]" title={image.filename}>
61
+ {image.filename}
62
+ </p>
63
+ <p className="text-[9px] font-medium text-dashboard-text-secondary opacity-60 uppercase tracking-widest">Media Asset</p>
64
+ </div>
65
+ </div>
66
+ </td>
67
+ <td className="px-8 py-4">
68
+ <span className="text-xs font-semibold text-dashboard-text/70">
69
+ {formatFileSize(image.size)}
70
+ </span>
71
+ </td>
72
+ <td className="px-8 py-4">
73
+ <span className="text-xs font-semibold text-dashboard-text/70">
74
+ {formatDate(image.uploadedAt)}
75
+ </span>
76
+ </td>
77
+ <td className="px-8 py-4">
78
+ {inUse ? (
79
+ <span className="inline-flex items-center gap-2 px-3 py-1 bg-emerald-500/10 text-emerald-600 border border-emerald-500/20 rounded-full text-[9px] font-bold uppercase tracking-wider">
80
+ <CheckCircle2 size={10} />
81
+ Active{usage.length > 0 ? ` (${usage.length})` : ''}
82
+ </span>
83
+ ) : (
84
+ <span className="inline-flex items-center gap-2 px-3 py-1 bg-dashboard-bg/50 border border-dashboard-border/60 text-dashboard-text-secondary/60 rounded-full text-[9px] font-bold uppercase tracking-wider">
85
+ <Circle size={10} />
86
+ Unlinked
87
+ </span>
88
+ )}
89
+ </td>
90
+ <td className="px-8 py-4 text-right">
91
+ <div className="flex items-center justify-end gap-1.5">
92
+ <button
93
+ onClick={() => onViewFull(image.url)}
94
+ className="p-2.5 text-dashboard-text-secondary hover:text-primary hover:bg-primary/10 rounded-lg transition-all active:scale-90"
95
+ title="View Image"
96
+ >
97
+ <Eye size={18} />
98
+ </button>
99
+ <button
100
+ onClick={() => onDelete(image)}
101
+ className={`p-2.5 rounded-lg transition-all active:scale-90 ${
102
+ inUse
103
+ ? 'text-neutral-300 cursor-not-allowed opacity-20'
104
+ : 'text-dashboard-text-secondary hover:text-red-500 hover:bg-red-500/10'
105
+ }`}
106
+ disabled={inUse}
107
+ title="Delete Image"
108
+ >
109
+ <Trash2 size={18} />
110
+ </button>
111
+ </div>
112
+ </td>
113
+ </tr>
114
+ );
115
+ })}
116
+ </tbody>
117
+ </table>
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,27 @@
1
+ export interface ImageMetadata {
2
+ id: string;
3
+ filename: string;
4
+ url: string;
5
+ size: number;
6
+ mimeType: string;
7
+ uploadedAt: string;
8
+ }
9
+
10
+ export interface ImageUsage {
11
+ imageId: string;
12
+ filename: string;
13
+ usage: Array<{
14
+ plugin: string;
15
+ type: string;
16
+ title: string;
17
+ id: string;
18
+ url?: string;
19
+ }>;
20
+ }
21
+
22
+ export interface StorageStats {
23
+ totalImages: number;
24
+ totalSize: number;
25
+ usedImages: number;
26
+ availableImages: number;
27
+ }
@@ -1,30 +1,325 @@
1
1
  /**
2
2
  * Image Manager View
3
- * Main view for managing uploaded images
3
+ * Refactored modular interface for media management
4
4
  */
5
5
 
6
6
  'use client';
7
7
 
8
- import React from 'react';
8
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
9
+ import {
10
+ ImageIcon,
11
+ Loader2
12
+ } from 'lucide-react';
13
+
14
+ // Components
15
+ import { ImageManagerHeader } from './ImageManager/components/ImageManagerHeader';
16
+ import { ImageManagerStats } from './ImageManager/components/ImageManagerStats';
17
+ import { ImageManagerToolbar } from './ImageManager/components/ImageManagerToolbar';
18
+ import { ImageGrid } from './ImageManager/components/ImageGrid';
19
+ import { ImageTable } from './ImageManager/components/ImageTable';
20
+ import { DeleteImageModal } from './ImageManager/components/DeleteImageModal';
21
+ import { CleanupLibraryModal } from './ImageManager/components/CleanupLibraryModal';
22
+
23
+ // Types
24
+ import { ImageMetadata, ImageUsage, StorageStats } from './ImageManager/types';
9
25
 
10
26
  export interface ImageManagerViewProps {
11
27
  siteId: string;
12
28
  locale: string;
13
29
  }
14
30
 
31
+ function formatFileSize(bytes: number): string {
32
+ if (bytes === 0) return '0 B';
33
+ const k = 1024;
34
+ const sizes = ['B', 'KB', 'MB', 'GB'];
35
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
36
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
37
+ }
38
+
39
+ function formatDate(dateString: string): string {
40
+ const date = new Date(dateString);
41
+ return date.toLocaleDateString(undefined, {
42
+ year: 'numeric',
43
+ month: 'short',
44
+ day: 'numeric'
45
+ });
46
+ }
47
+
15
48
  export function ImageManagerView({ siteId, locale }: ImageManagerViewProps) {
49
+ const [images, setImages] = useState<ImageMetadata[]>([]);
50
+ const [usageData, setUsageData] = useState<Record<string, ImageUsage['usage']>>({});
51
+ const [stats, setStats] = useState<StorageStats | null>(null);
52
+ const [mappedImages, setMappedImages] = useState<Set<string>>(new Set());
53
+ const [loading, setLoading] = useState(true);
54
+ const [search, setSearch] = useState('');
55
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
56
+ const [uploading, setUploading] = useState(false);
57
+ const [selectedImage, setSelectedImage] = useState<ImageMetadata | null>(null);
58
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
59
+ const [showCleanupModal, setShowCleanupModal] = useState(false);
60
+ const [deleting, setDeleting] = useState(false);
61
+ const [cleaning, setCleaning] = useState(false);
62
+ const [refreshKey, setRefreshKey] = useState(0);
63
+
64
+ const fetchImages = useCallback(async () => {
65
+ try {
66
+ setLoading(true);
67
+ const response = await fetch('/api/plugin-images/list');
68
+ const data = await response.json();
69
+
70
+ if (data.images) {
71
+ setImages(data.images);
72
+ setStats(data.stats || null);
73
+ setMappedImages(new Set(data.mappedImages || []));
74
+ }
75
+ } catch (error) {
76
+ console.error('Failed to fetch images:', error);
77
+ } finally {
78
+ setLoading(false);
79
+ }
80
+ }, []);
81
+
82
+ const fetchUsageData = useCallback(async () => {
83
+ try {
84
+ const response = await fetch('/api/plugin-images/usage');
85
+ const data = await response.json();
86
+
87
+ if (data.images) {
88
+ const usageMap: Record<string, ImageUsage['usage']> = {};
89
+ data.images.forEach((img: ImageUsage) => {
90
+ usageMap[img.filename] = img.usage;
91
+ });
92
+ setUsageData(usageMap);
93
+ }
94
+ } catch (error) {
95
+ console.error('Failed to fetch usage data:', error);
96
+ }
97
+ }, []);
98
+
99
+ useEffect(() => {
100
+ fetchImages();
101
+ fetchUsageData();
102
+ }, [fetchImages, fetchUsageData, refreshKey]);
103
+
104
+ const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
105
+ const files = e.target.files;
106
+ if (!files || files.length === 0) return;
107
+
108
+ setUploading(true);
109
+
110
+ try {
111
+ for (const file of Array.from(files)) {
112
+ const formData = new FormData();
113
+ formData.append('file', file);
114
+
115
+ const response = await fetch('/api/plugin-images/upload', {
116
+ method: 'POST',
117
+ body: formData,
118
+ });
119
+
120
+ const data = await response.json();
121
+
122
+ if (data.success && data.image) {
123
+ setImages(prev => [data.image, ...prev]);
124
+ }
125
+ }
126
+ handleRefresh();
127
+ } catch (error) {
128
+ console.error('Upload failed:', error);
129
+ } finally {
130
+ setUploading(false);
131
+ if (e.target) e.target.value = '';
132
+ }
133
+ };
134
+
135
+ const handleDelete = async () => {
136
+ if (!selectedImage) return;
137
+
138
+ setDeleting(true);
139
+
140
+ try {
141
+ const response = await fetch(`/api/plugin-images/uploads/${selectedImage.filename}`, {
142
+ method: 'DELETE',
143
+ });
144
+
145
+ if (response.ok) {
146
+ setImages(prev => prev.filter(img => img.id !== selectedImage.id));
147
+ setShowDeleteModal(false);
148
+ setSelectedImage(null);
149
+ handleRefresh();
150
+ } else {
151
+ const error = await response.json();
152
+ alert(error.error || 'Failed to delete image');
153
+ }
154
+ } catch (error) {
155
+ console.error('Delete failed:', error);
156
+ alert('Failed to delete image');
157
+ } finally {
158
+ setDeleting(false);
159
+ }
160
+ };
161
+
162
+ const handleCleanup = async () => {
163
+ setCleaning(true);
164
+
165
+ try {
166
+ const unusedImages = images.filter(img => !isImageInUse(img.filename));
167
+ let deletedCount = 0;
168
+ let failedCount = 0;
169
+
170
+ for (const image of unusedImages) {
171
+ try {
172
+ const response = await fetch(`/api/plugin-images/uploads/${image.filename}`, {
173
+ method: 'DELETE',
174
+ });
175
+ if (response.ok) {
176
+ deletedCount++;
177
+ } else {
178
+ failedCount++;
179
+ }
180
+ } catch {
181
+ failedCount++;
182
+ }
183
+ }
184
+
185
+ if (deletedCount > 0) {
186
+ setImages(prev => prev.filter(img => isImageInUse(img.filename)));
187
+ handleRefresh();
188
+ }
189
+
190
+ setShowCleanupModal(false);
191
+
192
+ if (failedCount > 0) {
193
+ alert(`Deleted ${deletedCount} images. ${failedCount} failed.`);
194
+ } else {
195
+ alert(`Successfully deleted ${deletedCount} unused assets.`);
196
+ }
197
+ } catch (error) {
198
+ console.error('Cleanup failed:', error);
199
+ alert('Failed to cleanup assets');
200
+ } finally {
201
+ setCleaning(false);
202
+ }
203
+ };
204
+
205
+ const getUnusedImagesCount = (): number => {
206
+ return images.filter(img => !isImageInUse(img.filename)).length;
207
+ };
208
+
209
+ const handleRefresh = () => {
210
+ setRefreshKey(prev => prev + 1);
211
+ };
212
+
213
+ const filteredImages = useMemo(() => {
214
+ return images.filter(img =>
215
+ search === '' ||
216
+ img.filename.toLowerCase().includes(search.toLowerCase())
217
+ );
218
+ }, [images, search]);
219
+
220
+ const getImageUsage = (filename: string): ImageUsage['usage'] => {
221
+ return usageData[filename] || [];
222
+ };
223
+
224
+ const isImageMapped = (filename: string): boolean => {
225
+ return mappedImages.has(filename);
226
+ };
227
+
228
+ const isImageInUse = (filename: string): boolean => {
229
+ return isImageMapped(filename) || getImageUsage(filename).length > 0;
230
+ };
231
+
16
232
  return (
17
- <div className="min-h-screen bg-dashboard-bg p-8">
18
- <div className="max-w-7xl mx-auto">
19
- <h1 className="text-4xl font-black uppercase tracking-tighter text-dashboard-text mb-8">
20
- Image Manager
21
- </h1>
22
- <p className="text-sm text-neutral-600 dark:text-neutral-400 mb-8">
23
- This plugin provides image upload and management functionality.
24
- Use the ImagePicker component in other plugins to select images.
25
- </p>
233
+ <div className="w-full flex flex-col space-y-8 px-6 lg:px-10 py-6 lg:py-10 pb-10 bg-transparent">
234
+ <ImageManagerHeader
235
+ uploading={uploading}
236
+ unusedCount={getUnusedImagesCount()}
237
+ onUpload={handleUpload}
238
+ onRefresh={handleRefresh}
239
+ onShowCleanup={() => setShowCleanupModal(true)}
240
+ />
241
+
242
+ {stats && <ImageManagerStats stats={stats} formatFileSize={formatFileSize} />}
243
+
244
+ <ImageManagerToolbar
245
+ search={search}
246
+ setSearch={setSearch}
247
+ viewMode={viewMode}
248
+ setViewMode={setViewMode}
249
+ />
250
+
251
+ <div className="flex-1 px-4 min-h-[400px]">
252
+ {loading ? (
253
+ <div className="h-full flex items-center justify-center py-20">
254
+ <div className="flex flex-col items-center gap-4">
255
+ <Loader2 size={40} className="animate-spin text-primary opacity-40" />
256
+ <p className="text-[10px] font-bold text-primary uppercase tracking-widest animate-pulse">Syncing Library</p>
257
+ </div>
258
+ </div>
259
+ ) : filteredImages.length === 0 ? (
260
+ <div className="h-full flex flex-col items-center justify-center py-32 text-center">
261
+ <div className="size-24 bg-dashboard-card/40 rounded-3xl border border-dashed border-dashboard-border/50 flex items-center justify-center mb-6 relative">
262
+ <ImageIcon size={40} className="text-dashboard-text-secondary opacity-20" />
263
+ <div className="absolute inset-0 bg-primary/5 rounded-3xl animate-pulse" />
264
+ </div>
265
+ <h3 className="text-xl font-bold text-dashboard-text tracking-tight opacity-40 mb-1.5">
266
+ {search ? 'Reference Not Found' : 'Repository Empty'}
267
+ </h3>
268
+ <p className="text-xs text-dashboard-text-secondary font-medium opacity-60">
269
+ {search ? 'Try refining your search parameters' : 'Upload your first image to begin building your library'}
270
+ </p>
271
+ </div>
272
+ ) : viewMode === 'grid' ? (
273
+ <ImageGrid
274
+ images={filteredImages}
275
+ isImageInUse={isImageInUse}
276
+ getImageUsage={getImageUsage}
277
+ formatFileSize={formatFileSize}
278
+ onViewFull={(url) => window.open(url, '_blank')}
279
+ onDelete={(img) => {
280
+ setSelectedImage(img);
281
+ setShowDeleteModal(true);
282
+ }}
283
+ />
284
+ ) : (
285
+ <ImageTable
286
+ images={filteredImages}
287
+ isImageInUse={isImageInUse}
288
+ getImageUsage={getImageUsage}
289
+ formatFileSize={formatFileSize}
290
+ formatDate={formatDate}
291
+ onViewFull={(url) => window.open(url, '_blank')}
292
+ onDelete={(img) => {
293
+ setSelectedImage(img);
294
+ setShowDeleteModal(true);
295
+ }}
296
+ />
297
+ )}
26
298
  </div>
299
+
300
+ <DeleteImageModal
301
+ isOpen={showDeleteModal}
302
+ image={selectedImage}
303
+ inUse={selectedImage ? isImageInUse(selectedImage.filename) : false}
304
+ usage={selectedImage ? getImageUsage(selectedImage.filename) : []}
305
+ isDeleting={deleting}
306
+ formatFileSize={formatFileSize}
307
+ formatDate={formatDate}
308
+ onClose={() => {
309
+ setShowDeleteModal(false);
310
+ setSelectedImage(null);
311
+ }}
312
+ onConfirm={handleDelete}
313
+ />
314
+
315
+ <CleanupLibraryModal
316
+ isOpen={showCleanupModal}
317
+ stats={stats}
318
+ unusedCount={getUnusedImagesCount()}
319
+ isCleaning={cleaning}
320
+ onClose={() => setShowCleanupModal(false)}
321
+ onConfirm={handleCleanup}
322
+ />
27
323
  </div>
28
324
  );
29
325
  }
30
-
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- export interface BackgroundImageProps {
3
- id: string;
4
- className?: string;
5
- style?: React.CSSProperties;
6
- children?: React.ReactNode;
7
- backgroundSize?: 'cover' | 'contain' | 'auto' | string;
8
- backgroundPosition?: string;
9
- }
10
- export declare function BackgroundImage({ id, className, style, children, backgroundSize, backgroundPosition, }: BackgroundImageProps): import("react/jsx-runtime").JSX.Element;
11
- //# sourceMappingURL=BackgroundImage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BackgroundImage.d.ts","sourceRoot":"","sources":["BackgroundImage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,MAAM,WAAW,oBAAoB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,eAAe,CAAC,EAC5B,EAAE,EACF,SAAc,EACd,KAAU,EACV,QAAQ,EACR,cAAwB,EACxB,kBAA6B,GAChC,EAAE,oBAAoB,2CAqEtB"}
@@ -1,9 +0,0 @@
1
- /**
2
- * Configuration utilities for GlobalImageEditor
3
- */
4
- import type { PluginConfig } from './types';
5
- /**
6
- * Read plugin configuration from window global
7
- */
8
- export declare function getPluginConfig(): PluginConfig;
9
- //# sourceMappingURL=config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAW9C"}
@@ -1,20 +0,0 @@
1
- /**
2
- * Event handlers for image changes and effects
3
- */
4
- import type { SelectedImage } from './types';
5
- import type { ImageMetadata } from '../../types';
6
- /**
7
- * Apply image changes to DOM and save to API
8
- */
9
- export declare function handleImageChange(image: ImageMetadata | null, selectedImage: SelectedImage, onClose: () => void): Promise<void>;
10
- /**
11
- * Handle brightness change
12
- * @param saveImmediately - If true, save to API immediately. If false, only update state/DOM (for editor preview)
13
- */
14
- export declare function handleBrightnessChange(brightness: number, selectedImage: SelectedImage, setSelectedImage: (image: SelectedImage) => void, saveImmediately?: boolean): Promise<void>;
15
- /**
16
- * Handle blur change
17
- * @param saveImmediately - If true, save to API immediately. If false, only update state/DOM (for editor preview)
18
- */
19
- export declare function handleBlurChange(blur: number, selectedImage: SelectedImage, setSelectedImage: (image: SelectedImage) => void, saveImmediately?: boolean): Promise<void>;
20
- //# sourceMappingURL=eventHandlers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"eventHandlers.d.ts","sourceRoot":"","sources":["eventHandlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,aAAa,GAAG,IAAI,EAC3B,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,MAAM,IAAI,GACpB,OAAO,CAAC,IAAI,CAAC,CAiDf;AAoID;;;GAGG;AACH,wBAAsB,sBAAsB,CACxC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,aAAa,EAC5B,gBAAgB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAChD,eAAe,GAAE,OAAc,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAClC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,gBAAgB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAChD,eAAe,GAAE,OAAc,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -1,16 +0,0 @@
1
- /**
2
- * Image detection and parsing utilities
3
- */
4
- import type { SelectedImage } from './types';
5
- /**
6
- * Extract image source from element (handles Next.js Image wrapper)
7
- */
8
- export declare function getImageSource(element: HTMLElement, isBackground: boolean): {
9
- currentSrc: string;
10
- actualImgElement: HTMLImageElement | null;
11
- };
12
- /**
13
- * Parse image data from element to create SelectedImage
14
- */
15
- export declare function parseImageData(element: HTMLElement, id: string, currentBrightness?: number, currentBlur?: number): SelectedImage | null;
16
- //# sourceMappingURL=imageDetection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imageDetection.d.ts","sourceRoot":"","sources":["imageDetection.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,GAAG;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC7C,CAmCA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,WAAW,EACpB,EAAE,EAAE,MAAM,EACV,iBAAiB,CAAC,EAAE,MAAM,EAC1B,WAAW,CAAC,EAAE,MAAM,GACrB,aAAa,GAAG,IAAI,CAoGtB"}
@@ -1,9 +0,0 @@
1
- /**
2
- * Image setup and click handlers
3
- */
4
- import type { SelectedImage } from './types';
5
- /**
6
- * Setup click handlers and hover effects for all images
7
- */
8
- export declare function setupImageHandlers(onImageClick: (image: SelectedImage) => void): () => void;
9
- //# sourceMappingURL=imageSetup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imageSetup.d.ts","sourceRoot":"","sources":["imageSetup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAC7C,MAAM,IAAI,CAmSZ"}
@@ -1,26 +0,0 @@
1
- /**
2
- * Save logic for image transforms and effects
3
- */
4
- import type { SelectedImage, PendingTransform } from './types';
5
- /**
6
- * Normalize position values - convert -50% (centering value) to 0
7
- */
8
- export declare function normalizePosition(position: number): number;
9
- /**
10
- * Get filename from element or API
11
- */
12
- export declare function getFilename(semanticId: string, selectedImage: SelectedImage): Promise<string | null>;
13
- /**
14
- * Save image transform to API
15
- */
16
- export declare function saveTransformToAPI(semanticId: string, filename: string, scale: number, positionX: number, positionY: number, brightness: number, blur: number): Promise<boolean>;
17
- /**
18
- * Flush pending save immediately
19
- */
20
- export declare function flushPendingSave(pending: PendingTransform, selectedImage: SelectedImage): Promise<void>;
21
- /**
22
- * Save image transform with debouncing support
23
- * This is a wrapper that handles the debouncing logic
24
- */
25
- export declare function saveImageTransform(semanticId: string, filename: string, scale: number, positionX: number, positionY: number, brightness: number, blur: number): Promise<void>;
26
- //# sourceMappingURL=saveLogic.d.ts.map