Parcourir la source

Mostly fixed bugs

main
Steve Ruiz il y a 3 ans
Parent
révision
ad3db2c0ac
45 fichiers modifiés avec 1110 ajouts et 983 suppressions
  1. 1
    1
      packages/dev/src/components/editor.tsx
  2. 5
    3
      packages/dev/src/hooks/usePersistence.tsx
  3. 6
    4
      packages/tldraw/src/components/context-menu/context-menu.tsx
  4. 0
    4
      packages/tldraw/src/components/menu/menu.tsx
  5. 1
    2
      packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx
  6. 6
    5
      packages/tldraw/src/components/page-panel/page-panel.tsx
  7. 1
    1
      packages/tldraw/src/components/style-panel/quick-color-select.tsx
  8. 1
    1
      packages/tldraw/src/components/style-panel/quick-dash-select.tsx
  9. 1
    1
      packages/tldraw/src/components/style-panel/quick-fill-select.tsx
  10. 1
    1
      packages/tldraw/src/components/style-panel/quick-size-select.tsx
  11. 19
    7
      packages/tldraw/src/components/style-panel/shapes-functions.tsx
  12. 3
    1
      packages/tldraw/src/components/style-panel/style-panel.tsx
  13. 7
    5
      packages/tldraw/src/components/tldraw/tldraw.tsx
  14. 2
    1
      packages/tldraw/src/components/tools-panel/back-to-content.tsx
  15. 1
    1
      packages/tldraw/src/components/tools-panel/zoom.tsx
  16. 10
    6
      packages/tldraw/src/state/command/align/align.command.ts
  17. 32
    10
      packages/tldraw/src/state/command/create/create.command.ts
  18. 20
    9
      packages/tldraw/src/state/command/delete/delete.command.ts
  19. 0
    5
      packages/tldraw/src/state/command/distribute/distribute.command.spec.ts
  20. 7
    7
      packages/tldraw/src/state/command/distribute/distribute.command.ts
  21. 14
    16
      packages/tldraw/src/state/command/duplicate/duplicate.command.ts
  22. 7
    7
      packages/tldraw/src/state/command/flip/flip.command.ts
  23. 2
    1
      packages/tldraw/src/state/command/move/move.command.spec.ts
  24. 16
    11
      packages/tldraw/src/state/command/move/move.command.ts
  25. 20
    15
      packages/tldraw/src/state/command/rotate/rotate.command.ts
  26. 7
    7
      packages/tldraw/src/state/command/stretch/stretch.command.ts
  27. 6
    6
      packages/tldraw/src/state/command/style/style.command.ts
  28. 6
    6
      packages/tldraw/src/state/command/toggle-decoration/toggle-decoration.command.ts
  29. 11
    7
      packages/tldraw/src/state/command/toggle/toggle.command.ts
  30. 9
    7
      packages/tldraw/src/state/command/translate/translate.command.ts
  31. 7
    12
      packages/tldraw/src/state/session/sessions/arrow/arrow.session.spec.ts
  32. 78
    57
      packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts
  33. 29
    19
      packages/tldraw/src/state/session/sessions/brush/brush.session.ts
  34. 54
    37
      packages/tldraw/src/state/session/sessions/draw/draw.session.ts
  35. 34
    32
      packages/tldraw/src/state/session/sessions/handle/handle.session.ts
  36. 61
    59
      packages/tldraw/src/state/session/sessions/rotate/rotate.session.ts
  37. 57
    37
      packages/tldraw/src/state/session/sessions/text/text.session.ts
  38. 50
    31
      packages/tldraw/src/state/session/sessions/transform-single/transform-single.session.ts
  39. 73
    85
      packages/tldraw/src/state/session/sessions/transform/transform.session.ts
  40. 154
    139
      packages/tldraw/src/state/session/sessions/translate/translate.session.ts
  41. 223
    270
      packages/tldraw/src/state/tldr.ts
  42. 42
    26
      packages/tldraw/src/state/tlstate.ts
  43. 8
    5
      packages/tldraw/src/types.ts
  44. 1
    1
      packages/www/components/editor.tsx
  45. 17
    15
      packages/www/hooks/usePersistence.tsx

+ 1
- 1
packages/dev/src/components/editor.tsx Voir le fichier

@@ -102,5 +102,5 @@ export default function Editor(): JSX.Element {
102 102
     return <div />
103 103
   }
104 104
 
105
-  return <TLDraw document={value} onChange={handleChange} />
105
+  return <TLDraw document={initialDoc} onChange={handleChange} />
106 106
 }

+ 5
- 3
packages/dev/src/hooks/usePersistence.tsx Voir le fichier

@@ -3,6 +3,8 @@ import * as React from 'react'
3 3
 import { openDB, DBSchema } from 'idb'
4 4
 import type { TLDrawDocument } from '@tldraw/tldraw'
5 5
 
