mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-17 05:29:27 +00:00
toolbar added
This commit is contained in:
@@ -14,77 +14,63 @@ import TransformableText, {
|
|||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { useAppDispatch, useAppSelector } from "@/hooks";
|
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||||
import { appSlice } from "@/store/app.slice";
|
import { appSlice, checkDeselect } from "@/store/app.slice";
|
||||||
|
import { Toolbar } from "./toolbar";
|
||||||
|
|
||||||
// Provider *
|
// Provider *
|
||||||
|
|
||||||
const Canvas = () => {
|
const Canvas = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
|
||||||
|
const selectItem = (id: string) => dispatch(appSlice.actions.selectItem(id));
|
||||||
|
const texts = useAppSelector((state) => state.app.texts);
|
||||||
|
const images = useAppSelector((state) => state.app.images);
|
||||||
|
|
||||||
const selectedItemId = useAppSelector((state) => state.app.selectedItemId)
|
const deselectHandler = (e) => {
|
||||||
|
dispatch(checkDeselect(e));
|
||||||
const selectItem = (id: string) => dispatch(appSlice.actions.selectItem(id))
|
|
||||||
|
|
||||||
const [selectedImageId, selectImage] = useState<string | null>(null);
|
|
||||||
const [selectedTextId, selectText] = useState<string | null>(null);
|
|
||||||
const [inputText, setInputText] = useState("");
|
|
||||||
|
|
||||||
const [images, setImages] = useState<TransformableImageProps["imageProps"][]>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [texts, setTexts] = useState<TransformableTextProps["textProps"][]>([]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setInputText(e.target.value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex">
|
<div className="flex h-screen w-full flex-col items-center justify-between">
|
||||||
|
<Toolbar />
|
||||||
<Stage width={window.innerWidth} height={window.innerHeight}>
|
<Stage
|
||||||
|
className="bg-white"
|
||||||
|
width={600}
|
||||||
|
height={500}
|
||||||
|
onTouchStart={deselectHandler}
|
||||||
|
onMouseDown={deselectHandler}
|
||||||
|
>
|
||||||
<Layer>
|
<Layer>
|
||||||
{images.map((image) => {
|
{images.map((image) => {
|
||||||
return (
|
return (
|
||||||
<TransformableImage
|
<TransformableImage
|
||||||
onSelect={() => selectItem(image.imageId)}
|
onSelect={() => selectItem(image.id)}
|
||||||
isSelected={image.imageId === selectedItemId}
|
isSelected={image.id === selectedItemId}
|
||||||
onChange={(newAttrs) => {
|
onChange={(newAttrs) => {
|
||||||
setImages(
|
images.map((i) => (i.id === image.id ? newAttrs : i));
|
||||||
images.map((i) =>
|
|
||||||
i.imageId === image.imageId ? newAttrs : i,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
imageProps={image}
|
imageProps={image}
|
||||||
key={image.imageId}
|
key={image.id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{texts.map((text) => {
|
{texts.map((text) => {
|
||||||
return (
|
return (
|
||||||
<TransformableText
|
<TransformableText
|
||||||
onSelect={() => selectItem(text.textId)}
|
onSelect={() => selectItem(text.id)}
|
||||||
isSelected={text.textId === selectedItemId}
|
isSelected={text.id === selectedItemId}
|
||||||
onChange={(newAttrs) => {
|
onChange={(newAttrs) => {
|
||||||
setTexts(
|
texts.map((t) => (t.id === text.id ? newAttrs : t));
|
||||||
texts.map((t) => (t.textId === text.textId ? newAttrs : t)),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
textProps={text}
|
textProps={text}
|
||||||
key={text.textId}
|
key={text.id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Layer>
|
</Layer>
|
||||||
</Stage>
|
</Stage>
|
||||||
</main>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Sidebar } from "./sidebar";
|
import { Sidebar } from "./sidebar";
|
||||||
|
import { Navbar } from "./navbar";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -7,9 +8,12 @@ type Props = {
|
|||||||
|
|
||||||
export const Layout = ({ children }: Props) => {
|
export const Layout = ({ children }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full ">
|
<>
|
||||||
<Sidebar {} />
|
<Navbar />
|
||||||
<main>{children}</main>
|
<div className="flex h-full ">
|
||||||
</div>
|
<Sidebar />
|
||||||
|
<main className="h-full w-full bg-slate-300">{children}</main>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
7
src/components/layout/navbar.tsx
Normal file
7
src/components/layout/navbar.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const Navbar = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Navbar
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,21 +1,30 @@
|
|||||||
|
import { addImage, addText } from "@/store/app.slice";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import { ChangeEvent, useState } from "react";
|
||||||
|
|
||||||
type Props = {
|
|
||||||
handleImageUploaded: any;
|
|
||||||
handleInputChange: any;
|
|
||||||
inputText: any;
|
|
||||||
handleTextAdd: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Sidebar({
|
export function Sidebar() {
|
||||||
handleImageUploaded,
|
const dispatch = useAppDispatch();
|
||||||
handleInputChange,
|
|
||||||
handleTextAdd,
|
const [inputText, setInputText] = useState("");
|
||||||
inputText,
|
|
||||||
}: Props) {
|
const handleImageUploaded = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
dispatch(addImage(e));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInputText(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(inputText);
|
||||||
|
|
||||||
|
|
||||||
|
const handleTextAdd = () => dispatch(addText({initialValue: inputText}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col ">
|
<div className="h-full flex flex-col ">
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
className="m-[2rem] w-auto "
|
className="m-[2rem] w-auto "
|
||||||
|
|||||||
51
src/components/toolbar.tsx
Normal file
51
src/components/toolbar.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||||
|
import { updateText } from "@/store/app.slice";
|
||||||
|
|
||||||
|
export const Toolbar = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
|
||||||
|
const texts = useAppSelector((state) => state.app.texts);
|
||||||
|
|
||||||
|
const currentText = texts.find((t) => t.id === selectedItemId);
|
||||||
|
|
||||||
|
console.log(currentText);
|
||||||
|
|
||||||
|
if (!currentText) return null;
|
||||||
|
|
||||||
|
const handleFontStyleToggle = (button: "bold" | "italic") => () => {
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
...currentText,
|
||||||
|
fontStyle: getFontStyle(
|
||||||
|
currentText.fontStyle as string | undefined,
|
||||||
|
button,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-6">
|
||||||
|
<button onClick={handleFontStyleToggle("italic")}>Italic</button>
|
||||||
|
<button onClick={handleFontStyleToggle("bold")}>Bold</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFontStyle = (
|
||||||
|
currentStyle: string | undefined,
|
||||||
|
buttonClicked: "bold" | "italic",
|
||||||
|
) => {
|
||||||
|
if (buttonClicked === "bold") {
|
||||||
|
if (currentStyle?.includes("bold")) {
|
||||||
|
return currentStyle?.replace("bold", "").trim();
|
||||||
|
}
|
||||||
|
return ((currentStyle ?? "") + " bold").trim();
|
||||||
|
}
|
||||||
|
if (buttonClicked === "italic") {
|
||||||
|
if (currentStyle?.includes("italic")) {
|
||||||
|
return currentStyle?.replace("italic", "").trim();
|
||||||
|
}
|
||||||
|
return ((currentStyle ?? "") + " italic").trim();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -6,17 +6,21 @@ import { api } from "@/utils/api";
|
|||||||
|
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import { Layout } from "@/components/layout/layout";
|
import { Layout } from "@/components/layout/layout";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { store } from "@/store/store";
|
||||||
|
|
||||||
const MyApp: AppType<{ session: Session | null }> = ({
|
const MyApp: AppType<{ session: Session | null }> = ({
|
||||||
Component,
|
Component,
|
||||||
pageProps: { session, ...pageProps },
|
pageProps: { session, ...pageProps },
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={session}>
|
<Provider store={store}>
|
||||||
<Layout>
|
<SessionProvider session={session}>
|
||||||
<Component {...pageProps} />
|
<Layout>
|
||||||
</Layout>
|
<Component {...pageProps} />
|
||||||
</SessionProvider>
|
</Layout>
|
||||||
|
</SessionProvider>
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { TransformableImageProps } from "@/components/transformable-image";
|
import type { TransformableImageProps } from "@/components/transformable-image";
|
||||||
import { TransformableTextProps } from "@/components/transformable-text";
|
import type { TransformableTextProps } from "@/components/transformable-text";
|
||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { KonvaEventObject } from "konva/lib/Node";
|
import type { KonvaEventObject } from "konva/lib/Node";
|
||||||
import { ChangeEvent } from "react";
|
import type { ChangeEvent } from "react";
|
||||||
|
|
||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { v1 } from "uuid";
|
import { v1 } from "uuid";
|
||||||
|
import { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
selectedItemId: null as string | null,
|
selectedItemId: null as string | null,
|
||||||
@@ -25,6 +28,7 @@ export const appSlice = createSlice({
|
|||||||
},
|
},
|
||||||
addText: (state, action: PayloadAction<{ initialValue: string }>) => {
|
addText: (state, action: PayloadAction<{ initialValue: string }>) => {
|
||||||
const textId = v1();
|
const textId = v1();
|
||||||
|
|
||||||
state.texts.push({ text: action.payload.initialValue, id: textId });
|
state.texts.push({ text: action.payload.initialValue, id: textId });
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -43,5 +47,20 @@ export const appSlice = createSlice({
|
|||||||
state.selectedItemId = null;
|
state.selectedItemId = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateText: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<Omit<TextConfig, "id"> & { id: string }>,
|
||||||
|
) => {
|
||||||
|
const textToUpdate = state.texts.findIndex(
|
||||||
|
(t) => t.id === action.payload.id,
|
||||||
|
);
|
||||||
|
state.texts[textToUpdate] = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const { addImage, addText, selectItem, checkDeselect, updateText } =
|
||||||
|
appSlice.actions;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -74,3 +74,15 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#__next {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user