File

projects/dvk/src/lib/dynamic-animations/default-animations-state-machine/default-animations-state-machine.service.ts

Description

The default implementation of the AnimationStateMachine interface. This class is provided to the DynamicAnimationsHandler in order to handle the animations based on string state transitions in the same way that the standard Angular Animations work.

Implements

AnimationStateMachine

Index

Properties
Methods

Constructor

constructor(element: any, transitions: AnimationTransitionsMap, styles: AnimationStylesMap, builder: AnimationBuilder)
Parameters :
Name Type Optional
element any no
transitions AnimationTransitionsMap no
styles AnimationStylesMap no
builder AnimationBuilder no

Properties

onAnimationDone
onAnimationDone:
Default value : ( state: string, mapper: StateCSSMapper = null, player: AnimationPlayer) => () => { if(mapper) { mapper.add(state); } if(this.currentPlayer) { this.currentPlayer.destroy(); } this.currentPlayer = player; }

Create the callback function for an animation to execute when the animation finishes. The callback will add the css class defined by the state and the StateCSSMapper.

Parameters :
Name Description
state

The string that represents the state.

mapper

The StateCSSMapper that modifies the css of an element.

onAnimationStart
onAnimationStart:
Default value : ( state: string, mapper: StateCSSMapper) => () => { if(mapper) { mapper.remove(state); } }

Create the callback function for an animation to execute when the animation starts. The callback will remove the css class defined by the state and the StateCSSMapper.

Parameters :
Name Description
state

The string that represents the state.

mapper

The StateCSSMapper that modifies the css of an element.

Methods

addStyleKeys
addStyleKeys(metadata: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata, styleKeys: literal type)

Add a set of style keys inside the passed metadata into a passed object representing the styles.

The style keys are set to the value of '*'.

Parameters :
Name Type Optional Description
metadata AnimationStyleMetadata | AnimationKeyframesSequenceMetadata no

The AnimationStyleMetadata or AnimationKeyframesSequenceMetadata containing the styles to add.

styleKeys literal type no

The object of style keys to add to.

Returns : void
buildPlayer
buildPlayer(fromState: string, toState: string)

Build the next animation player between 2 states. If no transition is applicable transition directly from the previous state to the next state.

NOTE: All animations will execute for 1 extra millisecond to apply the final state style.

Using animation_timeline_builder.ts as reference.

Parameters :
Name Type Optional Description
fromState string no

The state to transition from.

toState string no

The state to transition to.

Returns : any

An AnimationFactory representing the desired transition animation.

buildTransitionAnimationMetadata
buildTransitionAnimationMetadata(transitions: AnimationTransitionsMap)

Build metadata necessary to create animations dynamically that have the same behavior (I hope) of static Angular animations.

Parameters :
Name Type Optional Description
transitions AnimationTransitionsMap no

An AnimationTransitionsMap of transitions to build metadata from.

Returns : DvkTransitionsAnimationMetadata
destroy
destroy()

Clean up the state machine resources.

Returns : void
getParseAnimationMetadata
getParseAnimationMetadata(fromState: string, toState: string)

Return the metadata for a transition from one state to the next respecting wildcard (*) transitions.

Parameters :
Name Type Optional Description
fromState string no

The state to transition from.

toState string no

The state to transition to.

Returns : DvkAnimationMetadata
getStyle
getStyle(state: string)

Retreive the style, if defined, for a given state.

Parameters :
Name Type Optional Description
state string no

The state to retreive.

Returns : any
init
init(state: string, mapper: StateCSSMapper)

Initialize the state machine.

Parameters :
Name Type Optional Default value Description
state string no

The initial state.

mapper StateCSSMapper no null

The css classes to apply.

Returns : void
mergeStyleKeys
mergeStyleKeys(style: AnimationStyleMetadata, usedKeys: literal type)

Merge a set of style keys into a passed {@link AnimationStyleMetadata}

Parameters :
Name Type Optional Description
style AnimationStyleMetadata no

The AnimationStyleMetadata to merge into

usedKeys literal type no

The keys to merge in

Returns : any
next
next(nextState: string, mapper: StateCSSMapper)

Retreive the animation if one exists for the specified transitiona and play it. Apply the css classes as specified whether an animation exists or not.

Parameters :
Name Type Optional Default value Description
nextState string no

The state to transition to.

mapper StateCSSMapper no null

The map of css classes to apply.

