Merge pull request #31 from tldraw/testing-30-transform-command

Adds testMode, tests for transform
This commit is contained in:
Steve Ruiz 2021-07-09 10:34:14 +01:00 committed by GitHub
commit b2904d6a1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1796 additions and 231 deletions

View file

@ -12,3 +12,5 @@ jobs:
uses: mattallty/jest-github-action@v1.0.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
test-command: 'yarn test'

View file

@ -15,8 +15,8 @@
"name": "Rectangle",
"parentId": "page1",
"childIndex": 3,
"point": [171.47, 288.63],
"size": [176.22, 192.26],
"point": [100, 100],
"size": [100, 100],
"radius": 2,
"rotation": 0,
"style": {
@ -32,8 +32,8 @@
"name": "Rectangle",
"parentId": "page1",
"childIndex": 4,
"point": [511.7, 404.19],
"size": [181.08999999999992, 150.40999999999997],
"point": [500, 400],
"size": [200, 200],
"radius": 2,
"rotation": 0,
"style": {

View file

@ -64,14 +64,14 @@ for (let i = 0; i < count; i++) {
"name": "Rectangle",
"parentId": "page1",
"point": Array [
511.7,
404.19,
500,
400,
],
"radius": 2,
"rotation": 0,
"size": Array [
181.08999999999992,
150.40999999999997,
200,
200,
],
"style": Object {
"color": "Black",
@ -330,14 +330,14 @@ for (let i = 0; i < count; i++) {
"name": "Rectangle",
"parentId": "page1",
"point": Array [
171.47,
288.63,
100,
100,
],
"radius": 2,
"rotation": 0,
"size": Array [
176.22,
192.26,
100,
100,
],
"style": Object {
"color": "Black",
@ -468,14 +468,14 @@ for (let i = 0; i < count; i++) {
"name": "Rectangle",
"parentId": "page1",
"point": Array [
511.7,
404.19,
500,
400,
],
"radius": 2,
"rotation": 0,
"size": Array [
181.08999999999992,
150.40999999999997,
200,
200,
],
"style": Object {
"color": "Black",
@ -734,14 +734,14 @@ for (let i = 0; i < count; i++) {
"name": "Rectangle",
"parentId": "page1",
"point": Array [
171.47,
288.63,
100,
100,
],
"radius": 2,
"rotation": 0,
"size": Array [
176.22,
192.26,
100,
100,
],
"style": Object {
"color": "Black",

View file

@ -0,0 +1,852 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transform command snapshot tests shift-transforms corners 1`] = `
Object {
"height": 593.73,
"maxX": 700,
"maxY": 600,
"minX": -12.476,
"minY": 6.27,
"width": 712.476,
}
`;
exports[`transform command snapshot tests shift-transforms corners 2`] = `
Object {
"rect1": Object {
"point": Array [
-12.476,
6.27,
],
"size": Array [
118.75,
118.75,
],
},
"rect2": Object {
"point": Array [
462.51,
362.51,
],
"size": Array [
237.49,
237.49,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms corners 3`] = `
Object {
"height": 531.6320000000001,
"maxX": 737.9599999999999,
"maxY": 600,
"minX": 100,
"minY": 68.368,
"width": 637.9599999999999,
}
`;
exports[`transform command snapshot tests shift-transforms corners 4`] = `
Object {
"rect1": Object {
"point": Array [
100,
68.368,
],
"size": Array [
106.33,
106.33,
],
},
"rect2": Object {
"point": Array [
525.31,
387.35,
],
"size": Array [
212.65,
212.65,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms corners 5`] = `
Object {
"height": 533.62,
"maxX": 740.3499999999999,
"maxY": 633.62,
"minX": 100,
"minY": 100,
"width": 640.3499999999999,
}
`;
exports[`transform command snapshot tests shift-transforms corners 6`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
106.72,
106.72,
],
},
"rect2": Object {
"point": Array [
526.9,
420.17,
],
"size": Array [
213.45,
213.45,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms corners 7`] = `
Object {
"height": 490.52,
"maxX": 700,
"maxY": 590.52,
"minX": 111.38,
"minY": 100,
"width": 588.62,
}
`;
exports[`transform command snapshot tests shift-transforms corners 8`] = `
Object {
"rect1": Object {
"point": Array [
111.38,
100,
],
"size": Array [
98.104,
98.104,
],
},
"rect2": Object {
"point": Array [
503.79,
394.31,
],
"size": Array [
196.21,
196.21,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms edges 1`] = `
Object {
"height": 593.73,
"maxX": 756.24,
"maxY": 600,
"minX": 43.762,
"minY": 6.27,
"width": 712.4780000000001,
}
`;
exports[`transform command snapshot tests shift-transforms edges 2`] = `
Object {
"rect1": Object {
"point": Array [
43.762,
6.27,
],
"size": Array [
118.75,
118.75,
],
},
"rect2": Object {
"point": Array [
518.75,
362.51,
],
"size": Array [
237.49,
237.49,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms edges 3`] = `
Object {
"height": 531.6260000000001,
"maxX": 737.9599999999999,
"maxY": 615.8100000000001,
"minX": 100,
"minY": 84.184,
"width": 637.9599999999999,
}
`;
exports[`transform command snapshot tests shift-transforms edges 4`] = `
Object {
"rect1": Object {
"point": Array [
100,
84.184,
],
"size": Array [
106.33,
106.33,
],
},
"rect2": Object {
"point": Array [
525.31,
403.16,
],
"size": Array [
212.65,
212.65,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms edges 5`] = `
Object {
"height": 411.35,
"maxX": 646.81,
"maxY": 511.35,
"minX": 153.19,
"minY": 100,
"width": 493.61999999999995,
}
`;
exports[`transform command snapshot tests shift-transforms edges 6`] = `
Object {
"rect1": Object {
"point": Array [
153.19,
100,
],
"size": Array [
82.269,
82.269,
],
},
"rect2": Object {
"point": Array [
482.27,
346.81,
],
"size": Array [
164.54,
164.54,
],
},
}
`;
exports[`transform command snapshot tests shift-transforms edges 7`] = `
Object {
"height": 490.52,
"maxX": 700,
"maxY": 595.26,
"minX": 111.38,
"minY": 104.74,
"width": 588.62,
}
`;
exports[`transform command snapshot tests shift-transforms edges 8`] = `
Object {
"rect1": Object {
"point": Array [
111.38,
104.74,
],
"size": Array [
98.104,
98.104,
],
},
"rect2": Object {
"point": Array [
503.79,
399.05,
],
"size": Array [
196.21,
196.21,
],
},
}
`;
exports[`transform command snapshot tests transforms corners 1`] = `
Object {
"height": 593.73,
"maxX": 700,
"maxY": 600,
"minX": 27.892,
"minY": 6.27,
"width": 672.108,
}
`;
exports[`transform command snapshot tests transforms corners 2`] = `
Object {
"rect1": Object {
"point": Array [
27.892,
6.27,
],
"size": Array [
112.02,
118.75,
],
},
"rect2": Object {
"point": Array [
475.96,
362.51,
],
"size": Array [
224.04,
237.49,
],
},
}
`;
exports[`transform command snapshot tests transforms corners 3`] = `
Object {
"height": 490.17,
"maxX": 737.9599999999999,
"maxY": 600,
"minX": 100,
"minY": 109.83,
"width": 637.9599999999999,
}
`;
exports[`transform command snapshot tests transforms corners 4`] = `
Object {
"rect1": Object {
"point": Array [
100,
109.83,
],
"size": Array [
106.33,
98.034,
],
},
"rect2": Object {
"point": Array [
525.31,
403.93,
],
"size": Array [
212.65,
196.07,
],
},
}
`;
exports[`transform command snapshot tests transforms corners 5`] = `
Object {
"height": 411.35,
"maxX": 740.3499999999999,
"maxY": 511.35,
"minX": 100,
"minY": 100,
"width": 640.3499999999999,
}
`;
exports[`transform command snapshot tests transforms corners 6`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
106.72,
82.269,
],
},
"rect2": Object {
"point": Array [
526.9,
346.81,
],
"size": Array [
213.45,
164.54,
],
},
}
`;
exports[`transform command snapshot tests transforms corners 7`] = `
Object {
"height": 437.4,
"maxX": 700,
"maxY": 537.4,
"minX": 111.38,
"minY": 100,
"width": 588.62,
}
`;
exports[`transform command snapshot tests transforms corners 8`] = `
Object {
"rect1": Object {
"point": Array [
111.38,
100,
],
"size": Array [
98.104,
87.479,
],
},
"rect2": Object {
"point": Array [
503.79,
362.44,
],
"size": Array [
196.21,
174.96,
],
},
}
`;
exports[`transform command snapshot tests transforms edges 1`] = `
Object {
"height": 593.73,
"maxX": 700,
"maxY": 600,
"minX": 100,
"minY": 6.27,
"width": 600,
}
`;
exports[`transform command snapshot tests transforms edges 2`] = `
Object {
"rect1": Object {
"point": Array [
100,
6.27,
],
"size": Array [
100,
118.75,
],
},
"rect2": Object {
"point": Array [
500,
362.51,
],
"size": Array [
200,
237.49,
],
},
}
`;
exports[`transform command snapshot tests transforms edges 3`] = `
Object {
"height": 500,
"maxX": 737.9599999999999,
"maxY": 600,
"minX": 100,
"minY": 100,
"width": 637.9599999999999,
}
`;
exports[`transform command snapshot tests transforms edges 4`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
106.33,
100,
],
},
"rect2": Object {
"point": Array [
525.31,
400,
],
"size": Array [
212.65,
200,
],
},
}
`;
exports[`transform command snapshot tests transforms edges 5`] = `
Object {
"height": 411.35,
"maxX": 700,
"maxY": 511.35,
"minX": 100,
"minY": 100,
"width": 600,
}
`;
exports[`transform command snapshot tests transforms edges 6`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
100,
82.269,
],
},
"rect2": Object {
"point": Array [
500,
346.81,
],
"size": Array [
200,
164.54,
],
},
}
`;
exports[`transform command snapshot tests transforms edges 7`] = `
Object {
"height": 500,
"maxX": 700,
"maxY": 600,
"minX": 111.38,
"minY": 100,
"width": 588.62,
}
`;
exports[`transform command snapshot tests transforms edges 8`] = `
Object {
"rect1": Object {
"point": Array [
111.38,
100,
],
"size": Array [
98.104,
100,
],
},
"rect2": Object {
"point": Array [
503.79,
400,
],
"size": Array [
196.21,
200,
],
},
}
`;
exports[`transform command transforms from the bottom edge 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
100,
120,
],
},
"rect2": Object {
"point": Array [
500,
460,
],
"size": Array [
200,
240,
],
},
}
`;
exports[`transform command transforms from the bottom-left corner 1`] = `
Object {
"rect1": Object {
"point": Array [
200,
100,
],
"size": Array [
83.333,
120,
],
},
"rect2": Object {
"point": Array [
533.33,
460,
],
"size": Array [
166.67,
240,
],
},
}
`;
exports[`transform command transforms from the bottom-right corner 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
116.67,
120,
],
},
"rect2": Object {
"point": Array [
566.67,
460,
],
"size": Array [
233.33,
240,
],
},
}
`;
exports[`transform command transforms from the left edge 1`] = `
Object {
"rect1": Object {
"point": Array [
200,
100,
],
"size": Array [
83.333,
100,
],
},
"rect2": Object {
"point": Array [
533.33,
400,
],
"size": Array [
166.67,
200,
],
},
}
`;
exports[`transform command transforms from the right edge 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
116.67,
100,
],
},
"rect2": Object {
"point": Array [
566.67,
400,
],
"size": Array [
233.33,
200,
],
},
}
`;
exports[`transform command transforms from the top edge 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
200,
],
"size": Array [
100,
80,
],
},
"rect2": Object {
"point": Array [
500,
440,
],
"size": Array [
200,
160,
],
},
}
`;
exports[`transform command transforms from the top-left corner 1`] = `
Object {
"rect1": Object {
"point": Array [
200,
200,
],
"size": Array [
83.333,
80,
],
},
"rect2": Object {
"point": Array [
533.33,
440,
],
"size": Array [
166.67,
160,
],
},
}
`;
exports[`transform command transforms from the top-right corner 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
200,
],
"size": Array [
116.67,
80,
],
},
"rect2": Object {
"point": Array [
566.67,
440,
],
"size": Array [
233.33,
160,
],
},
}
`;
exports[`transform command when transforming from the bottom-right corner does command 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
116.67,
120,
],
},
"rect2": Object {
"point": Array [
566.67,
460,
],
"size": Array [
233.33,
240,
],
},
}
`;
exports[`transform command when transforming from the bottom-right corner re-does command 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
116.67,
120,
],
},
"rect2": Object {
"point": Array [
566.67,
460,
],
"size": Array [
233.33,
240,
],
},
}
`;
exports[`transform command when transforming from the bottom-right corner un-does command 1`] = `
Object {
"rect1": Object {
"point": Array [
100,
100,
],
"size": Array [
100,
100,
],
},
"rect2": Object {
"point": Array [
500,
400,
],
"size": Array [
200,
200,
],
},
}
`;

View file

@ -0,0 +1,40 @@
import TestState from '../test-utils'
describe('align command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when one item is selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
describe('when multiple items are selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
})

View file

@ -0,0 +1,21 @@
import TestState from '../test-utils'
describe('change page command', () => {
const tt = new TestState()
tt.resetDocumentState()
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})

View file

@ -0,0 +1,57 @@
import TestState from '../test-utils'
describe('delete page command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when last page is selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
describe('when first page is selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
describe('when project only has one page', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
})

View file

@ -0,0 +1,40 @@
import TestState from '../test-utils'
describe('delete-selected command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when one item is selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
describe('when multiple items are selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
})

View file

@ -1,7 +1,7 @@
import { ShapeType } from 'types'
import TestState, { rectangleId, arrowId } from './test-utils'
import TestState, { rectangleId, arrowId } from '../test-utils'
describe('deleting single shapes', () => {
describe('delete command', () => {
const tt = new TestState()
describe('deleting single shapes', () => {

View file

@ -0,0 +1,37 @@
import TestState from '../test-utils'
describe('distribute command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when one item is selected', () => {
it('does not change anything', () => {
// TODO
null
})
})
describe('when two items are selected', () => {
it('does not change anything', () => {
// TODO
null
})
})
describe('when three or more items are selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
})

View file

@ -0,0 +1,21 @@
import TestState from '../test-utils'
describe('draw command', () => {
const tt = new TestState()
tt.resetDocumentState()
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})

View file

@ -0,0 +1,40 @@
import TestState from '../test-utils'
describe('duplicate command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when one item is selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
describe('when multiple items are selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
})

View file

@ -0,0 +1,21 @@
import TestState from '../test-utils'
describe('edit command', () => {
const tt = new TestState()
tt.resetDocumentState()
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})

View file

@ -0,0 +1,21 @@
import TestState from '../test-utils'
describe('generate command', () => {
const tt = new TestState()
tt.resetDocumentState()
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})

View file

@ -0,0 +1,40 @@
import TestState from '../test-utils'
describe('group command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when one item is selected', () => {
it('does not change anything', () => {
// TODO
null
})
})
describe('when multiple items are selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
it('groups shapes with different parents', () => {
// TODO
null
})
it('does not group a parent group shape and its child', () => {
// TODO
null
})
})

View file

@ -0,0 +1,40 @@
import TestState from '../test-utils'
describe('move-to-page command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('when one item is selected', () => {
it('does not change anything', () => {
// TODO
null
})
})
describe('when multiple items are selected', () => {
it('does command', () => {
// TODO
null
})
it('un-does command', () => {
// TODO
null
})
it('re-does command', () => {
// TODO
null
})
})
it('reparents children of groups to page', () => {
// TODO
null
})
it('correctly preserves moved groups', () => {
// TODO
null
})
})

View file

@ -0,0 +1,347 @@
import { Corner, Edge, RectangleShape, ShapeType } from 'types'
import { rng } from 'utils'
import TestState from '../test-utils'
describe('transform command', () => {
const tt = new TestState()
tt.resetDocumentState()
.createShape(
{
type: ShapeType.Rectangle,
point: [100, 100],
size: [100, 100],
childIndex: 1,
},
'rect1'
)
.createShape(
{
type: ShapeType.Rectangle,
point: [500, 400],
size: [200, 200],
childIndex: 2,
},
'rect2'
)
.clickShape('rect1')
.clickShape('rect2', { shiftKey: true })
.save()
function getSnapInfo() {
return {
rect1: {
point: tt.getShape<RectangleShape>('rect1').point,
size: tt.getShape<RectangleShape>('rect1').size,
},
rect2: {
point: tt.getShape<RectangleShape>('rect2').point,
size: tt.getShape<RectangleShape>('rect2').size,
},
}
}
it('sets up initial bounds', () => {
expect(tt.selectedIds).toEqual(['rect1', 'rect2'])
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 700,
maxY: 600,
width: 600,
height: 500,
})
})
describe('when transforming from the bottom-right corner', () => {
it('does command', () => {
// Restore the saved data state, with the initial selection
tt.restore()
// Move the bounds handle
tt.startClickingBoundsHandle(Corner.BottomRight)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 800,
maxY: 700,
width: 700,
height: 600,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('un-does command', () => {
// Repeat the same actions, but add an undo at the end
tt.restore()
.startClickingBoundsHandle(Corner.BottomRight)
.movePointerBy([100, 100])
.stopClickingBounds()
.undo()
// Expect the bounds to be the initial bounds
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 700,
maxY: 600,
width: 600,
height: 500,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('re-does command', () => {
// Repeat the same actions but add an undo and a redo at the end
tt.restore()
.startClickingBoundsHandle(Corner.BottomRight)
.movePointerBy([100, 100])
.stopClickingBounds()
.undo()
.redo()
// Expect the bounds to be the transformed bounds
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 800,
maxY: 700,
width: 700,
height: 600,
})
expect(getSnapInfo()).toMatchSnapshot()
})
})
// From here on, let's assume that the undo and redos work as expected,
// so let's only test the command's execution.
it('transforms from the top edge', () => {
tt.restore()
.startClickingBoundsHandle(Edge.Top)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 200,
maxX: 700,
maxY: 600,
width: 600,
height: 400,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the right edge', () => {
tt.restore()
.startClickingBoundsHandle(Edge.Right)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 800,
maxY: 600,
width: 700,
height: 500,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the bottom edge', () => {
tt.restore()
.startClickingBoundsHandle(Edge.Bottom)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 700,
maxY: 700,
width: 600,
height: 600,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the left edge', () => {
tt.restore()
.startClickingBoundsHandle(Edge.Left)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 200,
minY: 100,
maxX: 700,
maxY: 600,
width: 500,
height: 500,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the top-left corner', () => {
tt.restore()
.startClickingBoundsHandle(Corner.TopLeft)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 200,
minY: 200,
maxX: 700,
maxY: 600,
width: 500,
height: 400,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the top-right corner', () => {
tt.restore()
.startClickingBoundsHandle(Corner.TopRight)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 200,
maxX: 800,
maxY: 600,
width: 700,
height: 400,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the bottom-right corner', () => {
tt.restore()
.startClickingBoundsHandle(Corner.BottomRight)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 100,
minY: 100,
maxX: 800,
maxY: 700,
width: 700,
height: 600,
})
expect(getSnapInfo()).toMatchSnapshot()
})
it('transforms from the bottom-left corner', () => {
tt.restore()
.startClickingBoundsHandle(Corner.BottomLeft)
.movePointerBy([100, 100])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchObject({
minX: 200,
minY: 100,
maxX: 700,
maxY: 700,
width: 500,
height: 600,
})
expect(getSnapInfo()).toMatchSnapshot()
})
describe('snapshot tests', () => {
it('transforms corners', () => {
const getRandom = rng('transform-tests-random-number-generator')
for (const corner of Object.values(Corner)) {
tt.restore()
.startClickingBoundsHandle(corner)
.movePointerBy([getRandom() * 200, getRandom() * 200])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchSnapshot()
expect(getSnapInfo()).toMatchSnapshot()
}
})
it('transforms edges', () => {
const getRandom = rng('transform-tests-random-number-generator')
for (const edge of Object.values(Edge)) {
tt.restore()
.startClickingBoundsHandle(edge)
.movePointerBy([getRandom() * 200, getRandom() * 200])
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchSnapshot()
expect(getSnapInfo()).toMatchSnapshot()
}
})
it('shift-transforms corners', () => {
const getRandom = rng('transform-tests-random-number-generator')
for (const corner of Object.values(Corner)) {
tt.restore()
.startClickingBoundsHandle(corner)
.movePointerBy([getRandom() * 200, getRandom() * 200], {
shiftKey: true,
})
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchSnapshot()
expect(getSnapInfo()).toMatchSnapshot()
}
})
it('shift-transforms edges', () => {
const getRandom = rng('transform-tests-random-number-generator')
for (const edge of Object.values(Edge)) {
tt.restore()
.startClickingBoundsHandle(edge)
.movePointerBy([getRandom() * 200, getRandom() * 200], {
shiftKey: true,
})
.stopClickingBounds()
// Ensure the bounds have been transformed
expect(tt.state.values.selectedBounds).toMatchSnapshot()
expect(getSnapInfo()).toMatchSnapshot()
}
})
})
})

View file

@ -1,11 +1,9 @@
import state from 'state'
import * as json from './__mocks__/document.json'
import TestState from '../test-utils'
state.reset()
state.send('MOUNTED').send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
state.send('CLEARED_PAGE')
describe('translate command', () => {
const tt = new TestState()
tt.resetDocumentState()
describe('translates shapes', () => {
it('translates a single selected shape', () => {
// TODO
null

View file

@ -1,6 +1,6 @@
import TestState from './test-utils'
describe('locked shapes', () => {
describe('lock command', () => {
const tt = new TestState()
tt.resetDocumentState()

View file

@ -2,8 +2,8 @@ import _state from 'state'
import tld from 'utils/tld'
import inputs from 'state/inputs'
import { createShape, getShapeUtils } from 'state/shape-utils'
import { Data, Shape, ShapeType, ShapeUtility } from 'types'
import { deepCompareArrays, setToArray, uniqueId, vec } from 'utils'
import { Corner, Data, Edge, Shape, ShapeType, ShapeUtility } from 'types'
import { deepClone, deepCompareArrays, uniqueId, vec } from 'utils'
import * as mockDocument from './__mocks__/document.json'
type State = typeof _state
@ -22,9 +22,12 @@ interface PointerOptions {
class TestState {
state: State
snapshot: Data
constructor() {
this.state = _state
this.state.send('TOGGLED_TEST_MODE')
this.snapshot = deepClone(this.state.data)
this.reset()
}
@ -57,7 +60,7 @@ class TestState {
*```
*/
resetDocumentState(): TestState {
this.state.send('RESET_DOCUMENT_STATE')
this.state.send('RESET_DOCUMENT_STATE').send('TOGGLED_TEST_MODE')
return this
}
@ -122,13 +125,13 @@ class TestState {
idsAreSelected(ids: string[], strict = true): boolean {
const selectedIds = tld.getSelectedIds(this.data)
return (
(strict ? selectedIds.size === ids.length : true) &&
ids.every((id) => selectedIds.has(id))
(strict ? selectedIds.length === ids.length : true) &&
ids.every((id) => selectedIds.includes(id))
)
}
get selectedIds(): string[] {
return setToArray(tld.getSelectedIds(this.data))
return tld.getSelectedIds(this.data)
}
/**
@ -340,6 +343,63 @@ class TestState {
return this
}
/**
* Start clicking bounds.
*
* ### Example
*
*```ts
* tt.startClickingBounds()
*```
*/
startClickingBounds(options: PointerOptions = {}): TestState {
this.state.send(
'POINTED_BOUNDS',
inputs.pointerDown(TestState.point(options), 'bounds')
)
return this
}
/**
* Stop clicking the bounding box.
*
* ### Example
*
*```ts
* tt.stopClickingBounds()
*```
*/
stopClickingBounds(options: PointerOptions = {}): TestState {
this.state.send(
'STOPPED_POINTING',
inputs.pointerUp(TestState.point(options), 'bounds')
)
return this
}
/**
* Start clicking a bounds handle.
*
* ### Example
*
*```ts
* tt.startClickingBoundsHandle(Edge.Top)
*```
*/
startClickingBoundsHandle(
handle: Corner | Edge | 'center',
options: PointerOptions = {}
): TestState {
this.state.send(
'POINTED_BOUNDS_HANDLE',
inputs.pointerDown(TestState.point(options), handle)
)
return this
}
/**
* Move the pointer to a new point, or to several points in order.
*
@ -572,6 +632,35 @@ class TestState {
return this
}
/**
* Save a snapshot of the state's current data.
*
* ### Example
*
*```ts
* tt.save()
*```
*/
save(): TestState {
this.snapshot = deepClone(this.data)
return this
}
/**
* Restore the state's saved data.
*
* ### Example
*
*```ts
* tt.save()
* tt.restore()
*```
*/
restore(): TestState {
this.state.forceData(this.snapshot)
return this
}
/**
* Get the state's current data.
*

View file

@ -1,91 +0,0 @@
import state from 'state'
import * as json from './__mocks__/document.json'
state.reset()
state.send('MOUNTED').send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
state.send('CLEARED_PAGE')
describe('transforms shapes', () => {
it('transforms from the top edge', () => {
// TODO
null
})
it('transforms from the right edge', () => {
// TODO
null
})
it('transforms from the bottom edge', () => {
// TODO
null
})
it('transforms from the left edge', () => {
// TODO
null
})
it('transforms from the top-left corner', () => {
// TODO
null
})
it('transforms from the top-right corner', () => {
// TODO
null
})
it('transforms from the bottom-right corner', () => {
// TODO
null
})
it('transforms from the bottom-left corner', () => {
// TODO
null
})
})
describe('transforms shapes while aspect-ratio locked', () => {
// Fixed
it('transforms from the top edge while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the right edge while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the bottom edge while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the left edge while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the top-left corner while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the top-right corner while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the bottom-right corner while aspect-ratio locked', () => {
// TODO
null
})
it('transforms from the bottom-left corner while aspect-ratio locked', () => {
// TODO
null
})
})

View file

@ -1840,14 +1840,6 @@ type RequiredKeys<T> = {
return Array.from(new Set(items).values())
}
/**
* Convert a set to an array.
* @param set
*/
static setToArray<T>(set: Set<T>): T[] {
return Array.from(set.values())
}
/**
* Get the outer of between a circle and a point.
* @param C The circle's center.

View file

@ -86,11 +86,11 @@ function ShapesFunctions() {
})
const hasSelection = useSelector((s) => {
return tld.getSelectedIds(s.data).size > 0
return tld.getSelectedIds(s.data).length > 0
})
const hasMultipleSelection = useSelector((s) => {
return tld.getSelectedIds(s.data).size > 1
return tld.getSelectedIds(s.data).length > 1
})
return (

View file

@ -7,13 +7,13 @@ import {
SizeStyle,
} from 'types'
import { createShape, getShapeUtils } from 'state/shape-utils'
import { setToArray, uniqueId } from 'utils'
import { uniqueId } from 'utils'
import Vec from 'utils/vec'
export const codeShapes = new Set<CodeShape<Shape>>([])
function getOrderedShapes() {
return setToArray(codeShapes).sort(
return Array.from(codeShapes.values()).sort(
(a, b) => a.shape.childIndex - b.shape.childIndex
)
}

View file

@ -1,5 +1,4 @@
import { Data } from 'types'
import { setToArray } from 'utils'
import tld from 'utils/tld'
/* ------------------ Command Class ----------------- */
@ -85,7 +84,7 @@ export class BaseCommand<T extends any> {
export default class Command extends BaseCommand<Data> {
saveSelectionState = (data: Data): ((next: Data) => void) => {
const { currentPageId } = data
const selectedIds = setToArray(tld.getSelectedIds(data))
const selectedIds = [...tld.getSelectedIds(data)]
return (next: Data) => {
next.currentPageId = currentPageId
next.hoveredId = undefined

View file

@ -54,7 +54,7 @@ function getSnapshot(data: Data) {
const pageState: PageState = {
id,
selectedIds: new Set([]),
selectedIds: [],
camera: {
point: [0, 0],
zoom: 1,

View file

@ -100,7 +100,7 @@ export default function groupCommand(data: Data): void {
getShapeUtils(oldParent).setProperty(
oldParent,
'children',
oldParent.children.filter((id) => !oldSelectedIds.has(id))
oldParent.children.filter((id) => !oldSelectedIds.includes(id))
)
}

View file

@ -1,7 +1,7 @@
import Command from './command'
import history from '../history'
import { Data } from 'types'
import { setToArray, uniqueArray } from 'utils'
import { uniqueArray } from 'utils'
import tld from 'utils/tld'
import { getShapeUtils } from 'state/shape-utils'
import storage from 'state/storage'
@ -9,7 +9,7 @@ import storage from 'state/storage'
export default function moveToPageCommand(data: Data, newPageId: string): void {
const { currentPageId: oldPageId } = data
const oldPage = tld.getPage(data)
const selectedIds = setToArray(tld.getSelectedIds(data))
const selectedIds = [...tld.getSelectedIds(data)]
const idsToMove = uniqueArray(
...selectedIds.flatMap((id) => tld.getDocumentBranch(data, id))
@ -59,7 +59,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void {
})
// Clear the current page state's selected ids
tld.getPageState(data).selectedIds.clear()
tld.setSelectedIds(data, [])
// Save the "from" page
storage.savePage(data, data.document.id, fromPageId)
@ -83,7 +83,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void {
})
// Select the selected ids on the new page
tld.getPageState(data).selectedIds = new Set(selectedIds)
tld.setSelectedIds(data, [...selectedIds])
// Move to the new page
data.currentPageId = toPageId
@ -113,7 +113,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void {
delete fromPage.shapes[shape.id]
})
tld.getPageState(data).selectedIds.clear()
tld.setSelectedIds(data, [])
storage.savePage(data, data.document.id, fromPageId)
@ -138,7 +138,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void {
}
})
tld.getPageState(data).selectedIds = new Set(selectedIds)
tld.setSelectedIds(data, [...selectedIds])
data.currentPageId = toPageId
},

View file

@ -1,14 +1,13 @@
import Command from './command'
import history from '../history'
import { Data, MoveType, Shape } from 'types'
import { setToArray } from 'utils'
import tld from 'utils/tld'
import { getShapeUtils } from 'state/shape-utils'
export default function moveCommand(data: Data, type: MoveType): void {
const page = tld.getPage(data)
const selectedIds = setToArray(tld.getSelectedIds(data))
const selectedIds = [...tld.getSelectedIds(data)]
const initialIndices = Object.fromEntries(
selectedIds.map((id) => [id, page.shapes[id].childIndex])

View file

@ -1,7 +1,7 @@
import Command from './command'
import history from '../history'
import { Data, Shape } from 'types'
import { getCommonBounds, setToArray } from 'utils'
import { getCommonBounds } from 'utils'
import tld from 'utils/tld'
import { uniqueId } from 'utils/utils'
import vec from 'utils/vec'
@ -26,7 +26,7 @@ export default function pasteCommand(data: Data, initialShapes: Shape[]): void {
initialShapes.map((shape) => [shape.id, uniqueId()])
)
const oldSelectedIds = setToArray(tld.getSelectedIds(data))
const oldSelectedIds = [...tld.getSelectedIds(data)]
history.execute(
data,

View file

@ -2,7 +2,7 @@ import Command from './command'
import history from '../history'
import { Data, ShapeStyles } from 'types'
import tld from 'utils/tld'
import { deepClone, setToArray } from 'utils'
import { deepClone } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
export default function styleCommand(
@ -11,7 +11,7 @@ export default function styleCommand(
): void {
const page = tld.getPage(data)
const selectedIds = setToArray(tld.getSelectedIds(data))
const selectedIds = [...tld.getSelectedIds(data)]
const shapesToStyle = selectedIds
.flatMap((id) => tld.getDocumentBranch(data, id))

View file

@ -1,5 +1,5 @@
import { DrawShape, PointerInfo } from 'types'
import { deepClone, setToArray } from 'utils'
import { deepClone } from 'utils'
import tld from 'utils/tld'
import { freeze } from 'immer'
import session from './session'
@ -56,7 +56,7 @@ export function fastDrawUpdate(info: PointerInfo): void {
info.shiftKey
)
const selectedId = setToArray(tld.getSelectedIds(data))[0]
const selectedId = [...tld.getSelectedIds(data)][0]
const { shapes } = data.document.pages[data.currentPageId]

View file

@ -2,7 +2,6 @@ import { Data } from 'types'
import clipboard from './clipboard'
import state from './state'
import { isDraft, current } from 'immer'
import { setToArray } from 'utils'
import tld from 'utils/tld'
import inputs from './inputs'
@ -59,9 +58,9 @@ class Logger {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.snapshotStart.pageStates[data.currentPageId].selectedIds = setToArray(
tld.getSelectedIds(data)
)
this.snapshotStart.pageStates[data.currentPageId].selectedIds = [
...tld.getSelectedIds(data),
]
return this
}
@ -84,9 +83,9 @@ class Logger {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.snapshotEnd.pageStates[data.currentPageId].selectedIds = setToArray(
tld.getSelectedIds(data)
)
this.snapshotEnd.pageStates[data.currentPageId].selectedIds = [
...tld.getSelectedIds(data),
]
// if (window.confirm('Stopped logging. Copy to clipboard?')) {
// this.copyToJson()
@ -138,9 +137,9 @@ class Logger {
this.isSimulating = true
try {
data.pageStates[data.currentPageId].selectedIds = new Set(
start.pageStates[start.currentPageId].selectedIds
)
data.pageStates[data.currentPageId].selectedIds = [
...start.pageStates[start.currentPageId].selectedIds,
]
state.send('RESET_DOCUMENT_STATE').forceData(start)

View file

@ -1,7 +1,7 @@
import { Bounds, Data, ShapeType } from 'types'
import BaseSession from './base-session'
import { getShapeUtils } from 'state/shape-utils'
import { deepClone, getBoundsFromPoints, setToArray } from 'utils'
import { deepClone, getBoundsFromPoints } from 'utils'
import vec from 'utils/vec'
import tld from 'utils/tld'
@ -24,10 +24,10 @@ export default class BrushSession extends BaseSession {
const hits = new Set<string>([])
const selectedIds = new Set(snapshot.selectedIds)
const selectedIds = [...snapshot.selectedIds]
for (const id in snapshot.shapeHitTests) {
if (selectedIds.has(id)) continue
if (selectedIds.includes(id)) continue
const { test, selectId } = snapshot.shapeHitTests[id]
if (!hits.has(selectId)) {
@ -35,11 +35,11 @@ export default class BrushSession extends BaseSession {
hits.add(selectId)
// When brushing a shape, select its top group parent.
if (!selectedIds.has(selectId)) {
selectedIds.add(selectId)
if (!selectedIds.includes(selectId)) {
selectedIds.push(selectId)
}
} else if (selectedIds.has(selectId)) {
selectedIds.delete(selectId)
} else if (selectedIds.includes(selectId)) {
selectedIds.splice(selectedIds.indexOf(selectId), 1)
}
}
}
@ -73,12 +73,15 @@ export function getBrushSnapshot(data: Data) {
.getShapes(cData)
.filter((shape) => shape.type !== ShapeType.Group && !shape.isHidden)
.filter(
(shape) => !(selectedIds.has(shape.id) || selectedIds.has(shape.parentId))
(shape) =>
!(
selectedIds.includes(shape.id) || selectedIds.includes(shape.parentId)
)
)
.map(deepClone)
return {
selectedIds: setToArray(selectedIds),
selectedIds: [...selectedIds],
shapeHitTests: Object.fromEntries(
shapesToTest.map((shape) => {
return [

View file

@ -133,22 +133,21 @@ const rectangle = registerShapeUtils<RectangleShape>({
transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) {
if (shape.rotation === 0 && !shape.isAspectRatioLocked) {
shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY]
shape.size = vec.round([bounds.width, bounds.height])
shape.point = vec.round([bounds.minX, bounds.minY])
} else {
shape.size = vec.mul(
initialShape.size,
Math.min(Math.abs(scaleX), Math.abs(scaleY))
shape.size = vec.round(
vec.mul(initialShape.size, Math.min(Math.abs(scaleX), Math.abs(scaleY)))
)
shape.point = [
shape.point = vec.round([
bounds.minX +
(bounds.width - shape.size[0]) *
(scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]),
bounds.minY +
(bounds.height - shape.size[1]) *
(scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
]
])
shape.rotation =
(scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
@ -160,8 +159,8 @@ const rectangle = registerShapeUtils<RectangleShape>({
},
transformSingle(shape, bounds) {
shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY]
shape.size = vec.round([bounds.width, bounds.height])
shape.point = vec.round([bounds.minX, bounds.minY])
return this
},
})

View file

@ -13,7 +13,6 @@ import {
getCommonBounds,
rotateBounds,
getBoundsCenter,
setToArray,
deepClone,
pointInBounds,
uniqueId,
@ -43,6 +42,7 @@ const initialData: Data = {
isReadOnly: false,
settings: {
fontSize: 13,
isTestMode: false,
isDarkMode: false,
isCodeOpen: false,
isDebugMode: false,
@ -133,7 +133,7 @@ for (let i = 0; i < count; i++) {
pageStates: {
page1: {
id: 'page1',
selectedIds: new Set([]),
selectedIds: [],
camera: {
point: [0, 0],
zoom: 1,
@ -147,6 +147,7 @@ const state = createState({
on: {
TOGGLED_DEBUG_PANEL: 'toggleDebugPanel',
TOGGLED_DEBUG_MODE: 'toggleDebugMode',
TOGGLED_TEST_MODE: 'toggleTestMode',
TOGGLED_LOGGER: 'toggleLogger',
COPIED_DEBUG_LOG: 'copyDebugLog',
LOADED_FROM_SNAPSHOT: {
@ -588,7 +589,10 @@ const state = createState({
onEnter: 'startTransformSession',
onExit: 'completeSession',
on: {
// MOVED_POINTER: 'updateTransformSession', using hacks.fastTransform
MOVED_POINTER: {
ifAny: ['isSimulating', 'isTestMode'],
do: 'updateTransformSession',
},
PANNED_CAMERA: 'updateTransformSession',
PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
@ -638,7 +642,7 @@ const state = createState({
'startBrushSession',
],
on: {
// MOVED_POINTER: 'updateBrushSession', using hacks.fastBrushSelect
MOVED_POINTER: { if: 'isTestMode', do: 'updateBrushSession' },
PANNED_CAMERA: 'updateBrushSession',
STOPPED_POINTING: { to: 'selecting' },
STARTED_PINCHING: { to: 'pinching' },
@ -694,7 +698,7 @@ const state = createState({
},
pinching: {
on: {
// PINCHED: { do: 'pinchCamera' }, using hacks.fastPinchCamera
PINCHED: { if: 'isTestMode', do: 'pinchCamera' },
},
initial: 'selectPinching',
onExit: { secretlyDo: 'updateZoomCSS' },
@ -760,7 +764,7 @@ const state = createState({
RELEASED_SHIFT: 'keyUpdateDrawSession',
PANNED_CAMERA: 'updateDrawSession',
MOVED_POINTER: {
if: 'isSimulating',
ifAny: ['isSimulating', 'isTestMode'],
do: 'updateDrawSession',
},
},
@ -1115,6 +1119,9 @@ const state = createState({
isSimulating() {
return logger.isSimulating
},
isTestMode(data) {
return data.settings.isTestMode
},
isEditingShape(data, payload: { id: string }) {
return payload.id === data.editingId
},
@ -1131,7 +1138,7 @@ const state = createState({
return tld.getShape(data, payload.target)?.type === ShapeType.Text
},
isPointingBounds(data, payload: PointerInfo) {
return tld.getSelectedIds(data).size > 0 && payload.target === 'bounds'
return tld.getSelectedIds(data).length > 0 && payload.target === 'bounds'
},
isPointingShape(data, payload: PointerInfo) {
return (
@ -1156,7 +1163,7 @@ const state = createState({
return payload.target !== undefined
},
isPointedShapeSelected(data) {
return tld.getSelectedIds(data).has(data.pointedId)
return tld.getSelectedIds(data).includes(data.pointedId)
},
isPressingShiftKey(data, payload: PointerInfo) {
return payload.shiftKey
@ -1192,13 +1199,13 @@ const state = createState({
return payload.target === 'rotate'
},
hasSelection(data) {
return tld.getSelectedIds(data).size > 0
return tld.getSelectedIds(data).length > 0
},
hasSingleSelection(data) {
return tld.getSelectedIds(data).size === 1
return tld.getSelectedIds(data).length === 1
},
hasMultipleSelection(data) {
return tld.getSelectedIds(data).size > 1
return tld.getSelectedIds(data).length > 1
},
hasCurrentParentShape(data) {
return data.currentParentId !== data.currentPageId
@ -1230,6 +1237,9 @@ const state = createState({
toggleDebugMode(data) {
data.settings.isDebugMode = !data.settings.isDebugMode
},
toggleTestMode(data) {
data.settings.isTestMode = !data.settings.isTestMode
},
toggleDebugPanel(data) {
data.settings.isDebugOpen = !data.settings.isDebugOpen
},
@ -1304,7 +1314,7 @@ const state = createState({
data.pageStates = {
[newPageId]: {
id: newPageId,
selectedIds: new Set(),
selectedIds: [],
camera: {
point: [0, 0],
zoom: 1,
@ -1464,7 +1474,7 @@ const state = createState({
// Handles
doublePointHandle(data, payload: PointerInfo) {
const id = setToArray(tld.getSelectedIds(data))[0]
const id = tld.getSelectedIds(data)[0]
commands.doublePointHandle(data, id, payload)
},
@ -1511,7 +1521,7 @@ const state = createState({
) {
const point = tld.screenToWorld(inputs.pointer.origin, data)
session.begin(
tld.getSelectedIds(data).size === 1
tld.getSelectedIds(data).length === 1
? new Sessions.TransformSingleSession(data, payload.target, point)
: new Sessions.TransformSession(data, payload.target, point)
)
@ -1618,7 +1628,7 @@ const state = createState({
inputs.clear()
},
deselectAll(data) {
tld.getSelectedIds(data).clear()
tld.setSelectedIds(data, [])
},
selectAll(data) {
tld.setSelectedIds(
@ -1673,10 +1683,10 @@ const state = createState({
pullPointedIdFromSelectedIds(data) {
const { pointedId } = data
const selectedIds = tld.getSelectedIds(data)
selectedIds.delete(pointedId)
selectedIds.splice(selectedIds.indexOf(pointedId), 1)
},
pushPointedIdToSelectedIds(data) {
tld.getSelectedIds(data).add(data.pointedId)
tld.getSelectedIds(data).push(data.pointedId)
},
moveSelection(data, payload: { type: MoveType }) {
commands.move(data, payload.type)
@ -1732,7 +1742,7 @@ const state = createState({
data.editingId = selectedShape.id
}
tld.getPageState(data).selectedIds = new Set([selectedShape.id])
tld.getPageState(data).selectedIds = [selectedShape.id]
},
clearEditingId(data) {
data.editingId = null
@ -2094,7 +2104,7 @@ const state = createState({
},
values: {
selectedIds(data) {
return setToArray(tld.getSelectedIds(data))
return tld.getSelectedIds(data)
},
selectedBounds(data) {
return getSelectionBounds(data)
@ -2107,7 +2117,7 @@ const state = createState({
.sort((a, b) => a.childIndex - b.childIndex)
},
selectedStyle(data) {
const selectedIds = setToArray(tld.getSelectedIds(data))
const selectedIds = tld.getSelectedIds(data)
const { currentStyle } = data
if (selectedIds.length === 0) {
@ -2195,9 +2205,9 @@ function getSelectionBounds(data: Data) {
const shapes = tld.getSelectedShapes(data)
if (selectedIds.size === 0) return null
if (selectedIds.length === 0) return null
if (selectedIds.size === 1) {
if (selectedIds.length === 1) {
if (!shapes[0]) {
console.warn('Could not find that shape! Clearing selected IDs.')
tld.setSelectedIds(data, [])

View file

@ -1,5 +1,5 @@
import { Data, PageState, TLDocument } from 'types'
import { decompress, compress, setToArray } from 'utils'
import { decompress, compress } from 'utils'
import state from './state'
import { uniqueId } from 'utils/utils'
import * as idb from 'idb-keyval'
@ -132,14 +132,11 @@ class Storage {
if (savedPageState !== null) {
// If we've found a page state in local storage, set it into state.
data.pageStates[pageId] = JSON.parse(decompress(savedPageState))
data.pageStates[pageId].selectedIds = new Set(
data.pageStates[pageId].selectedIds
)
} else {
// Or else create a new one.
data.pageStates[pageId] = {
id: pageId,
selectedIds: new Set([]),
selectedIds: [],
camera: {
point: [0, 0],
zoom: 1,
@ -161,13 +158,13 @@ class Storage {
throw new Error('Page state id not in document')
}
pageState.selectedIds = new Set([])
pageState.selectedIds = []
data.pageStates[pageState.id] = pageState
data.currentPageId = pageState.id
} catch (e) {
data.pageStates[data.currentPageId] = {
id: data.currentPageId,
selectedIds: new Set([]),
selectedIds: [],
camera: {
point: [0, 0],
zoom: 1,
@ -249,7 +246,7 @@ class Storage {
storageId(fileId, 'pageState', pageId),
JSON.stringify({
...currentPageState,
selectedIds: setToArray(currentPageState.selectedIds),
selectedIds: [...currentPageState.selectedIds],
})
)
}
@ -286,7 +283,6 @@ class Storage {
// If we have a page, move it into state
const restored: PageState = JSON.parse(savedPageState)
data.pageStates[pageId] = restored
data.pageStates[pageId].selectedIds = new Set(restored.selectedIds)
} else {
data.pageStates[pageId] = {
id: pageId,
@ -294,7 +290,7 @@ class Storage {
point: [0, 0],
zoom: 1,
},
selectedIds: new Set([]),
selectedIds: [],
}
}

View file

@ -8,6 +8,7 @@ export interface Data {
fontSize: number
isDarkMode: boolean
isCodeOpen: boolean
isTestMode: boolean
isDebugOpen: boolean
isDebugMode: boolean
isStyleOpen: boolean
@ -66,7 +67,7 @@ export interface Page {
export interface PageState {
id: string
selectedIds: Set<string>
selectedIds: string[]
camera: {
point: number[]
zoom: number

View file

@ -1,4 +1,4 @@
import { clamp, deepClone, getCommonBounds, setToArray } from 'utils'
import { clamp, deepClone, getCommonBounds } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
import vec from './vec'
import {
@ -98,7 +98,7 @@ export default class StateUtils {
*/
static getSelectedShapes(data: Data): Shape[] {
const page = this.getPage(data)
const ids = setToArray(this.getSelectedIds(data))
const ids = this.getSelectedIds(data)
return ids.map((id) => page.shapes[id])
}
@ -306,12 +306,12 @@ export default class StateUtils {
]
}
static getSelectedIds(data: Data): Set<string> {
static getSelectedIds(data: Data): string[] {
return data.pageStates[data.currentPageId].selectedIds
}
static setSelectedIds(data: Data, ids: string[]): Set<string> {
data.pageStates[data.currentPageId].selectedIds = new Set(ids)
static setSelectedIds(data: Data, ids: string[]): string[] {
data.pageStates[data.currentPageId].selectedIds = [...ids]
return data.pageStates[data.currentPageId].selectedIds
}
@ -347,7 +347,7 @@ export default class StateUtils {
>(data: Data, fn?: F): (Shape | K)[] {
const page = this.getPage(data)
const copies = setToArray(this.getSelectedIds(data))
const copies = this.getSelectedIds(data)
.flatMap((id) =>
this.getDocumentBranch(data, id).map((id) => page.shapes[id])
)

View file

@ -1572,14 +1572,6 @@ export function uniqueArray<T extends string | number>(...items: T[]): T[] {
return Array.from(new Set(items).values())
}
/**
* Convert a set to an array.
* @param set
*/
export function setToArray<T>(set: Set<T>): T[] {
return Array.from(set.values())
}
/* -------------------------------------------------- */
/* Browser and DOM */
/* -------------------------------------------------- */