Skip to content

Commit

Permalink
Merge pull request #19 from low-earth-orbit/shapes
Browse files Browse the repository at this point in the history
Add shapes
  • Loading branch information
low-earth-orbit authored Aug 27, 2024
2 parents 409fbe9 + 64d0571 commit 5d0273e
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 101 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A whiteboard app made with Konva library.

Latest deployed version: [Click Me](https://whiteboard.leohong.dev)
Demo: [Click Me](https://whiteboard.leohong.dev)

## Tech

Expand Down
143 changes: 109 additions & 34 deletions components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ export interface LineType {
}

export interface ShapeType {
shapeName: string;
id: string;
// shapeType: string;
x: number;
y: number;
width: number;
height: number;
x?: number;
y?: number;
width?: number;
height?: number;
radiusX?: number;
radiusY?: number;
points?: number[];
fill?: string;
stroke: string;
strokeWidth: number;
}
Expand All @@ -35,7 +39,7 @@ export default function Canvas() {
const [lines, setLines] = useState<LineType[]>([]);
const [shapes, setShapes] = useState<ShapeType[]>([]);

const [color, setColor] = useState<string>("#0000FF");
const [strokeColor, setStrokeColor] = useState<string>("#0000FF");
const [strokeWidth, setStrokeWidth] = useState<number>(5);

const [stageSize, setStageSize] = useState<StageSizeType>();
Expand All @@ -44,19 +48,88 @@ export default function Canvas() {

const [selectedShapeId, setSelectedShapeId] = useState<string>("");

const addRectangle = () => {
setShapes([
...shapes,
{
id: uuid(),
x: stageSize ? stageSize.width / 2 - 100 : 0,
y: stageSize ? stageSize.height / 2 - 50 : 0,
width: 200,
height: 100,
stroke: color,
strokeWidth: strokeWidth,
},
]);
function updateShapeProperty(property: keyof ShapeType, value: any) {
// Dynamically update state
if (property === "strokeWidth") {
setStrokeWidth(value);
} else if (property === "stroke") {
setStrokeColor(value);
}

// Update shape property
if (selectedShapeId !== "") {
setShapes((prevShapes) => {
return prevShapes.map((shape) =>
shape.id === selectedShapeId
? { ...shape, [property]: value } // Update the selected shape property
: shape
);
});
}
}

const addShape = (shapeName: string) => {
const newShapeId = uuid();

switch (shapeName) {
case "rectangle":
setShapes([
...shapes,
{
shapeName: shapeName,
id: newShapeId,
x: stageSize ? stageSize.width / 2 - 100 : 0,
y: stageSize ? stageSize.height / 2 - 50 : 0,
width: 200,
height: 100,
stroke: strokeColor,
strokeWidth: strokeWidth,
},
]);
break;

case "ellipse":
console.log("inside ellipse case");
console.log("shapes = ", shapes);
setShapes([
...shapes,
{
shapeName: shapeName,
id: newShapeId,
x: stageSize ? stageSize.width / 2 : 0,
y: stageSize ? stageSize.height / 2 : 0,
radiusX: 100,
radiusY: 100,
stroke: strokeColor,
strokeWidth: strokeWidth,
},
]);
break;

case "line":
setShapes([
...shapes,
{
shapeName: shapeName,
id: newShapeId,
points: [
stageSize ? stageSize.width / 2 - 50 : 0,
stageSize ? stageSize.height / 2 : 0,
stageSize ? stageSize.width / 2 + 50 : 0,
stageSize ? stageSize.height / 2 : 0,
],
stroke: strokeColor,
strokeWidth: strokeWidth,
},
]);
break;

default:
console.warn(`Unknown shapeName: ${shapeName}`);
break;
}

setSelectedShapeId(newShapeId);
};

function resetCanvas() {
Expand Down Expand Up @@ -95,7 +168,7 @@ export default function Canvas() {
{
tool,
points: [pos.x, pos.y],
stroke: color,
stroke: strokeColor,
strokeWidth: strokeWidth,
},
]);
Expand Down Expand Up @@ -138,35 +211,37 @@ export default function Canvas() {
onMouseup={handleMouseUp}
onTouchStart={handleMouseDown}
>
<ShapesLayer
shapes={shapes}
setShapes={setShapes}
<FreeDrawLayer
lines={lines}
setLines={setLines}
tool={tool}
color={color}
color={strokeColor}
strokeWidth={strokeWidth}
stageSize={stageSize}
isFreeDrawing={isFreeDrawing}
selectedShapeId={selectedShapeId}
setSelectedShapeId={setSelectedShapeId}
/>
<FreeDrawLayer
lines={lines}
setLines={setLines}
<ShapesLayer
shapes={shapes}
setShapes={setShapes}
tool={tool}
color={color}
color={strokeColor}
strokeWidth={strokeWidth}
stageSize={stageSize}
isFreeDrawing={isFreeDrawing}
selectedShapeId={selectedShapeId}
setSelectedShapeId={setSelectedShapeId}
/>
</Stage>
<Toolbar
selectTool={setTool}
color={color}
selectColor={setColor}
color={strokeColor}
selectColor={(newColor) => updateShapeProperty("stroke", newColor)}
resetCanvas={resetCanvas}
strokeWidth={strokeWidth}
setStrokeWidth={setStrokeWidth}
handleAddRectangle={addRectangle}
setStrokeWidth={(newWidth) =>
updateShapeProperty("strokeWidth", newWidth)
}
handleAddShape={addShape}
/>
</>
);
Expand Down
71 changes: 33 additions & 38 deletions components/ShapesLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { MutableRefObject, useState } from "react";
import { Stage, Layer } from "react-konva";
import { MutableRefObject } from "react";
import { Layer } from "react-konva";
import { ShapeType, StageSizeType } from "./Canvas";
import Rectangle from "./shapes/Rectangle";
import RectangleShape from "./shapes/RectangleShape";
import EllipseShape from "./shapes/EllipseShape";
import LineShape from "./shapes/LineShape";

type ShapesLayerProps = {
shapes: ShapeType[];
Expand All @@ -16,45 +18,38 @@ type ShapesLayerProps = {
};

export default function ShapesLayer({
tool,
shapes,
setShapes,
color,
strokeWidth,
stageSize,
isFreeDrawing,
selectedShapeId,
setSelectedShapeId,
}: ShapesLayerProps) {
return (
<Layer>
{shapes.map((shape, i) => {
const shapeProps = {
id: shape.id,
x: shape.x,
y: shape.y,
width: shape.width,
height: shape.height,
strokeWidth: shape.strokeWidth,
stroke: shape.stroke,
};
function onShapeChange(newAttrs: Partial<ShapeType>, i: number) {
console.log(newAttrs);
const newShapes = shapes.slice(); // Create a shallow copy of the shapes array
newShapes[i] = { ...newShapes[i], ...newAttrs }; // Update the specific shape with new attributes
setShapes(newShapes);
}

return (
<Rectangle
key={i}
shapeProps={shapeProps}
isSelected={shape.id === selectedShapeId}
onSelect={() => {
setSelectedShapeId(shape.id);
}}
onChange={(newAttrs: ShapeType) => {
const newShapes = shapes.slice(); // deep copy
newShapes[i] = newAttrs;
setShapes(newShapes);
}}
/>
);
})}
</Layer>
);
const renderShape = (shape: ShapeType, i: number) => {
const commonProps = {
shapeProps: shape,
isSelected: shape.id === selectedShapeId,
onSelect: () => setSelectedShapeId(shape.id),
onChange: (newAttrs: Partial<ShapeType>) => onShapeChange(newAttrs, i),
};

switch (shape.shapeName) {
case "rectangle":
return <RectangleShape key={shape.id} {...commonProps} />;
case "ellipse":
return <EllipseShape key={shape.id} {...commonProps} />;
case "line":
return <LineShape key={shape.id} {...commonProps} />;
default:
console.warn(`Unknown shape: ${shape.shapeName}`);
return null;
}
};

return <Layer>{shapes.map((shape, i) => renderShape(shape, i))}</Layer>;
}
Loading

0 comments on commit 5d0273e

Please sign in to comment.