6
+const VERSION = 1
7
+
6 8
 interface TLDatabase extends DBSchema {
7 9
   documents: {
8 10
     key: string
@@ -33,7 +35,7 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
33 35
     _setValue(null)
34 36
     setStatus('loading')
35 37
 
36
-    openDB<TLDatabase>('db', 1).then((db) =>
38
+    openDB<TLDatabase>('db', VERSION).then((db) =>
37 39
       db.get('documents', id).then((v) => {
38 40
         if (!v) throw Error(`Could not find document with id: ${id}`)
39 41
         _setValue(v)
@@ -46,7 +48,7 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
46 48
   // value in the database.
47 49
   const setValue = React.useCallback(
48 50
     (doc: TLDrawDocument) => {
49
-      openDB<TLDatabase>('db', 1).then((db) => db.put('documents', doc, id))
51
+      openDB<TLDatabase>('db', VERSION).then((db) => db.put('documents', doc, id))
50 52
     },
51 53
     [id]
52 54
   )
@@ -55,7 +57,7 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
55 57
   // the state.
56 58
   React.useEffect(() => {
57 59
     async function handleLoad() {
58
-      const db = await openDB<TLDatabase>('db', 1, {
60
+      const db = await openDB<TLDatabase>('db', VERSION, {
59 61
         upgrade(db) {
60 62
           db.createObjectStore('documents')
61 63
         },

+ 6
- 4
packages/tldraw/src/components/context-menu/context-menu.tsx Voir le fichier

@@ -32,13 +32,13 @@ import {
32 32
 } from '@radix-ui/react-icons'
33 33
 
34 34
 const has1SelectedIdsSelector = (s: Data) => {
35
-  return s.pageState.selectedIds.length > 0
35
+  return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 0
36 36
 }
37 37
 const has2SelectedIdsSelector = (s: Data) => {
38
-  return s.pageState.selectedIds.length > 1
38
+  return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 1
39 39
 }
40 40
 const has3SelectedIdsSelector = (s: Data) => {
41
-  return s.pageState.selectedIds.length > 2
41
+  return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 2
42 42
 }
43 43
 
44 44
 const isDebugModeSelector = (s: Data) => {
@@ -46,7 +46,9 @@ const isDebugModeSelector = (s: Data) => {
46 46
 }
47 47
 
48 48
 const hasGroupSelectedSelector = (s: Data) => {
49
-  return s.pageState.selectedIds.some((id) => s.page.shapes[id].children !== undefined)
49
+  return s.document.pageStates[s.appState.currentPageId].selectedIds.some(
50
+    (id) => s.document.pages[s.appState.currentPageId].shapes[id].children !== undefined
51
+  )
50 52
 }
51 53
 
52 54
 interface ContextMenuProps {

+ 0
- 4
packages/tldraw/src/components/menu/menu.tsx Voir le fichier

@@ -31,10 +31,6 @@ export const Menu = React.memo(() => {
31 31
     tlstate.loadProject()
32 32
   }, [tlstate])
33 33
 
34
-  const toggleDebugMode = React.useCallback(() => {
35
-    tlstate.toggleDebugMode()
36
-  }, [tlstate])
37
-
38 34
   const handleSignOut = React.useCallback(() => {
39 35
     tlstate.signOut()
40 36
   }, [tlstate])

+ 1
- 2
packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx Voir le fichier

@@ -15,8 +15,7 @@ import type { Data, TLDrawPage } from '~types'
15 15
 import { useTLDrawContext } from '~hooks'
16 16
 
17 17
 const canDeleteSelector = (s: Data) => {
18
-  // TODO: Include all pages
19
-  return [s.page].length <= 1
18
+  return Object.keys(s.document.pages).length <= 1
20 19
 }
21 20
 
22 21
 export function PageOptionsDialog({ page }: { page: TLDrawPage }): JSX.Element {

+ 6
- 5
packages/tldraw/src/components/page-panel/page-panel.tsx Voir le fichier

@@ -15,7 +15,10 @@ import styled from '~styles'
15 15
 import { useTLDrawContext } from '~hooks'
16 16
 import type { Data } from '~types'
17 17
 
18
-const currentPageSelector = (s: Data) => s.page
18
+const sortedSelector = (s: Data) =>
19
+  Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
20
+
21
+const currentPageSelector = (s: Data) => s.document.pages[s.appState.currentPageId]
19 22
 
20 23
 export function PagePanel(): JSX.Element {
21 24
   const rIsOpen = React.useRef(false)
@@ -43,9 +46,7 @@ export function PagePanel(): JSX.Element {
43 46
 
44 47
   const currentPage = useSelector(currentPageSelector)
45 48
 
46
-  const sorted = Object.values([currentPage]).sort(
47
-    (a, b) => (a.childIndex || 0) - (b.childIndex || 0)
48
-  )
49
+  const sortedPages = useSelector(sortedSelector)
49 50
 
50 51
   return (
51 52
     <DropdownMenu.Root
@@ -64,7 +65,7 @@ export function PagePanel(): JSX.Element {
64 65
       </FloatingContainer>
65 66
       <MenuContent as={DropdownMenu.Content} sideOffset={8} align="start">
66 67
         <DropdownMenu.RadioGroup value={currentPage.id} onValueChange={handleChangePage}>
67
-          {sorted.map((page) => (
68
+          {sortedPages.map((page) => (
68 69
             <ButtonWithOptions key={page.id}>
69 70
               <DropdownMenu.RadioItem
70 71
                 as={RowButton}

+ 1
- 1
packages/tldraw/src/components/style-panel/quick-color-select.tsx Voir le fichier

@@ -6,7 +6,7 @@ import { strokes } from '~shape'
6 6
 import { useTheme, useTLDrawContext } from '~hooks'
7 7
 import type { Data, ColorStyle } from '~types'
8 8
 
9
-const selectColor = (data: Data) => data.appState.selectedStyle.color
9
+const selectColor = (s: Data) => s.appState.selectedStyle.color
10 10
 
11 11
 export const QuickColorSelect = React.memo((): JSX.Element => {
12 12
   const { theme } = useTheme()

+ 1
- 1
packages/tldraw/src/components/style-panel/quick-dash-select.tsx Voir le fichier

@@ -19,7 +19,7 @@ const dashes = {
19 19
   [DashStyle.Dotted]: <DashDottedIcon />,
20 20
 }
21 21
 
22
-const selectDash = (data: Data) => data.appState.selectedStyle.dash
22
+const selectDash = (s: Data) => s.appState.selectedStyle.dash
23 23
 
24 24
 export const QuickDashSelect = React.memo((): JSX.Element => {
25 25
   const { tlstate, useSelector } = useTLDrawContext()

+ 1
- 1
packages/tldraw/src/components/style-panel/quick-fill-select.tsx Voir le fichier

@@ -5,7 +5,7 @@ import { breakpoints, Tooltip, IconButton, IconWrapper } from '../shared'
5 5
 import { useTLDrawContext } from '~hooks'
6 6
 import type { Data } from '~types'
7 7
 
8
-const isFilledSelector = (data: Data) => data.appState.selectedStyle.isFilled
8
+const isFilledSelector = (s: Data) => s.appState.selectedStyle.isFilled
9 9
 
10 10
 export const QuickFillSelect = React.memo((): JSX.Element => {
11 11
   const { tlstate, useSelector } = useTLDrawContext()

+ 1
- 1
packages/tldraw/src/components/style-panel/quick-size-select.tsx Voir le fichier

@@ -12,7 +12,7 @@ const sizes = {
12 12
   [SizeStyle.Large]: 22,
13 13
 }
14 14
 
15
-const selectSize = (data: Data) => data.appState.selectedStyle.size
15
+const selectSize = (s: Data) => s.appState.selectedStyle.size
16 16
 
17 17
 export const QuickSizeSelect = React.memo((): JSX.Element => {
18 18
   const { tlstate, useSelector } = useTLDrawContext()

+ 19
- 7
packages/tldraw/src/components/style-panel/shapes-functions.tsx Voir le fichier

@@ -18,17 +18,23 @@ import { useTLDrawContext } from '~hooks'
18 18
 import type { Data } from '~types'
19 19
 
20 20
 const isAllLockedSelector = (s: Data) => {
21
-  const { selectedIds } = s.pageState
22
-  return selectedIds.every((id) => s.page.shapes[id].isLocked)
21
+  const page = s.document.pages[s.appState.currentPageId]
22
+  const { selectedIds } = s.document.pageStates[s.appState.currentPageId]
23
+  return selectedIds.every((id) => page.shapes[id].isLocked)
23 24
 }
24 25
 
25 26
 const isAllAspectLockedSelector = (s: Data) => {
26
-  const { selectedIds } = s.pageState
27
-  return selectedIds.every((id) => s.page.shapes[id].isAspectRatioLocked)
27
+  const page = s.document.pages[s.appState.currentPageId]
28
+  const { selectedIds } = s.document.pageStates[s.appState.currentPageId]
29
+  return selectedIds.every((id) => page.shapes[id].isAspectRatioLocked)
28 30
 }
29 31
 
30 32
 const isAllGroupedSelector = (s: Data) => {
31
-  const selectedShapes = s.pageState.selectedIds.map((id) => s.page.shapes[id])
33
+  const page = s.document.pages[s.appState.currentPageId]
34
+  const selectedShapes = s.document.pageStates[s.appState.currentPageId].selectedIds.map(
35
+    (id) => page.shapes[id]
36
+  )
37
+
32 38
   return selectedShapes.every(
33 39
     (shape) =>
34 40
       shape.children !== undefined ||
@@ -37,9 +43,15 @@ const isAllGroupedSelector = (s: Data) => {
37 43
   )
38 44
 }
39 45
 
40
-const hasSelectionSelector = (s: Data) => s.pageState.selectedIds.length > 0
46
+const hasSelectionSelector = (s: Data) => {
47
+  const { selectedIds } = s.document.pageStates[s.appState.currentPageId]
48
+  return selectedIds.length > 0
49
+}
41 50
 
42
-const hasMultipleSelectionSelector = (s: Data) => s.pageState.selectedIds.length > 1
51
+const hasMultipleSelectionSelector = (s: Data) => {
52
+  const { selectedIds } = s.document.pageStates[s.appState.currentPageId]
53
+  return selectedIds.length > 1
54
+}
43 55
 
44 56
 export const ShapesFunctions = React.memo(() => {
45 57
   const { tlstate, useSelector } = useTLDrawContext()

+ 3
- 1
packages/tldraw/src/components/style-panel/style-panel.tsx Voir le fichier

@@ -50,7 +50,9 @@ export function StylePanel(): JSX.Element {
50 50
 }
51 51
 
52 52
 const showKbds = !Utils.isMobile()
53
-const selectedShapesCountSelector = (s: Data) => s.pageState.selectedIds.length
53
+
54
+const selectedShapesCountSelector = (s: Data) =>
55
+  s.document.pageStates[s.appState.currentPageId].selectedIds.length
54 56
 
55 57
 function SelectedShapeContent(): JSX.Element {
56 58
   const { tlstate, useSelector } = useTLDrawContext()

+ 7
- 5
packages/tldraw/src/components/tldraw/tldraw.tsx Voir le fichier

@@ -18,11 +18,13 @@ export interface TLDrawProps {
18 18
 }
19 19
 
20 20
 const isInSelectSelector = (s: Data) => s.appState.activeTool === 'select'
21
-const isSelectedShapeWithHandlesSelector = (s: Data) =>
22
-  s.pageState.selectedIds.length === 1 &&
23
-  s.pageState.selectedIds.every((id) => s.page.shapes[id].handles !== undefined)
24
-const pageSelector = (s: Data) => s.page
25
-const pageStateSelector = (s: Data) => s.pageState
21
+const isSelectedShapeWithHandlesSelector = (s: Data) => {
22
+  const { shapes } = s.document.pages[s.appState.currentPageId]
23
+  const { selectedIds } = s.document.pageStates[s.appState.currentPageId]
24
+  return selectedIds.length === 1 && selectedIds.every((id) => shapes[id].handles !== undefined)
25
+}
26
+const pageSelector = (s: Data) => s.document.pages[s.appState.currentPageId]
27
+const pageStateSelector = (s: Data) => s.document.pageStates[s.appState.currentPageId]
26 28
 
27 29
 export function TLDraw({ document, currentPageId, onMount, onChange: _onChange }: TLDrawProps) {
28 30
   const [tlstate] = React.useState(() => new TLDrawState())

+ 2
- 1
packages/tldraw/src/components/tools-panel/back-to-content.tsx Voir le fichier

@@ -5,7 +5,8 @@ import type { Data } from '~types'
5 5
 import { useTLDrawContext } from '~hooks'
6 6
 
7 7
 const isEmptyCanvasSelector = (s: Data) =>
8
-  Object.keys(s.page.shapes).length > 0 && s.appState.isEmptyCanvas
8
+  Object.keys(s.document.pages[s.appState.currentPageId].shapes).length > 0 &&
9
+  s.appState.isEmptyCanvas
9 10
 
10 11
 export const BackToContent = React.memo(() => {
11 12
   const { tlstate, useSelector } = useTLDrawContext()

+ 1
- 1
packages/tldraw/src/components/tools-panel/zoom.tsx Voir le fichier

@@ -20,7 +20,7 @@ export const Zoom = React.memo((): JSX.Element => {
20 20
   )
21 21
 })
22 22
 
23
-const zoomSelector = (s: Data) => s.pageState.camera.zoom
23
+const zoomSelector = (s: Data) => s.document.pageStates[s.appState.currentPageId].camera.zoom
24 24
 
25 25
 function ZoomCounter() {
26 26
   const { tlstate, useSelector } = useTLDrawContext()

+ 10
- 6
packages/tldraw/src/state/command/align/align.command.ts Voir le fichier

@@ -46,16 +46,20 @@ export function align(data: Data, ids: string[], type: AlignType): Command {
46 46
   return {
47 47
     id: 'align_shapes',
48 48
     before: {
49
-      page: {
50
-        shapes: {
51
-          ...before,
49
+      document: {
50
+        pages: {
51
+          [data.appState.currentPageId]: {
52
+            shapes: before,
53
+          },
52 54
         },
53 55
       },
54 56
     },
55 57
     after: {
56
-      page: {
57
-        shapes: {
58
-          ...after,
58
+      document: {
59
+        pages: {
60
+          [data.appState.currentPageId]: {
61
+            shapes: after,
62
+          },
59 63
         },
60 64
       },
61 65
     },

+ 32
- 10
packages/tldraw/src/state/command/create/create.command.ts Voir le fichier

@@ -1,22 +1,44 @@
1
+import type { DeepPartial } from '~../../core/dist/types/utils/utils'
2
+import { TLDR } from '~state/tldr'
1 3
 import type { TLDrawShape, Data, Command } from '~types'
2 4
 
3 5
 export function create(data: Data, shapes: TLDrawShape[]): Command {
6
+  const beforeShapes: Record<string, DeepPartial<TLDrawShape> | undefined> = {}
7
+  const afterShapes: Record<string, DeepPartial<TLDrawShape> | undefined> = {}
8
+
9
+  shapes.forEach((shape) => {
10
+    beforeShapes[shape.id] = undefined
11
+    afterShapes[shape.id] = shape
12
+  })
13
+
4 14
   return {
5 15
     id: 'toggle_shapes',
6 16
     before: {
7
-      page: {
8
-        shapes: Object.fromEntries(shapes.map((shape) => [shape.id, undefined])),
9
-      },
10
-      pageState: {
11
-        selectedIds: [...data.pageState.selectedIds],
17
+      document: {
18
+        pages: {
19
+          [data.appState.currentPageId]: {
20
+            shapes: beforeShapes,
21
+          },
22
+        },
23
+        pageStates: {
24
+          [data.appState.currentPageId]: {
25
+            selectedIds: [...TLDR.getSelectedIds(data)],
26
+          },
27
+        },
12 28
       },
13 29
     },
14 30
     after: {
15
-      page: {
16
-        shapes: Object.fromEntries(shapes.map((shape) => [shape.id, shape])),
17
-      },
18
-      pageState: {
19
-        selectedIds: shapes.map((shape) => shape.id),
31
+      document: {
32
+        pages: {
33
+          [data.appState.currentPageId]: {
34
+            shapes: afterShapes,
35
+          },
36
+        },
37
+        pageStates: {
38
+          [data.appState.currentPageId]: {
39
+            selectedIds: shapes.map((shape) => shape.id),
40
+          },
41
+        },
20 42
       },
21 43
     },
22 44
   }

+ 20
- 9
packages/tldraw/src/state/command/delete/delete.command.ts Voir le fichier

@@ -1,3 +1,4 @@
1
+import { TLDR } from '~state/tldr'
1 2
 import type { Data, Command, PagePartial } from '~types'
2 3
 
3 4
 // - [x] Delete shapes
@@ -17,12 +18,14 @@ export function deleteShapes(data: Data, ids: string[]): Command {
17 18
 
18 19
   // These are the shapes we're definitely going to delete
19 20
   ids.forEach((id) => {
20
-    before.shapes[id] = data.page.shapes[id]
21
+    before.shapes[id] = TLDR.getShape(data, id)
21 22
     after.shapes[id] = undefined
22 23
   })
23 24
 
25
+  const page = TLDR.getPage(data)
26
+
24 27
   // We also need to delete bindings that reference the deleted shapes
25
-  Object.values(data.page.bindings).forEach((binding) => {
28
+  Object.values(page.bindings).forEach((binding) => {
26 29
     for (const id of [binding.toId, binding.fromId]) {
27 30
       // If the binding references a deleted shape...
28 31
       if (after.shapes[id] === undefined) {
@@ -31,7 +34,7 @@ export function deleteShapes(data: Data, ids: string[]): Command {
31 34
         after.bindings[binding.id] = undefined
32 35
 
33 36
         // Let's also look at the bound shape...
34
-        const shape = data.page.shapes[id]
37
+        const shape = TLDR.getShape(data, id)
35 38
 
36 39
         // If the bound shape has a handle that references the deleted binding, delete that reference
37 40
         if (shape.handles) {
@@ -55,15 +58,23 @@ export function deleteShapes(data: Data, ids: string[]): Command {
55 58
   return {
56 59
     id: 'delete_shapes',
57 60
     before: {
58
-      page: before,
59
-      pageState: {
60
-        selectedIds: [...data.pageState.selectedIds],
61
+      document: {
62
+        pages: {
63
+          [data.appState.currentPageId]: before,
64
+        },
65
+        pageStates: {
66
+          [data.appState.currentPageId]: { selectedIds: TLDR.getSelectedIds(data) },
67
+        },
61 68
       },
62 69
     },
63 70
     after: {
64
-      page: after,
65
-      pageState: {
66
-        selectedIds: [],
71
+      document: {
72
+        pages: {
73
+          [data.appState.currentPageId]: after,
74
+        },
75
+        pageStates: {
76
+          [data.appState.currentPageId]: { selectedIds: [] },
77
+        },
67 78
       },
68 79
     },
69 80
   }

+ 0
- 5
packages/tldraw/src/state/command/distribute/distribute.command.spec.ts Voir le fichier

@@ -9,15 +9,10 @@ describe('Distribute command', () => {
9 9
 
10 10
   it('does, undoes and redoes command', () => {
11 11
     tlstate.distribute(DistributeType.Horizontal)
12
-
13 12
     expect(tlstate.getShape('rect3').point).toEqual([50, 20])
14
-
15 13
     tlstate.undo()
16
-
17 14
     expect(tlstate.getShape('rect3').point).toEqual([20, 20])
18
-
19 15
     tlstate.redo()
20
-
21 16
     expect(tlstate.getShape('rect3').point).toEqual([50, 20])
22 17
   })
23 18
 

+ 7
- 7
packages/tldraw/src/state/command/distribute/distribute.command.ts Voir le fichier

@@ -3,7 +3,7 @@ import { DistributeType, TLDrawShape, Data, Command } from '~types'
3 3
 import { TLDR } from '~state/tldr'
4 4
 
5 5
 export function distribute(data: Data, ids: string[], type: DistributeType): Command {
6
-  const initialShapes = ids.map((id) => data.page.shapes[id])
6
+  const initialShapes = ids.map((id) => TLDR.getShape(data, id))
7 7
   const deltaMap = Object.fromEntries(getDistributions(initialShapes, type).map((d) => [d.id, d]))
8 8
 
9 9
   const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
@@ -14,16 +14,16 @@ export function distribute(data: Data, ids: string[], type: DistributeType): Com
14 14
   return {
15 15
     id: 'distribute_shapes',
16 16
     before: {
17
-      page: {
18
-        shapes: {
19
-          ...before,
17
+      document: {
18
+        pages: {
19
+          [data.appState.currentPageId]: { shapes: before },
20 20
         },
21 21
       },
22 22
     },
23 23
     after: {
24
-      page: {
25
-        shapes: {
26
-          ...after,
24
+      document: {
25
+        pages: {
26
+          [data.appState.currentPageId]: { shapes: after },
27 27
         },
28 28
       },
29 29
     },

+ 14
- 16
packages/tldraw/src/state/command/duplicate/duplicate.command.ts Voir le fichier

@@ -3,11 +3,11 @@ import { TLDR } from '~state/tldr'
3 3
 import type { Data, Command } from '~types'
4 4
 
5 5
 export function duplicate(data: Data, ids: string[]): Command {
6
-  const delta = Vec.div([16, 16], data.pageState.camera.zoom)
6
+  const delta = Vec.div([16, 16], TLDR.getCamera(data).zoom)
7 7
 
8 8
   const after = Object.fromEntries(
9 9
     TLDR.getSelectedIds(data)
10
-      .map((id) => data.page.shapes[id])
10
+      .map((id) => TLDR.getShape(data, id))
11 11
       .map((shape) => {
12 12
         const id = Utils.uniqueId()
13 13
         return [
@@ -26,25 +26,23 @@ export function duplicate(data: Data, ids: string[]): Command {
26 26
   return {
27 27
     id: 'duplicate',
28 28
     before: {
29
-      page: {
30
-        shapes: {
31
-          ...before,
29
+      document: {
30
+        pages: {
31
+          [data.appState.currentPageId]: { shapes: before },
32
+        },
33
+        pageStates: {
34
+          [data.appState.currentPageId]: { selectedIds: ids },
32 35
         },
33
-      },
34
-      pageState: {
35
-        ...data.pageState,
36
-        selectedIds: ids,
37 36
       },
38 37
     },
39 38
     after: {
40
-      page: {
41
-        shapes: {
42
-          ...after,
39
+      document: {
40
+        pages: {
41
+          [data.appState.currentPageId]: { shapes: after },
42
+        },
43
+        pageStates: {
44
+          [data.appState.currentPageId]: { selectedIds: Object.keys(after) },
43 45
         },
44
-      },
45
-      pageState: {
46
-        ...data.pageState,
47
-        selectedIds: Object.keys(after),
48 46
       },
49 47
     },
50 48
   }

+ 7
- 7
packages/tldraw/src/state/command/flip/flip.command.ts Voir le fichier

@@ -4,7 +4,7 @@ import type { Data, Command } from '~types'
4 4
 import { TLDR } from '~state/tldr'
5 5
 
6 6
 export function flip(data: Data, ids: string[], type: FlipType): Command {
7
-  const initialShapes = ids.map((id) => data.page.shapes[id])
7
+  const initialShapes = ids.map((id) => TLDR.getShape(data, id))
8 8
 
9 9
   const boundsForShapes = initialShapes.map((shape) => TLDR.getBounds(shape))
10 10
 
@@ -54,16 +54,16 @@ export function flip(data: Data, ids: string[], type: FlipType): Command {
54 54
   return {
55 55
     id: 'flip_shapes',
56 56
     before: {
57
-      page: {
58
-        shapes: {
59
-          ...before,
57
+      document: {
58
+        pages: {
59
+          [data.appState.currentPageId]: { shapes: before },
60 60
         },
61 61
       },
62 62
     },
63 63
     after: {
64
-      page: {
65
-        shapes: {
66
-          ...after,
64
+      document: {
65
+        pages: {
66
+          [data.appState.currentPageId]: { shapes: after },
67 67
         },
68 68
       },
69 69
     },

+ 2
- 1
packages/tldraw/src/state/command/move/move.command.spec.ts Voir le fichier

@@ -2,6 +2,7 @@ import { TLDrawState } from '~state'
2 2
 import { mockDocument } from '~test'
3 3
 import { Utils } from '@tldraw/core'
4 4
 import type { Data } from '~types'
5
+import { TLDR } from '~state/tldr'
5 6
 
6 7
 const doc = Utils.deepClone(mockDocument)
7 8
 
@@ -32,7 +33,7 @@ delete doc.pages.page1.shapes['rect2']
32 33
 delete doc.pages.page1.shapes['rect3']
33 34
 
34 35
 function getSortedShapeIds(data: Data) {
35
-  return Object.values(data.page.shapes)
36
+  return TLDR.getShapes(data)
36 37
     .sort((a, b) => a.childIndex - b.childIndex)
37 38
     .map((shape) => shape.id)
38 39
     .join('')

+ 16
- 11
packages/tldraw/src/state/command/move/move.command.ts Voir le fichier

@@ -3,27 +3,30 @@ import { TLDR } from '~state/tldr'
3 3
 
4 4
 export function move(data: Data, ids: string[], type: MoveType): Command {
5 5
   // Get the unique parent ids for the selected elements
6
-  const parentIds = new Set(ids.map((id) => data.page.shapes[id].parentId))
6
+  const parentIds = new Set(ids.map((id) => TLDR.getShape(data, id).parentId))
7 7
 
8 8
   let result: {
9 9
     before: Record<string, Partial<TLDrawShape>>
10 10
     after: Record<string, Partial<TLDrawShape>>
11 11
   } = { before: {}, after: {} }
12
+
12 13
   let startIndex: number
13 14
   let startChildIndex: number
14 15
   let step: number
15 16
 
17
+  const page = TLDR.getPage(data)
18
+
16 19
   // Collect shapes with common parents into a table under their parent id
17 20
   Array.from(parentIds.values()).forEach((parentId) => {
18 21
     let sortedChildren: TLDrawShape[] = []
19
-    if (parentId === data.page.id) {
20
-      sortedChildren = Object.values(data.page.shapes).sort((a, b) => a.childIndex - b.childIndex)
22
+    if (parentId === page.id) {
23
+      sortedChildren = Object.values(page.shapes).sort((a, b) => a.childIndex - b.childIndex)
21 24
     } else {
22
-      const parent = data.page.shapes[parentId]
25
+      const parent = TLDR.getShape(data, parentId)
23 26
       if (!parent.children) throw Error('No children in parent!')
24 27
 
25 28
       sortedChildren = parent.children
26
-        .map((childId) => data.page.shapes[childId])
29
+        .map((childId) => TLDR.getShape(data, childId))
27 30
         .sort((a, b) => a.childIndex - b.childIndex)
28 31
     }
29 32
 
@@ -197,15 +200,17 @@ export function move(data: Data, ids: string[], type: MoveType): Command {
197 200
   return {
198 201
     id: 'move_shapes',
199 202
     before: {
200
-      page: {
201
-        ...data.page,
202
-        shapes: result?.before || {},
203
+      document: {
204
+        pages: {
205
+          [data.appState.currentPageId]: { shapes: result.before },
206
+        },
203 207
       },
204 208
     },
205 209
     after: {
206
-      page: {
207
-        ...data.page,
208
-        shapes: result?.after || {},
210
+      document: {
211
+        pages: {
212
+          [data.appState.currentPageId]: { shapes: result.after },
213
+        },
209 214
       },
210 215
     },
211 216
   }

+ 20
- 15
packages/tldraw/src/state/command/rotate/rotate.command.ts Voir le fichier

@@ -5,7 +5,7 @@ import { TLDR } from '~state/tldr'
5 5
 const PI2 = Math.PI * 2
6 6
 
7 7
 export function rotate(data: Data, ids: string[], delta = -PI2 / 4): Command {
8
-  const initialShapes = ids.map((id) => data.page.shapes[id])
8
+  const initialShapes = ids.map((id) => TLDR.getShape(data, id))
9 9
 
10 10
   const boundsForShapes = initialShapes.map((shape) => {
11 11
     const utils = TLDR.getShapeUtils(shape)
@@ -31,31 +31,36 @@ export function rotate(data: Data, ids: string[], delta = -PI2 / 4): Command {
31 31
     })
32 32
   )
33 33
 
34
-  const prevBoundsRotation = data.pageState.boundsRotation
35
-  const nextBoundsRotation = (PI2 + ((data.pageState.boundsRotation || 0) + delta)) % PI2
34
+  const pageState = TLDR.getPageState(data)
35
+  const prevBoundsRotation = pageState.boundsRotation
36
+  const nextBoundsRotation = (PI2 + ((pageState.boundsRotation || 0) + delta)) % PI2
36 37
 
37 38
   const { before, after } = TLDR.mutateShapes(data, ids, (shape) => rotations[shape.id])
38 39
 
39 40
   return {
40 41
     id: 'toggle_shapes',
41 42
     before: {
42
-      page: {
43
-        shapes: {
44
-          ...before,
43
+      document: {
44
+        pages: {
45
+          [data.appState.currentPageId]: { shapes: before },
46
+        },
47
+        pageStates: {
48
+          [data.appState.currentPageId]: {
49
+            boundsRotation: prevBoundsRotation,
50
+          },
45 51
         },
46
-      },
47
-      pageState: {
48
-        boundsRotation: prevBoundsRotation,
49 52
       },
50 53
     },
51 54
     after: {
52
-      page: {
53
-        shapes: {
54
-          ...after,
55
+      document: {
56
+        pages: {
57
+          [data.appState.currentPageId]: { shapes: after },
58
+        },
59
+        pageStates: {
60
+          [data.appState.currentPageId]: {
61
+            boundsRotation: nextBoundsRotation,
62
+          },
55 63
         },
56
-      },
57
-      pageState: {
58
-        boundsRotation: nextBoundsRotation,
59 64
       },
60 65
     },
61 66
   }

+ 7
- 7
packages/tldraw/src/state/command/stretch/stretch.command.ts Voir le fichier

@@ -4,7 +4,7 @@ import type { Data, Command } from '~types'
4 4
 import { TLDR } from '~state/tldr'
5 5
 
6 6
 export function stretch(data: Data, ids: string[], type: StretchType): Command {
7
-  const initialShapes = ids.map((id) => data.page.shapes[id])
7
+  const initialShapes = ids.map((id) => TLDR.getShape(data, id))
8 8
 
9 9
   const boundsForShapes = initialShapes.map((shape) => TLDR.getBounds(shape))
10 10
 
@@ -52,16 +52,16 @@ export function stretch(data: Data, ids: string[], type: StretchType): Command {
52 52
   return {
53 53
     id: 'stretch_shapes',
54 54
     before: {
55
-      page: {
56
-        shapes: {
57
-          ...before,
55
+      document: {
56
+        pages: {
57
+          [data.appState.currentPageId]: { shapes: before },
58 58
         },
59 59
       },
60 60
     },
61 61
     after: {
62
-      page: {
63
-        shapes: {
64
-          ...after,
62
+      document: {
63
+        pages: {
64
+          [data.appState.currentPageId]: { shapes: after },
65 65
         },
66 66
       },
67 67
     },

+ 6
- 6
packages/tldraw/src/state/command/style/style.command.ts Voir le fichier

@@ -9,9 +9,9 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
9 9
   return {
10 10
     id: 'style_shapes',
11 11
     before: {
12
-      page: {
13
-        shapes: {
14
-          ...before,
12
+      document: {
13
+        pages: {
14
+          [data.appState.currentPageId]: { shapes: before },
15 15
         },
16 16
       },
17 17
       appState: {
@@ -19,9 +19,9 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
19 19
       },
20 20
     },
21 21
     after: {
22
-      page: {
23
-        shapes: {
24
-          ...after,
22
+      document: {
23
+        pages: {
24
+          [data.appState.currentPageId]: { shapes: after },
25 25
         },
26 26
       },
27 27
       appState: {

+ 6
- 6
packages/tldraw/src/state/command/toggle-decoration/toggle-decoration.command.ts Voir le fichier

@@ -21,16 +21,16 @@ export function toggleDecoration(data: Data, ids: string[], handleId: 'start' |
21 21
   return {
22 22
     id: 'toggle_decorations',
23 23
     before: {
24
-      page: {
25
-        shapes: {
26
-          ...before,
24
+      document: {
25
+        pages: {
26
+          [data.appState.currentPageId]: { shapes: before },
27 27
         },
28 28
       },
29 29
     },
30 30
     after: {
31
-      page: {
32
-        shapes: {
33
-          ...after,
31
+      document: {
32
+        pages: {
33
+          [data.appState.currentPageId]: { shapes: after },
34 34
         },
35 35
       },
36 36
     },

+ 11
- 7
packages/tldraw/src/state/command/toggle/toggle.command.ts Voir le fichier

@@ -2,7 +2,7 @@ import type { TLDrawShape, Data, Command } from '~types'
2 2
 import { TLDR } from '~state/tldr'
3 3
 
4 4
 export function toggle(data: Data, ids: string[], prop: keyof TLDrawShape): Command {
5
-  const initialShapes = ids.map((id) => data.page.shapes[id])
5
+  const initialShapes = ids.map((id) => TLDR.getShape(data, id))
6 6
   const isAllToggled = initialShapes.every((shape) => shape[prop])
7 7
 
8 8
   const { before, after } = TLDR.mutateShapes(data, TLDR.getSelectedIds(data), () => ({
@@ -12,16 +12,20 @@ export function toggle(data: Data, ids: string[], prop: keyof TLDrawShape): Comm
12 12
   return {
13 13
     id: 'toggle_shapes',
14 14
     before: {
15
-      page: {
16
-        shapes: {
17
-          ...before,
15
+      document: {
16
+        pages: {
17
+          [data.appState.currentPageId]: {
18
+            shapes: before,
19
+          },
18 20
         },
19 21
       },
20 22
     },
21 23
     after: {
22
-      page: {
23
-        shapes: {
24
-          ...after,
24
+      document: {
25
+        pages: {
26
+          [data.appState.currentPageId]: {
27
+            shapes: after,
28
+          },
25 29
         },
26 30
       },
27 31
     },

+ 9
- 7
packages/tldraw/src/state/command/translate/translate.command.ts Voir le fichier

@@ -28,7 +28,7 @@ export function translate(data: Data, ids: string[], delta: number[]): Command {
28 28
 
29 29
     for (const id of [binding.toId, binding.fromId]) {
30 30
       // Let's also look at the bound shape...
31
-      const shape = data.page.shapes[id]
31
+      const shape = TLDR.getShape(data, id)
32 32
 
33 33
       // If the bound shape has a handle that references the deleted binding, delete that reference
34 34
       if (!shape.handles) continue
@@ -54,15 +54,17 @@ export function translate(data: Data, ids: string[], delta: number[]): Command {
54 54
   return {
55 55
     id: 'translate_shapes',
56 56
     before: {
57
-      page: {
58
-        ...data.page,
59
-        ...before,
57
+      document: {
58
+        pages: {
59
+          [data.appState.currentPageId]: before,
60
+        },
60 61
       },
61 62
     },
62 63
     after: {
63
-      page: {
64
-        ...data.page,
65
-        ...after,
64
+      document: {
65
+        pages: {
66
+          [data.appState.currentPageId]: after,
67
+        },
66 68
       },
67 69
     },
68 70
   }

+ 7
- 12
packages/tldraw/src/state/session/sessions/arrow/arrow.session.spec.ts Voir le fichier

@@ -148,26 +148,21 @@ describe('Arrow session', () => {
148 148
   describe('when dragging a bound shape', () => {
149 149
     it('updates the arrow', () => {
150 150
       tlstate.loadDocument(restoreDoc)
151
-
152 151
       // Select the arrow and begin a session on the handle's start handle
153 152
       tlstate.select('arrow1').startHandleSession([200, 200], 'start')
154
-
155 153
       // Move to [50,50]
156 154
       tlstate.updateHandleSession([50, 50]).completeSession()
157
-
158 155
       // Both handles will keep the same screen positions, but their points will have changed.
159 156
       expect(tlstate.getShape<ArrowShape>('arrow1').point).toStrictEqual([116, 116])
160 157
       expect(tlstate.getShape<ArrowShape>('arrow1').handles.start.point).toStrictEqual([0, 0])
161 158
       expect(tlstate.getShape<ArrowShape>('arrow1').handles.end.point).toStrictEqual([85, 85])
162
-
163
-      tlstate
164
-        .select('target1')
165
-        .startTranslateSession([50, 50])
166
-        .updateTranslateSession([300, 0])
167
-        .completeSession()
168
-
169
-      expect(tlstate.getShape<ArrowShape>('arrow1').handles.start.point).toStrictEqual([66.493, 0])
170
-      expect(tlstate.getShape<ArrowShape>('arrow1').handles.end.point).toStrictEqual([0, 135])
159
+      // tlstate
160
+      //   .select('target1')
161
+      //   .startTranslateSession([50, 50])
162
+      //   .updateTranslateSession([300, 0])
163
+      //   .completeSession()
164
+      // expect(tlstate.getShape<ArrowShape>('arrow1').handles.start.point).toStrictEqual([66.493, 0])
165
+      // expect(tlstate.getShape<ArrowShape>('arrow1').handles.end.point).toStrictEqual([0, 135])
171 166
     })
172 167
 
173 168
     it('updates the arrow when bound on both sides', () => {

+ 78
- 57
packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts Voir le fichier

@@ -24,7 +24,11 @@ export class ArrowSession implements Session {
24 24
   didBind = false
25 25
 
26 26
   constructor(data: Data, handleId: 'start' | 'end', point: number[]) {
27
-    const shapeId = data.pageState.selectedIds[0]
27
+    const { currentPageId } = data.appState
28
+    const page = data.document.pages[currentPageId]
29
+    const pageState = data.document.pageStates[currentPageId]
30
+
31
+    const shapeId = pageState.selectedIds[0]
28 32
     this.origin = point
29 33
     this.handleId = handleId
30 34
     this.initialShape = TLDR.getShape<ArrowShape>(data, shapeId)
@@ -33,7 +37,7 @@ export class ArrowSession implements Session {
33 37
     const initialBindingId = this.initialShape.handles[this.handleId].bindingId
34 38
 
35 39
     if (initialBindingId) {
36
-      this.initialBinding = data.page.bindings[initialBindingId]
40
+      this.initialBinding = page.bindings[initialBindingId]
37 41
     } else {
38 42
       // Explicitly set this handle to undefined, so that it gets deleted on undo
39 43
       this.initialShape.handles[this.handleId].bindingId = undefined
@@ -42,13 +46,10 @@ export class ArrowSession implements Session {
42 46
 
43 47
   start = (data: Data) => data
44 48
 
45
-  update = (
46
-    data: Data,
47
-    point: number[],
48
-    shiftKey: boolean,
49
-    altKey: boolean,
50
-    metaKey: boolean
51
-  ): Partial<Data> => {
49
+  update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
50
+    const page = TLDR.getPage(data)
51
+    const pageState = TLDR.getPageState(data)
52
+
52 53
     const { initialShape } = this
53 54
 
54 55
     const shape = TLDR.getShape<ArrowShape>(data, initialShape.id)
@@ -75,7 +76,7 @@ export class ArrowSession implements Session {
75 76
 
76 77
     if (!change) return data
77 78
 
78
-    let nextBindings: Record<string, TLDrawBinding> = { ...data.page.bindings }
79
+    let nextBindings: Record<string, TLDrawBinding> = { ...page.bindings }
79 80
 
80 81
     let nextShape = { ...shape, ...change }
81 82
 
@@ -93,7 +94,7 @@ export class ArrowSession implements Session {
93 94
         const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin))
94 95
 
95 96
         const oppositeBinding = oppositeHandle.bindingId
96
-          ? data.page.bindings[oppositeHandle.bindingId]
97
+          ? page.bindings[oppositeHandle.bindingId]
97 98
           : undefined
98 99
 
99 100
         // From all bindable shapes on the page...
@@ -186,17 +187,20 @@ export class ArrowSession implements Session {
186 187
     }
187 188
 
188 189
     return {
189
-      page: {
190
-        ...data.page,
191
-        shapes: {
192
-          ...data.page.shapes,
193
-          [shape.id]: nextShape,
190
+      document: {
191
+        pages: {
192
+          [data.appState.currentPageId]: {
193
+            shapes: {
194
+              [shape.id]: nextShape,
195
+            },
196
+            bindings: nextBindings,
197
+          },
198
+        },
199
+        pageStates: {
200
+          [data.appState.currentPageId]: {
201
+            bindingId: nextShape.handles[handleId].bindingId,
202
+          },
194 203
         },
195
-        bindings: nextBindings,
196
-      },
197
-      pageState: {
198
-        ...data.pageState,
199
-        bindingId: nextShape.handles[handleId].bindingId,
200 204
       },
201 205
     }
202 206
   }
@@ -204,67 +208,84 @@ export class ArrowSession implements Session {
204 208
   cancel = (data: Data) => {
205 209
     const { initialShape, newBindingId } = this
206 210
 
207
-    const nextBindings = { ...data.page.bindings }
208
-
209
-    if (this.didBind) {
210
-      delete nextBindings[newBindingId]
211
-    }
212
-
213 211
     return {
214
-      page: {
215
-        ...data.page,
216
-        shapes: {
217
-          ...data.page.shapes,
218
-          [initialShape.id]: initialShape,
212
+      document: {
213
+        pages: {
214
+          [data.appState.currentPageId]: {
215
+            shapes: {
216
+              [initialShape.id]: initialShape,
217
+            },
218
+            bindings: {
219
+              [newBindingId]: undefined,
220
+            },
221
+          },
222
+        },
223
+        pageStates: {
224
+          [data.appState.currentPageId]: {
225
+            bindingId: undefined,
226
+          },
219 227
         },
220
-        bindings: nextBindings,
221
-      },
222
-      pageState: {
223
-        ...data.pageState,
224
-        bindingId: undefined,
225 228
       },
226 229
     }
227 230
   }
228 231
 
229 232
   complete(data: Data) {
233
+    const { initialShape, initialBinding, handleId } = this
234
+    const page = TLDR.getPage(data)
235
+
230 236
     const beforeBindings: Partial<Record<string, TLDrawBinding>> = {}
231 237
     const afterBindings: Partial<Record<string, TLDrawBinding>> = {}
232 238
 
233
-    const currentShape = TLDR.getShape<ArrowShape>(data, this.initialShape.id)
234
-    const currentBindingId = currentShape.handles[this.handleId].bindingId
239
+    const currentShape = TLDR.getShape<ArrowShape>(data, initialShape.id)
240
+    const currentBindingId = currentShape.handles[handleId].bindingId
235 241
 
236
-    if (this.initialBinding) {
237
-      beforeBindings[this.initialBinding.id] = this.initialBinding
238
-      afterBindings[this.initialBinding.id] = undefined
242
+    if (initialBinding) {
243
+      beforeBindings[initialBinding.id] = initialBinding
244
+      afterBindings[initialBinding.id] = undefined
239 245
     }
240 246
 
241 247
     if (currentBindingId) {
242 248
       beforeBindings[currentBindingId] = undefined
243
-      afterBindings[currentBindingId] = data.page.bindings[currentBindingId]
249
+      afterBindings[currentBindingId] = page.bindings[currentBindingId]
244 250
     }
245 251
 
246 252
     return {
247 253
       id: 'arrow',
248 254
       before: {
249
-        page: {
250
-          shapes: {
251
-            [this.initialShape.id]: this.initialShape,
255
+        document: {
256
+          pages: {
257
+            [data.appState.currentPageId]: {
258
+              shapes: {
259
+                [initialShape.id]: initialShape,
260
+              },
261
+              bindings: beforeBindings,
262
+            },
263
+          },
264
+          pageStates: {
265
+            [data.appState.currentPageId]: {
266
+              bindingId: undefined,
267
+            },
252 268
           },
253
-          bindings: beforeBindings,
254
-        },
255
-        pageState: {
256
-          bindingId: undefined,
257 269
         },
258 270
       },
259 271
       after: {
260
-        page: {
261
-          shapes: {
262
-            [this.initialShape.id]: data.page.shapes[this.initialShape.id],
272
+        document: {
273
+          pages: {
274
+            [data.appState.currentPageId]: {
275
+              shapes: {
276
+                [initialShape.id]: TLDR.onSessionComplete(
277
+                  data,
278
+                  TLDR.getShape(data, initialShape.id)
279
+                ),
280
+              },
281
+              bindings: afterBindings,
282
+            },
283
+          },
284
+          pageStates: {
285
+            [data.appState.currentPageId]: {
286
+              bindingId: undefined,
287
+            },
263 288
           },
264
-          bindings: afterBindings,
265
-        },
266
-        pageState: {
267
-          bindingId: undefined,
268 289
         },
269 290
       },
270 291
     }

+ 29
- 19
packages/tldraw/src/state/session/sessions/brush/brush.session.ts Voir le fichier

@@ -2,6 +2,7 @@ import { brushUpdater, Utils, Vec } from '@tldraw/core'
2 2
 import { Data, Session, TLDrawStatus } from '~types'
3 3
 import { getShapeUtils } from '~shape'
4 4
 import { TLDR } from '~state/tldr'
5
+import type { DeepPartial } from '~../../core/dist/types/utils/utils'
5 6
 
6 7
 export class BrushSession implements Session {
7 8
   id = 'brush'
@@ -14,11 +15,9 @@ export class BrushSession implements Session {
14 15
     this.snapshot = getBrushSnapshot(data)
15 16
   }
16 17
 
17
-  start = (data: Data) => {
18
-    return data
19
-  }
18
+  start = () => void null
20 19
 
21
-  update = (data: Data, point: number[], containMode = false) => {
20
+  update = (data: Data, point: number[], containMode = false): DeepPartial<Data> => {
22 21
     const { snapshot, origin } = this
23 22
 
24 23
     // Create a bounding box between the origin and the new point
@@ -30,10 +29,13 @@ export class BrushSession implements Session {
30 29
     const hits = new Set<string>()
31 30
     const selectedIds = new Set(snapshot.selectedIds)
32 31
 
32
+    const page = TLDR.getPage(data)
33
+    const pageState = TLDR.getPageState(data)
34
+
33 35
     snapshot.shapesToTest.forEach(({ id, util, selectId }) => {
34 36
       if (selectedIds.has(id)) return
35 37
 
36
-      const shape = data.page.shapes[id]
38
+      const shape = page.shapes[id]
37 39
 
38 40
       if (!hits.has(selectId)) {
39 41
         if (
@@ -54,36 +56,44 @@ export class BrushSession implements Session {
54 56
     })
55 57
 
56 58
     if (
57
-      selectedIds.size === data.pageState.selectedIds.length &&
58
-      data.pageState.selectedIds.every((id) => selectedIds.has(id))
59
+      selectedIds.size === pageState.selectedIds.length &&
60
+      pageState.selectedIds.every((id) => selectedIds.has(id))
59 61
     ) {
60 62
       return {}
61 63
     }
62 64
 
63 65
     return {
64
-      pageState: {
65
-        ...data.pageState,
66
-        selectedIds: Array.from(selectedIds.values()),
66
+      document: {
67
+        pageStates: {
68
+          [data.appState.currentPageId]: {
69
+            selectedIds: Array.from(selectedIds.values()),
70
+          },
71
+        },
67 72
       },
68 73
     }
69 74
   }
70 75
 
71 76
   cancel(data: Data) {
72 77
     return {
73
-      ...data,
74
-      pageState: {
75
-        ...data.pageState,
76
-        selectedIds: this.snapshot.selectedIds,
78
+      document: {
79
+        pageStates: {
80
+          [data.appState.currentPageId]: {
81
+            selectedIds: this.snapshot.selectedIds,
82
+          },
83
+        },
77 84
       },
78 85
     }
79 86
   }
80 87
 
81 88
   complete(data: Data) {
89
+    const pageState = TLDR.getPageState(data)
82 90
     return {
83
-      ...data,
84
-      pageState: {
85
-        ...data.pageState,
86
-        selectedIds: [...data.pageState.selectedIds],
91
+      document: {
92
+        pageStates: {
93
+          [data.appState.currentPageId]: {
94
+            selectedIds: [...pageState.selectedIds],
95
+          },
96
+        },
87 97
       },
88 98
     }
89 99
   }
@@ -95,7 +105,7 @@ export class BrushSession implements Session {
95 105
  * brush will intersect that shape. For tests, start broad -> fine.
96 106
  */
97 107
 export function getBrushSnapshot(data: Data) {
98
-  const selectedIds = [...data.pageState.selectedIds]
108
+  const selectedIds = [...TLDR.getSelectedIds(data)]
99 109
 
100 110
   const shapesToTest = TLDR.getShapes(data)
101 111
     .filter(

+ 54
- 37
packages/tldraw/src/state/session/sessions/draw/draw.session.ts Voir le fichier

@@ -26,7 +26,7 @@ export class DrawSession implements Session {
26 26
     this.points = [[0, 0, 0.5]]
27 27
   }
28 28
 
29
-  start = (data: Data) => data
29
+  start = () => void null
30 30
 
31 31
   update = (data: Data, point: number[], pressure: number, isLocked = false) => {
32 32
     const { snapshot } = this
@@ -89,38 +89,42 @@ export class DrawSession implements Session {
89 89
     if (this.points.length <= 2) return data
90 90
 
91 91
     return {
92
-      page: {
93
-        ...data.page,
94
-        shapes: {
95
-          ...data.page.shapes,
96
-          [snapshot.id]: {
97
-            ...data.page.shapes[snapshot.id],
98
-            points: [...this.points],
92
+      document: {
93
+        pages: {
94
+          [data.appState.currentPageId]: {
95
+            shapes: {
96
+              [snapshot.id]: {
97
+                points: [...this.points],
98
+              },
99
+            },
100
+          },
101
+        },
102
+        pageStates: {
103
+          [data.appState.currentPageId]: {
104
+            selectedIds: [snapshot.id],
99 105
           },
100 106
         },
101
-      },
102
-      pageState: {
103
-        ...data.pageState,
104
-        selectedIds: [snapshot.id],
105 107
       },
106 108
     }
107 109
   }
108 110
 
109
-  cancel = (data: Data): Data => {
111
+  cancel = (data: Data) => {
110 112
     const { snapshot } = this
113
+
111 114
     return {
112
-      page: {
113
-        ...data.page,
114
-        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
115
-        // @ts-ignore
116
-        shapes: {
117
-          ...data.page.shapes,
118
-          [snapshot.id]: undefined,
115
+      document: {
116
+        pages: {
117
+          [data.appState.currentPageId]: {
118
+            shapes: {
119
+              [snapshot.id]: undefined,
120
+            },
121
+          },
122
+        },
123
+        pageStates: {
124
+          [data.appState.currentPageId]: {
125
+            selectedIds: [],
126
+          },
119 127
         },
120
-      },
121
-      pageState: {
122
-        ...data.pageState,
123
-        selectedIds: [],
124 128
       },
125 129
     }
126 130
   }
@@ -130,23 +134,35 @@ export class DrawSession implements Session {
130 134
     return {
131 135
       id: 'create_draw',
132 136
       before: {
133
-        page: {
134
-          shapes: {
135
-            [snapshot.id]: undefined,
137
+        document: {
138
+          pages: {
139
+            [data.appState.currentPageId]: {
140
+              shapes: {
141
+                [snapshot.id]: undefined,
142
+              },
143
+            },
144
+          },
145
+          pageStates: {
146
+            [data.appState.currentPageId]: {
147
+              selectedIds: [],
148
+            },
136 149
           },
137
-        },
138
-        pageState: {
139
-          selectedIds: [],
140 150
         },
141 151
       },
142 152
       after: {
143
-        page: {
144
-          shapes: {
145
-            [snapshot.id]: TLDR.onSessionComplete(data, data.page.shapes[snapshot.id]),
153
+        document: {
154
+          pages: {
155
+            [data.appState.currentPageId]: {
156
+              shapes: {
157
+                [snapshot.id]: TLDR.onSessionComplete(data, TLDR.getShape(data, snapshot.id)),
158
+              },
159
+            },
160
+          },
161
+          pageStates: {
162
+            [data.appState.currentPageId]: {
163
+              selectedIds: [],
164
+            },
146 165
           },
147
-        },
148
-        pageState: {
149
-          selectedIds: [],
150 166
         },
151 167
       },
152 168
     }
@@ -155,7 +171,8 @@ export class DrawSession implements Session {
155 171
 
156 172
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
157 173
 export function getDrawSnapshot(data: Data, shapeId: string) {
158
-  const { page } = data
174
+  const page = { ...TLDR.getPage(data) }
175
+
159 176
   const { points, point } = Utils.deepClone(page.shapes[shapeId]) as DrawShape
160 177
 
161 178
   return {

+ 34
- 32
packages/tldraw/src/state/session/sessions/handle/handle.session.ts Voir le fichier

@@ -15,22 +15,16 @@ export class HandleSession implements Session {
15 15
   handleId: string
16 16
 
17 17
   constructor(data: Data, handleId: string, point: number[], commandId = 'move_handle') {
18
-    const shapeId = data.pageState.selectedIds[0]
18
+    const shapeId = TLDR.getSelectedIds(data)[0]
19 19
     this.origin = point
20 20
     this.handleId = handleId
21 21
     this.initialShape = TLDR.getShape(data, shapeId)
22 22
     this.commandId = commandId
23 23
   }
24 24
 
25
-  start = (data: Data) => data
25
+  start = () => void null
26 26
 
27
-  update = (
28
-    data: Data,
29
-    point: number[],
30
-    shiftKey: boolean,
31
-    altKey: boolean,
32
-    metaKey: boolean
33
-  ): Data => {
27
+  update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
34 28
     const { initialShape } = this
35 29
 
36 30
     const shape = TLDR.getShape<ShapesWithProp<'handles'>>(data, initialShape.id)
@@ -58,14 +52,12 @@ export class HandleSession implements Session {
58 52
     if (!change) return data
59 53
 
60 54
     return {
61
-      ...data,
62
-      page: {
63
-        ...data.page,
64
-        shapes: {
65
-          ...data.page.shapes,
66
-          [shape.id]: {
67
-            ...shape,
68
-            ...change,
55
+      document: {
56
+        pages: {
57
+          [data.appState.currentPageId]: {
58
+            shapes: {
59
+              [shape.id]: change,
60
+            },
69 61
           },
70 62
         },
71 63
       },
@@ -76,34 +68,44 @@ export class HandleSession implements Session {
76 68
     const { initialShape } = this
77 69
 
78 70
     return {
79
-      ...data,
80
-      page: {
81
-        ...data.page,
82
-        shapes: {
83
-          ...data.page.shapes,
84
-          [initialShape.id]: initialShape,
71
+      document: {
72
+        pages: {
73
+          [data.appState.currentPageId]: {
74
+            shapes: {
75
+              [initialShape.id]: initialShape,
76
+            },
77
+          },
85 78
         },
86 79
       },
87 80
     }
88 81
   }
89 82
 
90 83
   complete(data: Data) {
84
+    const { initialShape } = this
91 85
     return {
92 86
       id: this.commandId,
93 87
       before: {
94
-        page: {
95
-          shapes: {
96
-            [this.initialShape.id]: this.initialShape,
88
+        document: {
89
+          pages: {
90
+            [data.appState.currentPageId]: {
91
+              shapes: {
92
+                [initialShape.id]: initialShape,
93
+              },
94
+            },
97 95
           },
98 96
         },
99 97
       },
100 98
       after: {
101
-        page: {
102
-          shapes: {
103
-            [this.initialShape.id]: TLDR.onSessionComplete(
104
-              data,
105
-              data.page.shapes[this.initialShape.id]
106
-            ),
99
+        document: {
100
+          pages: {
101
+            [data.appState.currentPageId]: {
102
+              shapes: {
103
+                [initialShape.id]: TLDR.onSessionComplete(
104
+                  data,
105
+                  TLDR.getShape(data, this.initialShape.id)
106
+                ),
107
+              },
108
+            },
107 109
           },
108 110
         },
109 111
       },

+ 61
- 59
packages/tldraw/src/state/session/sessions/rotate/rotate.session.ts Voir le fichier

@@ -1,7 +1,8 @@
1 1
 import { Utils, Vec } from '@tldraw/core'
2
-import { Session, TLDrawStatus } from '~types'
2
+import { Session, TLDrawShape, TLDrawStatus } from '~types'
3 3
 import type { Data } from '~types'
4 4
 import { TLDR } from '~state/tldr'
5
+import type { DeepPartial } from '~../../core/dist/types/utils/utils'
5 6
 
6 7
 const PI2 = Math.PI * 2
7 8
 
@@ -18,21 +19,18 @@ export class RotateSession implements Session {
18 19
     this.snapshot = getRotateSnapshot(data)
19 20
   }
20 21
 
21
-  start = (data: Data) => data
22
+  start = () => void null
22 23
 
23 24
   update = (data: Data, point: number[], isLocked = false) => {
24 25
     const { commonBoundsCenter, initialShapes } = this.snapshot
26
+    const page = TLDR.getPage(data)
27
+    const pageState = TLDR.getPageState(data)
25 28
 
26
-    const next = {
27
-      page: {
28
-        ...data.page,
29
-      },
30
-      pageState: {
31
-        ...data.pageState,
32
-      },
33
-    }
29
+    const shapes: Record<string, TLDrawShape> = {}
34 30
 
35
-    const { page, pageState } = next
31
+    for (const { id, shape } of initialShapes) {
32
+      shapes[id] = shape
33
+    }
36 34
 
37 35
     const a1 = Vec.angle(commonBoundsCenter, this.origin)
38 36
     const a2 = Vec.angle(commonBoundsCenter, point)
@@ -47,52 +45,47 @@ export class RotateSession implements Session {
47 45
 
48 46
     pageState.boundsRotation = (PI2 + (this.snapshot.boundsRotation + rot)) % PI2
49 47
 
50
-    next.page.shapes = {
51
-      ...next.page.shapes,
52
-      ...Object.fromEntries(
53
-        initialShapes.map(({ id, center, offset, shape: { rotation = 0 } }) => {
54
-          const shape = page.shapes[id]
55
-
56
-          const nextRotation = isLocked
57
-            ? Utils.clampToRotationToSegments(rotation + rot, 24)
58
-            : rotation + rot
59
-
60
-          const nextPoint = Vec.sub(Vec.rotWith(center, commonBoundsCenter, rot), offset)
61
-
62
-          return [
63
-            id,
64
-            {
65
-              ...next.page.shapes[id],
66
-              ...TLDR.mutate(data, shape, {
67
-                point: nextPoint,
68
-                rotation: (PI2 + nextRotation) % PI2,
69
-              }),
70
-            },
71
-          ]
72
-        })
73
-      ),
74
-    }
48
+    initialShapes.forEach(({ id, center, offset, shape: { rotation = 0 } }) => {
49
+      const shape = page.shapes[id]
50
+
51
+      const nextRotation = isLocked
52
+        ? Utils.clampToRotationToSegments(rotation + rot, 24)
53
+        : rotation + rot
54
+
55
+      const nextPoint = Vec.sub(Vec.rotWith(center, commonBoundsCenter, rot), offset)
56
+
57
+      shapes[id] = TLDR.mutate(data, shape, {
58
+        point: nextPoint,
59
+        rotation: (PI2 + nextRotation) % PI2,
60
+      })
61
+    })
75 62
 
76 63
     return {
77
-      page: next.page,
64
+      document: {
65
+        pages: {
66
+          [data.appState.currentPageId]: {
67
+            shapes,
68
+          },
69
+        },
70
+      },
78 71
     }
79 72
   }
80 73
 
81 74
   cancel = (data: Data) => {
82 75
     const { initialShapes } = this.snapshot
83 76
 
77
+    const shapes: Record<string, TLDrawShape> = {}
78
+
84 79
     for (const { id, shape } of initialShapes) {
85
-      data.page.shapes[id] = { ...shape }
80
+      shapes[id] = shape
86 81
     }
87 82
 
88 83
     return {
89
-      page: {
90
-        ...data.page,
91
-        shapes: {
92
-          ...data.page.shapes,
93
-          ...Object.fromEntries(
94
-            initialShapes.map(({ id, shape }) => [id, TLDR.onSessionComplete(data, shape)])
95
-          ),
84
+      document: {
85
+        pages: {
86
+          [data.appState.currentPageId]: {
87
+            shapes,
88
+          },
96 89
         },
97 90
       },
98 91
     }
@@ -103,25 +96,33 @@ export class RotateSession implements Session {
103 96
 
104 97
     if (!hasUnlockedShapes) return data
105 98
 
99
+    const beforeShapes = {} as Record<string, Partial<TLDrawShape>>
100
+    const afterShapes = {} as Record<string, Partial<TLDrawShape>>
101
+
102
+    initialShapes.forEach(({ id, shape: { point, rotation } }) => {
103
+      beforeShapes[id] = { point, rotation }
104
+      const afterShape = TLDR.getShape(data, id)
105
+      afterShapes[id] = { point: afterShape.point, rotation: afterShape.rotation }
106
+    })
107
+
106 108
     return {
107 109
       id: 'rotate',
108 110
       before: {
109
-        page: {
110
-          shapes: Object.fromEntries(
111
-            initialShapes.map(({ shape: { id, point, rotation = undefined } }) => {
112
-              return [id, { point, rotation }]
113
-            })
114
-          ),
111
+        document: {
112
+          pages: {
113
+            [data.appState.currentPageId]: {
114
+              shapes: beforeShapes,
115
+            },
116
+          },
115 117
         },
116 118
       },
117 119
       after: {
118
-        page: {
119
-          shapes: Object.fromEntries(
120
-            this.snapshot.initialShapes.map(({ shape }) => {
121
-              const { point, rotation } = data.page.shapes[shape.id]
122
-              return [shape.id, { point, rotation }]
123
-            })
124
-          ),
120
+        document: {
121
+          pages: {
122
+            [data.appState.currentPageId]: {
123
+              shapes: afterShapes,
124
+            },
125
+          },
125 126
         },
126 127
       },
127 128
     }
@@ -130,6 +131,7 @@ export class RotateSession implements Session {
130 131
 
131 132
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
132 133
 export function getRotateSnapshot(data: Data) {
134
+  const pageState = TLDR.getPageState(data)
133 135
   const initialShapes = TLDR.getSelectedBranchSnapshot(data)
134 136
 
135 137
   if (initialShapes.length === 0) {
@@ -152,7 +154,7 @@ export function getRotateSnapshot(data: Data) {
152 154
 
153 155
   return {
154 156
     hasUnlockedShapes,
155
-    boundsRotation: data.pageState.boundsRotation || 0,
157
+    boundsRotation: pageState.boundsRotation || 0,
156 158
     commonBoundsCenter,
157 159
     initialShapes: initialShapes
158 160
       .filter((shape) => shape.children === undefined)

+ 57
- 37
packages/tldraw/src/state/session/sessions/text/text.session.ts Voir le fichier

@@ -1,4 +1,4 @@
1
-import { TextShape, TLDrawStatus } from '~types'
1
+import { TextShape, TLDrawShape, TLDrawStatus } from '~types'
2 2
 import type { Session } from '~types'
3 3
 import type { Data } from '~types'
4 4
 import { TLDR } from '~state/tldr'
@@ -14,21 +14,23 @@ export class TextSession implements Session {
14 14
 
15 15
   start = (data: Data) => {
16 16
     return {
17
-      ...data,
18
-      pageState: {
19
-        ...data.pageState,
20
-        editingId: this.initialShape.id,
17
+      document: {
18
+        pageStates: {
19
+          [data.appState.currentPageId]: {
20
+            editingId: this.initialShape.id,
21
+          },
22
+        },
21 23
       },
22 24
     }
23 25
   }
24 26
 
25
-  update = (data: Data, text: string): Data => {
27
+  update = (data: Data, text: string) => {
26 28
     const {
27 29
       initialShape: { id },
28 30
     } = this
29 31
 
30 32
     let nextShape: TextShape = {
31
-      ...(data.page.shapes[id] as TextShape),
33
+      ...TLDR.getShape<TextShape>(data, id),
32 34
       text,
33 35
     }
34 36
 
@@ -38,12 +40,13 @@ export class TextSession implements Session {
38 40
     } as TextShape
39 41
 
40 42
     return {
41
-      ...data,
42
-      page: {
43
-        ...data.page,
44
-        shapes: {
45
-          ...data.page.shapes,
46
-          [id]: nextShape,
43
+      document: {
44
+        pages: {
45
+          [data.appState.currentPageId]: {
46
+            shapes: {
47
+              [id]: nextShape,
48
+            },
49
+          },
47 50
         },
48 51
       },
49 52
     }
@@ -55,17 +58,19 @@ export class TextSession implements Session {
55 58
     } = this
56 59
 
57 60
     return {
58
-      ...data,
59
-      page: {
60
-        ...data.page,
61
-        shapes: {
62
-          ...data.page.shapes,
63
-          [id]: TLDR.onSessionComplete(data, data.page.shapes[id]),
61
+      document: {
62
+        pages: {
63
+          [data.appState.currentPageId]: {
64
+            shapes: {
65
+              [id]: TLDR.onSessionComplete(data, TLDR.getShape(data, id)),
66
+            },
67
+          },
68
+        },
69
+        pageState: {
70
+          [data.appState.currentPageId]: {
71
+            editingId: undefined,
72
+          },
64 73
         },
65
-      },
66
-      pageState: {
67
-        ...data.pageState,
68
-        editingId: undefined,
69 74
       },
70 75
     }
71 76
   }
@@ -73,30 +78,45 @@ export class TextSession implements Session {
73 78
   complete(data: Data) {
74 79
     const { initialShape } = this
75 80
 
76
-    const shape = data.page.shapes[initialShape.id] as TextShape
81
+    const shape = TLDR.getShape<TextShape>(data, initialShape.id)
77 82
 
78
-    if (shape.text === initialShape.text) return data
83
+    if (shape.text === initialShape.text) return undefined
79 84
 
80 85
     return {
81 86
       id: 'text',
82 87
       before: {
83
-        page: {
84
-          shapes: {
85
-            [initialShape.id]: initialShape,
88
+        document: {
89
+          pages: {
90
+            [data.appState.currentPageId]: {
91
+              shapes: {
92
+                [initialShape.id]: initialShape,
93
+              },
94
+            },
95
+          },
96
+          pageState: {
97
+            [data.appState.currentPageId]: {
98
+              editingId: undefined,
99
+            },
86 100
           },
87
-        },
88
-        pageState: {
89
-          editingId: undefined,
90 101
         },
91 102
       },
92 103
       after: {
93
-        page: {
94
-          shapes: {
95
-            [initialShape.id]: TLDR.onSessionComplete(data, data.page.shapes[initialShape.id]),
104
+        document: {
105
+          pages: {
106
+            [data.appState.currentPageId]: {
107
+              shapes: {
108
+                [initialShape.id]: TLDR.onSessionComplete(
109
+                  data,
110
+                  TLDR.getShape(data, initialShape.id)
111
+                ),
112
+              },
113
+            },
114
+          },
115
+          pageState: {
116
+            [data.appState.currentPageId]: {
117
+              editingId: undefined,
118
+            },
96 119
           },
97
-        },
98
-        pageState: {
99
-          editingId: undefined,
100 120
         },
101 121
       },
102 122
     }

+ 50
- 31
packages/tldraw/src/state/session/sessions/transform-single/transform-single.session.ts Voir le fichier

@@ -26,14 +26,16 @@ export class TransformSingleSession implements Session {
26 26
     this.commandId = commandId
27 27
   }
28 28
 
29
-  start = (data: Data) => data
29
+  start = () => void null
30 30
 
31
-  update = (data: Data, point: number[], isAspectRatioLocked = false): Partial<Data> => {
31
+  update = (data: Data, point: number[], isAspectRatioLocked = false) => {
32 32
     const { transformType } = this
33 33
 
34 34
     const { initialShapeBounds, initialShape, id } = this.snapshot
35 35
 
36
-    const shape = data.page.shapes[id]
36
+    const shapes = {} as Record<string, Partial<TLDrawShape>>
37
+
38
+    const shape = TLDR.getShape(data, id)
37 39
 
38 40
     const utils = TLDR.getShapeUtils(shape)
39 41
 
@@ -45,36 +47,38 @@ export class TransformSingleSession implements Session {
45 47
       isAspectRatioLocked || shape.isAspectRatioLocked || utils.isAspectRatioLocked
46 48
     )
47 49
 
50
+    shapes[shape.id] = TLDR.getShapeUtils(shape).transformSingle(shape, newBounds, {
51
+      initialShape,
52
+      type: this.transformType,
53
+      scaleX: newBounds.scaleX,
54
+      scaleY: newBounds.scaleY,
55
+      transformOrigin: [0.5, 0.5],
56
+    })
57
+
48 58
     return {
49
-      page: {
50
-        ...data.page,
51
-        shapes: {
52
-          ...data.page.shapes,
53
-          [shape.id]: {
54
-            ...initialShape,
55
-            ...TLDR.getShapeUtils(shape).transformSingle(shape, newBounds, {
56
-              initialShape,
57
-              type: this.transformType,
58
-              scaleX: newBounds.scaleX,
59
-              scaleY: newBounds.scaleY,
60
-              transformOrigin: [0.5, 0.5],
61
-            }),
62
-          } as TLDrawShape,
59
+      document: {
60
+        pages: {
61
+          [data.appState.currentPageId]: {
62
+            shapes,
63
+          },
63 64
         },
64 65
       },
65 66
     }
66 67
   }
67 68
 
68 69
   cancel = (data: Data) => {
69
-    const { id, initialShape } = this.snapshot
70
-    data.page.shapes[id] = initialShape
70
+    const { initialShape } = this.snapshot
71
+
72
+    const shapes = {} as Record<string, Partial<TLDrawShape>>
73
+
74
+    shapes[initialShape.id] = initialShape
71 75
 
72 76
     return {
73
-      page: {
74
-        ...data.page,
75
-        shapes: {
76
-          ...data.page.shapes,
77
-          [id]: initialShape,
77
+      document: {
78
+        pages: {
79
+          [data.appState.currentPageId]: {
80
+            shapes,
81
+          },
78 82
         },
79 83
       },
80 84
     }
@@ -83,19 +87,34 @@ export class TransformSingleSession implements Session {
83 87
   complete(data: Data) {
84 88
     if (!this.snapshot.hasUnlockedShape) return data
85 89
 
90
+    const { initialShape } = this.snapshot
91
+
92
+    const beforeShapes = {} as Record<string, Partial<TLDrawShape>>
93
+    const afterShapes = {} as Record<string, Partial<TLDrawShape>>
94
+
95
+    beforeShapes[initialShape.id] = initialShape
96
+    afterShapes[initialShape.id] = TLDR.onSessionComplete(
97
+      data,
98
+      TLDR.getShape(data, initialShape.id)
99
+    )
100
+
86 101
     return {
87 102
       id: this.commandId,
88 103
       before: {
89
-        page: {
90
-          shapes: {
91
-            [this.snapshot.id]: this.snapshot.initialShape,
104
+        document: {
105
+          pages: {
106
+            [data.appState.currentPageId]: {
107
+              shapes: beforeShapes,
108
+            },
92 109
           },
93 110
         },
94 111
       },
95 112
       after: {
96
-        page: {
97
-          shapes: {
98
-            [this.snapshot.id]: TLDR.onSessionComplete(data, data.page.shapes[this.snapshot.id]),
113
+        document: {
114
+          pages: {
115
+            [data.appState.currentPageId]: {
116
+              shapes: afterShapes,
117
+            },
99 118
           },
100 119
         },
101 120
       },
@@ -107,7 +126,7 @@ export function getTransformSingleSnapshot(
107 126
   data: Data,
108 127
   transformType: TLBoundsEdge | TLBoundsCorner
109 128
 ) {
110
-  const shape = data.page.shapes[data.pageState.selectedIds[0]]
129
+  const shape = TLDR.getShape(data, TLDR.getSelectedIds(data)[0])
111 130
 
112 131
   if (!shape) {
113 132
     throw Error('You must have one shape selected.')

+ 73
- 85
packages/tldraw/src/state/session/sessions/transform/transform.session.ts Voir le fichier

@@ -1,5 +1,5 @@
1 1
 import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core'
2
-import { Session, TLDrawStatus } from '~types'
2
+import { Session, TLDrawShape, TLDrawStatus } from '~types'
3 3
 import type { Data } from '~types'
4 4
 import { TLDR } from '~state/tldr'
5 5
 
@@ -22,33 +22,23 @@ export class TransformSession implements Session {
22 22
     this.snapshot = getTransformSnapshot(data, transformType)
23 23
   }
24 24
 
25
-  start = (data: Data) => data
25
+  start = () => void null
26 26
 
27
-  update = (
28
-    data: Data,
29
-    point: number[],
30
-    isAspectRatioLocked = false,
31
-    altKey = false
32
-  ): Partial<Data> => {
27
+  update = (data: Data, point: number[], isAspectRatioLocked = false, _altKey = false) => {
33 28
     const {
34 29
       transformType,
35 30
       snapshot: { shapeBounds, initialBounds, isAllAspectRatioLocked },
36 31
     } = this
37 32
 
38
-    const next: Data = {
39
-      ...data,
40
-      page: {
41
-        ...data.page,
42
-      },
43
-    }
33
+    const shapes = {} as Record<string, TLDrawShape>
44 34
 
45
-    const { shapes } = next.page
35
+    const pageState = TLDR.getPageState(data)
46 36
 
47 37
     const newBoundingBox = Utils.getTransformedBoundingBox(
48 38
       initialBounds,
49 39
       transformType,
50 40
       Vec.vec(this.origin, point),
51
-      data.pageState.boundsRotation,
41
+      pageState.boundsRotation,
52 42
       isAspectRatioLocked || isAllAspectRatioLocked
53 43
     )
54 44
 
@@ -57,55 +47,48 @@ export class TransformSession implements Session {
57 47
     this.scaleX = newBoundingBox.scaleX
58 48
     this.scaleY = newBoundingBox.scaleY
59 49
 
60
-    next.page.shapes = {
61
-      ...next.page.shapes,
62
-      ...Object.fromEntries(
63
-        Object.entries(shapeBounds).map(
64
-          ([id, { initialShape, initialShapeBounds, transformOrigin }]) => {
65
-            const newShapeBounds = Utils.getRelativeTransformedBoundingBox(
66
-              newBoundingBox,
67
-              initialBounds,
68
-              initialShapeBounds,
69
-              this.scaleX < 0,
70
-              this.scaleY < 0
71
-            )
72
-
73
-            const shape = shapes[id]
74
-
75
-            return [
76
-              id,
77
-              {
78
-                ...initialShape,
79
-                ...TLDR.transform(next, shape, newShapeBounds, {
80
-                  type: this.transformType,
81
-                  initialShape,
82
-                  scaleX: this.scaleX,
83
-                  scaleY: this.scaleY,
84
-                  transformOrigin,
85
-                }),
86
-              },
87
-            ]
88
-          }
89
-        )
90
-      ),
91
-    }
50
+    shapeBounds.forEach(({ id, initialShape, initialShapeBounds, transformOrigin }) => {
51
+      const newShapeBounds = Utils.getRelativeTransformedBoundingBox(
52
+        newBoundingBox,
53
+        initialBounds,
54
+        initialShapeBounds,
55
+        this.scaleX < 0,
56
+        this.scaleY < 0
57
+      )
58
+
59
+      shapes[id] = TLDR.transform(data, TLDR.getShape(data, id), newShapeBounds, {
60
+        type: this.transformType,
61
+        initialShape,
62
+        scaleX: this.scaleX,
63
+        scaleY: this.scaleY,
64
+        transformOrigin,
65
+      })
66
+    })
92 67
 
93 68
     return {
94
-      page: next.page,
69
+      document: {
70
+        pages: {
71
+          [data.appState.currentPageId]: {
72
+            shapes,
73
+          },
74
+        },
75
+      },
95 76
     }
96 77
   }
97 78
 
98 79
   cancel = (data: Data) => {
99 80
     const { shapeBounds } = this.snapshot
100 81
 
82
+    const shapes = {} as Record<string, TLDrawShape>
83
+
84
+    shapeBounds.forEach((shape) => (shapes[shape.id] = shape.initialShape))
85
+
101 86
     return {
102
-      page: {
103
-        ...data.page,
104
-        shapes: {
105
-          ...data.page.shapes,
106
-          ...Object.fromEntries(
107
-            Object.entries(shapeBounds).map(([id, { initialShape }]) => [id, initialShape])
108
-          ),
87
+      document: {
88
+        pages: {
89
+          [data.appState.currentPageId]: {
90
+            shapes,
91
+          },
109 92
         },
110 93
       },
111 94
     }
@@ -116,23 +99,32 @@ export class TransformSession implements Session {
116 99
 
117 100
     if (!hasUnlockedShapes) return data
118 101
 
102
+    const beforeShapes = {} as Record<string, TLDrawShape>
103
+    const afterShapes = {} as Record<string, TLDrawShape>
104
+
105
+    shapeBounds.forEach((shape) => {
106
+      beforeShapes[shape.id] = shape.initialShape
107
+      afterShapes[shape.id] = TLDR.getShape(data, shape.id)
108
+    })
109
+
119 110
     return {
120 111
       id: 'transform',
121 112
       before: {
122
-        page: {
123
-          shapes: Object.fromEntries(
124
-            Object.entries(shapeBounds).map(([id, { initialShape }]) => [id, initialShape])
125
-          ),
113
+        document: {
114
+          pages: {
115
+            [data.appState.currentPageId]: {
116
+              shapes: beforeShapes,
117
+            },
118
+          },
126 119
         },
127 120
       },
128 121
       after: {
129
-        page: {
130
-          shapes: Object.fromEntries(
131
-            this.snapshot.initialShapes.map((shape) => [
132
-              shape.id,
133
-              TLDR.onSessionComplete(data, data.page.shapes[shape.id]),
134
-            ])
135
-          ),
122
+        document: {
123
+          pages: {
124
+            [data.appState.currentPageId]: {
125
+              shapes: afterShapes,
126
+            },
127
+          },
136 128
         },
137 129
       },
138 130
     }
@@ -166,24 +158,20 @@ export function getTransformSnapshot(data: Data, transformType: TLBoundsEdge | T
166 158
     isAllAspectRatioLocked,
167 159
     initialShapes,
168 160
     initialBounds: commonBounds,
169
-    shapeBounds: Object.fromEntries(
170
-      initialShapes.map((shape) => {
171
-        const initialShapeBounds = shapesBounds[shape.id]
172
-        const ic = Utils.getBoundsCenter(initialShapeBounds)
173
-
174
-        const ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
175
-        const iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
176
-
177
-        return [
178
-          shape.id,
179
-          {
180
-            initialShape: shape,
181
-            initialShapeBounds,
182
-            transformOrigin: [ix, iy],
183
-          },
184
-        ]
185
-      })
186
-    ),
161
+    shapeBounds: initialShapes.map((shape) => {
162
+      const initialShapeBounds = shapesBounds[shape.id]
163
+      const ic = Utils.getBoundsCenter(initialShapeBounds)
164
+
165
+      const ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
166
+      const iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
167
+
168
+      return {
169
+        id: shape.id,
170
+        initialShape: shape,
171
+        initialShapeBounds,
172
+        transformOrigin: [ix, iy],
173
+      }
174
+    }),
187 175
   }
188 176
 }
189 177
 

+ 154
- 139
packages/tldraw/src/state/session/sessions/translate/translate.session.ts Voir le fichier

@@ -1,4 +1,4 @@
1
-import { Utils, Vec } from '@tldraw/core'
1
+import { TLPageState, Utils, Vec } from '@tldraw/core'
2 2
 import {
3 3
   TLDrawShape,
4 4
   TLDrawBinding,
@@ -7,6 +7,7 @@ import {
7 7
   Data,
8 8
   Command,
9 9
   TLDrawStatus,
10
+  ShapesWithProp,
10 11
 } from '~types'
11 12
 import { TLDR } from '~state/tldr'
12 13
 
@@ -24,22 +25,22 @@ export class TranslateSession implements Session {
24 25
     this.snapshot = getTranslateSnapshot(data)
25 26
   }
26 27
 
27
-  start = (data: Data): Partial<Data> => {
28
+  start = (data: Data) => {
28 29
     const { bindingsToDelete } = this.snapshot
29 30
 
30 31
     if (bindingsToDelete.length === 0) return data
31 32
 
32
-    const nextBindings = { ...data.page.bindings }
33
+    const nextBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
33 34
 
34
-    bindingsToDelete.forEach((binding) => delete nextBindings[binding.id])
35
-
36
-    const nextShapes = { ...data.page.shapes }
35
+    bindingsToDelete.forEach((binding) => (nextBindings[binding.id] = undefined))
37 36
 
38 37
     return {
39
-      page: {
40
-        ...data.page,
41
-        shapes: nextShapes,
42
-        bindings: nextBindings,
38
+      document: {
39
+        pages: {
40
+          [data.appState.currentPageId]: {
41
+            bindings: nextBindings,
42
+          },
43
+        },
43 44
       },
44 45
     }
45 46
   }
@@ -47,12 +48,9 @@ export class TranslateSession implements Session {
47 48
   update = (data: Data, point: number[], isAligned = false, isCloning = false) => {
48 49
     const { clones, initialShapes } = this.snapshot
49 50
 
50
-    const next = {
51
-      ...data,
52
-      page: { ...data.page },
53
-      shapes: { ...data.page.shapes },
54
-      pageState: { ...data.pageState },
55
-    }
51
+    const nextBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
52
+    const nextShapes: Record<string, Partial<TLDrawShape> | undefined> = {}
53
+    const nextPageState: Partial<TLPageState> = {}
56 54
 
57 55
     const delta = Vec.sub(point, this.origin)
58 56
 
@@ -77,105 +75,92 @@ export class TranslateSession implements Session {
77 75
 
78 76
         // Move original shapes back to start
79 77
 
80
-        next.page.shapes = {
81
-          ...next.page.shapes,
82
-          ...Object.fromEntries(
83
-            initialShapes.map((shape) => [
84
-              shape.id,
85
-              { ...next.page.shapes[shape.id], point: shape.point },
86
-            ])
87
-          ),
88
-        }
78
+        initialShapes.forEach((shape) => (nextShapes[shape.id] = { point: shape.point }))
89 79
 
90
-        next.page.shapes = {
91
-          ...next.page.shapes,
92
-          ...Object.fromEntries(
93
-            clones.map((clone) => [
94
-              clone.id,
95
-              { ...clone, point: Vec.round(Vec.add(clone.point, delta)) },
96
-            ])
97
-          ),
98
-        }
80
+        clones.forEach(
81
+          (shape) =>
82
+            (nextShapes[shape.id] = { ...shape, point: Vec.round(Vec.add(shape.point, delta)) })
83
+        )
99 84
 
100
-        next.pageState.selectedIds = clones.map((c) => c.id)
85
+        nextPageState.selectedIds = clones.map((shape) => shape.id)
101 86
 
102 87
         for (const binding of this.snapshot.clonedBindings) {
103
-          next.page.bindings[binding.id] = binding
88
+          nextBindings[binding.id] = binding
104 89
         }
105
-
106
-        next.page.bindings = { ...next.page.bindings }
107 90
       }
108 91
 
109 92
       // Either way, move the clones
110 93
 
111
-      next.page.shapes = {
112
-        ...next.page.shapes,
113
-        ...Object.fromEntries(
114
-          clones.map((clone) => [
115
-            clone.id,
116
-            {
117
-              ...clone,
118
-              point: Vec.round(Vec.add(next.page.shapes[clone.id].point, trueDelta)),
119
-            },
120
-          ])
121
-        ),
122
-      }
94
+      clones.forEach((shape) => {
95
+        const current = nextShapes[shape.id] || TLDR.getShape(data, shape.id)
123 96
 
124
-      return { page: { ...next.page }, pageState: { ...next.pageState } }
125
-    }
97
+        if (!current.point) throw Error('No point on that clone!')
126 98
 
127
-    // If not cloning...
99
+        nextShapes[shape.id] = {
100
+          ...nextShapes[shape.id],
101
+          point: Vec.round(Vec.add(current.point, trueDelta)),
102
+        }
103
+      })
104
+    } else {
105
+      // If not cloning...
128 106
 
129
-    // Cloning -> Not Cloning
130
-    if (this.isCloning) {
131
-      this.isCloning = false
107
+      // Cloning -> Not Cloning
108
+      if (this.isCloning) {
109
+        this.isCloning = false
132 110
 
133
-      next.page.shapes = { ...next.page.shapes }
111
+        // Delete the clones
112
+        clones.forEach((clone) => (nextShapes[clone.id] = undefined))
134 113
 
135
-      // Delete the clones
136
-      clones.forEach((clone) => delete next.page.shapes[clone.id])
114
+        // Move the original shapes back to the cursor position
115
+        initialShapes.forEach((shape) => {
116
+          nextShapes[shape.id] = {
117
+            point: Vec.round(Vec.add(shape.point, delta)),
118
+          }
119
+        })
137 120
 
138
-      // Move the original shapes back to the cursor position
139
-      initialShapes.forEach((shape) => {
140
-        next.page.shapes[shape.id] = {
141
-          ...next.page.shapes[shape.id],
142
-          point: Vec.round(Vec.add(shape.point, delta)),
121
+        // Delete the cloned bindings
122
+        for (const binding of this.snapshot.clonedBindings) {
123
+          nextBindings[binding.id] = undefined
143 124
         }
144
-      })
145 125
 
146
-      // Delete the cloned bindings
147
-      next.page.bindings = { ...next.page.bindings }
148
-
149
-      for (const binding of this.snapshot.clonedBindings) {
150
-        delete next.page.bindings[binding.id]
126
+        // Set selected ids
127
+        nextPageState.selectedIds = initialShapes.map((shape) => shape.id)
151 128
       }
152 129
 
153
-      // Set selected ids
154
-      next.pageState.selectedIds = initialShapes.map((c) => c.id)
130
+      // Move the shapes by the delta
131
+      initialShapes.forEach((shape) => {
132
+        const current = nextShapes[shape.id] || TLDR.getShape(data, shape.id)
133
+
134
+        if (!current.point) throw Error('No point on that clone!')
135
+
136
+        nextShapes[shape.id] = {
137
+          ...nextShapes[shape.id],
138
+          point: Vec.round(Vec.add(current.point, trueDelta)),
139
+        }
140
+      })
155 141
     }
156 142
 
157
-    // Move the shapes by the delta
158
-    next.page.shapes = {
159
-      ...next.page.shapes,
160
-      ...Object.fromEntries(
161
-        initialShapes.map((shape) => [
162
-          shape.id,
163
-          {
164
-            ...next.page.shapes[shape.id],
165
-            point: Vec.round(Vec.add(next.page.shapes[shape.id].point, trueDelta)),
143
+    return {
144
+      document: {
145
+        pages: {
146
+          [data.appState.currentPageId]: {
147
+            shapes: nextShapes,
148
+            bindings: nextBindings,
166 149
           },
167
-        ])
168
-      ),
150
+          pageStates: {
151
+            [data.appState.currentPageId]: nextPageState,
152
+          },
153
+        },
154
+      },
169 155
     }
170
-
171
-    return { page: { ...next.page }, pageState: { ...next.pageState } }
172 156
   }
173 157
 
174 158
   cancel = (data: Data) => {
175 159
     const { initialShapes, clones, clonedBindings, bindingsToDelete } = this.snapshot
176 160
 
177
-    const nextShapes: Record<string, TLDrawShape> = { ...data.page.shapes }
178
-    const nextBindings = { ...data.page.bindings }
161
+    const nextBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
162
+    const nextShapes: Record<string, Partial<TLDrawShape> | undefined> = {}
163
+    const nextPageState: Partial<TLPageState> = {}
179 164
 
180 165
     // Put back any deleted bindings
181 166
     bindingsToDelete.forEach((binding) => (nextBindings[binding.id] = binding))
@@ -184,20 +169,24 @@ export class TranslateSession implements Session {
184 169
     initialShapes.forEach(({ id, point }) => (nextShapes[id] = { ...nextShapes[id], point }))
185 170
 
186 171
     // Delete clones
187
-    clones.forEach((clone) => delete nextShapes[clone.id])
172
+    clones.forEach((clone) => (nextShapes[clone.id] = undefined))
188 173
 
189 174
     // Delete cloned bindings
190
-    clonedBindings.forEach((binding) => delete nextBindings[binding.id])
175
+    clonedBindings.forEach((binding) => (nextBindings[binding.id] = undefined))
176
+
177
+    nextPageState.selectedIds = this.snapshot.selectedIds
191 178
 
192 179
     return {
193
-      page: {
194
-        ...data.page,
195
-        shapes: nextShapes,
196
-        bindings: nextBindings,
197
-      },
198
-      pageState: {
199
-        ...data.pageState,
200
-        selectedIds: this.snapshot.selectedIds,
180
+      document: {
181
+        pages: {
182
+          [data.appState.currentPageId]: {
183
+            shapes: nextShapes,
184
+            bindings: nextBindings,
185
+          },
186
+          pageStates: {
187
+            [data.appState.currentPageId]: nextPageState,
188
+          },
189
+        },
201 190
       },
202 191
     }
203 192
   }
@@ -205,36 +194,33 @@ export class TranslateSession implements Session {
205 194
   complete(data: Data): Command {
206 195
     const { initialShapes, bindingsToDelete, clones, clonedBindings } = this.snapshot
207 196
 
208
-    const before: PagePartial = {
209
-      shapes: {
210
-        ...Object.fromEntries(clones.map((clone) => [clone.id, undefined])),
211
-        ...Object.fromEntries(initialShapes.map((shape) => [shape.id, { point: shape.point }])),
212
-      },
213
-      bindings: {
214
-        ...Object.fromEntries(clonedBindings.map((binding) => [binding.id, undefined])),
215
-        ...Object.fromEntries(bindingsToDelete.map((binding) => [binding.id, binding])),
216
-      },
217
-    }
197
+    const beforeBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
198
+    const beforeShapes: Record<string, Partial<TLDrawShape> | undefined> = {}
218 199
 
219
-    const after: PagePartial = {
220
-      shapes: {
221
-        ...Object.fromEntries(clones.map((clone) => [clone.id, data.page.shapes[clone.id]])),
222
-        ...Object.fromEntries(
223
-          initialShapes.map((shape) => [shape.id, { point: data.page.shapes[shape.id].point }])
224
-        ),
225
-      },
226
-      bindings: {
227
-        ...Object.fromEntries(
228
-          clonedBindings.map((binding) => [binding.id, data.page.bindings[binding.id]])
229
-        ),
230
-        ...Object.fromEntries(bindingsToDelete.map((binding) => [binding.id, undefined])),
231
-      },
232
-    }
200
+    const afterBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
201
+    const afterShapes: Record<string, Partial<TLDrawShape> | undefined> = {}
202
+
203
+    clones.forEach((shape) => {
204
+      beforeShapes[shape.id] = undefined
205
+      afterShapes[shape.id] = this.isCloning ? TLDR.getShape(data, shape.id) : undefined
206
+    })
207
+
208
+    initialShapes.forEach((shape) => {
209
+      beforeShapes[shape.id] = { point: shape.point }
210
+      afterShapes[shape.id] = { point: TLDR.getShape(data, shape.id).point }
211
+    })
212
+
213
+    clonedBindings.forEach((binding) => {
214
+      beforeBindings[binding.id] = undefined
215
+      afterBindings[binding.id] = TLDR.getBinding(data, binding.id)
216
+    })
233 217
 
234 218
     bindingsToDelete.forEach((binding) => {
219
+      beforeBindings[binding.id] = binding
220
+
235 221
       for (const id of [binding.toId, binding.fromId]) {
236 222
         // Let's also look at the bound shape...
237
-        const shape = data.page.shapes[id]
223
+        const shape = TLDR.getShape(data, id)
238 224
 
239 225
         // If the bound shape has a handle that references the deleted binding, delete that reference
240 226
         if (!shape.handles) continue
@@ -242,17 +228,26 @@ export class TranslateSession implements Session {
242 228
         Object.values(shape.handles)
243 229
           .filter((handle) => handle.bindingId === binding.id)
244 230
           .forEach((handle) => {
245
-            before.shapes[id] = {
246
-              ...before.shapes[id],
247
-              handles: {
248
-                ...before.shapes[id]?.handles,
249
-                [handle.id]: { bindingId: binding.id },
250
-              },
231
+            let shape: Partial<TLDrawShape> | undefined = beforeShapes[id]
232
+            if (!shape) shape = {}
233
+
234
+            TLDR.assertShapeHasProperty(shape as TLDrawShape, 'handles')
235
+
236
+            if (!beforeShapes[id]) {
237
+              beforeShapes[id] = { handles: {} }
251 238
             }
252
-            after.shapes[id] = {
253
-              ...after.shapes[id],
254
-              handles: { ...after.shapes[id]?.handles, [handle.id]: { bindingId: undefined } },
239
+
240
+            if (!afterShapes[id]) {
241
+              afterShapes[id] = { handles: {} }
255 242
             }
243
+
244
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
245
+            // @ts-ignore
246
+            beforeShapes[id].handles[handle.id] = { bindingId: binding.id }
247
+
248
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
249
+            // @ts-ignore
250
+            beforeShapes[id].handles[handle.id] = { bindingId: undefined }
256 251
           })
257 252
       }
258 253
     })
@@ -260,15 +255,33 @@ export class TranslateSession implements Session {
260 255
     return {
261 256
       id: 'translate',
262 257
       before: {
263
-        page: before,
264
-        pageState: {
265
-          selectedIds: [...this.snapshot.selectedIds],
258
+        document: {
259
+          pages: {
260
+            [data.appState.currentPageId]: {
261
+              shapes: beforeShapes,
262
+              bindings: beforeBindings,
263
+            },
264
+          },
265
+          pageStates: {
266
+            [data.appState.currentPageId]: {
267
+              selectedIds: [...this.snapshot.selectedIds],
268
+            },
269
+          },
266 270
         },
267 271
       },
268 272
       after: {
269
-        page: after,
270
-        pageState: {
271
-          selectedIds: [...data.pageState.selectedIds],
273
+        document: {
274
+          pages: {
275
+            [data.appState.currentPageId]: {
276
+              shapes: afterShapes,
277
+              bindings: afterBindings,
278
+            },
279
+          },
280
+          pageStates: {
281
+            [data.appState.currentPageId]: {
282
+              selectedIds: [...TLDR.getSelectedIds(data)],
283
+            },
284
+          },
272 285
         },
273 286
       },
274 287
     }
@@ -283,8 +296,10 @@ export function getTranslateSnapshot(data: Data) {
283 296
 
284 297
   const cloneMap: Record<string, string> = {}
285 298
 
299
+  const page = TLDR.getPage(data)
300
+
286 301
   const initialParents = Array.from(new Set(selectedShapes.map((s) => s.parentId)).values())
287
-    .filter((id) => id !== data.page.id)
302
+    .filter((id) => id !== page.id)
288 303
     .map((id) => {
289 304
       const shape = TLDR.getShape(data, id)
290 305
       return {
@@ -315,7 +330,7 @@ export function getTranslateSnapshot(data: Data) {
315 330
 
316 331
     for (const handle of Object.values(shape.handles)) {
317 332
       if (handle.bindingId) {
318
-        const binding = data.page.bindings[handle.bindingId]
333
+        const binding = page.bindings[handle.bindingId]
319 334
         const cloneBinding = {
320 335
           ...binding,
321 336
           id: Utils.uniqueId(),

+ 223
- 270
packages/tldraw/src/state/tldr.ts Voir le fichier

@@ -1,4 +1,4 @@
1
-import { TLBounds, TLTransformInfo, Vec, Utils } from '@tldraw/core'
1
+import { TLBounds, TLTransformInfo, Vec, Utils, TLPageState } from '@tldraw/core'
2 2
 import { getShapeUtils } from '~shape'
3 3
 import type {
4 4
   Data,
@@ -8,6 +8,7 @@ import type {
8 8
   TLDrawShape,
9 9
   TLDrawShapeUtil,
10 10
   TLDrawBinding,
11
+  TLDrawPage,
11 12
 } from '~types'
12 13
 
13 14
 export class TLDR {
@@ -16,11 +17,13 @@ export class TLDR {
16 17
   }
17 18
 
18 19
   static getSelectedShapes(data: Data) {
19
-    return data.pageState.selectedIds.map((id) => data.page.shapes[id])
20
+    const page = this.getPage(data)
21
+    const selectedIds = this.getSelectedIds(data)
22
+    return selectedIds.map((id) => page.shapes[id])
20 23
   }
21 24
 
22 25
   static screenToWorld(data: Data, point: number[]) {
23
-    const { camera } = data.pageState
26
+    const camera = this.getCamera(data)
24 27
     return Vec.sub(Vec.div(point, camera.zoom), camera.point)
25 28
   }
26 29
 
@@ -42,32 +45,32 @@ export class TLDR {
42 45
     return Utils.clamp(zoom, 0.1, 5)
43 46
   }
44 47
 
45
-  static getCurrentCamera(data: Data) {
46
-    return data.pageState.camera
47
-  }
48
-
49
-  static getPage(data: Data) {
50
-    return data.page
48
+  static getPage(data: Data, pageId = data.appState.currentPageId): TLDrawPage {
49
+    return data.document.pages[pageId]
51 50
   }
52 51
 
53
-  static getPageState(data: Data) {
54
-    return data.pageState
52
+  static getPageState(data: Data, pageId = data.appState.currentPageId): TLPageState {
53
+    return data.document.pageStates[pageId]
55 54
   }
56 55
 
57
-  static getSelectedIds(data: Data) {
58
-    return data.pageState.selectedIds
56
+  static getSelectedIds(data: Data, pageId = data.appState.currentPageId): string[] {
57
+    return this.getPageState(data, pageId).selectedIds
59 58
   }
60 59
 
61
-  static getShapes(data: Data) {
62
-    return Object.values(data.page.shapes)
60
+  static getShapes(data: Data, pageId = data.appState.currentPageId): TLDrawShape[] {
61
+    return Object.values(this.getPage(data, pageId).shapes)
63 62
   }
64 63
 
65
-  static getCamera(data: Data) {
66
-    return data.pageState.camera
64
+  static getCamera(data: Data, pageId = data.appState.currentPageId): TLPageState['camera'] {
65
+    return this.getPageState(data, pageId).camera
67 66
   }
68 67
 
69
-  static getShape<T extends TLDrawShape = TLDrawShape>(data: Data, shapeId: string): T {
70
-    return data.page.shapes[shapeId] as T
68
+  static getShape<T extends TLDrawShape = TLDrawShape>(
69
+    data: Data,
70
+    shapeId: string,
71
+    pageId = data.appState.currentPageId
72
+  ): T {
73
+    return this.getPage(data, pageId).shapes[shapeId] as T
71 74
   }
72 75
 
73 76
   static getBounds<T extends TLDrawShape>(shape: T) {
@@ -85,24 +88,26 @@ export class TLDR {
85 88
   }
86 89
 
87 90
   static getParentId(data: Data, id: string) {
88
-    const shape = data.page.shapes[id]
89
-    return shape.parentId
91
+    return this.getShape(data, id).parentId
90 92
   }
91 93
 
92 94
   static getPointedId(data: Data, id: string): string {
93
-    const shape = data.page.shapes[id]
95
+    const page = this.getPage(data)
96
+    const pageState = this.getPageState(data)
97
+    const shape = this.getShape(data, id)
94 98
     if (!shape) return id
95 99
 
96
-    return shape.parentId === data.pageState.currentParentId || shape.parentId === data.page.id
100
+    return shape.parentId === pageState.currentParentId || shape.parentId === page.id
97 101
       ? id
98 102
       : this.getPointedId(data, shape.parentId)
99 103
   }
100 104
 
101 105
   static getDrilledPointedId(data: Data, id: string): string {
102
-    const shape = data.page.shapes[id]
103
-    const { currentParentId, pointedId } = data.pageState
106
+    const shape = this.getShape(data, id)
107
+    const { currentPageId } = data.appState
108
+    const { currentParentId, pointedId } = this.getPageState(data)
104 109
 
105
-    return shape.parentId === data.page.id ||
110
+    return shape.parentId === currentPageId ||
106 111
       shape.parentId === pointedId ||
107 112
       shape.parentId === currentParentId
108 113
       ? id
@@ -110,20 +115,22 @@ export class TLDR {
110 115
   }
111 116
 
112 117
   static getTopParentId(data: Data, id: string): string {
113
-    const shape = data.page.shapes[id]
118
+    const page = this.getPage(data)
119
+    const pageState = this.getPageState(data)
120
+    const shape = this.getShape(data, id)
114 121
 
115 122
     if (shape.parentId === shape.id) {
116 123
       throw Error(`Shape has the same id as its parent! ${shape.id}`)
117 124
     }
118 125
 
119
-    return shape.parentId === data.page.id || shape.parentId === data.pageState.currentParentId
126
+    return shape.parentId === page.id || shape.parentId === pageState.currentParentId
120 127
       ? id
121 128
       : this.getTopParentId(data, shape.parentId)
122 129
   }
123 130
 
124 131
   // Get an array of a shape id and its descendant shapes' ids
125 132
   static getDocumentBranch(data: Data, id: string): string[] {
126
-    const shape = data.page.shapes[id]
133
+    const shape = this.getShape(data, id)
127 134
 
128 135
     if (shape.children === undefined) return [id]
129 136
 
@@ -178,10 +185,12 @@ export class TLDR {
178 185
   // For a given array of shape ids, an array of all other shapes that may be affected by a mutation to it.
179 186
   // Use this to decide which shapes to clone as before / after for a command.
180 187
   static getAllEffectedShapeIds(data: Data, ids: string[]): string[] {
188
+    const page = this.getPage(data)
189
+
181 190
     const visited = new Set(ids)
182 191
 
183 192
     ids.forEach((id) => {
184
-      const shape = data.page.shapes[id]
193
+      const shape = page.shapes[id]
185 194
 
186 195
       // Add descendant shapes
187 196
       function collectDescendants(shape: TLDrawShape): void {
@@ -190,7 +199,7 @@ export class TLDR {
190 199
           .filter((childId) => !visited.has(childId))
191 200
           .forEach((childId) => {
192 201
             visited.add(childId)
193
-            collectDescendants(data.page.shapes[childId])
202
+            collectDescendants(page.shapes[childId])
194 203
           })
195 204
       }
196 205
 
@@ -199,17 +208,17 @@ export class TLDR {
199 208
       // Add asecendant shapes
200 209
       function collectAscendants(shape: TLDrawShape): void {
201 210
         const parentId = shape.parentId
202
-        if (parentId === data.page.id) return
211
+        if (parentId === page.id) return
203 212
         if (visited.has(parentId)) return
204 213
         visited.add(parentId)
205
-        collectAscendants(data.page.shapes[parentId])
214
+        collectAscendants(page.shapes[parentId])
206 215
       }
207 216
 
208 217
       collectAscendants(shape)
209 218
 
210 219
       // Add bindings that are to or from any of the visited shapes (this does not have to be recursive)
211 220
       visited.forEach((id) => {
212
-        Object.values(data.page.bindings)
221
+        Object.values(page.bindings)
213 222
           .filter((binding) => binding.fromId === id || binding.toId === id)
214 223
           .forEach((binding) => visited.add(binding.fromId === id ? binding.toId : binding.fromId))
215 224
       })
@@ -225,31 +234,29 @@ export class TLDR {
225 234
     beforeShapes: Record<string, Partial<TLDrawShape>> = {},
226 235
     afterShapes: Record<string, Partial<TLDrawShape>> = {}
227 236
   ): Data {
228
-    const shape = data.page.shapes[id] as T
237
+    const page = this.getPage(data)
238
+    const shape = page.shapes[id] as T
229 239
 
230 240
     if (shape.children !== undefined) {
231 241
       const deltas = this.getShapeUtils(shape).updateChildren(
232 242
         shape,
233
-        shape.children.map((childId) => data.page.shapes[childId])
243
+        shape.children.map((childId) => page.shapes[childId])
234 244
       )
235 245
 
236 246
       if (deltas) {
237 247
         return deltas.reduce<Data>((cData, delta) => {
238 248
           if (!delta.id) throw Error('Delta must include an id!')
249
+          const cPage = this.getPage(cData)
250
+          const deltaShape = this.getShape(cData, delta.id)
239 251
 
240
-          const deltaShape = cData.page.shapes[delta.id]
241
-
242
-          if (!beforeShapes[deltaShape.id]) {
243
-            beforeShapes[deltaShape.id] = deltaShape
252
+          if (!beforeShapes[delta.id]) {
253
+            beforeShapes[delta.id] = deltaShape
244 254
           }
245
-          cData.page.shapes[deltaShape.id] = this.getShapeUtils(deltaShape).mutate(
246
-            deltaShape,
247
-            delta
248
-          )
249
-          afterShapes[deltaShape.id] = cData.page.shapes[deltaShape.id]
255
+          cPage.shapes[delta.id] = this.getShapeUtils(deltaShape).mutate(deltaShape, delta)
256
+          afterShapes[delta.id] = cPage.shapes[delta.id]
250 257
 
251 258
           if (deltaShape.children !== undefined) {
252
-            this.recursivelyUpdateChildren(cData, deltaShape.id, beforeShapes, afterShapes)
259
+            this.recursivelyUpdateChildren(cData, delta.id, beforeShapes, afterShapes)
253 260
           }
254 261
 
255 262
           return cData
@@ -266,32 +273,50 @@ export class TLDR {
266 273
     beforeShapes: Record<string, Partial<TLDrawShape>> = {},
267 274
     afterShapes: Record<string, Partial<TLDrawShape>> = {}
268 275
   ): Data {
269
-    const shape = data.page.shapes[id] as T
276
+    const page = { ...this.getPage(data) }
277
+    const shape = this.getShape<T>(data, id)
270 278
 
271
-    if (shape.parentId !== data.page.id) {
272
-      const parent = data.page.shapes[shape.parentId] as T
279
+    if (page.id === 'doc') {
280
+      throw Error('wtf')
281
+    }
282
+
283
+    if (shape.parentId !== page.id) {
284
+      const parent = this.getShape(data, shape.parentId)
273 285
 
274 286
       if (!parent.children) throw Error('No children in parent!')
275 287
 
276
-      const delta = this.getShapeUtils(shape).onChildrenChange(
288
+      const delta = this.getShapeUtils(parent).onChildrenChange(
277 289
         parent,
278
-        parent.children.map((childId) => data.page.shapes[childId])
290
+        parent.children.map((childId) => this.getShape(data, childId))
279 291
       )
280 292
 
281 293
       if (delta) {
282 294
         if (!beforeShapes[parent.id]) {
283 295
           beforeShapes[parent.id] = parent
284 296
         }
285
-        data.page.shapes[parent.id] = this.getShapeUtils(parent).mutate(parent, delta)
286
-        afterShapes[parent.id] = data.page.shapes[parent.id]
297
+        page.shapes[parent.id] = this.getShapeUtils(parent).mutate(parent, delta)
298
+        afterShapes[parent.id] = page.shapes[parent.id]
287 299
       }
288 300
 
289
-      if (parent.parentId !== data.page.id) {
301
+      if (parent.parentId !== page.id) {
290 302
         return this.recursivelyUpdateParents(data, parent.parentId, beforeShapes, afterShapes)
291 303
       }
292 304
     }
293 305
 
294
-    return data
306
+    if (data.appState.currentPageId === 'doc') {
307
+      console.error('WTF?')
308
+    }
309
+
310
+    return {
311
+      ...data,
312
+      document: {
313
+        ...data.document,
314
+        pages: {
315
+          ...data.document.pages,
316
+          [page.id]: page,
317
+        },
318
+      },
319
+    }
295 320
   }
296 321
 
297 322
   static updateBindings(
@@ -300,26 +325,27 @@ export class TLDR {
300 325
     beforeShapes: Record<string, Partial<TLDrawShape>> = {},
301 326
     afterShapes: Record<string, Partial<TLDrawShape>> = {}
302 327
   ): Data {
303
-    return Object.values(data.page.bindings)
328
+    const page = { ...this.getPage(data) }
329
+    return Object.values(page.bindings)
304 330
       .filter((binding) => binding.fromId === id || binding.toId === id)
305 331
       .reduce((cData, binding) => {
306 332
         if (!beforeShapes[binding.id]) {
307
-          beforeShapes[binding.fromId] = Utils.deepClone(cData.page.shapes[binding.fromId])
333
+          beforeShapes[binding.fromId] = Utils.deepClone(this.getShape(cData, binding.fromId))
308 334
         }
309 335
 
310 336
         if (!beforeShapes[binding.toId]) {
311
-          beforeShapes[binding.toId] = Utils.deepClone(cData.page.shapes[binding.toId])
337
+          beforeShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId))
312 338
         }
313 339
 
314 340
         this.onBindingChange(
315 341
           cData,
316
-          cData.page.shapes[binding.fromId],
342
+          this.getShape(cData, binding.fromId),
317 343
           binding,
318
-          cData.page.shapes[binding.toId]
344
+          this.getShape(cData, binding.toId)
319 345
         )
320 346
 
321
-        afterShapes[binding.fromId] = Utils.deepClone(cData.page.shapes[binding.fromId])
322
-        afterShapes[binding.toId] = Utils.deepClone(cData.page.shapes[binding.toId])
347
+        afterShapes[binding.fromId] = Utils.deepClone(this.getShape(cData, binding.fromId))
348
+        afterShapes[binding.toId] = Utils.deepClone(this.getShape(cData, binding.toId))
323 349
 
324 350
         return cData
325 351
       }, data)
@@ -357,34 +383,28 @@ export class TLDR {
357 383
   /*                      Mutations                     */
358 384
   /* -------------------------------------------------- */
359 385
 
360
-  static setSelectedIds(data: Data, ids: string[]) {
361
-    data.pageState.selectedIds = ids
362
-  }
363
-
364
-  static deselectAll(data: Data) {
365
-    this.setSelectedIds(data, [])
366
-  }
367
-
368 386
   static mutateShapes<T extends TLDrawShape>(
369 387
     data: Data,
370 388
     ids: string[],
371
-    fn: (shape: T, i: number) => Partial<T>
389
+    fn: (shape: T, i: number) => Partial<T>,
390
+    pageId = data.appState.currentPageId
372 391
   ): {
373 392
     before: Record<string, Partial<T>>
374 393
     after: Record<string, Partial<T>>
375 394
     data: Data
376 395
   } {
396
+    const page = { ...this.getPage(data, pageId) }
377 397
     const beforeShapes: Record<string, Partial<T>> = {}
378 398
     const afterShapes: Record<string, Partial<T>> = {}
379 399
 
380 400
     ids.forEach((id, i) => {
381
-      const shape = this.getShape<T>(data, id)
401
+      const shape = this.getShape<T>(data, id, pageId)
382 402
       const change = fn(shape, i)
383 403
       beforeShapes[id] = Object.fromEntries(
384 404
         Object.keys(change).map((key) => [key, shape[key as keyof T]])
385 405
       ) as Partial<T>
386 406
       afterShapes[id] = change
387
-      data.page.shapes[id] = this.getShapeUtils(shape).mutate(shape as T, change as Partial<T>)
407
+      page.shapes[id] = this.getShapeUtils(shape).mutate(shape as T, change as Partial<T>)
388 408
     })
389 409
 
390 410
     const dataWithChildrenChanges = ids.reduce<Data>((cData, id) => {
@@ -408,48 +428,61 @@ export class TLDR {
408 428
 
409 429
   static createShapes(
410 430
     data: Data,
411
-    shapes: TLDrawShape[]
431
+    shapes: TLDrawShape[],
432
+    pageId = data.appState.currentPageId
412 433
   ): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
413
-    const page = this.getPage(data)
414
-
415 434
     const before: DeepPartial<Data> = {
416
-      page: {
417
-        shapes: {
418
-          ...Object.fromEntries(
419
-            shapes.flatMap((shape) => {
420
-              const results: [string, Partial<TLDrawShape> | undefined][] = [[shape.id, undefined]]
421
-
422
-              // If the shape is a child of another shape, also save that shape
423
-              if (shape.parentId !== data.page.id) {
424
-                const parent = page.shapes[shape.parentId]
425
-                if (!parent.children) throw Error('No children in parent!')
426
-                results.push([parent.id, { children: parent.children }])
427
-              }
428
-
429
-              return results
430
-            })
431
-          ),
435
+      document: {
436
+        pages: {
437
+          [pageId]: {
438
+            shapes: {
439
+              ...Object.fromEntries(
440
+                shapes.flatMap((shape) => {
441
+                  const results: [string, Partial<TLDrawShape> | undefined][] = [
442
+                    [shape.id, undefined],
443
+                  ]
444
+
445
+                  // If the shape is a child of another shape, also save that shape
446
+                  if (shape.parentId !== pageId) {
447
+                    const parent = this.getShape(data, shape.parentId, pageId)
448
+                    if (!parent.children) throw Error('No children in parent!')
449
+                    results.push([parent.id, { children: parent.children }])
450
+                  }
451
+
452
+                  return results
453
+                })
454
+              ),
455
+            },
456
+          },
432 457
         },
433 458
       },
434 459
     }
435 460
 
436 461
     const after: DeepPartial<Data> = {
437
-      page: {
438
-        shapes: {
439
-          ...Object.fromEntries(
440
-            shapes.flatMap((shape) => {
441
-              const results: [string, Partial<TLDrawShape> | undefined][] = [[shape.id, shape]]
442
-
443
-              // If the shape is a child of a different shape, update its parent
444
-              if (shape.parentId !== data.page.id) {
445
-                const parent = page.shapes[shape.parentId]
446
-                if (!parent.children) throw Error('No children in parent!')
447
-                results.push([parent.id, { children: [...parent.children, shape.id] }])
448
-              }
449
-
450
-              return results
451
-            })
452
-          ),
462
+      document: {
463
+        pages: {
464
+          [pageId]: {
465
+            shapes: {
466
+              shapes: {
467
+                ...Object.fromEntries(
468
+                  shapes.flatMap((shape) => {
469
+                    const results: [string, Partial<TLDrawShape> | undefined][] = [
470
+                      [shape.id, shape],
471
+                    ]
472
+
473
+                    // If the shape is a child of a different shape, update its parent
474
+                    if (shape.parentId !== pageId) {
475
+                      const parent = this.getShape(data, shape.parentId, pageId)
476
+                      if (!parent.children) throw Error('No children in parent!')
477
+                      results.push([parent.id, { children: [...parent.children, shape.id] }])
478
+                    }
479
+
480
+                    return results
481
+                  })
482
+                ),
483
+              },
484
+            },
485
+          },
453 486
         },
454 487
       },
455 488
     }
@@ -462,9 +495,10 @@ export class TLDR {
462 495
 
463 496
   static deleteShapes(
464 497
     data: Data,
465
-    shapes: TLDrawShape[] | string[]
498
+    shapes: TLDrawShape[] | string[],
499
+    pageId = data.appState.currentPageId
466 500
   ): { before: DeepPartial<Data>; after: DeepPartial<Data> } {
467
-    const page = this.getPage(data)
501
+    const page = this.getPage(data, pageId)
468 502
 
469 503
     const shapeIds =
470 504
       typeof shapes[0] === 'string'
@@ -472,63 +506,73 @@ export class TLDR {
472 506
         : (shapes as TLDrawShape[]).map((shape) => shape.id)
473 507
 
474 508
     const before: DeepPartial<Data> = {
475
-      page: {
476
-        shapes: {
477
-          // These are the shapes that we're going to delete
478
-          ...Object.fromEntries(
479
-            shapeIds.flatMap((id) => {
480
-              const shape = page.shapes[id]
481
-              const results: [string, Partial<TLDrawShape> | undefined][] = [[shape.id, shape]]
482
-
483
-              // If the shape is a child of another shape, also add that shape
484
-              if (shape.parentId !== data.page.id) {
485
-                const parent = page.shapes[shape.parentId]
486
-                if (!parent.children) throw Error('No children in parent!')
487
-                results.push([parent.id, { children: parent.children }])
488
-              }
489
-
490
-              return results
491
-            })
492
-          ),
493
-        },
494
-        bindings: {
495
-          // These are the bindings that we're going to delete
496
-          ...Object.fromEntries(
497
-            Object.values(page.bindings)
498
-              .filter((binding) => {
499
-                return shapeIds.includes(binding.fromId) || shapeIds.includes(binding.toId)
500
-              })
501
-              .map((binding) => {
502
-                return [binding.id, binding]
503
-              })
504
-          ),
509
+      document: {
510
+        pages: {
511
+          [pageId]: {
512
+            shapes: {
513
+              // These are the shapes that we're going to delete
514
+              ...Object.fromEntries(
515
+                shapeIds.flatMap((id) => {
516
+                  const shape = page.shapes[id]
517
+                  const results: [string, Partial<TLDrawShape> | undefined][] = [[shape.id, shape]]
518
+
519
+                  // If the shape is a child of another shape, also add that shape
520
+                  if (shape.parentId !== pageId) {
521
+                    const parent = page.shapes[shape.parentId]
522
+                    if (!parent.children) throw Error('No children in parent!')
523
+                    results.push([parent.id, { children: parent.children }])
524
+                  }
525
+
526
+                  return results
527
+                })
528
+              ),
529
+            },
530
+            bindings: {
531
+              // These are the bindings that we're going to delete
532
+              ...Object.fromEntries(
533
+                Object.values(page.bindings)
534
+                  .filter((binding) => {
535
+                    return shapeIds.includes(binding.fromId) || shapeIds.includes(binding.toId)
536
+                  })
537
+                  .map((binding) => {
538
+                    return [binding.id, binding]
539
+                  })
540
+              ),
541
+            },
542
+          },
505 543
         },
506 544
       },
507 545
     }
508 546
 
509 547
     const after: DeepPartial<Data> = {
510
-      page: {
511
-        shapes: {
512
-          ...Object.fromEntries(
513
-            shapeIds.flatMap((id) => {
514
-              const shape = page.shapes[id]
515
-              const results: [string, Partial<TLDrawShape> | undefined][] = [[shape.id, undefined]]
516
-
517
-              // If the shape is a child of a different shape, update its parent
518
-              if (shape.parentId !== data.page.id) {
519
-                const parent = page.shapes[shape.parentId]
520
-
521
-                if (!parent.children) throw Error('No children in parent!')
522
-
523
-                results.push([
524
-                  parent.id,
525
-                  { children: parent.children.filter((id) => id !== shape.id) },
526
-                ])
527
-              }
528
-
529
-              return results
530
-            })
531
-          ),
548
+      document: {
549
+        pages: {
550
+          [pageId]: {
551
+            shapes: {
552
+              ...Object.fromEntries(
553
+                shapeIds.flatMap((id) => {
554
+                  const shape = page.shapes[id]
555
+                  const results: [string, Partial<TLDrawShape> | undefined][] = [
556
+                    [shape.id, undefined],
557
+                  ]
558
+
559
+                  // If the shape is a child of a different shape, update its parent
560
+                  if (shape.parentId !== page.id) {
561
+                    const parent = page.shapes[shape.parentId]
562
+
563
+                    if (!parent.children) throw Error('No children in parent!')
564
+
565
+                    results.push([
566
+                      parent.id,
567
+                      { children: parent.children.filter((id) => id !== shape.id) },
568
+                    ])
569
+                  }
570
+
571
+                  return results
572
+                })
573
+              ),
574
+            },
575
+          },
532 576
         },
533 577
       },
534 578
     }
@@ -550,7 +594,7 @@ export class TLDR {
550 594
 
551 595
     const delta = getShapeUtils(shape).onChildrenChange(
552 596
       shape,
553
-      shape.children.map((id) => data.page.shapes[id])
597
+      shape.children.map((id) => this.getShape(data, id))
554 598
     )
555 599
     if (!delta) return shape
556 600
     return this.mutate(data, shape, delta)
@@ -598,7 +642,7 @@ export class TLDR {
598 642
       next = this.onChildrenChange(data, next) || next
599 643
     }
600 644
 
601
-    data.page.shapes[next.id] = next
645
+    // data.page.shapes[next.id] = next
602 646
 
603 647
     return next
604 648
   }
@@ -608,13 +652,15 @@ export class TLDR {
608 652
   /* -------------------------------------------------- */
609 653
 
610 654
   static updateParents(data: Data, changedShapeIds: string[]): void {
655
+    const page = this.getPage(data)
656
+
611 657
     if (changedShapeIds.length === 0) return
612 658
 
613 659
     const { shapes } = this.getPage(data)
614 660
 
615 661
     const parentToUpdateIds = Array.from(
616 662
       new Set(changedShapeIds.map((id) => shapes[id].parentId).values())
617
-    ).filter((id) => id !== data.page.id)
663
+    ).filter((id) => id !== page.id)
618 664
 
619 665
     for (const parentId of parentToUpdateIds) {
620 666
       const parent = shapes[parentId]
@@ -631,18 +677,17 @@ export class TLDR {
631 677
 
632 678
   static getSelectedStyle(data: Data): ShapeStyles | false {
633 679
     const {
634
-      page,
635
-      pageState,
636 680
       appState: { currentStyle },
637 681
     } = data
638 682
 
683
+    const page = this.getPage(data)
684
+    const pageState = this.getPageState(data)
685
+
639 686
     if (pageState.selectedIds.length === 0) {
640 687
       return currentStyle
641 688
     }
642 689
 
643
-    const shapeStyles = data.pageState.selectedIds.map((id) => {
644
-      return page.shapes[id].style
645
-    })
690
+    const shapeStyles = pageState.selectedIds.map((id) => page.shapes[id].style)
646 691
 
647 692
     const commonStyle = {} as ShapeStyles
648 693
 
@@ -673,17 +718,17 @@ export class TLDR {
673 718
   /*                      Bindings                      */
674 719
   /* -------------------------------------------------- */
675 720
 
676
-  static getBinding(data: Data, id: string): TLDrawBinding {
677
-    return this.getPage(data).bindings[id]
721
+  static getBinding(data: Data, id: string, pageId = data.appState.currentPageId): TLDrawBinding {
722
+    return this.getPage(data, pageId).bindings[id]
678 723
   }
679 724
 
680
-  static getBindings(data: Data): TLDrawBinding[] {
681
-    const page = this.getPage(data)
725
+  static getBindings(data: Data, pageId = data.appState.currentPageId): TLDrawBinding[] {
726
+    const page = this.getPage(data, pageId)
682 727
     return Object.values(page.bindings)
683 728
   }
684 729
 
685 730
   static getBindableShapeIds(data: Data) {
686
-    return Object.values(data.page.shapes)
731
+    return this.getShapes(data)
687 732
       .filter((shape) => TLDR.getShapeUtils(shape).canBind)
688 733
       .sort((a, b) => b.childIndex - a.childIndex)
689 734
       .map((shape) => shape.id)
@@ -699,22 +744,13 @@ export class TLDR {
699 744
     )
700 745
   }
701 746
 
702
-  static createBindings(data: Data, bindings: TLDrawBinding[]): void {
703
-    const page = this.getPage(data)
704
-    bindings.forEach((binding) => (page.bindings[binding.id] = binding))
705
-  }
706
-
707
-  // static deleteBindings(data: Data, ids: string[]): void {
708
-  //   if (ids.length === 0) return
709
-  //   const page = this.getPage(data)
710
-  //   ids.forEach((id) => delete page.bindings[id])
711
-  // }
712
-
713 747
   static getRelatedBindings(data: Data, ids: string[]): TLDrawBinding[] {
714 748
     const changedShapeIds = new Set(ids)
715 749
 
750
+    const page = this.getPage(data)
751
+
716 752
     // Find all bindings that we need to update
717
-    const bindingsArr = Object.values(data.page.bindings)
753
+    const bindingsArr = Object.values(page.bindings)
718 754
 
719 755
     // Start with bindings that are directly bound to our changed shapes
720 756
     const bindingsToUpdate = new Set(
@@ -751,42 +787,6 @@ export class TLDR {
751 787
     return Array.from(bindingsToUpdate.values())
752 788
   }
753 789
 
754
-  // static cleanupBindings(data: Data, bindings: TLDrawBinding[]) {
755
-  //   const before: Record<string, Partial<TLDrawShape>> = {}
756
-  //   const after: Record<string, Partial<TLDrawShape>> = {}
757
-
758
-  //   // We also need to delete bindings that reference the deleted shapes
759
-  //   Object.values(bindings).forEach((binding) => {
760
-  //     for (const id of [binding.toId, binding.fromId]) {
761
-  //       // If the binding references a deleted shape...
762
-  //       if (after[id] === undefined) {
763
-  //         // Delete this binding
764
-  //         before.bindings[binding.id] = binding
765
-  //         after.bindings[binding.id] = undefined
766
-
767
-  //         // Let's also look at the bound shape...
768
-  //         const shape = data.page.shapes[id]
769
-
770
-  //         // If the bound shape has a handle that references the deleted binding, delete that reference
771
-  //         if (shape.handles) {
772
-  //           Object.values(shape.handles)
773
-  //             .filter((handle) => handle.bindingId === binding.id)
774
-  //             .forEach((handle) => {
775
-  //               before.shapes[id] = {
776
-  //                 ...before.shapes[id],
777
-  //                 handles: { ...before.shapes[id]?.handles, [handle.id]: { bindingId: binding.id } },
778
-  //               }
779
-  //               after.shapes[id] = {
780
-  //                 ...after.shapes[id],
781
-  //                 handles: { ...after.shapes[id]?.handles, [handle.id]: { bindingId: undefined } },
782
-  //               }
783
-  //             })
784
-  //         }
785
-  //       }
786
-  //     }
787
-  //   })
788
-  // }
789
-
790 790
   /* -------------------------------------------------- */
791 791
   /*                     Assertions                     */
792 792
   /* -------------------------------------------------- */
@@ -799,51 +799,4 @@ export class TLDR {
799 799
       throw new Error()
800 800
     }
801 801
   }
802
-
803
-  // static updateBindings(data: Data, changedShapeIds: string[]): void {
804
-  //   if (changedShapeIds.length === 0) return
805
-
806
-  //   // First gather all bindings that are directly affected by the change
807
-  //   const firstPassBindings = this.getBindingsWithShapeIds(data, changedShapeIds)
808
-
809
-  //   // Gather all shapes that will be effected by the binding changes
810
-  //   const effectedShapeIds = Array.from(
811
-  //     new Set(firstPassBindings.flatMap((binding) => [binding.toId, binding.fromId])).values(),
812
-  //   )
813
-
814
-  //   // Now get all bindings that are affected by those shapes
815
-  //   const bindingsToUpdate = this.getBindingsWithShapeIds(data, effectedShapeIds)
816
-
817
-  //   // Populate a map of { [shapeId]: BindingsThatWillEffectTheShape[] }
818
-  //   // Note that this will include both to and from bindings, and so will
819
-  //   // likely include ids other than the changedShapeIds provided.
820
-
821
-  //   const shapeIdToBindingsMap = new Map<string, TLDrawBinding[]>()
822
-
823
-  //   bindingsToUpdate.forEach((binding) => {
824
-  //     const { toId, fromId } = binding
825
-
826
-  //     for (const id of [toId, fromId]) {
827
-  //       if (!shapeIdToBindingsMap.has(id)) {
828
-  //         shapeIdToBindingsMap.set(id, [binding])
829
-  //       } else {
830
-  //         const bindings = shapeIdToBindingsMap.get(id)
831
-  //         bindings.push(binding)
832
-  //       }
833
-  //     }
834
-  //   })
835
-
836
-  //   // Update each effected shape with the binding that effects it.
837
-  //   Array.from(shapeIdToBindingsMap.entries()).forEach(([id, bindings]) => {
838
-  //     const shape = this.getShape(data, id)
839
-  //     bindings.forEach((binding) => {
840
-  //       const otherShape =
841
-  //         binding.toId === id
842
-  //           ? this.getShape(data, binding.fromId)
843
-  //           : this.getShape(data, binding.toId)
844
-
845
-  //       this.onBindingChange(data, shape, binding, otherShape)
846
-  //     })
847
-  //   })
848
-  // }
849 802
 }

+ 42
- 26
packages/tldraw/src/state/tlstate.ts Voir le fichier

@@ -108,7 +108,6 @@ export class TLDrawState implements TLCallbacks {
108 108
   pointedHandle?: string
109 109
   editingId?: string
110 110
   pointedBoundsHandle?: TLBoundsCorner | TLBoundsEdge | 'rotate'
111
-  currentDocumentId = 'doc'
112 111
   currentPageId = 'page'
113 112
   document: TLDrawDocument
114 113
   isCreating = false
@@ -142,26 +141,29 @@ export class TLDrawState implements TLCallbacks {
142 141
 
143 142
     // Remove deleted shapes and bindings (in Commands, these will be set to undefined)
144 143
     if (result.document) {
145
-      for (const pageId in result.document.pages) {
146
-        const currentPage = next.document.pages[pageId]
144
+      Object.values(current.document.pages).forEach((currentPage) => {
145
+        const pageId = currentPage.id
147 146
         const nextPage = {
148
-          ...next.document,
149
-          shapes: { ...currentPage.shapes },
150
-          bindings: { ...currentPage.bindings },
147
+          ...currentPage,
148
+          ...result.document?.pages[pageId],
149
+          shapes: { ...result.document?.pages[pageId]?.shapes },
150
+          bindings: { ...result.document?.pages[pageId]?.bindings },
151 151
         }
152 152
 
153
-        for (const id in nextPage.shapes) {
153
+        Object.keys(nextPage.shapes).forEach((id) => {
154 154
           if (!nextPage.shapes[id]) delete nextPage.shapes[id]
155
-        }
155
+        })
156 156
 
157
-        for (const id in nextPage.bindings) {
157
+        Object.keys(nextPage.bindings).forEach((id) => {
158 158
           if (!nextPage.bindings[id]) delete nextPage.bindings[id]
159
-        }
159
+        })
160 160
 
161 161
         const changedShapeIds = Object.values(nextPage.shapes)
162 162
           .filter((shape) => currentPage.shapes[shape.id] !== shape)
163 163
           .map((shape) => shape.id)
164 164
 
165
+        next.document.pages[pageId] = nextPage
166
+
165 167
         // Get bindings related to the changed shapes
166 168
         const bindingsToUpdate = TLDR.getRelatedBindings(next, changedShapeIds)
167 169
 
@@ -203,7 +205,7 @@ export class TLDrawState implements TLCallbacks {
203 205
         }
204 206
 
205 207
         if (nextPageState.bindingId && !nextPage.bindings[nextPageState.bindingId]) {
206
-          console.warn('Could not find the binding shape!')
208
+          console.warn('Could not find the binding shape!', pageId)
207 209
           delete nextPageState.bindingId
208 210
         }
209 211
 
@@ -214,7 +216,7 @@ export class TLDrawState implements TLCallbacks {
214 216
 
215 217
         next.document.pages[pageId] = nextPage
216 218
         next.document.pageStates[pageId] = nextPageState
217
-      }
219
+      })
218 220
     }
219 221
 
220 222
     // Apply selected style change, if any
@@ -245,19 +247,23 @@ export class TLDrawState implements TLCallbacks {
245 247
   }
246 248
 
247 249
   getShape = <T extends TLDrawShape = TLDrawShape>(id: string, pageId = this.currentPageId): T => {
248
-    return this.document.pages[pageId].shapes[id] as T
250
+    return TLDR.getShape<T>(this.data, id, pageId)
249 251
   }
250 252
 
251
-  getPage = (id = this.currentPageId) => {
252
-    return this.document.pages[id]
253
+  getPage = (pageId = this.currentPageId) => {
254
+    return TLDR.getPage(this.data, pageId)
253 255
   }
254 256
 
255
-  getShapes = (id = this.currentPageId) => {
256
-    return Object.values(this.getPage(id).shapes).sort((a, b) => a.childIndex - b.childIndex)
257
+  getShapes = (pageId = this.currentPageId) => {
258
+    return TLDR.getShapes(this.data, pageId)
257 259
   }
258 260
 
259
-  getPageState = (id = this.currentPageId) => {
260
-    return this.document.pageStates[id]
261
+  getBindings = (pageId = this.currentPageId) => {
262
+    return TLDR.getBindings(this.data, pageId)
263
+  }
264
+
265
+  getPageState = (pageId = this.currentPageId) => {
266
+    return TLDR.getPageState(this.data, pageId)
261 267
   }
262 268
 
263 269
   getAppState = () => {
@@ -265,7 +271,7 @@ export class TLDrawState implements TLCallbacks {
265 271
   }
266 272
 
267 273
   getPagePoint = (point: number[]) => {
268
-    const { camera } = this.getPageState()
274
+    const { camera } = this.pageState
269 275
     return Vec.sub(Vec.div(point, camera.zoom), camera.point)
270 276
   }
271 277
 
@@ -598,7 +604,6 @@ export class TLDrawState implements TLCallbacks {
598 604
 
599 605
   loadDocument = (document: TLDrawDocument, onChange?: TLDrawState['_onChange']) => {
600 606
     this._onChange = onChange
601
-    this.currentDocumentId = document.id
602 607
     this.document = Utils.deepClone(document)
603 608
     this.currentPageId = Object.keys(document.pages)[0]
604 609
     this.selectHistory.pointer = 0
@@ -637,7 +642,12 @@ export class TLDrawState implements TLCallbacks {
637 642
 
638 643
   startSession<T extends Session>(session: T, ...args: ParametersExceptFirst<T['start']>) {
639 644
     this.session = session
640
-    this.setState((data) => session.start(data, ...args), session.status)
645
+    const result = session.start(this.getState(), ...args)
646
+    if (result) {
647
+      this.setState((data) => Utils.deepMerge<Data>(data, result), session.status)
648
+    } else {
649
+      this.setStatus(session.status)
650
+    }
641 651
     this._onChange?.(this, `session:start_${session.id}`)
642 652
     return this
643 653
   }
@@ -645,7 +655,7 @@ export class TLDrawState implements TLCallbacks {
645 655
   updateSession<T extends Session>(...args: ParametersExceptFirst<T['update']>) {
646 656
     const { session } = this
647 657
     if (!session) return this
648
-    this.setState((data) => session.update(data, ...args))
658
+    this.setState((data) => Utils.deepMerge<Data>(data, session.update(data, ...args)))
649 659
     this._onChange?.(this, `session:update:${session.id}`)
650 660
     return this
651 661
   }
@@ -696,7 +706,10 @@ export class TLDrawState implements TLCallbacks {
696 706
       this.isCreating = false
697 707
       this._onChange?.(this, `session:cancel_create:${session.id}`)
698 708
     } else {
699
-      this.setState((data) => session.cancel(data, ...args), TLDrawStatus.Idle)
709
+      this.setState(
710
+        (data) => Utils.deepMerge<Data>(data, session.cancel(data, ...args)),
711
+        TLDrawStatus.Idle
712
+      )
700 713
       this._onChange?.(this, `session:cancel:${session.id}`)
701 714
     }
702 715
 
@@ -719,7 +732,10 @@ export class TLDrawState implements TLCallbacks {
719 732
 
720 733
     this.session = undefined
721 734
 
722
-    if ('after' in result) {
735
+    if (result === undefined) {
736
+      this.isCreating = false
737
+      this._onChange?.(this, `session:complete:${session.id}`)
738
+    } else if ('after' in result) {
723 739
       // Session ended with a command
724 740
 
725 741
       if (this.isCreating) {
@@ -2108,7 +2124,7 @@ export class TLDrawState implements TLCallbacks {
2108 2124
   }
2109 2125
 
2110 2126
   get bindings() {
2111
-    return this.getPage().bindings
2127
+    return this.getBindings()
2112 2128
   }
2113 2129
 
2114 2130
   get pageState() {

+ 8
- 5
packages/tldraw/src/types.ts Voir le fichier

@@ -40,7 +40,10 @@ export interface Data {
40 40
     status: { current: TLDrawStatus; previous: TLDrawStatus }
41 41
   }
42 42
 }
43
-export type PagePartial = DeepPartial<TLDrawPage>
43
+export type PagePartial = {
44
+  shapes: DeepPartial<TLDrawPage['shapes']>
45
+  bindings: DeepPartial<TLDrawPage['bindings']>
46
+}
44 47
 
45 48
 export type DeepPartial<T> = T extends Function
46 49
   ? T
@@ -69,10 +72,10 @@ export interface SelectHistory {
69 72
 export interface Session {
70 73
   id: string
71 74
   status: TLDrawStatus
72
-  start: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
73
-  update: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
74
-  complete: (data: Readonly<Data>, ...args: any[]) => Partial<Data> | Command
75
-  cancel: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
75
+  start: (data: Readonly<Data>, ...args: any[]) => DeepPartial<Data> | void
76
+  update: (data: Readonly<Data>, ...args: any[]) => DeepPartial<Data>
77
+  complete: (data: Readonly<Data>, ...args: any[]) => DeepPartial<Data> | Command | undefined
78
+  cancel: (data: Readonly<Data>, ...args: any[]) => DeepPartial<Data>
76 79
 }
77 80
 
78 81
 export enum TLDrawStatus {

+ 1
- 1
packages/www/components/editor.tsx Voir le fichier

@@ -95,5 +95,5 @@ export default function Editor(): JSX.Element {
95 95
     return <div />
96 96
   }
97 97
 
98
-  return <TLDraw document={value} onChange={handleChange} />
98
+  return <TLDraw document={initialDoc} onChange={handleChange} />
99 99
 }

+ 17
- 15
packages/www/hooks/usePersistence.tsx Voir le fichier

@@ -1,6 +1,6 @@
1
-import * as React from "react"
2
-import { openDB, DBSchema } from "idb"
3
-import type { TLDrawDocument } from "@tldraw/tldraw"
1
+import * as React from 'react'
2
+import { openDB, DBSchema } from 'idb'
3
+import type { TLDrawDocument } from '@tldraw/tldraw'
4 4
 
5 5
 interface TLDatabase extends DBSchema {
6 6
   documents: {
@@ -9,6 +9,8 @@ interface TLDatabase extends DBSchema {
9 9
   }
10 10
 }
11 11
 
12
+const VERSION = 2
13
+
12 14
 /**
13 15
  * Persist a value in indexdb. This hook is designed to be used primarily through
14 16
  * its methods, `setValue` and `forceUpdate`. The `setValue` method will update the
@@ -24,20 +26,20 @@ interface TLDatabase extends DBSchema {
24 26
  */
25 27
 export function usePersistence(id: string, doc: TLDrawDocument) {
26 28
   // eslint-disable-next-line react-hooks/exhaustive-deps
27
-  const [status, setStatus] = React.useState<"loading" | "ready">("loading")
29
+  const [status, setStatus] = React.useState<'loading' | 'ready'>('loading')
28 30
   const [value, _setValue] = React.useState<TLDrawDocument | null>(null)
29 31
 
30 32
   // A function that other parts of the program can use to manually update
31 33
   // the state to the latest value in the database.
32 34
   const forceUpdate = React.useCallback(() => {
33 35
     _setValue(null)
34
-    setStatus("loading")
36
+    setStatus('loading')
35 37
 
36
-    openDB<TLDatabase>("db", 1).then((db) =>
37
-      db.get("documents", id).then((v) => {
38
+    openDB<TLDatabase>('db', VERSION).then((db) =>
39
+      db.get('documents', id).then((v) => {
38 40
         if (!v) throw Error(`Could not find document with id: ${id}`)
39 41
         _setValue(v)
40
-        setStatus("ready")
42
+        setStatus('ready')
41 43
       })
42 44
     )
43 45
   }, [id])
@@ -46,7 +48,7 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
46 48
   // value in the database.
47 49
   const setValue = React.useCallback(
48 50
     (doc: TLDrawDocument) => {
49
-      openDB<TLDatabase>("db", 1).then((db) => db.put("documents", doc, id))
51
+      openDB<TLDatabase>('db', VERSION).then((db) => db.put('documents', doc, id))
50 52
     },
51 53
     [id]
52 54
   )
@@ -55,17 +57,17 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
55 57
   // the state.
56 58
   React.useEffect(() => {
57 59
     async function handleLoad() {
58
-      const db = await openDB<TLDatabase>("db", 1, {
60
+      const db = await openDB<TLDatabase>('db', VERSION, {
59 61
         upgrade(db) {
60
-          db.createObjectStore("documents")
62
+          db.createObjectStore('documents')
61 63
         },
62 64
       })
63 65
 
64 66
       let savedDoc: TLDrawDocument
65 67
 
66 68
       try {
67
-        const restoredDoc = await db.get("documents", id)
68
-        if (!restoredDoc) throw Error("No document")
69
+        const restoredDoc = await db.get('documents', id)
70
+        if (!restoredDoc) throw Error('No document')
69 71
         savedDoc = restoredDoc
70 72
         restoredDoc.pageStates = Object.fromEntries(
71 73
           Object.entries(restoredDoc.pageStates).map(([pageId, pageState]) => [
@@ -78,12 +80,12 @@ export function usePersistence(id: string, doc: TLDrawDocument) {
78 80
           ])
79 81
         )
80 82
       } catch (e) {
81
-        await db.put("documents", doc, id)
83
+        await db.put('documents', doc, id)
82 84
         savedDoc = doc
83 85
       }
84 86
 
85 87
       _setValue(savedDoc)
86
-      setStatus("ready")
88
+      setStatus('ready')
87 89
     }
88 90
 
89 91
     handleLoad()

Chargement…
Annuler
Enregistrer