[refactor] reordering shapes (#1718)
This PR: - adds tests for shape reordering - removes `Editor.getParentsMappedToChildren` - removes `Editor.reorderShapes` - moves reordering shapes code into its own file, outside of the editor ### Change Type - [x] `major` — Breaking change (if you were using those APIs) ### Release Notes - [api] removes `Editor.getParentsMappedToChildren` - [api] removes `Editor.reorderShapes` - [api] moves reordering shapes code into its own file, outside of the editor
This commit is contained in:
parent
85db9ba469
commit
103809b83e
5 changed files with 1150 additions and 219 deletions
|
@ -503,7 +503,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getPageTransformById(id: TLShapeId): Matrix2d | undefined;
|
||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']): TLPageId | TLShapeId;
|
||||
getParentShape(shape?: TLShape): TLShape | undefined;
|
||||
getParentsMappedToChildren(ids: TLShapeId[]): Map<TLParentId, Set<TLShape>>;
|
||||
getParentTransform(shape: TLShape): Matrix2d;
|
||||
getPointInParentSpace(shapeId: TLShapeId, point: VecLike): Vec2d;
|
||||
getPointInShapeSpace(shape: TLShape, point: VecLike): Vec2d;
|
||||
|
@ -631,7 +630,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
isInViewport: boolean;
|
||||
maskedPageBounds: Box2d | undefined;
|
||||
}[];
|
||||
reorderShapes(operation: 'backward' | 'forward' | 'toBack' | 'toFront', ids: TLShapeId[]): this;
|
||||
reparentShapesById(ids: TLShapeId[], parentId: TLParentId, insertIndex?: string): this;
|
||||
replaceStoreContentsWithRecordsForOtherDocument(records: TLRecord[]): void;
|
||||
resetZoom(point?: Vec2d, opts?: TLAnimationOptions): this;
|
||||
|
|
|
@ -111,6 +111,7 @@ import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/Sh
|
|||
import { WeakMapCache } from '../utils/WeakMapCache'
|
||||
import { dataUrlToFile } from '../utils/assets'
|
||||
import { getIncrementedName, uniqueId } from '../utils/data'
|
||||
import { getReorderingShapesChanges } from '../utils/reorderShapes'
|
||||
import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
|
||||
import { arrowBindingsIndex } from './derivations/arrowBindingsIndex'
|
||||
import { parentsToChildrenWithIndexes } from './derivations/parentsToChildrenWithIndexes'
|
||||
|
@ -5148,31 +5149,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this.getDeltaInShapeSpace(parent, delta)
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given set of ids, get a map containing the ids of their parents and the children of those
|
||||
* parents.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.getParentsMappedToChildren(['id1', 'id2', 'id3'])
|
||||
* ```
|
||||
*
|
||||
* @param ids - The ids to get the parents and children of.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getParentsMappedToChildren(ids: TLShapeId[]) {
|
||||
const shapes = ids.map((id) => this.store.get(id)!)
|
||||
const parents = new Map<TLParentId, Set<TLShape>>()
|
||||
shapes.forEach((shape) => {
|
||||
if (!parents.has(shape.parentId)) {
|
||||
parents.set(shape.parentId, new Set())
|
||||
}
|
||||
parents.get(shape.parentId)?.add(shape)
|
||||
})
|
||||
return parents
|
||||
}
|
||||
|
||||
/**
|
||||
* An array containing all of the shapes in the current page.
|
||||
*
|
||||
|
@ -6014,179 +5990,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder shapes.
|
||||
*
|
||||
* @param operation - The operation to perform.
|
||||
* @param ids - The ids to reorder.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
reorderShapes(operation: 'toBack' | 'toFront' | 'forward' | 'backward', ids: TLShapeId[]) {
|
||||
if (this.isReadOnly) return this
|
||||
if (ids.length === 0) return this
|
||||
// this.emit('reorder-shapes', { pageId: this.currentPageId, ids, operation })
|
||||
|
||||
const parents = this.getParentsMappedToChildren(ids)
|
||||
|
||||
const changes: TLShapePartial[] = []
|
||||
|
||||
switch (operation) {
|
||||
case 'toBack': {
|
||||
parents.forEach((movingSet, parentId) => {
|
||||
const siblings = compact(
|
||||
this.getSortedChildIds(parentId).map((id) => this.getShapeById(id))
|
||||
)
|
||||
|
||||
if (movingSet.size === siblings.length) return
|
||||
|
||||
let below: string | undefined
|
||||
let above: string | undefined
|
||||
|
||||
for (const shape of siblings) {
|
||||
if (!movingSet.has(shape)) {
|
||||
above = shape.index
|
||||
break
|
||||
}
|
||||
movingSet.delete(shape)
|
||||
below = shape.index
|
||||
}
|
||||
|
||||
if (movingSet.size === 0) return
|
||||
|
||||
const indices = getIndicesBetween(below, above, movingSet.size)
|
||||
|
||||
Array.from(movingSet.values())
|
||||
.sort(sortByIndex)
|
||||
.forEach((node, i) =>
|
||||
changes.push({ id: node.id as any, type: node.type, index: indices[i] })
|
||||
)
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'toFront': {
|
||||
parents.forEach((movingSet, parentId) => {
|
||||
const siblings = compact(
|
||||
this.getSortedChildIds(parentId).map((id) => this.getShapeById(id))
|
||||
)
|
||||
const len = siblings.length
|
||||
|
||||
if (movingSet.size === len) return
|
||||
|
||||
let below: string | undefined
|
||||
let above: string | undefined
|
||||
|
||||
for (let i = len - 1; i > -1; i--) {
|
||||
const shape = siblings[i]
|
||||
|
||||
if (!movingSet.has(shape)) {
|
||||
below = shape.index
|
||||
break
|
||||
}
|
||||
|
||||
movingSet.delete(shape)
|
||||
above = shape.index
|
||||
}
|
||||
|
||||
if (movingSet.size === 0) return
|
||||
|
||||
const indices = getIndicesBetween(below, above, movingSet.size)
|
||||
|
||||
Array.from(movingSet.values())
|
||||
.sort(sortByIndex)
|
||||
.forEach((node, i) =>
|
||||
changes.push({ id: node.id as any, type: node.type, index: indices[i] })
|
||||
)
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'forward': {
|
||||
parents.forEach((movingSet, parentId) => {
|
||||
const siblings = compact(
|
||||
this.getSortedChildIds(parentId).map((id) => this.getShapeById(id))
|
||||
)
|
||||
const len = siblings.length
|
||||
|
||||
if (movingSet.size === len) return
|
||||
|
||||
const movingIndices = new Set(Array.from(movingSet).map((n) => siblings.indexOf(n)))
|
||||
|
||||
let selectIndex = -1
|
||||
let isSelecting = false
|
||||
let below: string | undefined
|
||||
let above: string | undefined
|
||||
let count: number
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const isMoving = movingIndices.has(i)
|
||||
|
||||
if (!isSelecting && isMoving) {
|
||||
isSelecting = true
|
||||
selectIndex = i
|
||||
above = undefined
|
||||
} else if (isSelecting && !isMoving) {
|
||||
isSelecting = false
|
||||
count = i - selectIndex
|
||||
below = siblings[i].index
|
||||
above = siblings[i + 1]?.index
|
||||
|
||||
const indices = getIndicesBetween(below, above, count)
|
||||
|
||||
for (let k = 0; k < count; k++) {
|
||||
const node = siblings[selectIndex + k]
|
||||
changes.push({ id: node.id as any, type: node.type, index: indices[k] })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'backward': {
|
||||
parents.forEach((movingSet, parentId) => {
|
||||
const siblings = compact(
|
||||
this.getSortedChildIds(parentId).map((id) => this.getShapeById(id))
|
||||
)
|
||||
const len = siblings.length
|
||||
|
||||
if (movingSet.size === len) return
|
||||
|
||||
const movingIndices = new Set(Array.from(movingSet).map((n) => siblings.indexOf(n)))
|
||||
|
||||
let selectIndex = -1
|
||||
let isSelecting = false
|
||||
let count: number
|
||||
|
||||
for (let i = len - 1; i > -1; i--) {
|
||||
const isMoving = movingIndices.has(i)
|
||||
|
||||
if (!isSelecting && isMoving) {
|
||||
isSelecting = true
|
||||
selectIndex = i
|
||||
} else if (isSelecting && !isMoving) {
|
||||
isSelecting = false
|
||||
count = selectIndex - i
|
||||
|
||||
const indices = getIndicesBetween(siblings[i - 1]?.index, siblings[i].index, count)
|
||||
|
||||
for (let k = 0; k < count; k++) {
|
||||
const node = siblings[i + k + 1]
|
||||
changes.push({ id: node.id as any, type: node.type, index: indices[k] })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.updateShapes(changes)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Send shapes to the back of the page's object list.
|
||||
*
|
||||
|
@ -6201,7 +6004,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
sendToBack(ids = this.pageState.selectedIds) {
|
||||
this.reorderShapes('toBack', ids)
|
||||
const changes = getReorderingShapesChanges(this, 'toBack', ids)
|
||||
if (changes) this.updateShapes(changes)
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -6219,7 +6023,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
sendBackward(ids = this.pageState.selectedIds) {
|
||||
this.reorderShapes('backward', ids)
|
||||
const changes = getReorderingShapesChanges(this, 'backward', ids)
|
||||
if (changes) this.updateShapes(changes)
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -6237,7 +6042,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
bringForward(ids = this.pageState.selectedIds) {
|
||||
this.reorderShapes('forward', ids)
|
||||
const changes = getReorderingShapesChanges(this, 'forward', ids)
|
||||
if (changes) this.updateShapes(changes)
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -6255,7 +6061,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
bringToFront(ids = this.pageState.selectedIds) {
|
||||
this.reorderShapes('toFront', ids)
|
||||
const changes = getReorderingShapesChanges(this, 'toFront', ids)
|
||||
if (changes) this.updateShapes(changes)
|
||||
return this
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ it('lists shapes in viewport sorted by id with correct indexes & background inde
|
|||
])
|
||||
|
||||
// Send B to the back
|
||||
editor.reorderShapes('toBack', [ids.B])
|
||||
editor.sendToBack([ids.B])
|
||||
|
||||
// The items should still be sorted by id
|
||||
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
|
||||
|
|
|
@ -1,25 +1,918 @@
|
|||
// import { TestEditor } from '../TestEditor'
|
||||
import { TLShapeId, createShapeId } from '@tldraw/tlschema'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
// let editor: TestEditor
|
||||
let editor: TestEditor
|
||||
|
||||
// beforeEach(() => {
|
||||
// editor =new TestEditor()
|
||||
// })
|
||||
function expectShapesInOrder(editor: TestEditor, ...ids: TLShapeId[]) {
|
||||
expect(editor.sortedShapesArray.map((shape) => shape.id)).toMatchObject(ids)
|
||||
}
|
||||
|
||||
describe('Send to Back', () => {
|
||||
it.todo('Reorders shapes to the back of the list')
|
||||
function getSiblingBelow(editor: TestEditor, id: TLShapeId) {
|
||||
const shape = editor.getShapeById(id)!
|
||||
const siblings = editor.getSortedChildIds(shape.parentId)
|
||||
const index = siblings.indexOf(id)
|
||||
return siblings[index - 1]
|
||||
}
|
||||
|
||||
function getSiblingAbove(editor: TestEditor, id: TLShapeId) {
|
||||
const shape = editor.getShapeById(id)!
|
||||
const siblings = editor.getSortedChildIds(shape.parentId)
|
||||
const index = siblings.indexOf(id)
|
||||
return siblings[index + 1]
|
||||
}
|
||||
|
||||
const ids = {
|
||||
A: createShapeId('A'),
|
||||
B: createShapeId('B'),
|
||||
C: createShapeId('C'),
|
||||
D: createShapeId('D'),
|
||||
E: createShapeId('E'),
|
||||
F: createShapeId('F'),
|
||||
G: createShapeId('G'),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
editor?.dispose()
|
||||
editor = new TestEditor()
|
||||
editor.createShapes([
|
||||
{
|
||||
id: ids['A'],
|
||||
type: 'geo',
|
||||
},
|
||||
{
|
||||
id: ids['B'],
|
||||
type: 'geo',
|
||||
},
|
||||
{
|
||||
id: ids['C'],
|
||||
type: 'geo',
|
||||
},
|
||||
{
|
||||
id: ids['D'],
|
||||
type: 'geo',
|
||||
},
|
||||
{
|
||||
id: ids['E'],
|
||||
type: 'geo',
|
||||
},
|
||||
{
|
||||
id: ids['F'],
|
||||
type: 'geo',
|
||||
},
|
||||
{
|
||||
id: ids['G'],
|
||||
type: 'geo',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
describe('Send backward', () => {
|
||||
it.todo('Reorders shapes backward in the list')
|
||||
describe('When running zindex tests', () => {
|
||||
it('Correctly initializes indices', () => {
|
||||
expect(editor.sortedShapesArray.map((shape) => shape.index)).toMatchObject([
|
||||
'a1',
|
||||
'a2',
|
||||
'a3',
|
||||
'a4',
|
||||
'a5',
|
||||
'a6',
|
||||
'a7',
|
||||
])
|
||||
})
|
||||
|
||||
describe('Bring forward', () => {
|
||||
it.todo('Reorders shapes forward in the list')
|
||||
it('Correctly identifies shape orders', () => {
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Bring to Front', () => {
|
||||
it.todo('Reorders shapes to the front of the list')
|
||||
describe('editor.getSiblingAbove', () => {
|
||||
it('Gets the correct shape above', () => {
|
||||
expect(getSiblingAbove(editor, ids['B'])).toBe(ids['C'])
|
||||
expect(getSiblingAbove(editor, ids['C'])).toBe(ids['D'])
|
||||
expect(getSiblingAbove(editor, ids['G'])).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it.todo('Does and undoes')
|
||||
describe('editor.getSiblingAbove', () => {
|
||||
it('Gets the correct shape above', () => {
|
||||
expect(getSiblingBelow(editor, ids['A'])).toBeUndefined()
|
||||
expect(getSiblingBelow(editor, ids['B'])).toBe(ids['A'])
|
||||
expect(getSiblingBelow(editor, ids['C'])).toBe(ids['B'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('When sending to back', () => {
|
||||
it('Moves one shape to back', () => {
|
||||
editor.sendToBack([ids['D']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['D'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendToBack([ids['D']]) // noop
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['D'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves no shapes when selecting shapes at the back', () => {
|
||||
editor.sendToBack([ids['A'], ids['B'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendToBack([ids['A'], ids['B'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves two adjacent shapes to back', () => {
|
||||
editor.sendToBack([ids['D'], ids['E']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendToBack([ids['D'], ids['E']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes to back', () => {
|
||||
editor.sendToBack([ids['E'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['E'],
|
||||
ids['G'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F']
|
||||
)
|
||||
editor.sendToBack([ids['E'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['E'],
|
||||
ids['G'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes to back when one is at the back', () => {
|
||||
editor.sendToBack([ids['A'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['G'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F']
|
||||
)
|
||||
editor.sendToBack([ids['A'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['G'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('When sending to front', () => {
|
||||
it('Moves one shape to front', () => {
|
||||
editor.bringToFront([ids['A']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['A']
|
||||
)
|
||||
editor.bringToFront([ids['A']]) // noop
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['A']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves no shapes when selecting shapes at the front', () => {
|
||||
editor.bringToFront([ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringToFront([ids['G']]) // noop
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves two adjacent shapes to front', () => {
|
||||
editor.bringToFront([ids['D'], ids['E']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['D'],
|
||||
ids['E']
|
||||
)
|
||||
editor.bringToFront([ids['D'], ids['E']]) // noop
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['D'],
|
||||
ids['E']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes to front', () => {
|
||||
editor.bringToFront([ids['A'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['A'],
|
||||
ids['C']
|
||||
)
|
||||
editor.bringToFront([ids['A'], ids['C']]) // noop
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['A'],
|
||||
ids['C']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes to front when one is at the front', () => {
|
||||
editor.bringToFront([ids['E'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['E'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringToFront([ids['E'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['E'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('When sending backward', () => {
|
||||
it('Moves one shape backward', () => {
|
||||
editor.sendBackward([ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['C'],
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['C'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves shapes to the first position', () => {
|
||||
editor.sendBackward([ids['B']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['A'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['A']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['B']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['A'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves two shapes to the first position', () => {
|
||||
editor.sendBackward([ids['B'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['A'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['C'], ids['A']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['C'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['A'], ids['B']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves no shapes when sending shapes at the back', () => {
|
||||
editor.sendBackward([ids['A'], ids['B'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['A'], ids['B'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves two adjacent shapes backward', () => {
|
||||
editor.sendBackward([ids['D'], ids['E']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves two adjacent shapes backward when one is at the back', () => {
|
||||
editor.sendBackward([ids['A'], ids['E']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['E'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['A'], ids['E']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['E'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes backward', () => {
|
||||
editor.sendBackward([ids['E'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['E'],
|
||||
ids['D'],
|
||||
ids['G'],
|
||||
ids['F']
|
||||
)
|
||||
editor.sendBackward([ids['E'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['E'],
|
||||
ids['C'],
|
||||
ids['G'],
|
||||
ids['D'],
|
||||
ids['F']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes backward when one is at the back', () => {
|
||||
editor.sendBackward([ids['A'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['G'],
|
||||
ids['F']
|
||||
)
|
||||
editor.sendBackward([ids['A'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['G'],
|
||||
ids['E'],
|
||||
ids['F']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes to backward when both are at the back', () => {
|
||||
editor.sendBackward([ids['A'], ids['B']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.sendBackward([ids['A'], ids['B']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('When moving forward', () => {
|
||||
it('Moves one shape forward', () => {
|
||||
editor.bringForward([ids['A']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['A'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringForward([ids['A']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['A'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves no shapes when sending shapes at the front', () => {
|
||||
editor.bringForward([ids['E'], ids['F'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringForward([ids['E'], ids['F'], ids['G']]) // noop
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves two adjacent shapes forward', () => {
|
||||
editor.bringForward([ids['C'], ids['D']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['E'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringForward([ids['C'], ids['D']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes forward', () => {
|
||||
editor.bringForward([ids['A'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['A'],
|
||||
ids['D'],
|
||||
ids['C'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringForward([ids['A'], ids['C']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['A'],
|
||||
ids['E'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves non-adjacent shapes to forward when one is at the front', () => {
|
||||
editor.bringForward([ids['C'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['C'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringForward([ids['C'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
|
||||
it('Moves adjacent shapes to forward when both are at the front', () => {
|
||||
editor.bringForward([ids['F'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
editor.bringForward([ids['F'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Edges
|
||||
|
||||
describe('Edge cases...', () => {
|
||||
it('When bringing forward, does not increment order if shapes at at the top', () => {
|
||||
editor.bringForward([ids['F'], ids['G']])
|
||||
})
|
||||
it('When bringing forward, does not increment order with non-adjacent shapes if shapes at at the top', () => {
|
||||
editor.bringForward([ids['E'], ids['G']])
|
||||
})
|
||||
|
||||
it('When bringing to front, does not change order of shapes already at top', () => {
|
||||
editor.bringToFront([ids['E'], ids['G']])
|
||||
})
|
||||
|
||||
it('When sending to back, does not change order of shapes already at bottom', () => {
|
||||
editor.sendToBack([ids['A'], ids['C']])
|
||||
})
|
||||
|
||||
it('When moving back to front...', () => {
|
||||
editor.sendBackward([ids['F'], ids['G']])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['E']
|
||||
)
|
||||
|
||||
editor.sendBackward([ids['F'], ids['G']])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['D'],
|
||||
ids['E']
|
||||
)
|
||||
|
||||
editor.sendBackward([ids['F'], ids['G']])
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E']
|
||||
)
|
||||
|
||||
editor.sendBackward([ids['F'], ids['G']])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E']
|
||||
)
|
||||
|
||||
editor.sendBackward([ids['F'], ids['G']])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E']
|
||||
)
|
||||
|
||||
editor
|
||||
.bringForward([ids['F'], ids['G']])
|
||||
.bringForward([ids['F'], ids['G']])
|
||||
.bringForward([ids['F'], ids['G']])
|
||||
.bringForward([ids['F'], ids['G']])
|
||||
.bringForward([ids['F'], ids['G']])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('When undoing and redoing...', () => {
|
||||
it('Undoes and redoes', () => {
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
|
||||
editor.mark()
|
||||
editor.sendBackward([ids['F'], ids['G']])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['F'],
|
||||
ids['G'],
|
||||
ids['E']
|
||||
)
|
||||
|
||||
editor.undo()
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['B'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
// .redo()
|
||||
// .expectShapesInOrder(ids['A'], ids['B'], ids['C'], ids['D'], ids['F'], ids['G'], ids['E'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('When shapes are parented...', () => {
|
||||
it('Sorted correctly by pageIndex', () => {
|
||||
editor.reparentShapesById([ids['C']], ids['A']).reparentShapesById([ids['B']], ids['D'])
|
||||
|
||||
expectShapesInOrder(
|
||||
editor,
|
||||
ids['A'],
|
||||
ids['C'],
|
||||
ids['D'],
|
||||
ids['B'],
|
||||
ids['E'],
|
||||
ids['F'],
|
||||
ids['G']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
233
packages/editor/src/lib/utils/reorderShapes.ts
Normal file
233
packages/editor/src/lib/utils/reorderShapes.ts
Normal file
|
@ -0,0 +1,233 @@
|
|||
import { getIndicesBetween, sortByIndex } from '@tldraw/indices'
|
||||
import { TLParentId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'
|
||||
import { compact } from '@tldraw/utils'
|
||||
import { Editor } from '../editor/Editor'
|
||||
|
||||
export function getReorderingShapesChanges(
|
||||
editor: Editor,
|
||||
operation: 'toBack' | 'toFront' | 'forward' | 'backward',
|
||||
ids: TLShapeId[]
|
||||
) {
|
||||
if (ids.length === 0) return []
|
||||
|
||||
// From the ids that are moving, collect the parents, their children, and which of those children are moving
|
||||
const parents = new Map<TLParentId, { moving: Set<TLShape>; children: TLShape[] }>()
|
||||
|
||||
for (const shape of compact(ids.map((id) => editor.getShapeById(id)))) {
|
||||
const { parentId } = shape
|
||||
if (!parents.has(parentId)) {
|
||||
parents.set(parentId, {
|
||||
children: compact(editor.getSortedChildIds(parentId).map((id) => editor.getShapeById(id))),
|
||||
moving: new Set(),
|
||||
})
|
||||
}
|
||||
parents.get(parentId)!.moving.add(shape)
|
||||
}
|
||||
|
||||
const changes: TLShapePartial[] = []
|
||||
|
||||
switch (operation) {
|
||||
case 'toBack': {
|
||||
parents.forEach(({ moving, children }) => reorderToBack(moving, children, changes))
|
||||
break
|
||||
}
|
||||
case 'toFront': {
|
||||
parents.forEach(({ moving, children }) => reorderToFront(moving, children, changes))
|
||||
break
|
||||
}
|
||||
case 'forward': {
|
||||
parents.forEach(({ moving, children }) => reorderForward(moving, children, changes))
|
||||
break
|
||||
}
|
||||
case 'backward': {
|
||||
parents.forEach(({ moving, children }) => reorderBackward(moving, children, changes))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the moving shapes to the back of the parent's children.
|
||||
*
|
||||
* @param moving The set of shapes that are moving
|
||||
* @param children The parent's children
|
||||
* @param changes The changes array to push changes to
|
||||
*/
|
||||
function reorderToBack(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {
|
||||
const len = children.length
|
||||
|
||||
// If all of the children are moving, there's nothing to do
|
||||
if (moving.size === len) return
|
||||
|
||||
let below: string | undefined
|
||||
let above: string | undefined
|
||||
|
||||
// Starting at the bottom of this parent's children...
|
||||
for (let i = 0; i < len; i++) {
|
||||
const shape = children[i]
|
||||
|
||||
if (moving.has(shape)) {
|
||||
// If we've found a moving shape before we've found a non-moving shape,
|
||||
// then that shape is already at the back; we can remove it from the
|
||||
// moving set and mark it as the shape that will be below the moved shapes.
|
||||
below = shape.index
|
||||
moving.delete(shape)
|
||||
} else {
|
||||
// The first non-moving shape we find will be above our moved shapes; we'll
|
||||
// put our moving shapes between it and the shape marked as below (if any).
|
||||
above = shape.index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (moving.size === 0) {
|
||||
// If our moving set is empty, there's nothing to do; all of our shapes were
|
||||
// already at the back of the parent's children.
|
||||
return
|
||||
} else {
|
||||
// Sort the moving shapes by their current index, then apply the new indices
|
||||
const indices = getIndicesBetween(below, above, moving.size)
|
||||
changes.push(
|
||||
...Array.from(moving.values())
|
||||
.sort(sortByIndex)
|
||||
.map((shape, i) => ({ ...shape, index: indices[i] }))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the moving shapes to the front of the parent's children.
|
||||
*
|
||||
* @param moving The set of shapes that are moving
|
||||
* @param children The parent's children
|
||||
* @param changes The changes array to push changes to
|
||||
*/
|
||||
function reorderToFront(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {
|
||||
const len = children.length
|
||||
|
||||
// If all of the children are moving, there's nothing to do
|
||||
if (moving.size === len) return
|
||||
|
||||
let below: string | undefined
|
||||
let above: string | undefined
|
||||
|
||||
// Starting at the top of this parent's children...
|
||||
for (let i = len - 1; i > -1; i--) {
|
||||
const shape = children[i]
|
||||
|
||||
if (moving.has(shape)) {
|
||||
// If we've found a moving shape before we've found a non-moving shape,
|
||||
// then that shape is already at the front; we can remove it from the
|
||||
// moving set and mark it as the shape that will be above the moved shapes.
|
||||
above = shape.index
|
||||
moving.delete(shape)
|
||||
} else {
|
||||
// The first non-moving shape we find will be below our moved shapes; we'll
|
||||
// put our moving shapes between it and the shape marked as above (if any).
|
||||
below = shape.index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (moving.size === 0) {
|
||||
// If our moving set is empty, there's nothing to do; all of our shapes were
|
||||
// already at the front of the parent's children.
|
||||
return
|
||||
} else {
|
||||
// Sort the moving shapes by their current index, then apply the new indices
|
||||
const indices = getIndicesBetween(below, above, moving.size)
|
||||
changes.push(
|
||||
...Array.from(moving.values())
|
||||
.sort(sortByIndex)
|
||||
.map((shape, i) => ({ ...shape, index: indices[i] }))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the moving shapes forward in the parent's children.
|
||||
*
|
||||
* @param moving The set of shapes that are moving
|
||||
* @param children The parent's children
|
||||
* @param changes The changes array to push changes to
|
||||
*/
|
||||
function reorderForward(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {
|
||||
const len = children.length
|
||||
|
||||
// If all of the children are moving, there's nothing to do
|
||||
if (moving.size === len) return
|
||||
|
||||
let state = { name: 'skipping' } as
|
||||
| { name: 'skipping' }
|
||||
| { name: 'selecting'; selectIndex: number }
|
||||
|
||||
// Starting at the bottom of this parent's children...
|
||||
for (let i = 0; i < len; i++) {
|
||||
const isMoving = moving.has(children[i])
|
||||
|
||||
switch (state.name) {
|
||||
case 'skipping': {
|
||||
if (!isMoving) continue
|
||||
// If we find a moving shape while skipping, start selecting
|
||||
state = { name: 'selecting', selectIndex: i }
|
||||
break
|
||||
}
|
||||
case 'selecting': {
|
||||
if (isMoving) continue
|
||||
// if we find a non-moving shape while selecting, move all selected
|
||||
// shapes in front of the not moving shape; and start skipping
|
||||
const { selectIndex } = state
|
||||
getIndicesBetween(children[i].index, children[i + 1]?.index, i - selectIndex).forEach(
|
||||
(index, k) => changes.push({ ...children[selectIndex + k], index })
|
||||
)
|
||||
state = { name: 'skipping' }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the moving shapes backward in the parent's children.
|
||||
*
|
||||
* @param moving The set of shapes that are moving
|
||||
* @param children The parent's children
|
||||
* @param changes The changes array to push changes to
|
||||
*/
|
||||
function reorderBackward(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {
|
||||
const len = children.length
|
||||
|
||||
if (moving.size === len) return
|
||||
|
||||
let state = { name: 'skipping' } as
|
||||
| { name: 'skipping' }
|
||||
| { name: 'selecting'; selectIndex: number }
|
||||
|
||||
// Starting at the top of this parent's children...
|
||||
for (let i = len - 1; i > -1; i--) {
|
||||
const isMoving = moving.has(children[i])
|
||||
|
||||
switch (state.name) {
|
||||
case 'skipping': {
|
||||
if (!isMoving) continue
|
||||
// If we find a moving shape while skipping, start selecting
|
||||
state = { name: 'selecting', selectIndex: i }
|
||||
break
|
||||
}
|
||||
case 'selecting': {
|
||||
if (isMoving) continue
|
||||
// if we find a non-moving shape while selecting, move all selected
|
||||
// shapes in behind of the not moving shape; and start skipping
|
||||
getIndicesBetween(children[i - 1]?.index, children[i].index, state.selectIndex - i).forEach(
|
||||
(index, k) => {
|
||||
changes.push({ ...children[i + k + 1], index })
|
||||
}
|
||||
)
|
||||
state = { name: 'skipping' }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue