Simplified export bookkeeping
This commit is contained in:
parent
832542c1c5
commit
f08d9bf9c1
9 changed files with 58 additions and 68 deletions
|
@ -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: {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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[]}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue