diff --git a/theatre/core/src/projects/Project.ts b/theatre/core/src/projects/Project.ts index 472c727..76e6533 100644 --- a/theatre/core/src/projects/Project.ts +++ b/theatre/core/src/projects/Project.ts @@ -57,6 +57,7 @@ export default class Project { historic: config.state ?? { sheetsById: {}, definitionVersion: globals.currentProjectStateDefinitionVersion, + revisionHistory: [], }, ephemeral: { loadingState: { diff --git a/theatre/core/src/projects/initialiseProjectState.ts b/theatre/core/src/projects/initialiseProjectState.ts index 9717ccd..03405a7 100644 --- a/theatre/core/src/projects/initialiseProjectState.ts +++ b/theatre/core/src/projects/initialiseProjectState.ts @@ -7,7 +7,7 @@ import globals from '@theatre/shared/globals' /** * @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( studio: Studio, @@ -42,6 +42,7 @@ export default async function initialiseProjectState( drafts.historic.coreByProject[projectId] = { sheetsById: {}, definitionVersion: globals.currentProjectStateDefinitionVersion, + revisionHistory: [], } } @@ -81,9 +82,8 @@ export default async function initialiseProjectState( useBrowserState() } else { if ( - !browserState.exportBookkeeping || - browserState.exportBookkeeping.basedOnRevisions.indexOf( - onDiskState.exportBookkeeping.revision, + browserState.revisionHistory.indexOf( + onDiskState.revisionHistory[onDiskState.revisionHistory.length - 1], ) == -1 ) { browserStateIsNotBasedOnDiskState(onDiskState) diff --git a/theatre/core/src/projects/store/storeTypes.ts b/theatre/core/src/projects/store/storeTypes.ts index 1372434..57bc6c0 100644 --- a/theatre/core/src/projects/store/storeTypes.ts +++ b/theatre/core/src/projects/store/storeTypes.ts @@ -1,13 +1,9 @@ import type {StrictRecord} from '@theatre/shared/utils/types' import type {SheetState_Historic} from './types/SheetState_Historic' -export interface ProjectLoadedState { - type: 'loaded' -} - type ProjectLoadingState = | {type: 'loading'} - | ProjectLoadedState + | {type: 'loaded'} | { type: 'browserStateIsNotBasedOnDiskState' onDiskState: OnDiskState @@ -34,7 +30,11 @@ export interface ProjectEphemeralState { */ export interface ProjectState_Historic { sheetsById: StrictRecord - 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 } @@ -44,6 +44,4 @@ export interface ProjectState { ephemeral: ProjectEphemeralState } -export interface OnDiskState extends ProjectState_Historic { - exportBookkeeping: {revision: string; basedOnRevisions: string[]} -} +export interface OnDiskState extends ProjectState_Historic {} diff --git a/theatre/shared/src/testUtils.ts b/theatre/shared/src/testUtils.ts index 965bc04..d142f1a 100644 --- a/theatre/shared/src/testUtils.ts +++ b/theatre/shared/src/testUtils.ts @@ -17,6 +17,7 @@ export async function setupTestSheet(sheetState: SheetState_Historic) { sheetsById: { Sheet: sheetState, }, + revisionHistory: [], } const project = getProject('Test Project ' + lastProjectN++, { state: projectState, diff --git a/theatre/studio/src/Studio.ts b/theatre/studio/src/Studio.ts index 571f53a..6a347cf 100644 --- a/theatre/studio/src/Studio.ts +++ b/theatre/studio/src/Studio.ts @@ -17,6 +17,7 @@ import SimpleCache from '@theatre/shared/utils/SimpleCache' import type {IProject, ISheet} from '@theatre/core' import PaneManager from './PaneManager' import type * as _coreExports from '@theatre/core/coreExports' +import type {OnDiskState} from '@theatre/core/projects/store/storeTypes' export type CoreExports = typeof _coreExports @@ -173,7 +174,7 @@ export class Studio { this._store.redo() } - createExportedStateOfProject(projectId: string): string { + createExportedStateOfProject(projectId: string): OnDiskState { return this._store.createExportedStateOfProject(projectId) } } diff --git a/theatre/studio/src/StudioStore/StudioStore.ts b/theatre/studio/src/StudioStore/StudioStore.ts index 56a07bd..22c888b 100644 --- a/theatre/studio/src/StudioStore/StudioStore.ts +++ b/theatre/studio/src/StudioStore/StudioStore.ts @@ -28,6 +28,7 @@ import {persistStateOfStudio} from './persistStateOfStudio' import {isSheetObject} from '@theatre/shared/instanceTypes' import globals from '@theatre/shared/globals' import {nanoid} from 'nanoid' +import type {OnDiskState} from '@theatre/core/projects/store/storeTypes' export type Drafts = { historic: Draft @@ -271,23 +272,29 @@ export default class StudioStore { 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 = this._reduxStore.getState().$persistent.historic.innerState.coreByProject[ projectId ] - const revision = nanoid(16) - const s = { - revision, - definitionVersion: globals.currentProjectStateDefinitionVersion, - projectState: projectHistoricState, + const generatedOnDiskState: OnDiskState = { + ...projectHistoricState, } - // pushOnDiskRevisionBrowserStateIsBasedOn.originalReducer(s, revision) - - const exportString = JSON.stringify(s, null, 2) - - return exportString + return generatedOnDiskState } } diff --git a/theatre/studio/src/UIRoot/BrowserStateIsNotBasedOnDiskStateModal.tsx b/theatre/studio/src/UIRoot/BrowserStateIsNotBasedOnDiskStateModal.tsx deleted file mode 100644 index a508c41..0000000 --- a/theatre/studio/src/UIRoot/BrowserStateIsNotBasedOnDiskStateModal.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' - -const BrowserStateIsNotBasedOnDiskStateModal: React.FC<{ - projectId: string -}> = (_props) => { - /* - * const projectId = props.projectId - * @todo implement me - */ - return
@todo BrowserStateIsNotBasedOnDiskStateModal
-} - -export default BrowserStateIsNotBasedOnDiskStateModal diff --git a/theatre/studio/src/UIRoot/EnsureProjectsDontHaveErrors.tsx b/theatre/studio/src/UIRoot/EnsureProjectsDontHaveErrors.tsx deleted file mode 100644 index 33f1dd7..0000000 --- a/theatre/studio/src/UIRoot/EnsureProjectsDontHaveErrors.tsx +++ /dev/null @@ -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 - } - } - - return
{children}
- }, [children]) -} - -export default EnsureProjectsDontHaveErrors diff --git a/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx b/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx index 49fffde..5624e80 100644 --- a/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx +++ b/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx @@ -1,15 +1,19 @@ import type Project from '@theatre/core/projects/Project' import getStudio from '@theatre/studio/getStudio' -import React from 'react' +import React, {useCallback, useState} from 'react' const ProjectDetails: React.FC<{ projects: Project[] }> = ({projects}) => { const project = projects[0] - const exportProject = () => { - const str = getStudio().createExportedStateOfProject( - project.address.projectId, + const [downloaded, setDownloaded] = useState(false) + + 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 objUrl = URL.createObjectURL(file) @@ -17,12 +21,27 @@ const ProjectDetails: React.FC<{ a.href = objUrl a.target = '_blank' a.setAttribute('download', 'state.json') + a.rel = 'noopener' a.click() - } + + setDownloaded(true) + setTimeout(() => { + setDownloaded(false) + }, 2000) + + setTimeout(() => { + URL.revokeObjectURL(objUrl) + }, 40000) + }, []) return (
- +
) }