Fixes deleting pages, adds local file saving / loading
This commit is contained in:
parent
e2b53b381b
commit
61f56b984e
18 changed files with 280 additions and 117 deletions
|
@ -25,8 +25,6 @@ export default function Handles() {
|
|||
|
||||
const center = getShapeUtils(shape).getCenter(shape)
|
||||
|
||||
console.log(shape)
|
||||
|
||||
return (
|
||||
<g transform={`rotate(${shape.rotation * (180 / Math.PI)},${center})`}>
|
||||
{Object.values(shape.handles).map((handle) => (
|
||||
|
|
|
@ -134,7 +134,11 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 's': {
|
||||
if (metaKey(e)) {
|
||||
state.send('SAVED', getKeyboardEventInfo(e))
|
||||
if (e.shiftKey) {
|
||||
state.send('SAVED_AS_TO_FILESYSTEM', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send('SAVED', getKeyboardEventInfo(e))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -187,6 +191,9 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 'l': {
|
||||
if (metaKey(e)) {
|
||||
if (e.shiftKey) {
|
||||
state.send('LOADED_FROM_FILE_STSTEM', getKeyboardEventInfo(e))
|
||||
}
|
||||
} else {
|
||||
state.send('SELECTED_LINE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@radix-ui/react-tooltip": "^0.0.18",
|
||||
"@state-designer/react": "^1.7.3",
|
||||
"@stitches/react": "^0.1.9",
|
||||
"browser-fs-access": "^0.17.3",
|
||||
"framer-motion": "^4.1.16",
|
||||
"ismobilejs": "^1.1.1",
|
||||
"next": "10.2.0",
|
||||
|
|
|
@ -13,9 +13,9 @@ export default function changePage(data: Data, pageId: string) {
|
|||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data) {
|
||||
storage.savePage(data, data.currentPageId)
|
||||
storage.savePage(data, data.document.id, prevPageId)
|
||||
data.currentPageId = pageId
|
||||
storage.loadPage(data, data.currentPageId)
|
||||
storage.loadPage(data)
|
||||
},
|
||||
undo(data) {
|
||||
data.currentPageId = prevPageId
|
||||
|
|
|
@ -19,7 +19,7 @@ export default function createPage(data: Data) {
|
|||
data.document.pages[page.id] = page
|
||||
data.pageStates[page.id] = pageState
|
||||
data.currentPageId = page.id
|
||||
storage.savePage(data, page.id)
|
||||
storage.savePage(data, data.document.id, page.id)
|
||||
},
|
||||
undo(data) {
|
||||
const { page, currentPageId } = snapshot
|
||||
|
|
|
@ -5,6 +5,7 @@ import { current } from 'immer'
|
|||
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import * as vec from 'utils/vec'
|
||||
import storage from 'state/storage'
|
||||
|
||||
export default function changePage(data: Data, pageId: string) {
|
||||
const snapshot = getSnapshot(data, pageId)
|
||||
|
@ -18,11 +19,13 @@ export default function changePage(data: Data, pageId: string) {
|
|||
data.currentPageId = snapshot.nextPageId
|
||||
delete data.document.pages[pageId]
|
||||
delete data.pageStates[pageId]
|
||||
storage.loadPage(data, snapshot.nextPageId)
|
||||
},
|
||||
undo(data) {
|
||||
data.currentPageId = snapshot.currentPageId
|
||||
data.document.pages[pageId] = snapshot.page
|
||||
data.pageStates[pageId] = snapshot.pageState
|
||||
storage.loadPage(data, snapshot.currentPageId)
|
||||
},
|
||||
})
|
||||
)
|
||||
|
@ -37,16 +40,14 @@ function getSnapshot(data: Data, pageId: string) {
|
|||
|
||||
const isCurrent = currentPageId === pageId
|
||||
|
||||
const nextIndex = isCurrent
|
||||
? page.childIndex === 0
|
||||
? 1
|
||||
: page.childIndex - 1
|
||||
: document.pages[currentPageId].childIndex
|
||||
// const nextIndex = isCurrent
|
||||
// ? page.childIndex === 0
|
||||
// ? 1
|
||||
// : page.childIndex - 1
|
||||
// : document.pages[currentPageId].childIndex
|
||||
|
||||
const nextPageId = isCurrent
|
||||
? Object.values(document.pages).find(
|
||||
(page) => page.childIndex === nextIndex
|
||||
)!.id
|
||||
? Object.values(document.pages).filter((page) => page.id !== pageId)[0]?.id // TODO: should be at nextIndex
|
||||
: cData.currentPageId
|
||||
|
||||
return {
|
||||
|
|
|
@ -10,7 +10,7 @@ export default function drawCommand(data: Data, id: string) {
|
|||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: 'set_points',
|
||||
name: 'create_draw_shape',
|
||||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data, initial) {
|
||||
|
|
|
@ -71,7 +71,7 @@ export default function nudgeCommand(data: Data, newPageId: string) {
|
|||
getPageState(data, fromPageId).selectedIds.clear()
|
||||
|
||||
// Save the "from" page
|
||||
storage.savePage(data, fromPageId)
|
||||
storage.savePage(data, data.document.id, fromPageId)
|
||||
|
||||
// Load the "to" page
|
||||
storage.loadPage(data, toPageId)
|
||||
|
@ -124,7 +124,7 @@ export default function nudgeCommand(data: Data, newPageId: string) {
|
|||
|
||||
getPageState(data, fromPageId).selectedIds.clear()
|
||||
|
||||
storage.savePage(data, fromPageId)
|
||||
storage.savePage(data, data.document.id, fromPageId)
|
||||
|
||||
storage.loadPage(data, toPageId)
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import { Data, ShapeType } from 'types'
|
|||
import shapeUtils from 'lib/shape-utils'
|
||||
|
||||
export const defaultDocument: Data['document'] = {
|
||||
id: '0001',
|
||||
name: 'My Document',
|
||||
pages: {
|
||||
page1: {
|
||||
id: 'page1',
|
||||
|
|
|
@ -23,7 +23,7 @@ class History<T extends Data> {
|
|||
this.pointer = this.maxLength - 1
|
||||
}
|
||||
|
||||
storage.save(data)
|
||||
storage.saveToLocalStorage(data)
|
||||
}
|
||||
|
||||
undo = (data: T) => {
|
||||
|
@ -32,7 +32,7 @@ class History<T extends Data> {
|
|||
command.undo(data)
|
||||
if (this.disabled) return
|
||||
this.pointer--
|
||||
storage.save(data)
|
||||
storage.saveToLocalStorage(data)
|
||||
}
|
||||
|
||||
redo = (data: T) => {
|
||||
|
@ -41,7 +41,7 @@ class History<T extends Data> {
|
|||
command.redo(data, false)
|
||||
if (this.disabled) return
|
||||
this.pointer++
|
||||
storage.save(data)
|
||||
storage.saveToLocalStorage(data)
|
||||
}
|
||||
|
||||
disable = () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { PointerInfo } from 'types'
|
||||
import { isDarwin } from 'utils/utils'
|
||||
import { isDarwin, getPoint } from 'utils/utils'
|
||||
|
||||
const DOUBLE_CLICK_DURATION = 300
|
||||
|
||||
|
@ -17,8 +17,8 @@ class Inputs {
|
|||
const info = {
|
||||
target,
|
||||
pointerId: touch.identifier,
|
||||
origin: [touch.clientX, touch.clientY],
|
||||
point: [touch.clientX, touch.clientY],
|
||||
origin: getPoint(touch),
|
||||
point: getPoint(touch),
|
||||
pressure: 0.5,
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -42,7 +42,7 @@ class Inputs {
|
|||
const info = {
|
||||
...prev,
|
||||
pointerId: touch.identifier,
|
||||
point: [touch.clientX, touch.clientY],
|
||||
point: getPoint(touch),
|
||||
pressure: 0.5,
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -63,8 +63,8 @@ class Inputs {
|
|||
const info = {
|
||||
target,
|
||||
pointerId: e.pointerId,
|
||||
origin: [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
origin: getPoint(e),
|
||||
point: getPoint(e),
|
||||
pressure: e.pressure || 0.5,
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -84,8 +84,8 @@ class Inputs {
|
|||
const info = {
|
||||
target,
|
||||
pointerId: e.pointerId,
|
||||
origin: [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
origin: getPoint(e),
|
||||
point: getPoint(e),
|
||||
pressure: e.pressure || 0.5,
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -104,7 +104,7 @@ class Inputs {
|
|||
const info = {
|
||||
...prev,
|
||||
pointerId: e.pointerId,
|
||||
point: [e.clientX, e.clientY],
|
||||
point: getPoint(e),
|
||||
pressure: e.pressure || 0.5,
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -126,8 +126,8 @@ class Inputs {
|
|||
|
||||
const info = {
|
||||
...prev,
|
||||
origin: prev?.origin || [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
origin: prev?.origin || getPoint(e),
|
||||
point: getPoint(e),
|
||||
pressure: e.pressure || 0.5,
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -144,7 +144,7 @@ class Inputs {
|
|||
|
||||
wheel(e: WheelEvent) {
|
||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||
return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
|
||||
return { point: getPoint(e), shiftKey, ctrlKey, metaKey, altKey }
|
||||
}
|
||||
|
||||
canAccept(pointerId: PointerEvent['pointerId']) {
|
||||
|
|
|
@ -72,7 +72,7 @@ export default class BrushSession extends BaseSession {
|
|||
|
||||
point = vec.med(this.previous, point)
|
||||
|
||||
const next = [...vec.sub(point, this.origin), pressure]
|
||||
const next = vec.round([...vec.sub(point, this.origin), pressure])
|
||||
|
||||
// Don't add duplicate points
|
||||
if (vec.isEqual(this.last, next)) return
|
||||
|
|
|
@ -172,15 +172,19 @@ const state = createState({
|
|||
CHANGED_PAGE: 'changePage',
|
||||
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
|
||||
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
|
||||
LOADED_FROM_FILE: 'loadDocumentFromJson',
|
||||
},
|
||||
initial: 'selecting',
|
||||
states: {
|
||||
selecting: {
|
||||
onEnter: 'setActiveToolSelect',
|
||||
on: {
|
||||
SAVED: 'forceSave',
|
||||
UNDO: 'undo',
|
||||
REDO: 'redo',
|
||||
SAVED: 'forceSave',
|
||||
LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
|
||||
SAVED_TO_FILESYSTEM: 'saveToFileSystem',
|
||||
SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
|
||||
SAVED_CODE: 'saveCode',
|
||||
DELETED: 'deleteSelection',
|
||||
INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
|
||||
|
@ -433,8 +437,12 @@ const state = createState({
|
|||
do: 'createShape',
|
||||
to: 'draw.editing',
|
||||
},
|
||||
UNDO: { do: 'undo' },
|
||||
REDO: { do: 'redo' },
|
||||
UNDO: 'undo',
|
||||
REDO: 'redo',
|
||||
SAVED: 'forceSave',
|
||||
LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
|
||||
SAVED_TO_FILESYSTEM: 'saveToFileSystem',
|
||||
SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
|
||||
},
|
||||
},
|
||||
editing: {
|
||||
|
@ -1474,25 +1482,41 @@ const state = createState({
|
|||
|
||||
/* ---------------------- Data ---------------------- */
|
||||
|
||||
saveToFileSystem(data) {
|
||||
storage.saveToFileSystem(data)
|
||||
},
|
||||
|
||||
saveAsToFileSystem(data) {
|
||||
storage.saveAsToFileSystem(data)
|
||||
},
|
||||
|
||||
loadFromFileSystem() {
|
||||
storage.loadDocumentFromFilesystem()
|
||||
},
|
||||
|
||||
loadDocumentFromJson(data, payload: { restoredData: any }) {
|
||||
storage.load(data, payload.restoredData)
|
||||
},
|
||||
|
||||
forceSave(data) {
|
||||
storage.save(data)
|
||||
storage.saveToLocalStorage(data)
|
||||
},
|
||||
|
||||
savePage(data) {
|
||||
storage.savePage(data, data.currentPageId)
|
||||
storage.savePage(data)
|
||||
},
|
||||
|
||||
loadPage(data) {
|
||||
storage.loadPage(data, data.currentPageId)
|
||||
storage.loadPage(data)
|
||||
},
|
||||
|
||||
saveCode(data, payload: { code: string }) {
|
||||
data.document.code[data.currentCodeFileId].code = payload.code
|
||||
storage.save(data)
|
||||
storage.saveToLocalStorage(data)
|
||||
},
|
||||
|
||||
restoreSavedData(data) {
|
||||
storage.load(data)
|
||||
storage.loadDocumentFromLocalStorage(data)
|
||||
},
|
||||
|
||||
clearBoundsRotation(data) {
|
||||
|
|
248
state/storage.ts
248
state/storage.ts
|
@ -1,67 +1,190 @@
|
|||
import { Data, Page, PageState } from 'types'
|
||||
import * as fa from 'browser-fs-access'
|
||||
import { Data, Page, PageState, TLDocument } from 'types'
|
||||
import { setToArray } from 'utils/utils'
|
||||
import state from './state'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
const CURRENT_VERSION = 'code_slate_0.0.4'
|
||||
const CURRENT_VERSION = 'code_slate_0.0.5'
|
||||
const DOCUMENT_ID = '0001'
|
||||
|
||||
function storageId(label: string, id: string) {
|
||||
return `${CURRENT_VERSION}_doc_${DOCUMENT_ID}_${label}_${id}`
|
||||
function storageId(label: string, fileId: string, id: string) {
|
||||
return `${CURRENT_VERSION}_doc_${fileId}_${label}_${id}`
|
||||
}
|
||||
|
||||
class Storage {
|
||||
// Saving
|
||||
load(data: Data, id = CURRENT_VERSION) {
|
||||
load(data: Data, restoredData: any) {
|
||||
// Empty shapes in state for each page
|
||||
for (let key in restoredData.document.pages) {
|
||||
restoredData.document.pages[key].shapes = {}
|
||||
}
|
||||
|
||||
// Empty page states for each page
|
||||
for (let key in restoredData.pageStates) {
|
||||
restoredData.document.pages[key].shapes = {}
|
||||
}
|
||||
|
||||
data.document = {} as TLDocument
|
||||
data.pageStates = {}
|
||||
|
||||
// Merge restored data into state
|
||||
Object.assign(data, restoredData)
|
||||
|
||||
// Minor migrtation: add id and name to document
|
||||
data.document = {
|
||||
id: 'document0',
|
||||
name: 'My Document',
|
||||
...restoredData.document,
|
||||
}
|
||||
|
||||
// Load current page
|
||||
this.loadPage(data, data.currentPageId)
|
||||
}
|
||||
|
||||
async loadDocumentFromFilesystem() {
|
||||
const blob = await fa.fileOpen({
|
||||
description: 'tldraw files',
|
||||
})
|
||||
|
||||
const text = await getTextFromBlob(blob)
|
||||
|
||||
const restoredData = JSON.parse(text)
|
||||
|
||||
if (restoredData === null) {
|
||||
console.warn('Could not load that data.')
|
||||
return
|
||||
}
|
||||
|
||||
state.send('LOADED_FROM_FILE', { restoredData })
|
||||
}
|
||||
|
||||
loadDocumentFromLocalStorage(data: Data, fileId = DOCUMENT_ID) {
|
||||
if (typeof window === 'undefined') return
|
||||
if (typeof localStorage === 'undefined') return
|
||||
|
||||
// Load data from local storage
|
||||
const savedData = localStorage.getItem(id)
|
||||
const savedData = localStorage.getItem(fileId)
|
||||
|
||||
if (savedData !== null) {
|
||||
const restoredData = JSON.parse(savedData)
|
||||
if (savedData === null) return false
|
||||
|
||||
// Empty shapes in state for each page
|
||||
for (let key in restoredData.document.pages) {
|
||||
restoredData.document.pages[key].shapes = {}
|
||||
}
|
||||
const restoredData = JSON.parse(savedData)
|
||||
|
||||
// Empty page states for each page
|
||||
for (let key in restoredData.pageStates) {
|
||||
restoredData.document.pages[key].shapes = {}
|
||||
}
|
||||
|
||||
// Merge restored data into state
|
||||
Object.assign(data, restoredData)
|
||||
|
||||
// Load current page
|
||||
this.loadPage(data, data.currentPageId)
|
||||
}
|
||||
this.load(data, restoredData)
|
||||
}
|
||||
|
||||
save = (data: Data, id = CURRENT_VERSION) => {
|
||||
getDataToSave = (data: Data) => {
|
||||
const dataToSave: any = { ...data }
|
||||
|
||||
for (let pageId in data.document) {
|
||||
const savedPage = localStorage.getItem(
|
||||
storageId(data.document.id, 'page', pageId)
|
||||
)
|
||||
|
||||
if (savedPage !== null) {
|
||||
const restored: Page = JSON.parse(savedPage)
|
||||
dataToSave.document.pages[pageId] = restored
|
||||
}
|
||||
}
|
||||
|
||||
dataToSave.pageStates = {}
|
||||
|
||||
return JSON.stringify(dataToSave, null, 2)
|
||||
}
|
||||
|
||||
saveToLocalStorage = (data: Data, id = data.document.id) => {
|
||||
if (typeof window === 'undefined') return
|
||||
if (typeof localStorage === 'undefined') return
|
||||
|
||||
const dataToSave: any = { ...data }
|
||||
|
||||
// Don't save pageStates
|
||||
dataToSave.pageStates = {}
|
||||
|
||||
// Save current data to local storage
|
||||
localStorage.setItem(id, JSON.stringify(dataToSave))
|
||||
localStorage.setItem(id, this.getDataToSave(data))
|
||||
|
||||
// Save current page
|
||||
this.savePage(data, data.currentPageId)
|
||||
// Save current page too
|
||||
this.savePage(data, id, data.currentPageId)
|
||||
|
||||
state.send('SAVED_FILE_TO_LOCAL_STORAGE')
|
||||
}
|
||||
|
||||
savePage(data: Data, pageId: string) {
|
||||
saveAsToFileSystem = (data: Data) => {
|
||||
// Create a new document id when saving to the file system
|
||||
this.saveToFileSystem(data, uuid())
|
||||
}
|
||||
|
||||
saveToFileSystem = (data: Data, id = data.document.id) => {
|
||||
// Save to local storage first
|
||||
this.saveToLocalStorage(data, id)
|
||||
|
||||
const json = this.getDataToSave(data)
|
||||
|
||||
const blob = new Blob([json], {
|
||||
type: 'application/vnd.tldraw+json',
|
||||
})
|
||||
|
||||
fa.fileSave(blob, {
|
||||
fileName: `${data.document.name}.tldr`,
|
||||
description: 'tldraw file',
|
||||
extensions: ['.tldr'],
|
||||
})
|
||||
.then(() => {
|
||||
state.send('SAVED_FILE_TO_FILE_SYSTEM')
|
||||
})
|
||||
.catch((e) => {
|
||||
state.send('CANCELLED_SAVE', { reason: e.message })
|
||||
})
|
||||
}
|
||||
|
||||
loadPageFromLocalStorage(fileId: string, pageId: string) {
|
||||
let restored: Page
|
||||
|
||||
const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
|
||||
|
||||
if (savedPage !== null) {
|
||||
restored = JSON.parse(savedPage)
|
||||
} else {
|
||||
restored = {
|
||||
id: pageId,
|
||||
type: 'page',
|
||||
childIndex: 0,
|
||||
name: 'Page',
|
||||
shapes: {},
|
||||
}
|
||||
}
|
||||
|
||||
return restored
|
||||
}
|
||||
|
||||
loadPageStateFromLocalStorage(fileId: string, pageId: string) {
|
||||
let restored: PageState
|
||||
|
||||
const savedPageState = localStorage.getItem(
|
||||
storageId(fileId, 'pageState', pageId)
|
||||
)
|
||||
|
||||
if (savedPageState !== null) {
|
||||
restored = JSON.parse(savedPageState)
|
||||
restored.selectedIds = new Set(restored.selectedIds)
|
||||
} else {
|
||||
restored = {
|
||||
camera: {
|
||||
point: [0, 0],
|
||||
zoom: 1,
|
||||
},
|
||||
selectedIds: new Set([]),
|
||||
}
|
||||
}
|
||||
|
||||
return restored
|
||||
}
|
||||
|
||||
savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
|
||||
if (typeof window === 'undefined') return
|
||||
if (typeof localStorage === 'undefined') return
|
||||
|
||||
// Save page
|
||||
const page = data.document.pages[pageId]
|
||||
|
||||
localStorage.setItem(storageId('page', pageId), JSON.stringify(page))
|
||||
localStorage.setItem(
|
||||
storageId(fileId, 'page', pageId),
|
||||
JSON.stringify(page)
|
||||
)
|
||||
|
||||
// Save page state
|
||||
|
||||
|
@ -80,39 +203,20 @@ class Storage {
|
|||
}
|
||||
|
||||
localStorage.setItem(
|
||||
storageId('pageState', pageId),
|
||||
storageId(fileId, 'pageState', pageId),
|
||||
JSON.stringify(pageState)
|
||||
)
|
||||
}
|
||||
|
||||
loadPage(data: Data, pageId: string) {
|
||||
loadPage(data: Data, pageId = data.currentPageId) {
|
||||
if (typeof window === 'undefined') return
|
||||
if (typeof localStorage === 'undefined') return
|
||||
|
||||
// Load page and merge into state
|
||||
const savedPage = localStorage.getItem(storageId('page', pageId))
|
||||
const fileId = data.document.id
|
||||
|
||||
if (savedPage !== null) {
|
||||
const restored: Page = JSON.parse(savedPage)
|
||||
data.document.pages[pageId] = restored
|
||||
}
|
||||
data.document.pages[pageId] = this.loadPageFromLocalStorage(fileId, pageId)
|
||||
|
||||
// Load page state and merge into state
|
||||
const savedPageState = localStorage.getItem(storageId('pageState', pageId))
|
||||
|
||||
if (savedPageState !== null) {
|
||||
const restored: PageState = JSON.parse(savedPageState)
|
||||
restored.selectedIds = new Set(restored.selectedIds)
|
||||
data.pageStates[pageId] = restored
|
||||
} else {
|
||||
data.pageStates[pageId] = {
|
||||
camera: {
|
||||
point: [0, 0],
|
||||
zoom: 1,
|
||||
},
|
||||
selectedIds: new Set([]),
|
||||
}
|
||||
}
|
||||
data.pageStates[pageId] = this.loadPageStateFromLocalStorage(fileId, pageId)
|
||||
|
||||
// Empty shapes in state for other pages
|
||||
for (let key in data.document.pages) {
|
||||
|
@ -120,13 +224,7 @@ class Storage {
|
|||
data.document.pages[key].shapes = {}
|
||||
}
|
||||
|
||||
// Empty page states for other pages
|
||||
for (let key in data.pageStates) {
|
||||
if (key === pageId) continue
|
||||
data.document.pages[key].shapes = {}
|
||||
}
|
||||
|
||||
// Update camera
|
||||
// Update camera for the new page state
|
||||
document.documentElement.style.setProperty(
|
||||
'--camera-zoom',
|
||||
data.pageStates[data.currentPageId].camera.zoom.toString()
|
||||
|
@ -137,3 +235,21 @@ class Storage {
|
|||
const storage = new Storage()
|
||||
|
||||
export default storage
|
||||
|
||||
async function getTextFromBlob(blob: Blob): Promise<string> {
|
||||
// Return blob as text if a text file.
|
||||
if ('text' in Blob) return blob.text()
|
||||
|
||||
// Return blob as text if a text file.
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onloadend = () => {
|
||||
if (reader.readyState === FileReader.DONE) {
|
||||
resolve(reader.result as string)
|
||||
}
|
||||
}
|
||||
|
||||
reader.readAsText(blob, 'utf8')
|
||||
})
|
||||
}
|
||||
|
|
12
types.ts
12
types.ts
|
@ -28,10 +28,7 @@ export interface Data {
|
|||
currentParentId: string
|
||||
currentCodeFileId: string
|
||||
codeControls: Record<string, CodeControl>
|
||||
document: {
|
||||
pages: Record<string, Page>
|
||||
code: Record<string, CodeFile>
|
||||
}
|
||||
document: TLDocument
|
||||
pageStates: Record<string, PageState>
|
||||
}
|
||||
|
||||
|
@ -39,6 +36,13 @@ export interface Data {
|
|||
/* Document */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
export interface TLDocument {
|
||||
id: string
|
||||
name: string
|
||||
pages: Record<string, Page>
|
||||
code: Record<string, CodeFile>
|
||||
}
|
||||
|
||||
export interface Page {
|
||||
id: string
|
||||
type: 'page'
|
||||
|
|
|
@ -1758,3 +1758,13 @@ export function getTopParentId(data: Data, id: string): string {
|
|||
export function uniqueArray<T extends string | number | Symbol>(...items: T[]) {
|
||||
return Array.from(new Set(items).values())
|
||||
}
|
||||
|
||||
export function getPoint(
|
||||
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent
|
||||
) {
|
||||
return [
|
||||
Number(e.clientX.toPrecision(4)),
|
||||
Number(e.clientY.toPrecision(4)),
|
||||
'pressure' in e ? e.pressure || 0.5 : 0.5,
|
||||
]
|
||||
}
|
||||
|
|
|
@ -339,13 +339,8 @@ export function clockwise(p1: number[], pc: number[], p2: number[]) {
|
|||
return isLeft(p1, pc, p2) > 0
|
||||
}
|
||||
|
||||
const rounds = [1, 10, 100, 1000]
|
||||
|
||||
export function round(a: number[], d = 2) {
|
||||
return [
|
||||
Math.round(a[0] * rounds[d]) / rounds[d],
|
||||
Math.round(a[1] * rounds[d]) / rounds[d],
|
||||
]
|
||||
return a.map((v) => +v.toFixed(d))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2567,6 +2567,11 @@ brorand@^1.0.1, brorand@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
||||
|
||||
browser-fs-access@^0.17.3:
|
||||
version "0.17.3"
|
||||
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.17.3.tgz#f91447b0b74bb8d224a8b01a7d18bd090468ef1d"
|
||||
integrity sha512-zrEWlsaQf3RKAeLjA6veRzTtf8ge3dFfun50RI74kSWKQKnJaioPc4I8oPzO6rDNiR0CJUI+oORuPOFMUkr0+g==
|
||||
|
||||
browser-process-hrtime@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||
|
|
Loading…
Reference in a new issue