Returns : string
parseAnimationMetadata
parseAnimationMetadata(metadata: AnimationMetadata | AnimationMetadata[])

Parse valid transition metadata into {@link DvkAnimationMetadata}

Parameters :
Name Type Optional Description
metadata AnimationMetadata | AnimationMetadata[] no

The AnimationMetadata or array of AnimationMetadata that specifies the animation of a transition.

Returns : DvkAnimationMetadata

Metadata as DvkAnimationMetadata

parseAnimationStep
parseAnimationStep(metadata: AnimationMetadata, usedStyleKeys: literal type, unstyledAnimations: AnimationAnimateMetadata[])

Parse an animation function recursively for any style or keyframe[] and map all keys of those styles to the usedStyleKeys object.

Call itself recursively and return the animation normalized into a {@link DvkAnimationStepMetadata} object.

Parameters :
Name Type Optional Description
metadata AnimationMetadata no

The top level animation metadata

usedStyleKeys literal type no

An object representing all the styles used, mutated by the recursive calls.

unstyledAnimations AnimationAnimateMetadata[] no
Returns : void
import { AnimationGroupMetadata, AnimationAnimateChildMetadata, AnimationQueryMetadata, AnimationStaggerMetadata, AnimationReferenceMetadata, AnimationAnimateRefMetadata, AnimationMetadata, style, AnimationAnimateMetadata, AnimationMetadataType, AnimationStyleMetadata, animate, sequence, AnimationSequenceMetadata, AnimationKeyframesSequenceMetadata } from '@angular/animations';
import { AnimationPlayer, AnimationBuilder } from '@angular/animations';
import { AnimationStateMachine } from '../animation-state-machine/animation-state-machine.model';
import { StateCSSMapper } from '../state-css-mapper/state-css-mapper.model';
import { AnimationTransitionsMap, AnimationStylesMap } from '../animation-transitions/animation-transitions.model';
import { NgTransitionStates } from '../ng-transition/ng-transition.states';

/**
 * @ignore
 */
interface DvkAnimationStepMetadata {
  unStyledAnimations: AnimationAnimateMetadata[];
}

/**
 * @ignore
 */
interface DvkAnimationMetadata extends DvkAnimationStepMetadata {
  animation: AnimationMetadata[],
  usedKeys: { [key:string]:string }
}

/**
 * @ignore
 */
interface DvkTransitionsAnimationMetadata {
  [fromState: string]: {
    [toState:string]: DvkAnimationMetadata
  }
}

/**
 * The default implementation of the {@link AnimationStateMachine} 
 * interface.  This class is provided to the {@link DynamicAnimationsHandler}
 * in order to handle the animations based on string state transitions
 * in the same way that the standard Angular Animations work. 
 */
export class DefaultAnimationsStateMachine implements AnimationStateMachine {
  /**
   * @ignore
   */
  private currentState: string;

  /**
   * @ignore
   */
  private currentTransition: AnimationPlayer;

  /**
   * @ignore
   */
  private currentPlayer: AnimationPlayer;

  /**
   * @ignore
   */
  private parsedMetadata:DvkTransitionsAnimationMetadata;

  constructor(
    private element: any, 
    private transitions: AnimationTransitionsMap,
    private styles: AnimationStylesMap,
    private builder: AnimationBuilder) { 
  }

  /**
   * Initialize the state machine.
   * @param state The initial state.
   * @param mapper The css classes to apply.
   */
  init(state:string, mapper: StateCSSMapper = null) { 
    this.currentState = state;

    this.parsedMetadata = 
      this.buildTransitionAnimationMetadata(this.transitions);

    if(mapper) {
      mapper.add(this.currentState);
    }
  }

  /**
   * Retreive the animation if one exists for the specified 
   * transitiona and play it.  Apply the css classes as specified
   * whether an animation exists or not.
   * @param nextState The state to transition to.
   * @param mapper The map of css classes to apply.
   */
  next(nextState: string, mapper: StateCSSMapper = null) {
    if(this.currentState !== nextState) {

      let player: AnimationPlayer;
      let factory = this.buildPlayer(        
        this.currentState,
        nextState
      );

      if(factory) {
        player = factory.create(this.element);
      } 

      if(player) {
        if(this.currentPlayer) {
          this.currentPlayer.reset();
        }

        player.onStart(this.onAnimationStart(this.currentState, mapper));
        player.onDone(this.onAnimationDone(nextState,mapper,player));
        player.onDestroy(()=>{});
        player.play();
        
      }

      /*
       * In case an animation isn't defined for
       * the transition but a css class is handle
       * that by explicitly swapping out  the css 
       * classes when the transition player doesn't 
       * exist.
       */
      else {
        if(mapper) {
          mapper.remove(this.currentState);
          mapper.add(nextState); 
        }
      } 

      this.currentState = nextState;
    }
    return this.currentState;
  }

  /**
   * Clean up the state machine resources.
   */
  destroy() {
    if(this.currentTransition) { this.currentTransition.destroy; }
    this.currentTransition = null;
    this.currentState = null;
    this.currentTransition = null;
    this.parsedMetadata = null;
  }

  /**
   * Create the callback function for an animation to 
   * execute when the animation starts.  The callback 
   * will remove the css class defined by the state 
   * and the [StateCSSMapper]{@link StateCSSMapper}.
   * 
   * @param state The string that represents the state.
   * @param mapper The StateCSSMapper that modifies the 
   * css of an element.
   */
  onAnimationStart = (
    state: string, 
    mapper: StateCSSMapper) => 
    () => {
      if(mapper) {
        mapper.remove(state);
      }
  }

 /**
   * Create the callback function for an animation to 
   * execute when the animation finishes.  The callback 
   * will add the css class defined by the state 
   * and the [StateCSSMapper]{@link StateCSSMapper}.
   * 
   * @param state The string that represents the state.
   * @param mapper The StateCSSMapper that modifies the 
   * css of an element.
   */
  onAnimationDone = (
    state: string, 
    mapper: StateCSSMapper = null, 
    player: AnimationPlayer) => 
    () => {
      if(mapper) {
        mapper.add(state);
      }
      if(this.currentPlayer) {
        this.currentPlayer.destroy();
      }
      this.currentPlayer = player;
    }  

  /**
   * Return the metadata for a transition from one state to the next 
   * respecting wildcard (*) transitions.
   * 
   * @param fromState The state to transition from.
   * @param toState The state to transition to.
   */
  getParseAnimationMetadata(fromState: string, toState: string) {
    if(this.parsedMetadata && this.parsedMetadata[fromState]) { 
      return this.parsedMetadata[fromState][toState] ||
      this.parsedMetadata[fromState][NgTransitionStates.WildCard];
    }
    else if(this.parsedMetadata[NgTransitionStates.WildCard]) { 
      return this.parsedMetadata[NgTransitionStates.WildCard][toState] ||
      this.parsedMetadata[NgTransitionStates.WildCard][NgTransitionStates.WildCard];
    }  
  }

  /**
   * Retreive the style, if defined, for a given state.
   * @param state The state to retreive.
   */
  getStyle(state: string) {
    if(this.styles && this.styles[state]) {
      return this.styles[state];
    }
    return style({});
  }

  /**
   * Build the next animation player between 2 states.
   * If no transition is applicable transition directly
   * from the previous state to the next state.
   * 
   * NOTE: All animations will execute for 1 extra millisecond
   * to apply the final state style.
   * 
   * Using 
   * [animation_timeline_builder.ts]{@link https://github.com/angular/angular/blob/master/packages/animations/browser/src/dsl/animation_timeline_builder.ts}
   * as reference.
   * 
   * @param fromState The state to transition from.
   * @param toState The state to transition to.
   * @returns An AnimationFactory representing the desired 
   * transition animation.
   */
  buildPlayer(
    fromState: string,
    toState: string
  ) {  
    const prevStyle = this.getStyle(fromState);
    const newStyle = this.getStyle(toState);
    const parsedMetadata = this.getParseAnimationMetadata(fromState,toState);

    if(parsedMetadata) {
      const usedStyleKeys = { ...parsedMetadata.usedKeys }; 
      this.addStyleKeys(prevStyle, usedStyleKeys);
      
      let toStyle = { ...newStyle, styles: newStyle.styles };
      toStyle = this.mergeStyleKeys(toStyle, usedStyleKeys)

      parsedMetadata.unStyledAnimations.forEach(animation=>{
        animation.styles = toStyle;
      });

      const factory = this.builder.build(sequence([
        prevStyle,
        ...parsedMetadata.animation,
        animate(1,toStyle)
      ]));

      parsedMetadata.unStyledAnimations.forEach(animation=>{
        animation.styles = null;
      });

      return factory;
    }

    else {
      const usedStyleKeys = {}; 
      this.addStyleKeys(prevStyle, usedStyleKeys);
      let toStyle = { ...newStyle, styles: newStyle.styles};
      toStyle = this.mergeStyleKeys(toStyle, usedStyleKeys)

      const factory = this.builder.build(sequence([
        prevStyle,
        animate(1,toStyle)
      ]));

      return factory;
    }
  }

  /**
   * Build metadata necessary to create animations dynamically
   * that have the same behavior (I hope) of static Angular animations.
   * 
   * @param transitions An AnimationTransitionsMap of transitions
   * to build metadata from.
   */
  buildTransitionAnimationMetadata(transitions: AnimationTransitionsMap) {
    const parsedMetadata: DvkTransitionsAnimationMetadata = {};

    Object.keys(transitions).forEach(fromState=>{
      parsedMetadata[fromState] = {};
      Object.keys(transitions[fromState]).forEach(toState=>{
        parsedMetadata[fromState][toState] = 
          this.parseAnimationMetadata(transitions[fromState][toState]);
      })
    });

    return parsedMetadata;
  }

  /**
   * Parse valid transition metadata into {@link DvkAnimationMetadata}
   *  
   * @param metadata The AnimationMetadata or array of AnimationMetadata
   * that specifies the animation of a transition.
   * @returns Metadata as DvkAnimationMetadata
   */
  parseAnimationMetadata(
    metadata: AnimationMetadata | AnimationMetadata[]): DvkAnimationMetadata {
    const usedKeys = {};
    let normalizedAnimation: AnimationMetadata[] = [];
    let unstyledAnimations: AnimationAnimateMetadata[] = [];

    if(Array.isArray(metadata)) {
      metadata.forEach(md=>{
        this.parseAnimationStep(md, usedKeys, unstyledAnimations);
      });
      normalizedAnimation = metadata; 
    }
    else {
      this.parseAnimationStep(metadata, usedKeys, unstyledAnimations);
      normalizedAnimation = [metadata];
    }

    return {
      animation: normalizedAnimation,
      unStyledAnimations: unstyledAnimations,
      usedKeys: usedKeys
    }
  }

  /**
   * Parse an animation function recursively for any
   * style or keyframe[] and map all keys of those styles 
   * to the usedStyleKeys object.
   * 
   * Call itself recursively and return the animation normalized 
   * into a {@link DvkAnimationStepMetadata} object.
   * 
   * @param metadata The top level animation metadata 
   * @param usedStyleKeys An object representing all the styles 
   * used, mutated by the recursive calls.
   */
  parseAnimationStep(
    metadata: AnimationMetadata, 
    usedStyleKeys: {[key:string]:string},
    unstyledAnimations: AnimationAnimateMetadata[]) {

      switch(metadata.type) {
        case AnimationMetadataType.Animate: { 
          const amd = (metadata as AnimationAnimateMetadata);
          if(amd.styles) {   
            this.addStyleKeys(amd.styles, usedStyleKeys);
          }
          else {
            unstyledAnimations.push(amd);
          }
          break; 
        }
  
        case AnimationMetadataType.Style: { 
          const amd = (metadata as AnimationStyleMetadata);
          this.addStyleKeys(amd, usedStyleKeys);
          break; 
        }
  
        case AnimationMetadataType.Group: {
          const amd = (metadata as AnimationGroupMetadata);
          amd.steps.forEach(md=>{
            this.parseAnimationStep(md,usedStyleKeys, unstyledAnimations);
          });
          break;
        }

        case AnimationMetadataType.Sequence: {
          const amd = (metadata as AnimationSequenceMetadata);
          amd.steps.forEach(md=>{
            this.parseAnimationStep(md,usedStyleKeys, unstyledAnimations);
          });
          break;
        }

        case AnimationMetadataType.Keyframes: {
          const amd = (metadata as AnimationKeyframesSequenceMetadata);
          amd.steps.forEach(md=>{
            this.parseAnimationStep(md,usedStyleKeys, unstyledAnimations);
          });
          break;
        }
  
        case AnimationMetadataType.AnimateChild: {
          // Nothing required?
          break;
        }
         
        
        case AnimationMetadataType.Query: {
          const amd = (metadata as AnimationQueryMetadata);
          if(Array.isArray(amd.animation)) {
            amd.animation.forEach(animation=>{
              this.parseAnimationStep(animation,usedStyleKeys, unstyledAnimations);
            });
          }
          else {
            this.parseAnimationStep(amd.animation,usedStyleKeys, unstyledAnimations);
          }
          break;
        }
        
        case AnimationMetadataType.Stagger: {
          const amd = (metadata as AnimationStaggerMetadata);
          if(Array.isArray(amd.animation)) {
            amd.animation.forEach(animation=>{
              this.parseAnimationStep(animation,usedStyleKeys, unstyledAnimations);
            });
          }
          else {
            this.parseAnimationStep(amd.animation,usedStyleKeys, unstyledAnimations);
          }
          break;
        } 

        case AnimationMetadataType.AnimateRef: { 
          const amd = (metadata as AnimationAnimateRefMetadata);
          if(Array.isArray(amd.animation)) {
            amd.animation.forEach(animation=>{
              this.parseAnimationStep(animation,usedStyleKeys, unstyledAnimations);
            });
          }
          else {
            this.parseAnimationStep(amd.animation,usedStyleKeys, unstyledAnimations);
          }
          break;
        }

        case AnimationMetadataType.Reference: {
          const amd = (metadata as AnimationReferenceMetadata);
          if(Array.isArray(amd.animation)) {
            amd.animation.forEach(animation=>{
              this.parseAnimationStep(animation,usedStyleKeys, unstyledAnimations);
            });
          }
          else {
            this.parseAnimationStep(amd.animation,usedStyleKeys, unstyledAnimations);
          }
          break;
        }
  
        case AnimationMetadataType.State: {
          console.error('Error: state is not valid inside a transition.');
          break;
        }

        case AnimationMetadataType.Transition: {
          console.error('Error: transition is not valid inside a transition.');
          break;
        }

        case AnimationMetadataType.Trigger: { 
          console.error('Error: Trigger is not currently supported by the dvk.');
          break; 
        }
  
        default: { break; }
      }
  }

