@pixi-board/board-plugin-canvas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1131 -0
- package/package.json +27 -0
- package/src/dto.ts +151 -0
- package/src/errors.ts +11 -0
- package/src/filter.ts +106 -0
- package/src/index.ts +497 -0
- package/src/projects.ts +269 -0
- package/src/reader.ts +177 -0
- package/src/writerClient.ts +141 -0
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pixi-board/board-plugin-canvas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"src",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^24.10.1",
|
|
19
|
+
"tsup": "^8.5.0",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
21
|
+
"@canvas/board-domain": "0.1.0",
|
|
22
|
+
"@canvas/board-plugin-sdk": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format esm --platform node --target node20 --out-dir dist --clean"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/dto.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
displayNodeName,
|
|
3
|
+
nodeBounds,
|
|
4
|
+
type Asset,
|
|
5
|
+
type BoardNode,
|
|
6
|
+
type BoardSnapshot,
|
|
7
|
+
type ProjectInfo,
|
|
8
|
+
} from "@canvas/board-domain";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
export type CanvasNodeDto = {
|
|
12
|
+
id: string;
|
|
13
|
+
type: BoardNode["type"];
|
|
14
|
+
name?: string;
|
|
15
|
+
displayName: string;
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
rotation: number;
|
|
21
|
+
zIndex: number;
|
|
22
|
+
locked: boolean;
|
|
23
|
+
bounds: {
|
|
24
|
+
minX: number;
|
|
25
|
+
minY: number;
|
|
26
|
+
maxX: number;
|
|
27
|
+
maxY: number;
|
|
28
|
+
};
|
|
29
|
+
assetId: string;
|
|
30
|
+
options?: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type CanvasAssetDto = {
|
|
34
|
+
id: string;
|
|
35
|
+
kind: Asset["kind"];
|
|
36
|
+
fileName?: string;
|
|
37
|
+
mimeType?: string;
|
|
38
|
+
size?: number;
|
|
39
|
+
original?: {
|
|
40
|
+
localPath: string;
|
|
41
|
+
absolutePath: string;
|
|
42
|
+
sourceUrl?: string;
|
|
43
|
+
webLink?: string;
|
|
44
|
+
};
|
|
45
|
+
derivatives: Array<{
|
|
46
|
+
variant: string;
|
|
47
|
+
localPath: string;
|
|
48
|
+
absolutePath: string;
|
|
49
|
+
extension: string;
|
|
50
|
+
updatedAt: number;
|
|
51
|
+
}>;
|
|
52
|
+
metadata: {
|
|
53
|
+
width?: number;
|
|
54
|
+
height?: number;
|
|
55
|
+
duration?: number;
|
|
56
|
+
format?: string;
|
|
57
|
+
hash?: string;
|
|
58
|
+
metadata?: Record<string, unknown>;
|
|
59
|
+
createdAt: number;
|
|
60
|
+
updatedAt: number;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function toNodeDto(node: BoardNode): CanvasNodeDto {
|
|
65
|
+
const bounds = nodeBounds(node);
|
|
66
|
+
return {
|
|
67
|
+
id: node.id,
|
|
68
|
+
type: node.type,
|
|
69
|
+
name: node.name,
|
|
70
|
+
displayName: displayNodeName(node.name),
|
|
71
|
+
x: node.x,
|
|
72
|
+
y: node.y,
|
|
73
|
+
width: node.width,
|
|
74
|
+
height: node.height,
|
|
75
|
+
rotation: node.rotation,
|
|
76
|
+
zIndex: node.zIndex,
|
|
77
|
+
locked: node.locked ?? false,
|
|
78
|
+
assetId: node.assetId,
|
|
79
|
+
options: node.options,
|
|
80
|
+
bounds: {
|
|
81
|
+
minX: bounds.minX,
|
|
82
|
+
minY: bounds.minY,
|
|
83
|
+
maxX: bounds.maxX,
|
|
84
|
+
maxY: bounds.maxY,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function toAssetDto(projectRoot: string, asset: Asset): CanvasAssetDto {
|
|
90
|
+
return {
|
|
91
|
+
id: asset.id,
|
|
92
|
+
kind: asset.kind,
|
|
93
|
+
fileName: asset.fileName,
|
|
94
|
+
mimeType: asset.mimeType,
|
|
95
|
+
size: asset.size,
|
|
96
|
+
original: asset.localPath
|
|
97
|
+
? {
|
|
98
|
+
localPath: asset.localPath,
|
|
99
|
+
absolutePath: path.resolve(projectRoot, asset.localPath),
|
|
100
|
+
sourceUrl: asset.sourceUrl,
|
|
101
|
+
webLink: asset.webLink,
|
|
102
|
+
}
|
|
103
|
+
: undefined,
|
|
104
|
+
derivatives: Object.entries(asset.derivatives ?? {}).map(([variant, derivative]) => ({
|
|
105
|
+
variant,
|
|
106
|
+
localPath: derivative.localPath,
|
|
107
|
+
absolutePath: path.resolve(projectRoot, derivative.localPath),
|
|
108
|
+
extension: derivative.extension,
|
|
109
|
+
updatedAt: derivative.updatedAt,
|
|
110
|
+
})),
|
|
111
|
+
metadata: {
|
|
112
|
+
width: asset.width,
|
|
113
|
+
height: asset.height,
|
|
114
|
+
duration: asset.duration,
|
|
115
|
+
format: asset.format,
|
|
116
|
+
hash: asset.hash,
|
|
117
|
+
metadata: asset.metadata,
|
|
118
|
+
createdAt: asset.createdAt,
|
|
119
|
+
updatedAt: asset.updatedAt,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function toProjectInfo(projectRoot: string): ProjectInfo {
|
|
125
|
+
return {
|
|
126
|
+
name: path.basename(projectRoot),
|
|
127
|
+
rootPath: projectRoot,
|
|
128
|
+
boardPath: path.join(projectRoot, "board.json"),
|
|
129
|
+
assetsPath: path.join(projectRoot, "assets.json"),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function toSnapshotDto(projectRoot: string, snapshot: BoardSnapshot) {
|
|
134
|
+
return {
|
|
135
|
+
project: toProjectInfo(projectRoot),
|
|
136
|
+
viewport: snapshot.viewport ?? null,
|
|
137
|
+
nodes: snapshot.nodes.map(toNodeDto),
|
|
138
|
+
assets: snapshot.assets.map((asset) => ({
|
|
139
|
+
id: asset.id,
|
|
140
|
+
kind: asset.kind,
|
|
141
|
+
fileName: asset.fileName,
|
|
142
|
+
mimeType: asset.mimeType,
|
|
143
|
+
size: asset.size,
|
|
144
|
+
metadata: asset.metadata,
|
|
145
|
+
width: asset.width,
|
|
146
|
+
height: asset.height,
|
|
147
|
+
duration: asset.duration,
|
|
148
|
+
updatedAt: asset.updatedAt,
|
|
149
|
+
})),
|
|
150
|
+
};
|
|
151
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class BoardToolUserError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "BoardToolUserError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function errorMessage(error: unknown): string {
|
|
9
|
+
if (error instanceof Error) return error.message;
|
|
10
|
+
return String(error);
|
|
11
|
+
}
|
package/src/filter.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { nodeBounds, type BoardNode } from "@canvas/board-domain";
|
|
2
|
+
|
|
3
|
+
const NODE_TYPE_VALUES = ["image", "video", "audio", "model", "text", "markdown", "html", "importing", "generating"] as const;
|
|
4
|
+
|
|
5
|
+
export type NodeFilter = {
|
|
6
|
+
type?: BoardNode["type"];
|
|
7
|
+
keyword?: string;
|
|
8
|
+
bounds?: {
|
|
9
|
+
x?: number;
|
|
10
|
+
y?: number;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
minX?: number;
|
|
14
|
+
minY?: number;
|
|
15
|
+
maxX?: number;
|
|
16
|
+
maxY?: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type NormalizedBounds = {
|
|
21
|
+
minX: number;
|
|
22
|
+
minY: number;
|
|
23
|
+
maxX: number;
|
|
24
|
+
maxY: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function filterNodes(nodes: BoardNode[], filter: unknown): BoardNode[] {
|
|
28
|
+
const parsed = parseFilter(filter);
|
|
29
|
+
return nodes.filter((node) => matchesType(node, parsed) && matchesKeyword(node, parsed) && matchesBounds(node, parsed));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseFilter(filter: unknown): NodeFilter {
|
|
33
|
+
if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
const source = filter as Record<string, unknown>;
|
|
37
|
+
const parsed: NodeFilter = {};
|
|
38
|
+
if (typeof source.type === "string" && NODE_TYPE_VALUES.includes(source.type as BoardNode["type"])) {
|
|
39
|
+
parsed.type = source.type as BoardNode["type"];
|
|
40
|
+
}
|
|
41
|
+
if (typeof source.keyword === "string") {
|
|
42
|
+
parsed.keyword = source.keyword;
|
|
43
|
+
} else if (typeof source.name === "string") {
|
|
44
|
+
parsed.keyword = source.name;
|
|
45
|
+
} else if (typeof source.text === "string") {
|
|
46
|
+
parsed.keyword = source.text;
|
|
47
|
+
}
|
|
48
|
+
if (source.bounds && typeof source.bounds === "object" && !Array.isArray(source.bounds)) {
|
|
49
|
+
parsed.bounds = source.bounds as NodeFilter["bounds"];
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function matchesType(node: BoardNode, filter: NodeFilter): boolean {
|
|
55
|
+
return !filter.type || node.type === filter.type;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function matchesKeyword(node: BoardNode, filter: NodeFilter): boolean {
|
|
59
|
+
const keyword = filter.keyword?.trim().toLowerCase();
|
|
60
|
+
if (!keyword) return true;
|
|
61
|
+
const haystack = [node.id, node.type, node.name ?? "", node.assetId].join("\n").toLowerCase();
|
|
62
|
+
return haystack.includes(keyword);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function matchesBounds(node: BoardNode, filter: NodeFilter): boolean {
|
|
66
|
+
if (!filter.bounds) return true;
|
|
67
|
+
const bounds = normalizeBounds(filter.bounds);
|
|
68
|
+
if (!bounds) return true;
|
|
69
|
+
const current = nodeBounds(node);
|
|
70
|
+
return (
|
|
71
|
+
current.maxX >= bounds.minX &&
|
|
72
|
+
current.minX <= bounds.maxX &&
|
|
73
|
+
current.maxY >= bounds.minY &&
|
|
74
|
+
current.minY <= bounds.maxY
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeBounds(bounds: NonNullable<NodeFilter["bounds"]>): NormalizedBounds | null {
|
|
79
|
+
if (
|
|
80
|
+
typeof bounds.minX === "number" &&
|
|
81
|
+
typeof bounds.minY === "number" &&
|
|
82
|
+
typeof bounds.maxX === "number" &&
|
|
83
|
+
typeof bounds.maxY === "number"
|
|
84
|
+
) {
|
|
85
|
+
return {
|
|
86
|
+
minX: bounds.minX,
|
|
87
|
+
minY: bounds.minY,
|
|
88
|
+
maxX: bounds.maxX,
|
|
89
|
+
maxY: bounds.maxY,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
typeof bounds.x === "number" &&
|
|
94
|
+
typeof bounds.y === "number" &&
|
|
95
|
+
typeof bounds.width === "number" &&
|
|
96
|
+
typeof bounds.height === "number"
|
|
97
|
+
) {
|
|
98
|
+
return {
|
|
99
|
+
minX: bounds.x,
|
|
100
|
+
minY: bounds.y,
|
|
101
|
+
maxX: bounds.x + bounds.width,
|
|
102
|
+
maxY: bounds.y + bounds.height,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|