import type { Keyframe } from '@theatre/core/projects/store/types/SheetState_Historic' ; import type SheetObject from '@theatre/core/sheetObjects/SheetObject' ; import getStudio from '@theatre/studio/getStudio' ; import type Scrub from '@theatre/studio/Scrub' ; import type { IContextMenuItem } from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' ; import getDeep from '@theatre/shared/utils/getDeep' ; import { usePrism } from '@theatre/react' ; import type { $FixMe , SerializablePrimitive } from '@theatre/shared/utils/types' ; import { getPointerParts , prism , val } from '@theatre/dataverse' ; import get from 'lodash-es/get' ; import last from 'lodash-es/last' ; import React from 'react' ; import DefaultOrStaticValueIndicator from './DefaultValueIndicator' ; import NextPrevKeyframeCursors from './NextPrevKeyframeCursors' ; import type { PropTypeConfig } from '@theatre/core/propTypes' ; interface CommonStuff < T > { value : T ; beingScrubbed : boolean ; contextMenuItems : Array < IContextMenuItem > ; controlIndicators : React . ReactElement ; temporarilySetValue ( v ) : void ; discardTemporaryValue ( ) : void ; permenantlySetValue ( v ) : void ; } interface Default < T > extends CommonStuff < T > { type : 'Default' ; shade : Shade ; } interface Static < T > extends CommonStuff < T > { type : 'Static' ; shade : Shade ; } interface Sequenced < T > extends CommonStuff < T > { type : 'Sequenced' ; shade : Shade ; nearbyKeyframes : NearbyKeyframes ; } type Stuff < T > = Default < T > | Static < T > | Sequenced < T > ; export function useEditingToolsForPrimitiveProp < T extends SerializablePrimitive > ( pointerToProp , obj , propConfig ) { return usePrism ( ( ) => { const pathToProp = getPointerParts ( pointerToProp ) . path ; const final = obj . getValueByPointer ( pointerToProp ) as T ; const callbacks = prism . memo ( 'callbacks' , ( ) => { let currentScrub = null ; return { temporarilySetValue ( v ) { if ( ! currentScrub ) { currentScrub = getStudio ( ) ! . scrub ( ) ; } currentScrub . capture ( ( api ) => { api . set ( pointerToProp , v ) ; } ) ; } , discardTemporaryValue ( ) { if ( currentScrub ) { currentScrub . discard ( ) ; currentScrub = null ; } } , permenantlySetValue ( v ) { if ( currentScrub ) { currentScrub . capture ( ( api ) => { api . set ( pointerToProp , v ) ; } ) ; currentScrub . commit ( ) ; currentScrub = null ; } else { getStudio ( ) ! . transaction ( ( api ) => { api . set ( pointerToProp , v ) ; } ) ; } } , } ; } , [ ] ) ; const beingScrubbed = val ( get ( getStudio ( ) ! . atomP . ephemeral . projects . stateByProjectId [ obj . address . projectId ] . stateBySheetId [ obj . address . sheetId ] . stateByObjectKey [ obj . address . objectKey ] . valuesBeingScrubbed , getPointerParts ( pointerToProp ) . path ) ) === true ; const contextMenuItems = [ ] ; const common = { ... callbacks , value : final , beingScrubbed , contextMenuItems , controlIndicators : < > < />, } ; const isSequencable = isPropConfSequencable ( propConfig ) ; if ( isSequencable ) { const validSequencedTracks = val ( obj . template . getMapOfValidSequenceTracks_forStudio ( ) ) ; const possibleSequenceTrackId = getDeep ( validSequencedTracks , pathToProp ) ; const isSequenced = typeof possibleSequenceTrackId === 'string' ; if ( isSequenced ) { contextMenuItems . push ( { label : 'Make static' , callback : ( ) => { getStudio ( ) ! . transaction ( ( { stateEditors } ) => { const propAddress = { ... obj . address , pathToProp } ; stateEditors . coreByProject . historic . sheetsById . sequence . setPrimitivePropAsStatic ( { ... propAddress , value : obj . getValueByPointer ( pointerToProp ) as T , } ) ; } ) ; } , } ) ; const sequenceTrcackId = possibleSequenceTrackId as $FixMe as string ; const nearbyKeyframes = prism . sub ( 'lcr' , ( ) : NearbyKeyframes => { const track = val ( obj . template . project . pointers . historic . sheetsById [ obj . address . sheetId ] . sequence . tracksByObject [ obj . address . objectKey ] . trackData [ sequenceTrcackId ] ) ; if ( ! track || track . keyframes . length === 0 ) return { } ; const pos = val ( obj . sheet . getSequence ( ) . positionDerivation ) ; const i = track . keyframes . findIndex ( ( kf ) => kf . position >= pos ) ; if ( i === - 1 ) return { prev : last ( track . keyframes ) , } ; const k = track . keyframes [ i ] ! ; if ( k . position === pos ) { return { prev : i > 0 ? track . keyframes [ i - 1 ] : undefined , cur : k , next : i === track . keyframes . length - 1 ? undefined : track . keyframes [ i + 1 ] , } ; } else { return { next : k , prev : i > 0 ? track . keyframes [ i - 1 ] : undefined , } ; } } , [ sequenceTrcackId ] ) ; let shade ; if ( common . beingScrubbed ) { shade = 'Sequenced_OnKeyframe_BeingScrubbed' ; } else { if ( nearbyKeyframes . cur ) { shade = 'Sequenced_OnKeyframe' ; } else if ( nearbyKeyframes . prev ?. connectedRight === true ) { shade = 'Sequenced_BeingInterpolated' ; } else { shade = 'Sequened_NotBeingInterpolated' ; } } const nextPrevKeyframeCursors = ( < NextPrevKeyframeCursors { ... nearbyKeyframes } jumpToPosition = { ( position ) => { obj . sheet . getSequence ( ) . position = position ; } } toggleKeyframeOnCurrentPosition = { ( ) => { if ( nearbyKeyframes . cur ) { getStudio ( ) ! . transaction ( ( api ) => { api . unset ( pointerToProp ) ; } ) ; } else { getStudio ( ) ! . transaction ( ( api ) => { api . set ( pointerToProp , common . value ) ; } ) ; } } } />); const ret = { ... common , type : 'Sequenced' , shade , nearbyKeyframes , controlIndicators : nextPrevKeyframeCursors , } ; return ret ; } } contextMenuItems . push ( { label : 'Reset to default' , callback : ( ) => { getStudio ( ) ! . transaction ( ( { unset } ) => { unset ( pointerToProp ) ; } ) ; } , } ) ; if ( isSequencable ) { contextMenuItems . push ( { label : 'Sequence' , callback : ( ) => { getStudio ( ) ! . transaction ( ( { stateEditors } ) => { const propAddress = { ... obj . address , pathToProp } ; stateEditors . coreByProject . historic . sheetsById . sequence . setPrimitivePropAsSequenced ( propAddress , propConfig ) ; } ) ; } , } ) ; } const statics = val ( obj . template . getStaticValues ( ) ) ; if ( typeof getDeep ( statics , pathToProp ) !== 'undefined' ) { const ret = { ... common , type : 'Static' , shade : common . beingScrubbed ? 'Static_BeingScrubbed' : 'Static' , controlIndicators : ( < DefaultOrStaticValueIndicator hasStaticOverride = { true } / > ) , } ; return ret ; } const ret = { ... common , type : 'Default' , shade : 'Default' , controlIndicators : ( < DefaultOrStaticValueIndicator hasStaticOverride = { false } / > ) , } ; return ret ; } , [ ] ) ; } type NearbyKeyframes = { prev ? : Keyframe ; cur ? : Keyframe ; next ? : Keyframe ; } ; export const shadeToColor = { Default : '#222' , Static : '#333' , Static_BeingScrubbed : '#91a100' , Sequenced_OnKeyframe : '#700202' , Sequenced_OnKeyframe_BeingScrubbed : '#c50000' , Sequenced_BeingInterpolated : '#0387a8' , Sequened_NotBeingInterpolated : '#004c5f' , } ; type Shade = 'Default' | 'Static' | 'Static_BeingScrubbed' | 'Sequenced_OnKeyframe' | 'Sequenced_OnKeyframe_BeingScrubbed' | 'Sequenced_BeingInterpolated' | 'Sequened_NotBeingInterpolated' ; function isPropConfSequencable ( conf ) { return conf . type === 'number' || ( ! ! conf . sanitize && ! ! conf . interpolate ) ; }