Parcourir la source

Annoying file system saving stuff

main
Steve Ruiz il y a 4 ans
Parent
révision
50cffb3404
7 fichiers modifiés avec 226 ajouts et 215 suppressions
  1. 1
    0
      next.config.js
  2. 89
    85
      public/sw.js
  3. 1
    1
      public/sw.js.map
  4. 3
    0
      state/history.ts
  5. 1
    1
      state/state.ts
  6. 130
    128
      state/storage.ts
  7. 1
    0
      worker/index.js

+ 1
- 0
next.config.js Voir le fichier

@@ -2,6 +2,7 @@ const withPWA = require('next-pwa')
2 2
 
3 3
 module.exports = withPWA({
4 4
   pwa: {
5
+    disable: process.env.NODE_ENV === 'development',
5 6
     dest: 'public',
6 7
   },
7 8
 })

+ 89
- 85
public/sw.js Voir le fichier

@@ -13,116 +13,120 @@
13 13
 
14 14
 // If the loader is already loaded, just stop.
15 15
 if (!self.define) {
16
-  const singleRequire = name => {
16
+  const singleRequire = (name) => {
17 17
     if (name !== 'require') {
18
-      name = name + '.js';
18
+      name = name + '.js'
19 19
     }
20
-    let promise = Promise.resolve();
20
+    let promise = Promise.resolve()
21 21
     if (!registry[name]) {
22
-      
23
-        promise = new Promise(async resolve => {
24
-          if ("document" in self) {
25
-            const script = document.createElement("script");
26
-            script.src = name;
27
-            document.head.appendChild(script);
28
-            script.onload = resolve;
29
-          } else {
30
-            importScripts(name);
31
-            resolve();
32
-          }
33
-        });
34
-      
22
+      promise = new Promise(async (resolve) => {
23
+        if ('document' in self) {
24
+          const script = document.createElement('script')
25
+          script.src = name
26
+          document.head.appendChild(script)
27
+          script.onload = resolve
28
+        } else {
29
+          importScripts(name)
30
+          resolve()
31
+        }
32
+      })
35 33
     }
36 34
     return promise.then(() => {
37 35
       if (!registry[name]) {
38
-        throw new Error(`Module ${name} didn’t register its module`);
36
+        throw new Error(`Module ${name} didn’t register its module`)
39 37
       }
40
-      return registry[name];
41
-    });
42
-  };
38
+      return registry[name]
39
+    })
40
+  }
43 41
 
44 42
   const require = (names, resolve) => {
45
-    Promise.all(names.map(singleRequire))
46
-      .then(modules => resolve(modules.length === 1 ? modules[0] : modules));
47
-  };
48
-  
43
+    Promise.all(names.map(singleRequire)).then((modules) =>
44
+      resolve(modules.length === 1 ? modules[0] : modules)
45
+    )
46
+  }
47
+
49 48
   const registry = {
50
-    require: Promise.resolve(require)
51
-  };
49
+    require: Promise.resolve(require),
50
+  }
52 51
 
53 52
   self.define = (moduleName, depsNames, factory) => {
54 53
     if (registry[moduleName]) {
55 54
       // Module is already loading or loaded.
56
-      return;
55
+      return
57 56
     }
58 57
     registry[moduleName] = Promise.resolve().then(() => {
59
-      let exports = {};
58
+      let exports = {}
60 59
       const module = {
61
-        uri: location.origin + moduleName.slice(1)
62
-      };
60
+        uri: location.origin + moduleName.slice(1),
61
+      }
63 62
       return Promise.all(
64
-        depsNames.map(depName => {
65
-          switch(depName) {
66
-            case "exports":
67
-              return exports;
68
-            case "module":
69
-              return module;
63
+        depsNames.map((depName) => {
64
+          switch (depName) {
65
+            case 'exports':
66
+              return exports
67
+            case 'module':
68
+              return module
70 69
             default:
71
-              return singleRequire(depName);
70
+              return singleRequire(depName)
72 71
           }
73 72
         })
74
-      ).then(deps => {
75
-        const facValue = factory(...deps);
76
-        if(!exports.default) {
77
-          exports.default = facValue;
73
+      ).then((deps) => {
74
+        const facValue = factory(...deps)
75
+        if (!exports.default) {
76
+          exports.default = facValue
78 77
         }
79
-        return exports;
80
-      });
81
-    });
82
-  };
78
+        return exports
79
+      })
80
+    })
81
+  }
83 82
 }
84
-define("./sw.js",['./workbox-6b19f60b'], function (workbox) { 'use strict';
83
+define('./sw.js', ['./workbox-6b19f60b'], function (workbox) {
84
+  'use strict'
85 85
 
86 86
   /**
87
-  * Welcome to your Workbox-powered service worker!
88
-  *
89
-  * You'll need to register this file in your web app.
90
-  * See https://goo.gl/nhQhGp
91
-  *
92
-  * The rest of the code is auto-generated. Please don't update this file
93
-  * directly; instead, make changes to your Workbox build configuration
94
-  * and re-run your build process.
95
-  * See https://goo.gl/2aRDsh
96
-  */
87
+   * Welcome to your Workbox-powered service worker!
88
+   *
89
+   * You'll need to register this file in your web app.
90
+   * See https://goo.gl/nhQhGp
91
+   *
92
+   * The rest of the code is auto-generated. Please don't update this file
93
+   * directly; instead, make changes to your Workbox build configuration
94
+   * and re-run your build process.
95
+   * See https://goo.gl/2aRDsh
96
+   */
97 97
 
98
-  importScripts();
99
-  self.skipWaiting();
100
-  workbox.clientsClaim();
101
-  workbox.registerRoute("/", new workbox.NetworkFirst({
102
-    "cacheName": "start-url",
103
-    plugins: [{
104
-      cacheWillUpdate: async ({
105
-        request,
106
-        response,
107
-        event,
108
-        state
109
-      }) => {
110
-        if (response && response.type === 'opaqueredirect') {
111
-          return new Response(response.body, {
112
-            status: 200,
113
-            statusText: 'OK',
114
-            headers: response.headers
115
-          });
116
-        }
117
-
118
-        return response;
119
-      }
120
-    }]
121
-  }), 'GET');
122
-  workbox.registerRoute(/.*/i, new workbox.NetworkOnly({
123
-    "cacheName": "dev",
124
-    plugins: []
125
-  }), 'GET');
98
+  importScripts()
99
+  self.skipWaiting()
100
+  workbox.clientsClaim()
101
+  workbox.registerRoute(
102
+    '/',
103
+    new workbox.NetworkFirst({
104
+      cacheName: 'start-url',
105
+      plugins: [
106
+        {
107
+          cacheWillUpdate: async ({ request, response, event, state }) => {
108
+            if (response && response.type === 'opaqueredirect') {
109
+              return new Response(response.body, {
110
+                status: 200,
111
+                statusText: 'OK',
112
+                headers: response.headers,
113
+              })
114
+            }
126 115
 
127
-});
116
+            return response
117
+          },
118
+        },
119
+      ],
120
+    }),
121
+    'GET'
122
+  )
123
+  workbox.registerRoute(
124
+    /.*/i,
125
+    new workbox.NetworkOnly({
126
+      cacheName: 'dev',
127
+      plugins: [],
128
+    }),
129
+    'GET'
130
+  )
131
+})
128 132
 //# sourceMappingURL=sw.js.map

+ 1
- 1
public/sw.js.map Voir le fichier

