From 55cd3f963d541961c0f5cff421795755eaa392d0 Mon Sep 17 00:00:00 2001 From: Artur AGH Date: Mon, 2 Oct 2023 15:02:45 +0200 Subject: [PATCH] work in progress --- package.json | 6 ++ pnpm-lock.yaml | 152 +++++++++++++++++++++++++++++- src/components/canvas.tsx | 60 +++++------- src/components/layout/layout.tsx | 15 +++ src/components/layout/sidebar.tsx | 37 ++++++++ src/components/ui/button.tsx | 56 +++++++++++ src/components/ui/input.tsx | 25 +++++ src/hooks/index.ts | 2 + src/hooks/use-app-dispatch.ts | 4 + src/hooks/use-app-selector.ts | 4 + src/pages/_app.tsx | 5 +- src/pages/index.tsx | 12 ++- src/store/app.slice.ts | 47 +++++++++ src/store/store.ts | 11 +++ 14 files changed, 398 insertions(+), 38 deletions(-) create mode 100644 src/components/layout/layout.tsx create mode 100644 src/components/layout/sidebar.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/use-app-dispatch.ts create mode 100644 src/hooks/use-app-selector.ts create mode 100644 src/store/app.slice.ts create mode 100644 src/store/store.ts diff --git a/package.json b/package.json index 0256cca..2bca7e3 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,11 @@ "start": "next start" }, "dependencies": { + "@headlessui/react": "^1.7.17", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.1.1", + "@radix-ui/react-icons": "^1.3.0", + "@reduxjs/toolkit": "^1.9.6", "@t3-oss/env-nextjs": "^0.6.0", "@tanstack/react-query": "^4.32.6", "@trpc/client": "^10.37.1", @@ -23,11 +26,14 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "konva": "^9.2.0", + "lucide-react": "^0.279.0", "next": "^13.4.19", "next-auth": "^4.23.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-icons": "^4.11.0", "react-konva": "^18.2.10", + "react-redux": "^8.1.3", "superjson": "^1.13.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b859eec..c2a1428 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,12 +5,21 @@ settings: excludeLinksFromLockfile: false dependencies: + '@headlessui/react': + specifier: ^1.7.17 + version: 1.7.17(react-dom@18.2.0)(react@18.2.0) '@next-auth/prisma-adapter': specifier: ^1.0.7 version: 1.0.7(@prisma/client@5.2.0)(next-auth@4.23.1) '@prisma/client': specifier: ^5.1.1 version: 5.2.0(prisma@5.2.0) + '@radix-ui/react-icons': + specifier: ^1.3.0 + version: 1.3.0(react@18.2.0) + '@reduxjs/toolkit': + specifier: ^1.9.6 + version: 1.9.6(react-redux@8.1.3)(react@18.2.0) '@t3-oss/env-nextjs': specifier: ^0.6.0 version: 0.6.1(typescript@5.2.2)(zod@3.22.2) @@ -41,6 +50,9 @@ dependencies: konva: specifier: ^9.2.0 version: 9.2.0 + lucide-react: + specifier: ^0.279.0 + version: 0.279.0(react@18.2.0) next: specifier: ^13.4.19 version: 13.4.19(react-dom@18.2.0)(react@18.2.0) @@ -53,9 +65,15 @@ dependencies: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) + react-icons: + specifier: ^4.11.0 + version: 4.11.0(react@18.2.0) react-konva: specifier: ^18.2.10 version: 18.2.10(konva@9.2.0)(react-dom@18.2.0)(react@18.2.0) + react-redux: + specifier: ^8.1.3 + version: 8.1.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) superjson: specifier: ^1.13.1 version: 1.13.1 @@ -176,6 +194,18 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@headlessui/react@1.7.17(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + dependencies: + client-only: 0.0.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@humanwhocodes/config-array@0.11.11: resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} @@ -366,6 +396,33 @@ packages: resolution: {integrity: sha512-dT7FOLUCdZmq+AunLqB1Iz+ZH/IIS1Fz2THmKZQ6aFONrQD/BQ5ecJ7g2wGS2OgyUFf4OaLam6/bxmgdOBDqig==} requiresBuild: true + /@radix-ui/react-icons@1.3.0(react@18.2.0): + resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x + dependencies: + react: 18.2.0 + dev: false + + /@reduxjs/toolkit@1.9.6(react-redux@8.1.3)(react@18.2.0): + resolution: {integrity: sha512-Gc4ikl90ORF4viIdAkY06JNUnODjKfGxZRwATM30EdHq8hLSVoSrwXne5dd739yenP5bJxAX7tLuOWK5RPGtrw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 + react-redux: ^7.2.1 || ^8.0.2 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + dependencies: + immer: 9.0.21 + react: 18.2.0 + react-redux: 8.1.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) + redux: 4.2.1 + redux-thunk: 2.4.2(redux@4.2.1) + reselect: 4.1.8 + dev: false + /@rushstack/eslint-patch@1.3.3: resolution: {integrity: sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==} dev: true @@ -479,6 +536,13 @@ packages: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: true + /@types/hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==} + dependencies: + '@types/react': 18.2.21 + hoist-non-react-statics: 3.3.2 + dev: false + /@types/json-schema@7.0.12: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true @@ -498,7 +562,6 @@ packages: resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: '@types/react': 18.2.21 - dev: true /@types/react-reconciler@0.28.4: resolution: {integrity: sha512-Xd55E2aLI9Q/ikDQEmfRzIwYJs4oO0h9ZHA3FZDakzt1WR6JMZcpqtCZlF97I72KVjoY4rHXU5TfvkRDOyr/rg==} @@ -520,6 +583,10 @@ packages: resolution: {integrity: sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==} dev: true + /@types/use-sync-external-store@0.0.3: + resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} + dev: false + /@types/uuid@9.0.3: resolution: {integrity: sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==} dev: false @@ -1677,11 +1744,21 @@ packages: dependencies: function-bind: 1.1.1 + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} dev: true + /immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + dev: false + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -2005,6 +2082,14 @@ packages: dependencies: yallist: 4.0.0 + /lucide-react@0.279.0(react@18.2.0): + resolution: {integrity: sha512-LJ8g66+Bxc3t3x9vKTeK3wn3xucrOQGfJ9ou9GsBwCt2offsrT2BB90XrTrIzE1noYYDe2O8jZaRHi6sAHXNxw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2484,9 +2569,20 @@ packages: scheduler: 0.23.0 dev: false + /react-icons@4.11.0(react@18.2.0): + resolution: {integrity: sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==} + peerDependencies: + react: '*' + dependencies: + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: true + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: false /react-konva@18.2.10(konva@9.2.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==} @@ -2515,6 +2611,40 @@ packages: scheduler: 0.23.0 dev: false + /react-redux@8.1.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): + resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==} + peerDependencies: + '@types/react': ^16.8 || ^17.0 || ^18.0 + '@types/react-dom': ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + react-native: '>=0.59' + redux: ^4 || ^5.0.0-beta.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + react-dom: + optional: true + react-native: + optional: true + redux: + optional: true + dependencies: + '@babel/runtime': 7.22.15 + '@types/hoist-non-react-statics': 3.3.2 + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + '@types/use-sync-external-store': 0.0.3 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + redux: 4.2.1 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /react-ssr-prepass@1.5.0(react@18.2.0): resolution: {integrity: sha512-yFNHrlVEReVYKsLI5lF05tZoHveA5pGzjFbFJY/3pOqqjGOmMmqx83N4hIjN2n6E1AOa+eQEUxs3CgRnPmT0RQ==} peerDependencies: @@ -2541,6 +2671,20 @@ packages: dependencies: picomatch: 2.3.1 + /redux-thunk@2.4.2(redux@4.2.1): + resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} + peerDependencies: + redux: ^4 + dependencies: + redux: 4.2.1 + dev: false + + /redux@4.2.1: + resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + dependencies: + '@babel/runtime': 7.22.15 + dev: false + /reflect.getprototypeof@1.0.4: resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} engines: {node: '>= 0.4'} @@ -2565,6 +2709,10 @@ packages: functions-have-names: 1.2.3 dev: true + /reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} diff --git a/src/components/canvas.tsx b/src/components/canvas.tsx index 61fffdf..f188660 100644 --- a/src/components/canvas.tsx +++ b/src/components/canvas.tsx @@ -4,17 +4,31 @@ import { } from "@/components/transformable-image"; import type { KonvaEventObject } from "konva/lib/Node"; -import { ChangeEvent, useState } from "react"; +import { ChangeEvent, FormEventHandler, useState } from "react"; import { Layer, Stage } from "react-konva"; import { v1 } from "uuid"; import TransformableText, { TransformableTextProps, } from "./transformable-text"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { useAppDispatch, useAppSelector } from "@/hooks"; +import { appSlice } from "@/store/app.slice"; + +// Provider * const Canvas = () => { + + const dispatch = useAppDispatch() + + const selectedItemId = useAppSelector((state) => state.app.selectedItemId) + + const selectItem = (id: string) => dispatch(appSlice.actions.selectItem(id)) + const [selectedImageId, selectImage] = useState(null); const [selectedTextId, selectText] = useState(null); + const [inputText, setInputText] = useState(""); const [images, setImages] = useState( [], @@ -22,47 +36,25 @@ const Canvas = () => { const [texts, setTexts] = useState([]); - const checkDeselect = (e: KonvaEventObject) => { - // deselect when clicked on empty area - const clickedOnEmpty = e.target === e.target.getStage(); - if (clickedOnEmpty) { - selectImage(null); - } + + + const handleInputChange = (e: React.ChangeEvent) => { + setInputText(e.target.value); }; - const handleImageUploaded = (e: ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; + - const imageId = v1(); - const imageUrl = URL.createObjectURL(file); - setImages((prev) => [...prev, { imageUrl, imageId }]); - }; - - const handleTextAdd = (e: ChangeEvent) => { - const text = e.target.value; - const textId = v1(); - - setTexts((prev) => [...prev, { text, textId }]); - }; - - /*console.log(texts, " ++++ ", images);*/ return ( -
- - +
+ {images.map((image) => { return ( selectImage(image.imageId)} - isSelected={image.imageId === selectedImageId} + onSelect={() => selectItem(image.imageId)} + isSelected={image.imageId === selectedItemId} onChange={(newAttrs) => { setImages( images.map((i) => @@ -78,8 +70,8 @@ const Canvas = () => { {texts.map((text) => { return ( selectText(text.textId)} - isSelected={text.textId === selectedTextId} + onSelect={() => selectItem(text.textId)} + isSelected={text.textId === selectedItemId} onChange={(newAttrs) => { setTexts( texts.map((t) => (t.textId === text.textId ? newAttrs : t)), diff --git a/src/components/layout/layout.tsx b/src/components/layout/layout.tsx new file mode 100644 index 0000000..bee5424 --- /dev/null +++ b/src/components/layout/layout.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "react"; +import { Sidebar } from "./sidebar"; + +type Props = { + children: ReactNode; +}; + +export const Layout = ({ children }: Props) => { + return ( +
+ +
{children}
+
+ ); +}; diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx new file mode 100644 index 0000000..68f2f07 --- /dev/null +++ b/src/components/layout/sidebar.tsx @@ -0,0 +1,37 @@ +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; + +type Props = { + handleImageUploaded: any; + handleInputChange: any; + inputText: any; + handleTextAdd: any; +}; + +export function Sidebar({ + handleImageUploaded, + handleInputChange, + handleTextAdd, + inputText, +}: Props) { + return ( +
+ +
+ + +
+
+ ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..5564245 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from '@radix-ui/react-icons' +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..677d05f --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..d4486cd --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./use-app-dispatch" +export * from "./use-app-selector" \ No newline at end of file diff --git a/src/hooks/use-app-dispatch.ts b/src/hooks/use-app-dispatch.ts new file mode 100644 index 0000000..419d9ca --- /dev/null +++ b/src/hooks/use-app-dispatch.ts @@ -0,0 +1,4 @@ +import type { AppDispatch } from "@/store/store"; +import { useDispatch } from "react-redux"; + +export const useAppDispatch: () => AppDispatch = useDispatch diff --git a/src/hooks/use-app-selector.ts b/src/hooks/use-app-selector.ts new file mode 100644 index 0000000..7266fd1 --- /dev/null +++ b/src/hooks/use-app-selector.ts @@ -0,0 +1,4 @@ +import type { RootState } from "@/store/store"; +import { TypedUseSelectorHook, useSelector } from "react-redux"; + +export const useAppSelector: TypedUseSelectorHook = useSelector \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2843ab6..5356a65 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -5,6 +5,7 @@ import { type AppType } from "next/app"; import { api } from "@/utils/api"; import "@/styles/globals.css"; +import { Layout } from "@/components/layout/layout"; const MyApp: AppType<{ session: Session | null }> = ({ Component, @@ -12,7 +13,9 @@ const MyApp: AppType<{ session: Session | null }> = ({ }) => { return ( - + + + ); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 97082b2..741a605 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,7 +1,17 @@ import dynamic from "next/dynamic"; +import Head from "next/head"; const Canvas = dynamic(() => import("../components/canvas"), { ssr: false }); export default function Home() { - return ; + return ( + <> + + Labbel Application + +
+ +
+ + ); } diff --git a/src/store/app.slice.ts b/src/store/app.slice.ts new file mode 100644 index 0000000..129e5bf --- /dev/null +++ b/src/store/app.slice.ts @@ -0,0 +1,47 @@ +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 { v1 } from "uuid"; + +const initialState = { + selectedItemId: null as string | null, + images: [] as TransformableImageProps["imageProps"][], + texts: [] as TransformableTextProps["textProps"][], +}; + +export const appSlice = createSlice({ + name: "app", + initialState, + reducers: { + addImage: (state, action: PayloadAction>) => { + const file = action.payload.target.files?.[0]; + if (!file) return; + + const imageId = v1(); + const imageUrl = URL.createObjectURL(file); + state.images.push({ imageUrl, id: imageId }); + }, + addText: (state, action: PayloadAction<{ initialValue: string }>) => { + const textId = v1(); + state.texts.push({ text: action.payload.initialValue, id: textId }); + }, + + selectItem: (state, action: PayloadAction) => { + state.selectedItemId = action.payload; + }, + + checkDeselect: ( + state, + action: PayloadAction>, + ) => { + // deselect when clicked on empty area + const clickedOnEmpty = + action.payload.target === action.payload.target.getStage(); + if (clickedOnEmpty) { + state.selectedItemId = null; + } + }, + }, +}); diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 0000000..0ae5ead --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,11 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { appSlice } from "./app.slice"; + +export const store = configureStore({ + reducer: { + [appSlice.name]: appSlice.reducer, + }, +}); + +export type AppDispatch = typeof store.dispatch; +export type RootState = ReturnType;