From 6d9f87db0a708af3df87ef3bfdd302fe8d4ed760 Mon Sep 17 00:00:00 2001 From: kripu77 Date: Sat, 21 Feb 2026 17:59:51 +1100 Subject: [PATCH 01/13] feat(draw): add fillStyle support for shapes --- .../draw/src/engines/basic-shapes/cloud.ts | 2 +- .../draw/src/engines/basic-shapes/comment.ts | 2 +- .../draw/src/engines/basic-shapes/ellipse.ts | 2 +- .../draw/src/engines/basic-shapes/polygon.ts | 2 +- .../src/engines/basic-shapes/rectangle.ts | 2 +- .../src/engines/basic-shapes/round-comment.ts | 2 +- .../engines/basic-shapes/round-rectangle.ts | 2 +- .../draw/src/engines/flowchart/database.ts | 2 +- packages/draw/src/engines/flowchart/delay.ts | 2 +- .../draw/src/engines/flowchart/display.ts | 2 +- .../draw/src/engines/flowchart/document.ts | 2 +- .../draw/src/engines/flowchart/hard-disk.ts | 2 +- .../src/engines/flowchart/internal-storage.ts | 2 +- .../src/engines/flowchart/multi-document.ts | 2 +- .../src/engines/flowchart/note-curly-left.ts | 2 +- .../src/engines/flowchart/note-curly-right.ts | 2 +- .../draw/src/engines/flowchart/note-square.ts | 2 +- packages/draw/src/engines/flowchart/or.ts | 2 +- .../engines/flowchart/predefined-process.ts | 2 +- .../draw/src/engines/flowchart/stored-data.ts | 2 +- .../src/engines/flowchart/summing-junction.ts | 2 +- .../draw/src/engines/flowchart/terminal.ts | 2 +- packages/draw/src/engines/table/table.ts | 2 +- .../draw/src/engines/uml/activity-class.ts | 2 +- packages/draw/src/engines/uml/actor.ts | 2 +- packages/draw/src/engines/uml/assembly.ts | 5 +- .../draw/src/engines/uml/combined-fragment.ts | 2 +- .../draw/src/engines/uml/component-box.ts | 2 +- packages/draw/src/engines/uml/component.ts | 2 +- packages/draw/src/engines/uml/container.ts | 2 +- packages/draw/src/engines/uml/deletion.ts | 2 +- packages/draw/src/engines/uml/note.ts | 2 +- packages/draw/src/engines/uml/package.ts | 2 +- .../src/engines/uml/provided-interface.ts | 5 +- .../src/engines/uml/required-interface.ts | 6 +- packages/draw/src/engines/uml/template.ts | 1 - .../generators/geometry-shape.generator.ts | 3 +- packages/draw/src/interfaces/geometry.ts | 4 + packages/draw/src/interfaces/index.ts | 2 +- packages/draw/src/utils/fillstyle.spec.ts | 95 +++++++++++++++++++ 40 files changed, 137 insertions(+), 48 deletions(-) create mode 100644 packages/draw/src/utils/fillstyle.spec.ts diff --git a/packages/draw/src/engines/basic-shapes/cloud.ts b/packages/draw/src/engines/basic-shapes/cloud.ts index 800502847..2e035501c 100644 --- a/packages/draw/src/engines/basic-shapes/cloud.ts +++ b/packages/draw/src/engines/basic-shapes/cloud.ts @@ -114,7 +114,7 @@ export const CloudEngine: ShapeEngine = { .join('\n') + ' Z'; - const svgElement = rs.path(pathData, { ...options, fillStyle: 'solid' }); + const svgElement = rs.path(pathData, options); setPathStrokeLinecap(svgElement, 'round'); return svgElement; }, diff --git a/packages/draw/src/engines/basic-shapes/comment.ts b/packages/draw/src/engines/basic-shapes/comment.ts index d9db63e40..791d99d1c 100644 --- a/packages/draw/src/engines/basic-shapes/comment.ts +++ b/packages/draw/src/engines/basic-shapes/comment.ts @@ -18,7 +18,7 @@ export const CommentEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { const points = getCommentPoints(rectangle); const rs = PlaitBoard.getRoughSVG(board); - const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); + const polygon = rs.polygon(points, options); setStrokeLinecap(polygon, 'round'); return polygon; }, diff --git a/packages/draw/src/engines/basic-shapes/ellipse.ts b/packages/draw/src/engines/basic-shapes/ellipse.ts index ad48b532a..d2202b935 100644 --- a/packages/draw/src/engines/basic-shapes/ellipse.ts +++ b/packages/draw/src/engines/basic-shapes/ellipse.ts @@ -23,7 +23,7 @@ export function createEllipseEngine(createOptions?: CreateEllipseOptions): Shape draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; const rs = PlaitBoard.getRoughSVG(board); - const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' }); + const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, options); setStrokeLinecap(shape, 'round'); return shape; }, diff --git a/packages/draw/src/engines/basic-shapes/polygon.ts b/packages/draw/src/engines/basic-shapes/polygon.ts index 223928b47..5932485f9 100644 --- a/packages/draw/src/engines/basic-shapes/polygon.ts +++ b/packages/draw/src/engines/basic-shapes/polygon.ts @@ -24,7 +24,7 @@ export function createPolygonEngine(options: CreateOptions): ShapeEngine { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { const points = getPoints(rectangle); const rs = PlaitBoard.getRoughSVG(board); - const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); + const polygon = rs.polygon(points, options); setStrokeLinecap(polygon, 'round'); return polygon; }, diff --git a/packages/draw/src/engines/basic-shapes/rectangle.ts b/packages/draw/src/engines/basic-shapes/rectangle.ts index 2c6ce61b8..8b592168f 100644 --- a/packages/draw/src/engines/basic-shapes/rectangle.ts +++ b/packages/draw/src/engines/basic-shapes/rectangle.ts @@ -5,7 +5,7 @@ import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; export const RectangleEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { - return drawRectangle(board, rectangle, { ...options, fillStyle: 'solid' }); + return drawRectangle(board, rectangle, options); }, isInsidePoint(rectangle: RectangleClient, point: Point) { const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); diff --git a/packages/draw/src/engines/basic-shapes/round-comment.ts b/packages/draw/src/engines/basic-shapes/round-comment.ts index 26be4d691..ca4f442d6 100644 --- a/packages/draw/src/engines/basic-shapes/round-comment.ts +++ b/packages/draw/src/engines/basic-shapes/round-comment.ts @@ -40,7 +40,7 @@ export const RoundCommentEngine: ShapeEngine = { const shape = rs.path( `M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/basic-shapes/round-rectangle.ts b/packages/draw/src/engines/basic-shapes/round-rectangle.ts index 3ea8428ff..09825b2f6 100644 --- a/packages/draw/src/engines/basic-shapes/round-rectangle.ts +++ b/packages/draw/src/engines/basic-shapes/round-rectangle.ts @@ -21,7 +21,7 @@ export const RoundRectangleEngine: ShapeEngine = { rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, - { ...options, fillStyle: 'solid' }, + options, false, getRoundRectangleRadius(rectangle) ); diff --git a/packages/draw/src/engines/flowchart/database.ts b/packages/draw/src/engines/flowchart/database.ts index 373b43581..fe40e2493 100644 --- a/packages/draw/src/engines/flowchart/database.ts +++ b/packages/draw/src/engines/flowchart/database.ts @@ -31,7 +31,7 @@ export const DatabaseEngine: ShapeEngine = { rectangle.y + rectangle.height - rectangle.height * 0.15 } V${rectangle.y + rectangle.height * 0.15}`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/delay.ts b/packages/draw/src/engines/flowchart/delay.ts index 633afaee5..db7c9919f 100644 --- a/packages/draw/src/engines/flowchart/delay.ts +++ b/packages/draw/src/engines/flowchart/delay.ts @@ -22,7 +22,7 @@ export const DelayEngine: ShapeEngine = { 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${ rectangle.x } ${rectangle.y + rectangle.height} Z`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/flowchart/display.ts b/packages/draw/src/engines/flowchart/display.ts index 7bfc909b7..3e917f05f 100644 --- a/packages/draw/src/engines/flowchart/display.ts +++ b/packages/draw/src/engines/flowchart/display.ts @@ -39,7 +39,7 @@ export const DisplayEngine: ShapeEngine = { L${rectangle.x} ${rectangle.y + rectangle.height / 2} Z `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/flowchart/document.ts b/packages/draw/src/engines/flowchart/document.ts index bff6e0b1b..80c5a72c1 100644 --- a/packages/draw/src/engines/flowchart/document.ts +++ b/packages/draw/src/engines/flowchart/document.ts @@ -28,7 +28,7 @@ export const DocumentEngine: ShapeEngine = { rectangle.height - rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/hard-disk.ts b/packages/draw/src/engines/flowchart/hard-disk.ts index 2237e983b..23789f23b 100644 --- a/packages/draw/src/engines/flowchart/hard-disk.ts +++ b/packages/draw/src/engines/flowchart/hard-disk.ts @@ -32,7 +32,7 @@ export const HardDiskEngine: ShapeEngine = { A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0, ${rectangle.x + rectangle.width * 0.15} ${rectangle.y + rectangle.height} H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/internal-storage.ts b/packages/draw/src/engines/flowchart/internal-storage.ts index ec1fbd4a5..03e39b015 100644 --- a/packages/draw/src/engines/flowchart/internal-storage.ts +++ b/packages/draw/src/engines/flowchart/internal-storage.ts @@ -22,7 +22,7 @@ export const InternalStorageEngine: ShapeEngine = { M${rectangle.x} ${rectangle.y + rectangle.height / 10} h${rectangle.width} M${rectangle.x + rectangle.width / 10} ${rectangle.y} v${rectangle.height} `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/multi-document.ts b/packages/draw/src/engines/flowchart/multi-document.ts index a88c74923..7ba86450a 100644 --- a/packages/draw/src/engines/flowchart/multi-document.ts +++ b/packages/draw/src/engines/flowchart/multi-document.ts @@ -74,7 +74,7 @@ export const MultiDocumentEngine: ShapeEngine = { rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3 } `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/note-curly-left.ts b/packages/draw/src/engines/flowchart/note-curly-left.ts index 0fa45f9d4..72b1db376 100644 --- a/packages/draw/src/engines/flowchart/note-curly-left.ts +++ b/packages/draw/src/engines/flowchart/note-curly-left.ts @@ -56,7 +56,7 @@ export const NoteCurlyLeftEngine: ShapeEngine = { ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` ].join(' '); - const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); + const shape = rs.path(pathData, { ...options, fill: 'transparent' }); setStrokeLinecap(shape, 'round'); return shape; }, diff --git a/packages/draw/src/engines/flowchart/note-curly-right.ts b/packages/draw/src/engines/flowchart/note-curly-right.ts index 2687956a0..8aa8e695a 100644 --- a/packages/draw/src/engines/flowchart/note-curly-right.ts +++ b/packages/draw/src/engines/flowchart/note-curly-right.ts @@ -55,7 +55,7 @@ export const NoteCurlyRightEngine: ShapeEngine = { ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` ].join(' '); - const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); + const shape = rs.path(pathData, { ...options, fill: 'transparent' }); setStrokeLinecap(shape, 'round'); return shape; }, diff --git a/packages/draw/src/engines/flowchart/note-square.ts b/packages/draw/src/engines/flowchart/note-square.ts index e289dbca1..acda658aa 100644 --- a/packages/draw/src/engines/flowchart/note-square.ts +++ b/packages/draw/src/engines/flowchart/note-square.ts @@ -21,7 +21,7 @@ export const NoteSquareEngine: ShapeEngine = { `M${rectangle.x + rectangle.width * 0.075} ${rectangle.y + rectangle.height} H${rectangle.x} V${rectangle.y} H${rectangle.x + rectangle.width * 0.075} `, - { ...options, fillStyle: 'solid', fill: 'transparent' } + { ...options, fill: 'transparent' } ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/or.ts b/packages/draw/src/engines/flowchart/or.ts index 2956a5146..611fa0e56 100644 --- a/packages/draw/src/engines/flowchart/or.ts +++ b/packages/draw/src/engines/flowchart/or.ts @@ -17,7 +17,7 @@ export const OrEngine: ShapeEngine = createEllipseEngine({ M${rectangle.x + rectangle.width / 2} ${rectangle.y} L${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height} `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/predefined-process.ts b/packages/draw/src/engines/flowchart/predefined-process.ts index d6bccf025..0310bc946 100644 --- a/packages/draw/src/engines/flowchart/predefined-process.ts +++ b/packages/draw/src/engines/flowchart/predefined-process.ts @@ -24,7 +24,7 @@ export const PredefinedProcessEngine: ShapeEngine = { rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/flowchart/stored-data.ts b/packages/draw/src/engines/flowchart/stored-data.ts index 8132b0338..42e42f89f 100644 --- a/packages/draw/src/engines/flowchart/stored-data.ts +++ b/packages/draw/src/engines/flowchart/stored-data.ts @@ -27,7 +27,7 @@ export const StoredDataEngine: ShapeEngine = { } ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${ rectangle.x + rectangle.width / 10 } ${rectangle.y}`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/flowchart/summing-junction.ts b/packages/draw/src/engines/flowchart/summing-junction.ts index 2e8d533f4..00ec063a4 100644 --- a/packages/draw/src/engines/flowchart/summing-junction.ts +++ b/packages/draw/src/engines/flowchart/summing-junction.ts @@ -35,7 +35,7 @@ export const SummingJunctionEngine: ShapeEngine = createEllipseEngine({ M${line2Points[0][0]} ${line2Points[0][1]} L${line2Points[1][0]} ${line2Points[1][1]} `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/flowchart/terminal.ts b/packages/draw/src/engines/flowchart/terminal.ts index c9be93203..191cd6cf5 100644 --- a/packages/draw/src/engines/flowchart/terminal.ts +++ b/packages/draw/src/engines/flowchart/terminal.ts @@ -21,7 +21,7 @@ export const TerminalEngine: ShapeEngine = { rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, - { ...options, fillStyle: 'solid' }, + options, false, getStartEndRadius(rectangle) ); diff --git a/packages/draw/src/engines/table/table.ts b/packages/draw/src/engines/table/table.ts index fbdf9d18b..8a54e8cc8 100644 --- a/packages/draw/src/engines/table/table.ts +++ b/packages/draw/src/engines/table/table.ts @@ -31,7 +31,7 @@ export const TableEngine: ShapeEngine `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), - { ...options, fillStyle: 'solid', strokeWidth: 4 } + { ...options, strokeWidth: 4 } ); setStrokeLinecap(shape, 'round'); return shape; diff --git a/packages/draw/src/engines/uml/note.ts b/packages/draw/src/engines/uml/note.ts index e5d60637f..93b96f4b5 100644 --- a/packages/draw/src/engines/uml/note.ts +++ b/packages/draw/src/engines/uml/note.ts @@ -30,7 +30,7 @@ export const NoteEngine: ShapeEngine = { M${rectangle.x + rectangle.width - 16} ${rectangle.y} A16 16, 0,0,1, ${rectangle.x + rectangle.width} ${rectangle.y + 16} `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/uml/package.ts b/packages/draw/src/engines/uml/package.ts index a484085f2..d444186a1 100644 --- a/packages/draw/src/engines/uml/package.ts +++ b/packages/draw/src/engines/uml/package.ts @@ -67,7 +67,7 @@ export const PackageEngine: ShapeEngine stroke: strokeColor, strokeWidth, fill, - strokeLineDash + strokeLineDash, + fillStyle: element.fillStyle }); } } diff --git a/packages/draw/src/interfaces/geometry.ts b/packages/draw/src/interfaces/geometry.ts index 9607bfeaa..7cf5623c9 100644 --- a/packages/draw/src/interfaces/geometry.ts +++ b/packages/draw/src/interfaces/geometry.ts @@ -1,6 +1,9 @@ import { PlaitElement, Point } from '@plait/core'; import { DrawTextInfo } from '../generators/text.generator'; import { ParagraphElement, StrokeStyle } from '@plait/common'; +import { Options } from 'roughjs/bin/core'; + +export type FillStyle = Options['fillStyle']; export enum BasicShapes { rectangle = 'rectangle', @@ -100,6 +103,7 @@ export interface PlaitCommonGeometry { + let board: PlaitBoard; + + beforeEach(() => { + board = createTestingBoard([withDraw], []); + }); + + describe('PlaitCommonGeometry interface', () => { + it('should accept fillStyle property on geometry elements', () => { + const element: PlaitGeometry = { + id: 'test-1', + type: 'geometry', + shape: BasicShapes.rectangle, + points: [ + [0, 0], + [100, 100] + ], + fill: '#FF5733', + fillStyle: 'hachure', + strokeColor: '#000000', + strokeWidth: 2 + }; + + expect(element.fillStyle).toBe('hachure'); + }); + + it('should allow all rough.js fill styles', () => { + const fillStyles: FillStyle[] = [ + 'solid', + 'hachure', + 'zigzag', + 'cross-hatch', + 'dots', + 'dashed', + 'zigzag-line' + ]; + + fillStyles.forEach((fillStyle) => { + const element: PlaitGeometry = { + id: `test-${fillStyle}`, + type: 'geometry', + shape: BasicShapes.rectangle, + points: [ + [0, 0], + [100, 100] + ], + fillStyle + }; + + expect(element.fillStyle).toBe(fillStyle); + }); + }); + + it('should make fillStyle optional', () => { + const element: PlaitGeometry = { + id: 'test-optional', + type: 'geometry', + shape: BasicShapes.rectangle, + points: [ + [0, 0], + [100, 100] + ] + }; + + expect(element.fillStyle).toBeUndefined(); + }); + }); + + describe('drawGeometry', () => { + it('should pass fillStyle to shape engine', () => { + const rectangle: RectangleClient = { + x: 0, + y: 0, + width: 100, + height: 100 + }; + + const roughOptions: Options = { + stroke: '#000000', + strokeWidth: 2, + fill: '#FF5733', + fillStyle: 'hachure' + }; + + const result = drawGeometry(board, rectangle, BasicShapes.rectangle, roughOptions); + + expect(result).toBeDefined(); + expect(result instanceof SVGGElement).toBe(true); + }); + }); +}); From 535d70986e05bedb19277d15137e2fca3e4d4a9c Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Fri, 26 Jun 2026 11:08:36 +0800 Subject: [PATCH 02/13] fix(draw): preserve fill style defaults --- packages/draw/src/engines/flowchart/delay.ts | 2 +- .../engines/flowchart/predefined-process.ts | 2 +- .../draw/src/engines/uml/activity-class.ts | 2 +- packages/draw/src/engines/uml/container.ts | 2 +- packages/draw/src/engines/uml/deletion.ts | 1 - .../generators/geometry-shape.generator.ts | 2 +- packages/draw/src/utils/fillstyle.spec.ts | 77 +++++++++++++++++-- 7 files changed, 75 insertions(+), 13 deletions(-) diff --git a/packages/draw/src/engines/flowchart/delay.ts b/packages/draw/src/engines/flowchart/delay.ts index b2abb3354..e54745a07 100644 --- a/packages/draw/src/engines/flowchart/delay.ts +++ b/packages/draw/src/engines/flowchart/delay.ts @@ -23,7 +23,7 @@ export const DelayEngine: ShapeEngine = { }, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${ rectangle.y + rectangle.height } Z`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/flowchart/predefined-process.ts b/packages/draw/src/engines/flowchart/predefined-process.ts index c1fdd75e9..ef6dca167 100644 --- a/packages/draw/src/engines/flowchart/predefined-process.ts +++ b/packages/draw/src/engines/flowchart/predefined-process.ts @@ -23,7 +23,7 @@ export const PredefinedProcessEngine: ShapeEngine = { } ${rectangle.y} L${rectangle.x + rectangle.width * 0.06} ${rectangle.y + rectangle.height} M${ rectangle.x + rectangle.width - rectangle.width * 0.06 } ${rectangle.y} L${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/uml/activity-class.ts b/packages/draw/src/engines/uml/activity-class.ts index cdb7c7ff4..585ad6199 100644 --- a/packages/draw/src/engines/uml/activity-class.ts +++ b/packages/draw/src/engines/uml/activity-class.ts @@ -23,7 +23,7 @@ export const ActiveClassEngine: ShapeEngine = { } ${rectangle.y} L${rectangle.x + rectangle.width * 0.125} ${rectangle.y + rectangle.height} M${ rectangle.x + rectangle.width - rectangle.width * 0.125 } ${rectangle.y} L${rectangle.x + rectangle.width - rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/uml/container.ts b/packages/draw/src/engines/uml/container.ts index 8923d8f7e..c1374b34a 100644 --- a/packages/draw/src/engines/uml/container.ts +++ b/packages/draw/src/engines/uml/container.ts @@ -21,7 +21,7 @@ export const ContainerEngine: ShapeEngine = { `M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${ rectangle.x + 40 } ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, - { ...options, fillStyle: 'solid' } + options ); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/engines/uml/deletion.ts b/packages/draw/src/engines/uml/deletion.ts index 5deed0732..2ec20ee1c 100644 --- a/packages/draw/src/engines/uml/deletion.ts +++ b/packages/draw/src/engines/uml/deletion.ts @@ -32,7 +32,6 @@ export const DeletionEngine: ShapeEngine = { const lines = getDeletionLines(rectangle); const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, - fillStyle: 'solid', strokeWidth: 4 }); setStrokeLinecap(shape, 'round'); diff --git a/packages/draw/src/generators/geometry-shape.generator.ts b/packages/draw/src/generators/geometry-shape.generator.ts index 3d83a32e9..2e10ebcaf 100644 --- a/packages/draw/src/generators/geometry-shape.generator.ts +++ b/packages/draw/src/generators/geometry-shape.generator.ts @@ -27,7 +27,7 @@ export class GeometryShapeGenerator extends Generator strokeWidth, fill, strokeLineDash, - fillStyle: element.fillStyle + fillStyle: element.fillStyle ?? 'solid' }); } } diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index e5982e441..484971e52 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -1,14 +1,24 @@ -import { createTestingBoard, PlaitBoard, RectangleClient } from '@plait/core'; -import { withDraw, PlaitGeometry, BasicShapes, drawGeometry, FillStyle } from '@plait/draw'; +import { BOARD_TO_ROUGH_SVG, createTestingBoard, PlaitBoard, RectangleClient } from '@plait/core'; +import { withDraw, PlaitGeometry, BasicShapes, drawGeometry, FillStyle, FlowchartSymbols, UMLSymbols } from '@plait/draw'; import { Options } from 'roughjs/bin/core'; +import { GeometryShapeGenerator } from '../generators/geometry-shape.generator'; describe('fillStyle', () => { let board: PlaitBoard; beforeEach(() => { board = createTestingBoard([withDraw], []); + BOARD_TO_ROUGH_SVG.set(board, createTestingRoughSVG()); }); + const createTestingRoughSVG = () => { + const createG = () => document.createElementNS('http://www.w3.org/2000/svg', 'g') as SVGGElement; + return { + rectangle: () => createG(), + path: () => createG() + } as any; + }; + describe('PlaitCommonGeometry interface', () => { it('should accept fillStyle property on geometry elements', () => { const element: PlaitGeometry = { @@ -70,8 +80,53 @@ describe('fillStyle', () => { }); }); + describe('GeometryShapeGenerator', () => { + it('should use solid fillStyle by default', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const element: PlaitGeometry = { + id: 'test-default-fill-style', + type: 'geometry', + shape: BasicShapes.rectangle, + points: [ + [0, 0], + [100, 100] + ], + fill: '#FF5733' + }; + + new GeometryShapeGenerator(board).draw(element, {}); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe('solid'); + }); + + it('should pass element fillStyle to shape engine', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const element: PlaitGeometry = { + id: 'test-custom-fill-style', + type: 'geometry', + shape: BasicShapes.rectangle, + points: [ + [0, 0], + [100, 100] + ], + fill: '#FF5733', + fillStyle: 'hachure' + }; + + new GeometryShapeGenerator(board).draw(element, {}); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe('hachure'); + }); + }); + describe('drawGeometry', () => { - it('should pass fillStyle to shape engine', () => { + it('should preserve fillStyle for path based geometry engines', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const pathSpy = spyOn(roughSVG, 'path').and.callThrough(); const rectangle: RectangleClient = { x: 0, y: 0, @@ -85,11 +140,19 @@ describe('fillStyle', () => { fill: '#FF5733', fillStyle: 'hachure' }; + const shapes = [ + FlowchartSymbols.delay, + FlowchartSymbols.predefinedProcess, + UMLSymbols.activityClass, + UMLSymbols.container, + UMLSymbols.deletion + ]; - const result = drawGeometry(board, rectangle, BasicShapes.rectangle, roughOptions); - - expect(result).toBeDefined(); - expect(result instanceof SVGGElement).toBe(true); + shapes.forEach((shape) => { + drawGeometry(board, rectangle, shape, roughOptions); + const options = pathSpy.calls.mostRecent().args[1] as Options; + expect(options.fillStyle).toBe('hachure'); + }); }); }); }); From bd41f83f42411d61ec70ee95330bb2789b27ce70 Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Fri, 26 Jun 2026 11:34:23 +0800 Subject: [PATCH 03/13] demo: add fill style examples --- src/app/editor/mock-data.ts | 180 ++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/src/app/editor/mock-data.ts b/src/app/editor/mock-data.ts index f93814230..5af6afc77 100644 --- a/src/app/editor/mock-data.ts +++ b/src/app/editor/mock-data.ts @@ -344,6 +344,186 @@ export const mockDrawData: PlaitDrawElement[] = [ [-277, -129.0000000000001] ], strokeWidth: 2 + }, + { + id: 'fill-style-title', + type: 'geometry', + shape: 'text', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'FillStyle examples' + } + ] + }, + points: [ + [320, -120], + [470, -100] + ], + autoSize: true + }, + { + id: 'fill-style-solid', + type: 'geometry', + shape: 'rectangle', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'solid' + } + ], + align: 'center' + }, + points: [ + [320, -50], + [500, 70] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#F6EDCF', + fillStyle: 'solid' + }, + { + id: 'fill-style-hachure', + type: 'geometry', + shape: 'diamond', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'hachure' + } + ], + align: 'center' + }, + points: [ + [560, -50], + [740, 70] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#CDEFF2', + fillStyle: 'hachure' + }, + { + id: 'fill-style-zigzag', + type: 'geometry', + shape: 'ellipse', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'zigzag' + } + ], + align: 'center' + }, + points: [ + [800, -50], + [980, 70] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#DADFEB', + fillStyle: 'zigzag' + }, + { + id: 'fill-style-cross-hatch', + type: 'geometry', + shape: 'process', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'cross-hatch' + } + ], + align: 'center' + }, + points: [ + [1040, -50], + [1220, 70] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#F6EDCF', + fillStyle: 'cross-hatch' + }, + { + id: 'fill-style-dots', + type: 'geometry', + shape: 'delay', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'dots' + } + ], + align: 'center' + }, + points: [ + [320, 130], + [500, 250] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#CDEFF2', + fillStyle: 'dots' + }, + { + id: 'fill-style-dashed', + type: 'geometry', + shape: 'predefinedProcess', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'dashed' + } + ], + align: 'center' + }, + points: [ + [560, 130], + [740, 250] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#DADFEB', + fillStyle: 'dashed' + }, + { + id: 'fill-style-zigzag-line', + type: 'geometry', + shape: 'activityClass', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'zigzag-line' + } + ], + align: 'center' + }, + points: [ + [800, 130], + [980, 250] + ], + strokeWidth: 2, + strokeColor: '#1e1e1e', + fill: '#F6EDCF', + fillStyle: 'zigzag-line' } ] as PlaitDrawElement[]; From 5b45ba4407a83bf6b28e5bb82788834c9a37e21a Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Fri, 26 Jun 2026 11:52:13 +0800 Subject: [PATCH 04/13] demo: add fill style setting control --- .../setting-panel.component.html | 55 +++++++++++++ .../setting-panel/setting-panel.component.ts | 30 ++++++- src/app/components/styles/panel.scss | 78 +++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) diff --git a/src/app/components/setting-panel/setting-panel.component.html b/src/app/components/setting-panel/setting-panel.component.html index 5f8616e06..bf8758478 100644 --- a/src/app/components/setting-panel/setting-panel.component.html +++ b/src/app/components/setting-panel/setting-panel.component.html @@ -322,6 +322,61 @@ } +@if (enableSetFillStyle) { +
+
填充样式:
+
+ @for (fillStyle of fillStyles; track fillStyle.value) { + + } +
+
+} +
边框颜色:
= {}; PlaitPointerType = PlaitPointerType; @@ -111,6 +114,16 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement branchColor = ['#A287E0', '#6E80DB', '#E0B75E', '#B1C675', '#77C386', '#E48484']; + fillStyles: { value: FillStyle; label: string }[] = [ + { value: 'solid', label: '实心' }, + { value: 'hachure', label: '斜线' }, + { value: 'zigzag', label: '锯齿' }, + { value: 'cross-hatch', label: '交叉线' }, + { value: 'dots', label: '点状' }, + { value: 'dashed', label: '虚线' }, + { value: 'zigzag-line', label: '锯齿线' } + ]; + align = Alignment.center; lineShape = ArrowLineShape.straight; @@ -129,6 +142,8 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement enableSetFillColor = true; + enableSetFillStyle = false; + @HostBinding('class.visible') get isVisible() { const selectedCount = getSelectedElements(this.board).length; @@ -149,6 +164,7 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.isSelectedVectorLine = !!selectedVectorLineElements.length; this.isSelectSwimlane = isSingleSelectSwimlane(this.board); this.enableSetFillColor = selectedDrawElements.some((item) => isClosedDrawElement(item)) || this.isSelectedMind; + this.enableSetFillStyle = selectedDrawElements.some((item) => isClosedDrawElement(item)); if (this.isSelectSwimlane) { this.swimlaneCount = getSwimlaneCount(getSelectedElements(this.board)[0] as PlaitSwimlane); } @@ -170,6 +186,10 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement const selectedTableCellsEditor = getSelectedTableCellsEditor(this.board); const selectedTableAndGeometryElements = [...selectedGeometryElements, ...selectedTableElements]; if (selectedTableAndGeometryElements.length) { + const firstClosedGeometry = selectedGeometryElements.find((item) => isClosedDrawElement(item)); + if (firstClosedGeometry) { + this.currentFillStyle = firstClosedGeometry.fillStyle || 'solid'; + } let editor: BaseEditor | undefined; let align: Alignment = this.align; if (selectedTableCellsEditor?.length) { @@ -260,6 +280,14 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement }); } + changeFillStyle(fillStyle: FillStyle) { + this.currentFillStyle = fillStyle; + PropertyTransforms.setProperty(this.board, { fillStyle } as Partial, { + getMemorizeKey, + match: (element: PlaitElement) => PlaitDrawElement.isDrawElement(element) && isClosedDrawElement(element) + }); + } + changeStroke(property: string) { PropertyTransforms.setStrokeColor(this.board, property, { getMemorizeKey }); } diff --git a/src/app/components/styles/panel.scss b/src/app/components/styles/panel.scss index 0ca23b651..86c96b4dd 100644 --- a/src/app/components/styles/panel.scss +++ b/src/app/components/styles/panel.scss @@ -85,6 +85,84 @@ } } + .fill-style-setting-toolbar { + width: 100%; + } + + .fill-style-options { + display: grid; + grid-template-columns: repeat(6, 24px); + gap: 6px 4px; + } + + .fill-style-button { + cursor: pointer; + width: 24px; + height: 24px; + border: 1px solid #d6d6d6; + border-radius: 4px; + padding: 0; + position: relative; + color: #333333; + background-color: #ffffff; + + svg { + width: 22px; + height: 22px; + display: block; + } + + .fill-style-icon-frame { + fill: none; + stroke: currentColor; + stroke-width: 1.2; + } + + .fill-style-icon-fill { + fill: currentColor; + opacity: 0.2; + stroke: none; + } + + path { + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.3; + } + + circle { + fill: currentColor; + } + + &.active { + color: #4a47b1; + } + + &.active .active-button-outline { + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + box-shadow: 0 0 0 1px #4a47b1; + z-index: 1; + border-radius: 5px; + } + + &:hover::after { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + box-shadow: 0 0 0 1px #d6d6d6; + border-radius: 5px; + } + } + .text-setting-toolbar { width: 100%; .text-setting-items { From 916ac3e67ab1f906e08cfd9240ef32c8a26fb950 Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Fri, 26 Jun 2026 15:12:13 +0800 Subject: [PATCH 05/13] test(draw): remove redundant fill style interface specs --- packages/draw/src/utils/fillstyle.spec.ts | 63 +---------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index 484971e52..8793a52fd 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -1,5 +1,5 @@ import { BOARD_TO_ROUGH_SVG, createTestingBoard, PlaitBoard, RectangleClient } from '@plait/core'; -import { withDraw, PlaitGeometry, BasicShapes, drawGeometry, FillStyle, FlowchartSymbols, UMLSymbols } from '@plait/draw'; +import { withDraw, PlaitGeometry, BasicShapes, drawGeometry, FlowchartSymbols, UMLSymbols } from '@plait/draw'; import { Options } from 'roughjs/bin/core'; import { GeometryShapeGenerator } from '../generators/geometry-shape.generator'; @@ -19,67 +19,6 @@ describe('fillStyle', () => { } as any; }; - describe('PlaitCommonGeometry interface', () => { - it('should accept fillStyle property on geometry elements', () => { - const element: PlaitGeometry = { - id: 'test-1', - type: 'geometry', - shape: BasicShapes.rectangle, - points: [ - [0, 0], - [100, 100] - ], - fill: '#FF5733', - fillStyle: 'hachure', - strokeColor: '#000000', - strokeWidth: 2 - }; - - expect(element.fillStyle).toBe('hachure'); - }); - - it('should allow all rough.js fill styles', () => { - const fillStyles: FillStyle[] = [ - 'solid', - 'hachure', - 'zigzag', - 'cross-hatch', - 'dots', - 'dashed', - 'zigzag-line' - ]; - - fillStyles.forEach((fillStyle) => { - const element: PlaitGeometry = { - id: `test-${fillStyle}`, - type: 'geometry', - shape: BasicShapes.rectangle, - points: [ - [0, 0], - [100, 100] - ], - fillStyle - }; - - expect(element.fillStyle).toBe(fillStyle); - }); - }); - - it('should make fillStyle optional', () => { - const element: PlaitGeometry = { - id: 'test-optional', - type: 'geometry', - shape: BasicShapes.rectangle, - points: [ - [0, 0], - [100, 100] - ] - }; - - expect(element.fillStyle).toBeUndefined(); - }); - }); - describe('GeometryShapeGenerator', () => { it('should use solid fillStyle by default', () => { const roughSVG = PlaitBoard.getRoughSVG(board); From 0a5c29f203aad5e80f497e056df13b254895da2b Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Sun, 28 Jun 2026 08:37:00 +0800 Subject: [PATCH 06/13] fix(draw): address fill style review feedback --- packages/draw/src/engines/table/table.ts | 2 +- packages/draw/src/interfaces/geometry.ts | 5 +- packages/draw/src/utils/common.ts | 10 +- packages/draw/src/utils/fillstyle.spec.ts | 91 ++++++++++++++++++- packages/draw/src/utils/geometry.ts | 7 +- .../setting-panel/setting-panel.component.ts | 31 ++++--- 6 files changed, 124 insertions(+), 22 deletions(-) diff --git a/packages/draw/src/engines/table/table.ts b/packages/draw/src/engines/table/table.ts index 8a54e8cc8..d580a4842 100644 --- a/packages/draw/src/engines/table/table.ts +++ b/packages/draw/src/engines/table/table.ts @@ -31,7 +31,7 @@ export const TableEngine: ShapeEngine { - return getEngine(shape).draw(board, outerRectangle, roughOptions, drawOptions); + return getEngine(shape).draw( + board, + outerRectangle, + { + ...roughOptions, + fillStyle: roughOptions.fillStyle ?? 'solid' + }, + drawOptions + ); }; export const drawBoundReaction = ( diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index 8793a52fd..5900022fb 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -1,7 +1,11 @@ import { BOARD_TO_ROUGH_SVG, createTestingBoard, PlaitBoard, RectangleClient } from '@plait/core'; -import { withDraw, PlaitGeometry, BasicShapes, drawGeometry, FlowchartSymbols, UMLSymbols } from '@plait/draw'; +import { withDraw } from '../plugins/with-draw'; import { Options } from 'roughjs/bin/core'; import { GeometryShapeGenerator } from '../generators/geometry-shape.generator'; +import { drawShape } from './common'; +import { TableSymbols, PlaitTable } from '../interfaces/table'; +import { PlaitGeometry, BasicShapes, FlowchartSymbols, FILL_STYLES, UMLSymbols } from '../interfaces/geometry'; +import { drawGeometry } from './geometry'; describe('fillStyle', () => { let board: PlaitBoard; @@ -15,7 +19,8 @@ describe('fillStyle', () => { const createG = () => document.createElementNS('http://www.w3.org/2000/svg', 'g') as SVGGElement; return { rectangle: () => createG(), - path: () => createG() + path: () => createG(), + linearPath: () => createG() } as any; }; @@ -63,6 +68,49 @@ describe('fillStyle', () => { }); describe('drawGeometry', () => { + it('should use solid fillStyle by default when called directly', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const rectangle: RectangleClient = { + x: 0, + y: 0, + width: 100, + height: 100 + }; + + drawGeometry(board, rectangle, BasicShapes.rectangle, { + stroke: '#000000', + strokeWidth: 2, + fill: '#FF5733' + }); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe('solid'); + }); + + it('should support all fill styles when called directly', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const rectangle: RectangleClient = { + x: 0, + y: 0, + width: 100, + height: 100 + }; + + FILL_STYLES.forEach((fillStyle) => { + drawGeometry(board, rectangle, BasicShapes.rectangle, { + stroke: '#000000', + strokeWidth: 2, + fill: '#FF5733', + fillStyle + }); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe(fillStyle); + }); + }); + it('should preserve fillStyle for path based geometry engines', () => { const roughSVG = PlaitBoard.getRoughSVG(board); const pathSpy = spyOn(roughSVG, 'path').and.callThrough(); @@ -94,4 +142,43 @@ describe('fillStyle', () => { }); }); }); + + describe('drawShape', () => { + it('should use solid fillStyle by default for table cell fills', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const rectangle: RectangleClient = { + x: 0, + y: 0, + width: 100, + height: 100 + }; + const table: PlaitTable = { + id: 'test-table', + type: 'table', + points: [ + [0, 0], + [100, 100] + ], + rows: [{ id: 'row-1' }], + columns: [{ id: 'column-1' }], + cells: [{ id: 'cell-1', rowId: 'row-1', columnId: 'column-1', fill: '#FF5733' }] + }; + (board as any).buildTable = (element: PlaitTable) => element; + + drawShape( + board, + rectangle, + TableSymbols.table, + { + stroke: '#000000', + strokeWidth: 2 + }, + { element: table } + ); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe('solid'); + }); + }); }); diff --git a/packages/draw/src/utils/geometry.ts b/packages/draw/src/utils/geometry.ts index 3f10e5f1e..92bd9366c 100644 --- a/packages/draw/src/utils/geometry.ts +++ b/packages/draw/src/utils/geometry.ts @@ -45,7 +45,7 @@ import { createUMLClassOrInterfaceGeometryElement } from './uml'; import { createMultipleTextGeometryElement, isMultipleTextGeometry, isMultipleTextShape } from './multi-text-geometry'; import { DEFAULT_FONT_SIZE } from '@plait/text-plugins'; -export type GeometryStyleOptions = Pick; +export type GeometryStyleOptions = Pick; export type TextProperties = Partial & { align?: Alignment }; @@ -110,7 +110,10 @@ export const createGeometryElementWithoutText = ( }; export const drawGeometry = (board: PlaitBoard, outerRectangle: RectangleClient, shape: GeometryShapes, roughOptions: Options) => { - return getEngine(shape).draw(board, outerRectangle, roughOptions); + return getEngine(shape).draw(board, outerRectangle, { + ...roughOptions, + fillStyle: roughOptions.fillStyle ?? 'solid' + }); }; export const getNearestPoint = (element: PlaitShapeElement, point: Point) => { diff --git a/src/app/components/setting-panel/setting-panel.component.ts b/src/app/components/setting-panel/setting-panel.component.ts index 08d3d61a5..8f60fb0f9 100644 --- a/src/app/components/setting-panel/setting-panel.component.ts +++ b/src/app/components/setting-panel/setting-panel.component.ts @@ -57,7 +57,8 @@ import { getSelectedTableCellsEditor, VectorLineShape, isClosedDrawElement, - FillStyle + FillStyle, + FILL_STYLES } from '@plait/draw'; import { MindLayoutType } from '@plait/layouts'; import { FontSizes, LinkEditor, MarkTypes, PlaitMarkEditor, TextTransforms } from '@plait/text-plugins'; @@ -114,15 +115,18 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement branchColor = ['#A287E0', '#6E80DB', '#E0B75E', '#B1C675', '#77C386', '#E48484']; - fillStyles: { value: FillStyle; label: string }[] = [ - { value: 'solid', label: '实心' }, - { value: 'hachure', label: '斜线' }, - { value: 'zigzag', label: '锯齿' }, - { value: 'cross-hatch', label: '交叉线' }, - { value: 'dots', label: '点状' }, - { value: 'dashed', label: '虚线' }, - { value: 'zigzag-line', label: '锯齿线' } - ]; + fillStyles: { value: FillStyle; label: string }[] = FILL_STYLES.map((value) => { + const labels: Record = { + solid: '实心', + hachure: '斜线', + zigzag: '锯齿', + 'cross-hatch': '交叉线', + dots: '点状', + dashed: '虚线', + 'zigzag-line': '锯齿线' + }; + return { value, label: labels[value] }; + }); align = Alignment.center; @@ -159,12 +163,13 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement const selectedArrowLineElements = getSelectedArrowLineElements(this.board); const selectedVectorLineElements = getSelectedVectorLineElements(this.board); const selectedDrawElements = getSelectedDrawElements(this.board); + const selectedGeometryElements = getSelectedGeometryElements(this.board); this.isSelectedMind = !!selectedMindElements.length; this.isSelectedLine = !!selectedArrowLineElements.length || !!selectedVectorLineElements.length; this.isSelectedVectorLine = !!selectedVectorLineElements.length; this.isSelectSwimlane = isSingleSelectSwimlane(this.board); this.enableSetFillColor = selectedDrawElements.some((item) => isClosedDrawElement(item)) || this.isSelectedMind; - this.enableSetFillStyle = selectedDrawElements.some((item) => isClosedDrawElement(item)); + this.enableSetFillStyle = selectedGeometryElements.some((item) => isClosedDrawElement(item)); if (this.isSelectSwimlane) { this.swimlaneCount = getSwimlaneCount(getSelectedElements(this.board)[0] as PlaitSwimlane); } @@ -180,8 +185,6 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.align = firstMindElement.data.topic.align || Alignment.left; } } - - const selectedGeometryElements = getSelectedGeometryElements(this.board); const selectedTableElements = getSelectedTableElements(this.board); const selectedTableCellsEditor = getSelectedTableCellsEditor(this.board); const selectedTableAndGeometryElements = [...selectedGeometryElements, ...selectedTableElements]; @@ -284,7 +287,7 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.currentFillStyle = fillStyle; PropertyTransforms.setProperty(this.board, { fillStyle } as Partial, { getMemorizeKey, - match: (element: PlaitElement) => PlaitDrawElement.isDrawElement(element) && isClosedDrawElement(element) + match: (element: PlaitElement) => PlaitDrawElement.isGeometry(element) && isClosedDrawElement(element) }); } From f11b02616a2b18b82eee9cf34cf953fc83151df9 Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Sun, 28 Jun 2026 10:44:27 +0800 Subject: [PATCH 07/13] chore: remove mock data --- src/app/editor/mock-data.ts | 818 ------------------------------------ 1 file changed, 818 deletions(-) diff --git a/src/app/editor/mock-data.ts b/src/app/editor/mock-data.ts index 5af6afc77..35887ef47 100644 --- a/src/app/editor/mock-data.ts +++ b/src/app/editor/mock-data.ts @@ -344,824 +344,6 @@ export const mockDrawData: PlaitDrawElement[] = [ [-277, -129.0000000000001] ], strokeWidth: 2 - }, - { - id: 'fill-style-title', - type: 'geometry', - shape: 'text', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'FillStyle examples' - } - ] - }, - points: [ - [320, -120], - [470, -100] - ], - autoSize: true - }, - { - id: 'fill-style-solid', - type: 'geometry', - shape: 'rectangle', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'solid' - } - ], - align: 'center' - }, - points: [ - [320, -50], - [500, 70] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#F6EDCF', - fillStyle: 'solid' - }, - { - id: 'fill-style-hachure', - type: 'geometry', - shape: 'diamond', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'hachure' - } - ], - align: 'center' - }, - points: [ - [560, -50], - [740, 70] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#CDEFF2', - fillStyle: 'hachure' - }, - { - id: 'fill-style-zigzag', - type: 'geometry', - shape: 'ellipse', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'zigzag' - } - ], - align: 'center' - }, - points: [ - [800, -50], - [980, 70] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#DADFEB', - fillStyle: 'zigzag' - }, - { - id: 'fill-style-cross-hatch', - type: 'geometry', - shape: 'process', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'cross-hatch' - } - ], - align: 'center' - }, - points: [ - [1040, -50], - [1220, 70] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#F6EDCF', - fillStyle: 'cross-hatch' - }, - { - id: 'fill-style-dots', - type: 'geometry', - shape: 'delay', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'dots' - } - ], - align: 'center' - }, - points: [ - [320, 130], - [500, 250] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#CDEFF2', - fillStyle: 'dots' - }, - { - id: 'fill-style-dashed', - type: 'geometry', - shape: 'predefinedProcess', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'dashed' - } - ], - align: 'center' - }, - points: [ - [560, 130], - [740, 250] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#DADFEB', - fillStyle: 'dashed' - }, - { - id: 'fill-style-zigzag-line', - type: 'geometry', - shape: 'activityClass', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'zigzag-line' - } - ], - align: 'center' - }, - points: [ - [800, 130], - [980, 250] - ], - strokeWidth: 2, - strokeColor: '#1e1e1e', - fill: '#F6EDCF', - fillStyle: 'zigzag-line' - } -] as PlaitDrawElement[]; - -export const mockTableData: PlaitDrawElement[] = [ - { - id: 'jhETT', - points: [ - [-100, -100], - [500, 300] - ], - type: 'table', - rows: [ - { - id: 'row-1', - height: 30 - }, - { - id: 'row-2', - height: 30 - }, - { - id: 'row-3' - }, - { - id: 'row-4' - } - ], - columns: [ - { - id: 'column-1' - }, - { - id: 'column-2' - }, - { - id: 'column-3' - } - ], - cells: [ - { - id: 'v-cell-1-1', - rowId: 'row-1', - columnId: 'column-1', - colspan: 3, - text: { - children: [ - { - text: 'merge cell' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-2-1', - rowId: 'row-2', - columnId: 'column-1', - text: { - children: [ - { - text: 'cell-2-1' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-2-2', - rowId: 'row-2', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-2-3', - rowId: 'row-2', - columnId: 'column-3', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-3-1', - rowId: 'row-3', - columnId: 'column-1', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-3-2', - rowId: 'row-3', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-3-3', - rowId: 'row-3', - columnId: 'column-3', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-4-1', - rowId: 'row-4', - columnId: 'column-1', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-4-2', - rowId: 'row-4', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-4-3', - rowId: 'row-4', - columnId: 'column-3', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - } - ] - }, - { - id: 'TTjhE', - points: [ - [600, -100], - [1200, 300] - ], - type: 'table', - rows: [ - { - id: 'row-1' - }, - { - id: 'row-2' - }, - { - id: 'row-3' - }, - { - id: 'row-4' - } - ], - columns: [ - { - id: 'column-1', - width: 30 - }, - { - id: 'column-2', - width: 30 - }, - { - id: 'column-3' - } - ], - cells: [ - { - id: 'h-cell-1-1', - rowId: 'row-1', - columnId: 'column-1', - rowspan: 4, - text: { - children: [ - { - text: '合并单元格' - } - ], - align: 'center', - direction: 'vertical' - } - }, - { - id: 'h-cell-1-2', - rowId: 'row-1', - columnId: 'column-2', - text: { - children: [ - { - text: 'cell-1-2' - } - ], - align: 'center', - direction: 'vertical' - } - }, - { - id: 'h-cell-1-3', - rowId: 'row-1', - columnId: 'column-3', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-2-2', - rowId: 'row-2', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-2-3', - rowId: 'row-2', - columnId: 'column-3', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-3-2', - rowId: 'row-3', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-3-3', - rowId: 'row-3', - columnId: 'column-3', - text: { - children: [ - { - text: 'cell-3-3' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-4-2', - rowId: 'row-4', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-4-3', - rowId: 'row-4', - columnId: 'column-3', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - } - ] - } -] as PlaitDrawElement[]; - -export const mockSwimlaneData: PlaitDrawElement[] = [ - { - id: 'swimlaneVertical', - points: [ - [-100, -100], - [200, 400] - ], - type: 'swimlane', - shape: 'swimlaneVertical', - rows: [ - { - id: 'row-1', - height: 30 - }, - { - id: 'row-2', - height: 30 - }, - { - id: 'row-3' - } - ], - columns: [ - { - id: 'column-1' - }, - { - id: 'column-2' - } - ], - cells: [ - { - id: 'v-cell-1-1', - rowId: 'row-1', - columnId: 'column-1', - text: { - children: [ - { - text: '垂直泳道' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-2-1', - rowId: 'row-2', - columnId: 'column-1', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-2-2', - rowId: 'row-2', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'v-cell-3-1', - rowId: 'row-3', - columnId: 'column-1' - }, - { - id: 'v-cell-3-2', - rowId: 'row-3', - columnId: 'column-2' - } - ] - }, - { - id: 'swimlaneHorizontal', - points: [ - [300, 0], - [900, 300] - ], - type: 'swimlane', - shape: 'swimlaneHorizontal', - rows: [ - { - id: 'row-1' - }, - { - id: 'row-2' - } - ], - columns: [ - { - id: 'column-1', - width: 30 - }, - { - id: 'column-2', - width: 30 - }, - { - id: 'column-3' - } - ], - cells: [ - { - id: 'h-cell-1-1', - rowId: 'row-1', - columnId: 'column-1', - text: { - children: [ - { - text: '水平泳道' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-1-2', - rowId: 'row-1', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-1-3', - rowId: 'row-1', - columnId: 'column-3' - }, - { - id: 'h-cell-2-2', - rowId: 'row-2', - columnId: 'column-2', - text: { - children: [ - { - text: '' - } - ], - align: 'center' - } - }, - { - id: 'h-cell-2-3', - rowId: 'row-2', - columnId: 'column-3' - } - ] - } -] as PlaitDrawElement[]; - -export const mockGroupData: PlaitDrawElement[] = [ - { - id: 'group1', - type: 'group' - }, - { - id: 'group2', - type: 'group', - groupId: 'group3' - }, - { - id: 'group3', - type: 'group' - }, - { - id: 'jimNt', - type: 'geometry', - shape: 'rectangle', - angle: 0, - opacity: 1, - groupId: 'group1', - text: { - children: [ - { - text: 'group1' - } - ], - align: 'center' - }, - points: [ - [-98.814453125, 66.53125], - [55.880859375, 126.71875] - ], - strokeWidth: 2, - fill: '#e48483' - }, - { - id: 'bRBzf', - type: 'geometry', - shape: 'rectangle', - angle: 0, - opacity: 1, - groupId: 'group1', - text: { - children: [ - { - text: 'group1' - } - ], - align: 'center' - }, - points: [ - [136.806640625, 66.53125], - [291.501953125, 126.71875] - ], - strokeWidth: 2, - fill: '#e48483' - }, - { - id: 'erasy', - type: 'geometry', - shape: 'rectangle', - angle: 0, - opacity: 1, - groupId: 'group3', - text: { - children: [ - { - text: 'group3' - } - ], - align: 'center' - }, - points: [ - [19.580078125, 318.6376953125], - [174.275390625, 378.8251953125] - ], - strokeWidth: 2, - fill: '#69b1e4' - }, - { - id: 'YcTFs', - type: 'geometry', - shape: 'text', - angle: 0, - opacity: 1, - text: { - children: [ - { - text: 'group3 包含 group2' - } - ] - }, - points: [ - [-275.482421875, 302.318359375], - [-138.091796875, 322.318359375] - ], - autoSize: true - }, - { - id: 'ztmWw', - type: 'geometry', - shape: 'rectangle', - angle: 0, - opacity: 1, - groupId: 'group2', - text: { - children: [ - { - text: 'group2' - } - ], - align: 'center' - }, - points: [ - [-98.814453125, 197.279296875], - [55.880859375, 257.466796875] - ], - strokeWidth: 2, - fill: '#e48483' - }, - { - id: 'bWiPp', - type: 'geometry', - shape: 'rectangle', - angle: 0, - opacity: 1, - groupId: 'group2', - text: { - children: [ - { - text: 'group2' - } - ], - align: 'center' - }, - points: [ - [139.3486328125, 197.279296875], - [294.0439453125, 257.466796875] - ], - strokeWidth: 2, - fill: '#e48483' } ] as PlaitDrawElement[]; From dbfd9404d2000d800a34e53c553fe5bebd302568 Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Sun, 28 Jun 2026 10:48:17 +0800 Subject: [PATCH 08/13] chore: revert mock data --- src/app/editor/mock-data.ts | 638 ++++++++++++++++++++++++++++++++++++ 1 file changed, 638 insertions(+) diff --git a/src/app/editor/mock-data.ts b/src/app/editor/mock-data.ts index 35887ef47..f93814230 100644 --- a/src/app/editor/mock-data.ts +++ b/src/app/editor/mock-data.ts @@ -347,6 +347,644 @@ export const mockDrawData: PlaitDrawElement[] = [ } ] as PlaitDrawElement[]; +export const mockTableData: PlaitDrawElement[] = [ + { + id: 'jhETT', + points: [ + [-100, -100], + [500, 300] + ], + type: 'table', + rows: [ + { + id: 'row-1', + height: 30 + }, + { + id: 'row-2', + height: 30 + }, + { + id: 'row-3' + }, + { + id: 'row-4' + } + ], + columns: [ + { + id: 'column-1' + }, + { + id: 'column-2' + }, + { + id: 'column-3' + } + ], + cells: [ + { + id: 'v-cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + colspan: 3, + text: { + children: [ + { + text: 'merge cell' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-1', + rowId: 'row-2', + columnId: 'column-1', + text: { + children: [ + { + text: 'cell-2-1' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-2', + rowId: 'row-2', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-3', + rowId: 'row-2', + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-3-1', + rowId: 'row-3', + columnId: 'column-1', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-3-2', + rowId: 'row-3', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-3-3', + rowId: 'row-3', + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-4-1', + rowId: 'row-4', + columnId: 'column-1', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-4-2', + rowId: 'row-4', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-4-3', + rowId: 'row-4', + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + } + ] + }, + { + id: 'TTjhE', + points: [ + [600, -100], + [1200, 300] + ], + type: 'table', + rows: [ + { + id: 'row-1' + }, + { + id: 'row-2' + }, + { + id: 'row-3' + }, + { + id: 'row-4' + } + ], + columns: [ + { + id: 'column-1', + width: 30 + }, + { + id: 'column-2', + width: 30 + }, + { + id: 'column-3' + } + ], + cells: [ + { + id: 'h-cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + rowspan: 4, + text: { + children: [ + { + text: '合并单元格' + } + ], + align: 'center', + direction: 'vertical' + } + }, + { + id: 'h-cell-1-2', + rowId: 'row-1', + columnId: 'column-2', + text: { + children: [ + { + text: 'cell-1-2' + } + ], + align: 'center', + direction: 'vertical' + } + }, + { + id: 'h-cell-1-3', + rowId: 'row-1', + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-2-2', + rowId: 'row-2', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-2-3', + rowId: 'row-2', + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-3-2', + rowId: 'row-3', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-3-3', + rowId: 'row-3', + columnId: 'column-3', + text: { + children: [ + { + text: 'cell-3-3' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-4-2', + rowId: 'row-4', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-4-3', + rowId: 'row-4', + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + } + ] + } +] as PlaitDrawElement[]; + +export const mockSwimlaneData: PlaitDrawElement[] = [ + { + id: 'swimlaneVertical', + points: [ + [-100, -100], + [200, 400] + ], + type: 'swimlane', + shape: 'swimlaneVertical', + rows: [ + { + id: 'row-1', + height: 30 + }, + { + id: 'row-2', + height: 30 + }, + { + id: 'row-3' + } + ], + columns: [ + { + id: 'column-1' + }, + { + id: 'column-2' + } + ], + cells: [ + { + id: 'v-cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + text: { + children: [ + { + text: '垂直泳道' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-1', + rowId: 'row-2', + columnId: 'column-1', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-2', + rowId: 'row-2', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-3-1', + rowId: 'row-3', + columnId: 'column-1' + }, + { + id: 'v-cell-3-2', + rowId: 'row-3', + columnId: 'column-2' + } + ] + }, + { + id: 'swimlaneHorizontal', + points: [ + [300, 0], + [900, 300] + ], + type: 'swimlane', + shape: 'swimlaneHorizontal', + rows: [ + { + id: 'row-1' + }, + { + id: 'row-2' + } + ], + columns: [ + { + id: 'column-1', + width: 30 + }, + { + id: 'column-2', + width: 30 + }, + { + id: 'column-3' + } + ], + cells: [ + { + id: 'h-cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + text: { + children: [ + { + text: '水平泳道' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-1-2', + rowId: 'row-1', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-1-3', + rowId: 'row-1', + columnId: 'column-3' + }, + { + id: 'h-cell-2-2', + rowId: 'row-2', + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-2-3', + rowId: 'row-2', + columnId: 'column-3' + } + ] + } +] as PlaitDrawElement[]; + +export const mockGroupData: PlaitDrawElement[] = [ + { + id: 'group1', + type: 'group' + }, + { + id: 'group2', + type: 'group', + groupId: 'group3' + }, + { + id: 'group3', + type: 'group' + }, + { + id: 'jimNt', + type: 'geometry', + shape: 'rectangle', + angle: 0, + opacity: 1, + groupId: 'group1', + text: { + children: [ + { + text: 'group1' + } + ], + align: 'center' + }, + points: [ + [-98.814453125, 66.53125], + [55.880859375, 126.71875] + ], + strokeWidth: 2, + fill: '#e48483' + }, + { + id: 'bRBzf', + type: 'geometry', + shape: 'rectangle', + angle: 0, + opacity: 1, + groupId: 'group1', + text: { + children: [ + { + text: 'group1' + } + ], + align: 'center' + }, + points: [ + [136.806640625, 66.53125], + [291.501953125, 126.71875] + ], + strokeWidth: 2, + fill: '#e48483' + }, + { + id: 'erasy', + type: 'geometry', + shape: 'rectangle', + angle: 0, + opacity: 1, + groupId: 'group3', + text: { + children: [ + { + text: 'group3' + } + ], + align: 'center' + }, + points: [ + [19.580078125, 318.6376953125], + [174.275390625, 378.8251953125] + ], + strokeWidth: 2, + fill: '#69b1e4' + }, + { + id: 'YcTFs', + type: 'geometry', + shape: 'text', + angle: 0, + opacity: 1, + text: { + children: [ + { + text: 'group3 包含 group2' + } + ] + }, + points: [ + [-275.482421875, 302.318359375], + [-138.091796875, 322.318359375] + ], + autoSize: true + }, + { + id: 'ztmWw', + type: 'geometry', + shape: 'rectangle', + angle: 0, + opacity: 1, + groupId: 'group2', + text: { + children: [ + { + text: 'group2' + } + ], + align: 'center' + }, + points: [ + [-98.814453125, 197.279296875], + [55.880859375, 257.466796875] + ], + strokeWidth: 2, + fill: '#e48483' + }, + { + id: 'bWiPp', + type: 'geometry', + shape: 'rectangle', + angle: 0, + opacity: 1, + groupId: 'group2', + text: { + children: [ + { + text: 'group2' + } + ], + align: 'center' + }, + points: [ + [139.3486328125, 197.279296875], + [294.0439453125, 257.466796875] + ], + strokeWidth: 2, + fill: '#e48483' + } +] as PlaitDrawElement[]; + export const mockRotateData: PlaitDrawElement[] = [ { id: 'jhETT', From 1295d6a4027465a787577e4bf6c034b59bf26f71 Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Mon, 29 Jun 2026 20:19:45 +0800 Subject: [PATCH 09/13] fix(draw): propagate fill style for UML tables --- .../draw/src/generators/table.generator.ts | 5 +++-- packages/draw/src/interfaces/index.ts | 2 +- packages/draw/src/utils/fillstyle.spec.ts | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/draw/src/generators/table.generator.ts b/packages/draw/src/generators/table.generator.ts index cdb961d19..6b4c5268a 100644 --- a/packages/draw/src/generators/table.generator.ts +++ b/packages/draw/src/generators/table.generator.ts @@ -1,4 +1,4 @@ -import { TableSymbols } from '../interfaces'; +import { PlaitCommonGeometry, TableSymbols } from '../interfaces'; import { Generator, getStrokeLineDash } from '@plait/common'; import { PlaitElement, RectangleClient } from '@plait/core'; import { PlaitBaseTable } from '../interfaces/table'; @@ -24,7 +24,8 @@ export class TableGenerator extends Gen { strokeWidth, stroke: strokeColor, - strokeLineDash + strokeLineDash, + fillStyle: (element as unknown as PlaitBaseTable & Pick).fillStyle ?? 'solid' }, { element: element diff --git a/packages/draw/src/interfaces/index.ts b/packages/draw/src/interfaces/index.ts index f905a252f..aa0a7ab3e 100644 --- a/packages/draw/src/interfaces/index.ts +++ b/packages/draw/src/interfaces/index.ts @@ -1,4 +1,4 @@ -import { BasicShapes, FillStyle, FlowchartSymbols, GeometryShapes, PlaitCustomGeometry, PlaitGeometry, UMLSymbols } from './geometry'; +import { BasicShapes, FlowchartSymbols, GeometryShapes, PlaitCustomGeometry, PlaitGeometry, UMLSymbols } from './geometry'; import { PlaitImage } from './image'; import { PlaitArrowLine } from './arrow-line'; import { PlaitSwimlane, SwimlaneSymbols } from './swimlane'; diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index 5900022fb..e7028a045 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -2,13 +2,16 @@ import { BOARD_TO_ROUGH_SVG, createTestingBoard, PlaitBoard, RectangleClient } f import { withDraw } from '../plugins/with-draw'; import { Options } from 'roughjs/bin/core'; import { GeometryShapeGenerator } from '../generators/geometry-shape.generator'; +import { TableGenerator } from '../generators/table.generator'; import { drawShape } from './common'; import { TableSymbols, PlaitTable } from '../interfaces/table'; import { PlaitGeometry, BasicShapes, FlowchartSymbols, FILL_STYLES, UMLSymbols } from '../interfaces/geometry'; import { drawGeometry } from './geometry'; +import { createUMLClassOrInterfaceGeometryElement } from './uml'; describe('fillStyle', () => { let board: PlaitBoard; + type TableBackedGeometry = PlaitGeometry & Omit; beforeEach(() => { board = createTestingBoard([withDraw], []); @@ -65,6 +68,23 @@ describe('fillStyle', () => { const options = rectangleSpy.calls.mostRecent().args[4] as Options; expect(options.fillStyle).toBe('hachure'); }); + + it('should pass element fillStyle to table backed UML geometry', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const element = createUMLClassOrInterfaceGeometryElement(board, UMLSymbols.class, [ + [0, 0], + [100, 100] + ]) as TableBackedGeometry; + element.fillStyle = 'hachure'; + element.cells[0].fill = '#FF5733'; + (board as any).buildTable = (value: TableBackedGeometry) => value; + + new TableGenerator(board).draw(element, {}); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe('hachure'); + }); }); describe('drawGeometry', () => { From 241a5ff130e055e888c00753e92d396922274a2e Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Mon, 29 Jun 2026 23:07:55 +0800 Subject: [PATCH 10/13] fix(draw): store fill style on table cells --- packages/draw/src/engines/table/table.ts | 2 +- packages/draw/src/interfaces/table.ts | 2 + packages/draw/src/transforms/index.ts | 3 +- packages/draw/src/transforms/table.ts | 30 ++++++++++++++- packages/draw/src/utils/fillstyle.spec.ts | 38 +++++++++++++++++++ .../setting-panel/setting-panel.component.ts | 27 +++++++++++-- 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/packages/draw/src/engines/table/table.ts b/packages/draw/src/engines/table/table.ts index d580a4842..4d747bf88 100644 --- a/packages/draw/src/engines/table/table.ts +++ b/packages/draw/src/engines/table/table.ts @@ -31,7 +31,7 @@ export const TableEngine: ShapeEngine { @@ -23,6 +23,34 @@ export const setTableFill = (board: PlaitBoard, element: PlaitBaseTable, fill: s Transforms.setNode(board, { cells: newCells }, path); }; +export const setTableFillStyle = (board: PlaitBoard, element: PlaitBaseTable, fillStyle: FillStyle, path: Path) => { + const selectedCells = getSelectedCells(element); + let newCells = element.cells; + if (selectedCells?.length) { + const selectedCellIds = selectedCells.map((item) => item.id); + newCells = element.cells.map((cell) => { + if (selectedCellIds.includes(cell.id)) { + return { + ...cell, + fillStyle + }; + } + return cell; + }); + } else { + newCells = element.cells.map((cell) => { + if (cell.text) { + return { + ...cell, + fillStyle + }; + } + return cell; + }); + } + Transforms.setNode(board, { cells: newCells }, path); +}; + const getNewCell = (cell: PlaitTableCell, fill: string | null) => { const newCell = { ...cell diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index e7028a045..2bed9ba40 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -200,5 +200,43 @@ describe('fillStyle', () => { const options = rectangleSpy.calls.mostRecent().args[4] as Options; expect(options.fillStyle).toBe('solid'); }); + + it('should use table cell fillStyle before table rough options', () => { + const roughSVG = PlaitBoard.getRoughSVG(board); + const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); + const rectangle: RectangleClient = { + x: 0, + y: 0, + width: 100, + height: 100 + }; + const table: PlaitTable = { + id: 'test-table-cell-fill-style', + type: 'table', + points: [ + [0, 0], + [100, 100] + ], + rows: [{ id: 'row-1' }], + columns: [{ id: 'column-1' }], + cells: [{ id: 'cell-1', rowId: 'row-1', columnId: 'column-1', fill: '#FF5733', fillStyle: 'hachure' }] + }; + (board as any).buildTable = (element: PlaitTable) => element; + + drawShape( + board, + rectangle, + TableSymbols.table, + { + stroke: '#000000', + strokeWidth: 2, + fillStyle: 'solid' + }, + { element: table } + ); + + const options = rectangleSpy.calls.mostRecent().args[4] as Options; + expect(options.fillStyle).toBe('hachure'); + }); }); }); diff --git a/src/app/components/setting-panel/setting-panel.component.ts b/src/app/components/setting-panel/setting-panel.component.ts index 8f60fb0f9..462a8aa80 100644 --- a/src/app/components/setting-panel/setting-panel.component.ts +++ b/src/app/components/setting-panel/setting-panel.component.ts @@ -55,6 +55,7 @@ import { PlaitDrawElement, getSwimlaneCount, getSelectedTableCellsEditor, + getSelectedCells, VectorLineShape, isClosedDrawElement, FillStyle, @@ -169,7 +170,10 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.isSelectedVectorLine = !!selectedVectorLineElements.length; this.isSelectSwimlane = isSingleSelectSwimlane(this.board); this.enableSetFillColor = selectedDrawElements.some((item) => isClosedDrawElement(item)) || this.isSelectedMind; - this.enableSetFillStyle = selectedGeometryElements.some((item) => isClosedDrawElement(item)); + const selectedTableElements = getSelectedTableElements(this.board); + this.enableSetFillStyle = + selectedGeometryElements.some((item) => isClosedDrawElement(item) && !PlaitDrawElement.isElementByTable(item)) || + !!selectedTableElements.length; if (this.isSelectSwimlane) { this.swimlaneCount = getSwimlaneCount(getSelectedElements(this.board)[0] as PlaitSwimlane); } @@ -185,11 +189,18 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.align = firstMindElement.data.topic.align || Alignment.left; } } - const selectedTableElements = getSelectedTableElements(this.board); const selectedTableCellsEditor = getSelectedTableCellsEditor(this.board); const selectedTableAndGeometryElements = [...selectedGeometryElements, ...selectedTableElements]; if (selectedTableAndGeometryElements.length) { - const firstClosedGeometry = selectedGeometryElements.find((item) => isClosedDrawElement(item)); + const firstTableElement = selectedTableElements[0]; + const firstTableCell = + firstTableElement && ((getSelectedCells(firstTableElement)?.[0] || firstTableElement.cells.find((cell) => cell.text)) ?? undefined); + if (firstTableCell) { + this.currentFillStyle = firstTableCell.fillStyle || 'solid'; + } + const firstClosedGeometry = selectedGeometryElements.find( + (item) => isClosedDrawElement(item) && !PlaitDrawElement.isElementByTable(item) + ); if (firstClosedGeometry) { this.currentFillStyle = firstClosedGeometry.fillStyle || 'solid'; } @@ -287,7 +298,15 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.currentFillStyle = fillStyle; PropertyTransforms.setProperty(this.board, { fillStyle } as Partial, { getMemorizeKey, - match: (element: PlaitElement) => PlaitDrawElement.isGeometry(element) && isClosedDrawElement(element) + match: (element: PlaitElement) => + (PlaitDrawElement.isGeometry(element) && isClosedDrawElement(element)) || PlaitDrawElement.isElementByTable(element), + callback: (element: PlaitElement, path: Path) => { + if (PlaitDrawElement.isElementByTable(element)) { + DrawTransforms.setTableFillStyle(this.board, element, fillStyle, path); + } else { + Transforms.setNode(this.board, { fillStyle }, path); + } + } }); } From 831cbdb59a2c0643604d4b95730eb869f16179fb Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Tue, 30 Jun 2026 23:02:39 +0800 Subject: [PATCH 11/13] fix(draw): limit fill style to geometry --- packages/draw/src/engines/table/table.ts | 2 +- .../draw/src/generators/table.generator.ts | 5 ++-- packages/draw/src/interfaces/table.ts | 2 -- packages/draw/src/transforms/index.ts | 3 +- packages/draw/src/transforms/table.ts | 30 +------------------ packages/draw/src/utils/fillstyle.spec.ts | 28 +++-------------- .../setting-panel/setting-panel.component.ts | 23 ++++---------- 7 files changed, 15 insertions(+), 78 deletions(-) diff --git a/packages/draw/src/engines/table/table.ts b/packages/draw/src/engines/table/table.ts index 4d747bf88..fbdf9d18b 100644 --- a/packages/draw/src/engines/table/table.ts +++ b/packages/draw/src/engines/table/table.ts @@ -31,7 +31,7 @@ export const TableEngine: ShapeEngine extends Gen { strokeWidth, stroke: strokeColor, - strokeLineDash, - fillStyle: (element as unknown as PlaitBaseTable & Pick).fillStyle ?? 'solid' + strokeLineDash }, { element: element diff --git a/packages/draw/src/interfaces/table.ts b/packages/draw/src/interfaces/table.ts index 778d3f17d..44c337274 100644 --- a/packages/draw/src/interfaces/table.ts +++ b/packages/draw/src/interfaces/table.ts @@ -1,7 +1,6 @@ import { PlaitBoard, PlaitElement, Point } from '@plait/core'; import { ParagraphElement } from '@plait/common'; import { DrawOptions } from './engine'; -import { FillStyle } from './geometry'; export enum TableSymbols { table = 'table' @@ -38,7 +37,6 @@ export interface PlaitTableCell { rowspan?: number; text?: PlaitTableCellParagraph; fill?: string; - fillStyle?: FillStyle; } export interface PlaitTableDrawOptions extends DrawOptions { diff --git a/packages/draw/src/transforms/index.ts b/packages/draw/src/transforms/index.ts index 673d71c6c..4644aae57 100644 --- a/packages/draw/src/transforms/index.ts +++ b/packages/draw/src/transforms/index.ts @@ -12,7 +12,7 @@ import { import { addSwimlaneColumn, addSwimlaneRow, removeSwimlaneColumn, removeSwimlaneRow, updateSwimlaneCount } from './swimlane'; import { setDrawTexts } from './multi-text-geometry-text'; import { setTableText } from './table-text'; -import { setTableFill, setTableFillStyle } from './table'; +import { setTableFill } from './table'; import { setVectorLineShape } from './vector-line'; import { insertDrawByVector } from './common'; @@ -39,6 +39,5 @@ export const DrawTransforms = { removeSwimlaneColumn, updateSwimlaneCount, setTableFill, - setTableFillStyle, insertDrawByVector }; diff --git a/packages/draw/src/transforms/table.ts b/packages/draw/src/transforms/table.ts index 2147f5aec..aa1cd2d5a 100644 --- a/packages/draw/src/transforms/table.ts +++ b/packages/draw/src/transforms/table.ts @@ -1,5 +1,5 @@ import { Path, PlaitBoard, Transforms } from '@plait/core'; -import { FillStyle, PlaitBaseTable, PlaitTableCell } from '../interfaces'; +import { PlaitBaseTable, PlaitTableCell } from '../interfaces'; import { getSelectedCells } from '../utils'; export const setTableFill = (board: PlaitBoard, element: PlaitBaseTable, fill: string | null, path: Path) => { @@ -23,34 +23,6 @@ export const setTableFill = (board: PlaitBoard, element: PlaitBaseTable, fill: s Transforms.setNode(board, { cells: newCells }, path); }; -export const setTableFillStyle = (board: PlaitBoard, element: PlaitBaseTable, fillStyle: FillStyle, path: Path) => { - const selectedCells = getSelectedCells(element); - let newCells = element.cells; - if (selectedCells?.length) { - const selectedCellIds = selectedCells.map((item) => item.id); - newCells = element.cells.map((cell) => { - if (selectedCellIds.includes(cell.id)) { - return { - ...cell, - fillStyle - }; - } - return cell; - }); - } else { - newCells = element.cells.map((cell) => { - if (cell.text) { - return { - ...cell, - fillStyle - }; - } - return cell; - }); - } - Transforms.setNode(board, { cells: newCells }, path); -}; - const getNewCell = (cell: PlaitTableCell, fill: string | null) => { const newCell = { ...cell diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index 2bed9ba40..9c37e78a3 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -2,16 +2,13 @@ import { BOARD_TO_ROUGH_SVG, createTestingBoard, PlaitBoard, RectangleClient } f import { withDraw } from '../plugins/with-draw'; import { Options } from 'roughjs/bin/core'; import { GeometryShapeGenerator } from '../generators/geometry-shape.generator'; -import { TableGenerator } from '../generators/table.generator'; import { drawShape } from './common'; import { TableSymbols, PlaitTable } from '../interfaces/table'; import { PlaitGeometry, BasicShapes, FlowchartSymbols, FILL_STYLES, UMLSymbols } from '../interfaces/geometry'; import { drawGeometry } from './geometry'; -import { createUMLClassOrInterfaceGeometryElement } from './uml'; describe('fillStyle', () => { let board: PlaitBoard; - type TableBackedGeometry = PlaitGeometry & Omit; beforeEach(() => { board = createTestingBoard([withDraw], []); @@ -68,23 +65,6 @@ describe('fillStyle', () => { const options = rectangleSpy.calls.mostRecent().args[4] as Options; expect(options.fillStyle).toBe('hachure'); }); - - it('should pass element fillStyle to table backed UML geometry', () => { - const roughSVG = PlaitBoard.getRoughSVG(board); - const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); - const element = createUMLClassOrInterfaceGeometryElement(board, UMLSymbols.class, [ - [0, 0], - [100, 100] - ]) as TableBackedGeometry; - element.fillStyle = 'hachure'; - element.cells[0].fill = '#FF5733'; - (board as any).buildTable = (value: TableBackedGeometry) => value; - - new TableGenerator(board).draw(element, {}); - - const options = rectangleSpy.calls.mostRecent().args[4] as Options; - expect(options.fillStyle).toBe('hachure'); - }); }); describe('drawGeometry', () => { @@ -201,7 +181,7 @@ describe('fillStyle', () => { expect(options.fillStyle).toBe('solid'); }); - it('should use table cell fillStyle before table rough options', () => { + it('should keep solid fillStyle for table cell fills even when table rough options include fillStyle', () => { const roughSVG = PlaitBoard.getRoughSVG(board); const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); const rectangle: RectangleClient = { @@ -211,7 +191,7 @@ describe('fillStyle', () => { height: 100 }; const table: PlaitTable = { - id: 'test-table-cell-fill-style', + id: 'test-table-solid-fill-style', type: 'table', points: [ [0, 0], @@ -219,7 +199,7 @@ describe('fillStyle', () => { ], rows: [{ id: 'row-1' }], columns: [{ id: 'column-1' }], - cells: [{ id: 'cell-1', rowId: 'row-1', columnId: 'column-1', fill: '#FF5733', fillStyle: 'hachure' }] + cells: [{ id: 'cell-1', rowId: 'row-1', columnId: 'column-1', fill: '#FF5733' }] }; (board as any).buildTable = (element: PlaitTable) => element; @@ -236,7 +216,7 @@ describe('fillStyle', () => { ); const options = rectangleSpy.calls.mostRecent().args[4] as Options; - expect(options.fillStyle).toBe('hachure'); + expect(options.fillStyle).toBe('solid'); }); }); }); diff --git a/src/app/components/setting-panel/setting-panel.component.ts b/src/app/components/setting-panel/setting-panel.component.ts index 462a8aa80..fd3c10dc5 100644 --- a/src/app/components/setting-panel/setting-panel.component.ts +++ b/src/app/components/setting-panel/setting-panel.component.ts @@ -55,7 +55,6 @@ import { PlaitDrawElement, getSwimlaneCount, getSelectedTableCellsEditor, - getSelectedCells, VectorLineShape, isClosedDrawElement, FillStyle, @@ -170,10 +169,9 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement this.isSelectedVectorLine = !!selectedVectorLineElements.length; this.isSelectSwimlane = isSingleSelectSwimlane(this.board); this.enableSetFillColor = selectedDrawElements.some((item) => isClosedDrawElement(item)) || this.isSelectedMind; - const selectedTableElements = getSelectedTableElements(this.board); - this.enableSetFillStyle = - selectedGeometryElements.some((item) => isClosedDrawElement(item) && !PlaitDrawElement.isElementByTable(item)) || - !!selectedTableElements.length; + this.enableSetFillStyle = selectedGeometryElements.some( + (item) => isClosedDrawElement(item) && !PlaitDrawElement.isElementByTable(item) + ); if (this.isSelectSwimlane) { this.swimlaneCount = getSwimlaneCount(getSelectedElements(this.board)[0] as PlaitSwimlane); } @@ -190,14 +188,9 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement } } const selectedTableCellsEditor = getSelectedTableCellsEditor(this.board); + const selectedTableElements = getSelectedTableElements(this.board); const selectedTableAndGeometryElements = [...selectedGeometryElements, ...selectedTableElements]; if (selectedTableAndGeometryElements.length) { - const firstTableElement = selectedTableElements[0]; - const firstTableCell = - firstTableElement && ((getSelectedCells(firstTableElement)?.[0] || firstTableElement.cells.find((cell) => cell.text)) ?? undefined); - if (firstTableCell) { - this.currentFillStyle = firstTableCell.fillStyle || 'solid'; - } const firstClosedGeometry = selectedGeometryElements.find( (item) => isClosedDrawElement(item) && !PlaitDrawElement.isElementByTable(item) ); @@ -299,13 +292,9 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement PropertyTransforms.setProperty(this.board, { fillStyle } as Partial, { getMemorizeKey, match: (element: PlaitElement) => - (PlaitDrawElement.isGeometry(element) && isClosedDrawElement(element)) || PlaitDrawElement.isElementByTable(element), + PlaitDrawElement.isGeometry(element) && isClosedDrawElement(element) && !PlaitDrawElement.isElementByTable(element), callback: (element: PlaitElement, path: Path) => { - if (PlaitDrawElement.isElementByTable(element)) { - DrawTransforms.setTableFillStyle(this.board, element, fillStyle, path); - } else { - Transforms.setNode(this.board, { fillStyle }, path); - } + Transforms.setNode(this.board, { fillStyle }, path); } }); } From 354207290d9cf13386d39bd8ea725271faffc9e4 Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Fri, 3 Jul 2026 23:35:36 +0800 Subject: [PATCH 12/13] test(draw): simplify fill style coverage --- packages/draw/src/interfaces/geometry.ts | 5 ++- packages/draw/src/utils/fillstyle.spec.ts | 37 ----------------------- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/packages/draw/src/interfaces/geometry.ts b/packages/draw/src/interfaces/geometry.ts index 8df34f37a..4e09cac27 100644 --- a/packages/draw/src/interfaces/geometry.ts +++ b/packages/draw/src/interfaces/geometry.ts @@ -1,8 +1,11 @@ import { PlaitElement, Point } from '@plait/core'; import { DrawTextInfo } from '../generators/text.generator'; import { ParagraphElement, StrokeStyle } from '@plait/common'; +import { Options } from 'roughjs/bin/core'; -export const FILL_STYLES = ['solid', 'hachure', 'zigzag', 'cross-hatch', 'dots', 'dashed', 'zigzag-line'] as const; +export const FILL_STYLES = ['solid', 'hachure', 'zigzag', 'cross-hatch', 'dots', 'dashed', 'zigzag-line'] as const satisfies readonly NonNullable< + Options['fillStyle'] +>[]; export type FillStyle = (typeof FILL_STYLES)[number]; diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fillstyle.spec.ts index 9c37e78a3..13e3aa2ea 100644 --- a/packages/draw/src/utils/fillstyle.spec.ts +++ b/packages/draw/src/utils/fillstyle.spec.ts @@ -181,42 +181,5 @@ describe('fillStyle', () => { expect(options.fillStyle).toBe('solid'); }); - it('should keep solid fillStyle for table cell fills even when table rough options include fillStyle', () => { - const roughSVG = PlaitBoard.getRoughSVG(board); - const rectangleSpy = spyOn(roughSVG, 'rectangle').and.callThrough(); - const rectangle: RectangleClient = { - x: 0, - y: 0, - width: 100, - height: 100 - }; - const table: PlaitTable = { - id: 'test-table-solid-fill-style', - type: 'table', - points: [ - [0, 0], - [100, 100] - ], - rows: [{ id: 'row-1' }], - columns: [{ id: 'column-1' }], - cells: [{ id: 'cell-1', rowId: 'row-1', columnId: 'column-1', fill: '#FF5733' }] - }; - (board as any).buildTable = (element: PlaitTable) => element; - - drawShape( - board, - rectangle, - TableSymbols.table, - { - stroke: '#000000', - strokeWidth: 2, - fillStyle: 'solid' - }, - { element: table } - ); - - const options = rectangleSpy.calls.mostRecent().args[4] as Options; - expect(options.fillStyle).toBe('solid'); - }); }); }); From c14fc27a4660d71ad39fccf7e607204dea909e4d Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Sat, 4 Jul 2026 10:21:56 +0800 Subject: [PATCH 13/13] test(draw): rename fill style spec --- packages/draw/src/utils/{fillstyle.spec.ts => fill-style.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/draw/src/utils/{fillstyle.spec.ts => fill-style.spec.ts} (100%) diff --git a/packages/draw/src/utils/fillstyle.spec.ts b/packages/draw/src/utils/fill-style.spec.ts similarity index 100% rename from packages/draw/src/utils/fillstyle.spec.ts rename to packages/draw/src/utils/fill-style.spec.ts