Add overwrite option to insertContent (#730)

This commit is contained in:
Steve Ruiz 2022-06-20 20:36:23 +01:00 committed by GitHub
parent 65ff5075f0
commit 0a52b5c317
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 234 additions and 191 deletions

View file

@ -2169,7 +2169,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
// 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)) {
if (idsSet.has(binding.fromId) || idsSet.has(binding.toId)) {
return true
}
@ -2261,11 +2261,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
* @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.
* @param opts.select (optional) When true, the inserted shapes will be selected. Defaults to false.
* @param opts.overwrite (optional) When true, the inserted shapes and bindings will overwrite any existing shapes and bindings. Defaults to false.
*/
insertContent = (
content: { shapes: TDShape[]; bindings?: TDBinding[]; assets?: TDAsset[] },
opts = {} as { point?: number[]; select?: boolean }
opts = {} as { point?: number[]; select?: boolean; overwrite?: boolean }
) => {
return this.setState(Commands.insertContent(this, content, opts), 'insert_content')
}

View file

@ -115,8 +115,8 @@ describe('insert command', () => {
const content = app.getContent()!
// getContent does not include the incomplete binding
expect(Object.values(content.bindings).length).toBe(0)
// getContent DOES include the incomplete binding
expect(Object.values(content.bindings).length).toBe(1)
app.insertContent(content)
@ -185,3 +185,21 @@ describe('insert command', () => {
expect(app.shapes.length).toBe(size)
})
})
describe('When opts.overwrite is true', () => {
it('replaces content', () => {
const content = app.getContent()!
const size = app.shapes.length
const ids = app.shapes.map((s) => s.id)
app.insertContent(content, { overwrite: true })
expect(app.shapes.length).toBe(size)
expect(app.shapes.map((s) => s.id)).toMatchObject(ids)
})
it('restores content under the same ids', () => {
const content = app.getContent()!
const ids = app.shapes.map((s) => s.id)
app.deleteAll().insertContent(content, { overwrite: true })
expect(app.shapes.map((s) => s.id)).toMatchObject(ids)
})
})

View file

@ -1,7 +1,6 @@
/* 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'
@ -9,20 +8,45 @@ import type { TldrawApp } from '../../internal'
export function insertContent(
app: TldrawApp,
content: { shapes: TDShape[]; bindings?: TDBinding[]; assets?: TDAsset[] },
opts = {} as { point?: number[]; select?: boolean }
opts = {} as { point?: number[]; select?: boolean; overwrite?: boolean }
): TldrawCommand {
const { currentPageId } = app
const { point, select, overwrite } = opts
const page = app.document.pages[currentPageId]
const before: PagePartial = {
shapes: {},
bindings: {},
}
const afterAssets: Record<string, TDAsset> = {}
const after: PagePartial = {
shapes: {},
bindings: {},
}
if (overwrite) {
// Map shapes and bindings onto new IDs to avoid overwriting existing content.
for (const shape of content.shapes) {
before.shapes[shape.id] = page.shapes[shape.id]
after.shapes[shape.id] = shape
}
if (content.bindings) {
for (const binding of content.bindings) {
before.bindings[binding.id] = page.bindings[binding.id]
after.bindings[binding.id] = binding
}
}
if (content.assets) {
for (const asset of content.assets) {
afterAssets[asset.id] = asset
}
}
} else {
// Map shapes and bindings onto new IDs to avoid overwriting existing content.
const oldToNewIds: Record<string, string> = {}
// The index of the new shape
@ -180,12 +204,12 @@ export function insertContent(
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 commonBounds = Utils.getCommonBounds(
shapesToMove.map((shape) => TLDR.getBounds(shape))
)
const center = Utils.getBoundsCenter(commonBounds)
shapesToMove.forEach((shape) => {
if (!shape.point) return
@ -215,6 +239,12 @@ export function insertContent(
}
}
}
if (content.assets) {
for (const asset of content.assets) {
afterAssets[asset.id] = asset
}
}
}
return {
id: 'insert',
@ -233,13 +263,7 @@ export function insertContent(
pages: {
[currentPageId]: after,
},
assets: content.assets
? Object.fromEntries(
content.assets
.filter((asset) => !app.document.assets[asset.id])
.map((asset) => [asset.id, asset])
)
: {},
assets: afterAssets,
pageStates: {
[currentPageId]: {
selectedIds: select ? Object.keys(after.shapes) : [...app.selectedIds],