Started snapping objects

This commit is contained in:
Artur AGH
2023-12-11 14:45:49 +01:00
parent af650e104c
commit a6135ff964
8 changed files with 74 additions and 25 deletions

View File

@@ -60,7 +60,9 @@ const Canvas = () => {
<div className="relative flex h-full w-full flex-col items-center">
<Toolbar />
<div
className={"flex h-full w-full items-center justify-center"}
className={
"vertical-line horizontal-line flex h-full w-full items-center justify-center"
}
onClick={(e) => {
if (e.target !== e.currentTarget) {
return;

View File

@@ -1,8 +1,8 @@
import React from "react";
import { Button } from "@/components/ui/button";
import { deleteStageItem } from "@/store/app.slice";
import { useAppDispatch } from "@/hooks";
import { BsTrash3 } from "react-icons/bs";
import { IconButton } from "@radix-ui/themes";
import { Trash2 } from "lucide-react";
type Props = {
selectedItemId: string;
@@ -14,12 +14,8 @@ export const DeleteShapeButton = ({ selectedItemId }: Props) => {
dispatch(deleteStageItem(selectedItemId));
};
return (
<Button
variant="destructive"
color="cyan"
onClick={() => deleteTextHandler(selectedItemId)}
>
<BsTrash3 />
</Button>
<IconButton onClick={() => deleteTextHandler(selectedItemId)}>
<Trash2 size={18} />
</IconButton>
);
};

View File

@@ -6,14 +6,15 @@ import {
type StageItem,
StageItemType,
} from "@/store/app.slice";
import { useAppDispatch } from "@/hooks";
import { Button } from "@/components/ui/button";
import { HiOutlineLockClosed, HiOutlineLockOpen } from "react-icons/hi2";
import { useAppDispatch, useAppSelector } from "@/hooks";
import { IconButton } from "@radix-ui/themes";
import { GripVertical } from "lucide-react";
import { Eye, GripVertical, Lock, Trash2, Unlock } from "lucide-react";
import { DeleteShapeButton } from "@/components/delete-shape-button";
import VisibleShapeToggle from "@/components/visible-shape-toggle";
export default function LayerItem({ item }: { item: StageItem }) {
const dispatch = useAppDispatch();
const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({
id: item.id,
@@ -33,12 +34,12 @@ export default function LayerItem({ item }: { item: StageItem }) {
style={style}
ref={setNodeRef}
{...attributes}
className="m-2 flex items-center justify-between rounded-md border p-2 text-black"
className="flex items-center justify-between rounded-md border bg-yellow-50 p-2 text-black"
>
<IconButton {...listeners}>
<GripVertical />
<GripVertical size={18} />
</IconButton>
<span className="">{item.type}</span>
<span className="text-sm">{item.type}</span>
{item.type === StageItemType.Text ? (
item.params.text < 5 ? (
@@ -53,9 +54,14 @@ export default function LayerItem({ item }: { item: StageItem }) {
src={item.params.imageUrl}
/>
)}
<Button onClick={handleBlockItemClicked}>
{item.isBlocked ? <HiOutlineLockClosed /> : <HiOutlineLockOpen />}
</Button>
<DeleteShapeButton selectedItemId={selectedItemId} />
<VisibleShapeToggle />
<span onClick={() => handleBlockItemClicked()}>
{item.isBlocked ? <Lock size={18} /> : <Unlock size={18} />}
</span>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { type ImageConfig } from "konva/lib/shapes/Image";
import { useRef, useEffect, type ElementRef } from "react";
import { Transformer, Image } from "react-konva";
import { type ElementRef, useEffect, useRef } from "react";
import { Image, Transformer } from "react-konva";
import useImage from "use-image";
type TransformableImageConfig = Omit<ImageConfig, "image"> & {
@@ -37,10 +37,26 @@ export const TransformableImage = ({
trRef.current?.getLayer()?.batchDraw();
}
}, [isSelected]);
return (
<>
<Image
onDragMove={(e) => {
console.log(e);
const positionX = e.target.x() + e.target.width() / 2;
const positionY = e.target.y() + e.target.height() / 2;
const canvasXCenter = (e.target.getStage()?.width() ?? 0) / 2;
const canvasYCenter = (e.target.getStage()?.height() ?? 0) / 2;
const isInTheXCenter =
Math.abs(Math.round(canvasXCenter) - Math.round(positionX)) <= 5;
const isInTheYCenter =
Math.abs(Math.round(canvasYCenter) - Math.round(positionY)) <= 5;
document.body.classList.toggle("show-vertical-line", isInTheXCenter);
document.body.classList.toggle(
"show-horizontal-line",
isInTheYCenter,
);
}}
id={id}
alt={"canvas image"}
onClick={onSelect}
@@ -53,10 +69,16 @@ export const TransformableImage = ({
if (isBlocked) {
return;
}
const positionX = e.target.x() + e.target.width() / 2;
const canvasXCenter = (e.target.getStage()?.width() ?? 0) / 2;
const isInTheCenter =
Math.abs(Math.round(canvasXCenter) - Math.round(positionX)) <= 5;
onChange({
...imageProps,
image,
x: e.target.x(),
x: isInTheCenter
? canvasXCenter - e.target.width() / 2
: e.target.x(),
y: e.target.y(),
});
}}
@@ -80,6 +102,8 @@ export const TransformableImage = ({
height: Math.max(node.height() * scaleY),
});
}}
x={imageProps.x}
y={imageProps.y}
/>
{isSelected && (
<Transformer

View File

@@ -0,0 +1,5 @@
import { Eye } from "lucide-react";
export default function VisibleShapeToggle() {
return <Eye size={18} />;
}

15
src/index.css Normal file
View File

@@ -0,0 +1,15 @@
.show-horizontal-line .horizontal-line::before {
content: '';
border-bottom: 1px solid red;
width: 100%;
position: absolute;
top: 50%;
z-index:9999
}
.show-vertical-line .vertical-line::after {
content: '';
border-right: 1px dotted #8a8888;
height: 100%;
position: absolute;
left: 50%
}

View File

@@ -5,6 +5,8 @@ import { type AppType } from "next/app";
import { api } from "@/utils/api";
import "@/styles/globals.css";
import "../index.css";
import { Layout } from "@/components/layout/layout";
import { Provider } from "react-redux";
import { store } from "@/store/store";

View File

@@ -1,6 +1,5 @@
import dynamic from "next/dynamic";
import Head from "next/head";
const Canvas = dynamic(() => import("../components/canvas"), { ssr: false });
export default function Home() {