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`
|
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 {
|
:root {
|
||||||
--tl-zoom: 1;
|
--tl-zoom: 1;
|
||||||
--tl-scale: calc(1 / var(--tl-zoom));
|
--tl-scale: calc(1 / var(--tl-zoom));
|
||||||
|
|
|
@ -102,5 +102,5 @@ export default function Editor(): JSX.Element {
|
||||||
return <div />
|
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 */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { openDB, DBSchema } from 'idb'
|
import { openDB, DBSchema, deleteDB } from 'idb'
|
||||||
import type { TLDrawDocument } from '@tldraw/tldraw'
|
import type { TLDrawDocument } from '@tldraw/tldraw'
|
||||||
|
|
||||||
const VERSION = 1
|
const VERSION = 1
|
||||||
|
@ -57,6 +57,8 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
|
||||||
// the state.
|
// the state.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
async function handleLoad() {
|
async function handleLoad() {
|
||||||
|
await deleteDB('db')
|
||||||
|
|
||||||
const db = await openDB<TLDatabase>('db', VERSION, {
|
const db = await openDB<TLDatabase>('db', VERSION, {
|
||||||
upgrade(db) {
|
upgrade(db) {
|
||||||
db.createObjectStore('documents')
|
db.createObjectStore('documents')
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './menu'
|
|
@ -15,7 +15,7 @@ import type { Data, TLDrawPage } from '~types'
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
|
|
||||||
const canDeleteSelector = (s: Data) => {
|
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 {
|
export function PageOptionsDialog({ page }: { page: TLDrawPage }): JSX.Element {
|
||||||
|
|
|
@ -73,7 +73,7 @@ export function PagePanel(): JSX.Element {
|
||||||
value={page.id}
|
value={page.id}
|
||||||
variant="pageButton"
|
variant="pageButton"
|
||||||
>
|
>
|
||||||
<span>{page.name}</span>
|
<span>{page.name || 'Page'}</span>
|
||||||
<DropdownMenu.ItemIndicator>
|
<DropdownMenu.ItemIndicator>
|
||||||
<IconWrapper size="small">
|
<IconWrapper size="small">
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { tldrawShapeUtils } from '~shape'
|
||||||
import { ContextMenu } from '~components/context-menu'
|
import { ContextMenu } from '~components/context-menu'
|
||||||
import { StylePanel } from '~components/style-panel'
|
import { StylePanel } from '~components/style-panel'
|
||||||
import { ToolsPanel } from '~components/tools-panel'
|
import { ToolsPanel } from '~components/tools-panel'
|
||||||
|
import { PagePanel } from '~components/page-panel'
|
||||||
|
import { Menu } from '~components/menu'
|
||||||
|
|
||||||
export interface TLDrawProps {
|
export interface TLDrawProps {
|
||||||
document?: TLDrawDocument
|
document?: TLDrawDocument
|
||||||
|
@ -126,7 +128,10 @@ export function TLDraw({ document, currentPageId, onMount, onChange: _onChange }
|
||||||
onTextKeyUp={tlstate.onTextKeyUp}
|
onTextKeyUp={tlstate.onTextKeyUp}
|
||||||
/>
|
/>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<MenuButtons />
|
<MenuButtons>
|
||||||
|
<Menu />
|
||||||
|
<PagePanel />
|
||||||
|
</MenuButtons>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<StylePanel />
|
<StylePanel />
|
||||||
<ToolsPanel />
|
<ToolsPanel />
|
||||||
|
|
|
@ -4,7 +4,8 @@ import type { Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function align(data: Data, ids: string[], type: AlignType): Command {
|
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) => {
|
const boundsForShapes = initialShapes.map((shape) => {
|
||||||
return {
|
return {
|
||||||
|
@ -38,17 +39,22 @@ export function align(data: Data, ids: string[], type: AlignType): Command {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
const { before, after } = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => {
|
||||||
if (!deltaMap[shape.id]) return shape
|
if (!deltaMap[shape.id]) return shape
|
||||||
return { point: deltaMap[shape.id].next }
|
return { point: deltaMap[shape.id].next }
|
||||||
})
|
},
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'align_shapes',
|
id: 'align_shapes',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: before,
|
shapes: before,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -57,7 +63,7 @@ export function align(data: Data, ids: string[], type: AlignType): Command {
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: after,
|
shapes: after,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,12 +17,16 @@ describe('Change page command', () => {
|
||||||
|
|
||||||
expect(tlstate.page.id).toBe(initialId)
|
expect(tlstate.page.id).toBe(initialId)
|
||||||
|
|
||||||
tlstate.undo()
|
tlstate.changePage(nextId)
|
||||||
|
|
||||||
expect(tlstate.page.id).toBe(nextId)
|
expect(tlstate.page.id).toBe(nextId)
|
||||||
|
|
||||||
tlstate.redo()
|
tlstate.undo()
|
||||||
|
|
||||||
expect(tlstate.page.id).toBe(initialId)
|
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 type { TLDrawShape, Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function changePage(data: Data): Command {
|
export function changePage(data: Data, pageId: string): Command {
|
||||||
return {
|
return {
|
||||||
id: 'create_page',
|
id: 'change_page',
|
||||||
before: {},
|
before: {
|
||||||
after: {},
|
appState: {
|
||||||
|
currentPageId: data.appState.currentPageId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
appState: {
|
||||||
|
currentPageId: pageId,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,27 @@ describe('Create page command', () => {
|
||||||
tlstate.loadDocument(mockDocument)
|
tlstate.loadDocument(mockDocument)
|
||||||
|
|
||||||
const initialId = tlstate.page.id
|
const initialId = tlstate.page.id
|
||||||
|
const initialPageState = tlstate.pageState
|
||||||
|
|
||||||
tlstate.createPage()
|
tlstate.createPage()
|
||||||
|
|
||||||
const nextId = tlstate.page.id
|
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.page.id).toBe(nextId)
|
||||||
|
expect(tlstate.pageState).toEqual(nextPageState)
|
||||||
|
|
||||||
tlstate.undo()
|
tlstate.undo()
|
||||||
|
|
||||||
|
expect(Object.keys(tlstate.document.pages).length).toBe(1)
|
||||||
expect(tlstate.page.id).toBe(initialId)
|
expect(tlstate.page.id).toBe(initialId)
|
||||||
|
expect(tlstate.pageState).toEqual(initialPageState)
|
||||||
|
|
||||||
tlstate.redo()
|
tlstate.redo()
|
||||||
|
|
||||||
|
expect(Object.keys(tlstate.document.pages).length).toBe(2)
|
||||||
expect(tlstate.page.id).toBe(nextId)
|
expect(tlstate.page.id).toBe(nextId)
|
||||||
|
expect(tlstate.pageState).toEqual(nextPageState)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,46 @@
|
||||||
import type { TLDrawShape, Data, Command } from '~types'
|
import type { Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { Utils } from '@tldraw/core'
|
||||||
|
|
||||||
export function createPage(data: Data): Command {
|
export function createPage(data: Data): Command {
|
||||||
|
const newId = Utils.uniqueId()
|
||||||
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'create_page',
|
id: 'create_page',
|
||||||
before: {},
|
before: {
|
||||||
after: {},
|
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'
|
import type { TLDrawShape, Data, Command } from '~types'
|
||||||
|
|
||||||
export function create(data: Data, shapes: TLDrawShape[]): Command {
|
export function create(data: Data, shapes: TLDrawShape[]): Command {
|
||||||
|
const { currentPageId } = data.appState
|
||||||
const beforeShapes: Record<string, DeepPartial<TLDrawShape> | undefined> = {}
|
const beforeShapes: Record<string, DeepPartial<TLDrawShape> | undefined> = {}
|
||||||
const afterShapes: 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: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: beforeShapes,
|
shapes: beforeShapes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
selectedIds: [...TLDR.getSelectedIds(data)],
|
selectedIds: [...TLDR.getSelectedIds(data, currentPageId)],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -30,12 +31,12 @@ export function create(data: Data, shapes: TLDrawShape[]): Command {
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: afterShapes,
|
shapes: afterShapes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
selectedIds: shapes.map((shape) => shape.id),
|
selectedIds: shapes.map((shape) => shape.id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,22 +7,22 @@ describe('Delete page', () => {
|
||||||
it('does, undoes and redoes command', () => {
|
it('does, undoes and redoes command', () => {
|
||||||
tlstate.loadDocument(mockDocument)
|
tlstate.loadDocument(mockDocument)
|
||||||
|
|
||||||
const initialId = tlstate.page.id
|
const initialId = tlstate.currentPageId
|
||||||
|
|
||||||
tlstate.createPage()
|
tlstate.createPage()
|
||||||
|
|
||||||
const nextId = tlstate.page.id
|
const nextId = tlstate.currentPageId
|
||||||
|
|
||||||
tlstate.deletePage()
|
tlstate.deletePage()
|
||||||
|
|
||||||
expect(tlstate.page.id).toBe(nextId)
|
expect(tlstate.currentPageId).toBe(initialId)
|
||||||
|
|
||||||
tlstate.undo()
|
tlstate.undo()
|
||||||
|
|
||||||
expect(tlstate.page.id).toBe(initialId)
|
expect(tlstate.currentPageId).toBe(nextId)
|
||||||
|
|
||||||
tlstate.redo()
|
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 type { Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
|
||||||
|
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 {
|
return {
|
||||||
id: 'delete_page',
|
id: 'delete_page',
|
||||||
before: {},
|
before: {
|
||||||
after: {},
|
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
|
// - [ ] Update parents and possibly delete parents
|
||||||
|
|
||||||
export function deleteShapes(data: Data, ids: string[]): Command {
|
export function deleteShapes(data: Data, ids: string[]): Command {
|
||||||
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
const before: PagePartial = {
|
const before: PagePartial = {
|
||||||
shapes: {},
|
shapes: {},
|
||||||
bindings: {},
|
bindings: {},
|
||||||
|
@ -18,11 +20,11 @@ export function deleteShapes(data: Data, ids: string[]): Command {
|
||||||
|
|
||||||
// These are the shapes we're definitely going to delete
|
// These are the shapes we're definitely going to delete
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
before.shapes[id] = TLDR.getShape(data, id)
|
before.shapes[id] = TLDR.getShape(data, id, currentPageId)
|
||||||
after.shapes[id] = undefined
|
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
|
// We also need to delete bindings that reference the deleted shapes
|
||||||
Object.values(page.bindings).forEach((binding) => {
|
Object.values(page.bindings).forEach((binding) => {
|
||||||
|
@ -34,7 +36,7 @@ export function deleteShapes(data: Data, ids: string[]): Command {
|
||||||
after.bindings[binding.id] = undefined
|
after.bindings[binding.id] = undefined
|
||||||
|
|
||||||
// Let's also look at the bound shape...
|
// 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 the bound shape has a handle that references the deleted binding, delete that reference
|
||||||
if (shape.handles) {
|
if (shape.handles) {
|
||||||
|
@ -60,20 +62,20 @@ export function deleteShapes(data: Data, ids: string[]): Command {
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: before,
|
[currentPageId]: before,
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: { selectedIds: TLDR.getSelectedIds(data) },
|
[currentPageId]: { selectedIds: TLDR.getSelectedIds(data, currentPageId) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: after,
|
[currentPageId]: after,
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: { selectedIds: [] },
|
[currentPageId]: { selectedIds: [] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,27 +3,33 @@ import { DistributeType, TLDrawShape, Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function distribute(data: Data, ids: string[], type: DistributeType): Command {
|
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 deltaMap = Object.fromEntries(getDistributions(initialShapes, type).map((d) => [d.id, d]))
|
||||||
|
|
||||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
const { before, after } = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => {
|
||||||
if (!deltaMap[shape.id]) return shape
|
if (!deltaMap[shape.id]) return shape
|
||||||
return { point: deltaMap[shape.id].next }
|
return { point: deltaMap[shape.id].next }
|
||||||
})
|
},
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'distribute_shapes',
|
id: 'distribute_shapes',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: before },
|
[currentPageId]: { shapes: before },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: after },
|
[currentPageId]: { shapes: after },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('Duplicate page', () => {
|
||||||
|
|
||||||
const initialId = tlstate.page.id
|
const initialId = tlstate.page.id
|
||||||
|
|
||||||
tlstate.duplicatePage()
|
tlstate.duplicatePage(tlstate.currentPageId)
|
||||||
|
|
||||||
const nextId = tlstate.page.id
|
const nextId = tlstate.page.id
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@ import { TLDR } from '~state/tldr'
|
||||||
import type { Data, Command } from '~types'
|
import type { Data, Command } from '~types'
|
||||||
|
|
||||||
export function duplicate(data: Data, ids: string[]): Command {
|
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(
|
const after = Object.fromEntries(
|
||||||
TLDR.getSelectedIds(data)
|
TLDR.getSelectedIds(data, currentPageId)
|
||||||
.map((id) => TLDR.getShape(data, id))
|
.map((id) => TLDR.getShape(data, id, currentPageId))
|
||||||
.map((shape) => {
|
.map((shape) => {
|
||||||
const id = Utils.uniqueId()
|
const id = Utils.uniqueId()
|
||||||
return [
|
return [
|
||||||
|
@ -28,20 +29,20 @@ export function duplicate(data: Data, ids: string[]): Command {
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: before },
|
[currentPageId]: { shapes: before },
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: { selectedIds: ids },
|
[currentPageId]: { selectedIds: ids },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: after },
|
[currentPageId]: { shapes: after },
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: { selectedIds: Object.keys(after) },
|
[currentPageId]: { selectedIds: Object.keys(after) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,13 +4,17 @@ import type { Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function flip(data: Data, ids: string[], type: FlipType): Command {
|
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 boundsForShapes = initialShapes.map((shape) => TLDR.getBounds(shape))
|
||||||
|
|
||||||
const commonBounds = Utils.getCommonBounds(boundsForShapes)
|
const commonBounds = Utils.getCommonBounds(boundsForShapes)
|
||||||
|
|
||||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
const { before, after } = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => {
|
||||||
const shapeBounds = TLDR.getBounds(shape)
|
const shapeBounds = TLDR.getBounds(shape)
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -49,7 +53,9 @@ export function flip(data: Data, ids: string[], type: FlipType): Command {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'flip_shapes',
|
id: 'flip_shapes',
|
||||||
|
|
|
@ -11,3 +11,8 @@ export * from './toggle'
|
||||||
export * from './translate'
|
export * from './translate'
|
||||||
export * from './flip'
|
export * from './flip'
|
||||||
export * from './toggle-decoration'
|
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']
|
delete doc.pages.page1.shapes['rect3']
|
||||||
|
|
||||||
function getSortedShapeIds(data: Data) {
|
function getSortedShapeIds(data: Data) {
|
||||||
return TLDR.getShapes(data)
|
return TLDR.getShapes(data, data.appState.currentPageId)
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
.sort((a, b) => a.childIndex - b.childIndex)
|
||||||
.map((shape) => shape.id)
|
.map((shape) => shape.id)
|
||||||
.join('')
|
.join('')
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { MoveType, Data, TLDrawShape, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function move(data: Data, ids: string[], type: MoveType): Command {
|
export function move(data: Data, ids: string[], type: MoveType): Command {
|
||||||
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
// Get the unique parent ids for the selected elements
|
// 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: {
|
let result: {
|
||||||
before: Record<string, Partial<TLDrawShape>>
|
before: Record<string, Partial<TLDrawShape>>
|
||||||
|
@ -14,7 +16,7 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
||||||
let startChildIndex: number
|
let startChildIndex: number
|
||||||
let step: 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
|
// Collect shapes with common parents into a table under their parent id
|
||||||
Array.from(parentIds.values()).forEach((parentId) => {
|
Array.from(parentIds.values()).forEach((parentId) => {
|
||||||
|
@ -22,11 +24,11 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
||||||
if (parentId === page.id) {
|
if (parentId === page.id) {
|
||||||
sortedChildren = Object.values(page.shapes).sort((a, b) => a.childIndex - b.childIndex)
|
sortedChildren = Object.values(page.shapes).sort((a, b) => a.childIndex - b.childIndex)
|
||||||
} else {
|
} else {
|
||||||
const parent = TLDR.getShape(data, parentId)
|
const parent = TLDR.getShape(data, parentId, currentPageId)
|
||||||
if (!parent.children) throw Error('No children in parent!')
|
if (!parent.children) throw Error('No children in parent!')
|
||||||
|
|
||||||
sortedChildren = parent.children
|
sortedChildren = parent.children
|
||||||
.map((childId) => TLDR.getShape(data, childId))
|
.map((childId) => TLDR.getShape(data, childId, currentPageId))
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
.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(),
|
sortedIndicesToMove.map((i) => sortedChildren[i].id).reverse(),
|
||||||
(_shape, i) => ({
|
(_shape, i) => ({
|
||||||
childIndex: startChildIndex - (i + 1) * step,
|
childIndex: startChildIndex - (i + 1) * step,
|
||||||
})
|
}),
|
||||||
|
currentPageId
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -95,7 +98,8 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
||||||
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
||||||
(_shape, i) => ({
|
(_shape, i) => ({
|
||||||
childIndex: startChildIndex + (i + 1),
|
childIndex: startChildIndex + (i + 1),
|
||||||
})
|
}),
|
||||||
|
currentPageId
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -140,7 +144,8 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
|
||||||
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
||||||
(shape) => ({
|
(shape) => ({
|
||||||
childIndex: indexMap[shape.id],
|
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),
|
sortedIndicesToMove.map((i) => sortedChildren[i].id),
|
||||||
(shape) => ({
|
(shape) => ({
|
||||||
childIndex: indexMap[shape.id],
|
childIndex: indexMap[shape.id],
|
||||||
})
|
}),
|
||||||
|
currentPageId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
import type { TLDrawShape, Data, Command } from '~types'
|
import type { Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
|
||||||
|
|
||||||
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 {
|
return {
|
||||||
id: 'edit_page',
|
id: 'rename_page',
|
||||||
before: {},
|
before: {
|
||||||
after: {},
|
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
|
const PI2 = Math.PI * 2
|
||||||
|
|
||||||
export function rotate(data: Data, ids: string[], delta = -PI2 / 4): Command {
|
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 boundsForShapes = initialShapes.map((shape) => {
|
||||||
const utils = TLDR.getShapeUtils(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 prevBoundsRotation = pageState.boundsRotation
|
||||||
const nextBoundsRotation = (PI2 + ((pageState.boundsRotation || 0) + delta)) % PI2
|
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 {
|
return {
|
||||||
id: 'toggle_shapes',
|
id: 'toggle_shapes',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: before },
|
[currentPageId]: { shapes: before },
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: { boundsRotation: prevBoundsRotation },
|
||||||
boundsRotation: prevBoundsRotation,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: after },
|
[currentPageId]: { shapes: after },
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: { boundsRotation: nextBoundsRotation },
|
||||||
boundsRotation: nextBoundsRotation,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,13 +4,18 @@ import type { Data, Command } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function stretch(data: Data, ids: string[], type: StretchType): Command {
|
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 boundsForShapes = initialShapes.map((shape) => TLDR.getBounds(shape))
|
||||||
|
|
||||||
const commonBounds = Utils.getCommonBounds(boundsForShapes)
|
const commonBounds = Utils.getCommonBounds(boundsForShapes)
|
||||||
|
|
||||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
const { before, after } = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => {
|
||||||
const bounds = TLDR.getBounds(shape)
|
const bounds = TLDR.getBounds(shape)
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -47,21 +52,23 @@ export function stretch(data: Data, ids: string[], type: StretchType): Command {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'stretch_shapes',
|
id: 'stretch_shapes',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: before },
|
[currentPageId]: { shapes: before },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
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'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>): Command {
|
export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>): Command {
|
||||||
const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
|
const { before, after } = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => {
|
||||||
return { style: { ...shape.style, ...changes } }
|
return { style: { ...shape.style, ...changes } }
|
||||||
})
|
},
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'style_shapes',
|
id: 'style_shapes',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: before },
|
[currentPageId]: { shapes: before },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
appState: {
|
appState: {
|
||||||
|
@ -21,7 +28,7 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: after },
|
[currentPageId]: { shapes: after },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
appState: {
|
appState: {
|
||||||
|
|
|
@ -3,7 +3,11 @@ import type { ArrowShape, Command, Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function toggleDecoration(data: Data, ids: string[], handleId: 'start' | 'end'): Command {
|
export function toggleDecoration(data: Data, ids: string[], handleId: 'start' | 'end'): Command {
|
||||||
const { before, after } = TLDR.mutateShapes<ArrowShape>(data, ids, (shape) => {
|
const { currentPageId } = data.appState
|
||||||
|
const { before, after } = TLDR.mutateShapes<ArrowShape>(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => {
|
||||||
const decorations = shape.decorations
|
const decorations = shape.decorations
|
||||||
? {
|
? {
|
||||||
...shape.decorations,
|
...shape.decorations,
|
||||||
|
@ -16,21 +20,23 @@ export function toggleDecoration(data: Data, ids: string[], handleId: 'start' |
|
||||||
return {
|
return {
|
||||||
decorations,
|
decorations,
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'toggle_decorations',
|
id: 'toggle_decorations',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: { shapes: before },
|
[currentPageId]: { shapes: before },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
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'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function toggle(data: Data, ids: string[], prop: keyof TLDrawShape): Command {
|
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 isAllToggled = initialShapes.every((shape) => shape[prop])
|
||||||
|
|
||||||
const { before, after } = TLDR.mutateShapes(data, TLDR.getSelectedIds(data), () => ({
|
const { before, after } = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
TLDR.getSelectedIds(data, currentPageId),
|
||||||
|
() => ({
|
||||||
[prop]: !isAllToggled,
|
[prop]: !isAllToggled,
|
||||||
}))
|
}),
|
||||||
|
currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'toggle_shapes',
|
id: 'toggle_shapes',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: before,
|
shapes: before,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -23,7 +29,7 @@ export function toggle(data: Data, ids: string[], prop: keyof TLDrawShape): Comm
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: after,
|
shapes: after,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,14 +13,19 @@ export function translate(data: Data, ids: string[], delta: number[]): Command {
|
||||||
bindings: {},
|
bindings: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = TLDR.mutateShapes(data, ids, (shape) => ({
|
const change = TLDR.mutateShapes(
|
||||||
|
data,
|
||||||
|
ids,
|
||||||
|
(shape) => ({
|
||||||
point: Vec.round(Vec.add(shape.point, delta)),
|
point: Vec.round(Vec.add(shape.point, delta)),
|
||||||
}))
|
}),
|
||||||
|
data.appState.currentPageId
|
||||||
|
)
|
||||||
|
|
||||||
before.shapes = change.before
|
before.shapes = change.before
|
||||||
after.shapes = change.after
|
after.shapes = change.after
|
||||||
|
|
||||||
const bindingsToDelete = TLDR.getRelatedBindings(data, ids)
|
const bindingsToDelete = TLDR.getRelatedBindings(data, ids, data.appState.currentPageId)
|
||||||
|
|
||||||
bindingsToDelete.forEach((binding) => {
|
bindingsToDelete.forEach((binding) => {
|
||||||
before.bindings[binding.id] = 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]) {
|
for (const id of [binding.toId, binding.fromId]) {
|
||||||
// Let's also look at the bound shape...
|
// 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 the bound shape has a handle that references the deleted binding, delete that reference
|
||||||
if (!shape.handles) continue
|
if (!shape.handles) continue
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
import type { ArrowShape, TLDrawShape } from '~types'
|
import { ArrowShape, TLDrawShape, TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Arrow session', () => {
|
describe('Arrow session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -41,6 +41,7 @@ describe('Arrow session', () => {
|
||||||
expect(binding.fromId).toBe('arrow1')
|
expect(binding.fromId).toBe('arrow1')
|
||||||
expect(binding.toId).toBe('target1')
|
expect(binding.toId).toBe('target1')
|
||||||
expect(binding.handleId).toBe('start')
|
expect(binding.handleId).toBe('start')
|
||||||
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
expect(tlstate.getShape('arrow1').handles?.start.bindingId).toBe(binding.id)
|
expect(tlstate.getShape('arrow1').handles?.start.bindingId).toBe(binding.id)
|
||||||
|
|
||||||
tlstate.undo()
|
tlstate.undo()
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class ArrowSession implements Session {
|
||||||
const shapeId = pageState.selectedIds[0]
|
const shapeId = pageState.selectedIds[0]
|
||||||
this.origin = point
|
this.origin = point
|
||||||
this.handleId = handleId
|
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)
|
this.bindableShapeIds = TLDR.getBindableShapeIds(data)
|
||||||
|
|
||||||
const initialBindingId = this.initialShape.handles[this.handleId].bindingId
|
const initialBindingId = this.initialShape.handles[this.handleId].bindingId
|
||||||
|
@ -47,12 +47,11 @@ export class ArrowSession implements Session {
|
||||||
start = (data: Data) => data
|
start = (data: Data) => data
|
||||||
|
|
||||||
update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
||||||
const page = TLDR.getPage(data)
|
const page = TLDR.getPage(data, data.appState.currentPageId)
|
||||||
const pageState = TLDR.getPageState(data)
|
|
||||||
|
|
||||||
const { initialShape } = this
|
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
|
const handles = shape.handles
|
||||||
|
|
||||||
|
@ -102,7 +101,7 @@ export class ArrowSession implements Session {
|
||||||
if (id === initialShape.id) continue
|
if (id === initialShape.id) continue
|
||||||
if (id === oppositeBinding?.toId) 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)
|
const util = TLDR.getShapeUtils(target)
|
||||||
|
|
||||||
|
@ -231,12 +230,16 @@ export class ArrowSession implements Session {
|
||||||
|
|
||||||
complete(data: Data) {
|
complete(data: Data) {
|
||||||
const { initialShape, initialBinding, handleId } = this
|
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 beforeBindings: Partial<Record<string, TLDrawBinding>> = {}
|
||||||
const afterBindings: 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
|
const currentBindingId = currentShape.handles[handleId].bindingId
|
||||||
|
|
||||||
if (initialBinding) {
|
if (initialBinding) {
|
||||||
|
@ -275,7 +278,8 @@ export class ArrowSession implements Session {
|
||||||
shapes: {
|
shapes: {
|
||||||
[initialShape.id]: TLDR.onSessionComplete(
|
[initialShape.id]: TLDR.onSessionComplete(
|
||||||
data,
|
data,
|
||||||
TLDR.getShape(data, initialShape.id)
|
TLDR.getShape(data, initialShape.id, data.appState.currentPageId),
|
||||||
|
data.appState.currentPageId
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
bindings: afterBindings,
|
bindings: afterBindings,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
|
import { TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Brush session', () => {
|
describe('Brush session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -10,6 +11,7 @@ describe('Brush session', () => {
|
||||||
tlstate.startBrushSession([-10, -10])
|
tlstate.startBrushSession([-10, -10])
|
||||||
tlstate.updateBrushSession([10, 10])
|
tlstate.updateBrushSession([10, 10])
|
||||||
tlstate.completeSession()
|
tlstate.completeSession()
|
||||||
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
expect(tlstate.selectedIds.length).toBe(1)
|
expect(tlstate.selectedIds.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ export class BrushSession implements Session {
|
||||||
|
|
||||||
update = (data: Data, point: number[], containMode = false): DeepPartial<Data> => {
|
update = (data: Data, point: number[], containMode = false): DeepPartial<Data> => {
|
||||||
const { snapshot, origin } = this
|
const { snapshot, origin } = this
|
||||||
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
// Create a bounding box between the origin and the new point
|
// Create a bounding box between the origin and the new point
|
||||||
const brush = Utils.getBoundsFromPoints([origin, point])
|
const brush = Utils.getBoundsFromPoints([origin, point])
|
||||||
|
@ -29,8 +30,8 @@ export class BrushSession implements Session {
|
||||||
const hits = new Set<string>()
|
const hits = new Set<string>()
|
||||||
const selectedIds = new Set(snapshot.selectedIds)
|
const selectedIds = new Set(snapshot.selectedIds)
|
||||||
|
|
||||||
const page = TLDR.getPage(data)
|
const page = TLDR.getPage(data, currentPageId)
|
||||||
const pageState = TLDR.getPageState(data)
|
const pageState = TLDR.getPageState(data, currentPageId)
|
||||||
|
|
||||||
snapshot.shapesToTest.forEach(({ id, util, selectId }) => {
|
snapshot.shapesToTest.forEach(({ id, util, selectId }) => {
|
||||||
if (selectedIds.has(id)) return
|
if (selectedIds.has(id)) return
|
||||||
|
@ -65,7 +66,7 @@ export class BrushSession implements Session {
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
selectedIds: Array.from(selectedIds.values()),
|
selectedIds: Array.from(selectedIds.values()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -74,10 +75,11 @@ export class BrushSession implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(data: Data) {
|
cancel(data: Data) {
|
||||||
|
const { currentPageId } = data.appState
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
selectedIds: this.snapshot.selectedIds,
|
selectedIds: this.snapshot.selectedIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -86,11 +88,12 @@ export class BrushSession implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(data: Data) {
|
complete(data: Data) {
|
||||||
const pageState = TLDR.getPageState(data)
|
const { currentPageId } = data.appState
|
||||||
|
const pageState = TLDR.getPageState(data, currentPageId)
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
selectedIds: [...pageState.selectedIds],
|
selectedIds: [...pageState.selectedIds],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -105,9 +108,10 @@ export class BrushSession implements Session {
|
||||||
* brush will intersect that shape. For tests, start broad -> fine.
|
* brush will intersect that shape. For tests, start broad -> fine.
|
||||||
*/
|
*/
|
||||||
export function getBrushSnapshot(data: Data) {
|
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(
|
.filter(
|
||||||
(shape) =>
|
(shape) =>
|
||||||
!(
|
!(
|
||||||
|
@ -121,7 +125,7 @@ export function getBrushSnapshot(data: Data) {
|
||||||
id: shape.id,
|
id: shape.id,
|
||||||
util: getShapeUtils(shape),
|
util: getShapeUtils(shape),
|
||||||
bounds: getShapeUtils(shape).getBounds(shape),
|
bounds: getShapeUtils(shape).getBounds(shape),
|
||||||
selectId: TLDR.getTopParentId(data, shape.id),
|
selectId: TLDR.getTopParentId(data, shape.id, currentPageId),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import { ColorStyle, DashStyle, SizeStyle, TLDrawShapeType } from '~types'
|
import { ColorStyle, DashStyle, SizeStyle, TLDrawShapeType, TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Transform session', () => {
|
describe('Transform session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -29,6 +29,8 @@ describe('Transform session', () => {
|
||||||
.startDrawSession('draw1', [0, 0])
|
.startDrawSession('draw1', [0, 0])
|
||||||
.updateDrawSession([10, 10], 0.5)
|
.updateDrawSession([10, 10], 0.5)
|
||||||
.completeSession()
|
.completeSession()
|
||||||
|
|
||||||
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does, undoes and redoes', () => {
|
it('does, undoes and redoes', () => {
|
||||||
|
|
|
@ -110,18 +110,19 @@ export class DrawSession implements Session {
|
||||||
|
|
||||||
cancel = (data: Data) => {
|
cancel = (data: Data) => {
|
||||||
const { snapshot } = this
|
const { snapshot } = this
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
|
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[snapshot.id]: undefined,
|
[snapshot.id]: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -131,19 +132,20 @@ export class DrawSession implements Session {
|
||||||
|
|
||||||
complete = (data: Data) => {
|
complete = (data: Data) => {
|
||||||
const { snapshot } = this
|
const { snapshot } = this
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
return {
|
return {
|
||||||
id: 'create_draw',
|
id: 'create_draw',
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[snapshot.id]: undefined,
|
[snapshot.id]: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -152,9 +154,13 @@ export class DrawSession implements Session {
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
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
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export function getDrawSnapshot(data: Data, shapeId: string) {
|
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
|
const { points, point } = Utils.deepClone(page.shapes[shapeId]) as DrawShape
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
import type { TLDrawShape } from '~types'
|
import { TLDrawShape, TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Handle session', () => {
|
describe('Handle session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -19,8 +19,10 @@ describe('Handle session', () => {
|
||||||
.startHandleSession([-10, -10], 'end')
|
.startHandleSession([-10, -10], 'end')
|
||||||
.updateHandleSession([10, 10])
|
.updateHandleSession([10, 10])
|
||||||
.completeSession()
|
.completeSession()
|
||||||
.undo()
|
|
||||||
.redo()
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
|
|
||||||
|
tlstate.undo().redo()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('cancels session', () => {
|
it('cancels session', () => {
|
||||||
|
|
|
@ -15,10 +15,11 @@ export class HandleSession implements Session {
|
||||||
handleId: string
|
handleId: string
|
||||||
|
|
||||||
constructor(data: Data, handleId: string, point: number[], commandId = 'move_handle') {
|
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.origin = point
|
||||||
this.handleId = handleId
|
this.handleId = handleId
|
||||||
this.initialShape = TLDR.getShape(data, shapeId)
|
this.initialShape = TLDR.getShape(data, shapeId, currentPageId)
|
||||||
this.commandId = commandId
|
this.commandId = commandId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +27,9 @@ export class HandleSession implements Session {
|
||||||
|
|
||||||
update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
||||||
const { initialShape } = this
|
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
|
const handles = shape.handles
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ export class HandleSession implements Session {
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[shape.id]: change,
|
[shape.id]: change,
|
||||||
},
|
},
|
||||||
|
@ -66,11 +68,12 @@ export class HandleSession implements Session {
|
||||||
|
|
||||||
cancel = (data: Data) => {
|
cancel = (data: Data) => {
|
||||||
const { initialShape } = this
|
const { initialShape } = this
|
||||||
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[currentPageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[initialShape.id]: initialShape,
|
[initialShape.id]: initialShape,
|
||||||
},
|
},
|
||||||
|
@ -82,12 +85,14 @@ export class HandleSession implements Session {
|
||||||
|
|
||||||
complete(data: Data) {
|
complete(data: Data) {
|
||||||
const { initialShape } = this
|
const { initialShape } = this
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.commandId,
|
id: this.commandId,
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[initialShape.id]: initialShape,
|
[initialShape.id]: initialShape,
|
||||||
},
|
},
|
||||||
|
@ -98,11 +103,12 @@ export class HandleSession implements Session {
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[initialShape.id]: TLDR.onSessionComplete(
|
[initialShape.id]: TLDR.onSessionComplete(
|
||||||
data,
|
data,
|
||||||
TLDR.getShape(data, this.initialShape.id)
|
TLDR.getShape(data, this.initialShape.id, pageId),
|
||||||
|
pageId
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
|
import { TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Brush session', () => {
|
describe('Brush session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -34,6 +35,8 @@ describe('Brush session', () => {
|
||||||
|
|
||||||
tlstate.completeSession()
|
tlstate.completeSession()
|
||||||
|
|
||||||
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
|
|
||||||
tlstate.undo()
|
tlstate.undo()
|
||||||
|
|
||||||
expect(tlstate.getShape('rect1').rotation).toBe(undefined)
|
expect(tlstate.getShape('rect1').rotation).toBe(undefined)
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Utils, Vec } from '@tldraw/core'
|
||||||
import { Session, TLDrawShape, TLDrawStatus } from '~types'
|
import { Session, TLDrawShape, TLDrawStatus } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
import type { DeepPartial } from '~../../core/dist/types/utils/utils'
|
|
||||||
|
|
||||||
const PI2 = Math.PI * 2
|
const PI2 = Math.PI * 2
|
||||||
|
|
||||||
|
@ -23,8 +22,9 @@ export class RotateSession implements Session {
|
||||||
|
|
||||||
update = (data: Data, point: number[], isLocked = false) => {
|
update = (data: Data, point: number[], isLocked = false) => {
|
||||||
const { commonBoundsCenter, initialShapes } = this.snapshot
|
const { commonBoundsCenter, initialShapes } = this.snapshot
|
||||||
const page = TLDR.getPage(data)
|
const pageId = data.appState.currentPageId
|
||||||
const pageState = TLDR.getPageState(data)
|
const page = TLDR.getPage(data, pageId)
|
||||||
|
const pageState = TLDR.getPageState(data, pageId)
|
||||||
|
|
||||||
const shapes: Record<string, TLDrawShape> = {}
|
const shapes: Record<string, TLDrawShape> = {}
|
||||||
|
|
||||||
|
@ -54,16 +54,21 @@ export class RotateSession implements Session {
|
||||||
|
|
||||||
const nextPoint = Vec.sub(Vec.rotWith(center, commonBoundsCenter, rot), offset)
|
const nextPoint = Vec.sub(Vec.rotWith(center, commonBoundsCenter, rot), offset)
|
||||||
|
|
||||||
shapes[id] = TLDR.mutate(data, shape, {
|
shapes[id] = TLDR.mutate(
|
||||||
|
data,
|
||||||
|
shape,
|
||||||
|
{
|
||||||
point: nextPoint,
|
point: nextPoint,
|
||||||
rotation: (PI2 + nextRotation) % PI2,
|
rotation: (PI2 + nextRotation) % PI2,
|
||||||
})
|
},
|
||||||
|
pageId
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes,
|
shapes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -73,6 +78,7 @@ export class RotateSession implements Session {
|
||||||
|
|
||||||
cancel = (data: Data) => {
|
cancel = (data: Data) => {
|
||||||
const { initialShapes } = this.snapshot
|
const { initialShapes } = this.snapshot
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
|
|
||||||
const shapes: Record<string, TLDrawShape> = {}
|
const shapes: Record<string, TLDrawShape> = {}
|
||||||
|
|
||||||
|
@ -83,7 +89,7 @@ export class RotateSession implements Session {
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes,
|
shapes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -93,6 +99,7 @@ export class RotateSession implements Session {
|
||||||
|
|
||||||
complete(data: Data) {
|
complete(data: Data) {
|
||||||
const { hasUnlockedShapes, initialShapes } = this.snapshot
|
const { hasUnlockedShapes, initialShapes } = this.snapshot
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
|
|
||||||
if (!hasUnlockedShapes) return data
|
if (!hasUnlockedShapes) return data
|
||||||
|
|
||||||
|
@ -101,7 +108,7 @@ export class RotateSession implements Session {
|
||||||
|
|
||||||
initialShapes.forEach(({ id, shape: { point, rotation } }) => {
|
initialShapes.forEach(({ id, shape: { point, rotation } }) => {
|
||||||
beforeShapes[id] = { 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 }
|
afterShapes[id] = { point: afterShape.point, rotation: afterShape.rotation }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -110,7 +117,7 @@ export class RotateSession implements Session {
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: beforeShapes,
|
shapes: beforeShapes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -119,7 +126,7 @@ export class RotateSession implements Session {
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: afterShapes,
|
shapes: afterShapes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -131,8 +138,9 @@ export class RotateSession implements Session {
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export function getRotateSnapshot(data: Data) {
|
export function getRotateSnapshot(data: Data) {
|
||||||
const pageState = TLDR.getPageState(data)
|
const currentPageId = data.appState.currentPageId
|
||||||
const initialShapes = TLDR.getSelectedBranchSnapshot(data)
|
const pageState = TLDR.getPageState(data, currentPageId)
|
||||||
|
const initialShapes = TLDR.getSelectedBranchSnapshot(data, currentPageId)
|
||||||
|
|
||||||
if (initialShapes.length === 0) {
|
if (initialShapes.length === 0) {
|
||||||
throw Error('No selected shapes!')
|
throw Error('No selected shapes!')
|
||||||
|
|
|
@ -9,14 +9,16 @@ export class TextSession implements Session {
|
||||||
initialShape: TextShape
|
initialShape: TextShape
|
||||||
|
|
||||||
constructor(data: Data, id?: string) {
|
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) => {
|
start = (data: Data) => {
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
editingId: this.initialShape.id,
|
editingId: this.initialShape.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -25,12 +27,11 @@ export class TextSession implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
update = (data: Data, text: string) => {
|
update = (data: Data, text: string) => {
|
||||||
const {
|
const { initialShape } = this
|
||||||
initialShape: { id },
|
const pageId = data.appState.currentPageId
|
||||||
} = this
|
|
||||||
|
|
||||||
let nextShape: TextShape = {
|
let nextShape: TextShape = {
|
||||||
...TLDR.getShape<TextShape>(data, id),
|
...TLDR.getShape<TextShape>(data, initialShape.id, pageId),
|
||||||
text,
|
text,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +43,9 @@ export class TextSession implements Session {
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[id]: nextShape,
|
[initialShape.id]: nextShape,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -53,21 +54,24 @@ export class TextSession implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel = (data: Data) => {
|
cancel = (data: Data) => {
|
||||||
const {
|
const { initialShape } = this
|
||||||
initialShape: { id },
|
const pageId = data.appState.currentPageId
|
||||||
} = this
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[id]: TLDR.onSessionComplete(data, TLDR.getShape(data, id)),
|
[initialShape.id]: TLDR.onSessionComplete(
|
||||||
|
data,
|
||||||
|
TLDR.getShape(data, initialShape.id, pageId),
|
||||||
|
pageId
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageState: {
|
pageState: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
editingId: undefined,
|
editingId: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -77,8 +81,9 @@ export class TextSession implements Session {
|
||||||
|
|
||||||
complete(data: Data) {
|
complete(data: Data) {
|
||||||
const { initialShape } = this
|
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
|
if (shape.text === initialShape.text) return undefined
|
||||||
|
|
||||||
|
@ -87,14 +92,14 @@ export class TextSession implements Session {
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[initialShape.id]: initialShape,
|
[initialShape.id]: initialShape,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageState: {
|
pageState: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
editingId: undefined,
|
editingId: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -103,17 +108,18 @@ export class TextSession implements Session {
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
shapes: {
|
shapes: {
|
||||||
[initialShape.id]: TLDR.onSessionComplete(
|
[initialShape.id]: TLDR.onSessionComplete(
|
||||||
data,
|
data,
|
||||||
TLDR.getShape(data, initialShape.id)
|
TLDR.getShape(data, initialShape.id, pageId),
|
||||||
|
pageId
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageState: {
|
pageState: {
|
||||||
[data.appState.currentPageId]: {
|
[pageId]: {
|
||||||
editingId: undefined,
|
editingId: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import { TLBoundsCorner } from '@tldraw/core'
|
import { TLBoundsCorner } from '@tldraw/core'
|
||||||
|
import { TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Transform single session', () => {
|
describe('Transform single session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -12,8 +13,10 @@ describe('Transform single session', () => {
|
||||||
.startTransformSession([-10, -10], TLBoundsCorner.TopLeft)
|
.startTransformSession([-10, -10], TLBoundsCorner.TopLeft)
|
||||||
.updateTransformSession([10, 10])
|
.updateTransformSession([10, 10])
|
||||||
.completeSession()
|
.completeSession()
|
||||||
.undo()
|
|
||||||
.redo()
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
|
|
||||||
|
tlstate.undo().redo()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('cancels session', () => {
|
it('cancels session', () => {
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class TransformSingleSession implements Session {
|
||||||
|
|
||||||
const shapes = {} as Record<string, Partial<TLDrawShape>>
|
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)
|
const utils = TLDR.getShapeUtils(shape)
|
||||||
|
|
||||||
|
@ -95,7 +95,8 @@ export class TransformSingleSession implements Session {
|
||||||
beforeShapes[initialShape.id] = initialShape
|
beforeShapes[initialShape.id] = initialShape
|
||||||
afterShapes[initialShape.id] = TLDR.onSessionComplete(
|
afterShapes[initialShape.id] = TLDR.onSessionComplete(
|
||||||
data,
|
data,
|
||||||
TLDR.getShape(data, initialShape.id)
|
TLDR.getShape(data, initialShape.id, data.appState.currentPageId),
|
||||||
|
data.appState.currentPageId
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -126,7 +127,11 @@ export function getTransformSingleSnapshot(
|
||||||
data: Data,
|
data: Data,
|
||||||
transformType: TLBoundsEdge | TLBoundsCorner
|
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) {
|
if (!shape) {
|
||||||
throw Error('You must have one shape selected.')
|
throw Error('You must have one shape selected.')
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import { TLBoundsCorner, Utils } from '@tldraw/core'
|
import { TLBoundsCorner, Utils } from '@tldraw/core'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
import { TLDrawStatus } from '~types'
|
||||||
|
|
||||||
function getShapeBounds(tlstate: TLDrawState, ...ids: string[]) {
|
function getShapeBounds(tlstate: TLDrawState, ...ids: string[]) {
|
||||||
return Utils.getCommonBounds(
|
return Utils.getCommonBounds(
|
||||||
|
@ -30,6 +31,8 @@ describe('Transform session', () => {
|
||||||
.updateTransformSession([10, 10])
|
.updateTransformSession([10, 10])
|
||||||
.completeSession()
|
.completeSession()
|
||||||
|
|
||||||
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
|
|
||||||
expect(getShapeBounds(tlstate, 'rect1')).toMatchObject({
|
expect(getShapeBounds(tlstate, 'rect1')).toMatchObject({
|
||||||
minX: 10,
|
minX: 10,
|
||||||
minY: 10,
|
minY: 10,
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class TransformSession implements Session {
|
||||||
|
|
||||||
const shapes = {} as Record<string, TLDrawShape>
|
const shapes = {} as Record<string, TLDrawShape>
|
||||||
|
|
||||||
const pageState = TLDR.getPageState(data)
|
const pageState = TLDR.getPageState(data, data.appState.currentPageId)
|
||||||
|
|
||||||
const newBoundingBox = Utils.getTransformedBoundingBox(
|
const newBoundingBox = Utils.getTransformedBoundingBox(
|
||||||
initialBounds,
|
initialBounds,
|
||||||
|
@ -56,13 +56,19 @@ export class TransformSession implements Session {
|
||||||
this.scaleY < 0
|
this.scaleY < 0
|
||||||
)
|
)
|
||||||
|
|
||||||
shapes[id] = TLDR.transform(data, TLDR.getShape(data, id), newShapeBounds, {
|
shapes[id] = TLDR.transform(
|
||||||
|
data,
|
||||||
|
TLDR.getShape(data, id, data.appState.currentPageId),
|
||||||
|
newShapeBounds,
|
||||||
|
{
|
||||||
type: this.transformType,
|
type: this.transformType,
|
||||||
initialShape,
|
initialShape,
|
||||||
scaleX: this.scaleX,
|
scaleX: this.scaleX,
|
||||||
scaleY: this.scaleY,
|
scaleY: this.scaleY,
|
||||||
transformOrigin,
|
transformOrigin,
|
||||||
})
|
},
|
||||||
|
data.appState.currentPageId
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -104,7 +110,7 @@ export class TransformSession implements Session {
|
||||||
|
|
||||||
shapeBounds.forEach((shape) => {
|
shapeBounds.forEach((shape) => {
|
||||||
beforeShapes[shape.id] = shape.initialShape
|
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 {
|
return {
|
||||||
|
@ -132,7 +138,7 @@ export class TransformSession implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTransformSnapshot(data: Data, transformType: TLBoundsEdge | TLBoundsCorner) {
|
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
|
const hasUnlockedShapes = initialShapes.length > 0
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import type { TLDrawShape } from '~types'
|
import { TLDrawShape, TLDrawStatus } from '~types'
|
||||||
|
|
||||||
describe('Brush session', () => {
|
describe('Brush session', () => {
|
||||||
const tlstate = new TLDrawState()
|
const tlstate = new TLDrawState()
|
||||||
|
@ -17,6 +17,8 @@ describe('Brush session', () => {
|
||||||
|
|
||||||
tlstate.completeSession()
|
tlstate.completeSession()
|
||||||
|
|
||||||
|
expect(tlstate.status.current).toBe(TLDrawStatus.Idle)
|
||||||
|
|
||||||
expect(tlstate.getShape('rect1').point).toStrictEqual([5, 5])
|
expect(tlstate.getShape('rect1').point).toStrictEqual([5, 5])
|
||||||
|
|
||||||
tlstate.undo()
|
tlstate.undo()
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
import { TLPageState, Utils, Vec } from '@tldraw/core'
|
import { TLPageState, Utils, Vec } from '@tldraw/core'
|
||||||
import {
|
import { TLDrawShape, TLDrawBinding, Session, Data, Command, TLDrawStatus } from '~types'
|
||||||
TLDrawShape,
|
|
||||||
TLDrawBinding,
|
|
||||||
PagePartial,
|
|
||||||
Session,
|
|
||||||
Data,
|
|
||||||
Command,
|
|
||||||
TLDrawStatus,
|
|
||||||
ShapesWithProp,
|
|
||||||
} from '~types'
|
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class TranslateSession implements Session {
|
export class TranslateSession implements Session {
|
||||||
|
@ -92,7 +83,8 @@ export class TranslateSession implements Session {
|
||||||
// Either way, move the clones
|
// Either way, move the clones
|
||||||
|
|
||||||
clones.forEach((shape) => {
|
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!')
|
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
|
// Move the shapes by the delta
|
||||||
initialShapes.forEach((shape) => {
|
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!')
|
if (!current.point) throw Error('No point on that clone!')
|
||||||
|
|
||||||
|
@ -147,11 +140,11 @@ export class TranslateSession implements Session {
|
||||||
shapes: nextShapes,
|
shapes: nextShapes,
|
||||||
bindings: nextBindings,
|
bindings: nextBindings,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: nextPageState,
|
[data.appState.currentPageId]: nextPageState,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,15 +176,17 @@ export class TranslateSession implements Session {
|
||||||
shapes: nextShapes,
|
shapes: nextShapes,
|
||||||
bindings: nextBindings,
|
bindings: nextBindings,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: nextPageState,
|
[data.appState.currentPageId]: nextPageState,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(data: Data): Command {
|
complete(data: Data): Command {
|
||||||
|
const pageId = data.appState.currentPageId
|
||||||
|
|
||||||
const { initialShapes, bindingsToDelete, clones, clonedBindings } = this.snapshot
|
const { initialShapes, bindingsToDelete, clones, clonedBindings } = this.snapshot
|
||||||
|
|
||||||
const beforeBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
|
const beforeBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
|
||||||
|
@ -202,17 +197,17 @@ export class TranslateSession implements Session {
|
||||||
|
|
||||||
clones.forEach((shape) => {
|
clones.forEach((shape) => {
|
||||||
beforeShapes[shape.id] = undefined
|
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) => {
|
initialShapes.forEach((shape) => {
|
||||||
beforeShapes[shape.id] = { point: shape.point }
|
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) => {
|
clonedBindings.forEach((binding) => {
|
||||||
beforeBindings[binding.id] = undefined
|
beforeBindings[binding.id] = undefined
|
||||||
afterBindings[binding.id] = TLDR.getBinding(data, binding.id)
|
afterBindings[binding.id] = TLDR.getBinding(data, binding.id, pageId)
|
||||||
})
|
})
|
||||||
|
|
||||||
bindingsToDelete.forEach((binding) => {
|
bindingsToDelete.forEach((binding) => {
|
||||||
|
@ -220,7 +215,7 @@ export class TranslateSession implements Session {
|
||||||
|
|
||||||
for (const id of [binding.toId, binding.fromId]) {
|
for (const id of [binding.toId, binding.fromId]) {
|
||||||
// Let's also look at the bound shape...
|
// 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 the bound shape has a handle that references the deleted binding, delete that reference
|
||||||
if (!shape.handles) continue
|
if (!shape.handles) continue
|
||||||
|
@ -279,7 +274,7 @@ export class TranslateSession implements Session {
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[data.appState.currentPageId]: {
|
[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
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export function getTranslateSnapshot(data: Data) {
|
export function getTranslateSnapshot(data: Data) {
|
||||||
const selectedShapes = TLDR.getSelectedShapeSnapshot(data)
|
const selectedShapes = TLDR.getSelectedShapeSnapshot(data, data.appState.currentPageId)
|
||||||
|
|
||||||
const hasUnlockedShapes = selectedShapes.length > 0
|
const hasUnlockedShapes = selectedShapes.length > 0
|
||||||
|
|
||||||
const cloneMap: Record<string, string> = {}
|
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())
|
const initialParents = Array.from(new Set(selectedShapes.map((s) => s.parentId)).values())
|
||||||
.filter((id) => id !== page.id)
|
.filter((id) => id !== page.id)
|
||||||
.map((id) => {
|
.map((id) => {
|
||||||
const shape = TLDR.getShape(data, id)
|
const shape = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
children: shape.children,
|
children: shape.children,
|
||||||
|
@ -315,7 +310,7 @@ export function getTranslateSnapshot(data: Data) {
|
||||||
...shape,
|
...shape,
|
||||||
id: Utils.uniqueId(),
|
id: Utils.uniqueId(),
|
||||||
parentId: shape.parentId,
|
parentId: shape.parentId,
|
||||||
childIndex: TLDR.getChildIndexAbove(data, shape.id),
|
childIndex: TLDR.getChildIndexAbove(data, shape.id, data.appState.currentPageId),
|
||||||
}
|
}
|
||||||
|
|
||||||
cloneMap[shape.id] = clone.id
|
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(
|
const bindingsToDelete = TLDR.getRelatedBindings(
|
||||||
data,
|
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 {
|
return {
|
||||||
|
|
|
@ -16,14 +16,14 @@ export class TLDR {
|
||||||
return getShapeUtils(typeof shape === 'string' ? ({ type: shape } as T) : shape)
|
return getShapeUtils(typeof shape === 'string' ? ({ type: shape } as T) : shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSelectedShapes(data: Data) {
|
static getSelectedShapes(data: Data, pageId: string) {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
const selectedIds = this.getSelectedIds(data)
|
const selectedIds = this.getSelectedIds(data, pageId)
|
||||||
return selectedIds.map((id) => page.shapes[id])
|
return selectedIds.map((id) => page.shapes[id])
|
||||||
}
|
}
|
||||||
|
|
||||||
static screenToWorld(data: Data, point: number[]) {
|
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)
|
return Vec.sub(Vec.div(point, camera.zoom), camera.point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,30 +45,30 @@ export class TLDR {
|
||||||
return Utils.clamp(zoom, 0.1, 5)
|
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]
|
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]
|
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
|
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)
|
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
|
return this.getPageState(data, pageId).camera
|
||||||
}
|
}
|
||||||
|
|
||||||
static getShape<T extends TLDrawShape = TLDrawShape>(
|
static getShape<T extends TLDrawShape = TLDrawShape>(
|
||||||
data: Data,
|
data: Data,
|
||||||
shapeId: string,
|
shapeId: string,
|
||||||
pageId = data.appState.currentPageId
|
pageId: string
|
||||||
): T {
|
): T {
|
||||||
return this.getPage(data, pageId).shapes[shapeId] as T
|
return this.getPage(data, pageId).shapes[shapeId] as T
|
||||||
}
|
}
|
||||||
|
@ -83,41 +83,43 @@ export class TLDR {
|
||||||
|
|
||||||
static getSelectedBounds(data: Data): TLBounds {
|
static getSelectedBounds(data: Data): TLBounds {
|
||||||
return Utils.getCommonBounds(
|
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) {
|
static getParentId(data: Data, id: string, pageId: string) {
|
||||||
return this.getShape(data, id).parentId
|
return this.getShape(data, id, pageId).parentId
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPointedId(data: Data, id: string): string {
|
static getPointedId(data: Data, id: string, pageId: string): string {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
const pageState = this.getPageState(data)
|
const pageState = this.getPageState(data, data.appState.currentPageId)
|
||||||
const shape = this.getShape(data, id)
|
const shape = this.getShape(data, id, pageId)
|
||||||
if (!shape) return id
|
if (!shape) return id
|
||||||
|
|
||||||
return shape.parentId === pageState.currentParentId || shape.parentId === page.id
|
return shape.parentId === pageState.currentParentId || shape.parentId === page.id
|
||||||
? id
|
? id
|
||||||
: this.getPointedId(data, shape.parentId)
|
: this.getPointedId(data, shape.parentId, pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDrilledPointedId(data: Data, id: string): string {
|
static getDrilledPointedId(data: Data, id: string, pageId: string): string {
|
||||||
const shape = this.getShape(data, id)
|
const shape = this.getShape(data, id, pageId)
|
||||||
const { currentPageId } = data.appState
|
const { currentPageId } = data.appState
|
||||||
const { currentParentId, pointedId } = this.getPageState(data)
|
const { currentParentId, pointedId } = this.getPageState(data, data.appState.currentPageId)
|
||||||
|
|
||||||
return shape.parentId === currentPageId ||
|
return shape.parentId === currentPageId ||
|
||||||
shape.parentId === pointedId ||
|
shape.parentId === pointedId ||
|
||||||
shape.parentId === currentParentId
|
shape.parentId === currentParentId
|
||||||
? id
|
? id
|
||||||
: this.getDrilledPointedId(data, shape.parentId)
|
: this.getDrilledPointedId(data, shape.parentId, pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTopParentId(data: Data, id: string): string {
|
static getTopParentId(data: Data, id: string, pageId: string): string {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
const pageState = this.getPageState(data)
|
const pageState = this.getPageState(data, pageId)
|
||||||
const shape = this.getShape(data, id)
|
const shape = this.getShape(data, id, pageId)
|
||||||
|
|
||||||
if (shape.parentId === shape.id) {
|
if (shape.parentId === shape.id) {
|
||||||
throw Error(`Shape has the same id as its parent! ${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
|
return shape.parentId === page.id || shape.parentId === pageState.currentParentId
|
||||||
? id
|
? id
|
||||||
: this.getTopParentId(data, shape.parentId)
|
: this.getTopParentId(data, shape.parentId, pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an array of a shape id and its descendant shapes' ids
|
// Get an array of a shape id and its descendant shapes' ids
|
||||||
static getDocumentBranch(data: Data, id: string): string[] {
|
static getDocumentBranch(data: Data, id: string, pageId: string): string[] {
|
||||||
const shape = this.getShape(data, id)
|
const shape = this.getShape(data, id, pageId)
|
||||||
|
|
||||||
if (shape.children === undefined) return [id]
|
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
|
// Get a deep array of unproxied shapes and their descendants
|
||||||
static getSelectedBranchSnapshot<K>(
|
static getSelectedBranchSnapshot<K>(
|
||||||
data: Data,
|
data: Data,
|
||||||
|
pageId: string,
|
||||||
fn: (shape: TLDrawShape) => K
|
fn: (shape: TLDrawShape) => K
|
||||||
): ({ id: string } & K)[]
|
): ({ id: string } & K)[]
|
||||||
static getSelectedBranchSnapshot(data: Data): TLDrawShape[]
|
static getSelectedBranchSnapshot(data: Data, pageId: string): TLDrawShape[]
|
||||||
static getSelectedBranchSnapshot<K>(
|
static getSelectedBranchSnapshot<K>(
|
||||||
data: Data,
|
data: Data,
|
||||||
|
pageId: string,
|
||||||
fn?: (shape: TLDrawShape) => K
|
fn?: (shape: TLDrawShape) => K
|
||||||
): (TLDrawShape | K)[] {
|
): (TLDrawShape | K)[] {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
|
|
||||||
const copies = this.getSelectedIds(data)
|
const copies = this.getSelectedIds(data, pageId)
|
||||||
.flatMap((id) => this.getDocumentBranch(data, id).map((id) => page.shapes[id]))
|
.flatMap((id) => this.getDocumentBranch(data, id, pageId).map((id) => page.shapes[id]))
|
||||||
.filter((shape) => !shape.isLocked)
|
.filter((shape) => !shape.isLocked)
|
||||||
.map(Utils.deepClone)
|
.map(Utils.deepClone)
|
||||||
|
|
||||||
|
@ -162,16 +169,18 @@ export class TLDR {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a shallow array of unproxied shapes
|
// Get a shallow array of unproxied shapes
|
||||||
static getSelectedShapeSnapshot(data: Data): TLDrawShape[]
|
static getSelectedShapeSnapshot(data: Data, pageId: string): TLDrawShape[]
|
||||||
static getSelectedShapeSnapshot<K>(
|
static getSelectedShapeSnapshot<K>(
|
||||||
data: Data,
|
data: Data,
|
||||||
|
pageId: string,
|
||||||
fn?: (shape: TLDrawShape) => K
|
fn?: (shape: TLDrawShape) => K
|
||||||
): ({ id: string } & K)[]
|
): ({ id: string } & K)[]
|
||||||
static getSelectedShapeSnapshot<K>(
|
static getSelectedShapeSnapshot<K>(
|
||||||
data: Data,
|
data: Data,
|
||||||
|
pageId: string,
|
||||||
fn?: (shape: TLDrawShape) => K
|
fn?: (shape: TLDrawShape) => K
|
||||||
): (TLDrawShape | K)[] {
|
): (TLDrawShape | K)[] {
|
||||||
const copies = this.getSelectedShapes(data)
|
const copies = this.getSelectedShapes(data, pageId)
|
||||||
.filter((shape) => !shape.isLocked)
|
.filter((shape) => !shape.isLocked)
|
||||||
.map(Utils.deepClone)
|
.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.
|
// 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.
|
// Use this to decide which shapes to clone as before / after for a command.
|
||||||
static getAllEffectedShapeIds(data: Data, ids: string[]): string[] {
|
static getAllEffectedShapeIds(data: Data, ids: string[], pageId: string): string[] {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
|
|
||||||
const visited = new Set(ids)
|
const visited = new Set(ids)
|
||||||
|
|
||||||
|
@ -232,9 +241,10 @@ export class TLDR {
|
||||||
data: Data,
|
data: Data,
|
||||||
id: string,
|
id: string,
|
||||||
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||||
afterShapes: Record<string, Partial<TLDrawShape>> = {}
|
afterShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||||
|
pageId: string
|
||||||
): Data {
|
): Data {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
const shape = page.shapes[id] as T
|
const shape = page.shapes[id] as T
|
||||||
|
|
||||||
if (shape.children !== undefined) {
|
if (shape.children !== undefined) {
|
||||||
|
@ -246,8 +256,8 @@ export class TLDR {
|
||||||
if (deltas) {
|
if (deltas) {
|
||||||
return deltas.reduce<Data>((cData, delta) => {
|
return deltas.reduce<Data>((cData, delta) => {
|
||||||
if (!delta.id) throw Error('Delta must include an id!')
|
if (!delta.id) throw Error('Delta must include an id!')
|
||||||
const cPage = this.getPage(cData)
|
const cPage = this.getPage(cData, pageId)
|
||||||
const deltaShape = this.getShape(cData, delta.id)
|
const deltaShape = this.getShape(cData, delta.id, pageId)
|
||||||
|
|
||||||
if (!beforeShapes[delta.id]) {
|
if (!beforeShapes[delta.id]) {
|
||||||
beforeShapes[delta.id] = deltaShape
|
beforeShapes[delta.id] = deltaShape
|
||||||
|
@ -256,7 +266,7 @@ export class TLDR {
|
||||||
afterShapes[delta.id] = cPage.shapes[delta.id]
|
afterShapes[delta.id] = cPage.shapes[delta.id]
|
||||||
|
|
||||||
if (deltaShape.children !== undefined) {
|
if (deltaShape.children !== undefined) {
|
||||||
this.recursivelyUpdateChildren(cData, delta.id, beforeShapes, afterShapes)
|
this.recursivelyUpdateChildren(cData, delta.id, beforeShapes, afterShapes, pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cData
|
return cData
|
||||||
|
@ -271,23 +281,24 @@ export class TLDR {
|
||||||
data: Data,
|
data: Data,
|
||||||
id: string,
|
id: string,
|
||||||
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||||
afterShapes: Record<string, Partial<TLDrawShape>> = {}
|
afterShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||||
|
pageId: string
|
||||||
): Data {
|
): Data {
|
||||||
const page = { ...this.getPage(data) }
|
const page = { ...this.getPage(data, pageId) }
|
||||||
const shape = this.getShape<T>(data, id)
|
const shape = this.getShape<T>(data, id, pageId)
|
||||||
|
|
||||||
if (page.id === 'doc') {
|
if (page.id === 'doc') {
|
||||||
throw Error('wtf')
|
throw Error('wtf')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shape.parentId !== page.id) {
|
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!')
|
if (!parent.children) throw Error('No children in parent!')
|
||||||
|
|
||||||
const delta = this.getShapeUtils(parent).onChildrenChange(
|
const delta = this.getShapeUtils(parent).onChildrenChange(
|
||||||
parent,
|
parent,
|
||||||
parent.children.map((childId) => this.getShape(data, childId))
|
parent.children.map((childId) => this.getShape(data, childId, pageId))
|
||||||
)
|
)
|
||||||
|
|
||||||
if (delta) {
|
if (delta) {
|
||||||
|
@ -299,7 +310,13 @@ export class TLDR {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent.parentId !== page.id) {
|
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,
|
data: Data,
|
||||||
id: string,
|
id: string,
|
||||||
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
beforeShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||||
afterShapes: Record<string, Partial<TLDrawShape>> = {}
|
afterShapes: Record<string, Partial<TLDrawShape>> = {},
|
||||||
|
pageId: string
|
||||||
): Data {
|
): Data {
|
||||||
const page = { ...this.getPage(data) }
|
const page = { ...this.getPage(data, pageId) }
|
||||||
return Object.values(page.bindings)
|
return Object.values(page.bindings)
|
||||||
.filter((binding) => binding.fromId === id || binding.toId === id)
|
.filter((binding) => binding.fromId === id || binding.toId === id)
|
||||||
.reduce((cData, binding) => {
|
.reduce((cData, binding) => {
|
||||||
if (!beforeShapes[binding.id]) {
|
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]) {
|
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(
|
this.onBindingChange(
|
||||||
cData,
|
cData,
|
||||||
this.getShape(cData, binding.fromId),
|
this.getShape(cData, binding.fromId, pageId),
|
||||||
binding,
|
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.fromId] = Utils.deepClone(this.getShape(cData, binding.fromId, pageId))
|
||||||
afterShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId))
|
afterShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId, pageId))
|
||||||
|
|
||||||
return cData
|
return cData
|
||||||
}, data)
|
}, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getChildIndexAbove(data: Data, id: string): number {
|
static getChildIndexAbove(data: Data, id: string, pageId: string): number {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
|
|
||||||
const shape = page.shapes[id]
|
const shape = page.shapes[id]
|
||||||
|
|
||||||
|
@ -387,7 +408,7 @@ export class TLDR {
|
||||||
data: Data,
|
data: Data,
|
||||||
ids: string[],
|
ids: string[],
|
||||||
fn: (shape: T, i: number) => Partial<T>,
|
fn: (shape: T, i: number) => Partial<T>,
|
||||||
pageId = data.appState.currentPageId
|
pageId: string
|
||||||
): {
|
): {
|
||||||
before: Record<string, Partial<T>>
|
before: Record<string, Partial<T>>
|
||||||
after: Record<string, Partial<T>>
|
after: Record<string, Partial<T>>
|
||||||
|
@ -408,15 +429,15 @@ export class TLDR {
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataWithChildrenChanges = ids.reduce<Data>((cData, id) => {
|
const dataWithChildrenChanges = ids.reduce<Data>((cData, id) => {
|
||||||
return this.recursivelyUpdateChildren(cData, id, beforeShapes, afterShapes)
|
return this.recursivelyUpdateChildren(cData, id, beforeShapes, afterShapes, pageId)
|
||||||
}, data)
|
}, data)
|
||||||
|
|
||||||
const dataWithParentChanges = ids.reduce<Data>((cData, id) => {
|
const dataWithParentChanges = ids.reduce<Data>((cData, id) => {
|
||||||
return this.recursivelyUpdateParents(cData, id, beforeShapes, afterShapes)
|
return this.recursivelyUpdateParents(cData, id, beforeShapes, afterShapes, pageId)
|
||||||
}, dataWithChildrenChanges)
|
}, dataWithChildrenChanges)
|
||||||
|
|
||||||
const dataWithBindingChanges = ids.reduce<Data>((cData, id) => {
|
const dataWithBindingChanges = ids.reduce<Data>((cData, id) => {
|
||||||
return this.updateBindings(cData, id, beforeShapes, afterShapes)
|
return this.updateBindings(cData, id, beforeShapes, afterShapes, pageId)
|
||||||
}, dataWithParentChanges)
|
}, dataWithParentChanges)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -429,7 +450,7 @@ export class TLDR {
|
||||||
static createShapes(
|
static createShapes(
|
||||||
data: Data,
|
data: Data,
|
||||||
shapes: TLDrawShape[],
|
shapes: TLDrawShape[],
|
||||||
pageId = data.appState.currentPageId
|
pageId: string
|
||||||
): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
|
): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
|
||||||
const before: DeepPartial<Data> = {
|
const before: DeepPartial<Data> = {
|
||||||
document: {
|
document: {
|
||||||
|
@ -496,8 +517,10 @@ export class TLDR {
|
||||||
static deleteShapes(
|
static deleteShapes(
|
||||||
data: Data,
|
data: Data,
|
||||||
shapes: TLDrawShape[] | string[],
|
shapes: TLDrawShape[] | string[],
|
||||||
pageId = data.appState.currentPageId
|
pageId?: string
|
||||||
): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
|
): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
|
||||||
|
pageId = pageId ? pageId : data.appState.currentPageId
|
||||||
|
|
||||||
const page = this.getPage(data, pageId)
|
const page = this.getPage(data, pageId)
|
||||||
|
|
||||||
const shapeIds =
|
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)
|
const delta = getShapeUtils(shape).onSessionComplete(shape)
|
||||||
if (!delta) return 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
|
if (!shape.children) return
|
||||||
|
|
||||||
const delta = getShapeUtils(shape).onChildrenChange(
|
const delta = getShapeUtils(shape).onChildrenChange(
|
||||||
shape,
|
shape,
|
||||||
shape.children.map((id) => this.getShape(data, id))
|
shape.children.map((id) => this.getShape(data, id, pageId))
|
||||||
)
|
)
|
||||||
if (!delta) return shape
|
if (!delta) return shape
|
||||||
return this.mutate(data, shape, delta)
|
return this.mutate(data, shape, delta, pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static onBindingChange<T extends TLDrawShape>(
|
static onBindingChange<T extends TLDrawShape>(
|
||||||
data: Data,
|
data: Data,
|
||||||
shape: T,
|
shape: T,
|
||||||
binding: TLDrawBinding,
|
binding: TLDrawBinding,
|
||||||
otherShape: TLDrawShape
|
otherShape: TLDrawShape,
|
||||||
|
pageId: string
|
||||||
) {
|
) {
|
||||||
const delta = getShapeUtils(shape).onBindingChange(
|
const delta = getShapeUtils(shape).onBindingChange(
|
||||||
shape,
|
shape,
|
||||||
|
@ -614,32 +638,39 @@ export class TLDR {
|
||||||
getShapeUtils(otherShape).getCenter(otherShape)
|
getShapeUtils(otherShape).getCenter(otherShape)
|
||||||
)
|
)
|
||||||
if (!delta) return shape
|
if (!delta) return shape
|
||||||
return this.mutate(data, shape, delta)
|
return this.mutate(data, shape, delta, pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static transform<T extends TLDrawShape>(
|
static transform<T extends TLDrawShape>(
|
||||||
data: Data,
|
data: Data,
|
||||||
shape: T,
|
shape: T,
|
||||||
bounds: TLBounds,
|
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>(
|
static transformSingle<T extends TLDrawShape>(
|
||||||
data: Data,
|
data: Data,
|
||||||
shape: T,
|
shape: T,
|
||||||
bounds: TLBounds,
|
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)
|
let next = getShapeUtils(shape).mutate(shape, props)
|
||||||
|
|
||||||
if (props.children) {
|
if (props.children) {
|
||||||
next = this.onChildrenChange(data, next) || next
|
next = this.onChildrenChange(data, next, pageId) || next
|
||||||
}
|
}
|
||||||
|
|
||||||
// data.page.shapes[next.id] = next
|
// data.page.shapes[next.id] = next
|
||||||
|
@ -651,12 +682,12 @@ export class TLDR {
|
||||||
/* Parents */
|
/* Parents */
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
static updateParents(data: Data, changedShapeIds: string[]): void {
|
static updateParents(data: Data, pageId: string, changedShapeIds: string[]): void {
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
|
|
||||||
if (changedShapeIds.length === 0) return
|
if (changedShapeIds.length === 0) return
|
||||||
|
|
||||||
const { shapes } = this.getPage(data)
|
const { shapes } = this.getPage(data, pageId)
|
||||||
|
|
||||||
const parentToUpdateIds = Array.from(
|
const parentToUpdateIds = Array.from(
|
||||||
new Set(changedShapeIds.map((id) => shapes[id].parentId).values())
|
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.')
|
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 {
|
static getSelectedStyle(data: Data, pageId: string): ShapeStyles | false {
|
||||||
const {
|
const { currentStyle } = data.appState
|
||||||
appState: { currentStyle },
|
|
||||||
} = data
|
|
||||||
|
|
||||||
const page = this.getPage(data)
|
const page = data.document.pages[pageId]
|
||||||
const pageState = this.getPageState(data)
|
const pageState = data.document.pageStates[pageId]
|
||||||
|
|
||||||
if (pageState.selectedIds.length === 0) {
|
if (pageState.selectedIds.length === 0) {
|
||||||
return currentStyle
|
return currentStyle
|
||||||
|
@ -718,36 +747,36 @@ export class TLDR {
|
||||||
/* Bindings */
|
/* 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]
|
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)
|
const page = this.getPage(data, pageId)
|
||||||
return Object.values(page.bindings)
|
return Object.values(page.bindings)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getBindableShapeIds(data: Data) {
|
static getBindableShapeIds(data: Data) {
|
||||||
return this.getShapes(data)
|
return this.getShapes(data, data.appState.currentPageId)
|
||||||
.filter((shape) => TLDR.getShapeUtils(shape).canBind)
|
.filter((shape) => TLDR.getShapeUtils(shape).canBind)
|
||||||
.sort((a, b) => b.childIndex - a.childIndex)
|
.sort((a, b) => b.childIndex - a.childIndex)
|
||||||
.map((shape) => shape.id)
|
.map((shape) => shape.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getBindingsWithShapeIds(data: Data, ids: string[]): TLDrawBinding[] {
|
static getBindingsWithShapeIds(data: Data, ids: string[], pageId: string): TLDrawBinding[] {
|
||||||
return Array.from(
|
return Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
this.getBindings(data).filter((binding) => {
|
this.getBindings(data, pageId).filter((binding) => {
|
||||||
return ids.includes(binding.toId) || ids.includes(binding.fromId)
|
return ids.includes(binding.toId) || ids.includes(binding.fromId)
|
||||||
})
|
})
|
||||||
).values()
|
).values()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRelatedBindings(data: Data, ids: string[]): TLDrawBinding[] {
|
static getRelatedBindings(data: Data, ids: string[], pageId: string): TLDrawBinding[] {
|
||||||
const changedShapeIds = new Set(ids)
|
const changedShapeIds = new Set(ids)
|
||||||
|
|
||||||
const page = this.getPage(data)
|
const page = this.getPage(data, pageId)
|
||||||
|
|
||||||
// Find all bindings that we need to update
|
// Find all bindings that we need to update
|
||||||
const bindingsArr = Object.values(page.bindings)
|
const bindingsArr = Object.values(page.bindings)
|
||||||
|
|
|
@ -28,6 +28,22 @@ describe('TLDrawState', () => {
|
||||||
|
|
||||||
expect(Object.keys(tlstate.page.shapes).length).toBe(prevCount + 1)
|
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', () => {
|
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 <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