@@ -1 +1 @@
1
-{"version":3,"file":"sw.js","sources":["../../../../../private/var/folders/3w/cj9n4h8j7xl3b82k45v7xw2r0000gn/T/c3a33184c37956dfaa3e384f35b959c0/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n  \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({request, response, event, state}) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, {status: 200, statusText: 'OK', headers: response.headers}); } return response; } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGoJ;EACpJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa;EAUbC,IAAI,CAACC,WAAL;AAEAC,sBAAyB;AAIzBC,uBAA6B,CAAC,GAAD,EAAM,IAAIC,oBAAJ,CAAoC;EAAE,eAAY,WAAd;EAA2BC,EAAAA,OAAO,EAAE,CAAC;EAAEC,IAAAA,eAAe,EAAE,OAAO;EAACC,MAAAA,OAAD;EAAUC,MAAAA,QAAV;EAAoBC,MAAAA,KAApB;EAA2BC,MAAAA;EAA3B,KAAP,KAA6C;EAAE,UAAIF,QAAQ,IAAIA,QAAQ,CAACG,IAAT,KAAkB,gBAAlC,EAAoD;EAAE,eAAO,IAAIC,QAAJ,CAAaJ,QAAQ,CAACK,IAAtB,EAA4B;EAACC,UAAAA,MAAM,EAAE,GAAT;EAAcC,UAAAA,UAAU,EAAE,IAA1B;EAAgCC,UAAAA,OAAO,EAAER,QAAQ,CAACQ;EAAlD,SAA5B,CAAP;EAAiG;;EAAC,aAAOR,QAAP;EAAkB;EAA5O,GAAD;EAApC,CAApC,CAAN,EAAmU,KAAnU,CAA7B;AACAL,uBAA6B,CAAC,KAAD,EAAQ,IAAIc,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBZ,EAAAA,OAAO,EAAE;EAA9B,CAAnC,CAAR,EAAgF,KAAhF,CAA7B;;"}
1
+{"version":3,"file":"sw.js","sources":["../../../../../private/var/folders/3w/cj9n4h8j7xl3b82k45v7xw2r0000gn/T/7c1696e7af803225aac54932551101fc/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/stephenruiz/Developer/GitHub/code-slate/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n  \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({request, response, event, state}) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, {status: 200, statusText: 'OK', headers: response.headers}); } return response; } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGoJ;EACpJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa;EAUbC,IAAI,CAACC,WAAL;AAEAC,sBAAyB;AAIzBC,uBAA6B,CAAC,GAAD,EAAM,IAAIC,oBAAJ,CAAoC;EAAE,eAAY,WAAd;EAA2BC,EAAAA,OAAO,EAAE,CAAC;EAAEC,IAAAA,eAAe,EAAE,OAAO;EAACC,MAAAA,OAAD;EAAUC,MAAAA,QAAV;EAAoBC,MAAAA,KAApB;EAA2BC,MAAAA;EAA3B,KAAP,KAA6C;EAAE,UAAIF,QAAQ,IAAIA,QAAQ,CAACG,IAAT,KAAkB,gBAAlC,EAAoD;EAAE,eAAO,IAAIC,QAAJ,CAAaJ,QAAQ,CAACK,IAAtB,EAA4B;EAACC,UAAAA,MAAM,EAAE,GAAT;EAAcC,UAAAA,UAAU,EAAE,IAA1B;EAAgCC,UAAAA,OAAO,EAAER,QAAQ,CAACQ;EAAlD,SAA5B,CAAP;EAAiG;;EAAC,aAAOR,QAAP;EAAkB;EAA5O,GAAD;EAApC,CAApC,CAAN,EAAmU,KAAnU,CAA7B;AACAL,uBAA6B,CAAC,KAAD,EAAQ,IAAIc,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBZ,EAAAA,OAAO,EAAE;EAA9B,CAAnC,CAAR,EAAgF,KAAhF,CAA7B;;"}

+ 3
- 0
state/history.ts Voir le fichier

@@ -23,6 +23,7 @@ class History<T extends Data> {
23 23
       this.pointer = this.maxLength - 1
24 24
     }
25 25
 
26
+    storage.savePage(data)
26 27
     storage.saveToLocalStorage(data)
27 28
   }
28 29
 
@@ -32,6 +33,7 @@ class History<T extends Data> {
32 33
     command.undo(data)
33 34
     if (this.disabled) return
34 35
     this.pointer--
36
+    storage.savePage(data)
35 37
     storage.saveToLocalStorage(data)
36 38
   }
37 39
 
@@ -41,6 +43,7 @@ class History<T extends Data> {
41 43
     command.redo(data, false)
42 44
     if (this.disabled) return
43 45
     this.pointer++
46
+    storage.savePage(data)
44 47
     storage.saveToLocalStorage(data)
45 48
   }
46 49
 

+ 1
- 1
state/state.ts Voir le fichier

@@ -1495,7 +1495,7 @@ const state = createState({
1495 1495
     },
1496 1496
 
1497 1497
     loadDocumentFromJson(data, payload: { restoredData: any }) {
1498
-      storage.load(data, payload.restoredData)
1498
+      storage.loadDocumentFromJson(data, payload.restoredData)
1499 1499
     },
1500 1500
 
1501 1501
     forceSave(data) {

+ 130
- 128
state/storage.ts Voir le fichier

@@ -7,7 +7,7 @@ import { v4 as uuid } from 'uuid'
7 7
 const CURRENT_VERSION = 'code_slate_0.0.5'
8 8
 const DOCUMENT_ID = '0001'
9 9
 
10
-function storageId(label: string, fileId: string, id: string) {
10
+function storageId(fileId: string, label: string, id: string) {
11 11
   return `${CURRENT_VERSION}_doc_${fileId}_${label}_${id}`
12 12
 }
13 13
 
@@ -17,17 +17,22 @@ class Storage {
17 17
   firstLoad(data: Data) {
18 18
     const lastOpened = localStorage.getItem(`${CURRENT_VERSION}_lastOpened`)
19 19
     this.loadDocumentFromLocalStorage(data, lastOpened || DOCUMENT_ID)
20
+    this.loadPage(data, data.currentPageId)
21
+    this.saveToLocalStorage(data, data.document.id)
22
+    localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
20 23
   }
21 24
 
22 25
   load(data: Data, restoredData: any) {
23
-    // Empty shapes in state for each page
26
+    console.log('loading restored data', restoredData)
27
+
28
+    // Before loading the state, save the pages / page states
24 29
     for (let key in restoredData.document.pages) {
25
-      restoredData.document.pages[key].shapes = {}
30
+      this.savePage(restoredData, restoredData.document.id, key)
26 31
     }
27 32
 
28
-    // Empty page states for each page
29
-    for (let key in restoredData.pageStates) {
30
-      restoredData.document.pages[key].shapes = {}
33
+    // Empty shapes in state for each page
34
+    for (let key in restoredData.document.pages) {
35
+      // restoredData.document.pages[key].shapes = {}
31 36
     }
32 37
 
33 38
     data.document = {} as TLDocument
@@ -42,36 +47,6 @@ class Storage {
42 47
       name: 'My Document',
43 48
       ...restoredData.document,
44 49
     }
45
-
46
-    localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
47
-
48
-    console.log('loaded', data.document)
49
-
50
-    // Let's also save the loaded document to local storage
51
-    this.saveToLocalStorage(data, data.document.id)
52
-
53
-    // Load current page
54
-    this.loadPage(data, data.currentPageId)
55
-  }
56
-
57
-  async loadDocumentFromFilesystem() {
58
-    const blob = await fa.fileOpen({
59
-      description: 'tldraw files',
60
-    })
61
-
62
-    const text = await getTextFromBlob(blob)
63
-
64
-    const restoredData = JSON.parse(text)
65
-
66
-    if (restoredData === null) {
67
-      console.warn('Could not load that data.')
68
-      return
69
-    }
70
-
71
-    // Save blob for future saves
72
-    this.previousSaveHandle = blob.handle
73
-
74
-    state.send('LOADED_FROM_FILE', { restoredData })
75 50
   }
76 51
 
77 52
   loadDocumentFromLocalStorage(data: Data, fileId = DOCUMENT_ID) {
@@ -79,7 +54,9 @@ class Storage {
79 54
     if (typeof localStorage === 'undefined') return
80 55
 
81 56
     // Load data from local storage
82
-    const savedData = localStorage.getItem(fileId)
57
+    const savedData = localStorage.getItem(
58
+      storageId(fileId, 'document', fileId)
59
+    )
83 60
 
84 61
     if (savedData === null) {
85 62
       // If we're going to use the default data, assign the
@@ -96,7 +73,8 @@ class Storage {
96 73
   getDataToSave = (data: Data) => {
97 74
     const dataToSave: any = { ...data }
98 75
 
99
-    for (let pageId in data.document) {
76
+    for (let pageId in data.document.pages) {
77
+      // Page
100 78
       const savedPage = localStorage.getItem(
101 79
         storageId(data.document.id, 'page', pageId)
102 80
       )
@@ -105,9 +83,9 @@ class Storage {
105 83
         const restored: Page = JSON.parse(savedPage)
106 84
         dataToSave.document.pages[pageId] = restored
107 85
       }
108
-    }
109 86
 
110
-    dataToSave.pageStates = {}
87
+      dataToSave.pageStates = {}
88
+    }
111 89
 
112 90
     return JSON.stringify(dataToSave, null, 2)
113 91
   }
@@ -116,61 +94,66 @@ class Storage {
116 94
     if (typeof window === 'undefined') return
117 95
     if (typeof localStorage === 'undefined') return
118 96
 
119
-    // Save current data to local storage
120
-    localStorage.setItem(id, this.getDataToSave(data))
97
+    const dataToSave = this.getDataToSave(data)
121 98
 
122
-    // Save current page too
123
-    this.savePage(data, id, data.currentPageId)
99
+    // Save current data to local storage
100
+    localStorage.setItem(storageId(id, 'document', id), dataToSave)
124 101
   }
125 102
 
126
-  saveAsToFileSystem = (data: Data) => {
127
-    // Create a new document id when saving to the file system
128
-
129
-    this.saveToFileSystem(data, uuid())
103
+  loadDocumentFromJson(data: Data, restoredData: any) {
104
+    this.load(data, restoredData)
105
+    this.loadPage(data, data.currentPageId)
106
+    this.saveToLocalStorage(data, data.document.id)
107
+    localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
130 108
   }
109
+  /* ---------------------- Pages --------------------- */
131 110
 
132
-  saveToFileSystem = (data: Data, id?: string) => {
133
-    // Save to local storage first
134
-    this.saveToLocalStorage(data, id)
111
+  savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
112
+    if (typeof window === 'undefined') return
113
+    if (typeof localStorage === 'undefined') return
135 114
 
136
-    const json = this.getDataToSave(data)
115
+    // Save page
116
+    const page = data.document.pages[pageId]
117
+    const json = JSON.stringify(page)
137 118
 
138
-    const blob = new Blob([json], {
139
-      type: 'application/vnd.tldraw+json',
140
-    })
119
+    localStorage.setItem(storageId(fileId, 'page', pageId), json)
141 120
 
142
-    fa.fileSave(
143
-      blob,
144
-      {
145
-        fileName: `${
146
-          id
147
-            ? data.document.name
148
-            : this.previousSaveHandle?.name || 'My Document'
149
-        }.tldr`,
150
-        description: 'tldraw file',
151
-        extensions: ['.tldr'],
121
+    // Save page state
122
+
123
+    let currentPageState = {
124
+      camera: {
125
+        point: [0, 0],
126
+        zoom: 1,
152 127
       },
153
-      id ? undefined : this.previousSaveHandle,
154
-      true
128
+      selectedIds: new Set([]),
129
+      ...data.pageStates[pageId],
130
+    }
131
+
132
+    const pageState = {
133
+      ...currentPageState,
134
+      selectedIds: setToArray(currentPageState.selectedIds),
135
+    }
136
+
137
+    localStorage.setItem(
138
+      storageId(fileId, 'pageState', pageId),
139
+      JSON.stringify(pageState)
155 140
     )
156
-      .then((handle) => {
157
-        this.previousSaveHandle = handle
158
-        state.send('SAVED_FILE_TO_FILE_SYSTEM')
159
-      })
160
-      .catch((e) => {
161
-        state.send('CANCELLED_SAVE', { reason: e.message })
162
-      })
163 141
   }
164 142
 
165
-  loadPageFromLocalStorage(fileId: string, pageId: string) {
166
-    let restored: Page
143
+  loadPage(data: Data, pageId = data.currentPageId) {
144
+    if (typeof window === 'undefined') return
145
+    if (typeof localStorage === 'undefined') return
146
+
147
+    const fileId = data.document.id
148
+
149
+    // Page
167 150
 
168 151
     const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
169 152
 
170 153
     if (savedPage !== null) {
171
-      restored = JSON.parse(savedPage)
154
+      data.document.pages[pageId] = JSON.parse(savedPage)
172 155
     } else {
173
-      restored = {
156
+      data.document.pages[pageId] = {
174 157
         id: pageId,
175 158
         type: 'page',
176 159
         childIndex: 0,
@@ -179,21 +162,18 @@ class Storage {
179 162
       }
180 163
     }
181 164
 
182
-    return restored
183
-  }
184
-
185
-  loadPageStateFromLocalStorage(fileId: string, pageId: string) {
186
-    let restored: PageState
165
+    // Page state
187 166
 
188 167
     const savedPageState = localStorage.getItem(
189 168
       storageId(fileId, 'pageState', pageId)
190 169
     )
191 170
 
192 171
     if (savedPageState !== null) {
193
-      restored = JSON.parse(savedPageState)
172
+      const restored: PageState = JSON.parse(savedPageState)
194 173
       restored.selectedIds = new Set(restored.selectedIds)
174
+      data.pageStates[pageId] = restored
195 175
     } else {
196
-      restored = {
176
+      data.pageStates[pageId] = {
197 177
         camera: {
198 178
           point: [0, 0],
199 179
           zoom: 1,
@@ -202,64 +182,86 @@ class Storage {
202 182
       }
203 183
     }
204 184
 
205
-    return restored
185
+    // Empty shapes in state for other pages
186
+
187
+    for (let key in data.document.pages) {
188
+      if (key === pageId) continue
189
+      data.document.pages[key].shapes = {}
190
+    }
191
+
192
+    // Update camera for the new page state
193
+    document.documentElement.style.setProperty(
194
+      '--camera-zoom',
195
+      data.pageStates[data.currentPageId].camera.zoom.toString()
196
+    )
206 197
   }
207 198
 
208
-  savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
209
-    if (typeof window === 'undefined') return
210
-    if (typeof localStorage === 'undefined') return
199
+  /* ------------------- File System ------------------ */
211 200
 
212
-    // Save page
213
-    const page = data.document.pages[pageId]
201
+  saveToFileSystem = (data: Data) => {
202
+    this.saveDataToFileSystem(data, data.document.id, false)
203
+  }
214 204
 
215
-    localStorage.setItem(
216
-      storageId(fileId, 'page', pageId),
217
-      JSON.stringify(page)
218
-    )
205
+  saveAsToFileSystem = (data: Data) => {
206
+    this.saveDataToFileSystem(data, uuid(), true)
207
+  }
219 208
 
220
-    // Save page state
209
+  saveDataToFileSystem = (data: Data, id: string, saveAs: boolean) => {
210
+    const json = this.getDataToSave(data)
221 211
 
222
-    let currentPageState = {
223
-      camera: {
224
-        point: [0, 0],
225
-        zoom: 1,
226
-      },
227
-      selectedIds: new Set([]),
228
-      ...data.pageStates[pageId],
229
-    }
212
+    this.saveToLocalStorage(data, id)
230 213
 
231
-    const pageState = {
232
-      ...currentPageState,
233
-      selectedIds: setToArray(currentPageState.selectedIds),
234
-    }
214
+    const blob = new Blob([json], {
215
+      type: 'application/vnd.tldraw+json',
216
+    })
235 217
 
236
-    localStorage.setItem(
237
-      storageId(fileId, 'pageState', pageId),
238
-      JSON.stringify(pageState)
218
+    fa.fileSave(
219
+      blob,
220
+      {
221
+        fileName: `${
222
+          saveAs
223
+            ? data.document.name
224
+            : this.previousSaveHandle?.name || 'My Document'
225
+        }.tldr`,
226
+        description: 'tldraw file',
227
+        extensions: ['.tldr'],
228
+      },
229
+      saveAs ? undefined : this.previousSaveHandle,
230
+      true
239 231
     )
232
+      .then((handle) => {
233
+        this.previousSaveHandle = handle
234
+        state.send('SAVED_FILE_TO_FILE_SYSTEM')
235
+      })
236
+      .catch((e) => {
237
+        state.send('CANCELLED_SAVE', { reason: e.message })
238
+      })
240 239
   }
241 240
 
242
-  loadPage(data: Data, pageId = data.currentPageId) {
243
-    if (typeof window === 'undefined') return
244
-    if (typeof localStorage === 'undefined') return
241
+  loadDocumentFromFilesystem() {
242
+    fa.fileOpen({
243
+      description: 'tldraw files',
244
+    })
245
+      .then((blob) =>
246
+        getTextFromBlob(blob).then((text) => {
247
+          const restoredData = JSON.parse(text)
245 248
 
246
-    const fileId = data.document.id
249
+          console.log('restoring data', restoredData)
247 250
 
248
-    data.document.pages[pageId] = this.loadPageFromLocalStorage(fileId, pageId)
251
+          if (restoredData === null) {
252
+            console.warn('Could not load that data.')
253
+            return
254
+          }
249 255
 
250
-    data.pageStates[pageId] = this.loadPageStateFromLocalStorage(fileId, pageId)
256
+          // Save blob for future saves
257
+          this.previousSaveHandle = blob.handle
251 258
 
252
-    // Empty shapes in state for other pages
253
-    for (let key in data.document.pages) {
254
-      if (key === pageId) continue
255
-      data.document.pages[key].shapes = {}
256
-    }
257
-
258
-    // Update camera for the new page state
259
-    document.documentElement.style.setProperty(
260
-      '--camera-zoom',
261
-      data.pageStates[data.currentPageId].camera.zoom.toString()
262
-    )
259
+          state.send('LOADED_FROM_FILE', { restoredData: { ...restoredData } })
260
+        })
261
+      )
262
+      .catch((e) => {
263
+        state.send('CANCELLED_SAVE', { reason: e.message })
264
+      })
263 265
   }
264 266
 }
265 267
 

+ 1
- 0
worker/index.js Voir le fichier

@@ -0,0 +1 @@
1
+self.__WB_DISABLE_DEV_LOGS = true

Chargement…
Annuler
Enregistrer