feat(nextjs): Icon editor & refactor image editor
This commit is contained in:
parent
83dab9346b
commit
5798bcdbdf
4 changed files with 16 additions and 87 deletions
|
|
@ -104,6 +104,7 @@ const IconsEditor = ({
|
|||
side="right"
|
||||
className="w-[400px]"
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Choose Icon</SheetTitle>
|
||||
|
|
@ -112,6 +113,7 @@ const IconsEditor = ({
|
|||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleIconSearch();
|
||||
}}
|
||||
>
|
||||
|
|
@ -122,6 +124,7 @@ const IconsEditor = ({
|
|||
placeholder="Search icons..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -129,6 +132,7 @@ const IconsEditor = ({
|
|||
type="submit"
|
||||
variant="outline"
|
||||
className="w-full text-semibold text-[#51459e]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
|
|
@ -147,7 +151,10 @@ const IconsEditor = ({
|
|||
{icons.map((iconSrc, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => handleIconChange(iconSrc)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleIconChange(iconSrc);
|
||||
}}
|
||||
className="w-12 h-12 cursor-pointer group relative rounded-lg overflow-hidden hover:bg-gray-100 p-2"
|
||||
>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import {
|
||||
Wand2,
|
||||
Upload,
|
||||
Edit,
|
||||
Move,
|
||||
Maximize,
|
||||
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
|
@ -28,20 +27,14 @@ import {
|
|||
} from "@/store/slices/presentationGeneration";
|
||||
import { getStaticFileUrl, ThemeImagePrompt } from "../utils/others";
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import ToolTip from "@/components/ToolTip";
|
||||
|
||||
|
||||
interface ImageEditorProps {
|
||||
initialImage: string | null;
|
||||
imageIdx?: number;
|
||||
title: string;
|
||||
|
||||
slideIndex: number;
|
||||
elementId: string;
|
||||
|
||||
className?: string;
|
||||
promptContent?: string;
|
||||
properties?: null | any;
|
||||
|
|
@ -51,10 +44,7 @@ interface ImageEditorProps {
|
|||
const ImageEditor = ({
|
||||
initialImage,
|
||||
imageIdx = 0,
|
||||
className,
|
||||
title,
|
||||
slideIndex,
|
||||
elementId,
|
||||
promptContent,
|
||||
properties,
|
||||
onClose,
|
||||
|
|
@ -66,9 +56,6 @@ const ImageEditor = ({
|
|||
const searchParams = useSearchParams();
|
||||
const [image, setImage] = useState(initialImage);
|
||||
const [previewImages, setPreviewImages] = useState([initialImage]);
|
||||
|
||||
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
||||
const [isToolbarOpen, setIsToolbarOpen] = useState(false);
|
||||
const [prompt, setPrompt] = useState<string>("");
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -110,7 +97,7 @@ const ImageEditor = ({
|
|||
!toolbarRef.current.contains(event.target as Node) &&
|
||||
!popoverContentRef.current
|
||||
) {
|
||||
setIsToolbarOpen(false);
|
||||
|
||||
if (isFocusPointMode) {
|
||||
// saveFocusPoint(); // Save focus point before closing
|
||||
saveImageProperties(objectFit, focusPoint);
|
||||
|
|
@ -125,16 +112,7 @@ const ImageEditor = ({
|
|||
};
|
||||
}, [isFocusPointMode, focusPoint]);
|
||||
|
||||
const handleImageClick = () => {
|
||||
if (!isFocusPointMode) {
|
||||
setIsToolbarOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenEditor = () => {
|
||||
setIsToolbarOpen(false);
|
||||
setIsEditorOpen(true);
|
||||
};
|
||||
|
||||
const handleImageChange = (newImage: string) => {
|
||||
setImage(newImage);
|
||||
|
|
@ -145,7 +123,6 @@ const ImageEditor = ({
|
|||
image: newImage,
|
||||
})
|
||||
);
|
||||
setIsEditorOpen(false);
|
||||
};
|
||||
|
||||
const handleFocusPointClick = (e: React.MouseEvent) => {
|
||||
|
|
@ -600,7 +577,7 @@ const ImageEditor = ({
|
|||
className="cursor-pointer group w-full h-full"
|
||||
>
|
||||
<img
|
||||
src={getStaticFileUrl(uploadedImageUrl)}
|
||||
src={uploadedImageUrl}
|
||||
alt="Uploaded preview"
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -70,9 +70,7 @@ export const SmartEditableProvider: React.FC<SmartEditableProviderProps> = ({
|
|||
dataPath: path,
|
||||
props: {
|
||||
slideIndex,
|
||||
elementId: `image-${path.replace(/[^\w]/g, '-')}`,
|
||||
initialImage: data.__image_url__,
|
||||
title: imgElement.alt || 'Image',
|
||||
promptContent: data.__image_prompt__ || '',
|
||||
imageIdx: elements.filter(e => e.type === 'image').length
|
||||
}
|
||||
|
|
@ -155,55 +153,6 @@ export const SmartEditableProvider: React.FC<SmartEditableProviderProps> = ({
|
|||
return getFilename(domSrc) === getFilename(dataSrc) && getFilename(domSrc) !== '';
|
||||
};
|
||||
|
||||
// Add event delegation for clicks
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'IMG') {
|
||||
const imgElement = target as HTMLImageElement;
|
||||
const editableElement = editableElements.find(el => el.element === imgElement);
|
||||
|
||||
if (editableElement) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const rect = imgElement.getBoundingClientRect();
|
||||
setActiveEditor({
|
||||
type: editableElement.type,
|
||||
element: imgElement,
|
||||
props: editableElement.props,
|
||||
rect
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add hover effects
|
||||
const handleMouseEnter = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'IMG') {
|
||||
const imgElement = target as HTMLImageElement;
|
||||
const isEditable = editableElements.some(el => el.element === imgElement);
|
||||
|
||||
if (isEditable) {
|
||||
imgElement.style.cursor = 'pointer';
|
||||
imgElement.style.filter = 'brightness(0.9)';
|
||||
imgElement.style.transition = 'filter 0.2s ease';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'IMG') {
|
||||
const imgElement = target as HTMLImageElement;
|
||||
const isEditable = editableElements.some(el => el.element === imgElement);
|
||||
|
||||
if (isEditable) {
|
||||
imgElement.style.filter = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set up event listeners after elements are found
|
||||
const timer = setTimeout(() => {
|
||||
findEditableElements();
|
||||
|
|
@ -211,11 +160,8 @@ export const SmartEditableProvider: React.FC<SmartEditableProviderProps> = ({
|
|||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
container.removeEventListener('click', handleClick);
|
||||
container.removeEventListener('mouseenter', handleMouseEnter, true);
|
||||
container.removeEventListener('mouseleave', handleMouseLeave, true);
|
||||
};
|
||||
}, [slideIndex, slideId, slideData, isEditMode, editableElements]);
|
||||
}, [slideIndex, slideId, slideData, isEditMode]);
|
||||
|
||||
// Set up event listeners when editableElements change
|
||||
useEffect(() => {
|
||||
|
|
@ -298,7 +244,7 @@ export const SmartEditableProvider: React.FC<SmartEditableProviderProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
// Simple overlay component for editors
|
||||
// overlay component for editors
|
||||
const EditorOverlay: React.FC<{
|
||||
activeEditor: {
|
||||
type: 'image' | 'icon';
|
||||
|
|
@ -320,7 +266,6 @@ const EditorOverlay: React.FC<{
|
|||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export async function POST(request: NextRequest) {
|
|||
// Return the relative path that can be used in the frontend
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
filePath: `/app/user_data/uploads/${filename}`
|
||||
filePath: `${userDataDir}/uploads/${filename}`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error saving image:", error);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue