Keyframes.ts

import { isInProperties, toFixed } from "./utils";
import PropertyObject from "./PropertyObject";
import Frame from "./Frame";
import { isObject, isArray, ObjectInterface } from "@daybrush/utils";
import { RoleInterface } from "./types";

function getNames(names: ObjectInterface<any>, stack: string[]) {
  let arr: string[][] = [];

  for (const name in names) {
    stack.push(name);

    if (isObject(names[name])) {
      arr = arr.concat(getNames(names[name], stack));
    } else {
      arr.push(stack.slice());
    }
    stack.pop();
  }
  return arr;
}
function updateFrame(names: ObjectInterface<any>, properties: ObjectInterface<any>) {
  for (const name in properties) {
    const value = properties[name];

    if (!isObject(value) || isArray(value) || value instanceof PropertyObject) {
      names[name] = true;
      continue;
    }
    if (!isObject(names[name])) {
      names[name] = {};
    }
    updateFrame(names[name], properties[name]);
  }
}
/**
* a list of objects in chronological order.
*/
class Keyframes {
  public times: number[];
  public items: ObjectInterface<Frame>;
  public names: RoleInterface;
  /**
	 */
  constructor() {
    this.times = [];
    this.items = {};
    this.names = {};
  }
  /**
	* A list of names
	* @return {} names
	* @example
keyframes.getNames(); // [["a"], ["transform", "translate"], ["transform", "scale"]]
	*/
  public getNames(): string[][] {
    const names = this.names;

    return getNames(names, []);
  }
  /**
	* Check if keyframes has propery's name
	* @param - property's time
	* @return {boolean} true: if has property, false: not
	* @example
keyframes.hasName("transform", "translate"); // true or not
	*/
  public hasName(...args: string[]) {
    return isInProperties(this.names, args, true);
  }
  /**
	 * update property names used in frames.
	 * @return {Keyframes} An instance itself
	 */
  public update() {
    const items = this.items;

    for (const time in items) {
      this.updateFrame(items[time]);
    }
    return this;
  }
  /**
	 * executes a provided function once for each scene item.
	 * @param - Function to execute for each element, taking three arguments
	 * @return {Keyframes} An instance itself
	 */
  public forEach(callback: (item: any, time: number, items: ObjectInterface<any>) => void) {
    const times = this.times;
    const items = this.items;

    times.forEach(time => {
      callback(items[time], time, items);
    });
    return this;
  }
  /**
	* update property names used in frame.
	* @param {Frame} [frame] - frame of that time.
	* @return {Keyframes} An instance itself
	* @example
keyframes.updateFrame(frame);
	*/
  public updateFrame(frame: Frame) {
    if (!frame) {
      return this;
    }
    const properties = frame.properties;
    const names = this.names;

    updateFrame(names, properties);
    return this;
  }
  /**
	 * Get how long an animation should take to complete one cycle.
	 * @return {number} duration
	 */
  public getDuration() {
    const times = this.times;

    return times.length === 0 ? 0 : times[times.length - 1];
  }
  /**
	 * Set how long an animation should take to complete one cycle.
	 * @param {number} duration - duration
	 * @return {Keyframes} An instance itself.
	 */
  public setDuration(duration: number, originalDuration: number = this.getDuration()) {
    const ratio = duration / originalDuration;
    const { times, items } = this;
    const obj: ObjectInterface<any> = {};

    this.times = times.map(time => {
      const time2 = toFixed(time * ratio);

      obj[time2] = items[time];

      return time2;
    });
    this.items = obj;
  }
  /**
	 * Set how much time you want to push ahead.
	 * @param {number} time - time
	 * @return {Keyframes} An instance itself.
	 */
  public unshift(time: number) {
    const { times, items } = this;
    const obj: ObjectInterface<any> = {};

    this.times = times.map(t => {
      const time2 = toFixed(time + t);

      obj[time2] = items[t];
      return time2;
    });
    this.items = obj;
    return this;
  }
  /**
	* get size of list
	* @return {Number} length of list
	*/
  public size() {
    return this.times.length;
  }
  /**
	* add object in list
	* @param {number} time - frame's time
	* @param {any} object - target
	* @return {Keyframes} An instance itself
	*/
  public add(time: number, object: any) {
    this.items[time] = object;
    this.addTime(time);
    return this;
  }
  /**
	* Check if keyframes has object at that time.
	* @param {number} time - object's time
	* @return {boolean} true: if has time, false: not
	*/
  public has(time: number) {
    return time in this.items;
  }
  /**
	* get object at that time.
	* @param {number} time - object's time
	* @return {object} object at that time
	*/
  public get(time: number) {
    return this.items[time];
  }
  /**
	* remove object at that time.
	* @param {number} time - object's time
	* @return {Keyframes} An instance itself
	*/
  public remove(time: number) {
    const items = this.items;

    delete items[time];
    this.removeTime(time);
    return this;
  }
  private addTime(time: number) {
    const times = this.times;
    const length = times.length;
    let pushIndex = length;

    for (let i = 0; i < length; ++i) {
      // if time is smaller than times[i], add time to index
      if (time === times[i]) {
        return this;
      } else if (time < times[i]) {
        pushIndex = i;
        break;
      }
    }
    this.times.splice(pushIndex, 0, time);
    return this;
  }
  private removeTime(time: number) {
    const index = this.times.indexOf(time);

    if (index > -1) {
      this.times.splice(index, 1);
    }
    return this;
  }
}
export default Keyframes;