[improvement] Add getContent
and insertContent
methods (#726)
* Add insertContent method, update copyJson * Add more tests * Update TldrawApp.spec.ts * Adds option object for select, point, and uses in paste
This commit is contained in:
parent
0cfa44f4d2
commit
6d91916804
7 changed files with 1384 additions and 224 deletions
|
@ -4,6 +4,9 @@ import { ArrowShape, ColorStyle, SessionType, TDShapeType } from '~types'
|
||||||
import { deepCopy } from './StateManager/copy'
|
import { deepCopy } from './StateManager/copy'
|
||||||
import type { SelectTool } from './tools/SelectTool'
|
import type { SelectTool } from './tools/SelectTool'
|
||||||
|
|
||||||
|
window.focus = jest.fn()
|
||||||
|
global.console.warn = jest.fn()
|
||||||
|
|
||||||
describe('TldrawTestApp', () => {
|
describe('TldrawTestApp', () => {
|
||||||
describe('When copying and pasting...', () => {
|
describe('When copying and pasting...', () => {
|
||||||
it('copies a shape', () => {
|
it('copies a shape', () => {
|
||||||
|
|
|
@ -81,6 +81,7 @@ import { StickyTool } from './tools/StickyTool'
|
||||||
import { StateManager } from './StateManager'
|
import { StateManager } from './StateManager'
|
||||||
import { clearPrevSize } from './shapes/shared/getTextSize'
|
import { clearPrevSize } from './shapes/shared/getTextSize'
|
||||||
import { getClipboard, setClipboard } from './IdbClipboard'
|
import { getClipboard, setClipboard } from './IdbClipboard'
|
||||||
|
import { deepCopy } from './StateManager/copy'
|
||||||
|
|
||||||
const uuid = Utils.uniqueId()
|
const uuid = Utils.uniqueId()
|
||||||
|
|
||||||
|
@ -1713,43 +1714,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
/* Clipboard */
|
/* Clipboard */
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
private getClipboard(ids = this.selectedIds):
|
|
||||||
| {
|
|
||||||
shapes: TDShape[]
|
|
||||||
bindings: TDBinding[]
|
|
||||||
assets: TDAsset[]
|
|
||||||
}
|
|
||||||
| undefined {
|
|
||||||
const copyingShapeIds = ids.flatMap((id) =>
|
|
||||||
TLDR.getDocumentBranch(this.state, id, this.currentPageId)
|
|
||||||
)
|
|
||||||
|
|
||||||
const copyingShapes = copyingShapeIds.map((id) =>
|
|
||||||
Utils.deepClone(this.getShape(id, this.currentPageId))
|
|
||||||
)
|
|
||||||
|
|
||||||
if (copyingShapes.length === 0) return
|
|
||||||
|
|
||||||
const copyingBindings: TDBinding[] = Object.values(this.page.bindings).filter(
|
|
||||||
(binding) =>
|
|
||||||
copyingShapeIds.includes(binding.fromId) && copyingShapeIds.includes(binding.toId)
|
|
||||||
)
|
|
||||||
|
|
||||||
const copyingAssets = copyingShapes
|
|
||||||
.map((shape) => {
|
|
||||||
if (!shape.assetId) return
|
|
||||||
|
|
||||||
return this.document.assets[shape.assetId]
|
|
||||||
})
|
|
||||||
.filter(Boolean) as TDAsset[]
|
|
||||||
|
|
||||||
return {
|
|
||||||
shapes: copyingShapes,
|
|
||||||
bindings: copyingBindings,
|
|
||||||
assets: copyingAssets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cut (copy and delete) one or more shapes to the clipboard.
|
* Cut (copy and delete) one or more shapes to the clipboard.
|
||||||
* @param ids The ids of the shapes to cut.
|
* @param ids The ids of the shapes to cut.
|
||||||
|
@ -1775,7 +1739,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
this.clipboard = this.getClipboard(ids)
|
this.clipboard = this.getContent(ids)
|
||||||
|
|
||||||
const jsonString = JSON.stringify({
|
const jsonString = JSON.stringify({
|
||||||
type: 'tldr/clipboard',
|
type: 'tldr/clipboard',
|
||||||
|
@ -1811,97 +1775,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
paste = async (point?: number[], e?: ClipboardEvent) => {
|
paste = async (point?: number[], e?: ClipboardEvent) => {
|
||||||
if (this.readOnly) return
|
if (this.readOnly) return
|
||||||
|
|
||||||
const pasteInCurrentPage = (shapes: TDShape[], bindings: TDBinding[], assets: TDAsset[]) => {
|
|
||||||
const idsMap: Record<string, string> = {}
|
|
||||||
|
|
||||||
const newAssets = assets.filter((asset) => this.document.assets[asset.id] === undefined)
|
|
||||||
|
|
||||||
if (newAssets.length) {
|
|
||||||
this.patchState({
|
|
||||||
document: {
|
|
||||||
assets: Object.fromEntries(newAssets.map((asset) => [asset.id, asset])),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
shapes.forEach((shape) => (idsMap[shape.id] = Utils.uniqueId()))
|
|
||||||
|
|
||||||
bindings.forEach((binding) => (idsMap[binding.id] = Utils.uniqueId()))
|
|
||||||
|
|
||||||
let startIndex = TLDR.getTopChildIndex(this.state, this.currentPageId)
|
|
||||||
|
|
||||||
const shapesToPaste = shapes
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => {
|
|
||||||
const parentShapeId = idsMap[shape.parentId]
|
|
||||||
|
|
||||||
const copy = {
|
|
||||||
...shape,
|
|
||||||
id: idsMap[shape.id],
|
|
||||||
parentId: parentShapeId || this.currentPageId,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shape.children) {
|
|
||||||
copy.children = shape.children.map((id) => idsMap[id])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parentShapeId) {
|
|
||||||
copy.childIndex = startIndex
|
|
||||||
startIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy.handles) {
|
|
||||||
Object.values(copy.handles).forEach((handle) => {
|
|
||||||
if (handle.bindingId) {
|
|
||||||
handle.bindingId = idsMap[handle.bindingId]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
|
|
||||||
const bindingsToPaste = bindings.map((binding) => ({
|
|
||||||
...binding,
|
|
||||||
id: idsMap[binding.id],
|
|
||||||
toId: idsMap[binding.toId],
|
|
||||||
fromId: idsMap[binding.fromId],
|
|
||||||
}))
|
|
||||||
|
|
||||||
const commonBounds = Utils.getCommonBounds(shapesToPaste.map(TLDR.getBounds))
|
|
||||||
|
|
||||||
let center = Vec.toFixed(this.getPagePoint(point || this.centerPoint))
|
|
||||||
|
|
||||||
if (
|
|
||||||
Vec.dist(center, this.pasteInfo.center) < 2 ||
|
|
||||||
Vec.dist(center, Vec.toFixed(Utils.getBoundsCenter(commonBounds))) < 2
|
|
||||||
) {
|
|
||||||
center = Vec.add(center, this.pasteInfo.offset)
|
|
||||||
this.pasteInfo.offset = Vec.add(this.pasteInfo.offset, [GRID_SIZE, GRID_SIZE])
|
|
||||||
} else {
|
|
||||||
this.pasteInfo.center = center
|
|
||||||
this.pasteInfo.offset = [0, 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const centeredBounds = Utils.centerBounds(commonBounds, center)
|
|
||||||
|
|
||||||
const delta = Vec.sub(
|
|
||||||
Utils.getBoundsCenter(centeredBounds),
|
|
||||||
Utils.getBoundsCenter(commonBounds)
|
|
||||||
)
|
|
||||||
|
|
||||||
this.create(
|
|
||||||
shapesToPaste.map((shape) =>
|
|
||||||
TLDR.getShapeUtil(shape.type).create({
|
|
||||||
...shape,
|
|
||||||
point: Vec.toFixed(Vec.add(shape.point, delta)),
|
|
||||||
parentId: shape.parentId || this.currentPageId,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
bindingsToPaste
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pasteTextAsSvg = async (text: string) => {
|
const pasteTextAsSvg = async (text: string) => {
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
div.innerHTML = text
|
div.innerHTML = text
|
||||||
|
@ -1951,7 +1824,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
} = JSON.parse(maybeJson)
|
} = JSON.parse(maybeJson)
|
||||||
|
|
||||||
if (json.type === 'tldr/clipboard') {
|
if (json.type === 'tldr/clipboard') {
|
||||||
pasteInCurrentPage(json.shapes, json.bindings, json.assets)
|
this.insertContent(json, { point, select: true })
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
throw Error('Not tldraw data!')
|
throw Error('Not tldraw data!')
|
||||||
|
@ -2058,7 +1931,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
} else {
|
} else {
|
||||||
TLDR.warn('This browser does not support the Clipboard API!')
|
TLDR.warn('This browser does not support the Clipboard API!')
|
||||||
if (this.clipboard) {
|
if (this.clipboard) {
|
||||||
pasteInCurrentPage(this.clipboard.shapes, this.clipboard.bindings, this.clipboard.assets)
|
this.insertContent(this.clipboard, { point, select: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2076,7 +1949,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')
|
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')
|
||||||
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
||||||
|
|
||||||
window.focus() // weird but necessary
|
if (typeof window !== 'undefined') {
|
||||||
|
window.focus() // weird but necessary
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.includeFonts) {
|
if (opts.includeFonts) {
|
||||||
try {
|
try {
|
||||||
|
@ -2238,7 +2113,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
const svgString = TLDR.getSvgString(svg, 1)
|
const svgString = TLDR.getSvgString(svg, 1)
|
||||||
|
|
||||||
this.clipboard = this.getClipboard(ids)
|
this.clipboard = this.getContent(ids)
|
||||||
|
|
||||||
const tldrawString = JSON.stringify({
|
const tldrawString = JSON.stringify({
|
||||||
type: 'tldr/clipboard',
|
type: 'tldr/clipboard',
|
||||||
|
@ -2257,23 +2132,104 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
return svgString
|
return svgString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shapes and bindings for the current selection, if any, or else the current page.
|
||||||
|
*
|
||||||
|
* @param ids The ids of the shapes to get content for.
|
||||||
|
*/
|
||||||
|
getContent = (ids?: string[]) => {
|
||||||
|
const page = this.getPage(this.currentPageId)
|
||||||
|
|
||||||
|
// If ids is explicitly empty ([]) return
|
||||||
|
if (ids && ids.length === 0) return
|
||||||
|
|
||||||
|
// If ids was not provided, use the selected ids
|
||||||
|
if (!ids) ids = this.selectedIds
|
||||||
|
|
||||||
|
// If there are no selected ids, use all the page's shape ids
|
||||||
|
if (ids.length === 0) ids = Object.keys(page.shapes)
|
||||||
|
|
||||||
|
// If the page was empty, return
|
||||||
|
if (ids.length === 0) return
|
||||||
|
|
||||||
|
const shapes = ids
|
||||||
|
.map((id) => page.shapes[id])
|
||||||
|
.flatMap((shape) => [shape, ...(shape.children ?? []).map((childId) => page.shapes[childId])])
|
||||||
|
.map(deepCopy)
|
||||||
|
|
||||||
|
const idsSet = new Set(shapes.map((s) => s.id))
|
||||||
|
|
||||||
|
shapes.forEach((shape) => {
|
||||||
|
if (shape.parentId === this.currentPageId) {
|
||||||
|
shape.parentId = 'currentPageId'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// If a binding's from and to are included, then include the binding;
|
||||||
|
// but if only one shape is included, discard the binding
|
||||||
|
const bindings = Object.values(page.bindings)
|
||||||
|
.filter((binding) => {
|
||||||
|
if (idsSet.has(binding.fromId) && idsSet.has(binding.toId)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idsSet.has(binding.fromId)) {
|
||||||
|
const shape = shapes.find((s) => s.id === binding.fromId)
|
||||||
|
const handles = shape!.handles
|
||||||
|
if (handles) {
|
||||||
|
Object.values(handles).forEach((handle) => {
|
||||||
|
if (handle!.bindingId === binding.id) {
|
||||||
|
handle!.bindingId = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idsSet.has(binding.toId)) {
|
||||||
|
const shape = shapes.find((s) => s.id === binding.toId)
|
||||||
|
const handles = shape!.handles
|
||||||
|
if (handles) {
|
||||||
|
Object.values(handles).forEach((handle) => {
|
||||||
|
if (handle!.bindingId === binding.id) {
|
||||||
|
handle!.bindingId = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
.map(deepCopy)
|
||||||
|
|
||||||
|
const assets = [
|
||||||
|
...new Set(
|
||||||
|
shapes
|
||||||
|
.map((shape) => {
|
||||||
|
if (!shape.assetId) return
|
||||||
|
return this.document.assets[shape.assetId]
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(deepCopy)
|
||||||
|
),
|
||||||
|
] as TDAsset[]
|
||||||
|
|
||||||
|
return { shapes, bindings, assets }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy one or more shapes as JSON.
|
* Copy one or more shapes as JSON.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
* @param pageId The page from which to copy the shapes.
|
* @param pageId The page from which to copy the shapes.
|
||||||
* @returns A string containing the JSON.
|
* @returns A string containing the JSON.
|
||||||
*/
|
*/
|
||||||
copyJson = (ids = this.selectedIds, pageId = this.currentPageId) => {
|
copyJson = (ids = this.selectedIds) => {
|
||||||
if (ids.length === 0) ids = Object.keys(this.page.shapes)
|
const content = this.getContent(ids)
|
||||||
|
|
||||||
if (ids.length === 0) return
|
if (content) {
|
||||||
|
TLDR.copyStringToClipboard(JSON.stringify(content))
|
||||||
|
}
|
||||||
|
|
||||||
const shapes = ids.map((id) => this.getShape(id, pageId))
|
return this
|
||||||
const json = JSON.stringify(shapes, null, 2)
|
|
||||||
|
|
||||||
TLDR.copyStringToClipboard(json)
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2281,20 +2237,37 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
* @param ids The ids of the shapes to copy from the current page.
|
* @param ids The ids of the shapes to copy from the current page.
|
||||||
* @returns A string containing the JSON.
|
* @returns A string containing the JSON.
|
||||||
*/
|
*/
|
||||||
exportJson = (
|
exportJson = (ids = this.selectedIds) => {
|
||||||
ids = this.selectedIds.length ? this.selectedIds : Object.keys(this.page.shapes)
|
const content = this.getContent(ids)
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
const blob = new Blob([JSON.stringify(content)], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `export.json`
|
||||||
|
link.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert content.
|
||||||
|
*
|
||||||
|
* @param content The content to insert.
|
||||||
|
* @param content.shapes An array of TDShape objects.
|
||||||
|
* @param content.bindings (optional) An array of TDBinding objects.
|
||||||
|
* @param content.assets (optional) An array of TDAsset objects.
|
||||||
|
* @param opts (optional) An options object
|
||||||
|
* @param opts.point (optional) A point at which to paste the content.
|
||||||
|
* @param opts.select (optional) When true, the inserted shapes will be selected.
|
||||||
|
*/
|
||||||
|
insertContent = (
|
||||||
|
content: { shapes: TDShape[]; bindings?: TDBinding[]; assets?: TDAsset[] },
|
||||||
|
opts = {} as { point?: number[]; select?: boolean }
|
||||||
) => {
|
) => {
|
||||||
if (ids.length === 0) return
|
return this.setState(Commands.insertContent(this, content, opts), 'insert_content')
|
||||||
|
|
||||||
const shapes = ids.map((id) => this.getShape(id, this.currentPageId))
|
|
||||||
const json = JSON.stringify(shapes, null, 2)
|
|
||||||
const blob = new Blob([json], { type: 'application/json' })
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = url
|
|
||||||
link.download = `export.json`
|
|
||||||
link.click()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,83 +1,827 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[` 1`] = `
|
exports[` 1`] = `
|
||||||
"[
|
TldrawTestApp {
|
||||||
{
|
"_idbId": undefined,
|
||||||
\\"id\\": \\"rect1\\",
|
"_snapshot": Object {
|
||||||
\\"parentId\\": \\"page1\\",
|
"appState": Object {
|
||||||
\\"name\\": \\"Rectangle\\",
|
"activeTool": "select",
|
||||||
\\"childIndex\\": 1,
|
"currentPageId": "page",
|
||||||
\\"type\\": \\"rectangle\\",
|
"currentStyle": Object {
|
||||||
\\"point\\": [
|
"color": "black",
|
||||||
|
"dash": "draw",
|
||||||
|
"isFilled": false,
|
||||||
|
"scale": 1,
|
||||||
|
"size": "small",
|
||||||
|
},
|
||||||
|
"disableAssets": false,
|
||||||
|
"eraseLine": Array [],
|
||||||
|
"hoveredId": undefined,
|
||||||
|
"isEmptyCanvas": false,
|
||||||
|
"isLoading": false,
|
||||||
|
"isMenuOpen": false,
|
||||||
|
"isToolLocked": false,
|
||||||
|
"snapLines": Array [],
|
||||||
|
"status": "idle",
|
||||||
|
},
|
||||||
|
"document": Object {
|
||||||
|
"assets": Object {},
|
||||||
|
"id": "doc",
|
||||||
|
"name": "New Document",
|
||||||
|
"pageStates": Object {
|
||||||
|
"page": Object {
|
||||||
|
"camera": Object {
|
||||||
|
"point": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"zoom": 1,
|
||||||
|
},
|
||||||
|
"id": "page",
|
||||||
|
"selectedIds": Array [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pages": Object {
|
||||||
|
"page": Object {
|
||||||
|
"bindings": Object {},
|
||||||
|
"childIndex": 1,
|
||||||
|
"id": "page",
|
||||||
|
"name": "Page 1",
|
||||||
|
"shapes": Object {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 15.3,
|
||||||
|
},
|
||||||
|
"settings": Object {
|
||||||
|
"isCadSelectMode": false,
|
||||||
|
"isDarkMode": false,
|
||||||
|
"isDebugMode": false,
|
||||||
|
"isFocusMode": false,
|
||||||
|
"isPenMode": false,
|
||||||
|
"isReadonlyMode": false,
|
||||||
|
"isSnapping": false,
|
||||||
|
"isZoomSnap": false,
|
||||||
|
"keepStyleMenuOpen": false,
|
||||||
|
"language": "en",
|
||||||
|
"nudgeDistanceLarge": 16,
|
||||||
|
"nudgeDistanceSmall": 1,
|
||||||
|
"showBindingHandles": true,
|
||||||
|
"showCloneHandles": false,
|
||||||
|
"showGrid": false,
|
||||||
|
"showRotateHandles": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"_state": Object {
|
||||||
|
"appState": Object {
|
||||||
|
"activeTool": "select",
|
||||||
|
"currentPageId": "page1",
|
||||||
|
"currentStyle": Object {
|
||||||
|
"color": "black",
|
||||||
|
"dash": "draw",
|
||||||
|
"isFilled": false,
|
||||||
|
"scale": 1,
|
||||||
|
"size": "small",
|
||||||
|
},
|
||||||
|
"disableAssets": false,
|
||||||
|
"eraseLine": Array [],
|
||||||
|
"hoveredId": undefined,
|
||||||
|
"isEmptyCanvas": false,
|
||||||
|
"isLoading": false,
|
||||||
|
"isMenuOpen": false,
|
||||||
|
"isToolLocked": false,
|
||||||
|
"snapLines": Array [],
|
||||||
|
"status": "idle",
|
||||||
|
},
|
||||||
|
"document": Object {
|
||||||
|
"assets": Object {},
|
||||||
|
"id": "doc",
|
||||||
|
"name": "New Document",
|
||||||
|
"pageStates": Object {
|
||||||
|
"page1": Object {
|
||||||
|
"bindingId": undefined,
|
||||||
|
"camera": Object {
|
||||||
|
"point": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"zoom": 1,
|
||||||
|
},
|
||||||
|
"editingId": undefined,
|
||||||
|
"hoveredId": undefined,
|
||||||
|
"id": "page1",
|
||||||
|
"pointedId": undefined,
|
||||||
|
"selectedIds": Array [
|
||||||
|
"rect1",
|
||||||
|
"rect2",
|
||||||
|
"rect3",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pages": Object {
|
||||||
|
"page1": Object {
|
||||||
|
"bindings": Object {},
|
||||||
|
"id": "page1",
|
||||||
|
"shapes": Object {
|
||||||
|
"rect1": Object {
|
||||||
|
"childIndex": 1,
|
||||||
|
"id": "rect1",
|
||||||
|
"label": "",
|
||||||
|
"labelPoint": Array [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
],
|
||||||
|
"name": "Rectangle",
|
||||||
|
"parentId": "page1",
|
||||||
|
"point": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"size": Array [
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
],
|
||||||
|
"style": Object {
|
||||||
|
"color": "blue",
|
||||||
|
"dash": "draw",
|
||||||
|
"size": "medium",
|
||||||
|
},
|
||||||
|
"type": "rectangle",
|
||||||
|
},
|
||||||
|
"rect2": Object {
|
||||||
|
"childIndex": 2,
|
||||||
|
"id": "rect2",
|
||||||
|
"label": "",
|
||||||
|
"labelPoint": Array [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
],
|
||||||
|
"name": "Rectangle",
|
||||||
|
"parentId": "page1",
|
||||||
|
"point": Array [
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
],
|
||||||
|
"size": Array [
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
],
|
||||||
|
"style": Object {
|
||||||
|
"color": "blue",
|
||||||
|
"dash": "draw",
|
||||||
|
"size": "medium",
|
||||||
|
},
|
||||||
|
"type": "rectangle",
|
||||||
|
},
|
||||||
|
"rect3": Object {
|
||||||
|
"childIndex": 3,
|
||||||
|
"id": "rect3",
|
||||||
|
"label": "",
|
||||||
|
"labelPoint": Array [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
],
|
||||||
|
"name": "Rectangle",
|
||||||
|
"parentId": "page1",
|
||||||
|
"point": Array [
|
||||||
|
20,
|
||||||
|
20,
|
||||||
|
],
|
||||||
|
"size": Array [
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
],
|
||||||
|
"style": Object {
|
||||||
|
"color": "blue",
|
||||||
|
"dash": "draw",
|
||||||
|
"size": "medium",
|
||||||
|
},
|
||||||
|
"type": "rectangle",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 15.3,
|
||||||
|
},
|
||||||
|
"settings": Object {
|
||||||
|
"isCadSelectMode": false,
|
||||||
|
"isDarkMode": false,
|
||||||
|
"isDebugMode": false,
|
||||||
|
"isFocusMode": false,
|
||||||
|
"isPenMode": false,
|
||||||
|
"isReadonlyMode": false,
|
||||||
|
"isSnapping": false,
|
||||||
|
"isZoomSnap": false,
|
||||||
|
"keepStyleMenuOpen": false,
|
||||||
|
"language": "en",
|
||||||
|
"nudgeDistanceLarge": 16,
|
||||||
|
"nudgeDistanceSmall": 1,
|
||||||
|
"showBindingHandles": true,
|
||||||
|
"showCloneHandles": false,
|
||||||
|
"showGrid": false,
|
||||||
|
"showRotateHandles": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"_status": "ready",
|
||||||
|
"addMediaFromFile": [Function],
|
||||||
|
"addToSelectHistory": [Function],
|
||||||
|
"align": [Function],
|
||||||
|
"altKey": false,
|
||||||
|
"applyPatch": [Function],
|
||||||
|
"broadcastPageChanges": [Function],
|
||||||
|
"callbacks": Object {},
|
||||||
|
"cancel": [Function],
|
||||||
|
"cancelSession": [Function],
|
||||||
|
"changePage": [Function],
|
||||||
|
"cleanup": [Function],
|
||||||
|
"clearSelectHistory": [Function],
|
||||||
|
"clickBounds": [Function],
|
||||||
|
"clickBoundsHandle": [Function],
|
||||||
|
"clickCanvas": [Function],
|
||||||
|
"clickShape": [Function],
|
||||||
|
"completeSession": [Function],
|
||||||
|
"copy": [Function],
|
||||||
|
"copyImage": [Function],
|
||||||
|
"copyJson": [Function],
|
||||||
|
"copySvg": [Function],
|
||||||
|
"create": [Function],
|
||||||
|
"createPage": [Function],
|
||||||
|
"createShapes": [Function],
|
||||||
|
"ctrlKey": false,
|
||||||
|
"currentPoint": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"currentTool": SelectTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"clonePaint": [Function],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"getShapeClone": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onDoubleClickBoundsHandle": [Function],
|
||||||
|
"onDoubleClickCanvas": [Function],
|
||||||
|
"onDoubleClickHandle": [Function],
|
||||||
|
"onDoubleClickShape": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onHoverShape": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointBounds": [Function],
|
||||||
|
"onPointBoundsHandle": [Function],
|
||||||
|
"onPointHandle": [Function],
|
||||||
|
"onPointShape": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"onReleaseBounds": [Function],
|
||||||
|
"onReleaseBoundsHandle": [Function],
|
||||||
|
"onReleaseHandle": [Function],
|
||||||
|
"onRightPointBounds": [Function],
|
||||||
|
"onRightPointShape": [Function],
|
||||||
|
"onShapeClone": [Function],
|
||||||
|
"onUnhoverShape": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "select",
|
||||||
|
},
|
||||||
|
"cut": [Function],
|
||||||
|
"delete": [Function],
|
||||||
|
"deleteAll": [Function],
|
||||||
|
"deletePage": [Function],
|
||||||
|
"distribute": [Function],
|
||||||
|
"doubleClickBoundHandle": [Function],
|
||||||
|
"doubleClickShape": [Function],
|
||||||
|
"duplicate": [Function],
|
||||||
|
"duplicatePage": [Function],
|
||||||
|
"editingStartTime": -1,
|
||||||
|
"expectSelectedIdsToBe": [Function],
|
||||||
|
"expectShapesToBeAtPoints": [Function],
|
||||||
|
"expectShapesToHaveProps": [Function],
|
||||||
|
"exportImage": [Function],
|
||||||
|
"exportJson": [Function],
|
||||||
|
"fileSystemHandle": null,
|
||||||
|
"flipHorizontal": [Function],
|
||||||
|
"flipVertical": [Function],
|
||||||
|
"forceUpdate": [Function],
|
||||||
|
"getAppState": [Function],
|
||||||
|
"getBinding": [Function],
|
||||||
|
"getBindings": [Function],
|
||||||
|
"getContent": [Function],
|
||||||
|
"getImage": [Function],
|
||||||
|
"getPage": [Function],
|
||||||
|
"getPagePoint": [Function],
|
||||||
|
"getPageState": [Function],
|
||||||
|
"getReservedContent": [Function],
|
||||||
|
"getShape": [Function],
|
||||||
|
"getShapeBounds": [Function],
|
||||||
|
"getShapeUtil": [Function],
|
||||||
|
"getShapes": [Function],
|
||||||
|
"getSvg": [Function],
|
||||||
|
"getViewboxFromSVG": [Function],
|
||||||
|
"group": [Function],
|
||||||
|
"hoverShape": [Function],
|
||||||
|
"initialState": Object {
|
||||||
|
"appState": Object {
|
||||||
|
"activeTool": "select",
|
||||||
|
"currentPageId": "page",
|
||||||
|
"currentStyle": Object {
|
||||||
|
"color": "black",
|
||||||
|
"dash": "draw",
|
||||||
|
"isFilled": false,
|
||||||
|
"scale": 1,
|
||||||
|
"size": "small",
|
||||||
|
},
|
||||||
|
"disableAssets": false,
|
||||||
|
"eraseLine": Array [],
|
||||||
|
"hoveredId": undefined,
|
||||||
|
"isEmptyCanvas": false,
|
||||||
|
"isLoading": false,
|
||||||
|
"isMenuOpen": false,
|
||||||
|
"isToolLocked": false,
|
||||||
|
"snapLines": Array [],
|
||||||
|
"status": "idle",
|
||||||
|
},
|
||||||
|
"document": Object {
|
||||||
|
"assets": Object {},
|
||||||
|
"id": "doc",
|
||||||
|
"name": "New Document",
|
||||||
|
"pageStates": Object {
|
||||||
|
"page": Object {
|
||||||
|
"camera": Object {
|
||||||
|
"point": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"zoom": 1,
|
||||||
|
},
|
||||||
|
"id": "page",
|
||||||
|
"selectedIds": Array [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pages": Object {
|
||||||
|
"page": Object {
|
||||||
|
"bindings": Object {},
|
||||||
|
"childIndex": 1,
|
||||||
|
"id": "page",
|
||||||
|
"name": "Page 1",
|
||||||
|
"shapes": Object {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 15.3,
|
||||||
|
},
|
||||||
|
"settings": Object {
|
||||||
|
"isCadSelectMode": false,
|
||||||
|
"isDarkMode": false,
|
||||||
|
"isDebugMode": false,
|
||||||
|
"isFocusMode": false,
|
||||||
|
"isPenMode": false,
|
||||||
|
"isReadonlyMode": false,
|
||||||
|
"isSnapping": false,
|
||||||
|
"isZoomSnap": false,
|
||||||
|
"keepStyleMenuOpen": false,
|
||||||
|
"language": "en",
|
||||||
|
"nudgeDistanceLarge": 16,
|
||||||
|
"nudgeDistanceSmall": 1,
|
||||||
|
"showBindingHandles": true,
|
||||||
|
"showCloneHandles": false,
|
||||||
|
"showGrid": false,
|
||||||
|
"showRotateHandles": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"insertContent": [Function],
|
||||||
|
"isCreating": false,
|
||||||
|
"isDirty": false,
|
||||||
|
"isForcePanning": false,
|
||||||
|
"isPaused": false,
|
||||||
|
"isPointing": false,
|
||||||
|
"justSent": false,
|
||||||
|
"loadDocument": [Function],
|
||||||
|
"loadRoom": [Function],
|
||||||
|
"mergeDocument": [Function],
|
||||||
|
"metaKey": false,
|
||||||
|
"migrate": [Function],
|
||||||
|
"moveBackward": [Function],
|
||||||
|
"moveForward": [Function],
|
||||||
|
"movePointer": [Function],
|
||||||
|
"moveToBack": [Function],
|
||||||
|
"moveToFront": [Function],
|
||||||
|
"moveToPage": [Function],
|
||||||
|
"newProject": [Function],
|
||||||
|
"nudge": [Function],
|
||||||
|
"onCommand": [Function],
|
||||||
|
"onDoubleClickBounds": [Function],
|
||||||
|
"onDoubleClickBoundsHandle": [Function],
|
||||||
|
"onDoubleClickCanvas": [Function],
|
||||||
|
"onDoubleClickHandle": [Function],
|
||||||
|
"onDoubleClickShape": [Function],
|
||||||
|
"onDragBounds": [Function],
|
||||||
|
"onDragBoundsHandle": [Function],
|
||||||
|
"onDragCanvas": [Function],
|
||||||
|
"onDragHandle": [Function],
|
||||||
|
"onDragOver": [Function],
|
||||||
|
"onDragShape": [Function],
|
||||||
|
"onDrop": [Function],
|
||||||
|
"onError": [Function],
|
||||||
|
"onHoverBounds": [Function],
|
||||||
|
"onHoverBoundsHandle": [Function],
|
||||||
|
"onHoverHandle": [Function],
|
||||||
|
"onHoverShape": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPan": [Function],
|
||||||
|
"onPatch": [Function],
|
||||||
|
"onPersist": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointBounds": [Function],
|
||||||
|
"onPointBoundsHandle": [Function],
|
||||||
|
"onPointCanvas": [Function],
|
||||||
|
"onPointHandle": [Function],
|
||||||
|
"onPointShape": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"onReady": [Function],
|
||||||
|
"onRedo": [Function],
|
||||||
|
"onReleaseBounds": [Function],
|
||||||
|
"onReleaseBoundsHandle": [Function],
|
||||||
|
"onReleaseCanvas": [Function],
|
||||||
|
"onReleaseHandle": [Function],
|
||||||
|
"onReleaseShape": [Function],
|
||||||
|
"onRenderCountChange": [Function],
|
||||||
|
"onReplace": [Function],
|
||||||
|
"onRightPointBounds": [Function],
|
||||||
|
"onRightPointBoundsHandle": [Function],
|
||||||
|
"onRightPointCanvas": [Function],
|
||||||
|
"onRightPointHandle": [Function],
|
||||||
|
"onRightPointShape": [Function],
|
||||||
|
"onShapeBlur": [Function],
|
||||||
|
"onShapeChange": [Function],
|
||||||
|
"onShapeClone": [Function],
|
||||||
|
"onStateDidChange": [Function],
|
||||||
|
"onUndo": [Function],
|
||||||
|
"onUnhoverBounds": [Function],
|
||||||
|
"onUnhoverBoundsHandle": [Function],
|
||||||
|
"onUnhoverHandle": [Function],
|
||||||
|
"onUnhoverShape": [Function],
|
||||||
|
"onZoom": [Function],
|
||||||
|
"openAsset": [Function],
|
||||||
|
"openProject": [Function],
|
||||||
|
"originPoint": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"pan": [Function],
|
||||||
|
"paste": [Function],
|
||||||
|
"pasteInfo": Object {
|
||||||
|
"center": Array [
|
||||||
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
|
||||||
],
|
],
|
||||||
\\"size\\": [
|
"offset": Array [
|
||||||
100,
|
0,
|
||||||
100
|
0,
|
||||||
],
|
],
|
||||||
\\"style\\": {
|
|
||||||
\\"dash\\": \\"draw\\",
|
|
||||||
\\"size\\": \\"medium\\",
|
|
||||||
\\"color\\": \\"blue\\"
|
|
||||||
},
|
|
||||||
\\"label\\": \\"\\",
|
|
||||||
\\"labelPoint\\": [
|
|
||||||
0.5,
|
|
||||||
0.5
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
"patchCreate": [Function],
|
||||||
\\"id\\": \\"rect2\\",
|
"patchState": [Function],
|
||||||
\\"parentId\\": \\"page1\\",
|
"persist": [Function],
|
||||||
\\"name\\": \\"Rectangle\\",
|
"pinchZoom": [Function],
|
||||||
\\"childIndex\\": 2,
|
"pointBounds": [Function],
|
||||||
\\"type\\": \\"rectangle\\",
|
"pointBoundsHandle": [Function],
|
||||||
\\"point\\": [
|
"pointCanvas": [Function],
|
||||||
100,
|
"pointShape": [Function],
|
||||||
100
|
"pointer": -1,
|
||||||
],
|
"pressKey": [Function],
|
||||||
\\"size\\": [
|
"prevAssets": Object {},
|
||||||
100,
|
"prevBindings": Object {},
|
||||||
100
|
"prevSelectedIds": Array [],
|
||||||
],
|
"prevShapes": Object {},
|
||||||
\\"style\\": {
|
"previousPoint": Array [
|
||||||
\\"dash\\": \\"draw\\",
|
0,
|
||||||
\\"size\\": \\"medium\\",
|
0,
|
||||||
\\"color\\": \\"blue\\"
|
],
|
||||||
},
|
"readOnly": false,
|
||||||
\\"label\\": \\"\\",
|
"ready": Promise {},
|
||||||
\\"labelPoint\\": [
|
"redo": [Function],
|
||||||
0.5,
|
"redoSelect": [Function],
|
||||||
0.5
|
"refreshBoundingBoxes": [Function],
|
||||||
]
|
"releaseKey": [Function],
|
||||||
|
"removeUser": [Function],
|
||||||
|
"renamePage": [Function],
|
||||||
|
"rendererBounds": Object {
|
||||||
|
"height": 100,
|
||||||
|
"maxX": 100,
|
||||||
|
"maxY": 100,
|
||||||
|
"minX": 0,
|
||||||
|
"minY": 0,
|
||||||
|
"width": 100,
|
||||||
},
|
},
|
||||||
{
|
"replaceHistory": [Function],
|
||||||
\\"id\\": \\"rect3\\",
|
"replacePageContent": [Function],
|
||||||
\\"parentId\\": \\"page1\\",
|
"replaceState": [Function],
|
||||||
\\"name\\": \\"Rectangle\\",
|
"reset": [Function],
|
||||||
\\"childIndex\\": 3,
|
"resetBounds": [Function],
|
||||||
\\"type\\": \\"rectangle\\",
|
"resetCamera": [Function],
|
||||||
\\"point\\": [
|
"resetDocument": [Function],
|
||||||
20,
|
"resetHistory": [Function],
|
||||||
20
|
"resetZoom": [Function],
|
||||||
|
"rotate": [Function],
|
||||||
|
"rotationInfo": Object {
|
||||||
|
"center": Array [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
],
|
],
|
||||||
\\"size\\": [
|
"selectedIds": Array [],
|
||||||
100,
|
},
|
||||||
100
|
"saveProject": [Function],
|
||||||
|
"saveProjectAs": [Function],
|
||||||
|
"select": [Function],
|
||||||
|
"selectAll": [Function],
|
||||||
|
"selectHistory": Object {
|
||||||
|
"pointer": 1,
|
||||||
|
"stack": Array [
|
||||||
|
Array [],
|
||||||
|
Array [
|
||||||
|
"rect1",
|
||||||
|
"rect2",
|
||||||
|
"rect3",
|
||||||
|
],
|
||||||
],
|
],
|
||||||
\\"style\\": {
|
},
|
||||||
\\"dash\\": \\"draw\\",
|
"selectNone": [Function],
|
||||||
\\"size\\": \\"medium\\",
|
"selectTool": [Function],
|
||||||
\\"color\\": \\"blue\\"
|
"session": undefined,
|
||||||
|
"setCamera": [Function],
|
||||||
|
"setDisableAssets": [Function],
|
||||||
|
"setEditingId": [Function],
|
||||||
|
"setHoveredId": [Function],
|
||||||
|
"setIsLoading": [Function],
|
||||||
|
"setMenuOpen": [Function],
|
||||||
|
"setSelectedIds": [Function],
|
||||||
|
"setSetting": [Function],
|
||||||
|
"setShapeProps": [Function],
|
||||||
|
"setSnapshot": [Function],
|
||||||
|
"setState": [Function],
|
||||||
|
"shiftKey": false,
|
||||||
|
"signOut": [Function],
|
||||||
|
"spaceKey": false,
|
||||||
|
"stack": Array [],
|
||||||
|
"startSession": [Function],
|
||||||
|
"stopPointing": [Function],
|
||||||
|
"store": Object {
|
||||||
|
"destroy": [Function],
|
||||||
|
"getState": [Function],
|
||||||
|
"setState": [Function],
|
||||||
|
"subscribe": [Function],
|
||||||
|
},
|
||||||
|
"stretch": [Function],
|
||||||
|
"style": [Function],
|
||||||
|
"toggleAspectRatioLocked": [Function],
|
||||||
|
"toggleDarkMode": [Function],
|
||||||
|
"toggleDebugMode": [Function],
|
||||||
|
"toggleDecoration": [Function],
|
||||||
|
"toggleFocusMode": [Function],
|
||||||
|
"toggleGrid": [Function],
|
||||||
|
"toggleHidden": [Function],
|
||||||
|
"toggleLocked": [Function],
|
||||||
|
"togglePenMode": [Function],
|
||||||
|
"toggleToolLock": [Function],
|
||||||
|
"toggleZoomSnap": [Function],
|
||||||
|
"tools": Object {
|
||||||
|
"arrow": ArrowTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
\\"label\\": \\"\\",
|
"draw": DrawTool {
|
||||||
\\"labelPoint\\": [
|
"app": [Circular],
|
||||||
0.5,
|
"getNextChildIndex": [Function],
|
||||||
0.5
|
"onCancel": [Function],
|
||||||
]
|
"onEnter": [Function],
|
||||||
}
|
"onExit": [Function],
|
||||||
]"
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "draw",
|
||||||
|
},
|
||||||
|
"ellipse": EllipseTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "ellipse",
|
||||||
|
},
|
||||||
|
"erase": EraseTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "erase",
|
||||||
|
},
|
||||||
|
"line": LineTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "line",
|
||||||
|
},
|
||||||
|
"rectangle": RectangleTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "rectangle",
|
||||||
|
},
|
||||||
|
"select": SelectTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"clonePaint": [Function],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"getShapeClone": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onDoubleClickBoundsHandle": [Function],
|
||||||
|
"onDoubleClickCanvas": [Function],
|
||||||
|
"onDoubleClickHandle": [Function],
|
||||||
|
"onDoubleClickShape": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onHoverShape": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointBounds": [Function],
|
||||||
|
"onPointBoundsHandle": [Function],
|
||||||
|
"onPointHandle": [Function],
|
||||||
|
"onPointShape": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"onReleaseBounds": [Function],
|
||||||
|
"onReleaseBoundsHandle": [Function],
|
||||||
|
"onReleaseHandle": [Function],
|
||||||
|
"onRightPointBounds": [Function],
|
||||||
|
"onRightPointShape": [Function],
|
||||||
|
"onShapeClone": [Function],
|
||||||
|
"onUnhoverShape": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "select",
|
||||||
|
},
|
||||||
|
"sticky": StickyTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "sticky",
|
||||||
|
},
|
||||||
|
"text": TextTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointShape": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"onShapeBlur": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"stopEditingShape": [Function],
|
||||||
|
"type": "text",
|
||||||
|
},
|
||||||
|
"triangle": TriangleTool {
|
||||||
|
"app": [Circular],
|
||||||
|
"getNextChildIndex": [Function],
|
||||||
|
"onCancel": [Function],
|
||||||
|
"onEnter": [Function],
|
||||||
|
"onExit": [Function],
|
||||||
|
"onKeyDown": [Function],
|
||||||
|
"onKeyUp": [Function],
|
||||||
|
"onPinch": [Function],
|
||||||
|
"onPinchEnd": [Function],
|
||||||
|
"onPinchStart": [Function],
|
||||||
|
"onPointerDown": [Function],
|
||||||
|
"onPointerMove": [Function],
|
||||||
|
"onPointerUp": [Function],
|
||||||
|
"setStatus": [Function],
|
||||||
|
"status": "idle",
|
||||||
|
"type": "triangle",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"undo": [Function],
|
||||||
|
"undoSelect": [Function],
|
||||||
|
"ungroup": [Function],
|
||||||
|
"updateBounds": [Function],
|
||||||
|
"updateDocument": [Function],
|
||||||
|
"updateInputs": [Function],
|
||||||
|
"updateSession": [Function],
|
||||||
|
"updateShapes": [Function],
|
||||||
|
"updateUsers": [Function],
|
||||||
|
"updateViewport": [Function],
|
||||||
|
"useStore": [Function],
|
||||||
|
"viewport": Object {
|
||||||
|
"height": 100,
|
||||||
|
"maxX": 100,
|
||||||
|
"maxY": 100,
|
||||||
|
"minX": 0,
|
||||||
|
"minY": 0,
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
"zoomBy": [Function],
|
||||||
|
"zoomIn": [Function],
|
||||||
|
"zoomOut": [Function],
|
||||||
|
"zoomTo": [Function],
|
||||||
|
"zoomToContent": [Function],
|
||||||
|
"zoomToFit": [Function],
|
||||||
|
"zoomToSelection": [Function],
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`TldrawTestApp Exposes undo/redo stack: history 1`] = `
|
exports[`TldrawTestApp Exposes undo/redo stack: history 1`] = `
|
||||||
|
|
|
@ -22,3 +22,4 @@ export * from './translateShapes'
|
||||||
export * from './ungroupShapes'
|
export * from './ungroupShapes'
|
||||||
export * from './updateShapes'
|
export * from './updateShapes'
|
||||||
export * from './setShapesProps'
|
export * from './setShapesProps'
|
||||||
|
export * from './insertContent'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './insertContent'
|
|
@ -0,0 +1,187 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import { Utils } from '@tldraw/core'
|
||||||
|
import { TLDR } from '~state/TLDR'
|
||||||
|
import { mockDocument, TldrawTestApp } from '~test'
|
||||||
|
import { ColorStyle, DashStyle, SessionType, SizeStyle, TDShapeType } from '~types'
|
||||||
|
|
||||||
|
let app: TldrawTestApp
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
app = new TldrawTestApp()
|
||||||
|
app.loadDocument(mockDocument)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('insert command', () => {
|
||||||
|
it('Inserts shapes, bindings, etc. into the current page', () => {
|
||||||
|
const content = app.getContent()!
|
||||||
|
const size = app.shapes.length
|
||||||
|
app.insertContent(content)
|
||||||
|
expect(app.shapes.length).toBe(size * 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Selects content when opts.select is true', () => {
|
||||||
|
const content = app.getContent()!
|
||||||
|
const size = app.shapes.length
|
||||||
|
const prevSelectedIds = [...app.selectedIds]
|
||||||
|
app.insertContent(content, { select: true })
|
||||||
|
expect(app.shapes.length).toBe(size * 2)
|
||||||
|
expect(app.selectedIds).not.toMatchObject(prevSelectedIds)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Centers inserted content at a point', () => {
|
||||||
|
app.select('rect1')
|
||||||
|
const content = app.getContent()!
|
||||||
|
|
||||||
|
const before = [...app.shapes]
|
||||||
|
|
||||||
|
const point = [222, 444]
|
||||||
|
|
||||||
|
app.insertContent(content, { point })
|
||||||
|
|
||||||
|
const inserted = [...app.shapes].filter((s) => !before.includes(s))
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Utils.getBoundsCenter(Utils.getCommonBounds(inserted.map(TLDR.getBounds)))
|
||||||
|
).toMatchObject(point)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does nothing when ids are explicitly empty', () => {
|
||||||
|
const content = app.getContent([])
|
||||||
|
expect(content).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the selected ids when no ids provided', () => {
|
||||||
|
app.select('rect1')
|
||||||
|
const content = app.getContent()!
|
||||||
|
const size = app.shapes.length
|
||||||
|
app.insertContent(content)
|
||||||
|
expect(app.shapes.length).toBe(size + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses all shape ids from the page when no selection, either', () => {
|
||||||
|
app.selectNone()
|
||||||
|
const content = app.getContent()!
|
||||||
|
const size = app.shapes.length
|
||||||
|
app.insertContent(content)
|
||||||
|
expect(app.shapes.length).toBe(size * 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does nothing if the page has no shapes, either', () => {
|
||||||
|
app.deleteAll()
|
||||||
|
const content = app.getContent()
|
||||||
|
expect(content).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('includes bindings', () => {
|
||||||
|
app
|
||||||
|
.createShapes({
|
||||||
|
type: TDShapeType.Arrow,
|
||||||
|
id: 'arrow1',
|
||||||
|
point: [200, 200],
|
||||||
|
})
|
||||||
|
.select('arrow1')
|
||||||
|
.movePointer([200, 200])
|
||||||
|
.startSession(SessionType.Arrow, 'arrow1', 'start')
|
||||||
|
.movePointer([50, 50])
|
||||||
|
.completeSession()
|
||||||
|
.selectNone()
|
||||||
|
|
||||||
|
expect(app.bindings.length).toBe(1)
|
||||||
|
|
||||||
|
const content = app.getContent()!
|
||||||
|
const size = app.shapes.length
|
||||||
|
|
||||||
|
app.insertContent(content)
|
||||||
|
expect(app.bindings.length).toBe(2)
|
||||||
|
expect(app.shapes.length).toBe(size * 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes bindings when only one shape is inserted', () => {
|
||||||
|
app
|
||||||
|
.createShapes({
|
||||||
|
type: TDShapeType.Arrow,
|
||||||
|
id: 'arrow1',
|
||||||
|
point: [200, 200],
|
||||||
|
})
|
||||||
|
.select('arrow1')
|
||||||
|
.movePointer([200, 200])
|
||||||
|
.startSession(SessionType.Arrow, 'arrow1', 'start')
|
||||||
|
.movePointer([50, 50])
|
||||||
|
.completeSession()
|
||||||
|
|
||||||
|
expect(app.bindings.length).toBe(1) // arrow1 -> rect3
|
||||||
|
|
||||||
|
app.select('rect3') // select only rect3, not arrow1
|
||||||
|
|
||||||
|
const content = app.getContent()!
|
||||||
|
|
||||||
|
// getContent does not include the incomplete binding
|
||||||
|
expect(Object.values(content.bindings).length).toBe(0)
|
||||||
|
|
||||||
|
app.insertContent(content)
|
||||||
|
|
||||||
|
// insertContent does not paste in the discarded binding
|
||||||
|
expect(app.bindings.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('works with groups', () => {
|
||||||
|
app.select('rect1', 'rect2').group().selectAll()
|
||||||
|
|
||||||
|
const content = app.getContent()!
|
||||||
|
|
||||||
|
const size = app.shapes.length
|
||||||
|
|
||||||
|
app.insertContent(content)
|
||||||
|
|
||||||
|
expect(app.shapes.length).toBe(size * 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('if a shapes parent is not inserted, inserts to the page instead', () => {
|
||||||
|
app.select('rect1', 'rect2').group().select('rect1')
|
||||||
|
|
||||||
|
const content = app.getContent()!
|
||||||
|
|
||||||
|
// insertContent discards the incomplete binding
|
||||||
|
const size = app.shapes.length
|
||||||
|
|
||||||
|
const before = [...app.shapes]
|
||||||
|
|
||||||
|
app.insertContent(content)
|
||||||
|
|
||||||
|
expect(app.shapes.length).toBe(size + 1)
|
||||||
|
|
||||||
|
const inserted = [...app.shapes].filter((s) => !before.includes(s))[0]
|
||||||
|
|
||||||
|
expect(inserted.parentId).toBe(app.currentPageId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not add groups without children', () => {
|
||||||
|
// insertContent discards the incomplete binding
|
||||||
|
const size = app.shapes.length
|
||||||
|
|
||||||
|
app.insertContent({
|
||||||
|
shapes: [
|
||||||
|
{
|
||||||
|
id: '935ff424-bf40-4e2d-3bfb-d26061150b03',
|
||||||
|
type: TDShapeType.Group,
|
||||||
|
name: 'Group',
|
||||||
|
parentId: 'currentPageId',
|
||||||
|
childIndex: 1,
|
||||||
|
point: [0, 0],
|
||||||
|
size: [100, 100],
|
||||||
|
rotation: 0,
|
||||||
|
children: [],
|
||||||
|
style: {
|
||||||
|
color: ColorStyle.Black,
|
||||||
|
size: SizeStyle.Small,
|
||||||
|
isFilled: false,
|
||||||
|
dash: DashStyle.Dashed,
|
||||||
|
scale: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(app.shapes.length).toBe(size)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,251 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import { Utils } from '@tldraw/core'
|
||||||
|
import { Vec } from '@tldraw/vec'
|
||||||
|
import { GRID_SIZE } from '~constants'
|
||||||
|
import { TLDR } from '~state/TLDR'
|
||||||
|
import type { PagePartial, TldrawCommand, TDShape, TDBinding, TDAsset } from '~types'
|
||||||
|
import type { TldrawApp } from '../../internal'
|
||||||
|
|
||||||
|
export function insertContent(
|
||||||
|
app: TldrawApp,
|
||||||
|
content: { shapes: TDShape[]; bindings?: TDBinding[]; assets?: TDAsset[] },
|
||||||
|
opts = {} as { point?: number[]; select?: boolean }
|
||||||
|
): TldrawCommand {
|
||||||
|
const { currentPageId } = app
|
||||||
|
|
||||||
|
const before: PagePartial = {
|
||||||
|
shapes: {},
|
||||||
|
bindings: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const after: PagePartial = {
|
||||||
|
shapes: {},
|
||||||
|
bindings: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldToNewIds: Record<string, string> = {}
|
||||||
|
|
||||||
|
// The index of the new shape
|
||||||
|
let nextIndex = TLDR.getTopChildIndex(app.state, currentPageId)
|
||||||
|
|
||||||
|
const shapesToInsert: TDShape[] = content.shapes
|
||||||
|
.sort((a, b) => a.childIndex - b.childIndex)
|
||||||
|
.map((shape) => {
|
||||||
|
const newShapeId = Utils.uniqueId()
|
||||||
|
oldToNewIds[shape.id] = newShapeId
|
||||||
|
|
||||||
|
// The redo should include a clone of the new shape
|
||||||
|
return {
|
||||||
|
...Utils.deepClone(shape),
|
||||||
|
id: newShapeId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const visited = new Set<string>()
|
||||||
|
|
||||||
|
// Iterate through the list, starting from the front
|
||||||
|
while (shapesToInsert.length > 0) {
|
||||||
|
const shape = shapesToInsert.shift()
|
||||||
|
|
||||||
|
if (!shape) break
|
||||||
|
|
||||||
|
visited.add(shape.id)
|
||||||
|
|
||||||
|
if (shape.parentId === 'currentPageId') {
|
||||||
|
shape.parentId = currentPageId
|
||||||
|
shape.childIndex = nextIndex++
|
||||||
|
} else {
|
||||||
|
// The shape had another shape as its parent.
|
||||||
|
|
||||||
|
// Re-assign the shape's parentId to the new id
|
||||||
|
shape.parentId = oldToNewIds[shape.parentId]
|
||||||
|
|
||||||
|
// Has that parent been added yet to the after object?
|
||||||
|
const parent = after.shapes[shape.parentId]
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
if (visited.has(shape.id)) {
|
||||||
|
// If we've already visited this shape, then that means
|
||||||
|
// its parent was not among the shapes to insert. Set it
|
||||||
|
// to be a child of the current page instead.
|
||||||
|
shape.parentId = 'currentPageId'
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the parent hasn't been added yet, push this shape
|
||||||
|
// to back of the queue; we'll try and add it again later
|
||||||
|
shapesToInsert.push(shape)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've found the parent, add this shape's id to its children
|
||||||
|
parent.children!.push(shape.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the inserting shape has its own children, set the children to
|
||||||
|
// an empty array; we'll add them later, as just shown above
|
||||||
|
if (shape.children) {
|
||||||
|
shape.children = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// The undo should remove the inserted shape
|
||||||
|
before.shapes[shape.id] = undefined
|
||||||
|
|
||||||
|
// The redo should include the inserted shape
|
||||||
|
after.shapes[shape.id] = shape
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.values(after.shapes).forEach((shape) => {
|
||||||
|
// If the shape used to have children, but no longer does have children,
|
||||||
|
// then delete the shape. This prevents inserting groups without children.
|
||||||
|
if (shape!.children && shape!.children.length === 0) {
|
||||||
|
delete before.shapes[shape!.id!]
|
||||||
|
delete after.shapes[shape!.id!]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Insert bindings
|
||||||
|
if (content.bindings) {
|
||||||
|
content.bindings.forEach((binding) => {
|
||||||
|
const newBindingId = Utils.uniqueId()
|
||||||
|
oldToNewIds[binding.id] = newBindingId
|
||||||
|
|
||||||
|
const toId = oldToNewIds[binding.toId]
|
||||||
|
const fromId = oldToNewIds[binding.fromId]
|
||||||
|
|
||||||
|
// If the binding is "to" or "from" a shape that hasn't been inserted,
|
||||||
|
// we'll need to skip the binding and remove it from any shape that
|
||||||
|
// references it.
|
||||||
|
if (!toId || !fromId) {
|
||||||
|
if (fromId) {
|
||||||
|
const handles = after.shapes[fromId]!.handles
|
||||||
|
if (handles) {
|
||||||
|
Object.values(handles).forEach((handle) => {
|
||||||
|
if (handle!.bindingId === binding.id) {
|
||||||
|
handle!.bindingId = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toId) {
|
||||||
|
const handles = after.shapes[toId]!.handles
|
||||||
|
if (handles) {
|
||||||
|
Object.values(handles).forEach((handle) => {
|
||||||
|
if (handle!.bindingId === binding.id) {
|
||||||
|
handle!.bindingId = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the shape's to and from references to the new bindingid
|
||||||
|
|
||||||
|
const fromHandles = after.shapes[fromId]!.handles
|
||||||
|
if (fromHandles) {
|
||||||
|
Object.values(fromHandles).forEach((handle) => {
|
||||||
|
if (handle!.bindingId === binding.id) {
|
||||||
|
handle!.bindingId = newBindingId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toHandles = after.shapes[toId]!.handles
|
||||||
|
if (toHandles) {
|
||||||
|
Object.values(after.shapes[toId]!.handles!).forEach((handle) => {
|
||||||
|
if (handle!.bindingId === binding.id) {
|
||||||
|
handle!.bindingId = newBindingId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const newBinding = {
|
||||||
|
...Utils.deepClone(binding),
|
||||||
|
id: newBindingId,
|
||||||
|
toId,
|
||||||
|
fromId,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The undo should remove the inserted binding
|
||||||
|
before.bindings[newBinding.id] = undefined
|
||||||
|
|
||||||
|
// The redo should include the inserted binding
|
||||||
|
after.bindings[newBinding.id] = newBinding
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now move the shapes
|
||||||
|
|
||||||
|
const shapesToMove = Object.values(after.shapes) as TDShape[]
|
||||||
|
|
||||||
|
const { point, select } = opts
|
||||||
|
|
||||||
|
if (shapesToMove.length > 0) {
|
||||||
|
if (point) {
|
||||||
|
// Move the shapes so that they're centered on the given point
|
||||||
|
const commonBounds = Utils.getCommonBounds(shapesToMove.map((shape) => TLDR.getBounds(shape)))
|
||||||
|
const center = Utils.getBoundsCenter(commonBounds)
|
||||||
|
shapesToMove.forEach((shape) => {
|
||||||
|
if (!shape.point) return
|
||||||
|
shape.point = Vec.sub(point, Vec.sub(center, shape.point))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const commonBounds = Utils.getCommonBounds(shapesToMove.map(TLDR.getBounds))
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
Utils.boundsContain(app.viewport, commonBounds) ||
|
||||||
|
Utils.boundsCollide(app.viewport, commonBounds)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const center = Vec.toFixed(app.getPagePoint(app.centerPoint))
|
||||||
|
|
||||||
|
const centeredBounds = Utils.centerBounds(commonBounds, center)
|
||||||
|
|
||||||
|
const delta = Vec.sub(
|
||||||
|
Utils.getBoundsCenter(centeredBounds),
|
||||||
|
Utils.getBoundsCenter(commonBounds)
|
||||||
|
)
|
||||||
|
|
||||||
|
shapesToMove.forEach((shape) => {
|
||||||
|
shape.point = Vec.toFixed(Vec.add(shape.point, delta))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'insert',
|
||||||
|
before: {
|
||||||
|
document: {
|
||||||
|
pages: {
|
||||||
|
[currentPageId]: before,
|
||||||
|
},
|
||||||
|
pageStates: {
|
||||||
|
[currentPageId]: { selectedIds: [...app.selectedIds] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
document: {
|
||||||
|
pages: {
|
||||||
|
[currentPageId]: after,
|
||||||
|
},
|
||||||
|
assets: content.assets
|
||||||
|
? Object.fromEntries(
|
||||||
|
content.assets
|
||||||
|
.filter((asset) => !app.document.assets[asset.id])
|
||||||
|
.map((asset) => [asset.id, asset])
|
||||||
|
)
|
||||||
|
: {},
|
||||||
|
pageStates: {
|
||||||
|
[currentPageId]: {
|
||||||
|
selectedIds: select ? Object.keys(after.shapes) : [...app.selectedIds],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue