Browse Source

Fixes deleting pages, adds local file saving / loading

main
Steve Ruiz 4 years ago
parent
commit
61f56b984e

+ 0
- 2
components/canvas/bounds/handles.tsx View File

25
 
25
 
26
   const center = getShapeUtils(shape).getCenter(shape)
26
   const center = getShapeUtils(shape).getCenter(shape)
27
 
27
 
28
-  console.log(shape)
29
-
30
   return (
28
   return (
31
     <g transform={`rotate(${shape.rotation * (180 / Math.PI)},${center})`}>
29
     <g transform={`rotate(${shape.rotation * (180 / Math.PI)},${center})`}>
32
       {Object.values(shape.handles).map((handle) => (
30
       {Object.values(shape.handles).map((handle) => (

+ 8
- 1
hooks/useKeyboardEvents.ts View File

134
         }
134
         }
135
         case 's': {
135
         case 's': {
136
           if (metaKey(e)) {
136
           if (metaKey(e)) {
137
-            state.send('SAVED', getKeyboardEventInfo(e))
137
+            if (e.shiftKey) {
138
+              state.send('SAVED_AS_TO_FILESYSTEM', getKeyboardEventInfo(e))
139
+            } else {
140
+              state.send('SAVED', getKeyboardEventInfo(e))
141
+            }
138
           }
142
           }
139
           break
143
           break
140
         }
144
         }
187
         }
191
         }
188
         case 'l': {
192
         case 'l': {
189
           if (metaKey(e)) {
193
           if (metaKey(e)) {
194
+            if (e.shiftKey) {
195
+              state.send('LOADED_FROM_FILE_STSTEM', getKeyboardEventInfo(e))
196
+            }
190
           } else {
197
           } else {
191
             state.send('SELECTED_LINE_TOOL', getKeyboardEventInfo(e))
198
             state.send('SELECTED_LINE_TOOL', getKeyboardEventInfo(e))
192
           }
199
           }

+ 1
- 0
package.json View File

18
     "@radix-ui/react-tooltip": "^0.0.18",
18
     "@radix-ui/react-tooltip": "^0.0.18",
19
     "@state-designer/react": "^1.7.3",
19
     "@state-designer/react": "^1.7.3",
20
     "@stitches/react": "^0.1.9",
20
     "@stitches/react": "^0.1.9",
21
+    "browser-fs-access": "^0.17.3",
21
     "framer-motion": "^4.1.16",
22
     "framer-motion": "^4.1.16",
22
     "ismobilejs": "^1.1.1",
23
     "ismobilejs": "^1.1.1",
23
     "next": "10.2.0",
24
     "next": "10.2.0",

+ 2
- 2
state/commands/change-page.ts View File

13
       category: 'canvas',
13
       category: 'canvas',
14
       manualSelection: true,
14
       manualSelection: true,
15
       do(data) {
15
       do(data) {
16
-        storage.savePage(data, data.currentPageId)
16
+        storage.savePage(data, data.document.id, prevPageId)
17
         data.currentPageId = pageId
17
         data.currentPageId = pageId
18
-        storage.loadPage(data, data.currentPageId)
18
+        storage.loadPage(data)
19
       },
19
       },
20
       undo(data) {
20
       undo(data) {
21
         data.currentPageId = prevPageId
21
         data.currentPageId = prevPageId

+ 1
- 1
state/commands/create-page.ts View File

19
         data.document.pages[page.id] = page
19
         data.document.pages[page.id] = page
20
         data.pageStates[page.id] = pageState
20
         data.pageStates[page.id] = pageState
21
         data.currentPageId = page.id
21
         data.currentPageId = page.id
22
-        storage.savePage(data, page.id)
22
+        storage.savePage(data, data.document.id, page.id)
23
       },
23
       },
24
       undo(data) {
24
       undo(data) {
25
         const { page, currentPageId } = snapshot
25
         const { page, currentPageId } = snapshot

+ 9
- 8
state/commands/delete-page.ts View File

5
 import { getPage, getSelectedShapes } from 'utils/utils'
5
 import { getPage, getSelectedShapes } from 'utils/utils'
6
 import { getShapeUtils } from 'lib/shape-utils'
6
 import { getShapeUtils } from 'lib/shape-utils'
7
 import * as vec from 'utils/vec'
7
 import * as vec from 'utils/vec'
8
+import storage from 'state/storage'
8
 
9
 
9
 export default function changePage(data: Data, pageId: string) {
10
 export default function changePage(data: Data, pageId: string) {
10
   const snapshot = getSnapshot(data, pageId)
11
   const snapshot = getSnapshot(data, pageId)
18
         data.currentPageId = snapshot.nextPageId
19
         data.currentPageId = snapshot.nextPageId
19
         delete data.document.pages[pageId]
20
         delete data.document.pages[pageId]
20
         delete data.pageStates[pageId]
21
         delete data.pageStates[pageId]
22
+        storage.loadPage(data, snapshot.nextPageId)
21
       },
23
       },
22
       undo(data) {
24
       undo(data) {
23
         data.currentPageId = snapshot.currentPageId
25
         data.currentPageId = snapshot.currentPageId
24
         data.document.pages[pageId] = snapshot.page
26
         data.document.pages[pageId] = snapshot.page
25
         data.pageStates[pageId] = snapshot.pageState
27
         data.pageStates[pageId] = snapshot.pageState
28
+        storage.loadPage(data, snapshot.currentPageId)
26
       },
29
       },
27
     })
30
     })
28
   )
31
   )
37
 
40
 
38
   const isCurrent = currentPageId === pageId
41
   const isCurrent = currentPageId === pageId
39
 
42
 
40
-  const nextIndex = isCurrent
41
-    ? page.childIndex === 0
42
-      ? 1
43
-      : page.childIndex - 1
44
-    : document.pages[currentPageId].childIndex
43
+  // const nextIndex = isCurrent
44
+  //   ? page.childIndex === 0
45
+  //     ? 1
46
+  //     : page.childIndex - 1
47
+  //   : document.pages[currentPageId].childIndex
45
 
48
 
46
   const nextPageId = isCurrent
49
   const nextPageId = isCurrent
47
-    ? Object.values(document.pages).find(
48
-        (page) => page.childIndex === nextIndex
49
-      )!.id
50
+    ? Object.values(document.pages).filter((page) => page.id !== pageId)[0]?.id // TODO: should be at nextIndex
50
     : cData.currentPageId
51
     : cData.currentPageId
51
 
52
 
52
   return {
53
   return {

+ 1
- 1
state/commands/draw.ts View File

10
   history.execute(
10
   history.execute(
11
     data,
11
     data,
12
     new Command({
12
     new Command({
13
-      name: 'set_points',
13
+      name: 'create_draw_shape',
14
       category: 'canvas',
14
       category: 'canvas',
15
       manualSelection: true,
15
       manualSelection: true,
16
       do(data, initial) {
16
       do(data, initial) {

+ 2
- 2
state/commands/move-to-page.ts View File

71
         getPageState(data, fromPageId).selectedIds.clear()
71
         getPageState(data, fromPageId).selectedIds.clear()
72
 
72
 
73
         // Save the "from" page
73
         // Save the "from" page
74
-        storage.savePage(data, fromPageId)
74
+        storage.savePage(data, data.document.id, fromPageId)
75
 
75
 
76
         // Load the "to" page
76
         // Load the "to" page
77
         storage.loadPage(data, toPageId)
77
         storage.loadPage(data, toPageId)
124
 
124
 
125
         getPageState(data, fromPageId).selectedIds.clear()
125
         getPageState(data, fromPageId).selectedIds.clear()
126
 
126
 
127
-        storage.savePage(data, fromPageId)
127
+        storage.savePage(data, data.document.id, fromPageId)
128
 
128
 
129
         storage.loadPage(data, toPageId)
129
         storage.loadPage(data, toPageId)
130
 
130
 

+ 2
- 0
state/data.ts View File

2
 import shapeUtils from 'lib/shape-utils'
2
 import shapeUtils from 'lib/shape-utils'
3
 
3
 
4
 export const defaultDocument: Data['document'] = {
4
 export const defaultDocument: Data['document'] = {
5
+  id: '0001',
6
+  name: 'My Document',
5
   pages: {
7
   pages: {
6
     page1: {
8
     page1: {
7
       id: 'page1',
9
       id: 'page1',

+ 3
- 3
state/history.ts View File

23
       this.pointer = this.maxLength - 1
23
       this.pointer = this.maxLength - 1
24
     }
24
     }
25
 
25
 
26
-    storage.save(data)
26
+    storage.saveToLocalStorage(data)
27
   }
27
   }
28
 
28
 
29
   undo = (data: T) => {
29
   undo = (data: T) => {
32
     command.undo(data)
32
     command.undo(data)
33
     if (this.disabled) return
33
     if (this.disabled) return
34
     this.pointer--
34
     this.pointer--
35
-    storage.save(data)
35
+    storage.saveToLocalStorage(data)
36
   }
36
   }
37
 
37
 
38
   redo = (data: T) => {
38
   redo = (data: T) => {
41
     command.redo(data, false)
41
     command.redo(data, false)
42
     if (this.disabled) return
42
     if (this.disabled) return
43
     this.pointer++
43
     this.pointer++
44
-    storage.save(data)
44
+    storage.saveToLocalStorage(data)
45
   }
45
   }
46
 
46
 
47
   disable = () => {
47
   disable = () => {

+ 12
- 12
state/inputs.tsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { PointerInfo } from 'types'
2
 import { PointerInfo } from 'types'
3
-import { isDarwin } from 'utils/utils'
3
+import { isDarwin, getPoint } from 'utils/utils'
4
 
4
 
5
 const DOUBLE_CLICK_DURATION = 300
5
 const DOUBLE_CLICK_DURATION = 300
6
 
6
 
17
     const info = {
17
     const info = {
18
       target,
18
       target,
19
       pointerId: touch.identifier,
19
       pointerId: touch.identifier,
20
-      origin: [touch.clientX, touch.clientY],
21
-      point: [touch.clientX, touch.clientY],
20
+      origin: getPoint(touch),
21
+      point: getPoint(touch),
22
       pressure: 0.5,
22
       pressure: 0.5,
23
       shiftKey,
23
       shiftKey,
24
       ctrlKey,
24
       ctrlKey,
42
     const info = {
42
     const info = {
43
       ...prev,
43
       ...prev,
44
       pointerId: touch.identifier,
44
       pointerId: touch.identifier,
45
-      point: [touch.clientX, touch.clientY],
45
+      point: getPoint(touch),
46
       pressure: 0.5,
46
       pressure: 0.5,
47
       shiftKey,
47
       shiftKey,
48
       ctrlKey,
48
       ctrlKey,
63
     const info = {
63
     const info = {
64
       target,
64
       target,
65
       pointerId: e.pointerId,
65
       pointerId: e.pointerId,
66
-      origin: [e.clientX, e.clientY],
67
-      point: [e.clientX, e.clientY],
66
+      origin: getPoint(e),
67
+      point: getPoint(e),
68
       pressure: e.pressure || 0.5,
68
       pressure: e.pressure || 0.5,
69
       shiftKey,
69
       shiftKey,
70
       ctrlKey,
70
       ctrlKey,
84
     const info = {
84
     const info = {
85
       target,
85
       target,
86
       pointerId: e.pointerId,
86
       pointerId: e.pointerId,
87
-      origin: [e.clientX, e.clientY],
88
-      point: [e.clientX, e.clientY],
87
+      origin: getPoint(e),
88
+      point: getPoint(e),
89
       pressure: e.pressure || 0.5,
89
       pressure: e.pressure || 0.5,
90
       shiftKey,
90
       shiftKey,
91
       ctrlKey,
91
       ctrlKey,
104
     const info = {
104
     const info = {
105
       ...prev,
105
       ...prev,
106
       pointerId: e.pointerId,
106
       pointerId: e.pointerId,
107
-      point: [e.clientX, e.clientY],
107
+      point: getPoint(e),
108
       pressure: e.pressure || 0.5,
108
       pressure: e.pressure || 0.5,
109
       shiftKey,
109
       shiftKey,
110
       ctrlKey,
110
       ctrlKey,
126
 
126
 
127
     const info = {
127
     const info = {
128
       ...prev,
128
       ...prev,
129
-      origin: prev?.origin || [e.clientX, e.clientY],
130
-      point: [e.clientX, e.clientY],
129
+      origin: prev?.origin || getPoint(e),
130
+      point: getPoint(e),
131
       pressure: e.pressure || 0.5,
131
       pressure: e.pressure || 0.5,
132
       shiftKey,
132
       shiftKey,
133
       ctrlKey,
133
       ctrlKey,
144
 
144
 
145
   wheel(e: WheelEvent) {
145
   wheel(e: WheelEvent) {
146
     const { shiftKey, ctrlKey, metaKey, altKey } = e
146
     const { shiftKey, ctrlKey, metaKey, altKey } = e
147
-    return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
147
+    return { point: getPoint(e), shiftKey, ctrlKey, metaKey, altKey }
148
   }
148
   }
149
 
149
 
150
   canAccept(pointerId: PointerEvent['pointerId']) {
150
   canAccept(pointerId: PointerEvent['pointerId']) {

+ 1
- 1
state/sessions/draw-session.ts View File

72
 
72
 
73
     point = vec.med(this.previous, point)
73
     point = vec.med(this.previous, point)
74
 
74
 
75
-    const next = [...vec.sub(point, this.origin), pressure]
75
+    const next = vec.round([...vec.sub(point, this.origin), pressure])
76
 
76
 
77
     // Don't add duplicate points
77
     // Don't add duplicate points
78
     if (vec.isEqual(this.last, next)) return
78
     if (vec.isEqual(this.last, next)) return

+ 32
- 8
state/state.ts View File

172
         CHANGED_PAGE: 'changePage',
172
         CHANGED_PAGE: 'changePage',
173
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
173
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
174
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
174
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
175
+        LOADED_FROM_FILE: 'loadDocumentFromJson',
175
       },
176
       },
176
       initial: 'selecting',
177
       initial: 'selecting',
177
       states: {
178
       states: {
178
         selecting: {
179
         selecting: {
179
           onEnter: 'setActiveToolSelect',
180
           onEnter: 'setActiveToolSelect',
180
           on: {
181
           on: {
181
-            SAVED: 'forceSave',
182
             UNDO: 'undo',
182
             UNDO: 'undo',
183
             REDO: 'redo',
183
             REDO: 'redo',
184
+            SAVED: 'forceSave',
185
+            LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
186
+            SAVED_TO_FILESYSTEM: 'saveToFileSystem',
187
+            SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
184
             SAVED_CODE: 'saveCode',
188
             SAVED_CODE: 'saveCode',
185
             DELETED: 'deleteSelection',
189
             DELETED: 'deleteSelection',
186
             INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
190
             INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
433
                       do: 'createShape',
437
                       do: 'createShape',
434
                       to: 'draw.editing',
438
                       to: 'draw.editing',
435
                     },
439
                     },
436
-                    UNDO: { do: 'undo' },
437
-                    REDO: { do: 'redo' },
440
+                    UNDO: 'undo',
441
+                    REDO: 'redo',
442
+                    SAVED: 'forceSave',
443
+                    LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
444
+                    SAVED_TO_FILESYSTEM: 'saveToFileSystem',
445
+                    SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
438
                   },
446
                   },
439
                 },
447
                 },
440
                 editing: {
448
                 editing: {
1474
 
1482
 
1475
     /* ---------------------- Data ---------------------- */
1483
     /* ---------------------- Data ---------------------- */
1476
 
1484
 
1485
+    saveToFileSystem(data) {
1486
+      storage.saveToFileSystem(data)
1487
+    },
1488
+
1489
+    saveAsToFileSystem(data) {
1490
+      storage.saveAsToFileSystem(data)
1491
+    },
1492
+
1493
+    loadFromFileSystem() {
1494
+      storage.loadDocumentFromFilesystem()
1495
+    },
1496
+
1497
+    loadDocumentFromJson(data, payload: { restoredData: any }) {
1498
+      storage.load(data, payload.restoredData)
1499
+    },
1500
+
1477
     forceSave(data) {
1501
     forceSave(data) {
1478
-      storage.save(data)
1502
+      storage.saveToLocalStorage(data)
1479
     },
1503
     },
1480
 
1504
 
1481
     savePage(data) {
1505
     savePage(data) {
1482
-      storage.savePage(data, data.currentPageId)
1506
+      storage.savePage(data)
1483
     },
1507
     },
1484
 
1508
 
1485
     loadPage(data) {
1509
     loadPage(data) {
1486
-      storage.loadPage(data, data.currentPageId)
1510
+      storage.loadPage(data)
1487
     },
1511
     },
1488
 
1512
 
1489
     saveCode(data, payload: { code: string }) {
1513
     saveCode(data, payload: { code: string }) {
1490
       data.document.code[data.currentCodeFileId].code = payload.code
1514
       data.document.code[data.currentCodeFileId].code = payload.code
1491
-      storage.save(data)
1515
+      storage.saveToLocalStorage(data)
1492
     },
1516
     },
1493
 
1517
 
1494
     restoreSavedData(data) {
1518
     restoreSavedData(data) {
1495
-      storage.load(data)
1519
+      storage.loadDocumentFromLocalStorage(data)
1496
     },
1520
     },
1497
 
1521
 
1498
     clearBoundsRotation(data) {
1522
     clearBoundsRotation(data) {

+ 178
- 62
state/storage.ts View File

1
-import { Data, Page, PageState } from 'types'
1
+import * as fa from 'browser-fs-access'
2
+import { Data, Page, PageState, TLDocument } from 'types'
2
 import { setToArray } from 'utils/utils'
3
 import { setToArray } from 'utils/utils'
4
+import state from './state'
5
+import { v4 as uuid } from 'uuid'
3
 
6
 
4
-const CURRENT_VERSION = 'code_slate_0.0.4'
7
+const CURRENT_VERSION = 'code_slate_0.0.5'
5
 const DOCUMENT_ID = '0001'
8
 const DOCUMENT_ID = '0001'
6
 
9
 
7
-function storageId(label: string, id: string) {
8
-  return `${CURRENT_VERSION}_doc_${DOCUMENT_ID}_${label}_${id}`
10
+function storageId(label: string, fileId: string, id: string) {
11
+  return `${CURRENT_VERSION}_doc_${fileId}_${label}_${id}`
9
 }
12
 }
10
 
13
 
11
 class Storage {
14
 class Storage {
12
-  // Saving
13
-  load(data: Data, id = CURRENT_VERSION) {
14
-    if (typeof window === 'undefined') return
15
-    if (typeof localStorage === 'undefined') return
15
+  load(data: Data, restoredData: any) {
16
+    // Empty shapes in state for each page
17
+    for (let key in restoredData.document.pages) {
18
+      restoredData.document.pages[key].shapes = {}
19
+    }
16
 
20
 
17
-    // Load data from local storage
18
-    const savedData = localStorage.getItem(id)
21
+    // Empty page states for each page
22
+    for (let key in restoredData.pageStates) {
23
+      restoredData.document.pages[key].shapes = {}
24
+    }
19
 
25
 
20
-    if (savedData !== null) {
21
-      const restoredData = JSON.parse(savedData)
26
+    data.document = {} as TLDocument
27
+    data.pageStates = {}
22
 
28
 
23
-      // Empty shapes in state for each page
24
-      for (let key in restoredData.document.pages) {
25
-        restoredData.document.pages[key].shapes = {}
26
-      }
29
+    // Merge restored data into state
30
+    Object.assign(data, restoredData)
27
 
31
 
28
-      // Empty page states for each page
29
-      for (let key in restoredData.pageStates) {
30
-        restoredData.document.pages[key].shapes = {}
31
-      }
32
+    // Minor migrtation: add id and name to document
33
+    data.document = {
34
+      id: 'document0',
35
+      name: 'My Document',
36
+      ...restoredData.document,
37
+    }
38
+
39
+    // Load current page
40
+    this.loadPage(data, data.currentPageId)
41
+  }
42
+
43
+  async loadDocumentFromFilesystem() {
44
+    const blob = await fa.fileOpen({
45
+      description: 'tldraw files',
46
+    })
32
 
47
 
33
-      // Merge restored data into state
34
-      Object.assign(data, restoredData)
48
+    const text = await getTextFromBlob(blob)
35
 
49
 
36
-      // Load current page
37
-      this.loadPage(data, data.currentPageId)
50
+    const restoredData = JSON.parse(text)
51
+
52
+    if (restoredData === null) {
53
+      console.warn('Could not load that data.')
54
+      return
38
     }
55
     }
56
+
57
+    state.send('LOADED_FROM_FILE', { restoredData })
39
   }
58
   }
40
 
59
 
41
-  save = (data: Data, id = CURRENT_VERSION) => {
60
+  loadDocumentFromLocalStorage(data: Data, fileId = DOCUMENT_ID) {
42
     if (typeof window === 'undefined') return
61
     if (typeof window === 'undefined') return
43
     if (typeof localStorage === 'undefined') return
62
     if (typeof localStorage === 'undefined') return
44
 
63
 
64
+    // Load data from local storage
65
+    const savedData = localStorage.getItem(fileId)
66
+
67
+    if (savedData === null) return false
68
+
69
+    const restoredData = JSON.parse(savedData)
70
+
71
+    this.load(data, restoredData)
72
+  }
73
+
74
+  getDataToSave = (data: Data) => {
45
     const dataToSave: any = { ...data }
75
     const dataToSave: any = { ...data }
46
 
76
 
47
-    // Don't save pageStates
77
+    for (let pageId in data.document) {
78
+      const savedPage = localStorage.getItem(
79
+        storageId(data.document.id, 'page', pageId)
80
+      )
81
+
82
+      if (savedPage !== null) {
83
+        const restored: Page = JSON.parse(savedPage)
84
+        dataToSave.document.pages[pageId] = restored
85
+      }
86
+    }
87
+
48
     dataToSave.pageStates = {}
88
     dataToSave.pageStates = {}
49
 
89
 
90
+    return JSON.stringify(dataToSave, null, 2)
91
+  }
92
+
93
+  saveToLocalStorage = (data: Data, id = data.document.id) => {
94
+    if (typeof window === 'undefined') return
95
+    if (typeof localStorage === 'undefined') return
96
+
50
     // Save current data to local storage
97
     // Save current data to local storage
51
-    localStorage.setItem(id, JSON.stringify(dataToSave))
98
+    localStorage.setItem(id, this.getDataToSave(data))
99
+
100
+    // Save current page too
101
+    this.savePage(data, id, data.currentPageId)
102
+
103
+    state.send('SAVED_FILE_TO_LOCAL_STORAGE')
104
+  }
105
+
106
+  saveAsToFileSystem = (data: Data) => {
107
+    // Create a new document id when saving to the file system
108
+    this.saveToFileSystem(data, uuid())
109
+  }
110
+
111
+  saveToFileSystem = (data: Data, id = data.document.id) => {
112
+    // Save to local storage first
113
+    this.saveToLocalStorage(data, id)
114
+
115
+    const json = this.getDataToSave(data)
116
+
117
+    const blob = new Blob([json], {
118
+      type: 'application/vnd.tldraw+json',
119
+    })
120
+
121
+    fa.fileSave(blob, {
122
+      fileName: `${data.document.name}.tldr`,
123
+      description: 'tldraw file',
124
+      extensions: ['.tldr'],
125
+    })
126
+      .then(() => {
127
+        state.send('SAVED_FILE_TO_FILE_SYSTEM')
128
+      })
129
+      .catch((e) => {
130
+        state.send('CANCELLED_SAVE', { reason: e.message })
131
+      })
132
+  }
133
+
134
+  loadPageFromLocalStorage(fileId: string, pageId: string) {
135
+    let restored: Page
136
+
137
+    const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
138
+
139
+    if (savedPage !== null) {
140
+      restored = JSON.parse(savedPage)
141
+    } else {
142
+      restored = {
143
+        id: pageId,
144
+        type: 'page',
145
+        childIndex: 0,
146
+        name: 'Page',
147
+        shapes: {},
148
+      }
149
+    }
52
 
150
 
53
-    // Save current page
54
-    this.savePage(data, data.currentPageId)
151
+    return restored
55
   }
152
   }
56
 
153
 
57
-  savePage(data: Data, pageId: string) {
154
+  loadPageStateFromLocalStorage(fileId: string, pageId: string) {
155
+    let restored: PageState
156
+
157
+    const savedPageState = localStorage.getItem(
158
+      storageId(fileId, 'pageState', pageId)
159
+    )
160
+
161
+    if (savedPageState !== null) {
162
+      restored = JSON.parse(savedPageState)
163
+      restored.selectedIds = new Set(restored.selectedIds)
164
+    } else {
165
+      restored = {
166
+        camera: {
167
+          point: [0, 0],
168
+          zoom: 1,
169
+        },
170
+        selectedIds: new Set([]),
171
+      }
172
+    }
173
+
174
+    return restored
175
+  }
176
+
177
+  savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
58
     if (typeof window === 'undefined') return
178
     if (typeof window === 'undefined') return
59
     if (typeof localStorage === 'undefined') return
179
     if (typeof localStorage === 'undefined') return
60
 
180
 
61
     // Save page
181
     // Save page
62
     const page = data.document.pages[pageId]
182
     const page = data.document.pages[pageId]
63
 
183
 
64
-    localStorage.setItem(storageId('page', pageId), JSON.stringify(page))
184
+    localStorage.setItem(
185
+      storageId(fileId, 'page', pageId),
186
+      JSON.stringify(page)
187
+    )
65
 
188
 
66
     // Save page state
189
     // Save page state
67
 
190
 
80
     }
203
     }
81
 
204
 
82
     localStorage.setItem(
205
     localStorage.setItem(
83
-      storageId('pageState', pageId),
206
+      storageId(fileId, 'pageState', pageId),
84
       JSON.stringify(pageState)
207
       JSON.stringify(pageState)
85
     )
208
     )
86
   }
209
   }
87
 
210
 
88
-  loadPage(data: Data, pageId: string) {
211
+  loadPage(data: Data, pageId = data.currentPageId) {
89
     if (typeof window === 'undefined') return
212
     if (typeof window === 'undefined') return
90
     if (typeof localStorage === 'undefined') return
213
     if (typeof localStorage === 'undefined') return
91
 
214
 
92
-    // Load page and merge into state
93
-    const savedPage = localStorage.getItem(storageId('page', pageId))
94
-
95
-    if (savedPage !== null) {
96
-      const restored: Page = JSON.parse(savedPage)
97
-      data.document.pages[pageId] = restored
98
-    }
215
+    const fileId = data.document.id
99
 
216
 
100
-    // Load page state and merge into state
101
-    const savedPageState = localStorage.getItem(storageId('pageState', pageId))
217
+    data.document.pages[pageId] = this.loadPageFromLocalStorage(fileId, pageId)
102
 
218
 
103
-    if (savedPageState !== null) {
104
-      const restored: PageState = JSON.parse(savedPageState)
105
-      restored.selectedIds = new Set(restored.selectedIds)
106
-      data.pageStates[pageId] = restored
107
-    } else {
108
-      data.pageStates[pageId] = {
109
-        camera: {
110
-          point: [0, 0],
111
-          zoom: 1,
112
-        },
113
-        selectedIds: new Set([]),
114
-      }
115
-    }
219
+    data.pageStates[pageId] = this.loadPageStateFromLocalStorage(fileId, pageId)
116
 
220
 
117
     // Empty shapes in state for other pages
221
     // Empty shapes in state for other pages
118
     for (let key in data.document.pages) {
222
     for (let key in data.document.pages) {
120
       data.document.pages[key].shapes = {}
224
       data.document.pages[key].shapes = {}
121
     }
225
     }
122
 
226
 
123
-    // Empty page states for other pages
124
-    for (let key in data.pageStates) {
125
-      if (key === pageId) continue
126
-      data.document.pages[key].shapes = {}
127
-    }
128
-
129
-    // Update camera
227
+    // Update camera for the new page state
130
     document.documentElement.style.setProperty(
228
     document.documentElement.style.setProperty(
131
       '--camera-zoom',
229
       '--camera-zoom',
132
       data.pageStates[data.currentPageId].camera.zoom.toString()
230
       data.pageStates[data.currentPageId].camera.zoom.toString()
137
 const storage = new Storage()
235
 const storage = new Storage()
138
 
236
 
139
 export default storage
237
 export default storage
238
+
239
+async function getTextFromBlob(blob: Blob): Promise<string> {
240
+  // Return blob as text if a text file.
241
+  if ('text' in Blob) return blob.text()
242
+
243
+  // Return blob as text if a text file.
244
+  return new Promise((resolve) => {
245
+    const reader = new FileReader()
246
+
247
+    reader.onloadend = () => {
248
+      if (reader.readyState === FileReader.DONE) {
249
+        resolve(reader.result as string)
250
+      }
251
+    }
252
+
253
+    reader.readAsText(blob, 'utf8')
254
+  })
255
+}

+ 8
- 4
types.ts View File

28
   currentParentId: string
28
   currentParentId: string
29
   currentCodeFileId: string
29
   currentCodeFileId: string
30
   codeControls: Record<string, CodeControl>
30
   codeControls: Record<string, CodeControl>
31
-  document: {
32
-    pages: Record<string, Page>
33
-    code: Record<string, CodeFile>
34
-  }
31
+  document: TLDocument
35
   pageStates: Record<string, PageState>
32
   pageStates: Record<string, PageState>
36
 }
33
 }
37
 
34
 
39
 /*                      Document                      */
36
 /*                      Document                      */
40
 /* -------------------------------------------------- */
37
 /* -------------------------------------------------- */
41
 
38
 
39
+export interface TLDocument {
40
+  id: string
41
+  name: string
42
+  pages: Record<string, Page>
43
+  code: Record<string, CodeFile>
44
+}
45
+
42
 export interface Page {
46
 export interface Page {
43
   id: string
47
   id: string
44
   type: 'page'
48
   type: 'page'

+ 10
- 0
utils/utils.ts View File

1758
 export function uniqueArray<T extends string | number | Symbol>(...items: T[]) {
1758
 export function uniqueArray<T extends string | number | Symbol>(...items: T[]) {
1759
   return Array.from(new Set(items).values())
1759
   return Array.from(new Set(items).values())
1760
 }
1760
 }
1761
+
1762
+export function getPoint(
1763
+  e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent
1764
+) {
1765
+  return [
1766
+    Number(e.clientX.toPrecision(4)),
1767
+    Number(e.clientY.toPrecision(4)),
1768
+    'pressure' in e ? e.pressure || 0.5 : 0.5,
1769
+  ]
1770
+}

+ 1
- 6
utils/vec.ts View File

339
   return isLeft(p1, pc, p2) > 0
339
   return isLeft(p1, pc, p2) > 0
340
 }
340
 }
341
 
341
 
342
-const rounds = [1, 10, 100, 1000]
343
-
344
 export function round(a: number[], d = 2) {
342
 export function round(a: number[], d = 2) {
345
-  return [
346
-    Math.round(a[0] * rounds[d]) / rounds[d],
347
-    Math.round(a[1] * rounds[d]) / rounds[d],
348
-  ]
343
+  return a.map((v) => +v.toFixed(d))
349
 }
344
 }
350
 
345
 
351
 /**
346
 /**

+ 5
- 0
yarn.lock View File

2567
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
2567
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
2568
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
2568
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
2569
 
2569
 
2570
+browser-fs-access@^0.17.3:
2571
+  version "0.17.3"
2572
+  resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.17.3.tgz#f91447b0b74bb8d224a8b01a7d18bd090468ef1d"
2573
+  integrity sha512-zrEWlsaQf3RKAeLjA6veRzTtf8ge3dFfun50RI74kSWKQKnJaioPc4I8oPzO6rDNiR0CJUI+oORuPOFMUkr0+g==
2574
+
2570
 browser-process-hrtime@^1.0.0:
2575
 browser-process-hrtime@^1.0.0:
2571
   version "1.0.0"
2576
   version "1.0.0"
2572
   resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
2577
   resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"

Loading…
Cancel
Save