@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.
- package/dist/api/list/index.d.ts +18 -0
- package/dist/api/list/index.d.ts.map +1 -1
- package/dist/api/list/index.js +121 -20
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +7 -0
- package/dist/api/usage/route.d.ts +23 -0
- package/dist/api/usage/route.d.ts.map +1 -0
- package/dist/api/usage/route.js +238 -0
- package/dist/components/BackgroundImage.d.ts.map +1 -1
- package/dist/components/BackgroundImage.js +5 -17
- package/dist/components/GlobalImageEditor.d.ts.map +1 -1
- package/dist/components/GlobalImageEditor.js +9 -4
- package/dist/components/Image.d.ts +3 -6
- package/dist/components/Image.d.ts.map +1 -1
- package/dist/components/Image.js +103 -206
- package/dist/components/ImageEditor.d.ts.map +1 -1
- package/dist/components/ImageEditor.js +21 -125
- package/dist/components/ImagePicker.d.ts.map +1 -1
- package/dist/components/ImagePicker.js +6 -59
- package/dist/utils/fallback.d.ts +9 -4
- package/dist/utils/fallback.d.ts.map +1 -1
- package/dist/utils/fallback.js +40 -12
- package/dist/utils/transforms.d.ts.map +1 -1
- package/dist/utils/transforms.js +7 -10
- package/dist/views/ImageManager/components/CleanupLibraryModal.d.ts +12 -0
- package/dist/views/ImageManager/components/CleanupLibraryModal.d.ts.map +1 -0
- package/dist/views/ImageManager/components/CleanupLibraryModal.js +7 -0
- package/dist/views/ImageManager/components/DeleteImageModal.d.ts +15 -0
- package/dist/views/ImageManager/components/DeleteImageModal.d.ts.map +1 -0
- package/dist/views/ImageManager/components/DeleteImageModal.js +8 -0
- package/dist/views/ImageManager/components/ImageGrid.d.ts +12 -0
- package/dist/views/ImageManager/components/ImageGrid.d.ts.map +1 -0
- package/dist/views/ImageManager/components/ImageGrid.js +15 -0
- package/dist/views/ImageManager/components/ImageManagerHeader.d.ts +11 -0
- package/dist/views/ImageManager/components/ImageManagerHeader.d.ts.map +1 -0
- package/dist/views/ImageManager/components/ImageManagerHeader.js +6 -0
- package/dist/views/ImageManager/components/ImageManagerStats.d.ts +8 -0
- package/dist/views/ImageManager/components/ImageManagerStats.d.ts.map +1 -0
- package/dist/views/ImageManager/components/ImageManagerStats.js +6 -0
- package/dist/views/ImageManager/components/ImageManagerToolbar.d.ts +9 -0
- package/dist/views/ImageManager/components/ImageManagerToolbar.d.ts.map +1 -0
- package/dist/views/ImageManager/components/ImageManagerToolbar.js +10 -0
- package/dist/views/ImageManager/components/ImageTable.d.ts +13 -0
- package/dist/views/ImageManager/components/ImageTable.d.ts.map +1 -0
- package/dist/views/ImageManager/components/ImageTable.js +13 -0
- package/dist/views/ImageManager/types.d.ts +26 -0
- package/dist/views/ImageManager/types.d.ts.map +1 -0
- package/dist/views/ImageManager/types.js +1 -0
- package/dist/views/ImageManager.d.ts +1 -1
- package/dist/views/ImageManager.d.ts.map +1 -1
- package/dist/views/ImageManager.js +206 -2
- package/package.json +10 -9
- package/src/api/list/index.ts +147 -22
- package/src/api/router.ts +8 -0
- package/src/api/usage/route.ts +294 -0
- package/src/components/BackgroundImage.tsx +5 -15
- package/src/components/GlobalImageEditor.tsx +9 -4
- package/src/components/Image.tsx +128 -268
- package/src/components/ImageEditor.tsx +31 -193
- package/src/components/ImagePicker.tsx +22 -107
- package/src/utils/fallback.ts +46 -13
- package/src/utils/transforms.ts +9 -12
- package/src/views/ImageManager/components/CleanupLibraryModal.tsx +96 -0
- package/src/views/ImageManager/components/DeleteImageModal.tsx +144 -0
- package/src/views/ImageManager/components/ImageGrid.tsx +119 -0
- package/src/views/ImageManager/components/ImageManagerHeader.tsx +72 -0
- package/src/views/ImageManager/components/ImageManagerStats.tsx +60 -0
- package/src/views/ImageManager/components/ImageManagerToolbar.tsx +60 -0
- package/src/views/ImageManager/components/ImageTable.tsx +120 -0
- package/src/views/ImageManager/types.ts +27 -0
- package/src/views/ImageManager.tsx +307 -12
- package/src/components/BackgroundImage.d.ts +0 -11
- package/src/components/BackgroundImage.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/config.d.ts +0 -9
- package/src/components/GlobalImageEditor/config.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/eventHandlers.d.ts +0 -20
- package/src/components/GlobalImageEditor/eventHandlers.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/imageDetection.d.ts +0 -16
- package/src/components/GlobalImageEditor/imageDetection.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/imageSetup.d.ts +0 -9
- package/src/components/GlobalImageEditor/imageSetup.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/saveLogic.d.ts +0 -26
- package/src/components/GlobalImageEditor/saveLogic.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/stylingDetection.d.ts +0 -9
- package/src/components/GlobalImageEditor/stylingDetection.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/transformParsing.d.ts +0 -16
- package/src/components/GlobalImageEditor/transformParsing.d.ts.map +0 -1
- package/src/components/GlobalImageEditor/types.d.ts +0 -36
- package/src/components/GlobalImageEditor/types.d.ts.map +0 -1
- package/src/components/GlobalImageEditor.d.ts +0 -8
- package/src/components/GlobalImageEditor.d.ts.map +0 -1
- package/src/components/Image.d.ts +0 -22
- package/src/components/Image.d.ts.map +0 -1
- package/src/components/ImageBrowserModal.d.ts +0 -13
- package/src/components/ImageBrowserModal.d.ts.map +0 -1
- package/src/components/ImageEditor.d.ts +0 -27
- package/src/components/ImageEditor.d.ts.map +0 -1
- package/src/components/ImagePicker.d.ts +0 -3
- package/src/components/ImagePicker.d.ts.map +0 -1
- package/src/components/ImagesPluginInit.d.ts +0 -24
- package/src/components/ImagesPluginInit.d.ts.map +0 -1
- package/src/hooks/useImagePicker.d.ts +0 -20
- package/src/hooks/useImagePicker.d.ts.map +0 -1
- package/src/types/index.d.ts +0 -80
- package/src/types/index.d.ts.map +0 -1
- package/src/utils/fallback.d.ts +0 -27
- package/src/utils/fallback.d.ts.map +0 -1
- package/src/utils/transforms.d.ts +0 -26
- package/src/utils/transforms.d.ts.map +0 -1
- package/src/views/ImageManager.d.ts +0 -10
- package/src/views/ImageManager.d.ts.map +0 -1
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect,
|
|
3
|
+
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { Search, Image as ImageIcon, Settings } from 'lucide-react';
|
|
6
6
|
import { ImageEditor } from './ImageEditor';
|
|
7
7
|
import { ImageBrowserModal } from './ImageBrowserModal';
|
|
8
8
|
import { useImagePicker } from '../hooks/useImagePicker';
|
|
9
|
-
import {
|
|
9
|
+
import { getImageFilter } from '../utils/transforms';
|
|
10
10
|
import { getFallbackImageUrl } from '../utils/fallback';
|
|
11
11
|
export function ImagePicker({ value, onChange, brightness = 100, blur = 0, scale = 1.0, positionX = 0, positionY = 0, aspectRatio = '16/9', borderRadius = 'rounded-xl', onBrightnessChange, onBlurChange, onScaleChange, onPositionXChange, onPositionYChange, onEditorSave, autoOpenEditor = false, }) {
|
|
12
12
|
const [transforms, setTransforms] = useState({ scale: scale >= 0.1 ? scale : 1.0, positionX, positionY });
|
|
13
13
|
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
|
14
14
|
const [isBrowserOpen, setIsBrowserOpen] = useState(false);
|
|
15
|
-
const [previewBaseScale, setPreviewBaseScale] = useState(null);
|
|
16
15
|
const [previewImageError, setPreviewImageError] = useState(false);
|
|
17
16
|
const [mounted, setMounted] = useState(false);
|
|
18
17
|
const [portalTarget, setPortalTarget] = useState(null);
|
|
19
|
-
const previewImageRef = useRef(null);
|
|
20
18
|
const previewContainerRef = useRef(null);
|
|
21
19
|
const editorRef = useRef(null);
|
|
22
20
|
const { selectedImage, setSelectedImage } = useImagePicker({ value, images: [] });
|
|
23
|
-
// Reset preview error when selected image changes
|
|
24
21
|
useEffect(() => {
|
|
25
22
|
setPreviewImageError(false);
|
|
26
23
|
}, [selectedImage?.id, selectedImage?.url]);
|
|
27
|
-
// Handle SSR & portal target - ensure we only render portal on client
|
|
28
24
|
useEffect(() => {
|
|
29
25
|
setMounted(true);
|
|
30
26
|
if (typeof document === 'undefined')
|
|
31
27
|
return;
|
|
32
|
-
// If we're inside the GlobalImageEditor overlay, portal into that instead of document.body
|
|
33
|
-
// This ensures the browser/editor modals share the same stacking context
|
|
34
28
|
const editorContainer = previewContainerRef.current?.closest('[data-image-editor="true"]');
|
|
35
29
|
if (editorContainer) {
|
|
36
30
|
setPortalTarget(editorContainer);
|
|
@@ -39,19 +33,15 @@ export function ImagePicker({ value, onChange, brightness = 100, blur = 0, scale
|
|
|
39
33
|
setPortalTarget(document.body);
|
|
40
34
|
}
|
|
41
35
|
}, []);
|
|
42
|
-
// Auto-open editor if requested (e.g., when opening from edit button)
|
|
43
|
-
// Only auto-open once when the prop first becomes true, not on every render
|
|
44
36
|
const hasAutoOpenedRef = useRef(false);
|
|
45
37
|
useEffect(() => {
|
|
46
38
|
if (autoOpenEditor && selectedImage && !isEditorOpen && !hasAutoOpenedRef.current) {
|
|
47
|
-
// Small delay to ensure ImagePicker is fully rendered
|
|
48
39
|
const timer = setTimeout(() => {
|
|
49
40
|
setIsEditorOpen(true);
|
|
50
41
|
hasAutoOpenedRef.current = true;
|
|
51
42
|
}, 100);
|
|
52
43
|
return () => clearTimeout(timer);
|
|
53
44
|
}
|
|
54
|
-
// Reset the flag when autoOpenEditor becomes false
|
|
55
45
|
if (!autoOpenEditor) {
|
|
56
46
|
hasAutoOpenedRef.current = false;
|
|
57
47
|
}
|
|
@@ -60,45 +50,21 @@ export function ImagePicker({ value, onChange, brightness = 100, blur = 0, scale
|
|
|
60
50
|
if (!isEditorOpen)
|
|
61
51
|
setTransforms({ scale: scale >= 0.1 ? scale : 1.0, positionX, positionY });
|
|
62
52
|
}, [scale, positionX, positionY, isEditorOpen]);
|
|
63
|
-
const calculatePreviewBaseScale = useCallback(() => {
|
|
64
|
-
if (!previewImageRef.current || !previewContainerRef.current)
|
|
65
|
-
return;
|
|
66
|
-
const fit = Math.max(previewContainerRef.current.offsetWidth / previewImageRef.current.naturalWidth, previewContainerRef.current.offsetHeight / previewImageRef.current.naturalHeight);
|
|
67
|
-
setPreviewBaseScale(fit);
|
|
68
|
-
}, []);
|
|
69
|
-
// Handle editor save - delegate to parent callbacks instead of saving directly
|
|
70
|
-
// This ensures all saves go through a single location (GlobalImageEditor.saveImageTransform)
|
|
71
53
|
const handleEditorSave = async () => {
|
|
72
54
|
if (!editorRef.current || !selectedImage)
|
|
73
55
|
return;
|
|
74
56
|
try {
|
|
75
|
-
// 1. Get current values from Editor UI (this will also call onBrightnessChange and onBlurChange)
|
|
76
57
|
const final = await editorRef.current.flushSave();
|
|
77
|
-
// 2. Normalize position values - if they're -50% (centering value), treat as 0
|
|
78
58
|
const normalizedPositionX = final.positionX === -50 ? 0 : final.positionX;
|
|
79
59
|
const normalizedPositionY = final.positionY === -50 ? 0 : final.positionY;
|
|
80
|
-
// 3. If onEditorSave is provided, use it exclusively to prevent duplicate saves
|
|
81
|
-
// Otherwise, update parent state through individual callbacks
|
|
82
|
-
console.log('[ImagePicker] handleEditorSave - final values:', final, 'has onEditorSave:', !!onEditorSave);
|
|
83
60
|
if (onEditorSave) {
|
|
84
|
-
// onEditorSave handles everything - don't call individual handlers to avoid duplicates
|
|
85
|
-
console.log('[ImagePicker] Calling onEditorSave with:', { scale: final.scale, positionX: normalizedPositionX, positionY: normalizedPositionY, brightness: final.brightness, blur: final.blur });
|
|
86
61
|
onEditorSave(final.scale, normalizedPositionX, normalizedPositionY, final.brightness, final.blur);
|
|
87
62
|
}
|
|
88
63
|
else {
|
|
89
|
-
// Fallback: update parent state through individual callbacks
|
|
90
|
-
// Since onEditorSave is not provided, call all callbacks and they should handle saving
|
|
91
|
-
// The last one (onPositionYChange) will trigger the final save
|
|
92
|
-
console.log('[ImagePicker] No onEditorSave, using individual callbacks');
|
|
93
|
-
// Update scale first
|
|
94
64
|
onScaleChange?.(final.scale);
|
|
95
|
-
// Update positions - these will trigger saves
|
|
96
65
|
onPositionXChange?.(normalizedPositionX);
|
|
97
|
-
// Last callback - this should trigger the final save with all values
|
|
98
|
-
// We need to ensure this saves immediately with all final values including brightness/blur
|
|
99
66
|
onPositionYChange?.(normalizedPositionY);
|
|
100
67
|
}
|
|
101
|
-
// 5. Close the editor
|
|
102
68
|
setIsEditorOpen(false);
|
|
103
69
|
}
|
|
104
70
|
catch (error) {
|
|
@@ -111,32 +77,13 @@ export function ImagePicker({ value, onChange, brightness = 100, blur = 0, scale
|
|
|
111
77
|
const [w, h] = aspectRatio.split('/').map(Number);
|
|
112
78
|
return w / h;
|
|
113
79
|
}, [aspectRatio]);
|
|
114
|
-
return (_jsxs("div", { className: "space-y-6", children: [selectedImage ? (_jsx("div", { className: "relative group max-w-md mx-auto", children: _jsxs("div", { className: `relative ${borderRadius} overflow-hidden border border-neutral-200 dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900/50 shadow-lg transition-all duration-300 hover:shadow-xl`, style: { aspectRatio: aspectValue, width: '100%' }, children: [_jsx("div", { ref: previewContainerRef, className: "relative w-full h-full overflow-hidden", children:
|
|
115
|
-
top: '50%', left: '50%', width: 'auto', height: 'auto',
|
|
116
|
-
minWidth: '100%', minHeight: '100%',
|
|
117
|
-
transform: 'translate(-50%, -50%)',
|
|
118
|
-
transformOrigin: 'center center',
|
|
119
|
-
objectFit: 'cover',
|
|
120
|
-
} })) : (_jsx("img", { ref: previewImageRef, src: selectedImage.url, alt: selectedImage.filename, className: "absolute max-w-none", onLoad: calculatePreviewBaseScale, onError: () => setPreviewImageError(true), style: {
|
|
121
|
-
top: '50%', left: '50%', width: 'auto', height: 'auto',
|
|
122
|
-
minWidth: '100%', minHeight: '100%',
|
|
80
|
+
return (_jsxs("div", { className: "space-y-6", children: [selectedImage ? (_jsx("div", { className: "relative group max-w-md mx-auto", children: _jsxs("div", { className: `relative ${borderRadius} overflow-hidden border border-neutral-200 dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900/50 shadow-lg transition-all duration-300 hover:shadow-xl`, style: { aspectRatio: aspectValue, width: '100%' }, children: [_jsx("div", { ref: previewContainerRef, className: "relative w-full h-full overflow-hidden", children: _jsx("img", { src: previewImageError ? getFallbackImageUrl() : selectedImage.url, alt: selectedImage.filename, onError: () => setPreviewImageError(true), className: "w-full h-full object-cover", style: {
|
|
123
81
|
filter: getImageFilter(brightness, blur),
|
|
124
|
-
transform:
|
|
82
|
+
transform: `translate(${transforms.positionX}%, ${transforms.positionY}%) scale(${transforms.scale})`,
|
|
125
83
|
transformOrigin: 'center center',
|
|
126
|
-
} })
|
|
127
|
-
// Only update local state during drag - don't trigger saves
|
|
128
|
-
// Saves will happen when editor closes via onEditorSave
|
|
129
|
-
setTransforms(t => ({ ...t, positionX: x, positionY: y }));
|
|
130
|
-
}, onBrightnessChange: onBrightnessChange, onBlurChange: onBlurChange, aspectRatio: aspectRatio, borderRadius: borderRadius }) }), _jsxs("div", { className: "px-6 py-5 border-t border-neutral-200 dark:border-neutral-800 bg-neutral-50/80 dark:bg-neutral-900/80 backdrop-blur-sm flex justify-end gap-3", children: [_jsx("button", { onClick: () => setIsEditorOpen(false), className: "px-6 py-2.5 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl font-semibold text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-all duration-200 shadow-sm hover:shadow-md active:scale-[0.98]", children: "Cancel" }), _jsx("button", { onClick: handleEditorSave, className: "px-8 py-2.5 bg-primary text-white rounded-xl font-semibold shadow-md hover:shadow-lg hover:bg-primary/90 transition-all duration-200 active:scale-[0.98]", children: "Done" })] })] }) }), portalTarget), _jsx(ImageBrowserModal, { isOpen: isBrowserOpen, onClose: () => setIsBrowserOpen(false), onSelectImage: (image) => {
|
|
84
|
+
} }) }), _jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/5 transition-colors duration-300 pointer-events-none" })] }) })) : (_jsxs("div", { onClick: () => setIsBrowserOpen(true), className: "aspect-video bg-gradient-to-br from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-950 rounded-2xl border-2 border-dashed border-neutral-300 dark:border-neutral-800 flex flex-col items-center justify-center text-neutral-400 hover:border-primary hover:text-primary cursor-pointer transition-all duration-300 hover:shadow-lg group", children: [_jsx("div", { className: "p-4 bg-white/50 dark:bg-neutral-800/50 rounded-full mb-3 group-hover:scale-110 transition-transform duration-300", children: _jsx(ImageIcon, { size: 28, className: "group-hover:scale-110 transition-transform duration-300" }) }), _jsx("span", { className: "text-xs font-bold uppercase tracking-[0.15em] group-hover:tracking-[0.2em] transition-all duration-300", children: "Select Image" })] })), _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [_jsxs("button", { onClick: () => setIsBrowserOpen(true), className: "flex items-center justify-center gap-2 px-5 py-2.5 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl text-sm font-semibold text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-750 hover:border-neutral-300 dark:hover:border-neutral-600 transition-all duration-200 shadow-sm hover:shadow-md active:scale-[0.98]", children: [_jsx(Search, { size: 16 }), " Browse"] }), _jsxs("button", { onClick: () => setIsEditorOpen(true), disabled: !selectedImage, className: "flex items-center justify-center gap-2 px-5 py-2.5 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl text-sm font-semibold text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-750 hover:border-neutral-300 dark:hover:border-neutral-600 transition-all duration-200 shadow-sm hover:shadow-md active:scale-[0.98] disabled:opacity-40", children: [_jsx(Settings, { size: 16 }), " Edit"] })] }), isEditorOpen && selectedImage && mounted && portalTarget && createPortal(_jsx("div", { className: "fixed inset-0 z-[200] flex items-center justify-center bg-neutral-950/80 dark:bg-neutral-950/90 backdrop-blur-md p-4 animate-in fade-in duration-200", children: _jsxs("div", { className: "w-full max-w-4xl bg-white dark:bg-neutral-900 rounded-3xl overflow-hidden shadow-2xl flex flex-col max-h-[85vh] border border-neutral-200 dark:border-neutral-800 animate-in zoom-in-95 duration-300", children: [_jsx("div", { className: "flex-1 overflow-hidden p-4 lg:p-6", children: _jsx(ImageEditor, { ref: editorRef, imageUrl: selectedImage.url, scale: transforms.scale, positionX: transforms.positionX, positionY: transforms.positionY, brightness: brightness, blur: blur, onScaleChange: (s) => setTransforms(t => ({ ...t, scale: s })), onPositionChange: (x, y) => setTransforms(t => ({ ...t, positionX: x, positionY: y })), onBrightnessChange: onBrightnessChange, onBlurChange: onBlurChange, aspectRatio: aspectRatio, borderRadius: borderRadius }) }), _jsxs("div", { className: "px-6 py-5 border-t border-neutral-200 dark:border-neutral-800 bg-neutral-50/80 dark:bg-neutral-900/80 backdrop-blur-sm flex justify-end gap-3", children: [_jsx("button", { onClick: () => setIsEditorOpen(false), className: "px-6 py-2.5 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl font-semibold text-neutral-700 dark:text-neutral-200", children: "Cancel" }), _jsx("button", { onClick: handleEditorSave, className: "px-8 py-2.5 bg-primary text-white rounded-xl font-semibold shadow-md", children: "Done" })] })] }) }), portalTarget), _jsx(ImageBrowserModal, { isOpen: isBrowserOpen, onClose: () => setIsBrowserOpen(false), onSelectImage: (image) => {
|
|
131
85
|
setSelectedImage(image);
|
|
132
86
|
onChange?.(image);
|
|
133
87
|
setIsBrowserOpen(false);
|
|
134
|
-
}, selectedImageId:
|
|
135
|
-
// Use resolved image's filename/URL if available, otherwise fall back to value
|
|
136
|
-
// This ensures semantic IDs are resolved to actual filenames for matching
|
|
137
|
-
if (selectedImage) {
|
|
138
|
-
return selectedImage.filename || selectedImage.url || selectedImage.id || value;
|
|
139
|
-
}
|
|
140
|
-
return value;
|
|
141
|
-
})(), darkMode: false })] }));
|
|
88
|
+
}, selectedImageId: selectedImage?.filename || selectedImage?.url || selectedImage?.id || value, darkMode: false })] }));
|
|
142
89
|
}
|
package/dist/utils/fallback.d.ts
CHANGED
|
@@ -4,18 +4,23 @@
|
|
|
4
4
|
* Also handles URL construction for image filenames
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* Returns the URL for the fallback "image not found" image
|
|
8
|
-
*
|
|
7
|
+
* Returns the URL for the fallback "image not found" image.
|
|
8
|
+
* Uses a static file from public/ so it works without any API/dashboard dependency.
|
|
9
9
|
*/
|
|
10
10
|
export declare function getFallbackImageUrl(): string;
|
|
11
11
|
/**
|
|
12
12
|
* Constructs the full image URL from a filename or URL
|
|
13
13
|
* - If it's already a full URL (http://, https://, or starts with /), returns as-is
|
|
14
|
-
* - If it's a filename, constructs
|
|
14
|
+
* - If it's a filename, constructs relative URL to /api/uploads/
|
|
15
15
|
*/
|
|
16
16
|
export declare function constructImageUrl(src: string | null | undefined): string | null;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Constructs a direct static URL for the image, using STATIC_IMAGE_BASE_URL if available.
|
|
19
|
+
* This is intended for use as a fallback when API-driven serving fails.
|
|
20
|
+
*/
|
|
21
|
+
export declare function constructStaticImageUrl(filename: string | null | undefined): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Validates if a URL is valid
|
|
19
24
|
*/
|
|
20
25
|
export declare function isValidImageUrl(url: string | null | undefined): boolean;
|
|
21
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../src/utils/fallback.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../src/utils/fallback.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAsB/E;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAU1F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAsBvE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAUtE"}
|
package/dist/utils/fallback.js
CHANGED
|
@@ -3,31 +3,56 @@
|
|
|
3
3
|
* Provides a default fallback image URL when images fail to load or are invalid
|
|
4
4
|
* Also handles URL construction for image filenames
|
|
5
5
|
*/
|
|
6
|
+
// If set, this URL will be used as a fallback for static images, served directly by Nginx/CDN.
|
|
7
|
+
// Example: NEXT_PUBLIC_STATIC_IMAGE_BASE_URL=https://cdn.yourdomain.com/static-uploads
|
|
8
|
+
const STATIC_IMAGE_BASE_URL = process.env.NEXT_PUBLIC_STATIC_IMAGE_BASE_URL || '';
|
|
6
9
|
/**
|
|
7
|
-
* Returns the URL for the fallback "image not found" image
|
|
8
|
-
*
|
|
10
|
+
* Returns the URL for the fallback "image not found" image.
|
|
11
|
+
* Uses a static file from public/ so it works without any API/dashboard dependency.
|
|
9
12
|
*/
|
|
10
13
|
export function getFallbackImageUrl() {
|
|
11
|
-
return
|
|
14
|
+
return `/noimagefound.jpg`;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Constructs the full image URL from a filename or URL
|
|
15
18
|
* - If it's already a full URL (http://, https://, or starts with /), returns as-is
|
|
16
|
-
* - If it's a filename, constructs
|
|
19
|
+
* - If it's a filename, constructs relative URL to /api/uploads/
|
|
17
20
|
*/
|
|
18
21
|
export function constructImageUrl(src) {
|
|
19
22
|
if (!src || typeof src !== 'string') {
|
|
20
23
|
return null;
|
|
21
24
|
}
|
|
22
|
-
// If it's already
|
|
23
|
-
if (src.startsWith('http://') || src.startsWith('https://')
|
|
25
|
+
// If it's already an absolute URL (http:// or https://), return as-is
|
|
26
|
+
if (src.startsWith('http://') || src.startsWith('https://')) {
|
|
24
27
|
return src;
|
|
25
28
|
}
|
|
26
|
-
//
|
|
29
|
+
// If it starts with /api/uploads, return as-is (already relative API path)
|
|
30
|
+
if (src.startsWith('/api/uploads/')) {
|
|
31
|
+
return src;
|
|
32
|
+
}
|
|
33
|
+
// If it starts with / but is not an API path, it might be a local asset, keep as-is
|
|
34
|
+
if (src.startsWith('/')) {
|
|
35
|
+
return src;
|
|
36
|
+
}
|
|
37
|
+
// Otherwise, it's a raw filename - construct the relative API URL
|
|
27
38
|
return `/api/uploads/${src}`;
|
|
28
39
|
}
|
|
29
40
|
/**
|
|
30
|
-
*
|
|
41
|
+
* Constructs a direct static URL for the image, using STATIC_IMAGE_BASE_URL if available.
|
|
42
|
+
* This is intended for use as a fallback when API-driven serving fails.
|
|
43
|
+
*/
|
|
44
|
+
export function constructStaticImageUrl(filename) {
|
|
45
|
+
if (!filename || !STATIC_IMAGE_BASE_URL) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
// Ensure filename doesn't start with /api/uploads/ if it was passed through from constructImageUrl
|
|
49
|
+
const cleanFilename = filename.startsWith('/api/uploads/') ? filename.replace('/api/uploads/', '') : filename;
|
|
50
|
+
// Ensure base URL ends with a slash
|
|
51
|
+
const baseUrl = STATIC_IMAGE_BASE_URL.endsWith('/') ? STATIC_IMAGE_BASE_URL : `${STATIC_IMAGE_BASE_URL}/`;
|
|
52
|
+
return `${baseUrl}${cleanFilename}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates if a URL is valid
|
|
31
56
|
*/
|
|
32
57
|
export function isValidImageUrl(url) {
|
|
33
58
|
if (!url || typeof url !== 'string') {
|
|
@@ -35,13 +60,16 @@ export function isValidImageUrl(url) {
|
|
|
35
60
|
}
|
|
36
61
|
// Check if it's a valid URL format
|
|
37
62
|
try {
|
|
38
|
-
//
|
|
63
|
+
// Absolute URLs (including http/https)
|
|
64
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
65
|
+
new URL(url);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
// Relative URLs starting with /
|
|
39
69
|
if (url.startsWith('/')) {
|
|
40
70
|
return true;
|
|
41
71
|
}
|
|
42
|
-
|
|
43
|
-
new URL(url);
|
|
44
|
-
return true;
|
|
72
|
+
return false;
|
|
45
73
|
}
|
|
46
74
|
catch {
|
|
47
75
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/utils/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,
|
|
1
|
+
{"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/utils/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,CAgBR;AAED,wBAAgB,cAAc,CAAC,UAAU,GAAE,MAAY,EAAE,IAAI,GAAE,MAAU,GAAG,MAAM,GAAG,SAAS,CAG7F"}
|
package/dist/utils/transforms.js
CHANGED
|
@@ -17,19 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export function getImageTransform(options, needsCentering = false, caller) {
|
|
19
19
|
const { scale, positionX, positionY, baseScale = 1 } = options;
|
|
20
|
+
// We combine baseScale (which brings image to min-cover size) with user's zoom scale
|
|
20
21
|
const totalScale = baseScale * scale;
|
|
21
|
-
//
|
|
22
|
+
// Center via translate -50% -50% (if top:50% left:50% is used)
|
|
22
23
|
const center = needsCentering ? 'translate(-50%, -50%)' : '';
|
|
23
|
-
//
|
|
24
|
-
//
|
|
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
|
|
24
|
+
// Position offset (stored as % of container)
|
|
25
|
+
// We apply this AFTER scaling so it moves relative to the scaled visual size
|
|
29
26
|
const offset = `translate(${positionX}%, ${positionY}%)`;
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
return `${center} ${
|
|
27
|
+
// Order: Center -> Move -> Scale
|
|
28
|
+
// This ensures position is relative to visual container space
|
|
29
|
+
return `${center} ${offset} scale(${totalScale})`.trim();
|
|
33
30
|
}
|
|
34
31
|
export function getImageFilter(brightness = 100, blur = 0) {
|
|
35
32
|
if (brightness === 100 && blur === 0)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StorageStats } from '../types';
|
|
2
|
+
interface CleanupLibraryModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
stats: StorageStats | null;
|
|
5
|
+
unusedCount: number;
|
|
6
|
+
isCleaning: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
onConfirm: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function CleanupLibraryModal({ isOpen, stats, unusedCount, isCleaning, onClose, onConfirm }: CleanupLibraryModalProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=CleanupLibraryModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CleanupLibraryModal.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/CleanupLibraryModal.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,UAAU,wBAAwB;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,mBAAmB,CAAC,EAChC,MAAM,EACN,KAAK,EACL,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACZ,EAAE,wBAAwB,2CAwE1B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { X, Zap, Loader2 } from 'lucide-react';
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
5
|
+
export function CleanupLibraryModal({ isOpen, stats, unusedCount, isCleaning, onClose, onConfirm }) {
|
|
6
|
+
return (_jsx(AnimatePresence, { children: isOpen && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-black/60 backdrop-blur-md", children: _jsxs(motion.div, { initial: { opacity: 0, scale: 0.9, y: 20 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.9, y: 20 }, className: "glass-panel w-full max-w-lg rounded-[3rem] shadow-2xl overflow-hidden flex flex-col border border-white/10 dark:border-black/10", children: [_jsxs("div", { className: "p-10 pb-6 flex justify-between items-center shrink-0", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter leading-none mb-2", children: "Library Optimization" }), _jsx("p", { className: "text-[10px] font-black text-amber-500 uppercase tracking-[0.2em]", children: "Maintenance Mode" })] }), _jsx("button", { onClick: onClose, className: "hover:rotate-90 hover:text-red-500 transition-all p-3 bg-dashboard-bg rounded-2xl border border-dashboard-border active:scale-90", children: _jsx(X, { size: 24 }) })] }), _jsxs("div", { className: "px-10 pb-10 space-y-8", children: [_jsxs("div", { className: "flex items-center gap-4 p-6 bg-amber-500/5 border border-amber-500/20 rounded-3xl", children: [_jsx(Zap, { className: "text-amber-500 shrink-0", size: 32 }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-black text-amber-500 uppercase tracking-tight", children: "Mass Deletion Warning" }), _jsxs("p", { className: "text-xs text-dashboard-text-secondary font-medium mt-1 leading-relaxed italic", children: [unusedCount, " standalone assets will be purged from the cloud repository."] })] })] }), _jsxs("div", { className: "p-8 bg-dashboard-bg/50 border border-dashboard-border rounded-[2rem] space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-[10px] font-black text-dashboard-text-secondary uppercase tracking-widest opacity-60", children: "Repository Total" }), _jsx("span", { className: "text-sm font-black text-dashboard-text", children: stats?.totalImages || 0 })] }), _jsxs("div", { className: "flex items-center justify-between border-t border-dashboard-border/30 pt-4", children: [_jsx("span", { className: "text-[10px] font-black text-primary uppercase tracking-widest", children: "Active Assets" }), _jsx("span", { className: "text-sm font-black text-primary", children: stats?.usedImages || 0 })] }), _jsxs("div", { className: "flex items-center justify-between border-t border-dashboard-border/30 pt-4", children: [_jsx("span", { className: "text-[10px] font-black text-red-500 uppercase tracking-widest", children: "Purge Bundle" }), _jsx("span", { className: "text-sm font-black text-red-500", children: unusedCount })] })] }), _jsxs("div", { className: "flex gap-4 pt-4", children: [_jsx("button", { onClick: onClose, className: "flex-1 py-4 bg-dashboard-bg text-dashboard-text-secondary border border-dashboard-border rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] hover:bg-dashboard-card transition-all active:scale-[0.98]", children: "Abort" }), _jsxs("button", { onClick: onConfirm, disabled: isCleaning, className: "flex-1 py-4 bg-red-500 text-white rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] shadow-xl shadow-red-500/20 hover:bg-red-600 transition-all active:scale-[0.98] flex items-center justify-center gap-3", children: [isCleaning && _jsx(Loader2, { size: 16, className: "animate-spin" }), "Initialize Purge"] })] })] })] }) })) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ImageMetadata } from '../types';
|
|
2
|
+
interface DeleteImageModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
image: ImageMetadata | null;
|
|
5
|
+
inUse: boolean;
|
|
6
|
+
usage: any[];
|
|
7
|
+
isDeleting: boolean;
|
|
8
|
+
formatFileSize: (bytes: number) => string;
|
|
9
|
+
formatDate: (date: string) => string;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
onConfirm: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function DeleteImageModal({ isOpen, image, inUse, usage, isDeleting, formatFileSize, formatDate, onClose, onConfirm }: DeleteImageModalProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=DeleteImageModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeleteImageModal.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/DeleteImageModal.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,UAAU,qBAAqB;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,KAAK,EACL,KAAK,EACL,UAAU,EACV,cAAc,EACd,UAAU,EACV,OAAO,EACP,SAAS,EACZ,EAAE,qBAAqB,2CAiHvB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Image from 'next/image';
|
|
4
|
+
import { X, AlertTriangle, ExternalLink, Loader2 } from 'lucide-react';
|
|
5
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
6
|
+
export function DeleteImageModal({ isOpen, image, inUse, usage, isDeleting, formatFileSize, formatDate, onClose, onConfirm }) {
|
|
7
|
+
return (_jsx(AnimatePresence, { children: isOpen && image && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-black/60 backdrop-blur-md", children: _jsxs(motion.div, { initial: { opacity: 0, scale: 0.9, y: 20 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.9, y: 20 }, className: "glass-panel w-full max-w-lg rounded-[3rem] shadow-2xl overflow-hidden flex flex-col border border-white/10 dark:border-black/10", children: [_jsxs("div", { className: "p-10 pb-6 flex justify-between items-center shrink-0", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter leading-none mb-2", children: "Purge Asset" }), _jsx("p", { className: "text-[10px] font-black text-red-500 uppercase tracking-[0.2em]", children: "Destructive Action" })] }), _jsx("button", { onClick: onClose, className: "hover:rotate-90 hover:text-red-500 transition-all p-3 bg-dashboard-bg rounded-2xl border border-dashboard-border active:scale-90", children: _jsx(X, { size: 24 }) })] }), _jsxs("div", { className: "px-10 pb-10 space-y-8", children: [inUse ? (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center gap-4 p-6 bg-red-500/5 border border-red-500/20 rounded-3xl", children: [_jsx(AlertTriangle, { className: "text-red-500 shrink-0", size: 32 }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-black text-red-500 uppercase tracking-tight", children: "System Integrity Violation" }), _jsx("p", { className: "text-xs text-dashboard-text-secondary font-medium mt-1 leading-relaxed italic", children: "This asset is actively connected to content and cannot be purged while linked." })] })] }), (usage.length > 0) && (_jsxs("div", { className: "space-y-4", children: [_jsx("p", { className: "text-[10px] font-black text-dashboard-text-secondary uppercase tracking-[0.2em] ml-1", children: "Active Connections:" }), _jsx("div", { className: "space-y-3 max-h-[250px] overflow-y-auto pr-2 custom-scrollbar", children: usage.map((item, idx) => (_jsxs("div", { className: "flex items-center justify-between p-4 bg-dashboard-bg/50 border border-dashboard-border rounded-2xl group hover:border-primary/30 transition-all", children: [_jsxs("div", { children: [_jsx("p", { className: "text-xs font-black text-dashboard-text uppercase tracking-tight", children: item.title }), _jsxs("p", { className: "text-[9px] font-bold text-dashboard-text-secondary uppercase tracking-widest opacity-60 mt-0.5", children: [item.plugin, " \u2022 ", item.type] })] }), item.url && (_jsx("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", className: "p-3 bg-dashboard-card text-primary rounded-xl border border-dashboard-border group-hover:bg-primary group-hover:text-white transition-all shadow-sm", children: _jsx(ExternalLink, { size: 14 }) }))] }, idx))) })] }))] })) : (_jsxs("div", { className: "space-y-8", children: [_jsxs("div", { className: "flex items-center gap-6 p-6 bg-dashboard-bg/50 border border-dashboard-border rounded-[2rem]", children: [_jsx("div", { className: "size-24 relative bg-dashboard-card rounded-2xl overflow-hidden border border-dashboard-border shadow-inner", children: _jsx(Image, { src: image.url, alt: image.filename, fill: true, className: "object-cover", unoptimized: true }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-lg font-black text-dashboard-text uppercase tracking-tight truncate", children: image.filename }), _jsxs("div", { className: "flex flex-col gap-1 mt-1", children: [_jsx("span", { className: "text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-widest opacity-60", children: formatFileSize(image.size) }), _jsx("span", { className: "text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-widest opacity-60", children: formatDate(image.uploadedAt) })] })] })] }), _jsx("p", { className: "text-sm text-dashboard-text-secondary font-medium leading-relaxed text-center italic px-4 opacity-70", children: "Are you certain you wish to permanently purge this asset from the repository? This operation is irreversible." })] })), _jsxs("div", { className: "flex gap-4 pt-4 border-t border-dashboard-border/50", children: [_jsx("button", { onClick: onClose, className: "flex-1 py-4 bg-dashboard-bg text-dashboard-text-secondary border border-dashboard-border rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] hover:bg-dashboard-card hover:text-dashboard-text transition-all active:scale-[0.98]", children: "Abort Protocol" }), !inUse && (_jsxs("button", { onClick: onConfirm, disabled: isDeleting, className: "flex-1 py-4 bg-red-500 text-white rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] shadow-xl shadow-red-500/20 hover:bg-red-600 transition-all active:scale-[0.98] flex items-center justify-center gap-3", children: [isDeleting && _jsx(Loader2, { size: 16, className: "animate-spin" }), "Confirm Purge"] }))] })] })] }) })) }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ImageMetadata } from '../types';
|
|
2
|
+
interface ImageGridProps {
|
|
3
|
+
images: ImageMetadata[];
|
|
4
|
+
isImageInUse: (filename: string) => boolean;
|
|
5
|
+
getImageUsage: (filename: string) => any[];
|
|
6
|
+
formatFileSize: (bytes: number) => string;
|
|
7
|
+
onViewFull: (url: string) => void;
|
|
8
|
+
onDelete: (image: ImageMetadata) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function ImageGrid({ images, isImageInUse, getImageUsage, formatFileSize, onViewFull, onDelete }: ImageGridProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=ImageGrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageGrid.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/ImageGrid.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,UAAU,cAAc;IACpB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;IAC3C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,SAAS,CAAC,EACtB,MAAM,EACN,YAAY,EACZ,aAAa,EACb,cAAc,EACd,UAAU,EACV,QAAQ,EACX,EAAE,cAAc,2CA+FhB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Maximize2, Trash2, CheckCircle2 } from 'lucide-react';
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
5
|
+
export function ImageGrid({ images, isImageInUse, getImageUsage, formatFileSize, onViewFull, onDelete }) {
|
|
6
|
+
return (_jsx("div", { className: "columns-2 sm:columns-3 lg:columns-4 xl:columns-5 gap-6 space-y-6", children: _jsx(AnimatePresence, { mode: "popLayout", children: images.map((image, index) => {
|
|
7
|
+
const usage = getImageUsage(image.filename);
|
|
8
|
+
const inUse = isImageInUse(image.filename);
|
|
9
|
+
return (_jsx(motion.div, { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.3, delay: index * 0.02 }, className: "break-inside-avoid mb-6 group relative", children: _jsxs("div", { className: `relative bg-dashboard-card/40 rounded-2xl overflow-hidden border transition-all duration-500 shadow-sm group-hover:shadow-xl group-hover:shadow-primary/10 ${inUse
|
|
10
|
+
? 'border-dashboard-border/40'
|
|
11
|
+
: 'border-dashboard-border/60 group-hover:border-primary/30'}`, children: [_jsx("img", { src: image.url, alt: image.filename, className: "w-full h-auto object-cover group-hover:scale-105 transition-transform duration-700 block", crossOrigin: "anonymous", loading: "lazy" }), inUse && (_jsxs("div", { className: "absolute top-3 right-3 px-2.5 py-1 bg-emerald-500/90 backdrop-blur-sm text-white text-[8px] font-bold uppercase tracking-wider rounded-full flex items-center gap-1.5 shadow-lg shadow-emerald-500/20 z-20", children: [_jsx(CheckCircle2, { size: 10 }), "In Use"] })), _jsxs("div", { className: "absolute inset-0 z-10 opacity-0 group-hover:opacity-100 transition-all duration-300 flex flex-col justify-between p-5", children: [_jsx("div", { className: "absolute inset-0 bg-gradient-to-b from-black/10 via-black/40 to-black/70 backdrop-blur-[1px]" }), _jsxs("div", { className: "relative z-20 flex-1 flex items-center justify-center gap-2.5 translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [_jsx("button", { onClick: () => onViewFull(image.url), className: "p-3 bg-white text-primary rounded-xl hover:scale-105 active:scale-95 transition-all shadow-xl", title: "View Image", children: _jsx(Maximize2, { size: 18 }) }), _jsx("button", { onClick: () => onDelete(image), className: `p-3 rounded-xl shadow-xl transition-all hover:scale-105 active:scale-95 ${inUse
|
|
12
|
+
? 'bg-white/10 text-white/30 cursor-not-allowed'
|
|
13
|
+
: 'bg-red-500 text-white hover:bg-red-600'}`, title: inUse ? 'Image is in use' : 'Delete Image', disabled: inUse, children: _jsx(Trash2, { size: 18 }) })] }), _jsxs("div", { className: "relative z-20 space-y-1.5 translate-y-2 group-hover:translate-y-0 transition-transform duration-300 delay-75", children: [_jsx("p", { className: "text-[11px] font-bold text-white tracking-tight truncate w-full", title: image.filename, children: image.filename }), _jsxs("div", { className: "flex items-center justify-between border-t border-white/10 pt-1.5", children: [_jsx("span", { className: "text-[9px] font-bold text-white/60 uppercase tracking-widest", children: formatFileSize(image.size) }), inUse ? (_jsx("span", { className: "text-[9px] text-emerald-400 font-bold uppercase", children: usage.length > 0 ? `${usage.length} Uses` : 'Active' })) : (_jsx("span", { className: "text-[9px] text-white/40 font-bold uppercase", children: "Available" }))] })] })] })] }) }, image.id));
|
|
14
|
+
}) }) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ImageManagerHeaderProps {
|
|
3
|
+
uploading: boolean;
|
|
4
|
+
unusedCount: number;
|
|
5
|
+
onUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
6
|
+
onRefresh: () => void;
|
|
7
|
+
onShowCleanup: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ImageManagerHeader({ uploading, unusedCount, onUpload, onRefresh, onShowCleanup }: ImageManagerHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=ImageManagerHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageManagerHeader.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/ImageManagerHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,uBAAuB;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAC3D,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,SAAS,EACT,WAAW,EACX,QAAQ,EACR,SAAS,EACT,aAAa,EAChB,EAAE,uBAAuB,2CAoDzB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { FileImage, Zap, Loader2, Upload, RefreshCw } from 'lucide-react';
|
|
4
|
+
export function ImageManagerHeader({ uploading, unusedCount, onUpload, onRefresh, onShowCleanup }) {
|
|
5
|
+
return (_jsxs("div", { className: "flex flex-col lg:flex-row lg:items-end justify-between gap-8 px-4", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/15 border border-primary/30 text-primary text-[10px] font-bold uppercase tracking-wider shadow-sm", children: [_jsx(FileImage, { size: 12 }), _jsx("span", { children: "Media Library" })] }), _jsxs("div", { children: [_jsxs("h1", { className: "text-4xl font-bold text-dashboard-text tracking-tight leading-none mb-2", children: ["Images ", _jsx("span", { className: "text-primary", children: "&" }), " Media"] }), _jsx("p", { className: "text-sm text-dashboard-text-secondary font-medium max-w-md leading-relaxed opacity-80", children: "Upload and manage your images. See exactly where they are used across your website." })] })] }), _jsxs("div", { className: "flex items-center gap-4", children: [unusedCount > 0 && (_jsxs("button", { onClick: onShowCleanup, className: "group flex items-center gap-3 px-6 py-3.5 bg-dashboard-card/50 border border-dashboard-border/40 text-amber-600 rounded-2xl text-[10px] font-bold uppercase tracking-widest transition-all hover:bg-amber-500/10 hover:border-amber-500/20 active:scale-95 shadow-sm", children: [_jsx(Zap, { size: 16 }), "Cleanup Library"] })), _jsxs("label", { className: "group relative flex items-center gap-3 px-7 py-3.5 bg-primary text-white rounded-2xl text-[10px] font-bold uppercase tracking-widest overflow-hidden transition-all hover:scale-[1.02] active:scale-95 shadow-lg shadow-primary/20 cursor-pointer", children: [uploading ? _jsx(Loader2, { size: 18, className: "relative z-10 animate-spin" }) : _jsx(Upload, { size: 18, className: "relative z-10" }), _jsx("span", { className: "relative z-10", children: uploading ? 'Uploading...' : 'Upload Media' }), _jsx("input", { type: "file", accept: "image/*", multiple: true, onChange: onUpload, className: "hidden", disabled: uploading })] }), _jsx("button", { onClick: onRefresh, className: "p-3.5 bg-dashboard-card/50 border border-dashboard-border/40 text-dashboard-text-secondary hover:text-primary hover:border-primary/30 transition-all rounded-2xl active:scale-95", title: "Refresh Gallery", children: _jsx(RefreshCw, { size: 18 }) })] })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StorageStats } from '../types';
|
|
2
|
+
interface ImageManagerStatsProps {
|
|
3
|
+
stats: StorageStats;
|
|
4
|
+
formatFileSize: (bytes: number) => string;
|
|
5
|
+
}
|
|
6
|
+
export declare function ImageManagerStats({ stats, formatFileSize }: ImageManagerStatsProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=ImageManagerStats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageManagerStats.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/ImageManagerStats.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,UAAU,sBAAsB;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7C;AAED,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,sBAAsB,2CAgDlF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Image as ImageIcon, Database, CheckCircle2, Activity } from 'lucide-react';
|
|
4
|
+
export function ImageManagerStats({ stats, formatFileSize }) {
|
|
5
|
+
return (_jsxs("div", { className: "grid grid-cols-2 lg:grid-cols-4 gap-5 px-4", children: [_jsxs("div", { className: "bg-dashboard-card/50 border border-dashboard-border/40 p-5 rounded-2xl relative overflow-hidden group transition-all duration-500", children: [_jsx("div", { className: "absolute top-0 right-0 p-4 opacity-5 group-hover:scale-105 group-hover:text-primary transition-all duration-700", children: _jsx(ImageIcon, { size: 80 }) }), _jsxs("div", { className: "relative z-10", children: [_jsx("label", { className: "text-[10px] font-bold text-primary uppercase tracking-widest mb-1 block opacity-80", children: "Total Assets" }), _jsx("p", { className: "text-2xl font-bold text-dashboard-text tracking-tight", children: stats.totalImages }), _jsx("p", { className: "text-[9px] text-dashboard-text-secondary mt-1 font-semibold uppercase opacity-60", children: "Optimized Library" })] })] }), _jsxs("div", { className: "bg-dashboard-card/50 border border-dashboard-border/40 p-5 rounded-2xl relative overflow-hidden group transition-all duration-500", children: [_jsx("div", { className: "absolute top-0 right-0 p-4 opacity-5 group-hover:scale-105 group-hover:text-amber-500 transition-all duration-700", children: _jsx(Database, { size: 80 }) }), _jsxs("div", { className: "relative z-10", children: [_jsx("label", { className: "text-[10px] font-bold text-amber-500 uppercase tracking-widest mb-1 block opacity-80", children: "Storage Usage" }), _jsx("p", { className: "text-2xl font-bold text-dashboard-text tracking-tight", children: formatFileSize(stats.totalSize) }), _jsx("p", { className: "text-[9px] text-dashboard-text-secondary mt-1 font-semibold uppercase opacity-60", children: "Cloud-optimized Storage" })] })] }), _jsxs("div", { className: "bg-dashboard-card/50 border border-dashboard-border/40 p-5 rounded-2xl relative overflow-hidden group transition-all duration-500", children: [_jsx("div", { className: "absolute top-0 right-0 p-4 opacity-5 group-hover:scale-105 group-hover:text-emerald-500 transition-all duration-700", children: _jsx(CheckCircle2, { size: 80 }) }), _jsxs("div", { className: "relative z-10", children: [_jsx("label", { className: "text-[10px] font-bold text-emerald-500 uppercase tracking-widest mb-1 block opacity-80", children: "Active Images" }), _jsx("p", { className: "text-2xl font-bold text-dashboard-text tracking-tight", children: stats.usedImages }), _jsx("p", { className: "text-[9px] text-dashboard-text-secondary mt-1 font-semibold uppercase opacity-60", children: "Linked to Website" })] })] }), _jsxs("div", { className: "bg-dashboard-card/50 border border-dashboard-border/40 p-5 rounded-2xl relative overflow-hidden group transition-all duration-500", children: [_jsx("div", { className: "absolute top-0 right-0 p-4 opacity-5 group-hover:scale-105 group-hover:text-neutral-500 transition-all duration-700", children: _jsx(Activity, { size: 80 }) }), _jsxs("div", { className: "relative z-10", children: [_jsx("label", { className: "text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-widest mb-1 block opacity-80", children: "Unused Assets" }), _jsx("p", { className: "text-2xl font-bold text-dashboard-text tracking-tight", children: stats.availableImages }), _jsx("p", { className: "text-[9px] text-dashboard-text-secondary mt-1 font-semibold uppercase opacity-60", children: "Ready for Removal" })] })] })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface ImageManagerToolbarProps {
|
|
2
|
+
search: string;
|
|
3
|
+
setSearch: (value: string) => void;
|
|
4
|
+
viewMode: 'grid' | 'list';
|
|
5
|
+
setViewMode: (mode: 'grid' | 'list') => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function ImageManagerToolbar({ search, setSearch, viewMode, setViewMode }: ImageManagerToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ImageManagerToolbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageManagerToolbar.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/ImageManagerToolbar.tsx"],"names":[],"mappings":"AAKA,UAAU,wBAAwB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CAChD;AAED,wBAAgB,mBAAmB,CAAC,EAChC,MAAM,EACN,SAAS,EACT,QAAQ,EACR,WAAW,EACd,EAAE,wBAAwB,2CA0C1B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Search, Grid3x3, List } from 'lucide-react';
|
|
4
|
+
export function ImageManagerToolbar({ search, setSearch, viewMode, setViewMode }) {
|
|
5
|
+
return (_jsx("div", { className: "px-4", children: _jsxs("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", children: [_jsxs("div", { className: "relative flex-1 group w-full", children: [_jsx(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 }), _jsx("input", { type: "text", placeholder: "Search assets by filename...", value: search, onChange: (e) => setSearch(e.target.value), 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" })] }), _jsxs("div", { className: "flex items-center gap-1.5 p-1.5 bg-dashboard-bg/50 rounded-xl border border-dashboard-border/40", children: [_jsx("button", { onClick: () => setViewMode('grid'), className: `p-2.5 rounded-lg transition-all ${viewMode === 'grid'
|
|
6
|
+
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
|
7
|
+
: 'text-dashboard-text-secondary hover:text-primary hover:bg-primary/5'}`, title: "Grid View", children: _jsx(Grid3x3, { size: 18 }) }), _jsx("button", { onClick: () => setViewMode('list'), className: `p-2.5 rounded-lg transition-all ${viewMode === 'list'
|
|
8
|
+
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
|
9
|
+
: 'text-dashboard-text-secondary hover:text-primary hover:bg-primary/5'}`, title: "List View", children: _jsx(List, { size: 18 }) })] })] }) }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ImageMetadata } from '../types';
|
|
2
|
+
interface ImageTableProps {
|
|
3
|
+
images: ImageMetadata[];
|
|
4
|
+
isImageInUse: (filename: string) => boolean;
|
|
5
|
+
getImageUsage: (filename: string) => any[];
|
|
6
|
+
formatFileSize: (bytes: number) => string;
|
|
7
|
+
formatDate: (date: string) => string;
|
|
8
|
+
onViewFull: (url: string) => void;
|
|
9
|
+
onDelete: (image: ImageMetadata) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function ImageTable({ images, isImageInUse, getImageUsage, formatFileSize, formatDate, onViewFull, onDelete }: ImageTableProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ImageTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageTable.d.ts","sourceRoot":"","sources":["../../../../src/views/ImageManager/components/ImageTable.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,UAAU,eAAe;IACrB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;IAC3C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,YAAY,EACZ,aAAa,EACb,cAAc,EACd,UAAU,EACV,UAAU,EACV,QAAQ,EACX,EAAE,eAAe,2CA8FjB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Image from 'next/image';
|
|
4
|
+
import { Eye, Trash2, Circle, CheckCircle2 } from 'lucide-react';
|
|
5
|
+
export function ImageTable({ images, isImageInUse, getImageUsage, formatFileSize, formatDate, onViewFull, onDelete }) {
|
|
6
|
+
return (_jsx("div", { className: "bg-dashboard-card/40 rounded-2xl overflow-hidden border border-dashboard-border/40 shadow-sm", children: _jsxs("table", { className: "w-full text-left border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-widest border-b border-dashboard-border/40 bg-dashboard-bg/20", children: [_jsx("th", { className: "px-8 py-5", children: "Asset Reference" }), _jsx("th", { className: "px-8 py-5", children: "File Size" }), _jsx("th", { className: "px-8 py-5", children: "Uploaded" }), _jsx("th", { className: "px-8 py-5", children: "Usage Status" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Actions" })] }) }), _jsx("tbody", { className: "divide-y divide-dashboard-border/30", children: images.map((image) => {
|
|
7
|
+
const usage = getImageUsage(image.filename);
|
|
8
|
+
const inUse = isImageInUse(image.filename);
|
|
9
|
+
return (_jsxs("tr", { className: "group hover:bg-primary/[0.03] transition-colors", children: [_jsx("td", { className: "px-8 py-4", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: `size-12 relative bg-dashboard-bg/50 rounded-xl overflow-hidden border transition-all ${inUse ? 'border-primary/30' : 'border-dashboard-border/60'}`, children: _jsx(Image, { src: image.url, alt: image.filename, fill: true, className: "object-cover", unoptimized: true }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-bold text-dashboard-text text-sm tracking-tight group-hover:text-primary transition-colors truncate max-w-[250px]", title: image.filename, children: image.filename }), _jsx("p", { className: "text-[9px] font-medium text-dashboard-text-secondary opacity-60 uppercase tracking-widest", children: "Media Asset" })] })] }) }), _jsx("td", { className: "px-8 py-4", children: _jsx("span", { className: "text-xs font-semibold text-dashboard-text/70", children: formatFileSize(image.size) }) }), _jsx("td", { className: "px-8 py-4", children: _jsx("span", { className: "text-xs font-semibold text-dashboard-text/70", children: formatDate(image.uploadedAt) }) }), _jsx("td", { className: "px-8 py-4", children: inUse ? (_jsxs("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", children: [_jsx(CheckCircle2, { size: 10 }), "Active", usage.length > 0 ? ` (${usage.length})` : ''] })) : (_jsxs("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", children: [_jsx(Circle, { size: 10 }), "Unlinked"] })) }), _jsx("td", { className: "px-8 py-4 text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [_jsx("button", { onClick: () => onViewFull(image.url), className: "p-2.5 text-dashboard-text-secondary hover:text-primary hover:bg-primary/10 rounded-lg transition-all active:scale-90", title: "View Image", children: _jsx(Eye, { size: 18 }) }), _jsx("button", { onClick: () => onDelete(image), className: `p-2.5 rounded-lg transition-all active:scale-90 ${inUse
|
|
10
|
+
? 'text-neutral-300 cursor-not-allowed opacity-20'
|
|
11
|
+
: 'text-dashboard-text-secondary hover:text-red-500 hover:bg-red-500/10'}`, disabled: inUse, title: "Delete Image", children: _jsx(Trash2, { size: 18 }) })] }) })] }, image.id));
|
|
12
|
+
}) })] }) }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ImageMetadata {
|
|
2
|
+
id: string;
|
|
3
|
+
filename: string;
|
|
4
|
+
url: string;
|
|
5
|
+
size: number;
|
|
6
|
+
mimeType: string;
|
|
7
|
+
uploadedAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ImageUsage {
|
|
10
|
+
imageId: string;
|
|
11
|
+
filename: string;
|
|
12
|
+
usage: Array<{
|
|
13
|
+
plugin: string;
|
|
14
|
+
type: string;
|
|
15
|
+
title: string;
|
|
16
|
+
id: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface StorageStats {
|
|
21
|
+
totalImages: number;
|
|
22
|
+
totalSize: number;
|
|
23
|
+
usedImages: number;
|
|
24
|
+
availableImages: number;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/views/ImageManager/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,GAAG,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACN;AAED,MAAM,WAAW,YAAY;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|