toolbar added

This commit is contained in:
Artur AGH
2023-10-03 15:57:10 +02:00
parent 55cd3f963d
commit 13e4e0a1a7
9 changed files with 181 additions and 87 deletions

View File

@@ -14,77 +14,63 @@ import TransformableText, {
import { Button } from "./ui/button";
import { Input } from "./ui/input";
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 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 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);
const deselectHandler = (e) => {
dispatch(checkDeselect(e));
};
return (
<main className="flex">
<Stage width={window.innerWidth} height={window.innerHeight}>
<div className="flex h-screen w-full flex-col items-center justify-between">
<Toolbar />
<Stage
className="bg-white"
width={600}
height={500}
onTouchStart={deselectHandler}
onMouseDown={deselectHandler}
>
<Layer>
{images.map((image) => {
return (
<TransformableImage
onSelect={() => selectItem(image.imageId)}
isSelected={image.imageId === selectedItemId}
onSelect={() => selectItem(image.id)}
isSelected={image.id === selectedItemId}
onChange={(newAttrs) => {
setImages(
images.map((i) =>
i.imageId === image.imageId ? newAttrs : i,
),
);
images.map((i) => (i.id === image.id ? newAttrs : i));
}}
imageProps={image}
key={image.imageId}
key={image.id}
/>
);
})}
{texts.map((text) => {
return (
<TransformableText
onSelect={() => selectItem(text.textId)}
isSelected={text.textId === selectedItemId}
onSelect={() => selectItem(text.id)}
isSelected={text.id === selectedItemId}
onChange={(newAttrs) => {
setTexts(
texts.map((t) => (t.textId === text.textId ? newAttrs : t)),
);
texts.map((t) => (t.id === text.id ? newAttrs : t));
}}
textProps={text}
key={text.textId}
key={text.id}
/>
);
})}
</Layer>
</Stage>
</main>
</div>
);
};

View File

@@ -1,5 +1,6 @@
import { ReactNode } from "react";
import { Sidebar } from "./sidebar";
import { Navbar } from "./navbar";
type Props = {
children: ReactNode;
@@ -7,9 +8,12 @@ type Props = {
export const Layout = ({ children }: Props) => {
return (
<div className="flex h-full ">
<Sidebar {} />
<main>{children}</main>
</div>
<>
<Navbar />
<div className="flex h-full ">
<Sidebar />
<main className="h-full w-full bg-slate-300">{children}</main>
</div>
</>
);
};

View File

@@ -0,0 +1,7 @@
export const Navbar = () => {
return (
<div>
Navbar
</div>
)
}

View File

@@ -1,21 +1,30 @@
import { addImage, addText } from "@/store/app.slice";
import { Button } from "../ui/button";
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({
handleImageUploaded,
handleInputChange,
handleTextAdd,
inputText,
}: Props) {
export function Sidebar() {
const dispatch = useAppDispatch();
const [inputText, setInputText] = useState("");
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 (
<div className="flex flex-col ">
<div className="h-full flex flex-col ">
<Input
type="file"
className="m-[2rem] w-auto "

View 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();
}
};

View File

@@ -6,17 +6,21 @@ import { api } from "@/utils/api";
import "@/styles/globals.css";
import { Layout } from "@/components/layout/layout";
import { Provider } from "react-redux";
import { store } from "@/store/store";
const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}) => {
return (
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
<Provider store={store}>
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
</Provider>
);
};

View File

@@ -1,9 +1,12 @@
import { TransformableImageProps } from "@/components/transformable-image";
import { TransformableTextProps } from "@/components/transformable-text";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { KonvaEventObject } from "konva/lib/Node";
import { ChangeEvent } from "react";
import type { TransformableImageProps } from "@/components/transformable-image";
import type { TransformableTextProps } from "@/components/transformable-text";
import type { PayloadAction } from "@reduxjs/toolkit";
import type { KonvaEventObject } from "konva/lib/Node";
import type { ChangeEvent } from "react";
import { createSlice } from "@reduxjs/toolkit";
import { v1 } from "uuid";
import { TextConfig } from "konva/lib/shapes/Text";
const initialState = {
selectedItemId: null as string | null,
@@ -25,6 +28,7 @@ export const appSlice = createSlice({
},
addText: (state, action: PayloadAction<{ initialValue: string }>) => {
const textId = v1();
state.texts.push({ text: action.payload.initialValue, id: textId });
},
@@ -43,5 +47,20 @@ export const appSlice = createSlice({
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;

View File

@@ -1,7 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
@@ -9,63 +9,63 @@
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
@@ -73,4 +73,16 @@
body {
@apply bg-background text-foreground;
}
}
:root {
height: 100%;
}
body {
height: 100%;
}
#__next {
height: 100%;
}

2
todo.md Normal file
View File

@@ -0,0 +1,2 @@
[ ] image toolbar
[ ] text toolbar