import forEachDeep from '@theatre/shared/utils/forEachDeep' ; import type { $FixMe } from '@theatre/shared/utils/types' ; import type { Pointer } from '@theatre/dataverse' ; import { getPointerParts } from '@theatre/dataverse' ; import type { Studio } from './Studio' ; import type { CommitOrDiscard } from './StudioStore/StudioStore' ; import logger from '@theatre/shared/logger' ; import { isSheetObject } from '@theatre/shared/instanceTypes' ; type State_Captured = { type : 'Captured' ; transaction : CommitOrDiscard ; flagsTransaction : CommitOrDiscard ; } ; type State = { type : 'Ready' ; } | State_Captured | { type : 'Committed' ; } | { type : 'Discarded' ; } ; let lastScrubIdAsNumber = 0 ; /**
 * The scrub API
 */ export interface IScrubApi { param pointer param value example set < T > ( pointer , value ) : void ; } export interface IScrub { /**
     * Clears all the ops in the scrub, but keeps the scrub open so you can call
     * `scrub.capture()` again.
     */ reset ( ) : void ; /**
     * Commits the scrub and creates a single undo level.
     */ commit ( ) : void ; example capture ( fn ) : void ; /**
     * Clearts the ops of the scrub and destroys it. After calling this,
     * you won't be able to call `scrub.capture()` anymore.
     */ discard ( ) : void ; } export default class Scrub implements IScrub { private readonly _id : string ; private _state : State = { type : 'Ready' } ; get status ( ) { return this . _state . type ; } constructor ( private readonly _studio ) { this . _id = String ( lastScrubIdAsNumber ++ ) ; } reset ( ) { const { _state : state } = this ; if ( state . type === 'Ready' ) { return ; } else if ( state . type === 'Captured' ) { this . _state = { type : 'Ready' } ; state . transaction . discard ( ) ; state . flagsTransaction . discard ( ) ; } else if ( state . type === 'Committed' ) { throw new Error ( `This scrub is already committed and can't be reset.` ) ; } else { throw new Error ( `This scrub is already discarded and can't be reset.` ) ; } } commit ( ) { const { _state : state } = this ; if ( state . type === 'Captured' ) { state . transaction . commit ( ) ; state . flagsTransaction . discard ( ) ; this . _state = { type : 'Committed' } ; } else if ( state . type === 'Ready' ) { logger . warn ( `Scrub is empty. Nothing to commit.` ) ; return ; } else if ( state . type === 'Committed' ) { throw new Error ( `This scrub is already committed.` ) ; } else { throw new Error ( `This scrub is already discarded and can't be comitted.` ) ; } } capture ( fn ) { if ( this . _state . type === 'Captured' ) { this . reset ( ) ; } if ( this . _state . type === 'Ready' ) { let errored = true ; try { this . _state = { type : 'Captured' , ... this . _capture ( fn ) } ; errored = false ; } finally { if ( errored ) { this . _state = { type : 'Discarded' } ; } } } else { if ( this . _state . type === 'Committed' ) { throw new Error ( `This scrub is already committed and cannot capture again.` + `If you wish to capture more, you can start a new studio.scrub() or do so before scrub.commit()` ) ; } else { throw new Error ( `This scrub is already discarded and cannot capture again.` + `If you wish to capture more, you can start a new studio.scrub() or do so before scrub.discard()` ) ; } } } private _capture ( fn ) { const sets = [ ] ; const transaction = this . _studio . tempTransaction ( ( transactionApi ) => { let running = true ; const api = { set : ( pointer , value ) => { if ( ! running ) { throw new Error ( `You seem to have called the scrub api after scrub.capture()` ) ; } const { root , path } = getPointerParts ( pointer as Pointer < $FixMe > ) ; if ( ! isSheetObject ( root ) ) { throw new Error ( `We can only scrub props of Sheet Objects for now` ) ; } transactionApi . set ( pointer , value ) ; sets . push ( pointer as Pointer < $FixMe > ) ; } , } ; try { fn ( api ) ; } finally { running = false ; } } ) ; const flagsTransaction = this . _studio . tempTransaction ( ( { stateEditors } ) => { sets . forEach ( ( pointer ) => { const { root , path } = getPointerParts ( pointer as Pointer < $FixMe > ) ; if ( ! isSheetObject ( root ) ) { return ; } const defaultValueOfProp = root . template . getDefaultsAtPointer ( pointer ) ; forEachDeep ( defaultValueOfProp , ( val , pathToProp ) => { stateEditors . studio . ephemeral . projects . stateByProjectId . stateBySheetId . stateByObjectKey . propsBeingScrubbed . flag ( { ... root . address , pathToProp } ) ; } , path ) ; } ) ; } ) ; return { transaction , flagsTransaction } ; } discard ( ) { const { _state : state } = this ; if ( state . type === 'Captured' || state . type === 'Ready' ) { if ( state . type === 'Captured' ) { state . transaction . discard ( ) ; state . flagsTransaction . discard ( ) ; } this . _state = { type : 'Discarded' } ; } else if ( state . type === 'Committed' ) { throw new Error ( `This scrub is already committed and can't be discarded.` ) ; } else { throw new Error ( `This scrub is already discarded` ) ; } } }