From 8a553256b2679d05c2927f5bfd586fb7e14dafa1 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sat, 28 Sep 2024 17:57:06 -0300
Subject: [PATCH 01/10] Add text panel
---
components/Canvas.tsx | 68 ++++--
components/textFields/TextFieldsLayer.tsx | 5 +
.../toolbar/{SidePanel.tsx => ShapePanel.tsx} | 2 +-
components/toolbar/TextPanel.tsx | 220 ++++++++++++++++++
4 files changed, 275 insertions(+), 20 deletions(-)
rename components/toolbar/{SidePanel.tsx => ShapePanel.tsx} (99%)
create mode 100644 components/toolbar/TextPanel.tsx
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index a7e28ec..a6378bf 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -24,7 +24,8 @@ import {
TEXT_DEFAULT_HEIGHT,
TEXT_DEFAULT_WIDTH,
} from "./textFields/textFieldUtils";
-import SidePanel from "./toolbar/SidePanel";
+import ShapePanel from "./toolbar/ShapePanel";
+import TextPanel from "./toolbar/TextPanel";
export interface StageSizeType {
width: number;
@@ -82,7 +83,8 @@ export default function Canvas() {
const [isConfirmationModalOpen, setConfirmationModalOpen] = useState(false); // confirmation modal for delete button - clear canvas
- const [isSidePanelVisible, setSidePanelVisible] = useState(false);
+ const [isShapePanelVisible, setShapePanelVisible] = useState(false);
+ const [isTextPanelVisible, setTextPanelVisible] = useState(false);
// Dark mode listener
useEffect(() => {
@@ -309,7 +311,7 @@ export default function Canvas() {
setNewObject(newShape);
dispatch(selectCanvasObject(newShapeId));
- setSidePanelVisible(true);
+ setShapePanelVisible(true);
};
const handleMouseDown = (e: any) => {
@@ -372,12 +374,17 @@ export default function Canvas() {
dispatch(selectCanvasObject(""));
}
+ // side panel
if (
e.target === e.target.getStage() ||
- e.target.attrs.name?.includes("ink") ||
- e.target.attrs.name?.includes("text")
+ e.target.attrs.name?.includes("ink")
) {
- setSidePanelVisible(false);
+ setShapePanelVisible(false);
+ setTextPanelVisible(false);
+ } else if (e.target.attrs.name?.includes("text")) {
+ setShapePanelVisible(false);
+ } else if (e.target.attrs.name?.includes("shape")) {
+ setTextPanelVisible(false);
}
};
@@ -455,7 +462,7 @@ export default function Canvas() {
setSelectedObjectId={(newObjectId) =>
dispatch(selectCanvasObject(newObjectId))
}
- setSidePanelVisible={setSidePanelVisible}
+ setSidePanelVisible={setShapePanelVisible}
/>
- {isSidePanelVisible && (
- setSidePanelVisible(false)}
- strokeWidth={strokeWidth}
- setStrokeWidth={(newWidth) => updateStyle("strokeWidth", newWidth)}
- color={strokeColor}
- onSelectColor={(newColor) => updateStyle("stroke", newColor)}
- fillColor={fillColor}
- onSelectFillColor={(newColor) => updateStyle("fill", newColor)}
- />
- )}
+ setShapePanelVisible(false)}
+ strokeWidth={strokeWidth}
+ setStrokeWidth={(newWidth) => updateStyle("strokeWidth", newWidth)}
+ color={strokeColor}
+ onSelectColor={(newColor) => updateStyle("stroke", newColor)}
+ fillColor={fillColor}
+ onSelectFillColor={(newColor) => updateStyle("fill", newColor)}
+ />
+ setTextPanelVisible(false)}
+ textSize={0}
+ setTextSize={function (newSize: number): void {
+ throw new Error("Function not implemented.");
+ }}
+ textStyle={[]}
+ setTextStyle={function (newStyle: string[]): void {
+ throw new Error("Function not implemented.");
+ }}
+ textColor={""}
+ onSelectTextColor={function (newColor: string): void {
+ throw new Error("Function not implemented.");
+ }}
+ textAlign={""}
+ setTextAlign={function (newAlign: string): void {
+ throw new Error("Function not implemented.");
+ }}
+ lineHeight={0}
+ setLineHeight={function (newHeight: number): void {
+ throw new Error("Function not implemented.");
+ }}
+ />
>
);
}
diff --git a/components/textFields/TextFieldsLayer.tsx b/components/textFields/TextFieldsLayer.tsx
index e873f04..be7fb2a 100644
--- a/components/textFields/TextFieldsLayer.tsx
+++ b/components/textFields/TextFieldsLayer.tsx
@@ -13,6 +13,7 @@ type Props = {
newAttrs: Partial,
selectedObjectId: string,
) => void;
+ setSidePanelVisible: (isVisible: boolean) => void;
};
export default function TextFieldsLayer({
@@ -21,6 +22,7 @@ export default function TextFieldsLayer({
selectedObjectId,
setSelectedObjectId,
onChange,
+ setSidePanelVisible,
}: Props) {
const { selectedTool } = useSelector((state: RootState) => state.canvas);
@@ -40,6 +42,9 @@ export default function TextFieldsLayer({
if (selectedTool === "select") {
setSelectedObjectId(text.id);
+ // Open side panel
+ setSidePanelVisible(true);
+
// Update cursor style
const stage = e.target.getStage();
if (stage) {
diff --git a/components/toolbar/SidePanel.tsx b/components/toolbar/ShapePanel.tsx
similarity index 99%
rename from components/toolbar/SidePanel.tsx
rename to components/toolbar/ShapePanel.tsx
index f79413a..00b5e73 100644
--- a/components/toolbar/SidePanel.tsx
+++ b/components/toolbar/ShapePanel.tsx
@@ -26,7 +26,7 @@ type Props = {
onSelectFillColor: (newColor: string) => void;
};
-export default function SidePanel({
+export default function ShapePanel({
onClose,
isOpen,
strokeWidth,
diff --git a/components/toolbar/TextPanel.tsx b/components/toolbar/TextPanel.tsx
new file mode 100644
index 0000000..b821ef0
--- /dev/null
+++ b/components/toolbar/TextPanel.tsx
@@ -0,0 +1,220 @@
+import * as React from "react";
+import {
+ Box,
+ Drawer,
+ Typography,
+ Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ Divider,
+ IconButton,
+ TextField,
+ Slider,
+ ToggleButtonGroup,
+ ToggleButton,
+} from "@mui/material";
+import CloseIcon from "@mui/icons-material/Close";
+import { HexColorPicker } from "react-colorful";
+import FormatBoldIcon from "@mui/icons-material/FormatBold";
+import FormatItalicIcon from "@mui/icons-material/FormatItalic";
+import FormatUnderlinedIcon from "@mui/icons-material/FormatUnderlined";
+import FormatAlignLeftIcon from "@mui/icons-material/FormatAlignLeft";
+import FormatAlignCenterIcon from "@mui/icons-material/FormatAlignCenter";
+import FormatAlignRightIcon from "@mui/icons-material/FormatAlignRight";
+
+type Props = {
+ onClose: () => void;
+ isOpen: boolean;
+ textSize: number;
+ setTextSize: (newSize: number) => void;
+ textStyle: string[];
+ setTextStyle: (newStyle: string[]) => void;
+ textColor: string;
+ onSelectTextColor: (newColor: string) => void;
+ textAlign: string;
+ setTextAlign: (newAlign: string) => void;
+ lineHeight: number;
+ setLineHeight: (newHeight: number) => void;
+};
+
+export default function TextPanel({
+ onClose,
+ isOpen,
+ textSize,
+ setTextSize,
+ textStyle,
+ setTextStyle,
+ textColor,
+ onSelectTextColor,
+ textAlign,
+ setTextAlign,
+ lineHeight,
+ setLineHeight,
+}: Props) {
+ const [colorPickerOpen, setColorPickerOpen] = React.useState(false);
+
+ const handleColorPickerOpen = () => {
+ setColorPickerOpen(true);
+ };
+
+ const handleColorChange = (color: string) => {
+ onSelectTextColor(color);
+ };
+
+ const handleTextStyleChange = (
+ event: React.MouseEvent,
+ newStyles: string[],
+ ) => {
+ setTextStyle(newStyles);
+ };
+
+ const handleTextAlignChange = (
+ event: React.MouseEvent,
+ newAlign: string,
+ ) => {
+ setTextAlign(newAlign);
+ };
+
+ const drawer = (
+ <>
+
+ Edit text
+
+
+
+
+
+ {/* Text Size Section */}
+
+
+ Size
+ setTextSize(Number(e.target.value))}
+ InputProps={{ inputProps: { min: 1 } }}
+ sx={{ width: 80, mt: 1 }}
+ />
+
+
+ {/* Text Style Section */}
+
+
+ Style
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Text Color Section */}
+
+
+ Color
+
+
+
+
+
+
+ {/* Text Alignment Section */}
+
+
+ Alignment
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Line Spacing Section */}
+
+
+ Line spacing
+ setLineHeight(Number(e.target.value))}
+ InputProps={{ inputProps: { min: 1 } }}
+ sx={{ width: 80, mt: 1 }}
+ />
+
+
+ {/* Color Picker Dialog */}
+
+ >
+ );
+
+ return (
+
+ {drawer}
+
+ );
+}
From 6278766f478034439d7dcd047c1c5c91450b3e45 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sat, 28 Sep 2024 23:46:56 -0300
Subject: [PATCH 02/10] Rename
---
components/Canvas.tsx | 9 +++------
components/{textFields => text}/TextField.tsx | 2 +-
.../TextFieldsLayer.tsx => text/TextLayer.tsx} | 2 +-
.../{textFields/textFieldUtils.ts => text/textUtils.ts} | 0
4 files changed, 5 insertions(+), 8 deletions(-)
rename components/{textFields => text}/TextField.tsx (99%)
rename components/{textFields/TextFieldsLayer.tsx => text/TextLayer.tsx} (97%)
rename components/{textFields/textFieldUtils.ts => text/textUtils.ts} (100%)
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index a6378bf..6a5e009 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -6,7 +6,7 @@ import Toolbar from "./toolbar/Toolbar";
import InkLayer from "./ink/InkLayer";
import ShapesLayer from "./shapes/ShapesLayer";
import ConfirmationDialog from "./ConfirmationDialog";
-import TextFieldsLayer from "./textFields/TextFieldsLayer";
+import TextLayer from "./text/TextLayer";
import { RootState } from "../redux/store";
import {
addCanvasObject,
@@ -20,10 +20,7 @@ import {
updateSelectedTool,
} from "../redux/canvasSlice";
import { SHAPE_DEFAULT_HEIGHT, SHAPE_DEFAULT_WIDTH } from "./shapes/shapeUtils";
-import {
- TEXT_DEFAULT_HEIGHT,
- TEXT_DEFAULT_WIDTH,
-} from "./textFields/textFieldUtils";
+import { TEXT_DEFAULT_HEIGHT, TEXT_DEFAULT_WIDTH } from "./text/textUtils";
import ShapePanel from "./toolbar/ShapePanel";
import TextPanel from "./toolbar/TextPanel";
@@ -464,7 +461,7 @@ export default function Canvas() {
}
setSidePanelVisible={setShapePanelVisible}
/>
- ;
diff --git a/components/textFields/TextFieldsLayer.tsx b/components/text/TextLayer.tsx
similarity index 97%
rename from components/textFields/TextFieldsLayer.tsx
rename to components/text/TextLayer.tsx
index be7fb2a..3107381 100644
--- a/components/textFields/TextFieldsLayer.tsx
+++ b/components/text/TextLayer.tsx
@@ -16,7 +16,7 @@ type Props = {
setSidePanelVisible: (isVisible: boolean) => void;
};
-export default function TextFieldsLayer({
+export default function TextLayer({
objects,
newObject,
selectedObjectId,
diff --git a/components/textFields/textFieldUtils.ts b/components/text/textUtils.ts
similarity index 100%
rename from components/textFields/textFieldUtils.ts
rename to components/text/textUtils.ts
From 202b662e741f06303cb15b8ec975b3d1efed4716 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sun, 29 Sep 2024 01:00:09 -0300
Subject: [PATCH 03/10] Adjust textarea move up amount
---
components/text/TextField.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/components/text/TextField.tsx b/components/text/TextField.tsx
index d6c3184..13e47dc 100644
--- a/components/text/TextField.tsx
+++ b/components/text/TextField.tsx
@@ -85,7 +85,7 @@ export default function TextField({
document.body.appendChild(textarea);
// adjust the styles to match
- textarea.id = `textarea-${node.id()}`;
+ textarea.id = `text-${node.id()}-textarea`;
textarea.value = node.text();
textarea.style.position = "absolute";
textarea.style.top = `${areaPosition.y}px`;
@@ -117,7 +117,7 @@ export default function TextField({
// slightly move textarea up
// because it jumps a bit
- const moveUpPx = Math.round(node.fontSize() / 20);
+ const moveUpPx = 2;
transform += `translateY(-${moveUpPx}px)`;
textarea.style.transform = transform;
From bad872ee0e0ab20ecb77d20a4459da5e33a85021 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sun, 29 Sep 2024 01:53:24 -0300
Subject: [PATCH 04/10] Redux refactor for text and shape configurations
---
.vscode/settings.json | 2 +-
components/Canvas.tsx | 45 +++++++++---------------
components/toolbar/TextPanel.tsx | 57 ++++++++++++++----------------
redux/shapeSlice.ts | 33 ++++++++++++++++++
redux/store.ts | 4 +++
redux/textSlice.ts | 59 ++++++++++++++++++++++++++++++++
6 files changed, 139 insertions(+), 61 deletions(-)
create mode 100644 redux/shapeSlice.ts
create mode 100644 redux/textSlice.ts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b319c44..cca127c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,4 @@
{
"prettier.prettierPath": "./node_modules/prettier",
- "cSpell.words": ["Konva"]
+ "cSpell.words": ["Konva", "reduxjs"]
}
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index 6a5e009..40d8d67 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -23,6 +23,11 @@ import { SHAPE_DEFAULT_HEIGHT, SHAPE_DEFAULT_WIDTH } from "./shapes/shapeUtils";
import { TEXT_DEFAULT_HEIGHT, TEXT_DEFAULT_WIDTH } from "./text/textUtils";
import ShapePanel from "./toolbar/ShapePanel";
import TextPanel from "./toolbar/TextPanel";
+import {
+ setFillColor,
+ setStrokeColor,
+ setStrokeWidth,
+} from "@/redux/shapeSlice";
export interface StageSizeType {
width: number;
@@ -63,19 +68,20 @@ export type ToolType =
export type ShapeName = "rectangle" | "oval" | "triangle" | "star";
export default function Canvas() {
- const dispatch = useDispatch();
const [stageSize, setStageSize] = useState();
+ const [isDarkMode, setIsDarkMode] = useState(false);
+
+ const dispatch = useDispatch();
const { canvasObjects, selectedObjectId, selectedTool } = useSelector(
(state: RootState) => state.canvas,
);
- const [isDarkMode, setIsDarkMode] = useState(false);
-
- const [strokeWidth, setStrokeWidth] = useState(5);
- const [strokeColor, setStrokeColor] = useState("#2986cc");
- const [fillColor, setFillColor] = useState("#FFFFFF");
+ const { strokeWidth, strokeColor, fillColor } = useSelector(
+ (state: RootState) => state.shape,
+ );
+ const { textSize, textStyle, textColor, textAlignment, lineSpacing } =
+ useSelector((state: RootState) => state.text);
const [isInProgress, setIsInProgress] = useState(false);
-
const [newObject, setNewObject] = useState(null); // new text/shape object to be added to the canvas
const [isConfirmationModalOpen, setConfirmationModalOpen] = useState(false); // confirmation modal for delete button - clear canvas
@@ -220,9 +226,9 @@ export default function Canvas() {
function updateStyle(property: keyof CanvasObjectType, value: any) {
// Dynamically update state
if (property === "strokeWidth") {
- setStrokeWidth(value);
+ dispatch(setStrokeWidth(value));
} else if (property === "stroke") {
- setStrokeColor(value);
+ dispatch(setStrokeColor(value));
}
// Update object property
@@ -502,26 +508,7 @@ export default function Canvas() {
setTextPanelVisible(false)}
- textSize={0}
- setTextSize={function (newSize: number): void {
- throw new Error("Function not implemented.");
- }}
- textStyle={[]}
- setTextStyle={function (newStyle: string[]): void {
- throw new Error("Function not implemented.");
- }}
- textColor={""}
- onSelectTextColor={function (newColor: string): void {
- throw new Error("Function not implemented.");
- }}
- textAlign={""}
- setTextAlign={function (newAlign: string): void {
- throw new Error("Function not implemented.");
- }}
- lineHeight={0}
- setLineHeight={function (newHeight: number): void {
- throw new Error("Function not implemented.");
- }}
+ selectedObjectId={selectedObjectId}
/>
>
);
diff --git a/components/toolbar/TextPanel.tsx b/components/toolbar/TextPanel.tsx
index b821ef0..889d6e6 100644
--- a/components/toolbar/TextPanel.tsx
+++ b/components/toolbar/TextPanel.tsx
@@ -22,35 +22,26 @@ import FormatUnderlinedIcon from "@mui/icons-material/FormatUnderlined";
import FormatAlignLeftIcon from "@mui/icons-material/FormatAlignLeft";
import FormatAlignCenterIcon from "@mui/icons-material/FormatAlignCenter";
import FormatAlignRightIcon from "@mui/icons-material/FormatAlignRight";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "@/redux/store";
+import {
+ setLineSpacing,
+ setTextAlignment,
+ setTextColor,
+ setTextSize,
+ toggleTextStyle,
+} from "@/redux/textSlice";
type Props = {
onClose: () => void;
isOpen: boolean;
- textSize: number;
- setTextSize: (newSize: number) => void;
- textStyle: string[];
- setTextStyle: (newStyle: string[]) => void;
- textColor: string;
- onSelectTextColor: (newColor: string) => void;
- textAlign: string;
- setTextAlign: (newAlign: string) => void;
- lineHeight: number;
- setLineHeight: (newHeight: number) => void;
+ selectedObjectId: string;
};
export default function TextPanel({
onClose,
isOpen,
- textSize,
- setTextSize,
- textStyle,
- setTextStyle,
- textColor,
- onSelectTextColor,
- textAlign,
- setTextAlign,
- lineHeight,
- setLineHeight,
+ selectedObjectId,
}: Props) {
const [colorPickerOpen, setColorPickerOpen] = React.useState(false);
@@ -58,22 +49,26 @@ export default function TextPanel({
setColorPickerOpen(true);
};
- const handleColorChange = (color: string) => {
- onSelectTextColor(color);
- };
+ const dispatch = useDispatch();
+ const { textSize, textStyle, textColor, textAlignment, lineSpacing } =
+ useSelector((state: RootState) => state.text);
const handleTextStyleChange = (
event: React.MouseEvent,
- newStyles: string[],
+ newStyle: string,
) => {
- setTextStyle(newStyles);
+ dispatch(toggleTextStyle(newStyle));
+ };
+
+ const handleColorChange = (color: string) => {
+ dispatch(setTextColor(color));
};
const handleTextAlignChange = (
event: React.MouseEvent,
- newAlign: string,
+ newValue: "left" | "center" | "right",
) => {
- setTextAlign(newAlign);
+ dispatch(setTextAlignment(newValue));
};
const drawer = (
@@ -99,7 +94,7 @@ export default function TextPanel({
setTextSize(Number(e.target.value))}
+ onChange={(e) => dispatch(setTextSize(Number(e.target.value)))}
InputProps={{ inputProps: { min: 1 } }}
sx={{ width: 80, mt: 1 }}
/>
@@ -157,7 +152,7 @@ export default function TextPanel({
Alignment
Line spacing
setLineHeight(Number(e.target.value))}
+ value={lineSpacing}
+ onChange={(e) => dispatch(setLineSpacing(Number(e.target.value)))}
InputProps={{ inputProps: { min: 1 } }}
sx={{ width: 80, mt: 1 }}
/>
diff --git a/redux/shapeSlice.ts b/redux/shapeSlice.ts
new file mode 100644
index 0000000..9f1e915
--- /dev/null
+++ b/redux/shapeSlice.ts
@@ -0,0 +1,33 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+interface ShapeState {
+ strokeWidth: number;
+ strokeColor: string;
+ fillColor: string;
+}
+
+const initialState: ShapeState = {
+ strokeWidth: 5,
+ strokeColor: "#2986cc",
+ fillColor: "#FFFFFF",
+};
+
+const shapeSlice = createSlice({
+ name: "shape",
+ initialState,
+ reducers: {
+ setStrokeWidth(state, action: PayloadAction) {
+ state.strokeWidth = action.payload;
+ },
+ setStrokeColor(state, action: PayloadAction) {
+ state.strokeColor = action.payload;
+ },
+ setFillColor(state, action: PayloadAction) {
+ state.fillColor = action.payload;
+ },
+ },
+});
+
+export const { setStrokeWidth, setStrokeColor, setFillColor } =
+ shapeSlice.actions;
+export default shapeSlice.reducer;
diff --git a/redux/store.ts b/redux/store.ts
index a740402..25f3a70 100644
--- a/redux/store.ts
+++ b/redux/store.ts
@@ -1,9 +1,13 @@
import { configureStore } from "@reduxjs/toolkit";
import canvasReducer from "./canvasSlice";
+import shapeReducer from "./shapeSlice";
+import textReducer from "./textSlice";
const store = configureStore({
reducer: {
canvas: canvasReducer,
+ shape: shapeReducer,
+ text: textReducer,
},
});
diff --git a/redux/textSlice.ts b/redux/textSlice.ts
new file mode 100644
index 0000000..d425d3a
--- /dev/null
+++ b/redux/textSlice.ts
@@ -0,0 +1,59 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+interface TextState {
+ textSize: number;
+ textStyle: string[];
+ textColor: string;
+ textAlignment: "left" | "center" | "right";
+ lineSpacing: number;
+}
+
+const initialState: TextState = {
+ textSize: 16,
+ textStyle: [],
+ textColor: "#000000",
+ textAlignment: "left",
+ lineSpacing: 1.5,
+};
+
+const textSlice = createSlice({
+ name: "text",
+ initialState,
+ reducers: {
+ setTextSize: (state, action: PayloadAction) => {
+ state.textSize = action.payload;
+ },
+ toggleTextStyle: (state, action: PayloadAction) => {
+ const style = action.payload;
+ if (state.textStyle.includes(style)) {
+ state.textStyle = state.textStyle.filter((s) => s !== style);
+ } else {
+ state.textStyle.push(style);
+ }
+ },
+ setTextColor: (state, action: PayloadAction) => {
+ state.textColor = action.payload;
+ },
+ setTextAlignment: (
+ state,
+ action: PayloadAction<"left" | "center" | "right">,
+ ) => {
+ state.textAlignment = action.payload;
+ },
+ setLineSpacing: (state, action: PayloadAction) => {
+ state.lineSpacing = action.payload;
+ },
+ },
+});
+
+// Export actions
+export const {
+ setTextSize,
+ toggleTextStyle,
+ setTextColor,
+ setTextAlignment,
+ setLineSpacing,
+} = textSlice.actions;
+
+// Export reducer
+export default textSlice.reducer;
From 72a0d7c71d823d6db78667a739cd1a22778aa48c Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sun, 29 Sep 2024 23:11:15 -0300
Subject: [PATCH 05/10] Implement event handlers
---
components/Canvas.tsx | 8 ++--
components/text/TextField.tsx | 38 +++++++++++-----
components/toolbar/TextPanel.tsx | 77 +++++++++++++++++++++++++++++---
redux/textSlice.ts | 11 ++---
4 files changed, 106 insertions(+), 28 deletions(-)
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index 40d8d67..eb18b91 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -41,7 +41,7 @@ export interface CanvasObjectType {
shapeName?: ShapeName;
stroke?: string; // stroke color
strokeWidth?: number;
- fill?: string;
+ fill?: string; // shape fill color / text color
points?: number[];
x?: number;
y?: number;
@@ -51,6 +51,10 @@ export interface CanvasObjectType {
text?: string;
fontSize?: number;
fontFamily?: string;
+ fontStyle?: string;
+ textDecoration?: string;
+ align?: string;
+ lineHeight?: number;
}
export type ObjectType = "ink" | "shape" | "text";
@@ -78,8 +82,6 @@ export default function Canvas() {
const { strokeWidth, strokeColor, fillColor } = useSelector(
(state: RootState) => state.shape,
);
- const { textSize, textStyle, textColor, textAlignment, lineSpacing } =
- useSelector((state: RootState) => state.text);
const [isInProgress, setIsInProgress] = useState(false);
const [newObject, setNewObject] = useState(null); // new text/shape object to be added to the canvas
diff --git a/components/text/TextField.tsx b/components/text/TextField.tsx
index 13e47dc..89fae09 100644
--- a/components/text/TextField.tsx
+++ b/components/text/TextField.tsx
@@ -31,31 +31,35 @@ export default function TextField({
const {
type,
id,
+ text,
x,
y,
width,
height,
+ rotation,
fill,
- text,
+ lineHeight,
fontSize,
- fontFamily,
- rotation,
+ align,
+ fontStyle,
+ textDecoration,
} = objectProps;
const selectedProps = {
type,
id,
+ text,
x,
y,
width,
height,
+ rotation,
fill,
- text,
- lineHeight: 1.5,
+ lineHeight,
fontSize,
- fontFamily,
- fontStyle: "normal",
- rotation,
+ align,
+ fontStyle,
+ textDecoration,
};
const handleTransform = () => {
@@ -85,6 +89,21 @@ export default function TextField({
document.body.appendChild(textarea);
// adjust the styles to match
+ if (node.fontStyle().includes("bold")) {
+ textarea.style.fontWeight = "bold";
+ }
+ if (node.fontStyle().includes("italic")) {
+ textarea.style.fontStyle = "italic";
+ }
+ if (node.textDecoration().includes("underline")) {
+ const offset = node.fontSize() * 0.19;
+ const thickness = node.fontSize() * 0.07;
+
+ textarea.style.textDecoration = "underline";
+ textarea.style.textUnderlineOffset = `${offset}px`;
+ textarea.style.textDecorationThickness = `${thickness}px`;
+ }
+
textarea.id = `text-${node.id()}-textarea`;
textarea.value = node.text();
textarea.style.position = "absolute";
@@ -102,7 +121,6 @@ export default function TextField({
textarea.style.resize = "none";
textarea.style.lineHeight = `${node.lineHeight()}`;
textarea.style.fontFamily = node.fontFamily();
- textarea.style.fontStyle = node.fontStyle();
textarea.style.textAlign = node.align();
textarea.style.color = node.fill() as string;
textarea.style.transformOrigin = "left top";
@@ -117,7 +135,7 @@ export default function TextField({
// slightly move textarea up
// because it jumps a bit
- const moveUpPx = 2;
+ const moveUpPx = node.fontSize() / 14.5;
transform += `translateY(-${moveUpPx}px)`;
textarea.style.transform = transform;
diff --git a/components/toolbar/TextPanel.tsx b/components/toolbar/TextPanel.tsx
index 889d6e6..283f52d 100644
--- a/components/toolbar/TextPanel.tsx
+++ b/components/toolbar/TextPanel.tsx
@@ -29,8 +29,9 @@ import {
setTextAlignment,
setTextColor,
setTextSize,
- toggleTextStyle,
+ setTextStyle,
} from "@/redux/textSlice";
+import { updateCanvasObject } from "@/redux/canvasSlice";
type Props = {
onClose: () => void;
@@ -55,13 +56,47 @@ export default function TextPanel({
const handleTextStyleChange = (
event: React.MouseEvent,
- newStyle: string,
+ newStyle: string[],
) => {
- dispatch(toggleTextStyle(newStyle));
+ console.log("newStyle = ", newStyle);
+ dispatch(setTextStyle(newStyle));
+
+ // fontStyle (bold / italic)
+ let newFontStyle = "";
+ if (newStyle.includes("bold")) {
+ newFontStyle += "bold";
+ }
+ if (newStyle.includes("italic")) {
+ newFontStyle += " italic";
+ }
+ dispatch(
+ updateCanvasObject({
+ id: selectedObjectId,
+ updates: { fontStyle: newFontStyle },
+ }),
+ );
+
+ // textDecoration (underline)
+ let newTextDecoration = "";
+ if (newStyle.includes("underline")) {
+ newTextDecoration += "underline";
+ }
+ dispatch(
+ updateCanvasObject({
+ id: selectedObjectId,
+ updates: { textDecoration: newTextDecoration },
+ }),
+ );
};
const handleColorChange = (color: string) => {
dispatch(setTextColor(color));
+ dispatch(
+ updateCanvasObject({
+ id: selectedObjectId,
+ updates: { fill: color },
+ }),
+ );
};
const handleTextAlignChange = (
@@ -69,6 +104,34 @@ export default function TextPanel({
newValue: "left" | "center" | "right",
) => {
dispatch(setTextAlignment(newValue));
+ dispatch(
+ updateCanvasObject({
+ id: selectedObjectId,
+ updates: { align: newValue },
+ }),
+ );
+ };
+
+ const handleLineSpacingChange = (e: any) => {
+ const value = Number(e.target.value);
+ dispatch(setLineSpacing(value));
+ dispatch(
+ updateCanvasObject({
+ id: selectedObjectId,
+ updates: { lineHeight: value },
+ }),
+ );
+ };
+
+ const handleTextSizeChange = (e: any) => {
+ const value = Number(e.target.value);
+ dispatch(setTextSize(value));
+ dispatch(
+ updateCanvasObject({
+ id: selectedObjectId,
+ updates: { fontSize: value },
+ }),
+ );
};
const drawer = (
@@ -94,8 +157,8 @@ export default function TextPanel({
dispatch(setTextSize(Number(e.target.value)))}
- InputProps={{ inputProps: { min: 1 } }}
+ onChange={handleTextSizeChange}
+ InputProps={{ inputProps: { min: 8, max: 100, step: 1 } }}
sx={{ width: 80, mt: 1 }}
/>
@@ -177,8 +240,8 @@ export default function TextPanel({
dispatch(setLineSpacing(Number(e.target.value)))}
- InputProps={{ inputProps: { min: 1 } }}
+ onChange={handleLineSpacingChange}
+ InputProps={{ inputProps: { min: 1, max: 3, step: 0.5 } }}
sx={{ width: 80, mt: 1 }}
/>
diff --git a/redux/textSlice.ts b/redux/textSlice.ts
index d425d3a..ccdec6f 100644
--- a/redux/textSlice.ts
+++ b/redux/textSlice.ts
@@ -23,13 +23,8 @@ const textSlice = createSlice({
setTextSize: (state, action: PayloadAction) => {
state.textSize = action.payload;
},
- toggleTextStyle: (state, action: PayloadAction) => {
- const style = action.payload;
- if (state.textStyle.includes(style)) {
- state.textStyle = state.textStyle.filter((s) => s !== style);
- } else {
- state.textStyle.push(style);
- }
+ setTextStyle: (state, action: PayloadAction) => {
+ state.textStyle = action.payload;
},
setTextColor: (state, action: PayloadAction) => {
state.textColor = action.payload;
@@ -49,7 +44,7 @@ const textSlice = createSlice({
// Export actions
export const {
setTextSize,
- toggleTextStyle,
+ setTextStyle,
setTextColor,
setTextAlignment,
setLineSpacing,
From 5db6d2808543b7fe24ec61f071463f75fae9bedc Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sun, 29 Sep 2024 23:17:51 -0300
Subject: [PATCH 06/10] Rename
---
.prettierrc | 3 +--
components/Canvas.tsx | 4 ++--
components/shapes/{ShapesLayer.tsx => ShapeLayer.tsx} | 3 ++-
components/text/TextLayer.tsx | 2 ++
4 files changed, 7 insertions(+), 5 deletions(-)
rename components/shapes/{ShapesLayer.tsx => ShapeLayer.tsx} (96%)
diff --git a/.prettierrc b/.prettierrc
index b893548..8756cd8 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -3,6 +3,5 @@
"semi": true,
"singleQuote": false,
"printWidth": 80,
- "endOfLine": "lf",
- "insertFinalNewline": true
+ "endOfLine": "lf"
}
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index eb18b91..d09222e 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -4,7 +4,7 @@ import { v4 as uuid } from "uuid";
import { Stage } from "react-konva";
import Toolbar from "./toolbar/Toolbar";
import InkLayer from "./ink/InkLayer";
-import ShapesLayer from "./shapes/ShapesLayer";
+import ShapeLayer from "./shapes/ShapeLayer";
import ConfirmationDialog from "./ConfirmationDialog";
import TextLayer from "./text/TextLayer";
import { RootState } from "../redux/store";
@@ -456,7 +456,7 @@ export default function Canvas() {
onTouchStart={handleMouseDown}
>
- void;
};
-export default function ShapesLayer({
+export default function ShapeLayer({
objects,
newObject,
onChange,
@@ -51,6 +51,7 @@ export default function ShapesLayer({
// Show the side panel
setSidePanelVisible(true);
+ // update settings to match selected shape's
setWidth(shape.strokeWidth as number);
setBorderColor(shape.stroke as string);
setFillColor(shape.fill as string);
diff --git a/components/text/TextLayer.tsx b/components/text/TextLayer.tsx
index 3107381..f05aa18 100644
--- a/components/text/TextLayer.tsx
+++ b/components/text/TextLayer.tsx
@@ -45,6 +45,8 @@ export default function TextLayer({
// Open side panel
setSidePanelVisible(true);
+ // update settings to match selected text's
+
// Update cursor style
const stage = e.target.getStage();
if (stage) {
From 1d23d2e624222b86f0652f3d5e5f061be06221b9 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Sun, 29 Sep 2024 23:25:03 -0300
Subject: [PATCH 07/10] Update readme
---
README.md | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 349d05c..d511009 100644
--- a/README.md
+++ b/README.md
@@ -21,21 +21,35 @@ Although there isn’t a strict timeline, the project is divided into three phas
## Implemented Features
-- Canvas objects
+- Supported canvas objects
- - Draw
+ - Freehand inking
- Text fields
- Shapes
- Rectangle
- Oval
- Triangle
- Star
- - Adjustable shape borders (width & color)
-- Canvas
+- Edit objects
+
+ - Drag & move, resize, rotate objects
+ - Edit shape
+ - Border width
+ - Color
+ - Fill
+ - Edit text
+ - Double click to edit content
+ - Size
+ - Style (bold, italic, underline)
+ - Color
+ - Alignment
+ - Line spacing
+
+- Canvas operations
- Select object
- - History (undo & redo)
- Delete an object or clear the entire canvas
+ - History (undo & redo)
- Keyboard shortcuts for delete/undo/redo actions
- Persistent canvas data stored in browser's local storage
From 3fc4638782c0714d3a97068dac8cb0d5b5e99020 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Mon, 30 Sep 2024 00:09:35 -0300
Subject: [PATCH 08/10] Update text configs upon selecting a text
---
components/Canvas.tsx | 19 +++++++++++----
components/text/TextLayer.tsx | 25 ++++++++++++++++++-
components/text/textUtils.ts | 40 +++++++++++++++++++++++++++++++
components/toolbar/ShapePanel.tsx | 2 +-
components/toolbar/TextPanel.tsx | 24 ++++++++-----------
redux/textSlice.ts | 9 +++----
6 files changed, 93 insertions(+), 26 deletions(-)
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index d09222e..87a4445 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -20,7 +20,12 @@ import {
updateSelectedTool,
} from "../redux/canvasSlice";
import { SHAPE_DEFAULT_HEIGHT, SHAPE_DEFAULT_WIDTH } from "./shapes/shapeUtils";
-import { TEXT_DEFAULT_HEIGHT, TEXT_DEFAULT_WIDTH } from "./text/textUtils";
+import {
+ getFontStyleStringFromTextStyleArray,
+ getTextDecorationStringFromTextStyleArray,
+ TEXT_DEFAULT_HEIGHT,
+ TEXT_DEFAULT_WIDTH,
+} from "./text/textUtils";
import ShapePanel from "./toolbar/ShapePanel";
import TextPanel from "./toolbar/TextPanel";
import {
@@ -83,6 +88,9 @@ export default function Canvas() {
(state: RootState) => state.shape,
);
+ const { textSize, textStyle, textColor, textAlignment, lineSpacing } =
+ useSelector((state: RootState) => state.text);
+
const [isInProgress, setIsInProgress] = useState(false);
const [newObject, setNewObject] = useState(null); // new text/shape object to be added to the canvas
@@ -260,10 +268,13 @@ export default function Canvas() {
y: y,
width: TEXT_DEFAULT_WIDTH,
height: TEXT_DEFAULT_HEIGHT,
- fill: strokeColor, // use strokeColor for fill for now
- // strokeWidth not applied to text field for now
+ fill: textColor,
text: "Double click to edit.",
- fontSize: 28,
+ fontSize: textSize,
+ align: textAlignment,
+ lineHeight: lineSpacing,
+ fontStyle: getFontStyleStringFromTextStyleArray(textStyle),
+ textDecoration: getTextDecorationStringFromTextStyleArray(textStyle),
fontFamily: "Arial",
};
diff --git a/components/text/TextLayer.tsx b/components/text/TextLayer.tsx
index f05aa18..e382ba4 100644
--- a/components/text/TextLayer.tsx
+++ b/components/text/TextLayer.tsx
@@ -2,7 +2,16 @@ import { Layer } from "react-konva";
import { CanvasObjectType } from "../Canvas";
import TextField from "./TextField";
import { RootState } from "@/redux/store";
-import { useSelector } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
+import { updateCanvasObject } from "@/redux/canvasSlice";
+import {
+ setLineSpacing,
+ setTextAlignment,
+ setTextColor,
+ setTextSize,
+ setTextStyle,
+} from "@/redux/textSlice";
+import { convertTextPropertiesToTextStyleArray } from "./textUtils";
type Props = {
objects: CanvasObjectType[];
@@ -24,6 +33,8 @@ export default function TextLayer({
onChange,
setSidePanelVisible,
}: Props) {
+ const dispatch = useDispatch();
+
const { selectedTool } = useSelector((state: RootState) => state.canvas);
const texts = [
@@ -46,6 +57,18 @@ export default function TextLayer({
setSidePanelVisible(true);
// update settings to match selected text's
+ dispatch(setTextSize(text.fontSize || 28));
+ dispatch(setTextColor(text.fill || "#000"));
+ dispatch(setTextAlignment(text.align || "left"));
+ dispatch(setLineSpacing(text.lineHeight || 1.5));
+ dispatch(
+ setTextStyle(
+ convertTextPropertiesToTextStyleArray(
+ text.fontStyle,
+ text.textDecoration,
+ ),
+ ),
+ );
// Update cursor style
const stage = e.target.getStage();
diff --git a/components/text/textUtils.ts b/components/text/textUtils.ts
index 980c377..547e30f 100644
--- a/components/text/textUtils.ts
+++ b/components/text/textUtils.ts
@@ -2,3 +2,43 @@ export const TEXT_DEFAULT_WIDTH = 200;
export const TEXT_DEFAULT_HEIGHT = 50;
export const TEXT_MIN_WIDTH = 5;
export const TEXT_MIN_HEIGHT = 5;
+
+export function convertTextPropertiesToTextStyleArray(
+ fontStyle: string | undefined,
+ textDecoration: string | undefined,
+): string[] {
+ let textStyle = [];
+ if (fontStyle?.includes("bold")) {
+ textStyle.push("bold");
+ }
+ if (fontStyle?.includes("italic")) {
+ textStyle.push("italic");
+ }
+ if (textDecoration?.includes("underline")) {
+ textStyle.push("underline");
+ }
+ return textStyle;
+}
+
+export function getFontStyleStringFromTextStyleArray(
+ textStyle: string[],
+): string {
+ let fontStyle = "";
+ if (textStyle.includes("bold")) {
+ fontStyle += "bold";
+ }
+ if (textStyle.includes("italic")) {
+ fontStyle += " italic";
+ }
+ return fontStyle;
+}
+
+export function getTextDecorationStringFromTextStyleArray(
+ textStyle: string[],
+): string {
+ let textDecoration = "";
+ if (textStyle.includes("underline")) {
+ textDecoration += "underline";
+ }
+ return textDecoration;
+}
diff --git a/components/toolbar/ShapePanel.tsx b/components/toolbar/ShapePanel.tsx
index 00b5e73..ca58c36 100644
--- a/components/toolbar/ShapePanel.tsx
+++ b/components/toolbar/ShapePanel.tsx
@@ -89,7 +89,7 @@ export default function ShapePanel({
}}
aria-label="Border width"
onChange={(_, value) => setStrokeWidth(value as number)}
- sx={{ flex: 1 }}
+ sx={{ flex: 1, mr: 2 }}
/>
diff --git a/components/toolbar/TextPanel.tsx b/components/toolbar/TextPanel.tsx
index 283f52d..be5a721 100644
--- a/components/toolbar/TextPanel.tsx
+++ b/components/toolbar/TextPanel.tsx
@@ -32,6 +32,10 @@ import {
setTextStyle,
} from "@/redux/textSlice";
import { updateCanvasObject } from "@/redux/canvasSlice";
+import {
+ getFontStyleStringFromTextStyleArray,
+ getTextDecorationStringFromTextStyleArray,
+} from "../text/textUtils";
type Props = {
onClose: () => void;
@@ -58,33 +62,25 @@ export default function TextPanel({
event: React.MouseEvent,
newStyle: string[],
) => {
- console.log("newStyle = ", newStyle);
+ // update textStyle in redux store
dispatch(setTextStyle(newStyle));
+ // update selected object
// fontStyle (bold / italic)
- let newFontStyle = "";
- if (newStyle.includes("bold")) {
- newFontStyle += "bold";
- }
- if (newStyle.includes("italic")) {
- newFontStyle += " italic";
- }
dispatch(
updateCanvasObject({
id: selectedObjectId,
- updates: { fontStyle: newFontStyle },
+ updates: { fontStyle: getFontStyleStringFromTextStyleArray(newStyle) },
}),
);
// textDecoration (underline)
- let newTextDecoration = "";
- if (newStyle.includes("underline")) {
- newTextDecoration += "underline";
- }
dispatch(
updateCanvasObject({
id: selectedObjectId,
- updates: { textDecoration: newTextDecoration },
+ updates: {
+ textDecoration: getTextDecorationStringFromTextStyleArray(newStyle),
+ },
}),
);
};
diff --git a/redux/textSlice.ts b/redux/textSlice.ts
index ccdec6f..d927ac5 100644
--- a/redux/textSlice.ts
+++ b/redux/textSlice.ts
@@ -4,12 +4,12 @@ interface TextState {
textSize: number;
textStyle: string[];
textColor: string;
- textAlignment: "left" | "center" | "right";
+ textAlignment: string;
lineSpacing: number;
}
const initialState: TextState = {
- textSize: 16,
+ textSize: 28,
textStyle: [],
textColor: "#000000",
textAlignment: "left",
@@ -29,10 +29,7 @@ const textSlice = createSlice({
setTextColor: (state, action: PayloadAction) => {
state.textColor = action.payload;
},
- setTextAlignment: (
- state,
- action: PayloadAction<"left" | "center" | "right">,
- ) => {
+ setTextAlignment: (state, action: PayloadAction) => {
state.textAlignment = action.payload;
},
setLineSpacing: (state, action: PayloadAction) => {
From 28664afe1963329b06be61442aaa1b6774f3301d Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Mon, 30 Sep 2024 00:27:22 -0300
Subject: [PATCH 09/10] Fix shape property update on select
---
components/Canvas.tsx | 13 +++----------
components/shapes/ShapeLayer.tsx | 21 +++++++++++----------
2 files changed, 14 insertions(+), 20 deletions(-)
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index 87a4445..65839bc 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -28,11 +28,7 @@ import {
} from "./text/textUtils";
import ShapePanel from "./toolbar/ShapePanel";
import TextPanel from "./toolbar/TextPanel";
-import {
- setFillColor,
- setStrokeColor,
- setStrokeWidth,
-} from "@/redux/shapeSlice";
+import { setStrokeColor, setStrokeWidth } from "@/redux/shapeSlice";
export interface StageSizeType {
width: number;
@@ -236,9 +232,9 @@ export default function Canvas() {
function updateStyle(property: keyof CanvasObjectType, value: any) {
// Dynamically update state
if (property === "strokeWidth") {
- dispatch(setStrokeWidth(value));
+ dispatch(setStrokeWidth(value)); // TODO: Ink property should separate from shape/text
} else if (property === "stroke") {
- dispatch(setStrokeColor(value));
+ dispatch(setStrokeColor(value)); // TODO: Ink property should separate from shape/text
}
// Update object property
@@ -471,9 +467,6 @@ export default function Canvas() {
objects={canvasObjects}
newObject={newObject}
onChange={updateSelectedObject}
- setWidth={setStrokeWidth}
- setBorderColor={setStrokeColor}
- setFillColor={setFillColor}
selectedObjectId={selectedObjectId}
setSelectedObjectId={(newObjectId) =>
dispatch(selectCanvasObject(newObjectId))
diff --git a/components/shapes/ShapeLayer.tsx b/components/shapes/ShapeLayer.tsx
index c420364..d856bec 100644
--- a/components/shapes/ShapeLayer.tsx
+++ b/components/shapes/ShapeLayer.tsx
@@ -4,8 +4,13 @@ import RectangleShape from "./RectangleShape";
import OvalShape from "./OvalShape";
import TriangleShape from "./TriangleShape";
import StarShape from "./StarShape";
-import { useSelector } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
import { RootState } from "@/redux/store";
+import {
+ setFillColor,
+ setStrokeColor,
+ setStrokeWidth,
+} from "@/redux/shapeSlice";
type ShapesLayerProps = {
objects: CanvasObjectType[];
@@ -14,9 +19,6 @@ type ShapesLayerProps = {
newAttrs: Partial,
selectedObjectId: string,
) => void;
- setWidth: (newWidth: number) => void;
- setBorderColor: (newColor: string) => void;
- setFillColor: (newColor: string) => void;
selectedObjectId: string;
setSelectedObjectId: (id: string) => void;
setSidePanelVisible: (isVisible: boolean) => void;
@@ -26,13 +28,12 @@ export default function ShapeLayer({
objects,
newObject,
onChange,
- setWidth,
- setBorderColor,
- setFillColor,
selectedObjectId,
setSelectedObjectId,
setSidePanelVisible,
}: ShapesLayerProps) {
+ const dispatch = useDispatch();
+
const { selectedTool } = useSelector((state: RootState) => state.canvas);
const shapes = [
@@ -52,9 +53,9 @@ export default function ShapeLayer({
setSidePanelVisible(true);
// update settings to match selected shape's
- setWidth(shape.strokeWidth as number);
- setBorderColor(shape.stroke as string);
- setFillColor(shape.fill as string);
+ dispatch(setStrokeWidth(shape.strokeWidth || 5));
+ dispatch(setStrokeColor(shape.stroke || "#2986cc"));
+ dispatch(setFillColor(shape.fill || "#FFFFFF"));
// Update cursor style
const stage = e.target.getStage();
From 8b4d95bbfb9e1a41f6e877757ee4bee60ae302c7 Mon Sep 17 00:00:00 2001
From: Leo Hong <5917188+low-earth-orbit@users.noreply.github.com>
Date: Mon, 30 Sep 2024 00:31:27 -0300
Subject: [PATCH 10/10] Adjust panel visibility upon adding text/shape
---
components/Canvas.tsx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/components/Canvas.tsx b/components/Canvas.tsx
index 65839bc..2aea8a9 100644
--- a/components/Canvas.tsx
+++ b/components/Canvas.tsx
@@ -256,6 +256,9 @@ export default function Canvas() {
}
const addTextField = (x: number, y: number) => {
+ setShapePanelVisible(false);
+ setTextPanelVisible(true);
+
const newObjectId = uuid();
let newObject: CanvasObjectType = {
id: newObjectId,
@@ -279,6 +282,9 @@ export default function Canvas() {
};
const addShape = (shapeName: ShapeName, x: number, y: number) => {
+ setTextPanelVisible(false);
+ setShapePanelVisible(true);
+
const newShapeId = uuid();
const baseShape = {
id: newShapeId,