feat(canvas): add inherited elements, resizing and rotation

This commit is contained in:
2026-11-06 02:03:23 +08:00
parent e28ddb673f
commit 54ff5a0fbd
12 changed files with 411 additions and 38 deletions
+54
View File
@@ -0,0 +1,54 @@
import type { Node, XYPosition } from "@xyflow/react";
export type ElementKind = "text" | "image" | "script" | "plain-text";
export type CanvasNodeData = {
kind: ElementKind;
title: string;
text: string;
src?: string;
rotation?: number;
fontSize?: number;
};
export type CanvasNode = Node<CanvasNodeData, "material" | "plainText">;
export type ElementCapabilities = { resizable: boolean; rotatable: boolean; connectable: boolean; branchable: boolean };
/** Business inheritance only: React views reuse these capabilities by composition. */
export abstract class CanvasElement {
static readonly capabilities: ElementCapabilities = { resizable: true, rotatable: true, connectable: false, branchable: false };
abstract readonly kind: ElementKind;
abstract readonly title: string;
protected readonly nodeType: CanvasNode["type"] = "plainText";
protected readonly size = { width: 240, height: 72 };
readonly position: XYPosition;
readonly text: string;
readonly src?: string;
constructor(position: XYPosition, text = "", src?: string) {
this.position = position;
this.text = text;
this.src = src;
}
toNode(): CanvasNode {
return {
id: crypto.randomUUID(), type: this.nodeType, position: { ...this.position },
width: this.size.width, height: this.size.height,
style: { width: this.size.width, height: this.size.height },
data: { kind: this.kind, title: this.title, text: this.text, rotation: 0,
fontSize: this.kind === "plain-text" ? 24 : 13, ...(this.src ? { src: this.src } : {}) },
};
}
}
export abstract class CardElement extends CanvasElement {
static override readonly capabilities: ElementCapabilities = { ...CanvasElement.capabilities, connectable: true, branchable: true };
protected override readonly nodeType = "material" as const;
protected override readonly size = { width: 260, height: 200 };
}
export class TextCardElement extends CardElement { readonly kind = "text"; readonly title = "灵感笔记"; }
export class ImageCardElement extends CardElement { readonly kind = "image"; readonly title = "视觉参考"; }
export class ScriptCardElement extends CardElement { readonly kind = "script"; readonly title = "脚本"; }
export class PlainTextElement extends CanvasElement { readonly kind = "plain-text"; readonly title = "纯文本"; }
export const elementClasses = { text: TextCardElement, image: ImageCardElement, script: ScriptCardElement, "plain-text": PlainTextElement };
export function elementCapabilities(kind: ElementKind) { return elementClasses[kind].capabilities; }
export function createCanvasElement(kind: ElementKind, position: XYPosition, src?: string): CanvasNode {
return new elementClasses[kind](position, "", src).toNode();
}