Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add select mode #53

Merged
merged 1 commit into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
undo,
redo,
setCanvasObjects,
updateSelectedTool,
} from "../redux/canvasSlice";
import { SHAPE_DEFAULT_HEIGHT, SHAPE_DEFAULT_WIDTH } from "./shapes/shapeUtils";
import {
Expand Down Expand Up @@ -51,6 +52,7 @@ export interface CanvasObjectType {
export type ObjectType = "ink" | "shape" | "text";

export type ToolType =
| "select"
| "eraser"
| "pen"
| "addText"
Expand All @@ -64,12 +66,11 @@ export type ShapeName = "rectangle" | "oval" | "triangle" | "star";
export default function Canvas() {
const dispatch = useDispatch();
const [stageSize, setStageSize] = useState<StageSizeType>();
const { canvasObjects, selectedObjectId } = useSelector(
const { canvasObjects, selectedObjectId, selectedTool } = useSelector(
(state: RootState) => state.canvas,
);
const [isDarkMode, setIsDarkMode] = useState(false);

const [selectedTool, setSelectedTool] = useState<ToolType>("pen");
const [strokeColor, setStrokeColor] = useState<string>("#2986cc");
const [strokeWidth, setStrokeWidth] = useState<number>(5);

Expand Down Expand Up @@ -340,22 +341,27 @@ export default function Canvas() {
}

// If the current selected tool is eraser or pen
const pos = e.target.getStage().getPointerPosition();
const newLine: CanvasObjectType = {
id: uuid(),
tool: selectedTool, // eraser or pen
type: "ink",
points: [pos.x, pos.y],
stroke: strokeColor,
strokeWidth: strokeWidth,
};
setNewObject(newLine);
return;
if (selectedTool === "eraser" || selectedTool === "pen") {
const pos = e.target.getStage().getPointerPosition();
const newLine: CanvasObjectType = {
id: uuid(),
tool: selectedTool, // eraser or pen
type: "ink",
points: [pos.x, pos.y],
stroke: strokeColor,
strokeWidth:
selectedTool === "eraser" ? Math.max(strokeWidth, 20) : strokeWidth,
};
setNewObject(newLine);
return;
}
}

// deselect object when clicked on empty area
const clickedOnEmpty = e.target === e.target.getStage();
if (clickedOnEmpty) {
// deselect object when clicked on empty area or clicked on an ink object (created by pen or eraser)
if (
e.target === e.target.getStage() ||
e.target.attrs.name.includes("ink")
) {
dispatch(selectCanvasObject(""));
}
};
Expand Down Expand Up @@ -402,6 +408,9 @@ export default function Canvas() {
if (isInProgress) {
if (newObject) dispatch(addCanvasObject(newObject));

if (selectedTool !== "pen" && selectedTool !== "eraser")
dispatch(updateSelectedTool("select"));

setNewObject(null);
setIsInProgress(false);
}
Expand Down Expand Up @@ -441,7 +450,6 @@ export default function Canvas() {
</Stage>
<Toolbar
objects={canvasObjects}
setTool={setSelectedTool}
color={strokeColor}
onSelectColor={(newColor) => updateStyle("stroke", newColor)}
onDelete={handleDelete}
Expand Down
1 change: 1 addition & 0 deletions components/ink/InkLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default function InkLayer({ objects, newObject }: Props) {
<Layer>
{lines.map((line, i) => (
<Line
name={`ink-${line.tool === "eraser" ? "eraser" : "pen"}`}
key={i}
points={line.points}
stroke={line.stroke}
Expand Down
20 changes: 3 additions & 17 deletions components/shapes/OvalShape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
type Props = {
shapeProps: Partial<CanvasObjectType>;
isSelected: boolean;
onSelect: () => void;
onSelect: (e: any) => void;
onChange: (newAttrs: Partial<CanvasObjectType>) => void;
};

Expand Down Expand Up @@ -48,22 +48,8 @@ export default function OvalShape({
width={width}
height={height}
rotation={rotation}
onClick={onSelect}
onTap={onSelect}
onMouseOver={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "pointer";
}
}}
onMouseLeave={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = ""; // Reset to tool's cursor
}
}}
onClick={(e) => onSelect(e)}
onTap={(e) => onSelect(e)}
onDragEnd={(e) => {
onChange({
...shapeProps,
Expand Down
20 changes: 3 additions & 17 deletions components/shapes/RectangleShape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
type Props = {
shapeProps: Partial<CanvasObjectType>;
isSelected: boolean;
onSelect: () => void;
onSelect: (e: any) => void;
onChange: (newAttrs: Partial<CanvasObjectType>) => void;
};

Expand Down Expand Up @@ -48,22 +48,8 @@ export default function RectangleShape({
width={width}
height={height}
rotation={rotation}
onClick={onSelect}
onTap={onSelect}
onMouseOver={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "pointer";
}
}}
onMouseLeave={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = ""; // Reset to tool's cursor
}
}}
onClick={(e) => onSelect(e)}
onTap={(e) => onSelect(e)}
onDragEnd={(e) => {
onChange({
...shapeProps,
Expand Down
27 changes: 23 additions & 4 deletions components/shapes/ShapesLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import RectangleShape from "./RectangleShape";
import OvalShape from "./OvalShape";
import TriangleShape from "./TriangleShape";
import StarShape from "./StarShape";
import { useSelector } from "react-redux";
import { RootState } from "@/redux/store";

type ShapesLayerProps = {
objects: CanvasObjectType[];
Expand All @@ -27,6 +29,8 @@ export default function ShapesLayer({
selectedObjectId,
setSelectedObjectId,
}: ShapesLayerProps) {
const { selectedTool } = useSelector((state: RootState) => state.canvas);

const shapes = [
...objects.filter((obj: CanvasObjectType) => obj.type === "shape"),
...(newObject && newObject.type === "shape" ? [newObject] : []),
Expand All @@ -36,10 +40,25 @@ export default function ShapesLayer({
const commonProps = {
shapeProps: shape,
isSelected: shape.id === selectedObjectId,
onSelect: () => {
setSelectedObjectId(shape.id);
setColor(shape.stroke as string);
setWidth(shape.strokeWidth as number);
onSelect: (e: any) => {
if (selectedTool === "select") {
setSelectedObjectId(shape.id);
setColor(shape.stroke as string);
setWidth(shape.strokeWidth as number);

// Update cursor style
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grab";
}

// TODO: #18
// console.log("e.target =", e.target);
// e.target.getParent().moveToTop(); // Upon select, move the object Group to top of canvas
// e.target.moveToTop(); // Upon select, move the object to top of canvas
// e.target.getLayer().batchDraw(); // Redraw
}
},
onChange: (newAttrs: Partial<CanvasObjectType>) =>
onChange(newAttrs, shape.id),
Expand Down
34 changes: 31 additions & 3 deletions components/shapes/StarShape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getStrokeWidth } from "./shapeUtils";
type Props = {
shapeProps: Partial<CanvasObjectType>;
isSelected: boolean;
onSelect: () => void;
onSelect: (e: any) => void;
onChange: (newAttrs: Partial<CanvasObjectType>) => void;
};

Expand Down Expand Up @@ -44,8 +44,8 @@ export default function StarShape({
width={width}
height={height}
rotation={rotation}
onClick={onSelect}
onTap={onSelect}
onClick={(e) => onSelect(e)}
onTap={(e) => onSelect(e)}
onDragEnd={(e) => {
onChange({
...shapeProps,
Expand Down Expand Up @@ -96,6 +96,34 @@ export default function StarShape({
ref={trRef}
flipEnabled={false}
shouldOverdrawWholeArea
onMouseOver={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grab";
}
}}
onMouseLeave={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = ""; // Reset to tool's cursor
}
}}
onMouseDown={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grabbing";
}
}}
onMouseUp={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grab";
}
}}
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (Math.abs(newBox.width) < 5 || Math.abs(newBox.height) < 5) {
Expand Down
34 changes: 31 additions & 3 deletions components/shapes/TriangleShape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getStrokeWidth } from "./shapeUtils";
type Props = {
shapeProps: Partial<CanvasObjectType>;
isSelected: boolean;
onSelect: () => void;
onSelect: (e: any) => void;
onChange: (newAttrs: Partial<CanvasObjectType>) => void;
};

Expand Down Expand Up @@ -54,8 +54,8 @@ export default function TriangleShape({
width={width}
height={height}
rotation={rotation}
onClick={onSelect}
onTap={onSelect}
onClick={(e) => onSelect(e)}
onTap={(e) => onSelect(e)}
onDragEnd={(e) => {
onChange({
...shapeProps,
Expand Down Expand Up @@ -103,6 +103,34 @@ export default function TriangleShape({
ref={trRef}
flipEnabled={false}
shouldOverdrawWholeArea
onMouseOver={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grab";
}
}}
onMouseLeave={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = ""; // Reset to tool's cursor
}
}}
onMouseDown={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grabbing";
}
}}
onMouseUp={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "grab";
}
}}
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (Math.abs(newBox.width) < 5 || Math.abs(newBox.height) < 5) {
Expand Down
23 changes: 6 additions & 17 deletions components/textFields/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TEXT_MIN_HEIGHT, TEXT_MIN_WIDTH } from "./textFieldUtils";
type Props = {
objectProps: Partial<CanvasObjectType>;
isSelected: boolean;
onSelect: () => void;
onSelect: (e: any) => void;
onChange: (newAttrs: Partial<CanvasObjectType>) => void;
};

Expand All @@ -29,6 +29,7 @@ export default function TextField({
}, [isSelected]);

const {
type,
id,
x,
y,
Expand All @@ -42,6 +43,7 @@ export default function TextField({
} = objectProps;

const selectedProps = {
type,
id,
x,
y,
Expand Down Expand Up @@ -159,25 +161,12 @@ export default function TextField({
return (
<>
<Text
onClick={onSelect}
onTap={onSelect}
name={type}
onClick={(e) => onSelect(e)}
onTap={(e) => onSelect(e)}
ref={textRef}
{...selectedProps}
draggable={isSelected}
onMouseOver={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = "pointer";
}
}}
onMouseLeave={(e) => {
const stage = e.target.getStage();
if (stage) {
const container = stage.container();
container.style.cursor = ""; // Reset to tool's cursor
}
}}
onDragEnd={(e) => {
onChange({
...selectedProps,
Expand Down
Loading