Implements multiple pages
This commit is contained in:
parent
ad3db2c0ac
commit
07dcfb8df5
53 changed files with 22276 additions and 887 deletions
18
.vscode/tasks.json
vendored
Normal file
18
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "tsc",
|
||||
"type": "shell",
|
||||
"command": "./node_modules/.bin/tsc",
|
||||
"args": ["--noEmit"],
|
||||
"presentation": {
|
||||
"reveal": "never",
|
||||
"echo": false,
|
||||
"focus": false,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -75,6 +75,39 @@ const defaultTheme: TLTheme = {
|
|||
}
|
||||
|
||||
const tlcss = css`
|
||||
@font-face {
|
||||
font-family: 'Recursive';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
||||
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Recursive';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
||||
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Recursive Mono';
|
||||
font-style: normal;
|
||||
font-weight: 420;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImqvTxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
||||
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
:root {
|
||||
--tl-zoom: 1;
|
||||
--tl-scale: calc(1 / var(--tl-zoom));
|
||||
|
|
|
@ -102,5 +102,5 @@ export default function Editor(): JSX.Element {
|
|||
return <div />
|
||||
}
|
||||
|
||||
return <TLDraw document={initialDoc} onChange={handleChange} />
|
||||
return <TLDraw document={value} onChange={handleChange} />
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import * as React from 'react'
|
||||
import { openDB, DBSchema } from 'idb'
|
||||
import { openDB, DBSchema, deleteDB } from 'idb'
|
||||
import type { TLDrawDocument } from '@tldraw/tldraw'
|
||||
|
||||
const VERSION = 1
|
||||
|
@ -57,6 +57,8 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
|
|||
// the state.
|
||||
React.useEffect(() => {
|
||||
async function handleLoad() {
|
||||
await deleteDB('db')
|
||||
|
||||
const db = await openDB<TLDatabase>('db', VERSION, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore('documents')
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './menu'
|
|
@ -15,7 +15,7 @@ import type { Data, TLDrawPage } from '~types'
|
|||
import { useTLDrawContext } from '~hooks'
|
||||
|
||||
const canDeleteSelector = (s: Data) => {
|
||||
return Object.keys(s.document.pages).length <= 1
|
||||
return Object.keys(s.document.pages).length > 1
|
||||
}
|
||||
|
||||
export function PageOptionsDialog({ page }: { page: TLDrawPage }): JSX.Element {
|
||||
|
|
|
@ -73,7 +73,7 @@ export function PagePanel(): JSX.Element {
|
|||
value={page.id}
|
||||
variant="pageButton"
|
||||
>
|
||||
<span>{page.name}</span>
|
||||
<span>{page.name || 'Page'}</span>
|
||||
<DropdownMenu.ItemIndicator>
|
||||
<IconWrapper size="small">
|
||||
<CheckIcon />
|
||||
|
|
|
@ -9,6 +9,8 @@ import { tldrawShapeUtils } from '~shape'
|
|||
import { ContextMenu } from '~components/context-menu'
|
||||
import { StylePanel } from '~components/style-panel'
|
||||
import { ToolsPanel } from '~components/tools-panel'
|
||||
import { PagePanel } from '~components/page-panel'
|
||||
import { Menu } from '~components/menu'
|
||||
|
||||
export interface TLDrawProps {
|
||||
document?: TLDrawDocument
|
||||
|
@ -126,7 +128,10 @@ export function TLDraw({ document, currentPageId, onMount, onChange: _onChange }
|
|||
onTextKeyUp={tlstate.onTextKeyUp}
|
||||
/>
|
||||
</ContextMenu>
|
||||
<MenuButtons />
|
||||
<MenuButtons>
|
||||
<Menu />
|
||||
<PagePanel />
|
||||
</MenuButtons>
|
||||
<Spacer />
|
||||
<StylePanel />
|
||||
<ToolsPanel />
|
||||
|
|
|
@ -4,7 +4,8 @@ import type { Data, Command } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function align(data: Data, ids: string[], type: AlignType): Command {
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id))
|
||||
const { currentPageId } = data.appState
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
|
||||
const boundsForShapes = initialShapes.map((shape) => {
|
||||
return {
|
||||
|
@ -38,17 +39,22 @@ export function align(data: Data, ids: string[], type: AlignType): Command {
|
|||
})
|
||||
)
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
||||
if (!deltaMap[shape.id]) return shape
|
||||
return { point: deltaMap[shape.id].next }
|
||||
})
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => {
|
||||
if (!deltaMap[shape.id]) return shape
|
||||
return { point: deltaMap[shape.id].next }
|
||||
},
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'align_shapes',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: before,
|
||||
},
|
||||
},
|
||||
|
@ -57,7 +63,7 @@ export function align(data: Data, ids: string[], type: AlignType): Command {
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: after,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,12 +17,16 @@ describe('Change page command', () => {
|
|||
|
||||
expect(tlstate.page.id).toBe(initialId)
|
||||
|
||||
tlstate.undo()
|
||||
tlstate.changePage(nextId)
|
||||
|
||||
expect(tlstate.page.id).toBe(nextId)
|
||||
|
||||
tlstate.redo()
|
||||
tlstate.undo()
|
||||
|
||||
expect(tlstate.page.id).toBe(initialId)
|
||||
|
||||
tlstate.redo()
|
||||
|
||||
expect(tlstate.page.id).toBe(nextId)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import type { TLDrawShape, Data, Command } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function changePage(data: Data): Command {
|
||||
export function changePage(data: Data, pageId: string): Command {
|
||||
return {
|
||||
id: 'create_page',
|
||||
before: {},
|
||||
after: {},
|
||||
id: 'change_page',
|
||||
before: {
|
||||
appState: {
|
||||
currentPageId: data.appState.currentPageId,
|
||||
},
|
||||
},
|
||||
after: {
|
||||
appState: {
|
||||
currentPageId: pageId,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,27 @@ describe('Create page command', () => {
|
|||
tlstate.loadDocument(mockDocument)
|
||||
|
||||
const initialId = tlstate.page.id
|
||||
const initialPageState = tlstate.pageState
|
||||
|
||||
tlstate.createPage()
|
||||
|
||||
const nextId = tlstate.page.id
|
||||
const nextPageState = tlstate.pageState
|
||||
|
||||
expect(Object.keys(tlstate.document.pages).length).toBe(2)
|
||||
expect(tlstate.page.id).toBe(nextId)
|
||||
expect(tlstate.pageState).toEqual(nextPageState)
|
||||
|
||||
tlstate.undo()
|
||||
|
||||
expect(Object.keys(tlstate.document.pages).length).toBe(1)
|
||||
expect(tlstate.page.id).toBe(initialId)
|
||||
expect(tlstate.pageState).toEqual(initialPageState)
|
||||
|
||||
tlstate.redo()
|
||||
|
||||
expect(Object.keys(tlstate.document.pages).length).toBe(2)
|
||||
expect(tlstate.page.id).toBe(nextId)
|
||||
expect(tlstate.pageState).toEqual(nextPageState)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,10 +1,46 @@
|
|||
import type { TLDrawShape, Data, Command } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { Data, Command } from '~types'
|
||||
import { Utils } from '@tldraw/core'
|
||||
|
||||
export function createPage(data: Data): Command {
|
||||
const newId = Utils.uniqueId()
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
return {
|
||||
id: 'create_page',
|
||||
before: {},
|
||||
after: {},
|
||||
before: {
|
||||
appState: {
|
||||
currentPageId,
|
||||
},
|
||||
document: {
|
||||
pages: {
|
||||
[newId]: undefined,
|
||||
},
|
||||
pageStates: {
|
||||
[newId]: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
appState: {
|
||||
currentPageId: newId,
|
||||
},
|
||||
document: {
|
||||
pages: {
|
||||
[newId]: { id: newId, shapes: {}, bindings: {} },
|
||||
},
|
||||
pageStates: {
|
||||
[newId]: {
|
||||
id: newId,
|
||||
selectedIds: [],
|
||||
camera: { point: [-window.innerWidth / 2, -window.innerHeight / 2], zoom: 1 },
|
||||
currentParentId: newId,
|
||||
editingId: undefined,
|
||||
bindingId: undefined,
|
||||
hoveredId: undefined,
|
||||
pointedId: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { TLDR } from '~state/tldr'
|
|||
import type { TLDrawShape, Data, Command } from '~types'
|
||||
|
||||
export function create(data: Data, shapes: TLDrawShape[]): Command {
|
||||
const { currentPageId } = data.appState
|
||||
const beforeShapes: Record<string, DeepPartial<TLDrawShape> | undefined> = {}
|
||||
const afterShapes: Record<string, DeepPartial<TLDrawShape> | undefined> = {}
|
||||
|
||||
|
@ -16,13 +17,13 @@ export function create(data: Data, shapes: TLDrawShape[]): Command {
|
|||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: beforeShapes,
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
selectedIds: [...TLDR.getSelectedIds(data)],
|
||||
[currentPageId]: {
|
||||
selectedIds: [...TLDR.getSelectedIds(data, currentPageId)],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -30,12 +31,12 @@ export function create(data: Data, shapes: TLDrawShape[]): Command {
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: afterShapes,
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
selectedIds: shapes.map((shape) => shape.id),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -7,22 +7,22 @@ describe('Delete page', () => {
|
|||
it('does, undoes and redoes command', () => {
|
||||
tlstate.loadDocument(mockDocument)
|
||||
|
||||
const initialId = tlstate.page.id
|
||||
const initialId = tlstate.currentPageId
|
||||
|
||||
tlstate.createPage()
|
||||
|
||||
const nextId = tlstate.page.id
|
||||
const nextId = tlstate.currentPageId
|
||||
|
||||
tlstate.deletePage()
|
||||
|
||||
expect(tlstate.page.id).toBe(nextId)
|
||||
expect(tlstate.currentPageId).toBe(initialId)
|
||||
|
||||
tlstate.undo()
|
||||
|
||||
expect(tlstate.page.id).toBe(initialId)
|
||||
expect(tlstate.currentPageId).toBe(nextId)
|
||||
|
||||
tlstate.redo()
|
||||
|
||||
expect(tlstate.page.id).toBe(nextId)
|
||||
expect(tlstate.currentPageId).toBe(initialId)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,10 +1,53 @@
|
|||
import type { TLDrawShape, Data, Command } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { Data, Command } from '~types'
|
||||
|
||||
export function deletePage(data: Data, pageId: string): Command {
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
const pagesArr = Object.values(data.document.pages).sort(
|
||||
(a, b) => (a.childIndex || 0) - (b.childIndex || 0)
|
||||
)
|
||||
|
||||
const currentIndex = pagesArr.findIndex((page) => page.id === pageId)
|
||||
|
||||
let nextCurrentPageId: string
|
||||
|
||||
if (pageId === currentPageId) {
|
||||
if (currentIndex === pagesArr.length - 1) {
|
||||
nextCurrentPageId = pagesArr[pagesArr.length - 2].id
|
||||
} else {
|
||||
nextCurrentPageId = pagesArr[currentIndex + 1].id
|
||||
}
|
||||
} else {
|
||||
nextCurrentPageId = currentPageId
|
||||
}
|
||||
|
||||
export function deletePage(data: Data, id: string): Command {
|
||||
return {
|
||||
id: 'delete_page',
|
||||
before: {},
|
||||
after: {},
|
||||
before: {
|
||||
appState: {
|
||||
currentPageId: pageId,
|
||||
},
|
||||
document: {
|
||||
pages: {
|
||||
[pageId]: { ...data.document.pages[pageId] },
|
||||
},
|
||||
pageStates: {
|
||||
[pageId]: { ...data.document.pageStates[pageId] },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
appState: {
|
||||
currentPageId: nextCurrentPageId,
|
||||
},
|
||||
document: {
|
||||
pages: {
|
||||
[pageId]: undefined,
|
||||
},
|
||||
pageStates: {
|
||||
[pageId]: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import type { Data, Command, PagePartial } from '~types'
|
|||
// - [ ] Update parents and possibly delete parents
|
||||
|
||||
export function deleteShapes(data: Data, ids: string[]): Command {
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
const before: PagePartial = {
|
||||
shapes: {},
|
||||
bindings: {},
|
||||
|
@ -18,11 +20,11 @@ export function deleteShapes(data: Data, ids: string[]): Command {
|
|||
|
||||
// These are the shapes we're definitely going to delete
|
||||
ids.forEach((id) => {
|
||||
before.shapes[id] = TLDR.getShape(data, id)
|
||||
before.shapes[id] = TLDR.getShape(data, id, currentPageId)
|
||||
after.shapes[id] = undefined
|
||||
})
|
||||
|
||||
const page = TLDR.getPage(data)
|
||||
const page = TLDR.getPage(data, currentPageId)
|
||||
|
||||
// We also need to delete bindings that reference the deleted shapes
|
||||
Object.values(page.bindings).forEach((binding) => {
|
||||
|
@ -34,7 +36,7 @@ export function deleteShapes(data: Data, ids: string[]): Command {
|
|||
after.bindings[binding.id] = undefined
|
||||
|
||||
// Let's also look at the bound shape...
|
||||
const shape = TLDR.getShape(data, id)
|
||||
const shape = TLDR.getShape(data, id, currentPageId)
|
||||
|
||||
// If the bound shape has a handle that references the deleted binding, delete that reference
|
||||
if (shape.handles) {
|
||||
|
@ -60,20 +62,20 @@ export function deleteShapes(data: Data, ids: string[]): Command {
|
|||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: before,
|
||||
[currentPageId]: before,
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: { selectedIds: TLDR.getSelectedIds(data) },
|
||||
[currentPageId]: { selectedIds: TLDR.getSelectedIds(data, currentPageId) },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: after,
|
||||
[currentPageId]: after,
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: { selectedIds: [] },
|
||||
[currentPageId]: { selectedIds: [] },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,27 +3,33 @@ import { DistributeType, TLDrawShape, Data, Command } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function distribute(data: Data, ids: string[], type: DistributeType): Command {
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id))
|
||||
const { currentPageId } = data.appState
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
const deltaMap = Object.fromEntries(getDistributions(initialShapes, type).map((d) => [d.id, d]))
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
||||
if (!deltaMap[shape.id]) return shape
|
||||
return { point: deltaMap[shape.id].next }
|
||||
})
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => {
|
||||
if (!deltaMap[shape.id]) return shape
|
||||
return { point: deltaMap[shape.id].next }
|
||||
},
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'distribute_shapes',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: before },
|
||||
[currentPageId]: { shapes: before },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: after },
|
||||
[currentPageId]: { shapes: after },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ describe('Duplicate page', () => {
|
|||
|
||||
const initialId = tlstate.page.id
|
||||
|
||||
tlstate.duplicatePage()
|
||||
tlstate.duplicatePage(tlstate.currentPageId)
|
||||
|
||||
const nextId = tlstate.page.id
|
||||
|
||||
|
|
|
@ -3,11 +3,12 @@ import { TLDR } from '~state/tldr'
|
|||
import type { Data, Command } from '~types'
|
||||
|
||||
export function duplicate(data: Data, ids: string[]): Command {
|
||||
const delta = Vec.div([16, 16], TLDR.getCamera(data).zoom)
|
||||
const { currentPageId } = data.appState
|
||||
const delta = Vec.div([16, 16], TLDR.getCamera(data, currentPageId).zoom)
|
||||
|
||||
const after = Object.fromEntries(
|
||||
TLDR.getSelectedIds(data)
|
||||
.map((id) => TLDR.getShape(data, id))
|
||||
TLDR.getSelectedIds(data, currentPageId)
|
||||
.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
.map((shape) => {
|
||||
const id = Utils.uniqueId()
|
||||
return [
|
||||
|
@ -28,20 +29,20 @@ export function duplicate(data: Data, ids: string[]): Command {
|
|||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: before },
|
||||
[currentPageId]: { shapes: before },
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: { selectedIds: ids },
|
||||
[currentPageId]: { selectedIds: ids },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: after },
|
||||
[currentPageId]: { shapes: after },
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: { selectedIds: Object.keys(after) },
|
||||
[currentPageId]: { selectedIds: Object.keys(after) },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,52 +4,58 @@ import type { Data, Command } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function flip(data: Data, ids: string[], type: FlipType): Command {
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id))
|
||||
const { currentPageId } = data.appState
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
|
||||
const boundsForShapes = initialShapes.map((shape) => TLDR.getBounds(shape))
|
||||
|
||||
const commonBounds = Utils.getCommonBounds(boundsForShapes)
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
||||
const shapeBounds = TLDR.getBounds(shape)
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => {
|
||||
const shapeBounds = TLDR.getBounds(shape)
|
||||
|
||||
switch (type) {
|
||||
case FlipType.Horizontal: {
|
||||
const newShapeBounds = Utils.getRelativeTransformedBoundingBox(
|
||||
commonBounds,
|
||||
commonBounds,
|
||||
shapeBounds,
|
||||
true,
|
||||
false
|
||||
)
|
||||
switch (type) {
|
||||
case FlipType.Horizontal: {
|
||||
const newShapeBounds = Utils.getRelativeTransformedBoundingBox(
|
||||
commonBounds,
|
||||
commonBounds,
|
||||
shapeBounds,
|
||||
true,
|
||||
false
|
||||
)
|
||||
|
||||
return TLDR.getShapeUtils(shape).transform(shape, newShapeBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: -1,
|
||||
scaleY: 1,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
return TLDR.getShapeUtils(shape).transform(shape, newShapeBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: -1,
|
||||
scaleY: 1,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
case FlipType.Vertical: {
|
||||
const newShapeBounds = Utils.getRelativeTransformedBoundingBox(
|
||||
commonBounds,
|
||||
commonBounds,
|
||||
shapeBounds,
|
||||
false,
|
||||
true
|
||||
)
|
||||
|
||||
return TLDR.getShapeUtils(shape).transform(shape, newShapeBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: 1,
|
||||
scaleY: -1,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
}
|
||||
case FlipType.Vertical: {
|
||||
const newShapeBounds = Utils.getRelativeTransformedBoundingBox(
|
||||
commonBounds,
|
||||
commonBounds,
|
||||
shapeBounds,
|
||||
false,
|
||||
true
|
||||
)
|
||||
|
||||
return TLDR.getShapeUtils(shape).transform(shape, newShapeBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: 1,
|
||||
scaleY: -1,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'flip_shapes',
|
||||
|
|
|
@ -11,3 +11,8 @@ export * from './toggle'
|
|||
export * from './translate'
|
||||
export * from './flip'
|
||||
export * from './toggle-decoration'
|
||||
export * from './create-page'
|
||||
export * from './delete-page'
|
||||
export * from './rename-page'
|
||||
export * from './duplicate-page'
|
||||
export * from './change-page'
|
||||
|
|
|
@ -33,7 +33,7 @@ delete doc.pages.page1.shapes['rect2']
|
|||
delete doc.pages.page1.shapes['rect3']
|
||||
|
||||
function getSortedShapeIds(data: Data) {
|
||||
return TLDR.getShapes(data)
|
||||
return TLDR.getShapes(data, data.appState.currentPageId)
|
||||
.sort((a, b) => a.childIndex - b.childIndex)
|
||||
.map((shape) => shape.id)
|
||||
.join('')
|
||||
|
|
|
@ -2,8 +2,10 @@ import { MoveType, Data, TLDrawShape, Command } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function move(data: Data, ids: string[], type: MoveType): Command {
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
// Get the unique parent ids for the selected elements
|
||||
const parentIds = new Set(ids.map((id) => TLDR.getShape(data, id).parentId))
|
||||
const parentIds = new Set(ids.map((id) => TLDR.getShape(data, id, currentPageId).parentId))
|
||||
|
||||
let result: {
|
||||
before: Record<string, Partial<TLDrawShape>>
|
||||
|
@ -14,7 +16,7 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
|||
let startChildIndex: number
|
||||
let step: number
|
||||
|
||||
const page = TLDR.getPage(data)
|
||||
const page = TLDR.getPage(data, currentPageId)
|
||||
|
||||
// Collect shapes with common parents into a table under their parent id
|
||||
Array.from(parentIds.values()).forEach((parentId) => {
|
||||
|
@ -22,11 +24,11 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
|||
if (parentId === page.id) {
|
||||
sortedChildren = Object.values(page.shapes).sort((a, b) => a.childIndex - b.childIndex)
|
||||
} else {
|
||||
const parent = TLDR.getShape(data, parentId)
|
||||
const parent = TLDR.getShape(data, parentId, currentPageId)
|
||||
if (!parent.children) throw Error('No children in parent!')
|
||||
|
||||
sortedChildren = parent.children
|
||||
.map((childId) => TLDR.getShape(data, childId))
|
||||
.map((childId) => TLDR.getShape(data, childId, currentPageId))
|
||||
.sort((a, b) => a.childIndex - b.childIndex)
|
||||
}
|
||||
|
||||
|
@ -65,7 +67,8 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
|||
sortedIndicesToMove.map((i) => sortedChildren[i].id).reverse(),
|
||||
(_shape, i) => ({
|
||||
childIndex: startChildIndex - (i + 1) * step,
|
||||
})
|
||||
}),
|
||||
currentPageId
|
||||
)
|
||||
|
||||
break
|
||||
|
@ -95,7 +98,8 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
|||
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
||||
(_shape, i) => ({
|
||||
childIndex: startChildIndex + (i + 1),
|
||||
})
|
||||
}),
|
||||
currentPageId
|
||||
)
|
||||
|
||||
break
|
||||
|
@ -140,7 +144,8 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
|||
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
||||
(shape) => ({
|
||||
childIndex: indexMap[shape.id],
|
||||
})
|
||||
}),
|
||||
currentPageId
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -188,7 +193,8 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
|||
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
||||
(shape) => ({
|
||||
childIndex: indexMap[shape.id],
|
||||
})
|
||||
}),
|
||||
currentPageId
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
import type { TLDrawShape, Data, Command } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { Data, Command } from '~types'
|
||||
|
||||
export function editPage(data: Data, id: string): Command {
|
||||
export function renamePage(data: Data, pageId: string, name: string): Command {
|
||||
const page = data.document.pages[pageId]
|
||||
return {
|
||||
id: 'edit_page',
|
||||
before: {},
|
||||
after: {},
|
||||
id: 'rename_page',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[pageId]: { name: page.name },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[pageId]: { name: name },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import { TLDR } from '~state/tldr'
|
|||
const PI2 = Math.PI * 2
|
||||
|
||||
export function rotate(data: Data, ids: string[], delta = -PI2 / 4): Command {
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id))
|
||||
const { currentPageId } = data.appState
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
|
||||
const boundsForShapes = initialShapes.map((shape) => {
|
||||
const utils = TLDR.getShapeUtils(shape)
|
||||
|
@ -31,35 +32,36 @@ export function rotate(data: Data, ids: string[], delta = -PI2 / 4): Command {
|
|||
})
|
||||
)
|
||||
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const pageState = TLDR.getPageState(data, currentPageId)
|
||||
const prevBoundsRotation = pageState.boundsRotation
|
||||
const nextBoundsRotation = (PI2 + ((pageState.boundsRotation || 0) + delta)) % PI2
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => rotations[shape.id])
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => rotations[shape.id],
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'toggle_shapes',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: before },
|
||||
[currentPageId]: { shapes: before },
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
boundsRotation: prevBoundsRotation,
|
||||
},
|
||||
[currentPageId]: { boundsRotation: prevBoundsRotation },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: after },
|
||||
[currentPageId]: { shapes: after },
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
boundsRotation: nextBoundsRotation,
|
||||
},
|
||||
[currentPageId]: { boundsRotation: nextBoundsRotation },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,64 +4,71 @@ import type { Data, Command } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function stretch(data: Data, ids: string[], type: StretchType): Command {
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id))
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
|
||||
const boundsForShapes = initialShapes.map((shape) => TLDR.getBounds(shape))
|
||||
|
||||
const commonBounds = Utils.getCommonBounds(boundsForShapes)
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
||||
const bounds = TLDR.getBounds(shape)
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => {
|
||||
const bounds = TLDR.getBounds(shape)
|
||||
|
||||
switch (type) {
|
||||
case StretchType.Horizontal: {
|
||||
const newBounds = {
|
||||
...bounds,
|
||||
minX: commonBounds.minX,
|
||||
maxX: commonBounds.maxX,
|
||||
width: commonBounds.width,
|
||||
switch (type) {
|
||||
case StretchType.Horizontal: {
|
||||
const newBounds = {
|
||||
...bounds,
|
||||
minX: commonBounds.minX,
|
||||
maxX: commonBounds.maxX,
|
||||
width: commonBounds.width,
|
||||
}
|
||||
|
||||
return TLDR.getShapeUtils(shape).transformSingle(shape, newBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: newBounds.width / bounds.width,
|
||||
scaleY: 1,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
case StretchType.Vertical: {
|
||||
const newBounds = {
|
||||
...bounds,
|
||||
minY: commonBounds.minY,
|
||||
maxY: commonBounds.maxY,
|
||||
height: commonBounds.height,
|
||||
}
|
||||
|
||||
return TLDR.getShapeUtils(shape).transformSingle(shape, newBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: newBounds.width / bounds.width,
|
||||
scaleY: 1,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
case StretchType.Vertical: {
|
||||
const newBounds = {
|
||||
...bounds,
|
||||
minY: commonBounds.minY,
|
||||
maxY: commonBounds.maxY,
|
||||
height: commonBounds.height,
|
||||
return TLDR.getShapeUtils(shape).transformSingle(shape, newBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: 1,
|
||||
scaleY: newBounds.height / bounds.height,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
|
||||
return TLDR.getShapeUtils(shape).transformSingle(shape, newBounds, {
|
||||
type: TLBoundsCorner.TopLeft,
|
||||
scaleX: 1,
|
||||
scaleY: newBounds.height / bounds.height,
|
||||
initialShape: shape,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'stretch_shapes',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: before },
|
||||
[currentPageId]: { shapes: before },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: after },
|
||||
[currentPageId]: { shapes: after },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2,16 +2,23 @@ import type { ShapeStyles, Command, Data } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>): Command {
|
||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
||||
return { style: { ...shape.style, ...changes } }
|
||||
})
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => {
|
||||
return { style: { ...shape.style, ...changes } }
|
||||
},
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'style_shapes',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: before },
|
||||
[currentPageId]: { shapes: before },
|
||||
},
|
||||
},
|
||||
appState: {
|
||||
|
@ -21,7 +28,7 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: after },
|
||||
[currentPageId]: { shapes: after },
|
||||
},
|
||||
},
|
||||
appState: {
|
||||
|
|
|
@ -3,34 +3,40 @@ import type { ArrowShape, Command, Data } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function toggleDecoration(data: Data, ids: string[], handleId: 'start' | 'end'): Command {
|
||||
const { before, after } = TLDR.mutateShapes<ArrowShape>(data, ids, (shape) => {
|
||||
const decorations = shape.decorations
|
||||
? {
|
||||
...shape.decorations,
|
||||
[handleId]: shape.decorations[handleId] ? undefined : Decoration.Arrow,
|
||||
}
|
||||
: {
|
||||
[handleId]: Decoration.Arrow,
|
||||
}
|
||||
const { currentPageId } = data.appState
|
||||
const { before, after } = TLDR.mutateShapes<ArrowShape>(
|
||||
data,
|
||||
ids,
|
||||
(shape) => {
|
||||
const decorations = shape.decorations
|
||||
? {
|
||||
...shape.decorations,
|
||||
[handleId]: shape.decorations[handleId] ? undefined : Decoration.Arrow,
|
||||
}
|
||||
: {
|
||||
[handleId]: Decoration.Arrow,
|
||||
}
|
||||
|
||||
return {
|
||||
decorations,
|
||||
}
|
||||
})
|
||||
return {
|
||||
decorations,
|
||||
}
|
||||
},
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'toggle_decorations',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: before },
|
||||
[currentPageId]: { shapes: before },
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: { shapes: after },
|
||||
[currentPageId]: { shapes: after },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2,19 +2,25 @@ import type { TLDrawShape, Data, Command } from '~types'
|
|||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export function toggle(data: Data, ids: string[], prop: keyof TLDrawShape): Command {
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id))
|
||||
const { currentPageId } = data.appState
|
||||
const initialShapes = ids.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||
const isAllToggled = initialShapes.every((shape) => shape[prop])
|
||||
|
||||
const { before, after } = TLDR.mutateShapes(data, TLDR.getSelectedIds(data), () => ({
|
||||
[prop]: !isAllToggled,
|
||||
}))
|
||||
const { before, after } = TLDR.mutateShapes(
|
||||
data,
|
||||
TLDR.getSelectedIds(data, currentPageId),
|
||||
() => ({
|
||||
[prop]: !isAllToggled,
|
||||
}),
|
||||
currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
id: 'toggle_shapes',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: before,
|
||||
},
|
||||
},
|
||||
|
@ -23,7 +29,7 @@ export function toggle(data: Data, ids: string[], prop: keyof TLDrawShape): Comm
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: after,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,14 +13,19 @@ export function translate(data: Data, ids: string[], delta: number[]): Command {
|
|||
bindings: {},
|
||||
}
|
||||
|
||||
const change = TLDR.mutateShapes(data, ids, (shape) => ({
|
||||
point: Vec.round(Vec.add(shape.point, delta)),
|
||||
}))
|
||||
const change = TLDR.mutateShapes(
|
||||
data,
|
||||
ids,
|
||||
(shape) => ({
|
||||
point: Vec.round(Vec.add(shape.point, delta)),
|
||||
}),
|
||||
data.appState.currentPageId
|
||||
)
|
||||
|
||||
before.shapes = change.before
|
||||
after.shapes = change.after
|
||||
|
||||
const bindingsToDelete = TLDR.getRelatedBindings(data, ids)
|
||||
const bindingsToDelete = TLDR.getRelatedBindings(data, ids, data.appState.currentPageId)
|
||||
|
||||
bindingsToDelete.forEach((binding) => {
|
||||
before.bindings[binding.id] = binding
|
||||
|
@ -28,7 +33,7 @@ export function translate(data: Data, ids: string[], delta: number[]): Command {
|
|||
|
||||
for (const id of [binding.toId, binding.fromId]) {
|
||||
// Let's also look at the bound shape...
|
||||
const shape = TLDR.getShape(data, id)
|
||||
const shape = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||
|
||||
// If the bound shape has a handle that references the deleted binding, delete that reference
|
||||
if (!shape.handles) continue
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { ArrowShape, TLDrawShape } from '~types'
|
||||
import { ArrowShape, TLDrawShape, TLDrawStatus } from '~types'
|
||||
|
||||
describe('Arrow session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -41,6 +41,7 @@ describe('Arrow session', () => {
|
|||
expect(binding.fromId).toBe('arrow1')
|
||||
expect(binding.toId).toBe('target1')
|
||||
expect(binding.handleId).toBe('start')
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
expect(tlstate.getShape('arrow1').handles?.start.bindingId).toBe(binding.id)
|
||||
|
||||
tlstate.undo()
|
||||
|
|
|
@ -31,7 +31,7 @@ export class ArrowSession implements Session {
|
|||
const shapeId = pageState.selectedIds[0]
|
||||
this.origin = point
|
||||
this.handleId = handleId
|
||||
this.initialShape = TLDR.getShape<ArrowShape>(data, shapeId)
|
||||
this.initialShape = TLDR.getShape<ArrowShape>(data, shapeId, data.appState.currentPageId)
|
||||
this.bindableShapeIds = TLDR.getBindableShapeIds(data)
|
||||
|
||||
const initialBindingId = this.initialShape.handles[this.handleId].bindingId
|
||||
|
@ -47,12 +47,11 @@ export class ArrowSession implements Session {
|
|||
start = (data: Data) => data
|
||||
|
||||
update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
||||
const page = TLDR.getPage(data)
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const page = TLDR.getPage(data, data.appState.currentPageId)
|
||||
|
||||
const { initialShape } = this
|
||||
|
||||
const shape = TLDR.getShape<ArrowShape>(data, initialShape.id)
|
||||
const shape = TLDR.getShape<ArrowShape>(data, initialShape.id, data.appState.currentPageId)
|
||||
|
||||
const handles = shape.handles
|
||||
|
||||
|
@ -102,7 +101,7 @@ export class ArrowSession implements Session {
|
|||
if (id === initialShape.id) continue
|
||||
if (id === oppositeBinding?.toId) continue
|
||||
|
||||
const target = TLDR.getShape(data, id)
|
||||
const target = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||
|
||||
const util = TLDR.getShapeUtils(target)
|
||||
|
||||
|
@ -231,12 +230,16 @@ export class ArrowSession implements Session {
|
|||
|
||||
complete(data: Data) {
|
||||
const { initialShape, initialBinding, handleId } = this
|
||||
const page = TLDR.getPage(data)
|
||||
const page = TLDR.getPage(data, data.appState.currentPageId)
|
||||
|
||||
const beforeBindings: Partial<Record<string, TLDrawBinding>> = {}
|
||||
const afterBindings: Partial<Record<string, TLDrawBinding>> = {}
|
||||
|
||||
const currentShape = TLDR.getShape<ArrowShape>(data, initialShape.id)
|
||||
const currentShape = TLDR.getShape<ArrowShape>(
|
||||
data,
|
||||
initialShape.id,
|
||||
data.appState.currentPageId
|
||||
)
|
||||
const currentBindingId = currentShape.handles[handleId].bindingId
|
||||
|
||||
if (initialBinding) {
|
||||
|
@ -275,7 +278,8 @@ export class ArrowSession implements Session {
|
|||
shapes: {
|
||||
[initialShape.id]: TLDR.onSessionComplete(
|
||||
data,
|
||||
TLDR.getShape(data, initialShape.id)
|
||||
TLDR.getShape(data, initialShape.id, data.appState.currentPageId),
|
||||
data.appState.currentPageId
|
||||
),
|
||||
},
|
||||
bindings: afterBindings,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import { TLDrawStatus } from '~types'
|
||||
|
||||
describe('Brush session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -10,6 +11,7 @@ describe('Brush session', () => {
|
|||
tlstate.startBrushSession([-10, -10])
|
||||
tlstate.updateBrushSession([10, 10])
|
||||
tlstate.completeSession()
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
expect(tlstate.selectedIds.length).toBe(1)
|
||||
})
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ export class BrushSession implements Session {
|
|||
|
||||
update = (data: Data, point: number[], containMode = false): DeepPartial<Data> => {
|
||||
const { snapshot, origin } = this
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
// Create a bounding box between the origin and the new point
|
||||
const brush = Utils.getBoundsFromPoints([origin, point])
|
||||
|
@ -29,8 +30,8 @@ export class BrushSession implements Session {
|
|||
const hits = new Set<string>()
|
||||
const selectedIds = new Set(snapshot.selectedIds)
|
||||
|
||||
const page = TLDR.getPage(data)
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const page = TLDR.getPage(data, currentPageId)
|
||||
const pageState = TLDR.getPageState(data, currentPageId)
|
||||
|
||||
snapshot.shapesToTest.forEach(({ id, util, selectId }) => {
|
||||
if (selectedIds.has(id)) return
|
||||
|
@ -65,7 +66,7 @@ export class BrushSession implements Session {
|
|||
return {
|
||||
document: {
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
selectedIds: Array.from(selectedIds.values()),
|
||||
},
|
||||
},
|
||||
|
@ -74,10 +75,11 @@ export class BrushSession implements Session {
|
|||
}
|
||||
|
||||
cancel(data: Data) {
|
||||
const { currentPageId } = data.appState
|
||||
return {
|
||||
document: {
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
selectedIds: this.snapshot.selectedIds,
|
||||
},
|
||||
},
|
||||
|
@ -86,11 +88,12 @@ export class BrushSession implements Session {
|
|||
}
|
||||
|
||||
complete(data: Data) {
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const { currentPageId } = data.appState
|
||||
const pageState = TLDR.getPageState(data, currentPageId)
|
||||
return {
|
||||
document: {
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
selectedIds: [...pageState.selectedIds],
|
||||
},
|
||||
},
|
||||
|
@ -105,9 +108,10 @@ export class BrushSession implements Session {
|
|||
* brush will intersect that shape. For tests, start broad -> fine.
|
||||
*/
|
||||
export function getBrushSnapshot(data: Data) {
|
||||
const selectedIds = [...TLDR.getSelectedIds(data)]
|
||||
const { currentPageId } = data.appState
|
||||
const selectedIds = [...TLDR.getSelectedIds(data, currentPageId)]
|
||||
|
||||
const shapesToTest = TLDR.getShapes(data)
|
||||
const shapesToTest = TLDR.getShapes(data, currentPageId)
|
||||
.filter(
|
||||
(shape) =>
|
||||
!(
|
||||
|
@ -121,7 +125,7 @@ export function getBrushSnapshot(data: Data) {
|
|||
id: shape.id,
|
||||
util: getShapeUtils(shape),
|
||||
bounds: getShapeUtils(shape).getBounds(shape),
|
||||
selectId: TLDR.getTopParentId(data, shape.id),
|
||||
selectId: TLDR.getTopParentId(data, shape.id, currentPageId),
|
||||
}))
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import { ColorStyle, DashStyle, SizeStyle, TLDrawShapeType } from '~types'
|
||||
import { ColorStyle, DashStyle, SizeStyle, TLDrawShapeType, TLDrawStatus } from '~types'
|
||||
|
||||
describe('Transform session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -29,6 +29,8 @@ describe('Transform session', () => {
|
|||
.startDrawSession('draw1', [0, 0])
|
||||
.updateDrawSession([10, 10], 0.5)
|
||||
.completeSession()
|
||||
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
})
|
||||
|
||||
it('does, undoes and redoes', () => {
|
||||
|
|
|
@ -110,18 +110,19 @@ export class DrawSession implements Session {
|
|||
|
||||
cancel = (data: Data) => {
|
||||
const { snapshot } = this
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[snapshot.id]: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
selectedIds: [],
|
||||
},
|
||||
},
|
||||
|
@ -131,19 +132,20 @@ export class DrawSession implements Session {
|
|||
|
||||
complete = (data: Data) => {
|
||||
const { snapshot } = this
|
||||
const pageId = data.appState.currentPageId
|
||||
return {
|
||||
id: 'create_draw',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[snapshot.id]: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
selectedIds: [],
|
||||
},
|
||||
},
|
||||
|
@ -152,9 +154,13 @@ export class DrawSession implements Session {
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[snapshot.id]: TLDR.onSessionComplete(data, TLDR.getShape(data, snapshot.id)),
|
||||
[snapshot.id]: TLDR.onSessionComplete(
|
||||
data,
|
||||
TLDR.getShape(data, snapshot.id, pageId),
|
||||
pageId
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -171,7 +177,7 @@ export class DrawSession implements Session {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getDrawSnapshot(data: Data, shapeId: string) {
|
||||
const page = { ...TLDR.getPage(data) }
|
||||
const page = { ...TLDR.getPage(data, data.appState.currentPageId) }
|
||||
|
||||
const { points, point } = Utils.deepClone(page.shapes[shapeId]) as DrawShape
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { TLDrawShape } from '~types'
|
||||
import { TLDrawShape, TLDrawStatus } from '~types'
|
||||
|
||||
describe('Handle session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -19,8 +19,10 @@ describe('Handle session', () => {
|
|||
.startHandleSession([-10, -10], 'end')
|
||||
.updateHandleSession([10, 10])
|
||||
.completeSession()
|
||||
.undo()
|
||||
.redo()
|
||||
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
|
||||
tlstate.undo().redo()
|
||||
})
|
||||
|
||||
it('cancels session', () => {
|
||||
|
|
|
@ -15,10 +15,11 @@ export class HandleSession implements Session {
|
|||
handleId: string
|
||||
|
||||
constructor(data: Data, handleId: string, point: number[], commandId = 'move_handle') {
|
||||
const shapeId = TLDR.getSelectedIds(data)[0]
|
||||
const { currentPageId } = data.appState
|
||||
const shapeId = TLDR.getSelectedIds(data, currentPageId)[0]
|
||||
this.origin = point
|
||||
this.handleId = handleId
|
||||
this.initialShape = TLDR.getShape(data, shapeId)
|
||||
this.initialShape = TLDR.getShape(data, shapeId, currentPageId)
|
||||
this.commandId = commandId
|
||||
}
|
||||
|
||||
|
@ -26,8 +27,9 @@ export class HandleSession implements Session {
|
|||
|
||||
update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
||||
const { initialShape } = this
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
const shape = TLDR.getShape<ShapesWithProp<'handles'>>(data, initialShape.id)
|
||||
const shape = TLDR.getShape<ShapesWithProp<'handles'>>(data, initialShape.id, currentPageId)
|
||||
|
||||
const handles = shape.handles
|
||||
|
||||
|
@ -54,7 +56,7 @@ export class HandleSession implements Session {
|
|||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: {
|
||||
[shape.id]: change,
|
||||
},
|
||||
|
@ -66,11 +68,12 @@ export class HandleSession implements Session {
|
|||
|
||||
cancel = (data: Data) => {
|
||||
const { initialShape } = this
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[currentPageId]: {
|
||||
shapes: {
|
||||
[initialShape.id]: initialShape,
|
||||
},
|
||||
|
@ -82,12 +85,14 @@ export class HandleSession implements Session {
|
|||
|
||||
complete(data: Data) {
|
||||
const { initialShape } = this
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
return {
|
||||
id: this.commandId,
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[initialShape.id]: initialShape,
|
||||
},
|
||||
|
@ -98,11 +103,12 @@ export class HandleSession implements Session {
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[initialShape.id]: TLDR.onSessionComplete(
|
||||
data,
|
||||
TLDR.getShape(data, this.initialShape.id)
|
||||
TLDR.getShape(data, this.initialShape.id, pageId),
|
||||
pageId
|
||||
),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import { TLDrawStatus } from '~types'
|
||||
|
||||
describe('Brush session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -34,6 +35,8 @@ describe('Brush session', () => {
|
|||
|
||||
tlstate.completeSession()
|
||||
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
|
||||
tlstate.undo()
|
||||
|
||||
expect(tlstate.getShape('rect1').rotation).toBe(undefined)
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Utils, Vec } from '@tldraw/core'
|
|||
import { Session, TLDrawShape, TLDrawStatus } from '~types'
|
||||
import type { Data } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { DeepPartial } from '~../../core/dist/types/utils/utils'
|
||||
|
||||
const PI2 = Math.PI * 2
|
||||
|
||||
|
@ -23,8 +22,9 @@ export class RotateSession implements Session {
|
|||
|
||||
update = (data: Data, point: number[], isLocked = false) => {
|
||||
const { commonBoundsCenter, initialShapes } = this.snapshot
|
||||
const page = TLDR.getPage(data)
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const pageId = data.appState.currentPageId
|
||||
const page = TLDR.getPage(data, pageId)
|
||||
const pageState = TLDR.getPageState(data, pageId)
|
||||
|
||||
const shapes: Record<string, TLDrawShape> = {}
|
||||
|
||||
|
@ -54,16 +54,21 @@ export class RotateSession implements Session {
|
|||
|
||||
const nextPoint = Vec.sub(Vec.rotWith(center, commonBoundsCenter, rot), offset)
|
||||
|
||||
shapes[id] = TLDR.mutate(data, shape, {
|
||||
point: nextPoint,
|
||||
rotation: (PI2 + nextRotation) % PI2,
|
||||
})
|
||||
shapes[id] = TLDR.mutate(
|
||||
data,
|
||||
shape,
|
||||
{
|
||||
point: nextPoint,
|
||||
rotation: (PI2 + nextRotation) % PI2,
|
||||
},
|
||||
pageId
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes,
|
||||
},
|
||||
},
|
||||
|
@ -73,6 +78,7 @@ export class RotateSession implements Session {
|
|||
|
||||
cancel = (data: Data) => {
|
||||
const { initialShapes } = this.snapshot
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
const shapes: Record<string, TLDrawShape> = {}
|
||||
|
||||
|
@ -83,7 +89,7 @@ export class RotateSession implements Session {
|
|||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes,
|
||||
},
|
||||
},
|
||||
|
@ -93,6 +99,7 @@ export class RotateSession implements Session {
|
|||
|
||||
complete(data: Data) {
|
||||
const { hasUnlockedShapes, initialShapes } = this.snapshot
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
if (!hasUnlockedShapes) return data
|
||||
|
||||
|
@ -101,7 +108,7 @@ export class RotateSession implements Session {
|
|||
|
||||
initialShapes.forEach(({ id, shape: { point, rotation } }) => {
|
||||
beforeShapes[id] = { point, rotation }
|
||||
const afterShape = TLDR.getShape(data, id)
|
||||
const afterShape = TLDR.getShape(data, id, pageId)
|
||||
afterShapes[id] = { point: afterShape.point, rotation: afterShape.rotation }
|
||||
})
|
||||
|
||||
|
@ -110,7 +117,7 @@ export class RotateSession implements Session {
|
|||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: beforeShapes,
|
||||
},
|
||||
},
|
||||
|
@ -119,7 +126,7 @@ export class RotateSession implements Session {
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: afterShapes,
|
||||
},
|
||||
},
|
||||
|
@ -131,8 +138,9 @@ export class RotateSession implements Session {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getRotateSnapshot(data: Data) {
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const initialShapes = TLDR.getSelectedBranchSnapshot(data)
|
||||
const currentPageId = data.appState.currentPageId
|
||||
const pageState = TLDR.getPageState(data, currentPageId)
|
||||
const initialShapes = TLDR.getSelectedBranchSnapshot(data, currentPageId)
|
||||
|
||||
if (initialShapes.length === 0) {
|
||||
throw Error('No selected shapes!')
|
||||
|
|
|
@ -9,14 +9,16 @@ export class TextSession implements Session {
|
|||
initialShape: TextShape
|
||||
|
||||
constructor(data: Data, id?: string) {
|
||||
this.initialShape = TLDR.getShape(data, id || TLDR.getSelectedIds(data)[0])
|
||||
const pageId = data.appState.currentPageId
|
||||
this.initialShape = TLDR.getShape(data, id || TLDR.getSelectedIds(data, pageId)[0], pageId)
|
||||
}
|
||||
|
||||
start = (data: Data) => {
|
||||
const pageId = data.appState.currentPageId
|
||||
return {
|
||||
document: {
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
editingId: this.initialShape.id,
|
||||
},
|
||||
},
|
||||
|
@ -25,12 +27,11 @@ export class TextSession implements Session {
|
|||
}
|
||||
|
||||
update = (data: Data, text: string) => {
|
||||
const {
|
||||
initialShape: { id },
|
||||
} = this
|
||||
const { initialShape } = this
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
let nextShape: TextShape = {
|
||||
...TLDR.getShape<TextShape>(data, id),
|
||||
...TLDR.getShape<TextShape>(data, initialShape.id, pageId),
|
||||
text,
|
||||
}
|
||||
|
||||
|
@ -42,9 +43,9 @@ export class TextSession implements Session {
|
|||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[id]: nextShape,
|
||||
[initialShape.id]: nextShape,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -53,21 +54,24 @@ export class TextSession implements Session {
|
|||
}
|
||||
|
||||
cancel = (data: Data) => {
|
||||
const {
|
||||
initialShape: { id },
|
||||
} = this
|
||||
const { initialShape } = this
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
return {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[id]: TLDR.onSessionComplete(data, TLDR.getShape(data, id)),
|
||||
[initialShape.id]: TLDR.onSessionComplete(
|
||||
data,
|
||||
TLDR.getShape(data, initialShape.id, pageId),
|
||||
pageId
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
pageState: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
editingId: undefined,
|
||||
},
|
||||
},
|
||||
|
@ -77,8 +81,9 @@ export class TextSession implements Session {
|
|||
|
||||
complete(data: Data) {
|
||||
const { initialShape } = this
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
const shape = TLDR.getShape<TextShape>(data, initialShape.id)
|
||||
const shape = TLDR.getShape<TextShape>(data, initialShape.id, pageId)
|
||||
|
||||
if (shape.text === initialShape.text) return undefined
|
||||
|
||||
|
@ -87,14 +92,14 @@ export class TextSession implements Session {
|
|||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[initialShape.id]: initialShape,
|
||||
},
|
||||
},
|
||||
},
|
||||
pageState: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
editingId: undefined,
|
||||
},
|
||||
},
|
||||
|
@ -103,17 +108,18 @@ export class TextSession implements Session {
|
|||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
shapes: {
|
||||
[initialShape.id]: TLDR.onSessionComplete(
|
||||
data,
|
||||
TLDR.getShape(data, initialShape.id)
|
||||
TLDR.getShape(data, initialShape.id, pageId),
|
||||
pageId
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
pageState: {
|
||||
[data.appState.currentPageId]: {
|
||||
[pageId]: {
|
||||
editingId: undefined,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import { TLBoundsCorner } from '@tldraw/core'
|
||||
import { TLDrawStatus } from '~types'
|
||||
|
||||
describe('Transform single session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -12,8 +13,10 @@ describe('Transform single session', () => {
|
|||
.startTransformSession([-10, -10], TLBoundsCorner.TopLeft)
|
||||
.updateTransformSession([10, 10])
|
||||
.completeSession()
|
||||
.undo()
|
||||
.redo()
|
||||
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
|
||||
tlstate.undo().redo()
|
||||
})
|
||||
|
||||
it('cancels session', () => {
|
||||
|
|
|
@ -35,7 +35,7 @@ export class TransformSingleSession implements Session {
|
|||
|
||||
const shapes = {} as Record<string, Partial<TLDrawShape>>
|
||||
|
||||
const shape = TLDR.getShape(data, id)
|
||||
const shape = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||
|
||||
const utils = TLDR.getShapeUtils(shape)
|
||||
|
||||
|
@ -95,7 +95,8 @@ export class TransformSingleSession implements Session {
|
|||
beforeShapes[initialShape.id] = initialShape
|
||||
afterShapes[initialShape.id] = TLDR.onSessionComplete(
|
||||
data,
|
||||
TLDR.getShape(data, initialShape.id)
|
||||
TLDR.getShape(data, initialShape.id, data.appState.currentPageId),
|
||||
data.appState.currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -126,7 +127,11 @@ export function getTransformSingleSnapshot(
|
|||
data: Data,
|
||||
transformType: TLBoundsEdge | TLBoundsCorner
|
||||
) {
|
||||
const shape = TLDR.getShape(data, TLDR.getSelectedIds(data)[0])
|
||||
const shape = TLDR.getShape(
|
||||
data,
|
||||
TLDR.getSelectedIds(data, data.appState.currentPageId)[0],
|
||||
data.appState.currentPageId
|
||||
)
|
||||
|
||||
if (!shape) {
|
||||
throw Error('You must have one shape selected.')
|
||||
|
|
|
@ -2,6 +2,7 @@ import { TLDrawState } from '~state'
|
|||
import { mockDocument } from '~test'
|
||||
import { TLBoundsCorner, Utils } from '@tldraw/core'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import { TLDrawStatus } from '~types'
|
||||
|
||||
function getShapeBounds(tlstate: TLDrawState, ...ids: string[]) {
|
||||
return Utils.getCommonBounds(
|
||||
|
@ -30,6 +31,8 @@ describe('Transform session', () => {
|
|||
.updateTransformSession([10, 10])
|
||||
.completeSession()
|
||||
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
|
||||
expect(getShapeBounds(tlstate, 'rect1')).toMatchObject({
|
||||
minX: 10,
|
||||
minY: 10,
|
||||
|
|
|
@ -32,7 +32,7 @@ export class TransformSession implements Session {
|
|||
|
||||
const shapes = {} as Record<string, TLDrawShape>
|
||||
|
||||
const pageState = TLDR.getPageState(data)
|
||||
const pageState = TLDR.getPageState(data, data.appState.currentPageId)
|
||||
|
||||
const newBoundingBox = Utils.getTransformedBoundingBox(
|
||||
initialBounds,
|
||||
|
@ -56,13 +56,19 @@ export class TransformSession implements Session {
|
|||
this.scaleY < 0
|
||||
)
|
||||
|
||||
shapes[id] = TLDR.transform(data, TLDR.getShape(data, id), newShapeBounds, {
|
||||
type: this.transformType,
|
||||
initialShape,
|
||||
scaleX: this.scaleX,
|
||||
scaleY: this.scaleY,
|
||||
transformOrigin,
|
||||
})
|
||||
shapes[id] = TLDR.transform(
|
||||
data,
|
||||
TLDR.getShape(data, id, data.appState.currentPageId),
|
||||
newShapeBounds,
|
||||
{
|
||||
type: this.transformType,
|
||||
initialShape,
|
||||
scaleX: this.scaleX,
|
||||
scaleY: this.scaleY,
|
||||
transformOrigin,
|
||||
},
|
||||
data.appState.currentPageId
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -104,7 +110,7 @@ export class TransformSession implements Session {
|
|||
|
||||
shapeBounds.forEach((shape) => {
|
||||
beforeShapes[shape.id] = shape.initialShape
|
||||
afterShapes[shape.id] = TLDR.getShape(data, shape.id)
|
||||
afterShapes[shape.id] = TLDR.getShape(data, shape.id, data.appState.currentPageId)
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -132,7 +138,7 @@ export class TransformSession implements Session {
|
|||
}
|
||||
|
||||
export function getTransformSnapshot(data: Data, transformType: TLBoundsEdge | TLBoundsCorner) {
|
||||
const initialShapes = TLDR.getSelectedBranchSnapshot(data)
|
||||
const initialShapes = TLDR.getSelectedBranchSnapshot(data, data.appState.currentPageId)
|
||||
|
||||
const hasUnlockedShapes = initialShapes.length > 0
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TLDR } from '~state/tldr'
|
||||
import { TLDrawState } from '~state'
|
||||
import { mockDocument } from '~test'
|
||||
import type { TLDrawShape } from '~types'
|
||||
import { TLDrawShape, TLDrawStatus } from '~types'
|
||||
|
||||
describe('Brush session', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
|
@ -17,6 +17,8 @@ describe('Brush session', () => {
|
|||
|
||||
tlstate.completeSession()
|
||||
|
||||
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||
|
||||
expect(tlstate.getShape('rect1').point).toStrictEqual([5, 5])
|
||||
|
||||
tlstate.undo()
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
import { TLPageState, Utils, Vec } from '@tldraw/core'
|
||||
import {
|
||||
TLDrawShape,
|
||||
TLDrawBinding,
|
||||
PagePartial,
|
||||
Session,
|
||||
Data,
|
||||
Command,
|
||||
TLDrawStatus,
|
||||
ShapesWithProp,
|
||||
} from '~types'
|
||||
import { TLDrawShape, TLDrawBinding, Session, Data, Command, TLDrawStatus } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class TranslateSession implements Session {
|
||||
|
@ -92,7 +83,8 @@ export class TranslateSession implements Session {
|
|||
// Either way, move the clones
|
||||
|
||||
clones.forEach((shape) => {
|
||||
const current = nextShapes[shape.id] || TLDR.getShape(data, shape.id)
|
||||
const current =
|
||||
nextShapes[shape.id] || TLDR.getShape(data, shape.id, data.appState.currentPageId)
|
||||
|
||||
if (!current.point) throw Error('No point on that clone!')
|
||||
|
||||
|
@ -129,7 +121,8 @@ export class TranslateSession implements Session {
|
|||
|
||||
// Move the shapes by the delta
|
||||
initialShapes.forEach((shape) => {
|
||||
const current = nextShapes[shape.id] || TLDR.getShape(data, shape.id)
|
||||
const current =
|
||||
nextShapes[shape.id] || TLDR.getShape(data, shape.id, data.appState.currentPageId)
|
||||
|
||||
if (!current.point) throw Error('No point on that clone!')
|
||||
|
||||
|
@ -147,9 +140,9 @@ export class TranslateSession implements Session {
|
|||
shapes: nextShapes,
|
||||
bindings: nextBindings,
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: nextPageState,
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: nextPageState,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -183,15 +176,17 @@ export class TranslateSession implements Session {
|
|||
shapes: nextShapes,
|
||||
bindings: nextBindings,
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: nextPageState,
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: nextPageState,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
complete(data: Data): Command {
|
||||
const pageId = data.appState.currentPageId
|
||||
|
||||
const { initialShapes, bindingsToDelete, clones, clonedBindings } = this.snapshot
|
||||
|
||||
const beforeBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
|
||||
|
@ -202,17 +197,17 @@ export class TranslateSession implements Session {
|
|||
|
||||
clones.forEach((shape) => {
|
||||
beforeShapes[shape.id] = undefined
|
||||
afterShapes[shape.id] = this.isCloning ? TLDR.getShape(data, shape.id) : undefined
|
||||
afterShapes[shape.id] = this.isCloning ? TLDR.getShape(data, shape.id, pageId) : undefined
|
||||
})
|
||||
|
||||
initialShapes.forEach((shape) => {
|
||||
beforeShapes[shape.id] = { point: shape.point }
|
||||
afterShapes[shape.id] = { point: TLDR.getShape(data, shape.id).point }
|
||||
afterShapes[shape.id] = { point: TLDR.getShape(data, shape.id, pageId).point }
|
||||
})
|
||||
|
||||
clonedBindings.forEach((binding) => {
|
||||
beforeBindings[binding.id] = undefined
|
||||
afterBindings[binding.id] = TLDR.getBinding(data, binding.id)
|
||||
afterBindings[binding.id] = TLDR.getBinding(data, binding.id, pageId)
|
||||
})
|
||||
|
||||
bindingsToDelete.forEach((binding) => {
|
||||
|
@ -220,7 +215,7 @@ export class TranslateSession implements Session {
|
|||
|
||||
for (const id of [binding.toId, binding.fromId]) {
|
||||
// Let's also look at the bound shape...
|
||||
const shape = TLDR.getShape(data, id)
|
||||
const shape = TLDR.getShape(data, id, pageId)
|
||||
|
||||
// If the bound shape has a handle that references the deleted binding, delete that reference
|
||||
if (!shape.handles) continue
|
||||
|
@ -279,7 +274,7 @@ export class TranslateSession implements Session {
|
|||
},
|
||||
pageStates: {
|
||||
[data.appState.currentPageId]: {
|
||||
selectedIds: [...TLDR.getSelectedIds(data)],
|
||||
selectedIds: [...TLDR.getSelectedIds(data, data.appState.currentPageId)],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -290,18 +285,18 @@ export class TranslateSession implements Session {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getTranslateSnapshot(data: Data) {
|
||||
const selectedShapes = TLDR.getSelectedShapeSnapshot(data)
|
||||
const selectedShapes = TLDR.getSelectedShapeSnapshot(data, data.appState.currentPageId)
|
||||
|
||||
const hasUnlockedShapes = selectedShapes.length > 0
|
||||
|
||||
const cloneMap: Record<string, string> = {}
|
||||
|
||||
const page = TLDR.getPage(data)
|
||||
const page = TLDR.getPage(data, data.appState.currentPageId)
|
||||
|
||||
const initialParents = Array.from(new Set(selectedShapes.map((s) => s.parentId)).values())
|
||||
.filter((id) => id !== page.id)
|
||||
.map((id) => {
|
||||
const shape = TLDR.getShape(data, id)
|
||||
const shape = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||
return {
|
||||
id,
|
||||
children: shape.children,
|
||||
|
@ -315,7 +310,7 @@ export function getTranslateSnapshot(data: Data) {
|
|||
...shape,
|
||||
id: Utils.uniqueId(),
|
||||
parentId: shape.parentId,
|
||||
childIndex: TLDR.getChildIndexAbove(data, shape.id),
|
||||
childIndex: TLDR.getChildIndexAbove(data, shape.id, data.appState.currentPageId),
|
||||
}
|
||||
|
||||
cloneMap[shape.id] = clone.id
|
||||
|
@ -343,11 +338,12 @@ export function getTranslateSnapshot(data: Data) {
|
|||
}
|
||||
})
|
||||
|
||||
const selectedIds = TLDR.getSelectedIds(data)
|
||||
const selectedIds = TLDR.getSelectedIds(data, data.appState.currentPageId)
|
||||
|
||||
const bindingsToDelete = TLDR.getRelatedBindings(
|
||||
data,
|
||||
selectedShapes.filter((shape) => shape.handles !== undefined).map((shape) => shape.id)
|
||||
selectedShapes.filter((shape) => shape.handles !== undefined).map((shape) => shape.id),
|
||||
data.appState.currentPageId
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
|
@ -16,14 +16,14 @@ export class TLDR {
|
|||
return getShapeUtils(typeof shape === 'string' ? ({ type: shape } as T) : shape)
|
||||
}
|
||||
|
||||
static getSelectedShapes(data: Data) {
|
||||
const page = this.getPage(data)
|
||||
const selectedIds = this.getSelectedIds(data)
|
||||
static getSelectedShapes(data: Data, pageId: string) {
|
||||
const page = this.getPage(data, pageId)
|
||||
const selectedIds = this.getSelectedIds(data, pageId)
|
||||
return selectedIds.map((id) => page.shapes[id])
|
||||
}
|
||||
|
||||
static screenToWorld(data: Data, point: number[]) {
|
||||
const camera = this.getCamera(data)
|
||||
const camera = this.getPageState(data, data.appState.currentPageId).camera
|
||||
return Vec.sub(Vec.div(point, camera.zoom), camera.point)
|
||||
}
|
||||
|
||||
|
@ -45,30 +45,30 @@ export class TLDR {
|
|||
return Utils.clamp(zoom, 0.1, 5)
|
||||
}
|
||||
|
||||
static getPage(data: Data, pageId = data.appState.currentPageId): TLDrawPage {
|
||||
static getPage(data: Data, pageId: string): TLDrawPage {
|
||||
return data.document.pages[pageId]
|
||||
}
|
||||
|
||||
static getPageState(data: Data, pageId = data.appState.currentPageId): TLPageState {
|
||||
static getPageState(data: Data, pageId: string): TLPageState {
|
||||
return data.document.pageStates[pageId]
|
||||
}
|
||||
|
||||
static getSelectedIds(data: Data, pageId = data.appState.currentPageId): string[] {
|
||||
static getSelectedIds(data: Data, pageId: string): string[] {
|
||||
return this.getPageState(data, pageId).selectedIds
|
||||
}
|
||||
|
||||
static getShapes(data: Data, pageId = data.appState.currentPageId): TLDrawShape[] {
|
||||
static getShapes(data: Data, pageId: string): TLDrawShape[] {
|
||||
return Object.values(this.getPage(data, pageId).shapes)
|
||||
}
|
||||
|
||||
static getCamera(data: Data, pageId = data.appState.currentPageId): TLPageState['camera'] {
|
||||
static getCamera(data: Data, pageId: string): TLPageState['camera'] {
|
||||
return this.getPageState(data, pageId).camera
|
||||
}
|
||||
|
||||
static getShape<T extends TLDrawShape = TLDrawShape>(
|
||||
data: Data,
|
||||
shapeId: string,
|
||||
pageId = data.appState.currentPageId
|
||||
pageId: string
|
||||
): T {
|
||||
return this.getPage(data, pageId).shapes[shapeId] as T
|
||||
}
|
||||
|
@ -83,41 +83,43 @@ export class TLDR {
|
|||
|
||||
static getSelectedBounds(data: Data): TLBounds {
|
||||
return Utils.getCommonBounds(
|
||||
this.getSelectedShapes(data).map((shape) => getShapeUtils(shape).getBounds(shape))
|
||||
this.getSelectedShapes(data, data.appState.currentPageId).map((shape) =>
|
||||
getShapeUtils(shape).getBounds(shape)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
static getParentId(data: Data, id: string) {
|
||||
return this.getShape(data, id).parentId
|
||||
static getParentId(data: Data, id: string, pageId: string) {
|
||||
return this.getShape(data, id, pageId).parentId
|
||||
}
|
||||
|
||||
static getPointedId(data: Data, id: string): string {
|
||||
const page = this.getPage(data)
|
||||
const pageState = this.getPageState(data)
|
||||
const shape = this.getShape(data, id)
|
||||
static getPointedId(data: Data, id: string, pageId: string): string {
|
||||
const page = this.getPage(data, pageId)
|
||||
const pageState = this.getPageState(data, data.appState.currentPageId)
|
||||
const shape = this.getShape(data, id, pageId)
|
||||
if (!shape) return id
|
||||
|
||||
return shape.parentId === pageState.currentParentId || shape.parentId === page.id
|
||||
? id
|
||||
: this.getPointedId(data, shape.parentId)
|
||||
: this.getPointedId(data, shape.parentId, pageId)
|
||||
}
|
||||
|
||||
static getDrilledPointedId(data: Data, id: string): string {
|
||||
const shape = this.getShape(data, id)
|
||||
static getDrilledPointedId(data: Data, id: string, pageId: string): string {
|
||||
const shape = this.getShape(data, id, pageId)
|
||||
const { currentPageId } = data.appState
|
||||
const { currentParentId, pointedId } = this.getPageState(data)
|
||||
const { currentParentId, pointedId } = this.getPageState(data, data.appState.currentPageId)
|
||||
|
||||
return shape.parentId === currentPageId ||
|
||||
shape.parentId === pointedId ||
|
||||
shape.parentId === currentParentId
|
||||
? id
|
||||
: this.getDrilledPointedId(data, shape.parentId)
|
||||
: this.getDrilledPointedId(data, shape.parentId, pageId)
|
||||
}
|
||||
|
||||
static getTopParentId(data: Data, id: string): string {
|
||||
const page = this.getPage(data)
|
||||
const pageState = this.getPageState(data)
|
||||
const shape = this.getShape(data, id)
|
||||
static getTopParentId(data: Data, id: string, pageId: string): string {
|
||||
const page = this.getPage(data, pageId)
|
||||
const pageState = this.getPageState(data, pageId)
|
||||
const shape = this.getShape(data, id, pageId)
|
||||
|
||||
if (shape.parentId === shape.id) {
|
||||
throw Error(`Shape has the same id as its parent! ${shape.id}`)
|
||||
|
@ -125,32 +127,37 @@ export class TLDR {
|
|||
|
||||
return shape.parentId === page.id || shape.parentId === pageState.currentParentId
|
||||
? id
|
||||
: this.getTopParentId(data, shape.parentId)
|
||||
: this.getTopParentId(data, shape.parentId, pageId)
|
||||
}
|
||||
|
||||
// Get an array of a shape id and its descendant shapes' ids
|
||||
static getDocumentBranch(data: Data, id: string): string[] {
|
||||
const shape = this.getShape(data, id)
|
||||
static getDocumentBranch(data: Data, id: string, pageId: string): string[] {
|
||||
const shape = this.getShape(data, id, pageId)
|
||||
|
||||
if (shape.children === undefined) return [id]
|
||||
|
||||
return [id, ...shape.children.flatMap((childId) => this.getDocumentBranch(data, childId))]
|
||||
return [
|
||||
id,
|
||||
...shape.children.flatMap((childId) => this.getDocumentBranch(data, childId, pageId)),
|
||||
]
|
||||
}
|
||||
|
||||
// Get a deep array of unproxied shapes and their descendants
|
||||
static getSelectedBranchSnapshot<K>(
|
||||
data: Data,
|
||||
pageId: string,
|
||||
fn: (shape: TLDrawShape) => K
|
||||
): ({ id: string } & K)[]
|
||||
static getSelectedBranchSnapshot(data: Data): TLDrawShape[]
|
||||
static getSelectedBranchSnapshot(data: Data, pageId: string): TLDrawShape[]
|
||||
static getSelectedBranchSnapshot<K>(
|
||||
data: Data,
|
||||
pageId: string,
|
||||
fn?: (shape: TLDrawShape) => K
|
||||
): (TLDrawShape | K)[] {
|
||||
const page = this.getPage(data)
|
||||
const page = this.getPage(data, pageId)
|
||||
|
||||
const copies = this.getSelectedIds(data)
|
||||
.flatMap((id) => this.getDocumentBranch(data, id).map((id) => page.shapes[id]))
|
||||
const copies = this.getSelectedIds(data, pageId)
|
||||
.flatMap((id) => this.getDocumentBranch(data, id, pageId).map((id) => page.shapes[id]))
|
||||
.filter((shape) => !shape.isLocked)
|
||||
.map(Utils.deepClone)
|
||||
|
||||
|
@ -162,16 +169,18 @@ export class TLDR {
|
|||
}
|
||||
|
||||
// Get a shallow array of unproxied shapes
|
||||
static getSelectedShapeSnapshot(data: Data): TLDrawShape[]
|
||||
static getSelectedShapeSnapshot(data: Data, pageId: string): TLDrawShape[]
|
||||
static getSelectedShapeSnapshot<K>(
|
||||
data: Data,
|
||||
pageId: string,
|
||||
fn?: (shape: TLDrawShape) => K
|
||||
): ({ id: string } & K)[]
|
||||
static getSelectedShapeSnapshot<K>(
|
||||
data: Data,
|
||||
pageId: string,
|
||||
fn?: (shape: TLDrawShape) => K
|
||||
): (TLDrawShape | K)[] {
|
||||
const copies = this.getSelectedShapes(data)
|
||||
const copies = this.getSelectedShapes(data, pageId)
|
||||
.filter((shape) => !shape.isLocked)
|
||||
.map(Utils.deepClone)
|
||||
|
||||
|
@ -184,8 +193,8 @@ export class TLDR {
|
|||
|
||||
// For a given array of shape ids, an array of all other shapes that may be affected by a mutation to it.
|
||||
// Use this to decide which shapes to clone as before / after for a command.
|
||||
static getAllEffectedShapeIds(data: Data, ids: string[]): string[] {
|
||||
const page = this.getPage(data)
|
||||
static getAllEffectedShapeIds(data: Data, ids: string[], pageId: string): string[] {
|
||||
const page = this.getPage(data, pageId)
|
||||
|
||||
const visited = new Set(ids)
|
||||
|
||||
|
@ -232,9 +241,10 @@ export class TLDR {
|
|||
data: Data,
|
||||
id: string,
|
||||
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||
afterShapes: Record<string, Partial<TLDrawShape>> = {}
|
||||
afterShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||
pageId: string
|
||||
): Data {
|
||||
const page = this.getPage(data)
|
||||
const page = this.getPage(data, pageId)
|
||||
const shape = page.shapes[id] as T
|
||||
|
||||
if (shape.children !== undefined) {
|
||||
|
@ -246,8 +256,8 @@ export class TLDR {
|
|||
if (deltas) {
|
||||
return deltas.reduce<Data>((cData, delta) => {
|
||||
if (!delta.id) throw Error('Delta must include an id!')
|
||||
const cPage = this.getPage(cData)
|
||||
const deltaShape = this.getShape(cData, delta.id)
|
||||
const cPage = this.getPage(cData, pageId)
|
||||
const deltaShape = this.getShape(cData, delta.id, pageId)
|
||||
|
||||
if (!beforeShapes[delta.id]) {
|
||||
beforeShapes[delta.id] = deltaShape
|
||||
|
@ -256,7 +266,7 @@ export class TLDR {
|
|||
afterShapes[delta.id] = cPage.shapes[delta.id]
|
||||
|
||||
if (deltaShape.children !== undefined) {
|
||||
this.recursivelyUpdateChildren(cData, delta.id, beforeShapes, afterShapes)
|
||||
this.recursivelyUpdateChildren(cData, delta.id, beforeShapes, afterShapes, pageId)
|
||||
}
|
||||
|
||||
return cData
|
||||
|
@ -271,23 +281,24 @@ export class TLDR {
|
|||
data: Data,
|
||||
id: string,
|
||||
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||
afterShapes: Record<string, Partial<TLDrawShape>> = {}
|
||||
afterShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||
pageId: string
|
||||
): Data {
|
||||
const page = { ...this.getPage(data) }
|
||||
const shape = this.getShape<T>(data, id)
|
||||
const page = { ...this.getPage(data, pageId) }
|
||||
const shape = this.getShape<T>(data, id, pageId)
|
||||
|
||||
if (page.id === 'doc') {
|
||||
throw Error('wtf')
|
||||
}
|
||||
|
||||
if (shape.parentId !== page.id) {
|
||||
const parent = this.getShape(data, shape.parentId)
|
||||
const parent = this.getShape(data, shape.parentId, pageId)
|
||||
|
||||
if (!parent.children) throw Error('No children in parent!')
|
||||
|
||||
const delta = this.getShapeUtils(parent).onChildrenChange(
|
||||
parent,
|
||||
parent.children.map((childId) => this.getShape(data, childId))
|
||||
parent.children.map((childId) => this.getShape(data, childId, pageId))
|
||||
)
|
||||
|
||||
if (delta) {
|
||||
|
@ -299,7 +310,13 @@ export class TLDR {
|
|||
}
|
||||
|
||||
if (parent.parentId !== page.id) {
|
||||
return this.recursivelyUpdateParents(data, parent.parentId, beforeShapes, afterShapes)
|
||||
return this.recursivelyUpdateParents(
|
||||
data,
|
||||
parent.parentId,
|
||||
beforeShapes,
|
||||
afterShapes,
|
||||
pageId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,36 +340,40 @@ export class TLDR {
|
|||
data: Data,
|
||||
id: string,
|
||||
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||
afterShapes: Record<string, Partial<TLDrawShape>> = {}
|
||||
afterShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||
pageId: string
|
||||
): Data {
|
||||
const page = { ...this.getPage(data) }
|
||||
const page = { ...this.getPage(data, pageId) }
|
||||
return Object.values(page.bindings)
|
||||
.filter((binding) => binding.fromId === id || binding.toId === id)
|
||||
.reduce((cData, binding) => {
|
||||
if (!beforeShapes[binding.id]) {
|
||||
beforeShapes[binding.fromId] = Utils.deepClone(this.getShape(cData, binding.fromId))
|
||||
beforeShapes[binding.fromId] = Utils.deepClone(
|
||||
this.getShape(cData, binding.fromId, pageId)
|
||||
)
|
||||
}
|
||||
|
||||
if (!beforeShapes[binding.toId]) {
|
||||
beforeShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId))
|
||||
beforeShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId, pageId))
|
||||
}
|
||||
|
||||
this.onBindingChange(
|
||||
cData,
|
||||
this.getShape(cData, binding.fromId),
|
||||
this.getShape(cData, binding.fromId, pageId),
|
||||
binding,
|
||||
this.getShape(cData, binding.toId)
|
||||
this.getShape(cData, binding.toId, pageId),
|
||||
pageId
|
||||
)
|
||||
|
||||
afterShapes[binding.fromId] = Utils.deepClone(this.getShape(cData, binding.fromId))
|
||||
afterShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId))
|
||||
afterShapes[binding.fromId] = Utils.deepClone(this.getShape(cData, binding.fromId, pageId))
|
||||
afterShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId, pageId))
|
||||
|
||||
return cData
|
||||
}, data)
|
||||
}
|
||||
|
||||
static getChildIndexAbove(data: Data, id: string): number {
|
||||
const page = this.getPage(data)
|
||||
static getChildIndexAbove(data: Data, id: string, pageId: string): number {
|
||||
const page = this.getPage(data, pageId)
|
||||
|
||||
const shape = page.shapes[id]
|
||||
|
||||
|
@ -387,7 +408,7 @@ export class TLDR {
|
|||
data: Data,
|
||||
ids: string[],
|
||||
fn: (shape: T, i: number) => Partial<T>,
|
||||
pageId = data.appState.currentPageId
|
||||
pageId: string
|
||||
): {
|
||||
before: Record<string, Partial<T>>
|
||||
after: Record<string, Partial<T>>
|
||||
|
@ -408,15 +429,15 @@ export class TLDR {
|
|||
})
|
||||
|
||||
const dataWithChildrenChanges = ids.reduce<Data>((cData, id) => {
|
||||
return this.recursivelyUpdateChildren(cData, id, beforeShapes, afterShapes)
|
||||
return this.recursivelyUpdateChildren(cData, id, beforeShapes, afterShapes, pageId)
|
||||
}, data)
|
||||
|
||||
const dataWithParentChanges = ids.reduce<Data>((cData, id) => {
|
||||
return this.recursivelyUpdateParents(cData, id, beforeShapes, afterShapes)
|
||||
return this.recursivelyUpdateParents(cData, id, beforeShapes, afterShapes, pageId)
|
||||
}, dataWithChildrenChanges)
|
||||
|
||||
const dataWithBindingChanges = ids.reduce<Data>((cData, id) => {
|
||||
return this.updateBindings(cData, id, beforeShapes, afterShapes)
|
||||
return this.updateBindings(cData, id, beforeShapes, afterShapes, pageId)
|
||||
}, dataWithParentChanges)
|
||||
|
||||
return {
|
||||
|
@ -429,7 +450,7 @@ export class TLDR {
|
|||
static createShapes(
|
||||
data: Data,
|
||||
shapes: TLDrawShape[],
|
||||
pageId = data.appState.currentPageId
|
||||
pageId: string
|
||||
): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
|
||||
const before: DeepPartial<Data> = {
|
||||
document: {
|
||||
|
@ -496,8 +517,10 @@ export class TLDR {
|
|||
static deleteShapes(
|
||||
data: Data,
|
||||
shapes: TLDrawShape[] | string[],
|
||||
pageId = data.appState.currentPageId
|
||||
pageId?: string
|
||||
): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
|
||||
pageId = pageId ? pageId : data.appState.currentPageId
|
||||
|
||||
const page = this.getPage(data, pageId)
|
||||
|
||||
const shapeIds =
|
||||
|
@ -583,28 +606,29 @@ export class TLDR {
|
|||
}
|
||||
}
|
||||
|
||||
static onSessionComplete<T extends TLDrawShape>(data: Data, shape: T) {
|
||||
static onSessionComplete<T extends TLDrawShape>(data: Data, shape: T, pageId: string) {
|
||||
const delta = getShapeUtils(shape).onSessionComplete(shape)
|
||||
if (!delta) return shape
|
||||
return this.mutate(data, shape, delta)
|
||||
return this.mutate(data, shape, delta, pageId)
|
||||
}
|
||||
|
||||
static onChildrenChange<T extends TLDrawShape>(data: Data, shape: T) {
|
||||
static onChildrenChange<T extends TLDrawShape>(data: Data, shape: T, pageId: string) {
|
||||
if (!shape.children) return
|
||||
|
||||
const delta = getShapeUtils(shape).onChildrenChange(
|
||||
shape,
|
||||
shape.children.map((id) => this.getShape(data, id))
|
||||
shape.children.map((id) => this.getShape(data, id, pageId))
|
||||
)
|
||||
if (!delta) return shape
|
||||
return this.mutate(data, shape, delta)
|
||||
return this.mutate(data, shape, delta, pageId)
|
||||
}
|
||||
|
||||
static onBindingChange<T extends TLDrawShape>(
|
||||
data: Data,
|
||||
shape: T,
|
||||
binding: TLDrawBinding,
|
||||
otherShape: TLDrawShape
|
||||
otherShape: TLDrawShape,
|
||||
pageId: string
|
||||
) {
|
||||
const delta = getShapeUtils(shape).onBindingChange(
|
||||
shape,
|
||||
|
@ -614,32 +638,39 @@ export class TLDR {
|
|||
getShapeUtils(otherShape).getCenter(otherShape)
|
||||
)
|
||||
if (!delta) return shape
|
||||
return this.mutate(data, shape, delta)
|
||||
return this.mutate(data, shape, delta, pageId)
|
||||
}
|
||||
|
||||
static transform<T extends TLDrawShape>(
|
||||
data: Data,
|
||||
shape: T,
|
||||
bounds: TLBounds,
|
||||
info: TLTransformInfo<T>
|
||||
info: TLTransformInfo<T>,
|
||||
pageId: string
|
||||
) {
|
||||
return this.mutate(data, shape, getShapeUtils(shape).transform(shape, bounds, info))
|
||||
return this.mutate(data, shape, getShapeUtils(shape).transform(shape, bounds, info), pageId)
|
||||
}
|
||||
|
||||
static transformSingle<T extends TLDrawShape>(
|
||||
data: Data,
|
||||
shape: T,
|
||||
bounds: TLBounds,
|
||||
info: TLTransformInfo<T>
|
||||
info: TLTransformInfo<T>,
|
||||
pageId: string
|
||||
) {
|
||||
return this.mutate(data, shape, getShapeUtils(shape).transformSingle(shape, bounds, info))
|
||||
return this.mutate(
|
||||
data,
|
||||
shape,
|
||||
getShapeUtils(shape).transformSingle(shape, bounds, info),
|
||||
pageId
|
||||
)
|
||||
}
|
||||
|
||||
static mutate<T extends TLDrawShape>(data: Data, shape: T, props: Partial<T>) {
|
||||
static mutate<T extends TLDrawShape>(data: Data, shape: T, props: Partial<T>, pageId: string) {
|
||||
let next = getShapeUtils(shape).mutate(shape, props)
|
||||
|
||||
if (props.children) {
|
||||
next = this.onChildrenChange(data, next) || next
|
||||
next = this.onChildrenChange(data, next, pageId) || next
|
||||
}
|
||||
|
||||
// data.page.shapes[next.id] = next
|
||||
|
@ -651,12 +682,12 @@ export class TLDR {
|
|||
/* Parents */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
static updateParents(data: Data, changedShapeIds: string[]): void {
|
||||
const page = this.getPage(data)
|
||||
static updateParents(data: Data, pageId: string, changedShapeIds: string[]): void {
|
||||
const page = this.getPage(data, pageId)
|
||||
|
||||
if (changedShapeIds.length === 0) return
|
||||
|
||||
const { shapes } = this.getPage(data)
|
||||
const { shapes } = this.getPage(data, pageId)
|
||||
|
||||
const parentToUpdateIds = Array.from(
|
||||
new Set(changedShapeIds.map((id) => shapes[id].parentId).values())
|
||||
|
@ -669,19 +700,17 @@ export class TLDR {
|
|||
throw Error('A shape is parented to a shape without a children array.')
|
||||
}
|
||||
|
||||
this.onChildrenChange(data, parent)
|
||||
this.onChildrenChange(data, parent, pageId)
|
||||
}
|
||||
|
||||
this.updateParents(data, parentToUpdateIds)
|
||||
this.updateParents(data, pageId, parentToUpdateIds)
|
||||
}
|
||||
|
||||
static getSelectedStyle(data: Data): ShapeStyles | false {
|
||||
const {
|
||||
appState: { currentStyle },
|
||||
} = data
|
||||
static getSelectedStyle(data: Data, pageId: string): ShapeStyles | false {
|
||||
const { currentStyle } = data.appState
|
||||
|
||||
const page = this.getPage(data)
|
||||
const pageState = this.getPageState(data)
|
||||
const page = data.document.pages[pageId]
|
||||
const pageState = data.document.pageStates[pageId]
|
||||
|
||||
if (pageState.selectedIds.length === 0) {
|
||||
return currentStyle
|
||||
|
@ -718,36 +747,36 @@ export class TLDR {
|
|||
/* Bindings */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
static getBinding(data: Data, id: string, pageId = data.appState.currentPageId): TLDrawBinding {
|
||||
static getBinding(data: Data, id: string, pageId: string): TLDrawBinding {
|
||||
return this.getPage(data, pageId).bindings[id]
|
||||
}
|
||||
|
||||
static getBindings(data: Data, pageId = data.appState.currentPageId): TLDrawBinding[] {
|
||||
static getBindings(data: Data, pageId: string): TLDrawBinding[] {
|
||||
const page = this.getPage(data, pageId)
|
||||
return Object.values(page.bindings)
|
||||
}
|
||||
|
||||
static getBindableShapeIds(data: Data) {
|
||||
return this.getShapes(data)
|
||||
return this.getShapes(data, data.appState.currentPageId)
|
||||
.filter((shape) => TLDR.getShapeUtils(shape).canBind)
|
||||
.sort((a, b) => b.childIndex - a.childIndex)
|
||||
.map((shape) => shape.id)
|
||||
}
|
||||
|
||||
static getBindingsWithShapeIds(data: Data, ids: string[]): TLDrawBinding[] {
|
||||
static getBindingsWithShapeIds(data: Data, ids: string[], pageId: string): TLDrawBinding[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
this.getBindings(data).filter((binding) => {
|
||||
this.getBindings(data, pageId).filter((binding) => {
|
||||
return ids.includes(binding.toId) || ids.includes(binding.fromId)
|
||||
})
|
||||
).values()
|
||||
)
|
||||
}
|
||||
|
||||
static getRelatedBindings(data: Data, ids: string[]): TLDrawBinding[] {
|
||||
static getRelatedBindings(data: Data, ids: string[], pageId: string): TLDrawBinding[] {
|
||||
const changedShapeIds = new Set(ids)
|
||||
|
||||
const page = this.getPage(data)
|
||||
const page = this.getPage(data, pageId)
|
||||
|
||||
// Find all bindings that we need to update
|
||||
const bindingsArr = Object.values(page.bindings)
|
||||
|
|
|
@ -28,6 +28,22 @@ describe('TLDrawState', () => {
|
|||
|
||||
expect(Object.keys(tlstate.page.shapes).length).toBe(prevCount + 1)
|
||||
})
|
||||
|
||||
it('pastes a shape to a new page', () => {
|
||||
tlstate.loadDocument(mockDocument)
|
||||
|
||||
tlstate.deselectAll().copy(['rect1']).createPage().paste()
|
||||
|
||||
expect(Object.keys(tlstate.page.shapes).length).toBe(1)
|
||||
|
||||
tlstate.undo()
|
||||
|
||||
expect(Object.keys(tlstate.page.shapes).length).toBe(0)
|
||||
|
||||
tlstate.redo()
|
||||
|
||||
expect(Object.keys(tlstate.page.shapes).length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Selection', () => {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -95,5 +95,5 @@ export default function Editor(): JSX.Element {
|
|||
return <div />
|
||||
}
|
||||
|
||||
return <TLDraw document={initialDoc} onChange={handleChange} />
|
||||
return <TLDraw document={value} onChange={handleChange} />
|
||||
}
|
||||
|
|
21077
tsconfig.tsbuildinfo
Normal file
21077
tsconfig.tsbuildinfo
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue