Fixes save and load states, fix group bg

This commit is contained in:
Steve Ruiz 2021-07-07 13:46:46 +01:00
parent ee48a7afb8
commit ad0ce1ba3a
8 changed files with 154 additions and 98 deletions

View file

@ -10,10 +10,10 @@ export default function useLoadOnMount(roomId?: string) {
fonts.load('12px Verveine Regular', 'Fonts are loaded!').then(() => { fonts.load('12px Verveine Regular', 'Fonts are loaded!').then(() => {
state.send('MOUNTED', { roomId }) state.send('MOUNTED', { roomId })
if (roomId !== undefined) { // if (roomId !== undefined) {
state.send('RT_LOADED_ROOM', { id: roomId }) // state.send('RT_LOADED_ROOM', { id: roomId })
coopState.send('JOINED_ROOM', { id: roomId }) // coopState.send('JOINED_ROOM', { id: roomId })
} // }
}) })
} else { } else {
setTimeout(() => state.send('MOUNTED'), 1000) setTimeout(() => state.send('MOUNTED'), 1000)

View file

@ -3,8 +3,11 @@ import Head from 'next/head'
import { AppProps } from 'next/app' import { AppProps } from 'next/app'
import { globalStyles } from 'styles' import { globalStyles } from 'styles'
import { Provider } from 'next-auth/client' import { Provider } from 'next-auth/client'
import { init } from 'utils/sentry'
import 'styles/globals.css' import 'styles/globals.css'
init()
function MyApp({ Component, pageProps }: AppProps): JSX.Element { function MyApp({ Component, pageProps }: AppProps): JSX.Element {
globalStyles() globalStyles()

View file

@ -17,15 +17,14 @@ export default function createPage(data: Data, goToPage = true): void {
data.document.pages[page.id] = page data.document.pages[page.id] = page
data.pageStates[page.id] = pageState data.pageStates[page.id] = pageState
data.currentPageId = page.id
storage.savePage(data, data.document.id, page.id)
storage.saveDocumentToLocalStorage(data)
if (goToPage) { if (goToPage) {
data.currentPageId = page.id data.currentPageId = page.id
} else { } else {
data.currentPageId = currentPageId data.currentPageId = currentPageId
} }
storage.savePage(data, data.document.id, page.id)
storage.saveDocumentToLocalStorage(data)
}, },
undo(data) { undo(data) {
const { page, currentPageId } = snapshot const { page, currentPageId } = snapshot

View file

@ -70,6 +70,8 @@ class CoopClient {
} }
disconnect(): CoopClient { disconnect(): CoopClient {
if (!this.room) return
this.room.unsubscribe('connection', this.handleConnectionEvent) this.room.unsubscribe('connection', this.handleConnectionEvent)
this.room.unsubscribe('my-presence', this.handleMyPresenceEvent) this.room.unsubscribe('my-presence', this.handleMyPresenceEvent)
this.room.unsubscribe('others', this.handleOthersEvent) this.room.unsubscribe('others', this.handleOthersEvent)
@ -79,6 +81,8 @@ class CoopClient {
} }
reconnect(): CoopClient { reconnect(): CoopClient {
if (!this.room) return
this.connect(this.roomId) this.connect(this.roomId)
return this return this
} }
@ -128,6 +132,8 @@ class CoopClient {
} }
clearCursor(): CoopClient { clearCursor(): CoopClient {
if (!this.room) return
this.room.updatePresence({ cursor: null }) this.room.updatePresence({ cursor: null })
return this return this
} }

View file

@ -34,6 +34,7 @@ const group = registerShapeUtils<GroupShape>({
width={size[0]} width={size[0]}
height={size[1]} height={size[1]}
data-shy={true} data-shy={true}
fill="none"
/> />
) )
}, },
@ -169,6 +170,7 @@ const group = registerShapeUtils<GroupShape>({
const StyledGroupShape = styled('rect', { const StyledGroupShape = styled('rect', {
zDash: 5, zDash: 5,
zStrokeWidth: 1, zStrokeWidth: 1,
stroke: '$text',
}) })
export default group export default group

View file

@ -160,7 +160,8 @@ const state = createState({
on: { on: {
MOUNTED: [ MOUNTED: [
'resetHistory', 'resetHistory',
{ unless: 'hasRoomId', do: 'restoredPreviousDocument' }, 'resetStorage',
'restoredPreviousDocument',
{ to: 'ready' }, { to: 'ready' },
], ],
}, },
@ -174,26 +175,9 @@ const state = createState({
}, },
on: { on: {
UNMOUNTED: { UNMOUNTED: {
do: [ do: ['saveDocumentState', 'resetDocumentState'],
'saveAppState',
'saveDocumentState',
'resetDocumentState',
'resetHistory',
],
to: 'loading', to: 'loading',
}, },
// Network-Related
RT_LOADED_ROOM: [
'clearRoom',
{ if: 'hasRoom', do: ['resetDocumentState', 'resetHistory'] },
],
// RT_UNLOADED_ROOM: ['clearRoom', 'resetDocumentState'],
// RT_DISCONNECTED_ROOM: ['clearRoom', 'resetDocumentState'],
// RT_CREATED_SHAPE: 'addRtShape',
// RT_CHANGED_STATUS: 'setRtStatus',
// RT_DELETED_SHAPE: 'deleteRtShape',
// RT_EDITED_SHAPE: 'editRtShape',
// Client
RESIZED_WINDOW: 'resetPageState', RESIZED_WINDOW: 'resetPageState',
RESET_DOCUMENT_STATE: [ RESET_DOCUMENT_STATE: [
'resetHistory', 'resetHistory',
@ -306,7 +290,7 @@ const state = createState({
}, },
SAVED: { SAVED: {
unlessAny: ['isInSession', 'isReadOnly'], unlessAny: ['isInSession', 'isReadOnly'],
do: 'forceSave', do: ['saveDocumentState', 'saveToFileSystem'],
}, },
LOADED_FROM_FILE: { LOADED_FROM_FILE: {
unless: 'isInSession', unless: 'isInSession',
@ -346,8 +330,10 @@ const state = createState({
RESET_CAMERA: 'resetCamera', RESET_CAMERA: 'resetCamera',
COPIED_TO_SVG: 'copyToSvg', COPIED_TO_SVG: 'copyToSvg',
LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem', LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem', SAVED_AS_TO_FILESYSTEM: ['saveDocumentState', 'saveAsToFileSystem'],
SAVED_TO_FILESYSTEM: { SAVED_TO_FILESYSTEM: [
'saveDocumentState',
{
unless: 'isReadOnly', unless: 'isReadOnly',
then: { then: {
if: 'isReadOnly', if: 'isReadOnly',
@ -355,6 +341,7 @@ const state = createState({
else: 'saveToFileSystem', else: 'saveToFileSystem',
}, },
}, },
],
}, },
initial: 'selecting', initial: 'selecting',
states: { states: {
@ -1281,30 +1268,38 @@ const state = createState({
clearRoom(data) { clearRoom(data) {
data.room = undefined data.room = undefined
}, },
resetDocumentState(data) { resetStorage() {
data.document.id = uniqueId() storage.reset()
},
resetDocumentState(data, payload: { roomId?: string }) {
// Save the current document and app state.
storage.savePage(data)
storage.savePageState(data)
storage.saveAppStateToLocalStorage(data)
storage.saveDocumentToLocalStorage(data)
// Cancel all current sessions, reset history, etc..
session.cancel(data) session.cancel(data)
inputs.reset() inputs.reset()
history.reset() history.reset()
storage.reset()
const newId = 'page1' // Populate a new app state.
const newDocumentId = payload?.roomId ? payload.roomId : uniqueId()
data.currentPageId = newId const newPageId = 'page1'
data.document.id = newDocumentId
data.pointedId = null data.pointedId = null
data.hoveredId = null data.hoveredId = null
data.editingId = null data.editingId = null
data.currentPageId = 'page1' data.currentPageId = newPageId
data.currentParentId = 'page1' data.currentParentId = newPageId
data.currentCodeFileId = 'file0' data.currentCodeFileId = 'file0'
data.codeControls = {} data.codeControls = {}
data.document.pages = { data.document.pages = {
[newId]: { [newPageId]: {
id: newId, id: newPageId,
name: 'Page 1', name: 'Page 1',
type: 'page', type: 'page',
shapes: {}, shapes: {},
@ -1313,8 +1308,8 @@ const state = createState({
} }
data.pageStates = { data.pageStates = {
[newId]: { [newPageId]: {
id: newId, id: newPageId,
selectedIds: new Set(), selectedIds: new Set(),
camera: { camera: {
point: [0, 0], point: [0, 0],
@ -1322,6 +1317,12 @@ const state = createState({
}, },
}, },
} }
// Save the new app state.
storage.savePage(data)
storage.savePageState(data)
storage.saveAppStateToLocalStorage(data)
storage.saveDocumentToLocalStorage(data)
}, },
resetPageState(data) { resetPageState(data) {
const pageState = data.pageStates[data.currentPageId] const pageState = data.pageStates[data.currentPageId]
@ -1344,6 +1345,7 @@ const state = createState({
/* --------------------- Shapes --------------------- */ /* --------------------- Shapes --------------------- */
resetShapes(data) { resetShapes(data) {
const page = tld.getPage(data) const page = tld.getPage(data)
Object.values(page.shapes).forEach((shape) => { Object.values(page.shapes).forEach((shape) => {
page.shapes[shape.id] = { ...shape } page.shapes[shape.id] = { ...shape }
}) })
@ -2052,8 +2054,8 @@ const state = createState({
/* ---------------------- Data ---------------------- */ /* ---------------------- Data ---------------------- */
restoredPreviousDocument(data) { restoredPreviousDocument(data, payload: { roomId?: string }) {
storage.firstLoad(data) storage.firstLoad(data, payload?.roomId)
}, },
saveToFileSystem(data) { saveToFileSystem(data) {
@ -2077,6 +2079,9 @@ const state = createState({
}, },
saveDocumentState(data) { saveDocumentState(data) {
storage.savePage(data)
storage.savePageState(data)
storage.saveAppStateToLocalStorage(data)
storage.saveDocumentToLocalStorage(data) storage.saveDocumentToLocalStorage(data)
}, },
@ -2084,14 +2089,6 @@ const state = createState({
storage.saveToFileSystem(data) storage.saveToFileSystem(data)
}, },
savePage(data) {
storage.savePage(data)
},
loadPage(data) {
storage.loadPage(data)
},
saveCode(data, payload: { code: string }) { saveCode(data, payload: { code: string }) {
data.document.code[data.currentCodeFileId].code = payload.code data.document.code[data.currentCodeFileId].code = payload.code
storage.saveDocumentToLocalStorage(data) storage.saveDocumentToLocalStorage(data)

View file

@ -13,14 +13,9 @@ function storageId(fileId: string, label: string, id?: string) {
class Storage { class Storage {
previousSaveHandle?: any // FileSystemHandle previousSaveHandle?: any // FileSystemHandle
constructor() { firstLoad(data: Data, roomId?: string) {
// this.loadPreviousHandle() // Still needs debugging const lastOpenedFileId =
} roomId || localStorage.getItem(`${CURRENT_VERSION}_lastOpened`)
firstLoad(data: Data) {
const lastOpenedFileId = localStorage.getItem(
`${CURRENT_VERSION}_lastOpened`
)
// 1. Load Document from Local Storage // 1. Load Document from Local Storage
// Using the "last opened file id" in local storage. // Using the "last opened file id" in local storage.
@ -30,9 +25,9 @@ class Storage {
storageId(lastOpenedFileId, 'document-state', lastOpenedFileId) storageId(lastOpenedFileId, 'document-state', lastOpenedFileId)
) )
if (savedState === null) { if (!savedState) {
// If no state with that document was found, create a fresh random id. // If no state with that document was found, create a fresh random id.
data.document.id = uniqueId() data.document.id = roomId ? roomId : uniqueId()
} else { } else {
// If we did find a state and document, load it into state. // If we did find a state and document, load it into state.
const restoredDocument: Data = JSON.parse(decompress(savedState)) const restoredDocument: Data = JSON.parse(decompress(savedState))
@ -42,11 +37,7 @@ class Storage {
} }
} }
try {
this.load(data) this.load(data)
} catch (error) {
console.error(error)
}
} }
saveAppStateToLocalStorage = (data: Data) => { saveAppStateToLocalStorage = (data: Data) => {
@ -63,6 +54,8 @@ class Storage {
storageId(data.document.id, 'document', data.document.id), storageId(data.document.id, 'document', data.document.id),
compress(JSON.stringify(document)) compress(JSON.stringify(document))
) )
localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
} }
getCompleteDocument = (data: Data) => { getCompleteDocument = (data: Data) => {
@ -138,7 +131,9 @@ class Storage {
if (savedPageState !== null) { if (savedPageState !== null) {
// If we've found a page state in local storage, set it into state. // If we've found a page state in local storage, set it into state.
data.pageStates[pageId] = JSON.parse(decompress(savedPageState)) data.pageStates[pageId] = JSON.parse(decompress(savedPageState))
data.pageStates[pageId].selectedIds = new Set([]) data.pageStates[pageId].selectedIds = new Set(
data.pageStates[pageId].selectedIds
)
} else { } else {
// Or else create a new one. // Or else create a new one.
data.pageStates[pageId] = { data.pageStates[pageId] = {
@ -154,15 +149,31 @@ class Storage {
// 3. Restore the last page state // 3. Restore the last page state
// Using the "last page state" in local storage. // Using the "last page state" in local storage.
try {
const savedPageState = localStorage.getItem( const savedPageState = localStorage.getItem(
storageId(data.document.id, 'lastPageState', data.document.id) storageId(data.document.id, 'lastPageState', data.document.id)
) )
if (savedPageState !== null) {
const pageState = JSON.parse(decompress(savedPageState)) const pageState = JSON.parse(decompress(savedPageState))
if (!data.document.pages[pageState.id]) {
throw new Error('Page state id not in document')
}
pageState.selectedIds = new Set([]) pageState.selectedIds = new Set([])
data.pageStates[pageState.id] = pageState data.pageStates[pageState.id] = pageState
data.currentPageId = pageState.id data.currentPageId = pageState.id
} catch (e) {
console.error('Could not restore page state:', e.message)
data.pageStates[data.currentPageId] = {
id: data.currentPageId,
selectedIds: new Set([]),
camera: {
point: [0, 0],
zoom: 1,
},
}
} }
// 4. Save the current app state / document // 4. Save the current app state / document
@ -203,6 +214,9 @@ class Storage {
} }
}) })
// Load the current page
this.loadPage(data, data.document.id, data.currentPageId)
// Update camera for the new page state // Update camera for the new page state
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
'--camera-zoom', '--camera-zoom',
@ -247,13 +261,13 @@ class Storage {
data.currentPageId = pageId data.currentPageId = pageId
// Get saved page from local storage try {
// If we have a page in local storage, move it into state
const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId)) const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
if (savedPage !== null) {
// If we have a page, move it into state
data.document.pages[pageId] = JSON.parse(decompress(savedPage)) data.document.pages[pageId] = JSON.parse(decompress(savedPage))
} else { } catch (e) {
console.warn('Could not load a page with the id', pageId)
// If we don't have a page, create a new page // If we don't have a page, create a new page
data.document.pages[pageId] = { data.document.pages[pageId] = {
id: pageId, id: pageId,
@ -309,23 +323,20 @@ class Storage {
/* ------------------- File System ------------------ */ /* ------------------- File System ------------------ */
reset = () => {
this.previousSaveHandle = undefined
}
saveToFileSystem = (data: Data) => { saveToFileSystem = (data: Data) => {
this.saveAppStateToLocalStorage(data) this.saveDataToFileSystem(data, false)
this.saveDocumentToLocalStorage(data)
this.saveDataToFileSystem(data, data.document.id, false)
} }
saveAsToFileSystem = (data: Data) => { saveAsToFileSystem = (data: Data) => {
this.saveAppStateToLocalStorage(data) this.saveDataToFileSystem(data, true)
this.saveDocumentToLocalStorage(data)
this.saveDataToFileSystem(data, uniqueId(), true)
} }
saveDataToFileSystem = async ( saveDataToFileSystem = async (data: Data, saveAs: boolean) => {
data: Data, const isSavingAs = saveAs || !this.previousSaveHandle
fileId: string,
saveAs: boolean
) => {
const document = this.getCompleteDocument(data) const document = this.getCompleteDocument(data)
// Then save to file system // Then save to file system
@ -351,12 +362,14 @@ class Storage {
blob, blob,
{ {
fileName: `${ fileName: `${
saveAs ? documentName : this.previousSaveHandle?.name || 'My Document' isSavingAs
? documentName
: this.previousSaveHandle?.name || 'My Document'
}.tldr`, }.tldr`,
description: 'tldraw file', description: 'tldraw file',
extensions: ['.tldr'], extensions: ['.tldr'],
}, },
saveAs ? undefined : this.previousSaveHandle, isSavingAs ? undefined : this.previousSaveHandle,
true true
) )
.then((handle) => { .then((handle) => {

36
utils/sentry.ts Normal file
View file

@ -0,0 +1,36 @@
import * as Sentry from '@sentry/node'
import { RewriteFrames } from '@sentry/integrations'
export function init(): void {
if (!process.env.NEXT_PUBLIC_SENTRY_DSN) return
const integrations = []
if (
process.env.NEXT_IS_SERVER === 'true' &&
process.env.NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR
) {
// For Node.js, rewrite Error.stack to use relative paths, so that source
// maps starting with ~/_next map to files in Error.stack with path
// app:///_next
integrations.push(
new RewriteFrames({
iteratee: (frame) => {
frame.filename = frame.filename.replace(
process.env.NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR,
'app:///'
)
frame.filename = frame.filename.replace('.next', '_next')
return frame
},
})
)
}
Sentry.init({
enabled: process.env.NODE_ENV === 'production',
integrations,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_COMMIT_SHA,
})
}