@hippy4tv/vue-native-components
Version:
Hippy Vue Native components for TV
196 lines (188 loc) • 5.35 kB
JavaScript
function registerAnimation(Vue) {
// Constants for animations
const MODULE_NAME = 'AnimationModule';
const DEFAULT_OPTION = {
valueType: undefined,
delay: 0,
startValue: 0,
toValue: 0,
duration: 0,
direction: 'center',
timingFunction: 'linear',
repeatCount: 0,
inputRange: [],
outputRange: [],
};
/**
* Create the standalone animation
*/
function createAnimation(option) {
const {
mode = 'timing',
valueType,
...others
} = option;
const fullOption = {
...DEFAULT_OPTION,
...others,
};
if (valueType !== undefined) {
fullOption.valueType = option.valueType;
}
const animationId = Vue.Native.callNativeWithCallbackId(MODULE_NAME, 'createAnimation', true, mode, fullOption);
return {
animationId,
};
}
/**
* Create the animationSet
*/
function createAnimationSet(children, repeatCount = 0) {
return Vue.Native.callNativeWithCallbackId(MODULE_NAME, 'createAnimationSet', true, {
children,
repeatCount,
});
}
/**
* Generate the styles from animation and animationSet Ids.
*/
function getStyle(actions) {
const style = {};
Object.keys(actions).forEach((key) => {
if (Array.isArray(actions[key])) {
// Process AnimationSet from Array.
const actionSet = actions[key];
const animationSetActions = actionSet.map((a) => {
const action = createAnimation(a);
action.follow = true;
return action;
});
const { repeatCount } = actionSet[actionSet.length - 1];
const animationSetId = createAnimationSet(animationSetActions, repeatCount);
style[key] = {
animationId: animationSetId,
};
} else {
// Process standalone Animation.
const action = actions[key];
const animation = createAnimation(action);
style[key] = {
animationId: animation.animationId,
};
}
});
return style;
}
/**
* Get animationIds from style for start/pause/destroy actions.
*/
function getAnimationIds(style) {
const { transform, ...otherStyles } = style;
let animationIds = Object.keys(otherStyles).map(key => style[key].animationId);
if (Array.isArray(transform) && transform.length > 0) {
const transformIds = Object.keys(transform[0]).map(key => transform[0][key].animationId);
animationIds = [...animationIds, ...transformIds];
}
return animationIds;
}
/**
* Register the animation component.
*/
Vue.component('animation', {
inheritAttrs: false,
props: {
tag: {
type: String,
default: 'div',
},
playing: {
type: Boolean,
default: false,
},
actions: {
type: Object,
required: true,
},
props: Object,
},
beforeMount() {
this.create();
},
mounted() {
const { playing } = this.$props;
if (playing) {
this.start();
}
},
beforeDestroy() {
this.destroy();
},
data() {
return {
style: {},
};
},
methods: {
create() {
const { actions: { transform, ...actions } } = this.$props;
const style = getStyle(actions);
if (transform) {
const transformAnimations = getStyle(transform);
style.transform = Object.keys(transformAnimations).map(key => ({
[key]: transformAnimations[key],
}));
}
// Turn to be true at first startAnimation, and be false again when destroy.
this.$alreadyStarted = false;
// Generated style
this.style = style;
},
start() {
const animationIds = getAnimationIds(this.style);
if (!this.$alreadyStarted) {
this.$alreadyStarted = true;
animationIds.forEach(animationId => Vue.Native.callNative(MODULE_NAME, 'startAnimation', animationId));
} else {
animationIds.forEach(animationId => Vue.Native.callNative(MODULE_NAME, 'resumeAnimation', animationId));
}
},
pause() {
if (!this.$alreadyStarted) {
return;
}
const animationIds = getAnimationIds(this.style);
animationIds.forEach(animationId => Vue.Native.callNative(MODULE_NAME, 'pauseAnimation', animationId));
},
destroy() {
this.$alreadyStarted = false;
const animationIds = getAnimationIds(this.style);
animationIds.forEach(animationId => Vue.Native.callNative(MODULE_NAME, 'destroyAnimation', animationId));
},
},
watch: {
playing(to, from) {
if (!from && to) {
this.start();
} else if (from && !to) {
this.pause();
}
},
actions() {
// FIXME: Should diff the props and use updateAnimation method to update the animation.
// Hard restart the animation is no correct.
const { playing } = this.$props;
this.destroy();
this.create();
if (playing) {
this.$nextTick(() => this.start());
}
},
},
template: `
<component :is="tag" :useAnimation="true" :style="style" v-bind="props">
<slot />
</component>
`,
});
}
export default registerAnimation;