Simplified export bookkeeping

This commit is contained in:
Aria Minaei 2021-08-09 21:50:12 +02:00
parent 832542c1c5
commit f08d9bf9c1
9 changed files with 58 additions and 68 deletions

View file

@ -57,6 +57,7 @@ export default class Project {
historic: config.state ?? { historic: config.state ?? {
sheetsById: {}, sheetsById: {},
definitionVersion: globals.currentProjectStateDefinitionVersion, definitionVersion: globals.currentProjectStateDefinitionVersion,
revisionHistory: [],
}, },
ephemeral: { ephemeral: {
loadingState: { loadingState: {

View file

@ -7,7 +7,7 @@ import globals from '@theatre/shared/globals'
/** /**
* @todo this could be turned into a simple derivation, like: * @todo this could be turned into a simple derivation, like:
* editor.isReady: IDerivation<{isReady: true} | {isReady: false, readon: 'conflictBetweenDiskStateAndBrowserState'}> * editor.isReady: IDerivation<{isReady: true} | {isReady: false, reason: 'conflictBetweenDiskStateAndBrowserState'}>
*/ */
export default async function initialiseProjectState( export default async function initialiseProjectState(
studio: Studio, studio: Studio,
@ -42,6 +42,7 @@ export default async function initialiseProjectState(
drafts.historic.coreByProject[projectId] = { drafts.historic.coreByProject[projectId] = {
sheetsById: {}, sheetsById: {},
definitionVersion: globals.currentProjectStateDefinitionVersion, definitionVersion: globals.currentProjectStateDefinitionVersion,
revisionHistory: [],
} }
} }
@ -81,9 +82,8 @@ export default async function initialiseProjectState(
useBrowserState() useBrowserState()
} else { } else {
if ( if (
!browserState.exportBookkeeping || browserState.revisionHistory.indexOf(
browserState.exportBookkeeping.basedOnRevisions.indexOf( onDiskState.revisionHistory[onDiskState.revisionHistory.length - 1],
onDiskState.exportBookkeeping.revision,
) == -1 ) == -1
) { ) {
browserStateIsNotBasedOnDiskState(onDiskState) browserStateIsNotBasedOnDiskState(onDiskState)

View file

@ -1,13 +1,9 @@
import type {StrictRecord} from '@theatre/shared/utils/types' import type {StrictRecord} from '@theatre/shared/utils/types'
import type {SheetState_Historic} from './types/SheetState_Historic' import type {SheetState_Historic} from './types/SheetState_Historic'
export interface ProjectLoadedState {
type: 'loaded'
}
type ProjectLoadingState = type ProjectLoadingState =
| {type: 'loading'} | {type: 'loading'}
| ProjectLoadedState | {type: 'loaded'}
| { | {
type: 'browserStateIsNotBasedOnDiskState' type: 'browserStateIsNotBasedOnDiskState'
onDiskState: OnDiskState onDiskState: OnDiskState
@ -34,7 +30,11 @@ export interface ProjectEphemeralState {
*/ */
export interface ProjectState_Historic { export interface ProjectState_Historic {
sheetsById: StrictRecord<string, SheetState_Historic> sheetsById: StrictRecord<string, SheetState_Historic>
exportBookkeeping?: {revision: string; basedOnRevisions: string[]} /**
* The last 50 revision IDs this state is based on, starting with the most recent one.
* The most recent one is the revision ID of this state
*/
revisionHistory: string[]
definitionVersion: string definitionVersion: string
} }
@ -44,6 +44,4 @@ export interface ProjectState {
ephemeral: ProjectEphemeralState ephemeral: ProjectEphemeralState
} }
export interface OnDiskState extends ProjectState_Historic { export interface OnDiskState extends ProjectState_Historic {}
exportBookkeeping: {revision: string; basedOnRevisions: string[]}
}

View file

@ -17,6 +17,7 @@ export async function setupTestSheet(sheetState: SheetState_Historic) {
sheetsById: { sheetsById: {
Sheet: sheetState, Sheet: sheetState,
}, },
revisionHistory: [],
} }
const project = getProject('Test Project ' + lastProjectN++, { const project = getProject('Test Project ' + lastProjectN++, {
state: projectState, state: projectState,

View file

@ -17,6 +17,7 @@ import SimpleCache from '@theatre/shared/utils/SimpleCache'
import type {IProject, ISheet} from '@theatre/core' import type {IProject, ISheet} from '@theatre/core'
import PaneManager from './PaneManager' import PaneManager from './PaneManager'
import type * as _coreExports from '@theatre/core/coreExports' import type * as _coreExports from '@theatre/core/coreExports'
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
export type CoreExports = typeof _coreExports export type CoreExports = typeof _coreExports
@ -173,7 +174,7 @@ export class Studio {
this._store.redo() this._store.redo()
} }
createExportedStateOfProject(projectId: string): string { createExportedStateOfProject(projectId: string): OnDiskState {
return this._store.createExportedStateOfProject(projectId) return this._store.createExportedStateOfProject(projectId)
} }
} }

View file

@ -28,6 +28,7 @@ import {persistStateOfStudio} from './persistStateOfStudio'
import {isSheetObject} from '@theatre/shared/instanceTypes' import {isSheetObject} from '@theatre/shared/instanceTypes'
import globals from '@theatre/shared/globals' import globals from '@theatre/shared/globals'
import {nanoid} from 'nanoid' import {nanoid} from 'nanoid'
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
export type Drafts = { export type Drafts = {
historic: Draft<StudioHistoricState> historic: Draft<StudioHistoricState>
@ -271,23 +272,29 @@ export default class StudioStore {
this._reduxStore.dispatch(studioActions.historic.redo()) this._reduxStore.dispatch(studioActions.historic.redo())
} }
createExportedStateOfProject(projectId: string): string { createExportedStateOfProject(projectId: string): OnDiskState {
const revision = nanoid(16)
// let's assume projectId is already loaded
this.tempTransaction(({drafts}) => {
const state = drafts.historic.coreByProject[projectId]
const maxNumOfRevisionsToKeep = 50
state.revisionHistory.unshift(revision)
if (state.revisionHistory.length > maxNumOfRevisionsToKeep) {
state.revisionHistory.length = maxNumOfRevisionsToKeep
}
}).commit()
const projectHistoricState = const projectHistoricState =
this._reduxStore.getState().$persistent.historic.innerState.coreByProject[ this._reduxStore.getState().$persistent.historic.innerState.coreByProject[
projectId projectId
] ]
const revision = nanoid(16)
const s = { const generatedOnDiskState: OnDiskState = {
revision, ...projectHistoricState,
definitionVersion: globals.currentProjectStateDefinitionVersion,
projectState: projectHistoricState,
} }
// pushOnDiskRevisionBrowserStateIsBasedOn.originalReducer(s, revision) return generatedOnDiskState
const exportString = JSON.stringify(s, null, 2)
return exportString
} }
} }

View file

@ -1,13 +0,0 @@
import React from 'react'
const BrowserStateIsNotBasedOnDiskStateModal: React.FC<{
projectId: string
}> = (_props) => {
/*
* const projectId = props.projectId
* @todo implement me
*/
return <div>@todo BrowserStateIsNotBasedOnDiskStateModal</div>
}
export default BrowserStateIsNotBasedOnDiskStateModal

View file

@ -1,24 +0,0 @@
import {usePrism} from '@theatre/dataverse-react'
import {val} from '@theatre/dataverse'
import React from 'react'
import BrowserStateIsNotBasedOnDiskStateModal from './BrowserStateIsNotBasedOnDiskStateModal'
import getStudio from '@theatre/studio/getStudio'
const EnsureProjectsDontHaveErrors: React.FC<{}> = ({children}) => {
return usePrism(() => {
const projects = val(getStudio().projectsP)
const projectIds = Object.keys(projects)
for (const projectId of projectIds) {
const project = projects[projectId]
const loadingStateType = val(project.pointers.ephemeral.loadingState.type)
if (loadingStateType === 'browserStateIsNotBasedOnDiskState') {
return <BrowserStateIsNotBasedOnDiskStateModal projectId={projectId} />
}
}
return <div>{children}</div>
}, [children])
}
export default EnsureProjectsDontHaveErrors

View file

@ -1,15 +1,19 @@
import type Project from '@theatre/core/projects/Project' import type Project from '@theatre/core/projects/Project'
import getStudio from '@theatre/studio/getStudio' import getStudio from '@theatre/studio/getStudio'
import React from 'react' import React, {useCallback, useState} from 'react'
const ProjectDetails: React.FC<{ const ProjectDetails: React.FC<{
projects: Project[] projects: Project[]
}> = ({projects}) => { }> = ({projects}) => {
const project = projects[0] const project = projects[0]
const exportProject = () => { const [downloaded, setDownloaded] = useState(false)
const str = getStudio().createExportedStateOfProject(
project.address.projectId, const exportProject = useCallback(() => {
const str = JSON.stringify(
getStudio().createExportedStateOfProject(project.address.projectId),
null,
2,
) )
const file = new File([str], 'state.json', {type: 'application/json'}) const file = new File([str], 'state.json', {type: 'application/json'})
const objUrl = URL.createObjectURL(file) const objUrl = URL.createObjectURL(file)
@ -17,12 +21,27 @@ const ProjectDetails: React.FC<{
a.href = objUrl a.href = objUrl
a.target = '_blank' a.target = '_blank'
a.setAttribute('download', 'state.json') a.setAttribute('download', 'state.json')
a.rel = 'noopener'
a.click() a.click()
}
setDownloaded(true)
setTimeout(() => {
setDownloaded(false)
}, 2000)
setTimeout(() => {
URL.revokeObjectURL(objUrl)
}, 40000)
}, [])
return ( return (
<div> <div>
<button onClick={exportProject}>Export project</button> <button
onClick={!downloaded ? exportProject : undefined}
disabled={downloaded}
>
Export project {downloaded ? 'Done' : ''}
</button>
</div> </div>
) )
} }