feat(nextjs): Image edit with object fit & focal point added
This commit is contained in:
parent
b8c81a38d5
commit
fc68faea43
4 changed files with 53 additions and 53 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { ReactNode, useRef, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateSlideImage, updateSlideIcon } from '@/store/slices/presentationGeneration';
|
||||
import { updateSlideImage, updateSlideIcon, updateImageProperties } from '@/store/slices/presentationGeneration';
|
||||
import ImageEditor from './ImageEditor';
|
||||
import IconsEditor from './IconsEditor';
|
||||
|
||||
|
|
@ -170,10 +170,10 @@ const EditableLayoutWrapper: React.FC<EditableLayoutWrapperProps> = ({
|
|||
htmlImg.setAttribute('data-editable-processed', 'true');
|
||||
|
||||
// Add a unique identifier to help with debugging
|
||||
htmlImg.setAttribute('data-editable-id', `${type}-${dataPath}-${index}`);
|
||||
htmlImg.setAttribute('data-editable-id', `${slideIndex}-${type}-${dataPath}-${index}`);
|
||||
|
||||
const editableElement: EditableElement = {
|
||||
id: `${type}-${dataPath}-${index}`,
|
||||
id: `${slideIndex}-${type}-${dataPath}-${index}`,
|
||||
type,
|
||||
src,
|
||||
dataPath,
|
||||
|
|
@ -192,9 +192,14 @@ const EditableLayoutWrapper: React.FC<EditableLayoutWrapperProps> = ({
|
|||
|
||||
htmlImg.addEventListener('click', clickHandler);
|
||||
|
||||
const itemIndex = parseInt(`${slideIndex}-${type}-${dataPath}-${index}`.split('-').pop() || '0');
|
||||
const properties = slideData.properties?.[itemIndex];
|
||||
|
||||
// Add hover effects without changing layout
|
||||
htmlImg.style.cursor = 'pointer';
|
||||
htmlImg.style.transition = 'opacity 0.2s, transform 0.2s';
|
||||
htmlImg.style.objectFit = properties?.objectFit;
|
||||
htmlImg.style.objectPosition = `${properties?.focusPoint?.x}% ${properties?.focusPoint?.y}%`;
|
||||
|
||||
const mouseEnterHandler = () => {
|
||||
htmlImg.style.opacity = '0.8';
|
||||
|
|
@ -327,16 +332,22 @@ const EditableLayoutWrapper: React.FC<EditableLayoutWrapperProps> = ({
|
|||
}
|
||||
};
|
||||
const handleFocusPointClick = (propertiesData: any) => {
|
||||
console.log('activeEditor', activeEditor);
|
||||
|
||||
const id = activeEditor?.id;
|
||||
const editableId = document.querySelector(`[data-editable-id="${id}"]`);
|
||||
console.log('editableId', editableId);
|
||||
|
||||
if (editableId) {
|
||||
const editableElement = editableId as HTMLImageElement;
|
||||
editableElement.style.objectPosition = `${propertiesData.initialFocusPoint.x}px ${propertiesData.initialFocusPoint.y}px`;
|
||||
editableElement.style.objectFit = propertiesData.initialObjectFit;
|
||||
editableElement.style.objectPosition = `${propertiesData.initialFocusPoint.x}% ${propertiesData.initialFocusPoint.y}%`;
|
||||
}
|
||||
|
||||
dispatch(updateImageProperties({
|
||||
slideIndex,
|
||||
itemIndex: parseInt(activeEditor?.id.split('-').pop() || '0'),
|
||||
properties: propertiesData
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -374,47 +385,3 @@ const EditableLayoutWrapper: React.FC<EditableLayoutWrapperProps> = ({
|
|||
|
||||
export default EditableLayoutWrapper;
|
||||
|
||||
|
||||
|
||||
|
||||
const setNestedImageValue = (obj: any, path: string, url: string, promptText?: string) => {
|
||||
const keys = path.split(/[.\[\]]+/).filter(Boolean);
|
||||
let current = obj;
|
||||
|
||||
// Navigate to the parent object
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (isNaN(Number(key))) {
|
||||
if (!current[key]) {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key];
|
||||
} else {
|
||||
const index = Number(key);
|
||||
if (!current[index]) {
|
||||
current[index] = {};
|
||||
}
|
||||
current = current[index];
|
||||
}
|
||||
}
|
||||
|
||||
// Set the image properties
|
||||
const finalKey = keys[keys.length - 1];
|
||||
const target = isNaN(Number(finalKey)) ? current[finalKey] : current[Number(finalKey)];
|
||||
|
||||
// Preserve existing properties if the target already exists
|
||||
const updatedValue = {
|
||||
...(target && typeof target === 'object' ? target : {}),
|
||||
__image_url__: url,
|
||||
__image_prompt__: promptText || (target?.__image_prompt__) || ''
|
||||
};
|
||||
|
||||
if (isNaN(Number(finalKey))) {
|
||||
current[finalKey] = updatedValue;
|
||||
} else {
|
||||
current[Number(finalKey)] = updatedValue;
|
||||
}
|
||||
|
||||
// Add debugging
|
||||
console.log('Redux: Updated slide image at path:', path, 'with URL:', url);
|
||||
};
|
||||
|
|
@ -269,14 +269,14 @@ const ImageEditor = ({
|
|||
|
||||
<div className="mt-6">
|
||||
<Tabs defaultValue="generate" className="w-full">
|
||||
<TabsList className="grid bg-blue-100 border border-blue-300 w-full grid-cols-2 mx-auto">
|
||||
<TabsList className="grid bg-blue-100 border border-blue-300 w-full grid-cols-3 mx-auto">
|
||||
<TabsTrigger className="font-medium" value="generate">
|
||||
AI Generate
|
||||
</TabsTrigger>
|
||||
<TabsTrigger className="font-medium" value="upload">
|
||||
Upload
|
||||
</TabsTrigger>
|
||||
{/* <TabsTrigger className="font-medium" value="edit">Edit</TabsTrigger> */}
|
||||
<TabsTrigger className="font-medium" value="edit">Edit</TabsTrigger>
|
||||
</TabsList>
|
||||
{/* Generate Tab */}
|
||||
<TabsContent value="generate" className="mt-4 space-y-4">
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const useOutlineStreaming = (presentationId: string | null) => {
|
|||
|
||||
eventSource.addEventListener("response", (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
console.log('data', data);
|
||||
switch (data.type) {
|
||||
case "chunk":
|
||||
accumulatedChunks += data.chunk;
|
||||
|
|
@ -66,6 +66,15 @@ export const useOutlineStreaming = (presentationId: string | null) => {
|
|||
setStreamState({ isStreaming: false, isLoading: false });
|
||||
eventSource.close();
|
||||
break;
|
||||
case "error":
|
||||
setStreamState({ isStreaming: false, isLoading: false });
|
||||
eventSource.close();
|
||||
toast.error('Error in outline streaming',
|
||||
{
|
||||
description: data.detail || 'Failed to connect to the server. Please try again.',
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,29 @@ const presentationGenerationSlice = createSlice({
|
|||
}
|
||||
},
|
||||
|
||||
updateImageProperties: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
slideIndex: number;
|
||||
itemIndex: number;
|
||||
properties: any;
|
||||
}>
|
||||
) => {
|
||||
if (
|
||||
state.presentationData &&
|
||||
state.presentationData.slides &&
|
||||
state.presentationData.slides[action.payload.slideIndex]
|
||||
) {
|
||||
const slide = state.presentationData.slides[action.payload.slideIndex];
|
||||
const { itemIndex, properties } = action.payload;
|
||||
slide['properties'] = {
|
||||
...slide.properties,
|
||||
[itemIndex]: properties
|
||||
};
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
// Update slide icon at specific data path
|
||||
updateSlideIcon: (
|
||||
state,
|
||||
|
|
@ -385,6 +408,7 @@ export const {
|
|||
deletePresentationSlide,
|
||||
updateSlideContent,
|
||||
updateSlideImage,
|
||||
updateImageProperties,
|
||||
updateSlideIcon,
|
||||
addNewSlide,
|
||||
} = presentationGenerationSlice.actions;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue