fix(animations): ensure animations work with web-workers (#12656)
This commit is contained in:
parent
7cab30f85d
commit
19e869e7c9
|
@ -100,6 +100,7 @@ export class AnimationSequencePlayer implements AnimationPlayer {
|
||||||
this._onFinish();
|
this._onFinish();
|
||||||
this._players.forEach(player => player.destroy());
|
this._players.forEach(player => player.destroy());
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
this._activePlayer = new NoOpAnimationPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,7 @@ import * as dom_events from './dom/events/dom_events';
|
||||||
import * as hammer_gesture from './dom/events/hammer_gestures';
|
import * as hammer_gesture from './dom/events/hammer_gestures';
|
||||||
import * as key_events from './dom/events/key_events';
|
import * as key_events from './dom/events/key_events';
|
||||||
import * as shared_styles_host from './dom/shared_styles_host';
|
import * as shared_styles_host from './dom/shared_styles_host';
|
||||||
|
import {WebAnimationsDriver} from './dom/web_animations_driver';
|
||||||
|
|
||||||
|
|
||||||
export var __platform_browser_private__: {
|
export var __platform_browser_private__: {
|
||||||
_BrowserPlatformLocation?: location.BrowserPlatformLocation,
|
_BrowserPlatformLocation?: location.BrowserPlatformLocation,
|
||||||
|
@ -54,7 +53,8 @@ export var __platform_browser_private__: {
|
||||||
HammerGesturesPlugin: typeof hammer_gesture.HammerGesturesPlugin,
|
HammerGesturesPlugin: typeof hammer_gesture.HammerGesturesPlugin,
|
||||||
initDomAdapter: typeof browser.initDomAdapter,
|
initDomAdapter: typeof browser.initDomAdapter,
|
||||||
INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof browser.INTERNAL_BROWSER_PLATFORM_PROVIDERS,
|
INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof browser.INTERNAL_BROWSER_PLATFORM_PROVIDERS,
|
||||||
BROWSER_SANITIZATION_PROVIDERS: typeof browser.BROWSER_SANITIZATION_PROVIDERS
|
BROWSER_SANITIZATION_PROVIDERS: typeof browser.BROWSER_SANITIZATION_PROVIDERS,
|
||||||
|
WebAnimationsDriver: typeof WebAnimationsDriver
|
||||||
} = {
|
} = {
|
||||||
BrowserPlatformLocation: location.BrowserPlatformLocation,
|
BrowserPlatformLocation: location.BrowserPlatformLocation,
|
||||||
DomAdapter: dom_adapter.DomAdapter,
|
DomAdapter: dom_adapter.DomAdapter,
|
||||||
|
@ -78,5 +78,6 @@ export var __platform_browser_private__: {
|
||||||
HammerGesturesPlugin: hammer_gesture.HammerGesturesPlugin,
|
HammerGesturesPlugin: hammer_gesture.HammerGesturesPlugin,
|
||||||
initDomAdapter: browser.initDomAdapter,
|
initDomAdapter: browser.initDomAdapter,
|
||||||
INTERNAL_BROWSER_PLATFORM_PROVIDERS: browser.INTERNAL_BROWSER_PLATFORM_PROVIDERS,
|
INTERNAL_BROWSER_PLATFORM_PROVIDERS: browser.INTERNAL_BROWSER_PLATFORM_PROVIDERS,
|
||||||
BROWSER_SANITIZATION_PROVIDERS: browser.BROWSER_SANITIZATION_PROVIDERS
|
BROWSER_SANITIZATION_PROVIDERS: browser.BROWSER_SANITIZATION_PROVIDERS,
|
||||||
|
WebAnimationsDriver: WebAnimationsDriver
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,3 +24,4 @@ export const KeyEventsPlugin: typeof _.KeyEventsPlugin = _.KeyEventsPlugin;
|
||||||
export const HammerGesturesPlugin: typeof _.HammerGesturesPlugin = _.HammerGesturesPlugin;
|
export const HammerGesturesPlugin: typeof _.HammerGesturesPlugin = _.HammerGesturesPlugin;
|
||||||
export const DomAdapter: typeof _.DomAdapter = _.DomAdapter;
|
export const DomAdapter: typeof _.DomAdapter = _.DomAdapter;
|
||||||
export const setRootDomAdapter: typeof _.setRootDomAdapter = _.setRootDomAdapter;
|
export const setRootDomAdapter: typeof _.setRootDomAdapter = _.setRootDomAdapter;
|
||||||
|
export const WebAnimationsDriver: typeof _.WebAnimationsDriver = _.WebAnimationsDriver;
|
||||||
|
|
|
@ -13,8 +13,6 @@ import {isPresent} from '../../facade/lang';
|
||||||
import {RenderStore} from './render_store';
|
import {RenderStore} from './render_store';
|
||||||
import {LocationType} from './serialized_types';
|
import {LocationType} from './serialized_types';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// PRIMITIVE is any type that does not need to be serialized (string, number, boolean)
|
// PRIMITIVE is any type that does not need to be serialized (string, number, boolean)
|
||||||
// We set it to String so that it is considered a Type.
|
// We set it to String so that it is considered a Type.
|
||||||
/**
|
/**
|
||||||
|
@ -121,5 +119,6 @@ export class Serializer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ANIMATION_WORKER_PLAYER_PREFIX = 'AnimationPlayer.';
|
||||||
|
|
||||||
export class RenderStoreObject {}
|
export class RenderStoreObject {}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {EventEmitter} from '../../facade/async';
|
import {EventEmitter} from '../../facade/async';
|
||||||
import {RenderStoreObject, Serializer} from '../shared/serializer';
|
import {RenderStoreObject, Serializer} from '../shared/serializer';
|
||||||
|
|
||||||
|
@ -15,6 +13,15 @@ import {serializeEventWithTarget, serializeGenericEvent, serializeKeyboardEvent,
|
||||||
export class EventDispatcher {
|
export class EventDispatcher {
|
||||||
constructor(private _sink: EventEmitter<any>, private _serializer: Serializer) {}
|
constructor(private _sink: EventEmitter<any>, private _serializer: Serializer) {}
|
||||||
|
|
||||||
|
dispatchAnimationEvent(player: any, phaseName: string, element: any): boolean {
|
||||||
|
this._sink.emit({
|
||||||
|
'element': this._serializer.serialize(element, RenderStoreObject),
|
||||||
|
'animationPlayer': this._serializer.serialize(player, RenderStoreObject),
|
||||||
|
'phaseName': phaseName
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
dispatchRenderEvent(element: any, eventTarget: string, eventName: string, event: any): boolean {
|
dispatchRenderEvent(element: any, eventTarget: string, eventName: string, event: any): boolean {
|
||||||
var serializedEvent: any /** TODO #9100 */;
|
var serializedEvent: any /** TODO #9100 */;
|
||||||
// TODO (jteplitz602): support custom events #3350
|
// TODO (jteplitz602): support custom events #3350
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
|
import {AnimationPlayer, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
|
||||||
|
|
||||||
import {MessageBus} from '../shared/message_bus';
|
import {MessageBus} from '../shared/message_bus';
|
||||||
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
|
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
|
||||||
import {RenderStore} from '../shared/render_store';
|
import {RenderStore} from '../shared/render_store';
|
||||||
import {PRIMITIVE, RenderStoreObject, Serializer} from '../shared/serializer';
|
import {ANIMATION_WORKER_PLAYER_PREFIX, PRIMITIVE, RenderStoreObject, Serializer} from '../shared/serializer';
|
||||||
import {ServiceMessageBrokerFactory} from '../shared/service_message_broker';
|
import {ServiceMessageBroker, ServiceMessageBrokerFactory} from '../shared/service_message_broker';
|
||||||
import {EventDispatcher} from '../ui/event_dispatcher';
|
import {EventDispatcher} from '../ui/event_dispatcher';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -86,6 +85,65 @@ export class MessageBasedRenderer {
|
||||||
this._listenGlobal.bind(this));
|
this._listenGlobal.bind(this));
|
||||||
broker.registerMethod(
|
broker.registerMethod(
|
||||||
'listenDone', [RenderStoreObject, RenderStoreObject], this._listenDone.bind(this));
|
'listenDone', [RenderStoreObject, RenderStoreObject], this._listenDone.bind(this));
|
||||||
|
broker.registerMethod(
|
||||||
|
'animate',
|
||||||
|
[
|
||||||
|
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
|
||||||
|
PRIMITIVE, PRIMITIVE
|
||||||
|
],
|
||||||
|
this._animate.bind(this));
|
||||||
|
|
||||||
|
this._bindAnimationPlayerMethods(broker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _bindAnimationPlayerMethods(broker: ServiceMessageBroker) {
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'play', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => player.play());
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'pause', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => player.pause());
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'init', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => player.init());
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'restart', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => player.restart());
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'destroy', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => {
|
||||||
|
player.destroy();
|
||||||
|
this._renderStore.remove(player);
|
||||||
|
});
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'finish', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => player.finish());
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'getPosition', [RenderStoreObject, RenderStoreObject],
|
||||||
|
(player: AnimationPlayer, element: any) => player.getPosition());
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'onStart',
|
||||||
|
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
|
||||||
|
(player: AnimationPlayer, element: any) =>
|
||||||
|
this._listenOnAnimationPlayer(player, element, 'onStart'));
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'onDone',
|
||||||
|
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
|
||||||
|
(player: AnimationPlayer, element: any) =>
|
||||||
|
this._listenOnAnimationPlayer(player, element, 'onDone'));
|
||||||
|
|
||||||
|
broker.registerMethod(
|
||||||
|
ANIMATION_WORKER_PLAYER_PREFIX + 'setPosition',
|
||||||
|
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
|
||||||
|
(player: AnimationPlayer, element: any, position: number) => player.setPosition(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderComponent(renderComponentType: RenderComponentType, rendererId: number) {
|
private _renderComponent(renderComponentType: RenderComponentType, rendererId: number) {
|
||||||
|
@ -187,4 +245,24 @@ export class MessageBasedRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _listenDone(renderer: Renderer, unlistenCallback: Function) { unlistenCallback(); }
|
private _listenDone(renderer: Renderer, unlistenCallback: Function) { unlistenCallback(); }
|
||||||
|
|
||||||
|
private _animate(
|
||||||
|
renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number,
|
||||||
|
delay: number, easing: string, playerId: any) {
|
||||||
|
var player = renderer.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||||
|
this._renderStore.store(player, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _listenOnAnimationPlayer(player: AnimationPlayer, element: any, phaseName: string) {
|
||||||
|
const onEventComplete =
|
||||||
|
() => { this._eventDispatcher.dispatchAnimationEvent(player, phaseName, element); };
|
||||||
|
|
||||||
|
// there is no need to register a unlistener value here since the
|
||||||
|
// internal player callbacks are removed when the player is destroyed
|
||||||
|
if (phaseName == 'onDone') {
|
||||||
|
player.onDone(() => onEventComplete());
|
||||||
|
} else {
|
||||||
|
player.onStart(() => onEventComplete());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// no deserialization is necessary in TS.
|
// no deserialization is necessary in TS.
|
||||||
// This is only here to match dart interface
|
// This is only here to match dart interface
|
||||||
export function deserializeGenericEvent(serializedEvent: {[key: string]: any}):
|
export function deserializeGenericEvent(serializedEvent: {[key: string]: any}):
|
||||||
|
|
|
@ -15,8 +15,7 @@ import {ClientMessageBrokerFactory, FnArg, UiArguments} from '../shared/client_m
|
||||||
import {MessageBus} from '../shared/message_bus';
|
import {MessageBus} from '../shared/message_bus';
|
||||||
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
|
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
|
||||||
import {RenderStore} from '../shared/render_store';
|
import {RenderStore} from '../shared/render_store';
|
||||||
import {RenderStoreObject, Serializer} from '../shared/serializer';
|
import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
|
||||||
|
|
||||||
import {deserializeGenericEvent} from './event_deserializer';
|
import {deserializeGenericEvent} from './event_deserializer';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -28,7 +27,7 @@ export class WebWorkerRootRenderer implements RootRenderer {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
messageBrokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
|
messageBrokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
|
||||||
private _serializer: Serializer, private _renderStore: RenderStore) {
|
private _serializer: Serializer, public renderStore: RenderStore) {
|
||||||
this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_CHANNEL);
|
this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_CHANNEL);
|
||||||
bus.initChannel(EVENT_CHANNEL);
|
bus.initChannel(EVENT_CHANNEL);
|
||||||
var source = bus.from(EVENT_CHANNEL);
|
var source = bus.from(EVENT_CHANNEL);
|
||||||
|
@ -36,25 +35,32 @@ export class WebWorkerRootRenderer implements RootRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dispatchEvent(message: {[key: string]: any}): void {
|
private _dispatchEvent(message: {[key: string]: any}): void {
|
||||||
|
var element =
|
||||||
|
<WebWorkerRenderNode>this._serializer.deserialize(message['element'], RenderStoreObject);
|
||||||
|
var playerData = message['animationPlayer'];
|
||||||
|
if (playerData) {
|
||||||
|
var phaseName = message['phaseName'];
|
||||||
|
var player = <AnimationPlayer>this._serializer.deserialize(playerData, RenderStoreObject);
|
||||||
|
element.animationPlayerEvents.dispatchEvent(player, phaseName);
|
||||||
|
} else {
|
||||||
var eventName = message['eventName'];
|
var eventName = message['eventName'];
|
||||||
var target = message['eventTarget'];
|
var target = message['eventTarget'];
|
||||||
var event = deserializeGenericEvent(message['event']);
|
var event = deserializeGenericEvent(message['event']);
|
||||||
if (isPresent(target)) {
|
if (isPresent(target)) {
|
||||||
this.globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
|
this.globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
|
||||||
} else {
|
} else {
|
||||||
var element =
|
|
||||||
<WebWorkerRenderNode>this._serializer.deserialize(message['element'], RenderStoreObject);
|
|
||||||
element.events.dispatchEvent(eventName, event);
|
element.events.dispatchEvent(eventName, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderComponent(componentType: RenderComponentType): Renderer {
|
renderComponent(componentType: RenderComponentType): Renderer {
|
||||||
var result = this._componentRenderers.get(componentType.id);
|
var result = this._componentRenderers.get(componentType.id);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = new WebWorkerRenderer(this, componentType);
|
result = new WebWorkerRenderer(this, componentType);
|
||||||
this._componentRenderers.set(componentType.id, result);
|
this._componentRenderers.set(componentType.id, result);
|
||||||
var id = this._renderStore.allocateId();
|
var id = this.renderStore.allocateId();
|
||||||
this._renderStore.store(result, id);
|
this.renderStore.store(result, id);
|
||||||
this.runOnService('renderComponent', [
|
this.runOnService('renderComponent', [
|
||||||
new FnArg(componentType, RenderComponentType),
|
new FnArg(componentType, RenderComponentType),
|
||||||
new FnArg(result, RenderStoreObject),
|
new FnArg(result, RenderStoreObject),
|
||||||
|
@ -70,16 +76,16 @@ export class WebWorkerRootRenderer implements RootRenderer {
|
||||||
|
|
||||||
allocateNode(): WebWorkerRenderNode {
|
allocateNode(): WebWorkerRenderNode {
|
||||||
var result = new WebWorkerRenderNode();
|
var result = new WebWorkerRenderNode();
|
||||||
var id = this._renderStore.allocateId();
|
var id = this.renderStore.allocateId();
|
||||||
this._renderStore.store(result, id);
|
this.renderStore.store(result, id);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
allocateId(): number { return this._renderStore.allocateId(); }
|
allocateId(): number { return this.renderStore.allocateId(); }
|
||||||
|
|
||||||
destroyNodes(nodes: any[]) {
|
destroyNodes(nodes: any[]) {
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
this._renderStore.remove(nodes[i]);
|
this.renderStore.remove(nodes[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,10 +238,20 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
animate(
|
animate(
|
||||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||||
// TODO
|
const playerId = this._rootRenderer.allocateId();
|
||||||
return null;
|
|
||||||
|
this._runOnService('animate', [
|
||||||
|
new FnArg(renderElement, RenderStoreObject), new FnArg(startingStyles, null),
|
||||||
|
new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null),
|
||||||
|
new FnArg(easing, null), new FnArg(playerId, null)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement);
|
||||||
|
this._rootRenderer.renderStore.store(player, playerId);
|
||||||
|
|
||||||
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,8 +284,101 @@ export class NamedEventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AnimationPlayerEmitter {
|
||||||
|
private _listeners: Map<AnimationPlayer, {[phaseName: string]: Function[]}>;
|
||||||
|
|
||||||
|
private _getListeners(player: AnimationPlayer, phaseName: string): Function[] {
|
||||||
|
if (!this._listeners) {
|
||||||
|
this._listeners = new Map<AnimationPlayer, {[phaseName: string]: Function[]}>();
|
||||||
|
}
|
||||||
|
var phaseMap = this._listeners.get(player);
|
||||||
|
if (!phaseMap) {
|
||||||
|
this._listeners.set(player, phaseMap = {});
|
||||||
|
}
|
||||||
|
var phaseFns = phaseMap[phaseName];
|
||||||
|
if (!phaseFns) {
|
||||||
|
phaseFns = phaseMap[phaseName] = [];
|
||||||
|
}
|
||||||
|
return phaseFns;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(player: AnimationPlayer, phaseName: string, callback: Function) {
|
||||||
|
this._getListeners(player, phaseName).push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlisten(player: AnimationPlayer) { this._listeners.delete(player); }
|
||||||
|
|
||||||
|
dispatchEvent(player: AnimationPlayer, phaseName: string) {
|
||||||
|
var listeners = this._getListeners(player, phaseName);
|
||||||
|
for (var i = 0; i < listeners.length; i++) {
|
||||||
|
listeners[i]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function eventNameWithTarget(target: string, eventName: string): string {
|
function eventNameWithTarget(target: string, eventName: string): string {
|
||||||
return `${target}:${eventName}`;
|
return `${target}:${eventName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebWorkerRenderNode { events: NamedEventEmitter = new NamedEventEmitter(); }
|
export class WebWorkerRenderNode {
|
||||||
|
events = new NamedEventEmitter();
|
||||||
|
animationPlayerEvents = new AnimationPlayerEmitter();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject {
|
||||||
|
public parentPlayer: AnimationPlayer = null;
|
||||||
|
|
||||||
|
private _destroyed: boolean = false;
|
||||||
|
private _started: boolean = false;
|
||||||
|
|
||||||
|
constructor(private _rootRenderer: WebWorkerRootRenderer, private _renderElement: any) {}
|
||||||
|
|
||||||
|
private _runOnService(fnName: string, fnArgs: FnArg[]) {
|
||||||
|
if (!this._destroyed) {
|
||||||
|
var fnArgsWithRenderer = [
|
||||||
|
new FnArg(this, RenderStoreObject), new FnArg(this._renderElement, RenderStoreObject)
|
||||||
|
].concat(fnArgs);
|
||||||
|
this._rootRenderer.runOnService(ANIMATION_WORKER_PLAYER_PREFIX + fnName, fnArgsWithRenderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(fn: () => void): void {
|
||||||
|
this._renderElement.animationPlayerEvents.listen(this, 'onStart', fn);
|
||||||
|
this._runOnService('onStart', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDone(fn: () => void): void {
|
||||||
|
this._renderElement.animationPlayerEvents.listen(this, 'onDone', fn);
|
||||||
|
this._runOnService('onDone', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasStarted(): boolean { return this._started; }
|
||||||
|
|
||||||
|
init(): void { this._runOnService('init', []); }
|
||||||
|
|
||||||
|
play(): void {
|
||||||
|
this._started = true;
|
||||||
|
this._runOnService('play', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
pause(): void { this._runOnService('pause', []); }
|
||||||
|
|
||||||
|
restart(): void { this._runOnService('restart', []); }
|
||||||
|
|
||||||
|
finish(): void { this._runOnService('finish', []); }
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (!this._destroyed) {
|
||||||
|
this._renderElement.animationPlayerEvents.unlisten(this);
|
||||||
|
this._runOnService('destroy', []);
|
||||||
|
this._rootRenderer.renderStore.remove(this);
|
||||||
|
this._destroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void { this._runOnService('reset', []); }
|
||||||
|
|
||||||
|
setPosition(p: number): void { this._runOnService('setPosition', [new FnArg(p, null)]); }
|
||||||
|
|
||||||
|
getPosition(): number { return 0; }
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {ErrorHandler, Injectable, Injector, NgZone, OpaqueToken, PLATFORM_INITIA
|
||||||
import {AnimationDriver, DOCUMENT, EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from '@angular/platform-browser';
|
import {AnimationDriver, DOCUMENT, EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {APP_ID_RANDOM_PROVIDER} from './private_import_core';
|
import {APP_ID_RANDOM_PROVIDER} from './private_import_core';
|
||||||
import {BROWSER_SANITIZATION_PROVIDERS, BrowserDomAdapter, BrowserGetTestability, DomEventsPlugin, DomRootRenderer, DomRootRenderer_, DomSharedStylesHost, HammerGesturesPlugin, KeyEventsPlugin, SharedStylesHost, getDOM} from './private_import_platform-browser';
|
import {BROWSER_SANITIZATION_PROVIDERS, BrowserDomAdapter, BrowserGetTestability, DomEventsPlugin, DomRootRenderer, DomRootRenderer_, DomSharedStylesHost, HammerGesturesPlugin, KeyEventsPlugin, SharedStylesHost, WebAnimationsDriver, getDOM} from './private_import_platform-browser';
|
||||||
import {ON_WEB_WORKER} from './web_workers/shared/api';
|
import {ON_WEB_WORKER} from './web_workers/shared/api';
|
||||||
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from './web_workers/shared/client_message_broker';
|
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from './web_workers/shared/client_message_broker';
|
||||||
import {MessageBus} from './web_workers/shared/message_bus';
|
import {MessageBus} from './web_workers/shared/message_bus';
|
||||||
|
@ -21,7 +21,6 @@ import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_w
|
||||||
import {MessageBasedRenderer} from './web_workers/ui/renderer';
|
import {MessageBasedRenderer} from './web_workers/ui/renderer';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper class that exposes the Worker
|
* Wrapper class that exposes the Worker
|
||||||
* and underlying {@link MessageBus} for lower level message passing.
|
* and underlying {@link MessageBus} for lower level message passing.
|
||||||
|
@ -155,7 +154,8 @@ function spawnWebWorker(uri: string, instance: WebWorkerInstance): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||||
// web workers have not been tested or configured to
|
if (getDOM().supportsWebAnimation()) {
|
||||||
// work with animations just yet...
|
return new WebAnimationsDriver();
|
||||||
|
}
|
||||||
return AnimationDriver.NOOP;
|
return AnimationDriver.NOOP;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,348 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {AUTO_STYLE, AnimationTransitionEvent, Component, Injector, animate, state, style, transition, trigger} from '@angular/core';
|
||||||
|
import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer';
|
||||||
|
import {RootRenderer} from '@angular/core/src/render/api';
|
||||||
|
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||||
|
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
|
||||||
|
import {AnimationDriver} from '@angular/platform-browser/src/dom/animation_driver';
|
||||||
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
import {DomRootRenderer, DomRootRenderer_} from '@angular/platform-browser/src/dom/dom_renderer';
|
||||||
|
import {BrowserTestingModule} from '@angular/platform-browser/testing';
|
||||||
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver';
|
||||||
|
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
|
||||||
|
import {RenderStore} from '@angular/platform-webworker/src/web_workers/shared/render_store';
|
||||||
|
import {Serializer} from '@angular/platform-webworker/src/web_workers/shared/serializer';
|
||||||
|
import {ServiceMessageBrokerFactory_} from '@angular/platform-webworker/src/web_workers/shared/service_message_broker';
|
||||||
|
import {MessageBasedRenderer} from '@angular/platform-webworker/src/web_workers/ui/renderer';
|
||||||
|
import {WebWorkerRootRenderer} from '@angular/platform-webworker/src/web_workers/worker/renderer';
|
||||||
|
|
||||||
|
import {platformBrowserDynamicTesting} from '../../../../platform-browser-dynamic/testing';
|
||||||
|
import {PairedMessageBuses, createPairedMessageBuses} from '../shared/web_worker_test_util';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
function createWebWorkerBrokerFactory(
|
||||||
|
messageBuses: PairedMessageBuses, workerSerializer: Serializer, uiSerializer: Serializer,
|
||||||
|
domRootRenderer: DomRootRenderer, uiRenderStore: RenderStore): ClientMessageBrokerFactory {
|
||||||
|
var uiMessageBus = messageBuses.ui;
|
||||||
|
var workerMessageBus = messageBuses.worker;
|
||||||
|
|
||||||
|
// set up the worker side
|
||||||
|
var webWorkerBrokerFactory =
|
||||||
|
new ClientMessageBrokerFactory_(workerMessageBus, workerSerializer);
|
||||||
|
|
||||||
|
// set up the ui side
|
||||||
|
var uiMessageBrokerFactory = new ServiceMessageBrokerFactory_(uiMessageBus, uiSerializer);
|
||||||
|
var renderer = new MessageBasedRenderer(
|
||||||
|
uiMessageBrokerFactory, uiMessageBus, uiSerializer, uiRenderStore, domRootRenderer);
|
||||||
|
renderer.start();
|
||||||
|
|
||||||
|
return webWorkerBrokerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkerRenderer(
|
||||||
|
workerSerializer: Serializer, uiSerializer: Serializer, domRootRenderer: DomRootRenderer,
|
||||||
|
uiRenderStore: RenderStore, workerRenderStore: RenderStore): RootRenderer {
|
||||||
|
var messageBuses = createPairedMessageBuses();
|
||||||
|
var brokerFactory = createWebWorkerBrokerFactory(
|
||||||
|
messageBuses, workerSerializer, uiSerializer, domRootRenderer, uiRenderStore);
|
||||||
|
var workerRootRenderer = new WebWorkerRootRenderer(
|
||||||
|
brokerFactory, messageBuses.worker, workerSerializer, workerRenderStore);
|
||||||
|
return new DebugDomRootRenderer(workerRootRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Web Worker Renderer Animations', () => {
|
||||||
|
// Don't run on server...
|
||||||
|
if (!getDOM().supportsDOMEvents()) return;
|
||||||
|
|
||||||
|
var uiTestBed: TestBed;
|
||||||
|
var uiRenderStore: RenderStore;
|
||||||
|
var workerRenderStore: RenderStore;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uiRenderStore = new RenderStore();
|
||||||
|
uiTestBed = new TestBed();
|
||||||
|
uiTestBed.platform = platformBrowserDynamicTesting();
|
||||||
|
uiTestBed.ngModule = BrowserTestingModule;
|
||||||
|
uiTestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{provide: AnimationDriver, useClass: MockAnimationDriver}, Serializer,
|
||||||
|
{provide: RenderStore, useValue: uiRenderStore},
|
||||||
|
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||||
|
{provide: RootRenderer, useExisting: DomRootRenderer}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
var uiSerializer = uiTestBed.get(Serializer);
|
||||||
|
var domRootRenderer = uiTestBed.get(DomRootRenderer);
|
||||||
|
workerRenderStore = new RenderStore();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [AnimationCmp, MultiAnimationCmp],
|
||||||
|
providers: [
|
||||||
|
Serializer, {provide: RenderStore, useValue: workerRenderStore}, {
|
||||||
|
provide: RootRenderer,
|
||||||
|
useFactory: (workerSerializer: Serializer) => {
|
||||||
|
return createWorkerRenderer(
|
||||||
|
workerSerializer, uiSerializer, domRootRenderer, uiRenderStore,
|
||||||
|
workerRenderStore);
|
||||||
|
},
|
||||||
|
deps: [Serializer]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var uiDriver: MockAnimationDriver;
|
||||||
|
beforeEach(() => { uiDriver = uiTestBed.get(AnimationDriver) as MockAnimationDriver; });
|
||||||
|
|
||||||
|
function retrieveFinalAnimationStepStyles(keyframes: any[]) { return keyframes[1][1]; }
|
||||||
|
|
||||||
|
it('should trigger an animation and animate styles', fakeAsync(() => {
|
||||||
|
const fixture = TestBed.createComponent(AnimationCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
cmp.state = 'on';
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
var step1 = uiDriver.log.shift();
|
||||||
|
var step2 = uiDriver.log.shift();
|
||||||
|
|
||||||
|
var step1Styles = retrieveFinalAnimationStepStyles(step1['keyframeLookup']);
|
||||||
|
var step2Styles = retrieveFinalAnimationStepStyles(step2['keyframeLookup']);
|
||||||
|
|
||||||
|
expect(step1Styles).toEqual({fontSize: '20px'});
|
||||||
|
expect(step2Styles).toEqual({opacity: '1', fontSize: '50px'});
|
||||||
|
|
||||||
|
cmp.state = 'off';
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
var step3 = uiDriver.log.shift();
|
||||||
|
var step3Styles = retrieveFinalAnimationStepStyles(step3['keyframeLookup']);
|
||||||
|
|
||||||
|
expect(step3Styles).toEqual({opacity: '0', fontSize: AUTO_STYLE});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire the onStart callback when the animation starts', fakeAsync(() => {
|
||||||
|
const fixture = TestBed.createComponent(AnimationCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
var capturedEvent: AnimationTransitionEvent = null;
|
||||||
|
cmp.stateStartFn = event => { capturedEvent = event; };
|
||||||
|
|
||||||
|
cmp.state = 'on';
|
||||||
|
|
||||||
|
expect(capturedEvent).toBe(null);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
expect(capturedEvent instanceof AnimationTransitionEvent).toBe(true);
|
||||||
|
|
||||||
|
expect(capturedEvent.toState).toBe('on');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire the onDone callback when the animation ends', fakeAsync(() => {
|
||||||
|
const fixture = TestBed.createComponent(AnimationCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
var capturedEvent: AnimationTransitionEvent = null;
|
||||||
|
cmp.stateDoneFn = event => { capturedEvent = event; };
|
||||||
|
|
||||||
|
cmp.state = 'off';
|
||||||
|
|
||||||
|
expect(capturedEvent).toBe(null);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
expect(capturedEvent).toBe(null);
|
||||||
|
|
||||||
|
const step = uiDriver.log.shift();
|
||||||
|
step['player'].finish();
|
||||||
|
|
||||||
|
expect(capturedEvent instanceof AnimationTransitionEvent).toBe(true);
|
||||||
|
|
||||||
|
expect(capturedEvent.toState).toBe('off');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should handle multiple animations on the same element that contain refs to .start and .done callbacks',
|
||||||
|
fakeAsync(() => {
|
||||||
|
const fixture = TestBed.createComponent(MultiAnimationCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
let log: {[triggerName: string]: AnimationTransitionEvent[]} = {};
|
||||||
|
cmp.callback = (triggerName: string, event: AnimationTransitionEvent) => {
|
||||||
|
log[triggerName] = log[triggerName] || [];
|
||||||
|
log[triggerName].push(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
cmp.oneTriggerState = 'a';
|
||||||
|
cmp.twoTriggerState = 'c';
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
// clear any animation logs that were collected when
|
||||||
|
// the component was rendered (void => *)
|
||||||
|
log = {};
|
||||||
|
|
||||||
|
cmp.oneTriggerState = 'b';
|
||||||
|
cmp.twoTriggerState = 'd';
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
uiDriver.log.shift()['player'].finish();
|
||||||
|
const [triggerOneStart, triggerOneDone] = log['one'];
|
||||||
|
expect(triggerOneStart)
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'a', toState: 'b', totalTime: 500, phaseName: 'start'}));
|
||||||
|
|
||||||
|
expect(triggerOneDone)
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'a', toState: 'b', totalTime: 500, phaseName: 'done'}));
|
||||||
|
|
||||||
|
uiDriver.log.shift()['player'].finish();
|
||||||
|
const [triggerTwoStart, triggerTwoDone] = log['two'];
|
||||||
|
expect(triggerTwoStart)
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'c', toState: 'd', totalTime: 1000, phaseName: 'start'}));
|
||||||
|
|
||||||
|
expect(triggerTwoDone)
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'c', toState: 'd', totalTime: 1000, phaseName: 'done'}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should handle .start and .done callbacks for mutliple elements that contain animations that are fired at the same time',
|
||||||
|
fakeAsync(() => {
|
||||||
|
function logFactory(
|
||||||
|
log: {[phaseName: string]: AnimationTransitionEvent},
|
||||||
|
phaseName: string): (event: AnimationTransitionEvent) => any {
|
||||||
|
return (event: AnimationTransitionEvent) => { log[phaseName] = event; };
|
||||||
|
}
|
||||||
|
|
||||||
|
const f1 = TestBed.createComponent(AnimationCmp);
|
||||||
|
const f2 = TestBed.createComponent(AnimationCmp);
|
||||||
|
const cmp1 = f1.componentInstance;
|
||||||
|
const cmp2 = f2.componentInstance;
|
||||||
|
|
||||||
|
var cmp1Log: {[phaseName: string]: AnimationTransitionEvent} = {};
|
||||||
|
var cmp2Log: {[phaseName: string]: AnimationTransitionEvent} = {};
|
||||||
|
|
||||||
|
cmp1.stateStartFn = logFactory(cmp1Log, 'start');
|
||||||
|
cmp1.stateDoneFn = logFactory(cmp1Log, 'done');
|
||||||
|
cmp2.stateStartFn = logFactory(cmp2Log, 'start');
|
||||||
|
cmp2.stateDoneFn = logFactory(cmp2Log, 'done');
|
||||||
|
|
||||||
|
cmp1.state = 'off';
|
||||||
|
cmp2.state = 'on';
|
||||||
|
f1.detectChanges();
|
||||||
|
f2.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
uiDriver.log.shift()['player'].finish();
|
||||||
|
|
||||||
|
expect(cmp1Log['start'])
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'void', toState: 'off', totalTime: 500, phaseName: 'start'}));
|
||||||
|
|
||||||
|
expect(cmp1Log['done'])
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'void', toState: 'off', totalTime: 500, phaseName: 'done'}));
|
||||||
|
|
||||||
|
// the * => on transition has two steps
|
||||||
|
uiDriver.log.shift()['player'].finish();
|
||||||
|
uiDriver.log.shift()['player'].finish();
|
||||||
|
|
||||||
|
expect(cmp2Log['start'])
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'void', toState: 'on', totalTime: 1000, phaseName: 'start'}));
|
||||||
|
|
||||||
|
expect(cmp2Log['done'])
|
||||||
|
.toEqual(new AnimationTransitionEvent(
|
||||||
|
{fromState: 'void', toState: 'on', totalTime: 1000, phaseName: 'done'}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should destroy the player when the animation is complete', fakeAsync(() => {
|
||||||
|
const fixture = TestBed.createComponent(AnimationCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
cmp.state = 'off';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
var player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
|
||||||
|
expect(player.log.indexOf('destroy') >= 0).toBe(false);
|
||||||
|
|
||||||
|
cmp.state = 'on';
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
expect(player.log.indexOf('destroy') >= 0).toBe(true);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-comp',
|
||||||
|
template: `
|
||||||
|
<div *ngIf="state"
|
||||||
|
[@myTrigger]="state"
|
||||||
|
(@myTrigger.start)="stateStartFn($event)"
|
||||||
|
(@myTrigger.done)="stateDoneFn($event)">...</div>
|
||||||
|
`,
|
||||||
|
animations: [trigger(
|
||||||
|
'myTrigger',
|
||||||
|
[
|
||||||
|
state('void, off', style({opacity: '0'})),
|
||||||
|
state('on', style({opacity: '1', fontSize: '50px'})),
|
||||||
|
transition('* => on', [animate(500, style({fontSize: '20px'})), animate(500)]),
|
||||||
|
transition('* => off', [animate(500)])
|
||||||
|
])]
|
||||||
|
})
|
||||||
|
class AnimationCmp {
|
||||||
|
state = 'off';
|
||||||
|
stateStartFn = (event: AnimationTransitionEvent): any => {};
|
||||||
|
stateDoneFn = (event: AnimationTransitionEvent): any => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-multi-comp',
|
||||||
|
template: `
|
||||||
|
<div [@one]="oneTriggerState"
|
||||||
|
(@one.start)="callback('one', $event)"
|
||||||
|
(@one.done)="callback('one', $event)">...</div>
|
||||||
|
<div [@two]="twoTriggerState"
|
||||||
|
(@two.start)="callback('two', $event)"
|
||||||
|
(@two.done)="callback('two', $event)">...</div>
|
||||||
|
`,
|
||||||
|
animations: [
|
||||||
|
trigger(
|
||||||
|
'one',
|
||||||
|
[
|
||||||
|
state('a', style({width: '0px'})), state('b', style({width: '100px'})),
|
||||||
|
transition('a => b', animate(500))
|
||||||
|
]),
|
||||||
|
trigger(
|
||||||
|
'two',
|
||||||
|
[
|
||||||
|
state('c', style({height: '0px'})), state('d', style({height: '100px'})),
|
||||||
|
transition('c => d', animate(1000))
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
class MultiAnimationCmp {
|
||||||
|
oneTriggerState: string;
|
||||||
|
twoTriggerState: string;
|
||||||
|
callback = (triggerName: string, event: AnimationTransitionEvent): any => {};
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {verifyNoBrowserErrors} from 'e2e_util/e2e_util';
|
||||||
|
|
||||||
|
describe('WebWorkers Animations', function() {
|
||||||
|
afterEach(() => {
|
||||||
|
verifyNoBrowserErrors();
|
||||||
|
browser.ignoreSynchronization = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = 'animation-app';
|
||||||
|
const URL = 'all/playground/src/web_workers/animations/index.html';
|
||||||
|
|
||||||
|
it('should bootstrap', () => {
|
||||||
|
// This test can't wait for Angular 2 as Testability is not available when using WebWorker
|
||||||
|
browser.ignoreSynchronization = true;
|
||||||
|
browser.get(URL);
|
||||||
|
|
||||||
|
waitForBootstrap();
|
||||||
|
let elem = element(by.css(selector + ' .box'));
|
||||||
|
expect(elem.getText()).toEqual('...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should animate to open', () => {
|
||||||
|
// This test can't wait for Angular 2 as Testability is not available when using WebWorker
|
||||||
|
browser.ignoreSynchronization = true;
|
||||||
|
browser.get(URL);
|
||||||
|
|
||||||
|
waitForBootstrap();
|
||||||
|
element(by.css(selector + ' button')).click();
|
||||||
|
|
||||||
|
let boxElm = element(by.css(selector + ' .box'));
|
||||||
|
browser.wait(() => boxElm.getSize().then(sizes => sizes['width'] > 750), 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function waitForBootstrap() {
|
||||||
|
browser.wait(protractor.until.elementLocated(by.css(selector + ' .box')), 5000)
|
||||||
|
.then(() => {}, () => {
|
||||||
|
// jasmine will timeout if this gets called too many times
|
||||||
|
console.log('>> unexpected timeout -> browser.refresh()');
|
||||||
|
browser.refresh();
|
||||||
|
waitForBootstrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {WorkerAppModule} from '@angular/platform-webworker';
|
||||||
|
import {platformWorkerAppDynamic} from '@angular/platform-webworker-dynamic';
|
||||||
|
|
||||||
|
import {AnimationCmp} from './index_common';
|
||||||
|
|
||||||
|
@NgModule({imports: [WorkerAppModule], bootstrap: [AnimationCmp], declarations: [AnimationCmp]})
|
||||||
|
class ExampleModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
platformWorkerAppDynamic().bootstrapModule(ExampleModule);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<title>WebWorker Animation Tests</title>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<animation-app>
|
||||||
|
Loading...
|
||||||
|
</animation-app>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/web-animations/2.2.2/web-animations-next-lite.min.js"></script>
|
||||||
|
<script src="../../bootstrap.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {bootstrapWorkerUi} from '@angular/platform-webworker';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
bootstrapWorkerUi('loader.js');
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, animate, state, style, transition, trigger} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'animation-app',
|
||||||
|
styles: [`
|
||||||
|
.box {
|
||||||
|
border:10px solid black;
|
||||||
|
text-align:center;
|
||||||
|
overflow:hidden;
|
||||||
|
background:red;
|
||||||
|
color:white;
|
||||||
|
font-size:100px;
|
||||||
|
line-height:200px;
|
||||||
|
}
|
||||||
|
`],
|
||||||
|
animations: [trigger(
|
||||||
|
'animate',
|
||||||
|
[
|
||||||
|
state('off', style({width: '0px'})), state('on', style({width: '750px'})),
|
||||||
|
transition('off <=> on', animate(500))
|
||||||
|
])],
|
||||||
|
template: `
|
||||||
|
<button (click)="animate=!animate">
|
||||||
|
Start Animation
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="box" [@animate]="animate ? 'on' : 'off'">
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AnimationCmp {
|
||||||
|
animate = false;
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
importScripts(
|
||||||
|
'../../../vendor/core.js', '../../../vendor/zone.js',
|
||||||
|
'../../../vendor/long-stack-trace-zone.js', '../../../vendor/system.src.js',
|
||||||
|
'../../../vendor/Reflect.js');
|
||||||
|
|
||||||
|
|
||||||
|
System.config({
|
||||||
|
baseURL: '/all',
|
||||||
|
|
||||||
|
map: {'rxjs': '/all/playground/vendor/rxjs'},
|
||||||
|
|
||||||
|
packages: {
|
||||||
|
'@angular/core': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/platform-webworker': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/platform-webworker-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'rxjs': {defaultExtension: 'js'},
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultJSExtensions: true
|
||||||
|
});
|
||||||
|
|
||||||
|
System.import('playground/src/web_workers/animations/background_index')
|
||||||
|
.then(
|
||||||
|
function(m) {
|
||||||
|
try {
|
||||||
|
m.main();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(error) { console.error('error loading background', error); });
|
Loading…
Reference in New Issue