  /**
   * Add a set of style keys inside the passed metadata
   * into a passed object representing the styles.
   * 
   * The style keys are set to the value of '*'.
   * 
   * @param metadata The AnimationStyleMetadata or AnimationKeyframesSequenceMetadata
   * containing the styles to add.
   * @param styleKeys The object of style keys to add to.
   */
  addStyleKeys(
    metadata: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata,
    styleKeys: { [key:string]: string }) {
    if(this.isStyle(metadata)) {  
      Object.keys(metadata.styles).forEach(key=>{
        styleKeys[key]='*';
      });
    }
    else {
      styleKeys = metadata.steps.reduce((allKeys,style)=>{
        Object.keys(style).forEach(key=>{
          allKeys[key]='*';
        });
        return allKeys;
      },styleKeys);
    }
  }

  /**
   * Merge a set of style keys into a passed {@link AnimationStyleMetadata}
   * @param style The AnimationStyleMetadata to merge into
   * @param usedKeys The keys to merge in
   */
  mergeStyleKeys(
    style: AnimationStyleMetadata, 
    usedKeys: { [key:string]: string }) {
      const newStyle =  {  
        ...style,
        styles: { ...usedKeys }, 
      };
      if(!Array.isArray(style.styles) && !(style.styles as String).length) {
        Object.keys(style.styles).forEach(key=>{
          newStyle.styles[key] = style.styles[key]; 
        });
      } 
      else if(Array.isArray(style.styles)) {
        //unimplemented
        console.error('Error: Styles as arrays currently not supported');
      }
      else {
        //unimplemented, style.styles is '*'
        console.error('Error: Styles as "*" currently not supported');
      }
      return newStyle;
  }

  /*
   * Metadata type constraint functions. 
   */

  /**
   * @ignore
   */
  isStyle(metadata: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata): metadata is AnimationStyleMetadata {
    return metadata.type === AnimationMetadataType.Style;
  }

  /**
   * @ignore
   */
  isAnimate(metadata: AnimationMetadata): metadata is AnimationAnimateMetadata {
    return metadata.type === AnimationMetadataType.Animate;
  }

  /**
   * @ignore
   */
  isSequence(metadata: AnimationMetadata): metadata is AnimationSequenceMetadata {
    return metadata.type === AnimationMetadataType.Sequence;
  } 

  /**
   * @ignore
   */
  isGroup(metadata: AnimationMetadata): metadata is AnimationGroupMetadata {
    return metadata.type === AnimationMetadataType.Group;
  } 

}

results matching ""

    No results matching ""