import { useReducer } from 'react' ; import { cadPackages , initCodeMap , initGuideMap } from 'src/helpers/cadPackages' ; import type { RootState } from '@react-three/fiber' ; import type { RawCustomizerParams , ArtifactTypes , } from 'src/helpers/cadPackages/common' ; import { CadhubParams } from 'src/components/Customizer/customizerConverter' ; import { CadPackageType } from 'src/components/CadPackage/CadPackage' ; function withThunk ( dispatch , getState ) { return ( actionOrThunk ) => typeof actionOrThunk === 'function' ? actionOrThunk ( dispatch , getState ) : dispatch ( actionOrThunk ) ; } const codeStorageKey = 'Last-editor-code' ; export const makeCodeStoreKey = ( ideType ) => `${ codeStorageKey }-${ ideType }` ; let mutableState = null ; export interface XYZ { x : number ; y : number ; z : number ; } export interface Camera { dist ? : number ; position ? : XYZ ; rotation ? : XYZ ; isScadUpdate ? : boolean ; } export interface MosaicTree { first : string | MosaicTree ; second : string | MosaicTree ; direction ? : string ; splitPercentage ? : number ; } interface CodeTab { type : 'code' ; label : string ; content ? : string ; } interface GuideTab { type : 'guide' ; label : string ; content : string ; } interface ComponentTab { type : 'component' ; label : string ; Component : React . FC ; } type EditorTab = GuideTab | CodeTab | ComponentTab ; export interface State { ideType : 'INIT' | CadPackageType ; viewerContext : 'ide' | 'viewer' ; ideGuide ? : string ; consoleMessages : { type : 'message' | 'error' ; message : string ; time : Date ; } [ ] ; code : string ; editorTabs : EditorTab [ ] ; currentModel : number ; objectData : { type : 'INIT' | ArtifactTypes ; data : any ; quality : 'low' | 'high' ; } ; customizerParams : CadhubParams [ ] ; currentParameters ? : RawCustomizerParams ; isCustomizerOpen : boolean ; layout : MosaicTree ; camera : Camera ; viewerSize : { width : number ; height : number ; } ; isLoading : boolean ; threeInstance : RootState ; sideTray : string [ ] ; } const code = '' ; const initialLayout = { direction : 'row' , first : 'Editor' , second : { direction : 'column' , first : 'Viewer' , second : 'Console' , splitPercentage : 70 , } , } ; export const initialState = { ideType : 'INIT' , viewerContext : 'ide' , consoleMessages : [ { type : 'message' , message : 'Initialising' , time : new Date ( ) } , ] , code , editorTabs : [ { type : 'code' , label : 'Code' } ] , currentModel : 0 , objectData : { type : 'INIT' , data : null , quality : 'low' , } , customizerParams : [ ] , isCustomizerOpen : false , layout : initialLayout , camera : { } , viewerSize : { width : 0 , height : 0 } , isLoading : false , threeInstance : null , sideTray : [ ] , } ; const reducer = ( state , { type , payload } ) : State => { switch ( type ) { case 'initIde' : return { ... state , code : payload . code || initCodeMap [ payload . cadPackage ] || '' , ideType : payload . cadPackage , ideGuide : initGuideMap [ payload . cadPackage ] , viewerContext : payload . viewerContext , } ; case 'updateCode' : return { ... state , code : payload } ; case 'resetCustomizer' : { const resetParameters = { } ; state . customizerParams . forEach ( ( { name , initial } ) => { resetParameters [ name ] = initial ; } ) ; return { ... state , currentParameters : resetParameters , } ; } case 'healthyRender' : { const currentParameters = { } ; const customizerParams = payload . customizerParams || [ ] ; customizerParams . forEach ( ( param ) => { currentParameters [ param . name ] = typeof state ?. currentParameters ?. [ param . name ] === 'undefined' ? param . initial : state ?. currentParameters ?. [ param . name ] ; } ) ; return { ... state , objectData : { ... state . objectData , type : payload . objectData ?. type , data : payload . objectData ?. data , } , customizerParams , currentParameters , consoleMessages : payload . message ? [ ... state . consoleMessages , payload . message ] : payload . message , isLoading : false , camera : payload . camera || state . camera , } ; } case 'errorRender' : return { ... state , consoleMessages : payload . message ? [ ... state . consoleMessages , payload . message ] : payload . message , isLoading : false , } ; case 'setCurrentCustomizerParams' : if ( ! Object . keys ( payload || { } ) . length ) return state ; return { ... state , currentParameters : payload , } ; case 'setCustomizerOpenState' : return { ... state , isCustomizerOpen : payload , } ; case 'setLayout' : return { ... state , layout : payload , } ; case 'updateCamera' : return { ... state , camera : payload . camera , } ; case 'updateViewerSize' : return { ... state , viewerSize : payload . viewerSize , } ; case 'setLoading' : return { ... state , isLoading : true , } ; case 'resetLoading' : return { ... state , isLoading : false , } ; case 'resetLayout' : return { ... state , layout : initialLayout , } ; case 'setThreeInstance' : return { ... state , threeInstance : payload , } ; case 'settingsButtonClicked' : { const isReClick = state . sideTray . length && state . sideTray . length === payload . length && state . sideTray . every ( ( original , index ) => original === payload [ index ] ) ; const payloadInOriginal = payload . length && state . sideTray . indexOf ( payload [ 0 ] ) ; const isAncestorClick = state . sideTray . length && state . sideTray . length > payload . length && payloadInOriginal >= 0 && payload . every ( ( incoming , i ) => incoming === state . sideTray [ i + payloadInOriginal ] ) ; if ( isReClick ) { return { ... state , sideTray : state . sideTray . slice ( 0 , - 1 ) , } ; } else if ( isAncestorClick ) { return { ... state , sideTray : state . sideTray . slice ( 0 , payload . length * - 1 - 1 ) , } ; } return { ... state , sideTray : payload , } ; } case 'switchEditorModel' : return { ... state , currentModel : payload , } ; case 'addEditorModel' : return { ... state , editorTabs : [ ... state . editorTabs , payload ] , } ; case 'removeEditorModel' : return { ... state , editorTabs : [ ... state . editorTabs . slice ( 0 , payload ) , ... state . editorTabs . slice ( payload + 1 ) , ] , currentModel : payload === 0 ? 0 : payload - 1 , } ; default : return state ; } } ; export const useIdeState = ( ) : [ State , ( actionOrThunk : any ) => any ] => { const [ state , dispatch ] = useReducer ( reducer , initialState ) ; mutableState = state ; const getState = ( ) : State => mutableState ; const thunkDispatch = withThunk ( dispatch , getState ) ; return [ state , thunkDispatch ] ; } ; interface RequestRenderArgsStateless { state : State ; camera ? : State [ 'camera' ] ; viewAll ? : boolean ; viewerSize ? : State [ 'viewerSize' ] ; quality ? : State [ 'objectData' ] [ 'quality' ] ; specialCadProcess ? : string ; parameters ? : { [ key ] : any ; } ; } export const requestRenderStateless = ( { state , camera , viewAll , viewerSize , quality = 'low' , specialCadProcess = null , parameters , } ) : null | Promise < any > => { if ( ! ( state . ideType !== 'INIT' && ( ! state . isLoading || state . objectData ?. type === 'INIT' ) ) ) { return null ; } const renderFn = specialCadProcess ? cadPackages [ state . ideType ] [ specialCadProcess ] : cadPackages [ state . ideType ] . render ; return renderFn ( { code : state . code , settings : { parameters : state . isCustomizerOpen ? parameters || state . currentParameters : { } , viewAll , camera : camera || state . camera , viewerSize : viewerSize || state . viewerSize , quality , } , } ) ; } ; interface RequestRenderArgs extends RequestRenderArgsStateless { dispatch : any ; } export const requestRender = ( { dispatch , ... rest } ) => { const renderPromise = requestRenderStateless ( rest ) ; if ( ! renderPromise ) { return ; } return renderPromise . then ( ( { objectData , message , status , customizerParams , currentParameters , camera , } ) => { if ( status === 'error' ) { dispatch ( { type : 'errorRender' , payload : { message } , } ) ; } else { dispatch ( { type : 'healthyRender' , payload : { objectData , message , lastRunCode : code , customizerParams , currentParameters , camera , } , } ) ; return objectData ; } } ) . catch ( ( ) => dispatch ( { type : 'resetLoading' } ) ) ; } ;