kopia lustrzana https://github.com/Tldraw/Tldraw
494 wiersze
10 KiB
TypeScript
494 wiersze
10 KiB
TypeScript
import { TLFrameShape, TLGeoShape, approximately, createShapeId } from '@tldraw/editor'
|
|
import { TestEditor } from './TestEditor'
|
|
|
|
let editor: TestEditor
|
|
|
|
const ids = {
|
|
box1: createShapeId('box1'),
|
|
box2: createShapeId('box2'),
|
|
box3: createShapeId('box3'),
|
|
frame1: createShapeId('frame1'),
|
|
frame2: createShapeId('frame2'),
|
|
frame3: createShapeId('frame3'),
|
|
frame4: createShapeId('frame4'),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
editor = new TestEditor()
|
|
editor.createShapes([
|
|
{
|
|
id: ids.frame1,
|
|
type: 'frame',
|
|
x: 0,
|
|
y: 0,
|
|
props: {
|
|
w: 100, // ! we're using w to identify the clones
|
|
h: 100,
|
|
},
|
|
},
|
|
{
|
|
id: ids.frame2,
|
|
type: 'frame',
|
|
x: 0,
|
|
y: 100,
|
|
props: {
|
|
w: 101,
|
|
h: 100,
|
|
},
|
|
},
|
|
{
|
|
id: ids.frame3,
|
|
type: 'frame',
|
|
x: 0,
|
|
y: 200,
|
|
props: {
|
|
w: 102,
|
|
h: 100,
|
|
},
|
|
},
|
|
{
|
|
id: ids.frame4,
|
|
type: 'frame',
|
|
x: 0,
|
|
y: 300,
|
|
props: {
|
|
w: 103,
|
|
h: 100,
|
|
},
|
|
},
|
|
{
|
|
id: ids.box1,
|
|
type: 'geo',
|
|
x: 500,
|
|
y: 500,
|
|
props: {
|
|
w: 88,
|
|
},
|
|
},
|
|
{
|
|
id: ids.box2,
|
|
type: 'geo',
|
|
x: 600,
|
|
y: 600,
|
|
props: {
|
|
w: 89,
|
|
},
|
|
},
|
|
{
|
|
id: ids.box3,
|
|
type: 'geo',
|
|
x: 700,
|
|
y: 700,
|
|
props: {
|
|
w: 90,
|
|
},
|
|
},
|
|
])
|
|
})
|
|
|
|
function getShapes() {
|
|
const arr = editor.getCurrentPageShapes() as any[]
|
|
|
|
const results = { old: {}, new: {} } as {
|
|
old: Record<string, TLGeoShape | TLFrameShape>
|
|
new: Record<string, TLGeoShape | TLFrameShape>
|
|
}
|
|
|
|
Object.entries(ids).map(([normalId, shapeId]) => {
|
|
const shape = editor.getShape(shapeId as any) as any
|
|
const newShape = arr.find((s) => s.id !== shapeId && s.props.w === shape?.props.w)
|
|
results.old[normalId] = shape
|
|
results.new[normalId] = newShape
|
|
})
|
|
|
|
return results
|
|
}
|
|
|
|
it('Gets pasted shapes correctly', () => {
|
|
editor.select(ids.box1, ids.box2, ids.frame1, ids.box3)
|
|
editor.copy()
|
|
editor.selectNone()
|
|
let shapes = getShapes()
|
|
|
|
expect(editor.getCurrentPageShapesSorted().map((m) => m.id)).toStrictEqual([
|
|
shapes.old.frame1.id,
|
|
shapes.old.frame2.id,
|
|
shapes.old.frame3.id,
|
|
shapes.old.frame4.id,
|
|
shapes.old.box1.id,
|
|
shapes.old.box2.id,
|
|
shapes.old.box3.id,
|
|
])
|
|
|
|
editor.paste()
|
|
|
|
shapes = getShapes()
|
|
|
|
expect(editor.getCurrentPageShapesSorted().map((m) => m.id)).toStrictEqual([
|
|
shapes.old.frame1.id,
|
|
shapes.old.frame2.id,
|
|
shapes.old.frame3.id,
|
|
shapes.old.frame4.id,
|
|
shapes.old.box1.id,
|
|
shapes.old.box2.id,
|
|
shapes.old.box3.id,
|
|
shapes.new.frame1.id,
|
|
shapes.new.box1.id,
|
|
shapes.new.box2.id,
|
|
shapes.new.box3.id,
|
|
])
|
|
})
|
|
|
|
describe('When pasting', () => {
|
|
it('pastes shapes onto the page', () => {
|
|
/*
|
|
Before:
|
|
page
|
|
- frame1
|
|
- frame2
|
|
- frame3
|
|
- frame4
|
|
- box1
|
|
- box2
|
|
- box3
|
|
|
|
After:
|
|
page
|
|
- frame1
|
|
- frame2
|
|
- frame3
|
|
- frame4
|
|
- box1
|
|
- box2
|
|
- box3
|
|
- box1copy
|
|
- box2copy
|
|
*/
|
|
|
|
editor.select(ids.box1, ids.box2)
|
|
editor.copy()
|
|
editor.selectNone()
|
|
editor.paste()
|
|
|
|
const shapes = getShapes()
|
|
expect(shapes.new.box1?.parentId).toBe(editor.getCurrentPageId())
|
|
expect(shapes.new.box2?.parentId).toBe(editor.getCurrentPageId())
|
|
|
|
expect(editor.getCurrentPageShapesSorted().map((m) => m.id)).toStrictEqual([
|
|
shapes.old.frame1.id,
|
|
shapes.old.frame2.id,
|
|
shapes.old.frame3.id,
|
|
shapes.old.frame4.id,
|
|
shapes.old.box1.id,
|
|
shapes.old.box2.id,
|
|
shapes.old.box3.id,
|
|
shapes.new.box1.id,
|
|
shapes.new.box2.id,
|
|
])
|
|
})
|
|
|
|
it('pastes shapes as children of the selected shape when shape is a frame', () => {
|
|
/*
|
|
Before:
|
|
page
|
|
- frame1 *
|
|
- frame2
|
|
- frame3
|
|
- frame4
|
|
- box1
|
|
- box2
|
|
- box3
|
|
|
|
After:
|
|
page
|
|
- frame1
|
|
- box1copy *
|
|
- box2copy *
|
|
- frame2
|
|
- frame3
|
|
- frame4
|
|
- box1
|
|
- box2
|
|
- box3
|
|
*/
|
|
editor.select(ids.box1, ids.box2)
|
|
editor.copy()
|
|
editor.select(ids.frame1)
|
|
editor.paste()
|
|
|
|
const shapes = getShapes()
|
|
|
|
// Should make the pasted shapes the children of the frame
|
|
expect(shapes.new.box1?.parentId).toBe(shapes.old.frame1.id)
|
|
expect(shapes.new.box2?.parentId).toBe(shapes.old.frame1.id)
|
|
|
|
// Should put the pasted shapes centered in the frame
|
|
editor.select(shapes.new.box1!.id, shapes.new.box1!.id)
|
|
expect(editor.getSelectionPageCenter()).toMatchObject(
|
|
editor.getPageCenter(editor.getShape(ids.frame1)!)!
|
|
)
|
|
})
|
|
|
|
it('pastes shapes as children of the most common ancestor', () => {
|
|
editor.reparentShapes([ids.frame3], ids.frame1)
|
|
editor.reparentShapes([ids.frame4], ids.frame2)
|
|
editor.reparentShapes([ids.box1], ids.frame3)
|
|
editor.reparentShapes([ids.box2], ids.frame4)
|
|
/*
|
|
Before:
|
|
page
|
|
- frame1
|
|
- frame3
|
|
- box1 *
|
|
- frame2
|
|
- frame4
|
|
- box2 *
|
|
- box3
|
|
|
|
After:
|
|
page
|
|
- frame1
|
|
- frame3
|
|
- box1
|
|
- frame2
|
|
- frame4
|
|
- box2
|
|
- box3
|
|
- box1copy *
|
|
- box2copy *
|
|
*/
|
|
|
|
editor.select(ids.box1, ids.box2)
|
|
editor.copy()
|
|
editor.paste()
|
|
|
|
const shapes = getShapes()
|
|
|
|
// Should make the pasted shapes the children of the frame
|
|
expect(shapes.new.box1?.parentId).toBe(editor.getCurrentPageId())
|
|
expect(shapes.new.box2?.parentId).toBe(editor.getCurrentPageId())
|
|
|
|
// Should put the pasted shapes centered in the frame
|
|
editor.select(shapes.new.box1!.id, shapes.new.box1!.id)
|
|
expect(editor.getShapePageBounds(shapes.old.box1)).toMatchObject(
|
|
editor.getShapePageBounds(shapes.new.box1)!
|
|
)
|
|
})
|
|
|
|
it('pastes shapes as children of the most common ancestor', () => {
|
|
editor.reparentShapes([ids.frame3], ids.frame1)
|
|
editor.reparentShapes([ids.frame4], ids.frame1)
|
|
editor.reparentShapes([ids.box1], ids.frame3)
|
|
editor.reparentShapes([ids.box2], ids.frame4)
|
|
/*
|
|
Before:
|
|
page
|
|
- frame1
|
|
- frame3
|
|
- box1 *
|
|
- frame4
|
|
- box2 *
|
|
- frame2
|
|
- box3
|
|
|
|
After:
|
|
page
|
|
- frame1
|
|
- frame3
|
|
- box1
|
|
- frame4
|
|
- box2
|
|
- box1copy *
|
|
- box2copy *
|
|
- frame2
|
|
- box2
|
|
- box3
|
|
*/
|
|
|
|
editor.select(ids.box1, ids.box2)
|
|
editor.copy()
|
|
editor.paste()
|
|
|
|
const shapes = getShapes()
|
|
|
|
// Should make the pasted shapes the children of the frame
|
|
expect(shapes.new.box1?.parentId).toBe(shapes.old.frame1.id)
|
|
expect(shapes.new.box2?.parentId).toBe(shapes.old.frame1.id)
|
|
|
|
// Should put the pasted shapes centered in the frame
|
|
editor.select(shapes.new.box1!.id, shapes.new.box1!.id)
|
|
expect(editor.getSelectionPageCenter()).toMatchObject(editor.getPageCenter(shapes.old.frame1)!)
|
|
})
|
|
})
|
|
|
|
it('pastes shapes with children', () => {
|
|
editor.reparentShapes([ids.box1, ids.box2], ids.frame3)
|
|
/*
|
|
Before:
|
|
page
|
|
- frame1
|
|
- frame2
|
|
- frame3 *
|
|
- box1
|
|
- box2
|
|
- frame4
|
|
- box3
|
|
|
|
After:
|
|
page
|
|
- frame1
|
|
- frame2
|
|
- frame3
|
|
- box1
|
|
- box2
|
|
- frame4
|
|
- box3
|
|
- frame3copy
|
|
- box1copy
|
|
- box2copy
|
|
*/
|
|
|
|
editor.select(ids.frame3)
|
|
editor.copy()
|
|
editor.paste()
|
|
|
|
const shapes = getShapes()
|
|
|
|
// Should make the pasted shapes the children of the frame
|
|
expect(shapes.new.box1.parentId).toBe(shapes.new.frame3.id)
|
|
expect(shapes.new.box2.parentId).toBe(shapes.new.frame3.id)
|
|
expect(shapes.new.frame3.parentId).toBe(editor.getCurrentPageId())
|
|
})
|
|
|
|
describe('When pasting into frames...', () => {
|
|
it('Does not paste into a clipped frame', () => {
|
|
// clear the page
|
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
|
|
|
|
editor
|
|
// move the two frames far from all other shapes
|
|
.createShapes([
|
|
{
|
|
id: ids.frame1,
|
|
type: 'frame',
|
|
x: 2000,
|
|
y: 2000,
|
|
props: {
|
|
w: 100,
|
|
h: 100,
|
|
},
|
|
},
|
|
{
|
|
id: ids.frame2,
|
|
type: 'frame',
|
|
x: 2000,
|
|
y: 2000,
|
|
props: {
|
|
w: 100,
|
|
h: 100,
|
|
},
|
|
},
|
|
{
|
|
id: ids.box1,
|
|
type: 'geo',
|
|
x: 500,
|
|
y: 500,
|
|
},
|
|
])
|
|
.setScreenBounds({ x: 0, y: 0, w: 1000, h: 1000 })
|
|
|
|
// put frame2 inside frame1
|
|
editor.reparentShapes([ids.frame2], ids.frame1)
|
|
|
|
// move frame 2 so that it's clipped AND so that it covers the whole viewport
|
|
editor
|
|
.updateShapes([
|
|
{
|
|
id: ids.frame2,
|
|
type: 'frame',
|
|
x: 50,
|
|
y: 50,
|
|
props: {
|
|
w: 2000,
|
|
h: 2000,
|
|
},
|
|
},
|
|
])
|
|
// Make sure that frame 1 is brought to front
|
|
.select(ids.frame1)
|
|
.bringToFront(editor.getSelectedShapeIds())
|
|
|
|
editor.setCamera({ x: -2000, y: -2000, z: 1 })
|
|
|
|
// Copy box 1 (should be out of viewport)
|
|
editor.select(ids.box1).copy()
|
|
|
|
const shapesBefore = editor.getCurrentPageShapes()
|
|
// Paste it
|
|
editor.paste()
|
|
|
|
const newShape = editor.getCurrentPageShapes().find((s) => !shapesBefore.includes(s))!
|
|
|
|
// it should be on the canvas, NOT a child of frame2
|
|
expect(newShape.parentId).not.toBe(ids.frame2)
|
|
})
|
|
|
|
it('keeps things in the right place', () => {
|
|
// clear the page
|
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
|
|
// create a small box and copy it
|
|
editor.createShapes([
|
|
{
|
|
type: 'geo',
|
|
x: 0,
|
|
y: 0,
|
|
props: {
|
|
geo: 'rectangle',
|
|
w: 10,
|
|
h: 10,
|
|
},
|
|
},
|
|
])
|
|
editor.selectAll().copy()
|
|
// now delete it
|
|
editor.deleteShapes(editor.getSelectedShapeIds())
|
|
|
|
// create a big frame away from the origin, the size of the viewport
|
|
editor
|
|
.createShapes([
|
|
{
|
|
id: ids.frame1,
|
|
type: 'frame',
|
|
x: editor.getViewportScreenBounds().w,
|
|
y: editor.getViewportScreenBounds().h,
|
|
props: {
|
|
w: editor.getViewportScreenBounds().w,
|
|
h: editor.getViewportScreenBounds().h,
|
|
},
|
|
},
|
|
])
|
|
.selectAll()
|
|
// rotate the frame for hard mode
|
|
editor.rotateSelection(45)
|
|
// center on the center of the frame
|
|
editor.setCamera({
|
|
x: -editor.getViewportScreenBounds().w,
|
|
y: -editor.getViewportScreenBounds().h,
|
|
z: 1,
|
|
})
|
|
// paste the box
|
|
editor.paste()
|
|
const boxId = editor.getOnlySelectedShape()!.id
|
|
// it should be a child of the frame
|
|
expect(editor.getOnlySelectedShape()?.parentId).toBe(ids.frame1)
|
|
// it should have pageBounds of 10x10 because it is not rotated relative to the viewport
|
|
expect(editor.getShapePageBounds(boxId)).toMatchObject({ w: 10, h: 10 })
|
|
// it should be in the middle of the frame
|
|
const framePageCenter = editor.getPageCenter(editor.getShape(ids.frame1)!)!
|
|
const boxPageCenter = editor.getPageCenter(editor.getShape(boxId)!)!
|
|
|
|
expect(approximately(framePageCenter.x, boxPageCenter.x)).toBe(true)
|
|
expect(approximately(framePageCenter.y, boxPageCenter.y)).toBe(true)
|
|
})
|
|
})
|