import * as React from "react" ; import { ReactNode , RefObject , PureComponent } from "react" ; import SceneRenderer , { sceneDefaultProps } from "./SceneRenderer" ; import Sidepanel from "./Sidepanel" ; import View , { CommonViewClass } from "./View" ; import { ViewProps } from "./ViewRenderer" ; import Mayer , { PreparedProps as MayerProps } from "./Mayer" ; import update from "immutability-helper" ; import { SidepanelConfig } from "./Airr" ; import ViewsAPIHelper from "./Scene/ViewsAPIHelper" ; import SidepanelAPIHelper from "./Scene/SidepanelAPIHelper" ; import MayersAPIHelper from "./Scene/MayersAPIHelper" ; import { CoreSceneProps } from "./SceneRenderer" ; import { getViewProps } from "./CommonViewHelpers" ; export interface SceneProps extends CoreSceneProps { /**
     * This propety changes behaviour of views animation when overlay animation is set
     */ stackMode ? : boolean ; } export type SceneState = SceneProps ; export type CommonViewProps = ViewProps | SceneProps ; export interface ViewConfig < T > { /**
     * Refference to class or function that will render View. Use View class refference or class that extends View.
     */ type : React . ComponentClass < T & CommonViewProps , any > ; /**
     * Special properties of AirrView class. Go to class declaration for further properties documenation.
     */ props : T & CommonViewProps ; } export interface ViewsConfigItem < T > extends ViewConfig < T > { /**
     * Props to modify this Scene
     */ sceneProps ? : Pick < SceneProps , Exclude < keyof SceneProps , "name" > > ; /**
     *
     * Common view configutaion can have nameGenerator function used to create another view name propperty.
     * Gets current state views list as argument.
     * Example:
     * nameGenerator: views => { return "common-view-*".replace("*", views.length + 1);}
     */ nameGenerator ? ( views ) : string ; } /**
 * View configuraion which can be found by key which is also it's name.
 */ export type ViewsConfig < T = { } > = { [ K in keyof T ] : ViewsConfigItem < T [ K ] > ; } ; export interface RefsCOMPViews { [ viewname ] : RefObject < View < ViewProps > > ; } export default class Scene < P extends SceneProps = SceneProps , S extends SceneState = SceneState > extends PureComponent < P , S > implements CommonViewClass { static defaultProps = { ... sceneDefaultProps , stackMode : false } ; /**
     * Class member that keep information about views configuraion objects.
     * Every key in this object describes another view.
     * That configuration later will be used to create new view and add it to state views array.
     * Used by ::getFreshViewConfig to deliver new view config.
     * This variable is mainly used in crucial components's ::changeView method.
     */ viewsConfig : ViewsConfig ; /**
     * Refferency to view's DOM element.
     */ refDOM = React . createRef < HTMLDivElement > ( ) ; /**
     * Special method for delivering props to View component's.
     * Used in render method.
     */ getViewProps = getViewProps . bind ( this ) ; /**
     * Instantiated views Component's refferences
     */ refsCOMPViews : RefsCOMPViews = { } ; /**
     * Instantiated mayers Components refferences
     */ refsCOMPMayers : { [ configName ] : RefObject < Mayer > ; } = { } ; /**
     * Instantiated sidepanel Component refference
     */ refCOMPSidepanel = React . createRef < Sidepanel > ( ) ; /**
     * Refference to DOM element of container's div (first child of most outer element)
     */ refDOMContainer = React . createRef < HTMLDivElement > ( ) ; /**
     * Refference to DOM element of navbar's div
     */ refDOMNavbar = React . createRef < HTMLDivElement > ( ) ; /**
     * Helper variable for storing views names that will be filtered
     */ viewsNamesToStayList : string [ ] = [ ] ; /**
     * Describes if views animation is taking place
     */ viewChangeInProgress = false ; /**
     * Common view life-cycle method to be overwriten in classes that extends this class
     */ viewAfterActivation ( ) { } viewAfterDeactivation ( ) { } viewBeforeActivation ( ) { } viewBeforeDeactivation ( ) { } state : S = { ... this . state , name : this . props . name , active : this . props . active , className : this . props . className , title : this . props . title , navbar : this . props . navbar , navbarHeight : this . props . navbarHeight , navbarMenu : this . props . navbarMenu , navbarClass : this . props . navbarClass , backButton : this . props . backButton , backButtonOnFirstView : this . props . backButtonOnFirstView , activeViewName : this . props . activeViewName , animation : this . props . animation , views : this . props . views , sidepanel : this . props . sidepanel , sidepanelVisibilityCallback : this . props . sidepanelVisibilityCallback , GUIDisabled : this . props . GUIDisabled , GUIDisableCover : this . props . GUIDisableCover , mayers : this . props . mayers , children : this . props . children , animationTime : this . props . animationTime , handleBackBehaviourOnFirstView : this . props . handleBackBehaviourOnFirstView , handleBackButton : this . props . handleBackButton , stackMode : this . props . stackMode } ; componentDidMount ( ) { return new Promise ( ( resolve ) : void => { SidepanelAPIHelper . initWindowResizeListener ( this ) ; /**
             * Call first active view life cycle method - viewAfterActivation
             */ ViewsAPIHelper . invokeActivationEffectOnActiveView ( this ) ; if ( this . state . sidepanel ) { SidepanelAPIHelper . updateSidepanelSizeProps ( this ) . then ( resolve ) ; } else { resolve ( ) ; } } ) ; } render ( ) { const { views , sidepanel , className , ... stateRest } = this . state ; return ( < SceneRenderer { ... { ... stateRest , views : views , sidepanel : sidepanel , refDOMContainer : this . refDOMContainer , refDOMNavbar : this . refDOMNavbar , refsCOMPViews : this . refsCOMPViews , refCOMPSidepanel : this . refCOMPSidepanel , sidepanelVisibilityCallback : this . __sidepanelVisibilityCallback } } { ... this . getViewProps ( ) } { ... { className } } />); } /**
     * Special lifecycle method to be overwritten in descendant classes.
     * Called, as name sugest, when views animation finish.
     */ viewsAnimationEnd ( oldViewName , newViewName ) { } param { string | ViewConfig } view param { object } props getFreshViewConfig < T > ( view , props = { } ) { if ( typeof view === "string" && view in this . viewsConfig ) { const config = Object . assign ( { } , this . viewsConfig [ view ] ) ; const viewGenerator = this . viewsConfig [ view ] . nameGenerator ; return update ( this . viewsConfig [ view ] , { props : { $set : { ... Object . assign ( { } , config . props ) , ... Object . assign ( { } , props ) , name : viewGenerator && typeof viewGenerator === "function" ? viewGenerator ( this . state . views ) : view } } } ) ; } else if ( typeof view === "object" && this . isValidViewConfig < T > ( view ) ) { return Object . assign ( { } , view , { props : { ... view . props , ... props } } ) ; } else { throw new Error ( `Passed view is not present in viewsConfig or is invalid raw ViewConfig object.` ) ; } } param { array } viewsNameList returns { Promise } filterViews ( viewsNameList = [ ] ) { return new Promise ( ( resolve ) : void => { this . setState ( { views : this . state . views . filter ( ( view ) : boolean => viewsNameList . indexOf ( view . props . name ) !== - 1 ) } , resolve ) ; } ) ; } param { object } viewProps param { object } sceneProps returns { Promise } popView = async ( viewProps = { } , sceneProps = { } ) : Promise < string | void > => { if ( this . state . views . length > 1 ) { const viewName = this . state . views [ this . state . views . length - 2 ] . props . name ; await this . changeView ( viewName , viewProps , sceneProps ) ; const newviewdefinition = update ( this . state . views , { $splice : [ [ this . state . views . length - 1 , 1 ] ] } ) ; delete this . refsCOMPViews [ this . state . views [ this . state . views . length - 1 ] . props . name ] ; return new Promise ( ( resolve ) : void => this . setState ( { views : newviewdefinition } , ( ) : void => resolve ( viewName ) ) ) ; } else { console . warn ( "[] No view to pop." ) ; return Promise . resolve ( ) ; } } ; param { string | object } view param { object } viewProps param { object } sceneProps returns { Promise } async changeView ( view , viewProps = { } , sceneProps = { } ) { const viewName = await ViewsAPIHelper . changeView ( this , view , viewProps , sceneProps ) ; return ViewsAPIHelper . performViewsAnimation ( this , viewName ) ; } param { string } name destroyView ( name ) { return new Promise ( ( resolve , reject ) : void => { const index = this . state . views . findIndex ( ( view ) : boolean => view . props . name === name ) ; if ( index !== - 1 ) { this . setState ( { views : update ( this . state . views , { $splice : [ [ index , 1 ] ] } ) } , resolve ) ; } else { reject ( `View with name: ${ name } was not found in this scene.` ) ; } } ) ; } returns { Promise } handleBackButton = ( viewProps = { } , sceneProps = { } ) : Promise < string | void > => { if ( this . state . views . length > 1 ) { return this . popView ( viewProps , sceneProps ) ; } return Promise . resolve ( ) ; } ; returns { Promise } setSidepanelConfig = ( config ) : Promise < void > => { return new Promise ( ( resolve ) : void => this . setState ( { sidepanel : config } , ( ) : void => { SidepanelAPIHelper . updateSidepanelSizeProps ( this ) . then ( resolve ) ; } ) ) ; } ; returns { Promise } disableSidepanel = ( ) : Promise < void > => { return SidepanelAPIHelper . toggleSidepanel ( this , false ) ; } ; returns { Promise } enableSidepanel = ( ) : Promise < void > => { return SidepanelAPIHelper . toggleSidepanel ( this , true ) ; } ; returns { Promise } openSidepanel = ( ) : Promise < boolean | void > => { if ( this . state . sidepanel && this . refCOMPSidepanel . current ) { this . setState ( { sidepanel : update ( this . state . sidepanel , { props : { enabled : { $set : true } } } ) } ) ; return this . refCOMPSidepanel . current . show ( ) ; } return Promise . resolve ( ) ; } ; returns { Promise } hideSidepanel = ( ) : Promise < boolean | void > => { if ( this . state . sidepanel && this . refCOMPSidepanel . current ) { return this . refCOMPSidepanel . current . hide ( ) ; } return Promise . resolve ( ) ; } ; param { object } config returns { Promise } openMayer ( config ) { if ( this . state . mayers . findIndex ( ( item ) : boolean => item . name === config . name ) !== - 1 ) { console . warn ( "[] Scene allready has Mayer with this name: " + config . name ) ; return Promise . reject ( ) ; } const preparedConfig = MayersAPIHelper . prepareMayerConfig ( this , config ) ; return MayersAPIHelper . addMayer ( this , preparedConfig ) ; } param { string } name returns { Promise } closeMayer ( name ) { if ( MayersAPIHelper . hasMountedMayer ( this , name ) ) { return new Promise ( ( resolve ) : void => { this . refsCOMPMayers [ name ] . current . animateOut ( ( ) : void => { if ( MayersAPIHelper . hasMountedMayer ( this , name ) ) { MayersAPIHelper . removeMayer ( this , name ) . then ( resolve ) ; } } ) ; } ) ; } return Promise . resolve ( ) ; } param { string } name param { array } viewsNamesToStayList returns { function } goToView = ( name , viewsNamesToStayList = [ ] ) : Function => { return ( params = { } , sceneProps = { } ) : Promise < string | void > => { this . viewsNamesToStayList = viewsNamesToStayList ; return this . changeView ( name , params , sceneProps ) ; } ; } ; param { object } object returns { boolean } isValidViewConfig < T > ( object ) { return ( typeof object === "object" && "type" in object && typeof object . props === "object" && "name" in object . props ) ; } param { string } name returns { boolean } hasViewInConfig = ( name ) : boolean => name in this . viewsConfig ; param { string } name returns { boolean } hasViewInState = ( name ) : boolean => this . state . views . findIndex ( ( view ) : boolean => view . props . name === name ) !== - 1 ? true : false ; param { string } viewName returns { number } getViewIndex = ( viewName ) : number => this . state . views . findIndex ( ( view ) : boolean => view . props . name === viewName ) ; /**
     * Utility function for updating sidepanel isShown prop
     */ __sidepanelVisibilityCallback = ( isShown ) : void => { return SidepanelAPIHelper . sidepanelVisibilityCallback ( this , isShown ) ; } ; }