From 24fb28d622b9f39d99540d97eac963b7edae3164 Mon Sep 17 00:00:00 2001 From: jlightner Date: Thu, 26 Mar 2026 05:55:47 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Added=20TextObject=20to=20CanvasObject?= =?UTF-8?q?=20union=20and=20wired=20text=20tool=20into=20K=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "app/src/types/canvas.ts" - "app/src/components/canvas/KonvaStage.tsx" - "app/src/components/canvas/CanvasToolbar.tsx" - "app/src/components/canvas/ObjectPanel.tsx" - "app/src/components/canvas/ShapeProperties.tsx" - "app/src/components/canvas/AlignmentBar.tsx" GSD-Task: S03/T02 --- app/src/components/canvas/AlignmentBar.tsx | 4 ++ app/src/components/canvas/CanvasToolbar.tsx | 1 + app/src/components/canvas/KonvaStage.tsx | 50 ++++++++++++++++++- app/src/components/canvas/ObjectPanel.tsx | 1 + app/src/components/canvas/ShapeProperties.tsx | 6 ++- app/src/types/canvas.ts | 17 ++++++- 6 files changed, 76 insertions(+), 3 deletions(-) diff --git a/app/src/components/canvas/AlignmentBar.tsx b/app/src/components/canvas/AlignmentBar.tsx index ef2fd63..af037ba 100644 --- a/app/src/components/canvas/AlignmentBar.tsx +++ b/app/src/components/canvas/AlignmentBar.tsx @@ -48,6 +48,10 @@ function toBoundingRect(obj: CanvasObject): BoundingRect { h = Math.max(...ys) - Math.min(...ys); break; } + case 'text': + w = obj.width; + h = obj.fontSize * obj.lineHeight; + break; } return { id: obj.id, x: obj.x, y: obj.y, width: w, height: h }; } diff --git a/app/src/components/canvas/CanvasToolbar.tsx b/app/src/components/canvas/CanvasToolbar.tsx index d39961e..984d869 100644 --- a/app/src/components/canvas/CanvasToolbar.tsx +++ b/app/src/components/canvas/CanvasToolbar.tsx @@ -14,6 +14,7 @@ const TOOLS: { tool: CanvasTool; label: string; icon: string }[] = [ { tool: 'circle', label: 'Circle', icon: '○' }, { tool: 'ellipse', label: 'Ellipse', icon: '⬯' }, { tool: 'line', label: 'Line', icon: '╱' }, + { tool: 'text', label: 'Text', icon: 'T' }, ]; // -- Props -------------------------------------------------------------------- diff --git a/app/src/components/canvas/KonvaStage.tsx b/app/src/components/canvas/KonvaStage.tsx index 394ed1e..3055c25 100644 --- a/app/src/components/canvas/KonvaStage.tsx +++ b/app/src/components/canvas/KonvaStage.tsx @@ -23,6 +23,7 @@ import { Ellipse, Line, Image as KonvaImage, + Text as KonvaText, Transformer, Path, } from 'react-konva'; @@ -36,7 +37,7 @@ import { toPx, artboardClipPath } from '../../utils/artboardShapes'; // -- Types -------------------------------------------------------------------- -export type CanvasTool = 'pointer' | 'rect' | 'circle' | 'ellipse' | 'line'; +export type CanvasTool = 'pointer' | 'rect' | 'circle' | 'ellipse' | 'line' | 'text'; export interface KonvaStageProps { width: number; @@ -247,6 +248,9 @@ export default function KonvaStage({ } else if (obj.type === 'ellipse') { (changes as Record).radiusX = Math.max(5, (node.width() * scaleX) / 2); (changes as Record).radiusY = Math.max(5, (node.height() * scaleY) / 2); + } else if (obj.type === 'text') { + // Scale width for wrapping, keep fontSize unchanged + (changes as Record).width = Math.max(20, node.width() * scaleX); } onUpdateObject(obj.id, changes); @@ -315,6 +319,23 @@ export default function KonvaStage({ /> ); + case 'text': + return ( + + ); + default: return null; } @@ -426,6 +447,29 @@ export default function KonvaStage({ dash: [], }; break; + + case 'text': + newObj = { + id: nextId('text'), + type: 'text', + name: 'Text', + x, + y, + rotation: 0, + visible: true, + locked: false, + opacity: 1, + text: 'Text', + fontFamily: 'Roboto', + fontSize: 24, + letterSpacing: 0, + lineHeight: 1.2, + fill: '#000000', + stroke: 'transparent', + strokeWidth: 0, + width: 200, + }; + break; } if (newObj) { @@ -592,6 +636,8 @@ function getObjWidth(obj: CanvasObject): number { case 'line': return Math.max(...obj.points.filter((_, i) => i % 2 === 0)) - Math.min(...obj.points.filter((_, i) => i % 2 === 0)); + case 'text': + return obj.width || obj.fontSize * obj.text.length * 0.6; } } @@ -607,6 +653,8 @@ function getObjHeight(obj: CanvasObject): number { case 'line': return Math.max(...obj.points.filter((_, i) => i % 2 === 1)) - Math.min(...obj.points.filter((_, i) => i % 2 === 1)); + case 'text': + return obj.fontSize * obj.lineHeight; } } diff --git a/app/src/components/canvas/ObjectPanel.tsx b/app/src/components/canvas/ObjectPanel.tsx index 7037a70..aba960f 100644 --- a/app/src/components/canvas/ObjectPanel.tsx +++ b/app/src/components/canvas/ObjectPanel.tsx @@ -17,6 +17,7 @@ const TYPE_ICONS: Record = { ellipse: '⬯', line: '╱', image: '🖼', + text: 'T', }; // -- Props -------------------------------------------------------------------- diff --git a/app/src/components/canvas/ShapeProperties.tsx b/app/src/components/canvas/ShapeProperties.tsx index d761fe4..f6bafc1 100644 --- a/app/src/components/canvas/ShapeProperties.tsx +++ b/app/src/components/canvas/ShapeProperties.tsx @@ -23,6 +23,8 @@ function getWidth(obj: CanvasObject): number { const xs = obj.points.filter((_, i) => i % 2 === 0); return Math.round((Math.max(...xs) - Math.min(...xs)) * 100) / 100; } + case 'text': + return Math.round(obj.width * 100) / 100; } } @@ -39,6 +41,8 @@ function getHeight(obj: CanvasObject): number { const ys = obj.points.filter((_, i) => i % 2 === 1); return Math.round((Math.max(...ys) - Math.min(...ys)) * 100) / 100; } + case 'text': + return Math.round(obj.fontSize * obj.lineHeight * 100) / 100; } } @@ -62,7 +66,7 @@ export default function ShapeProperties({ onUpdate, }: ShapePropertiesProps) { const hasStroke = object.type !== 'image'; - const hasFill = object.type === 'rect' || object.type === 'circle' || object.type === 'ellipse'; + const hasFill = object.type === 'rect' || object.type === 'circle' || object.type === 'ellipse' || object.type === 'text'; const isLine = object.type === 'line'; const handleChange = useCallback( diff --git a/app/src/types/canvas.ts b/app/src/types/canvas.ts index 503c7ea..bf27fdc 100644 --- a/app/src/types/canvas.ts +++ b/app/src/types/canvas.ts @@ -82,12 +82,27 @@ export interface ImageObject extends BaseCanvasObject { src: string; } +export interface TextObject extends BaseCanvasObject { + type: 'text'; + text: string; + fontFamily: string; + fontSize: number; + letterSpacing: number; + lineHeight: number; + fill: string; + stroke: string; + strokeWidth: number; + /** Wrapping width for text layout. */ + width: number; +} + export type CanvasObject = | RectObject | CircleObject | EllipseObject | LineObject - | ImageObject; + | ImageObject + | TextObject; export type CanvasObjectType = CanvasObject['type'];