pi-tldraw 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/LICENSE +21 -0
- package/README.md +222 -0
- package/bridge/app-bridge-entry.js +6 -0
- package/mcp-app/LICENSE.md +9 -0
- package/mcp-app/PI_TLDRAW_PROVENANCE.json +32 -0
- package/mcp-app/README.md +129 -0
- package/mcp-app/dev-tunnel.sh +51 -0
- package/mcp-app/dist/editor-api.json +8493 -0
- package/mcp-app/dist/mcp-app.html +643 -0
- package/mcp-app/dist/method-map.json +915 -0
- package/mcp-app/package.json +42 -0
- package/mcp-app/plugins/tldraw-mcp/.cursor-plugin/plugin.json +10 -0
- package/mcp-app/plugins/tldraw-mcp/assets/logo.svg +3 -0
- package/mcp-app/plugins/tldraw-mcp/mcp.json +8 -0
- package/mcp-app/scripts/extract-editor-api.ts +1374 -0
- package/mcp-app/server.json +21 -0
- package/mcp-app/src/logger.ts +45 -0
- package/mcp-app/src/register-tools.ts +368 -0
- package/mcp-app/src/shared/generated-data.ts +160 -0
- package/mcp-app/src/shared/pending-requests.ts +69 -0
- package/mcp-app/src/shared/types.ts +76 -0
- package/mcp-app/src/shared/utils.ts +132 -0
- package/mcp-app/src/tools/exec.ts +120 -0
- package/mcp-app/src/tools/loadCachedCanvasWidgetHtml.ts +16 -0
- package/mcp-app/src/tools/search.ts +150 -0
- package/mcp-app/src/widget/app-context.tsx +29 -0
- package/mcp-app/src/widget/dev-log.tsx +70 -0
- package/mcp-app/src/widget/exec-helpers.ts +232 -0
- package/mcp-app/src/widget/export-tldr.ts +35 -0
- package/mcp-app/src/widget/focused/defaults.ts +141 -0
- package/mcp-app/src/widget/focused/focused-editor-proxy.ts +434 -0
- package/mcp-app/src/widget/focused/format.ts +366 -0
- package/mcp-app/src/widget/focused/to-focused.ts +258 -0
- package/mcp-app/src/widget/focused/to-tldraw.ts +570 -0
- package/mcp-app/src/widget/image-guard.tsx +106 -0
- package/mcp-app/src/widget/index.html +33 -0
- package/mcp-app/src/widget/mcp-app.css +113 -0
- package/mcp-app/src/widget/mcp-app.tsx +857 -0
- package/mcp-app/src/widget/persistence.ts +337 -0
- package/mcp-app/src/widget/snapshot.ts +157 -0
- package/mcp-app/src/worker.ts +305 -0
- package/mcp-app/tsconfig.json +23 -0
- package/mcp-app/vite.config.ts +13 -0
- package/mcp-app/wrangler.toml +45 -0
- package/mcp-app-source.json +36 -0
- package/package.json +90 -0
- package/patches/tldraw-mcp-app/001-pi-runtime.patch +35 -0
- package/scripts/assemble-mcp-app.mjs +193 -0
- package/scripts/build-bridge.mjs +74 -0
- package/scripts/e2e-mcp.mjs +69 -0
- package/scripts/e2e-packaged-mcp-app.mjs +79 -0
- package/scripts/run-mcp-app-dev.mjs +44 -0
- package/scripts/verify-bundle.mjs +41 -0
- package/scripts/verify-mcp-app-source.mjs +51 -0
- package/scripts/verify-mcp-app.mjs +38 -0
- package/scripts/verify-package-files.mjs +50 -0
- package/src/canvas/export.ts +164 -0
- package/src/canvas/state.ts +117 -0
- package/src/canvas/workflow.ts +105 -0
- package/src/commands/tldraw-command.ts +48 -0
- package/src/diagram/guidance.ts +44 -0
- package/src/host/local-host.ts +289 -0
- package/src/index.ts +762 -0
- package/src/mcp/client.ts +126 -0
- package/src/mcp/response.ts +74 -0
- package/src/semantic/layer.ts +309 -0
- package/src/server/server-manager.ts +153 -0
- package/src/store/export-store.ts +33 -0
- package/src/store/project-store.ts +251 -0
- package/src/ui/tldraw-status.ts +88 -0
- package/static/app-bridge-bundle.js +18114 -0
- package/static/app-bridge-bundle.meta.json +164 -0
- package/static/host.html +390 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ES Proxy that wraps the tldraw Editor to auto-convert between focused format
|
|
3
|
+
* and tldraw's internal format at method boundaries.
|
|
4
|
+
*
|
|
5
|
+
* AI agents interact entirely in focused format — simple string IDs, flat shape
|
|
6
|
+
* objects with `_type` — and the proxy silently translates to/from tldraw's
|
|
7
|
+
* `TLShape`, `TLShapeId`, etc.
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
Editor,
|
|
11
|
+
TLBindingId,
|
|
12
|
+
TLCreateShapePartial,
|
|
13
|
+
TLShape,
|
|
14
|
+
TLShapeId,
|
|
15
|
+
TLShapePartial,
|
|
16
|
+
} from 'tldraw'
|
|
17
|
+
import type { MethodMap, RetKind } from '../../shared/generated-data'
|
|
18
|
+
import { getDefaultShape } from './defaults'
|
|
19
|
+
import { convertSimpleIdToTldrawId, convertTldrawIdToSimpleId, type FocusedShape } from './format'
|
|
20
|
+
import { convertTldrawShapeToFocusedShape } from './to-focused'
|
|
21
|
+
import { convertFocusedShapeToTldrawShape } from './to-tldraw'
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Input conversion helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Convert a single value that might be a focused ID or focused shape into tldraw format. */
|
|
28
|
+
function convertIdOrShape(val: string | TLShapeId | TLShape | FocusedShape | null | undefined) {
|
|
29
|
+
if (val === null || val === undefined) return val
|
|
30
|
+
if (typeof val === 'string') return ensureTldrawId(val)
|
|
31
|
+
if ('_type' in val) {
|
|
32
|
+
return ensureTldrawId((val as FocusedShape).shapeId)
|
|
33
|
+
}
|
|
34
|
+
return val
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Convert an array where each element might be a focused ID or focused shape. */
|
|
38
|
+
function convertIdsOrShapes(
|
|
39
|
+
arr: Array<string | TLShapeId | TLShape | FocusedShape> | TLShapeId[] | TLShape[]
|
|
40
|
+
) {
|
|
41
|
+
return arr.map(convertIdOrShape)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Ensure a string is a valid TLShapeId. Passthrough if already prefixed. */
|
|
45
|
+
function ensureTldrawId(id: string): TLShapeId {
|
|
46
|
+
if (id.startsWith('shape:')) return id as TLShapeId
|
|
47
|
+
return convertSimpleIdToTldrawId(id)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Detect whether a value is a focused shape (has `_type` field). */
|
|
51
|
+
function isFocusedShape(
|
|
52
|
+
val: FocusedShape | TLShapePartial | TLCreateShapePartial
|
|
53
|
+
): val is FocusedShape {
|
|
54
|
+
return '_type' in val
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Detect update payloads that use focused `shapeId` but omit `_type`. */
|
|
58
|
+
function hasFocusedShapeId(
|
|
59
|
+
val: FocusedShape | TLShapePartial | TLCreateShapePartial
|
|
60
|
+
): val is TLShapePartial & { shapeId: string } {
|
|
61
|
+
return (
|
|
62
|
+
typeof val === 'object' &&
|
|
63
|
+
val !== null &&
|
|
64
|
+
'shapeId' in val &&
|
|
65
|
+
typeof (val as { shapeId?: unknown }).shapeId === 'string'
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Normalize raw tldraw shape partial IDs when models omit the `shape:` prefix. */
|
|
70
|
+
function normalizeRawShapePartialId<T extends TLShapePartial>(partial: T): T {
|
|
71
|
+
const rawId = partial.id
|
|
72
|
+
if (typeof rawId !== 'string') return partial
|
|
73
|
+
if (rawId.startsWith('shape:')) return partial
|
|
74
|
+
return { ...partial, id: ensureTldrawId(rawId) } as T
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Normalize an incoming partial into focused shape format when possible.
|
|
79
|
+
* - If `_type` is already present, returns as-is.
|
|
80
|
+
* - If `shapeId` is present, infers `_type` from the existing canvas shape.
|
|
81
|
+
* - Otherwise returns null, meaning callers should treat it as raw tldraw input.
|
|
82
|
+
*/
|
|
83
|
+
function toFocusedShapeIfPossible(
|
|
84
|
+
editor: Editor,
|
|
85
|
+
partial: FocusedShape | TLShapePartial | TLCreateShapePartial
|
|
86
|
+
): FocusedShape | null {
|
|
87
|
+
if (isFocusedShape(partial)) return partial
|
|
88
|
+
if (!hasFocusedShapeId(partial)) return null
|
|
89
|
+
|
|
90
|
+
const shapeId = ensureTldrawId(partial.shapeId)
|
|
91
|
+
const existingShape = editor.getShape(shapeId)
|
|
92
|
+
if (!existingShape) return null
|
|
93
|
+
|
|
94
|
+
const focusedExisting = convertTldrawShapeToFocusedShape(editor, existingShape)
|
|
95
|
+
const merged = {
|
|
96
|
+
...focusedExisting,
|
|
97
|
+
...partial,
|
|
98
|
+
_type: focusedExisting._type,
|
|
99
|
+
shapeId: focusedExisting.shapeId,
|
|
100
|
+
}
|
|
101
|
+
// `partial` may be a broad TLShapePartial union; runtime merge is safe here
|
|
102
|
+
// because we anchor on a valid focused shape from the existing record.
|
|
103
|
+
return merged as unknown as FocusedShape
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Output conversion helpers
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
function convertOutputShape(editor: Editor, shape: TLShape): FocusedShape {
|
|
111
|
+
try {
|
|
112
|
+
return convertTldrawShapeToFocusedShape(editor, shape)
|
|
113
|
+
} catch {
|
|
114
|
+
return {
|
|
115
|
+
_type: 'unknown',
|
|
116
|
+
shapeId: convertTldrawIdToSimpleId(shape.id),
|
|
117
|
+
subType: shape.type,
|
|
118
|
+
note: '',
|
|
119
|
+
x: shape.x,
|
|
120
|
+
y: shape.y,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isTLShape(val: TLShape | FocusedShape | string | null | undefined): val is TLShape {
|
|
126
|
+
if (val === null || val === undefined || typeof val === 'string') return false
|
|
127
|
+
return 'typeName' in val && val.typeName === 'shape'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Convert a return value from tldraw format to focused format based on the method spec.
|
|
132
|
+
* Uses targeted casts because the Proxy handler is inherently dynamic dispatch —
|
|
133
|
+
* the `result` type varies per method and can't be statically narrowed from the spec string.
|
|
134
|
+
*/
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
136
|
+
type ProxyResult =
|
|
137
|
+
| TLShape
|
|
138
|
+
| TLShape[]
|
|
139
|
+
| TLShapeId
|
|
140
|
+
| TLShapeId[]
|
|
141
|
+
| Set<TLShapeId>
|
|
142
|
+
| Editor
|
|
143
|
+
| null
|
|
144
|
+
| undefined
|
|
145
|
+
| void
|
|
146
|
+
|
|
147
|
+
function convertReturnValue(editor: Editor, proxy: Editor, spec: RetKind, result: ProxyResult) {
|
|
148
|
+
switch (spec) {
|
|
149
|
+
case 'this':
|
|
150
|
+
return proxy
|
|
151
|
+
case 'shape': {
|
|
152
|
+
const shape = result as TLShape | undefined
|
|
153
|
+
return shape && isTLShape(shape) ? convertOutputShape(editor, shape) : result
|
|
154
|
+
}
|
|
155
|
+
case 'shape-or-null': {
|
|
156
|
+
if (result === null || result === undefined) return result
|
|
157
|
+
const shape = result as TLShape
|
|
158
|
+
return isTLShape(shape) ? convertOutputShape(editor, shape) : result
|
|
159
|
+
}
|
|
160
|
+
case 'shapes': {
|
|
161
|
+
const shapes = result as TLShape[]
|
|
162
|
+
if (!Array.isArray(shapes)) return result
|
|
163
|
+
return shapes.map((s) => (isTLShape(s) ? convertOutputShape(editor, s) : s))
|
|
164
|
+
}
|
|
165
|
+
case 'id': {
|
|
166
|
+
const id = result as TLShapeId
|
|
167
|
+
return typeof id === 'string' ? convertTldrawIdToSimpleId(id) : result
|
|
168
|
+
}
|
|
169
|
+
case 'id-or-null': {
|
|
170
|
+
if (result === null || result === undefined) return result
|
|
171
|
+
const id = result as TLShapeId
|
|
172
|
+
return typeof id === 'string' ? convertTldrawIdToSimpleId(id) : result
|
|
173
|
+
}
|
|
174
|
+
case 'ids': {
|
|
175
|
+
const ids = result as TLShapeId[]
|
|
176
|
+
if (!Array.isArray(ids)) return result
|
|
177
|
+
return ids.map((id) => (typeof id === 'string' ? convertTldrawIdToSimpleId(id) : id))
|
|
178
|
+
}
|
|
179
|
+
case 'id-set': {
|
|
180
|
+
const idSet = result as Set<TLShapeId>
|
|
181
|
+
if (!(idSet instanceof Set)) return result
|
|
182
|
+
const out = new Set<string>()
|
|
183
|
+
for (const id of idSet) {
|
|
184
|
+
out.add(convertTldrawIdToSimpleId(id))
|
|
185
|
+
}
|
|
186
|
+
return out
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Special-case handlers for create/update (arrow bindings)
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
type CreateEditorMethod = (...args: Parameters<Editor['createShape']>) => Editor
|
|
196
|
+
type UpdateEditorMethod = (...args: Parameters<Editor['updateShape']>) => Editor
|
|
197
|
+
|
|
198
|
+
function handleCreateShape(
|
|
199
|
+
editor: Editor,
|
|
200
|
+
proxy: Editor,
|
|
201
|
+
partial: FocusedShape | TLCreateShapePartial,
|
|
202
|
+
realMethod: CreateEditorMethod
|
|
203
|
+
): Editor {
|
|
204
|
+
if (!isFocusedShape(partial)) {
|
|
205
|
+
realMethod.call(editor, partial)
|
|
206
|
+
return proxy
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const result = convertFocusedShapeToTldrawShape(editor, partial, {
|
|
210
|
+
defaultShape: getDefaultShape(partial._type),
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
editor.createShape(result.shape)
|
|
214
|
+
|
|
215
|
+
if (result.bindings) {
|
|
216
|
+
for (const binding of result.bindings) {
|
|
217
|
+
editor.createBinding({
|
|
218
|
+
type: binding.type,
|
|
219
|
+
fromId: binding.fromId,
|
|
220
|
+
toId: binding.toId,
|
|
221
|
+
props: binding.props,
|
|
222
|
+
meta: binding.meta,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return proxy
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function handleCreateShapes(
|
|
231
|
+
editor: Editor,
|
|
232
|
+
proxy: Editor,
|
|
233
|
+
partials: Array<FocusedShape | TLCreateShapePartial>,
|
|
234
|
+
realMethod: CreateEditorMethod
|
|
235
|
+
): Editor {
|
|
236
|
+
if (!Array.isArray(partials)) {
|
|
237
|
+
realMethod.call(editor, partials)
|
|
238
|
+
return proxy
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const partial of partials) {
|
|
242
|
+
handleCreateShape(editor, proxy, partial, realMethod)
|
|
243
|
+
}
|
|
244
|
+
return proxy
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function handleUpdateShape(
|
|
248
|
+
editor: Editor,
|
|
249
|
+
proxy: Editor,
|
|
250
|
+
partial: FocusedShape | TLShapePartial,
|
|
251
|
+
realMethod: UpdateEditorMethod
|
|
252
|
+
): Editor {
|
|
253
|
+
const focusedPartial = toFocusedShapeIfPossible(editor, partial)
|
|
254
|
+
if (!focusedPartial) {
|
|
255
|
+
realMethod.call(editor, normalizeRawShapePartialId(partial as TLShapePartial))
|
|
256
|
+
return proxy
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const shapeId = ensureTldrawId(focusedPartial.shapeId)
|
|
260
|
+
const existingShape = editor.getShape(shapeId)
|
|
261
|
+
if (!existingShape) {
|
|
262
|
+
return proxy
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const result = convertFocusedShapeToTldrawShape(editor, focusedPartial, {
|
|
266
|
+
defaultShape: existingShape,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
editor.updateShape(result.shape)
|
|
270
|
+
|
|
271
|
+
if (result.bindings) {
|
|
272
|
+
const existingBindings = editor.getBindingsFromShape(shapeId, 'arrow')
|
|
273
|
+
for (const binding of existingBindings) {
|
|
274
|
+
editor.deleteBinding(binding.id as TLBindingId)
|
|
275
|
+
}
|
|
276
|
+
for (const binding of result.bindings) {
|
|
277
|
+
editor.createBinding({
|
|
278
|
+
type: binding.type,
|
|
279
|
+
fromId: binding.fromId,
|
|
280
|
+
toId: binding.toId,
|
|
281
|
+
props: binding.props,
|
|
282
|
+
meta: binding.meta,
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return proxy
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function handleUpdateShapes(
|
|
291
|
+
editor: Editor,
|
|
292
|
+
proxy: Editor,
|
|
293
|
+
partials: Array<FocusedShape | TLShapePartial>,
|
|
294
|
+
realMethod: UpdateEditorMethod
|
|
295
|
+
): Editor {
|
|
296
|
+
if (!Array.isArray(partials)) {
|
|
297
|
+
realMethod.call(editor, partials)
|
|
298
|
+
return proxy
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const partial of partials) {
|
|
302
|
+
handleUpdateShape(editor, proxy, partial, realMethod)
|
|
303
|
+
}
|
|
304
|
+
return proxy
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// The Proxy factory
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
export function createFocusedEditorProxy(editor: Editor, methodMap: MethodMap): Editor {
|
|
312
|
+
const proxy: Editor = new Proxy(editor, {
|
|
313
|
+
get(target, prop, receiver) {
|
|
314
|
+
const value = Reflect.get(target, prop, receiver)
|
|
315
|
+
|
|
316
|
+
// Only intercept function calls on string-named properties
|
|
317
|
+
if (typeof prop !== 'string' || typeof value !== 'function') {
|
|
318
|
+
return value
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const spec = methodMap[prop]
|
|
322
|
+
|
|
323
|
+
// --- Special-case: create/update need binding handling ---
|
|
324
|
+
if (prop === 'createShape') {
|
|
325
|
+
return (partial: FocusedShape | TLCreateShapePartial) =>
|
|
326
|
+
handleCreateShape(target, proxy, partial, value as CreateEditorMethod)
|
|
327
|
+
}
|
|
328
|
+
if (prop === 'createShapes') {
|
|
329
|
+
return (partials: Array<FocusedShape | TLCreateShapePartial>) =>
|
|
330
|
+
handleCreateShapes(target, proxy, partials, value as CreateEditorMethod)
|
|
331
|
+
}
|
|
332
|
+
if (prop === 'updateShape') {
|
|
333
|
+
return (partial: FocusedShape | TLShapePartial) =>
|
|
334
|
+
handleUpdateShape(target, proxy, partial, value as UpdateEditorMethod)
|
|
335
|
+
}
|
|
336
|
+
if (prop === 'updateShapes') {
|
|
337
|
+
return (partials: Array<FocusedShape | TLShapePartial>) =>
|
|
338
|
+
handleUpdateShapes(target, proxy, partials, value as UpdateEditorMethod)
|
|
339
|
+
}
|
|
340
|
+
if (prop === 'animateShape') {
|
|
341
|
+
return (partial: FocusedShape | TLShapePartial, ...rest: [Record<string, number>?]) => {
|
|
342
|
+
const focusedPartial = toFocusedShapeIfPossible(target, partial)
|
|
343
|
+
if (focusedPartial) {
|
|
344
|
+
const shapeId = ensureTldrawId(focusedPartial.shapeId)
|
|
345
|
+
const existing = target.getShape(shapeId)
|
|
346
|
+
if (existing) {
|
|
347
|
+
const converted = convertFocusedShapeToTldrawShape(target, focusedPartial, {
|
|
348
|
+
defaultShape: existing,
|
|
349
|
+
})
|
|
350
|
+
value.call(target, converted.shape, ...rest)
|
|
351
|
+
return proxy
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
value.call(target, normalizeRawShapePartialId(partial as TLShapePartial), ...rest)
|
|
355
|
+
return proxy
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (prop === 'animateShapes') {
|
|
359
|
+
return (
|
|
360
|
+
partials: Array<FocusedShape | TLShapePartial>,
|
|
361
|
+
...rest: [Record<string, number>?]
|
|
362
|
+
) => {
|
|
363
|
+
if (Array.isArray(partials)) {
|
|
364
|
+
const converted = partials.map((p) => {
|
|
365
|
+
const focusedPartial = toFocusedShapeIfPossible(target, p)
|
|
366
|
+
if (focusedPartial) {
|
|
367
|
+
const shapeId = ensureTldrawId(focusedPartial.shapeId)
|
|
368
|
+
const existing = target.getShape(shapeId)
|
|
369
|
+
if (existing) {
|
|
370
|
+
return convertFocusedShapeToTldrawShape(target, focusedPartial, {
|
|
371
|
+
defaultShape: existing,
|
|
372
|
+
}).shape
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return normalizeRawShapePartialId(p as TLShapePartial)
|
|
376
|
+
})
|
|
377
|
+
value.call(target, converted, ...rest)
|
|
378
|
+
} else {
|
|
379
|
+
value.call(target, partials, ...rest)
|
|
380
|
+
}
|
|
381
|
+
return proxy
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// --- No spec: pass through, but still catch `this` returns ---
|
|
386
|
+
if (!spec) {
|
|
387
|
+
return (...args: Parameters<typeof value>) => {
|
|
388
|
+
const result = value.apply(target, args)
|
|
389
|
+
return result === target ? proxy : result
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// --- Generic handler for mapped methods ---
|
|
394
|
+
// The proxy handler is dynamic dispatch: args vary per intercepted method.
|
|
395
|
+
// We use targeted casts inside the switch rather than a single static signature.
|
|
396
|
+
return (...args: Parameters<typeof value>) => {
|
|
397
|
+
const convertedArgs: Parameters<typeof value> = [...args]
|
|
398
|
+
for (let i = 0; i < spec.args.length && i < convertedArgs.length; i++) {
|
|
399
|
+
const kind = spec.args[i]
|
|
400
|
+
const arg = convertedArgs[i]
|
|
401
|
+
switch (kind) {
|
|
402
|
+
case 'id':
|
|
403
|
+
if (typeof arg === 'string') {
|
|
404
|
+
convertedArgs[i] = ensureTldrawId(arg)
|
|
405
|
+
}
|
|
406
|
+
break
|
|
407
|
+
case 'id-or-shape':
|
|
408
|
+
convertedArgs[i] = convertIdOrShape(
|
|
409
|
+
arg as string | TLShapeId | TLShape | FocusedShape
|
|
410
|
+
)
|
|
411
|
+
break
|
|
412
|
+
case 'ids-or-shapes':
|
|
413
|
+
convertedArgs[i] = convertIdsOrShapes(
|
|
414
|
+
arg as Array<string | TLShapeId | TLShape | FocusedShape>
|
|
415
|
+
)
|
|
416
|
+
break
|
|
417
|
+
case 'spread-ids':
|
|
418
|
+
for (let j = i; j < convertedArgs.length; j++) {
|
|
419
|
+
convertedArgs[j] = convertIdOrShape(
|
|
420
|
+
convertedArgs[j] as string | TLShapeId | TLShape | FocusedShape
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
break
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const result = value.apply(target, convertedArgs)
|
|
428
|
+
return convertReturnValue(target, proxy, spec.ret, result)
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
}) as Editor
|
|
432
|
+
|
|
433
|
+
return proxy
|
|
434
|
+
}
|