new design added

This commit is contained in:
Saimon8420 2025-02-08 15:38:30 +06:00
parent 704fb6cee1
commit 2c0ab8b736
8 changed files with 271 additions and 41 deletions

View file

@ -11,35 +11,52 @@ import { useQuery } from '@tanstack/react-query';
import { generateToken } from './api/authApi'; import { generateToken } from './api/authApi';
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Toaster } from '@/components/ui/toaster'; import { Toaster } from '@/components/ui/toaster';
import { getProjectById } from './api/projectApi';
import { Loader } from 'lucide-react';
import CanvasContext from './components/Context/canvasContext/CanvasContext';
export const Home = () => { export const Home = () => {
const { activeObject } = useContext(ActiveObjectContext); const { activeObject } = useContext(ActiveObjectContext);
const { canvas, selectedPanel } = useContext(CanvasContext);
const params = useParams(); const params = useParams();
const getToken = localStorage.getItem('canvas_token');
const { id } = params;
const navigate = useNavigate(); const navigate = useNavigate();
const getToken = () => localStorage.getItem('canvas_token');
const { id } = params;
// Fetch token only if it doesn't exist
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ['get-token'], queryKey: ['get-token'],
queryFn: () => generateToken(id), queryFn: () => generateToken(id),
enabled: !getToken });
})
console.log(data); // Fetch project data only if token and id exist
const { data: projectData, isLoading: projectLoading } = useQuery({
queryKey: ['project', id],
queryFn: async () => await getProjectById(id),
enabled: !!getToken() && !!id && selectedPanel === "project",
});
useEffect(() => { useEffect(() => {
if (!getToken && !isLoading) { const token = getToken(); // Get latest token
if (!token && !isLoading) {
navigate("/unAuthenticated"); navigate("/unAuthenticated");
} }
if (getToken && !isLoading && data?.status === 201) { if (token && !isLoading && data?.status === 201) {
navigate("/"); navigate("/");
} }
}, [getToken, navigate, isLoading, data]) if (projectData?.status === 200 & !projectLoading && projectData?.data?.object && canvas && canvas?._objects?.length === 0 && selectedPanel === "project") {
canvas.loadFromJSON(projectData?.data?.object);
canvas.renderAll();
}
}, [navigate, isLoading, data, projectData, projectLoading, canvas, selectedPanel]);
return ( return (
<div className="flex h-screen overflow-hidden relative"> <div className="flex h-screen overflow-hidden relative">
{ {
isLoading && <div>Loading...</div> isLoading && <p><Loader className="animate-spin mx-auto" /></p>
} }
{!isLoading && {!isLoading &&
<> <>

View file

@ -8,7 +8,7 @@ interface Project {
export const getProjects = async () => { export const getProjects = async () => {
try { try {
const response = await fetch(`${import.meta.env.VITE_SERVER_URL}/projects`, { const response = await fetch(`${import.meta.env.VITE_SERVER_URL}/projects/`, {
method: 'GET', method: 'GET',
credentials: 'include', credentials: 'include',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },

View file

@ -31,7 +31,12 @@ const CanvasSetting = () => {
const createEmptyProject = async () => { const createEmptyProject = async () => {
try { try {
const response = await createProject(); const response = await createProject();
console.log(response); if (response?.status === 200) {
toast({
title: response?.status,
description: response?.message
})
}
if (response?.data?.id) { if (response?.data?.id) {
navigate(`/${response.data.id}`); navigate(`/${response.data.id}`);
} }
@ -42,7 +47,7 @@ const CanvasSetting = () => {
if (!id) { if (!id) {
createEmptyProject(); createEmptyProject();
} }
}, [id, navigate]); }, [id, navigate, toast]);
// upload bg-image handler // upload bg-image handler
const { mutate: uploadBackgroundImage } = useMutation({ const { mutate: uploadBackgroundImage } = useMutation({

View file

@ -26,9 +26,9 @@ import ImageCustomization from "./Customization/ImageCustomization";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
import ActiveObjectContext from "../Context/activeObject/ObjectContext"; import ActiveObjectContext from "../Context/activeObject/ObjectContext";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { useMutation } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { deleteImage, uploadImage } from "../../api/uploadApi"; import { deleteImage, uploadImage } from "../../api/uploadApi";
import { createProject } from "../../api/projectApi"; import { createProject, getProjectById, updateProject } from "../../api/projectApi";
import { useToast } from "../../hooks/use-toast"; import { useToast } from "../../hooks/use-toast";
const UploadImage = () => { const UploadImage = () => {
@ -56,6 +56,12 @@ const UploadImage = () => {
const createEmptyProject = async () => { const createEmptyProject = async () => {
try { try {
const response = await createProject(); const response = await createProject();
if (response?.status === 200) {
toast({
title: response?.status,
description: response?.message
})
}
if (response?.data?.id) { if (response?.data?.id) {
navigate(`/${response.data.id}`); navigate(`/${response.data.id}`);
} }
@ -66,7 +72,38 @@ const UploadImage = () => {
if (!id) { if (!id) {
createEmptyProject(); createEmptyProject();
} }
}, [id, navigate]); }, [id, navigate, toast]);
const queryClient = useQueryClient();
// to get each canvas_project data
const { data: projectData } = useQuery({
queryKey: ['project', id],
queryFn: async () => await getProjectById(id),
enabled: !!id,
});
console.log(projectData);
// to update the project
const { mutate: projectUpdate } = useMutation({
mutationFn: async ({ id, updateData }) => {
return await updateProject({ id, ...updateData })
},
onSuccess: (data) => {
if (data?.status === 200) {
// Invalidate a single query key
queryClient.invalidateQueries({ queryKey: ["project", id] });
}
else {
toast({
title: data?.status,
description: data?.message,
variant: "destructive"
})
}
}
});
// upload image handler // upload image handler
const { mutate } = useMutation({ const { mutate } = useMutation({
@ -94,6 +131,12 @@ const UploadImage = () => {
}, },
{ crossOrigin: "anonymous" } { crossOrigin: "anonymous" }
); );
if (canvas) {
const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need
const updateData = { ...projectData?.data, object, preview_url: "" };
// Wait for the project update before continuing
projectUpdate({ id, updateData });
}
} }
else { else {
toast({ toast({
@ -104,7 +147,7 @@ const UploadImage = () => {
removeFile(); removeFile();
} }
} }
}) });
// handle image remove // handle image remove
const { mutate: deleteMutate } = useMutation({ const { mutate: deleteMutate } = useMutation({
@ -117,6 +160,12 @@ const UploadImage = () => {
title: data?.status, title: data?.status,
description: data?.message description: data?.message
}) })
if (canvas) {
const object = canvas.toJSON(['id', 'selectable']); // Include any custom properties you need
const updateData = { ...projectData?.data, object };
// Wait for the project update before continuing
projectUpdate({ id, updateData });
}
} }
else { else {
toast({ toast({

View file

@ -12,6 +12,8 @@ import PositionPanel from "./PositionPanel";
import ImagePanel from "./ImagePanel"; import ImagePanel from "./ImagePanel";
import GroupObjectPanel from "./GroupObjectPanel"; import GroupObjectPanel from "./GroupObjectPanel";
import CanvasPanel from "./CanvasPanel"; import CanvasPanel from "./CanvasPanel";
import { ProjectPanel } from "./ProjectPanel";
import ImageLibrary from "./ImageLibrary";
const EditorPanel = () => { const EditorPanel = () => {
const { selectedPanel } = useContext(CanvasContext); const { selectedPanel } = useContext(CanvasContext);
@ -42,6 +44,10 @@ const EditorPanel = () => {
return <GroupObjectPanel />; return <GroupObjectPanel />;
case "canvas": case "canvas":
return <CanvasPanel />; return <CanvasPanel />;
case "project":
return <ProjectPanel />;
case "image":
return <ImageLibrary />;
default: default:
return; return;
} }

View file

@ -0,0 +1,28 @@
import { useContext } from 'react'
import CanvasContext from '../Context/canvasContext/CanvasContext';
import { Button } from '../ui/button';
import { X } from 'lucide-react';
import { ScrollArea } from '../ui/scroll-area';
const ImageLibrary = () => {
const { setSelectedPanel } = useContext(CanvasContext);
return (
<div>
<div className="flex justify-between items-center p-4 border-b">
<h2 className="text-lg font-semibold">Image Library</h2>
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedPanel("")}
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="md:h-[calc(90vh-190px)] px-4 py-4">
{/* Image library content goes here */}
</ScrollArea>
</div>
);
}
export default ImageLibrary

View file

@ -0,0 +1,119 @@
import { useContext } from 'react';
import CanvasContext from '../Context/canvasContext/CanvasContext';
import { Button } from '../ui/button';
import { Loader, Trash, X } from 'lucide-react';
import { ScrollArea } from '../ui/scroll-area';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { deleteProject, getProjects } from '@/api/projectApi';
import { useNavigate } from 'react-router-dom';
import { toast } from '@/hooks/use-toast';
import ActiveObjectContext from '../Context/activeObject/ObjectContext';
import { FixedSizeList as List } from 'react-window'; // Import FixedSizeList from react-window
export const ProjectPanel = () => {
const { setSelectedPanel, canvas } = useContext(CanvasContext);
const { setActiveObject } = useContext(ActiveObjectContext);
const navigate = useNavigate();
const queryClient = useQueryClient(); // Initialize query client
const { data: projects, isLoading: projectLoading, isSuccess: projectSuccess } = useQuery({
queryKey: ['projects'],
queryFn: getProjects, // Simplified function call
});
// To delete a project
const { mutate: projectDelete, isPending: deletePending } = useMutation({
mutationFn: async (id) => await deleteProject(id),
onSuccess: (data, id) => {
if (data?.status === 200) {
toast({
title: data?.status,
description: data?.message,
});
// Invalidate queries to refresh data
queryClient.invalidateQueries({ queryKey: ["projects"] });
queryClient.invalidateQueries({ queryKey: ["project", id] });
// Clear canvas if it exists
if (canvas) {
canvas.clear();
canvas.renderAll();
canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
}
setActiveObject(null);
setSelectedPanel("");
navigate("/");
} else {
toast({
title: data?.status,
description: data?.message,
variant: "destructive"
});
}
},
onError: (error) => {
toast({
title: "Error",
description: error.message || "Failed to delete the project",
variant: "destructive",
});
}
});
// Row renderer for react-window
const Row = ({ index, style }) => {
const project = projects?.data?.[index]; // Get project by index
return (
<div key={project?.id} className='flex flex-col gap-1 bg-red-50 p-1 rounded-md' style={style}>
<div className='rounded-md flex p-1 flex-col gap-1 hover:bg-red-100 cursor-pointer transition-all' onClick={() => navigate(`/${project.id}`)}>
<p className='font-bold text-sm truncate'>{project?.name}</p>
<p className='text-xs truncate'>{project?.description} </p>
<img className='rounded-md' src={project?.preview_url} alt="each_project" />
</div>
<Button
disabled={deletePending}
className="w-fit p-1 ml-auto" size="small" onClick={() => { projectDelete(project?.id) }}><Trash className="h-4 w-4" /></Button>
</div>
);
};
return (
<div>
<div className="flex justify-between items-center p-4 border-b">
<h2 className="text-lg font-semibold">Projects</h2>
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedPanel("")}
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="md:h-[calc(90vh-190px)] px-4 py-4">
{
projectLoading && <p><Loader className="animate-spin mx-auto" /></p>
}
{
!projectLoading && projectSuccess && projects?.status === 200 &&
<List
height={500} // Set the height of the viewport
itemCount={projects?.data?.length} // Total number of items
itemSize={300} // Height of each row
width="100%" // Full width of the container
>
{Row}
</List>
}
{
projects?.status !== 200 && <p>{projects?.message}</p>
}
</ScrollArea>
</div>
);
};

View file

@ -49,16 +49,18 @@ const SaveCanvas = () => {
title: data?.status, title: data?.status,
description: data?.message, description: data?.message,
}) })
console.log(data);
// Invalidate a single query key // Invalidate a single query key
queryClient.invalidateQueries({ queryKey: ["project", id] }); queryClient.invalidateQueries({ queryKey: ["project", id] });
// to load the object into canvas // Clear canvas if it exists
// canvas.loadFromJSON(data?.data?.object, () => { if (canvas) {
// canvas.renderAll(); canvas.clear();
// }, (err) => { canvas.renderAll();
// console.error('Error loading object:', err); canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
// }); }
setActiveObject(null);
setSelectedPanel("");
navigate("/");
} }
else { else {
toast({ toast({
@ -81,15 +83,19 @@ const SaveCanvas = () => {
title: data?.status, title: data?.status,
description: data?.message, description: data?.message,
}) })
// Clear canvas if it exists
if (canvas) {
canvas.clear();
canvas.renderAll();
canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
}
setActiveObject(null);
// Invalidate a single query key // Invalidate a single query key
queryClient.invalidateQueries({ queryKey: ["project", id] }); queryClient.invalidateQueries({ queryKey: ["project", id] });
setSelectedPanel(""); setSelectedPanel("");
navigate("/"); navigate("/");
// for clear canvas
canvas.clear();
canvas.renderAll();
canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
setActiveObject(null);
} }
else { else {
toast({ toast({
@ -179,19 +185,14 @@ const SaveCanvas = () => {
if (saveCanvas?.preview_url) { if (saveCanvas?.preview_url) {
try { try {
removeCanvasImage(saveCanvas?.preview_url); removeCanvasImage(saveCanvas?.preview_url);
console.log("Image removed", saveCanvas?.preview_url);
} catch (error) { } catch (error) {
console.error("Error removing image:", error); console.error("Error removing image:", error);
} }
} else { } else {
try { try {
console.log("Capturing image...");
const file = await captureImage(); const file = await captureImage();
console.log("Captured file:", file);
if (file) { if (file) {
uploadCanvasImage({ file, id }); uploadCanvasImage({ file, id });
console.log("Image uploaded successfully");
} }
} catch (error) { } catch (error) {
console.error("Error capturing image:", error); console.error("Error capturing image:", error);
@ -270,13 +271,18 @@ const SaveCanvas = () => {
return ( return (
<div className='my-2'> <div className='my-2'>
<h2 className='font-bold text-sm flex gap-2'><Save /> Save Canvas </h2> <h2 className='font-bold text-sm flex gap-2 my-4'><Save /> Save Canvas </h2>
{
saveCanvas?.preview_url &&
<div className='bg-red-50 p-2 my-2'> <div className='bg-red-50 p-2 my-2'>
{ {
saveCanvas?.preview_url && saveCanvas?.preview_url &&
<img src={saveCanvas?.preview_url} alt="" className='rounded-md shadow-sm' /> <img src={saveCanvas?.preview_url} alt="" className='rounded-md shadow-sm' />
} }
</div> </div>
}
<div> <div>
<Label>Name:</Label> <Label>Name:</Label>
<Input <Input