mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-16 21:19:38 +00:00
Started snapping objects
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
5
src/components/visible-shape-toggle.tsx
Normal file
5
src/components/visible-shape-toggle.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Eye } from "lucide-react";
|
||||
|
||||
export default function VisibleShapeToggle() {
|
||||
return <Eye size={18} />;
|
||||
}
|
||||
15
src/index.css
Normal file
15
src/index.css
Normal 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%
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user