|
|
@@ -196,6 +196,84 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
|
|
|
return this.app.createNewLineBinding(source, target)
|
|
|
}
|
|
|
|
|
|
+ /** Clone shapes with given context */
|
|
|
+ cloneShapes = ({
|
|
|
+ shapes,
|
|
|
+ assets,
|
|
|
+ bindings,
|
|
|
+ point,
|
|
|
+ }: {
|
|
|
+ shapes: TLShapeModel[]
|
|
|
+ point: number[]
|
|
|
+ // assets & bindings are the context for creating shapes
|
|
|
+ assets: TLAsset[]
|
|
|
+ bindings: Record<string, TLBinding>
|
|
|
+ }) => {
|
|
|
+ const commonBounds = BoundsUtils.getCommonBounds(
|
|
|
+ shapes.map(shape => ({
|
|
|
+ minX: shape.point?.[0] ?? point[0],
|
|
|
+ minY: shape.point?.[1] ?? point[1],
|
|
|
+ width: shape.size?.[0] ?? 4,
|
|
|
+ height: shape.size?.[1] ?? 4,
|
|
|
+ maxX: (shape.point?.[0] ?? point[0]) + (shape.size?.[0] ?? 4),
|
|
|
+ maxY: (shape.point?.[1] ?? point[1]) + (shape.size?.[1] ?? 4),
|
|
|
+ }))
|
|
|
+ )
|
|
|
+
|
|
|
+ const clonedShapes = shapes.map(shape => {
|
|
|
+ return {
|
|
|
+ ...shape,
|
|
|
+ id: uniqueId(),
|
|
|
+ point: [
|
|
|
+ point[0] + shape.point![0] - commonBounds.minX,
|
|
|
+ point[1] + shape.point![1] - commonBounds.minY,
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const clonedBindings: TLBinding[] = []
|
|
|
+
|
|
|
+ // Try to rebinding the shapes with the given bindings
|
|
|
+ clonedShapes
|
|
|
+ .flatMap(s => Object.values(s.handles ?? {}))
|
|
|
+ .forEach(handle => {
|
|
|
+ if (!handle.bindingId) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // try to bind the new shape
|
|
|
+ const binding = bindings[handle.bindingId]
|
|
|
+ if (binding) {
|
|
|
+ // if the copied binding from/to is in the source
|
|
|
+ const oldFromIdx = shapes.findIndex(s => s.id === binding.fromId)
|
|
|
+ const oldToIdx = shapes.findIndex(s => s.id === binding.toId)
|
|
|
+ if (binding && oldFromIdx !== -1 && oldToIdx !== -1) {
|
|
|
+ const newBinding: TLBinding = {
|
|
|
+ ...binding,
|
|
|
+ id: uniqueId(),
|
|
|
+ fromId: clonedShapes[oldFromIdx].id,
|
|
|
+ toId: clonedShapes[oldToIdx].id,
|
|
|
+ }
|
|
|
+ clonedBindings.push(newBinding)
|
|
|
+ handle.bindingId = newBinding.id
|
|
|
+ } else {
|
|
|
+ handle.bindingId = undefined
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.warn('binding not found', handle.bindingId)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const clonedAssets = assets.filter(asset => {
|
|
|
+ // do we need to create new asset id?
|
|
|
+ return clonedShapes.some(shape => shape.assetId === asset.id)
|
|
|
+ })
|
|
|
+ return {
|
|
|
+ shapes: clonedShapes,
|
|
|
+ assets: clonedAssets,
|
|
|
+ bindings: clonedBindings,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
getClonedShapesFromTldrString = (text: string, point: number[]) => {
|
|
|
const safeParseJson = (json: string) => {
|
|
|
try {
|
|
|
@@ -213,91 +291,34 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- let assetsToClone: TLAsset[] = []
|
|
|
- const bindingsToCreate: TLBinding[] = []
|
|
|
const data = getWhiteboardsTldrFromText(text)
|
|
|
-
|
|
|
- const shapes = data.shapes as TLShapeModel[]
|
|
|
- assetsToClone = data.assets as TLAsset[]
|
|
|
- const commonBounds = BoundsUtils.getCommonBounds(
|
|
|
- shapes.map(shape => ({
|
|
|
- minX: shape.point?.[0] ?? point[0],
|
|
|
- minY: shape.point?.[1] ?? point[1],
|
|
|
- width: shape.size?.[0] ?? 4,
|
|
|
- height: shape.size?.[1] ?? 4,
|
|
|
- maxX: (shape.point?.[0] ?? point[0]) + (shape.size?.[0] ?? 4),
|
|
|
- maxY: (shape.point?.[1] ?? point[1]) + (shape.size?.[1] ?? 4),
|
|
|
- }))
|
|
|
- )
|
|
|
-
|
|
|
- const bindings = data.bindings as Record<string, TLBinding>
|
|
|
- const shapesToCreate = shapes.map(shape => {
|
|
|
- return {
|
|
|
- ...shape,
|
|
|
- id: uniqueId(),
|
|
|
- point: [
|
|
|
- point[0] + shape.point![0] - commonBounds.minX,
|
|
|
- point[1] + shape.point![1] - commonBounds.minY,
|
|
|
- ],
|
|
|
- }
|
|
|
+ if (!data) return null
|
|
|
+ const { shapes, bindings, assets } = data
|
|
|
+
|
|
|
+ return this.cloneShapes({
|
|
|
+ shapes,
|
|
|
+ bindings,
|
|
|
+ assets,
|
|
|
+ point,
|
|
|
})
|
|
|
-
|
|
|
- // Try to rebinding the shapes to the new assets
|
|
|
- shapesToCreate
|
|
|
- .flatMap(s => Object.values(s.handles ?? {}))
|
|
|
- .forEach(h => {
|
|
|
- if (!h.bindingId) {
|
|
|
- return
|
|
|
- }
|
|
|
- // try to bind the new shape
|
|
|
- const binding = bindings[h.bindingId]
|
|
|
- if (binding) {
|
|
|
- // if the copied binding from/to is in the source
|
|
|
- const oldFromIdx = shapes.findIndex(s => s.id === binding.fromId)
|
|
|
- const oldToIdx = shapes.findIndex(s => s.id === binding.toId)
|
|
|
- if (binding && oldFromIdx !== -1 && oldToIdx !== -1) {
|
|
|
- const newBinding: TLBinding = {
|
|
|
- ...binding,
|
|
|
- id: uniqueId(),
|
|
|
- fromId: shapesToCreate[oldFromIdx].id,
|
|
|
- toId: shapesToCreate[oldToIdx].id,
|
|
|
- }
|
|
|
- bindingsToCreate.push(newBinding)
|
|
|
- h.bindingId = newBinding.id
|
|
|
- } else {
|
|
|
- h.bindingId = undefined
|
|
|
- }
|
|
|
- } else {
|
|
|
- console.warn('binding not found', h.bindingId)
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- return {
|
|
|
- shapes: shapesToCreate as S['props'][],
|
|
|
- assets: assetsToClone,
|
|
|
- bindings: bindingsToCreate,
|
|
|
- }
|
|
|
} catch (err) {
|
|
|
console.log(err)
|
|
|
}
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
- addClonedShapes = (shapes: S[] | TLShapeModel[], assets: TLAsset[], bindings: TLBinding[]) => {
|
|
|
- if (assets.length > 0) {
|
|
|
- this.app.createAssets(assets)
|
|
|
- }
|
|
|
- if (shapes.length > 0) {
|
|
|
- this.app.createShapes(shapes)
|
|
|
- }
|
|
|
- this.app.currentPage.updateBindings(Object.fromEntries(bindings.map(b => [b.id, b])))
|
|
|
- this.app.selectedTool.transition('idle') // clears possible editing states
|
|
|
- }
|
|
|
-
|
|
|
addClonedShapesFromTldrString = (text: string, point: number[]) => {
|
|
|
const data = this.getClonedShapesFromTldrString(text, point)
|
|
|
if (data) {
|
|
|
- this.addClonedShapes(data.shapes, data.assets, data.bindings)
|
|
|
+ const { shapes, bindings, assets } = data
|
|
|
+ if (assets.length > 0) {
|
|
|
+ this.app.createAssets(assets)
|
|
|
+ }
|
|
|
+ if (shapes.length > 0) {
|
|
|
+ this.app.createShapes(shapes)
|
|
|
+ }
|
|
|
+ this.app.currentPage.updateBindings(Object.fromEntries(bindings.map(b => [b.id, b])))
|
|
|
+ this.app.selectedTool.transition('idle') // clears possible editing states
|
|
|
}
|
|
|
}
|
|
|
}
|