@foblex/flow-animator
Version:
Animation library for @foblex/flow.
321 lines (304 loc) • 12.1 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Inject } from '@angular/core';
import { Observable, from, concatMap, forkJoin, tap, finalize } from 'rxjs';
import { DOCUMENT } from '@angular/common';
import * as i1 from '@angular/animations';
import { style, animate } from '@angular/animations';
class CreateConnectionOverlayHandler {
handle(request) {
const svgNS = "http://www.w3.org/2000/svg";
const result = document.createElementNS(svgNS, 'path');
result.classList.add('f-animation', 'f-animated-connection');
result.setAttribute('d', request.element.getAttribute('d'));
const parentElement = request.element.parentElement;
parentElement.appendChild(result);
return result;
}
}
class CreateConnectionOverlayRequest {
constructor(element) {
this.element = element;
}
}
class AnimateConnectionOverlayHandler {
constructor(animationBuilder) {
this.animationBuilder = animationBuilder;
}
handle(request) {
const overlayElement = new CreateConnectionOverlayHandler().handle(new CreateConnectionOverlayRequest(request.element));
const length = overlayElement.getTotalLength();
overlayElement.style.strokeDasharray = length + ' ' + length;
overlayElement.style.strokeDashoffset = String(length);
const animation = this.animationBuilder.build([
style({ strokeDashoffset: length }),
animate(request.duration + 'ms', style({ strokeDashoffset: 0 }))
]);
const player = animation.create(overlayElement);
return new Observable((observer) => {
player.onDone(() => {
observer.next(overlayElement);
observer.complete();
});
player.play();
});
}
}
class AnimateConnectionOverlayRequest {
constructor(element, duration) {
this.element = element;
this.duration = duration;
}
}
class CreateNodeOverlayHandler {
handle(request) {
const result = document.createElement('div');
result.classList.add('f-animation', 'f-animated-node');
result.style.position = 'absolute';
result.style.width = `${request.element.clientWidth}px`;
result.style.height = '0px';
result.style.top = `0px`;
result.style.left = `0px`;
result.style.zIndex = '1';
request.element.appendChild(result);
return result;
}
}
class CreateNodeOverlayRequest {
constructor(element) {
this.element = element;
}
}
class AnimateNodeOverlayHandler {
constructor(animationBuilder) {
this.animationBuilder = animationBuilder;
}
handle(request) {
const overlayElement = new CreateNodeOverlayHandler().handle(new CreateNodeOverlayRequest(request.element));
const animation = this.animationBuilder.build([
style({ height: '0px' }),
animate(request.duration + 'ms', style({ height: `${request.element.offsetHeight}px` }))
]);
const player = animation.create(overlayElement);
return new Observable((observer) => {
player.onDone(() => {
observer.next(overlayElement);
observer.complete();
});
player.play();
});
}
}
class AnimateNodeOverlayRequest {
constructor(element, duration) {
this.element = element;
this.duration = duration;
}
}
/**
* Handler for processing animation requests for individual elements.
*/
class AnimateElementHandler {
/**
* Creates an instance of AnimateElementHandler.
* @param {AnimationBuilder} animationBuilder - The AnimationBuilder service for creating animations.
*/
constructor(animationBuilder) {
this.animationBuilder = animationBuilder;
}
/**
* Handles the provided animation request and returns an Observable for the animation process.
* @param {AnimateElementRequest} request - The animation request to be handled.
* @return {Observable<HTMLElement | SVGPathElement>} An Observable emitting the animated element.
*/
handle(request) {
let result;
if (request.element instanceof SVGPathElement) {
result = this.animateConnection(request);
}
else {
result = this.animateNode(request);
}
return result;
}
animateConnection(request) {
const result = new AnimateConnectionOverlayHandler(this.animationBuilder).handle(new AnimateConnectionOverlayRequest(request.element, request.duration));
return result;
}
animateNode(request) {
const result = new AnimateNodeOverlayHandler(this.animationBuilder).handle(new AnimateNodeOverlayRequest(request.element, request.duration));
return result;
}
}
/**
* Class representing a request to animate a specific element.
*/
class AnimateElementRequest {
/**
* Creates an instance of AnimateElementRequest.
* @param {HTMLElement | SVGPathElement} element - The element to be animated.
* @param {number} duration - The duration of the animation in milliseconds.
*/
constructor(element, duration) {
this.element = element;
this.duration = duration;
}
}
class GetAllAnimatedElementsRequest {
constructor(flowId, items) {
this.flowId = flowId;
this.items = items;
}
}
class GetAllAnimatedElementsHandler {
constructor(document) {
this.document = document;
this.allNodes = [];
this.allConnections = [];
}
handle(request) {
if (!request.flowId) {
throw new Error('Flow id is required');
}
const flowElement = this.getFlowElement(request.flowId);
this.allNodes = this.getAllNodes(flowElement);
this.allConnections = this.getAllConnections(flowElement);
const result = request.items.map((row) => {
return row.map((item) => {
return {
element: item.isConnection ? this.getConnectionElement(item.id) : this.getNodeElement(item.id),
duration: item.duration
};
});
});
return result;
}
getAllNodes(flowElement) {
const result = Array.from(flowElement.querySelectorAll('[fNode]'));
return result;
}
getAllConnections(flowElement) {
const result = Array.from(flowElement.getElementsByTagName('f-connection'));
return result;
}
getFlowElement(flowId) {
const flowElement = this.document.getElementById(flowId);
if (!flowElement) {
throw new Error(`FFlowComponent with id ${flowId} not found`);
}
return flowElement;
}
getNodeElement(nodeId) {
const nodeElement = this.allNodes.find((x) => x.dataset['fNodeId'] === nodeId);
if (!nodeElement) {
throw new Error(`FNodeDirective with id ${nodeId} not found`);
}
return nodeElement;
}
getConnectionElement(connectionId) {
const connectionElement = this.allConnections.find((connection) => connection.id === connectionId);
if (!connectionElement) {
throw new Error(`FConnectionComponent with id ${connectionId} not found`);
}
const path = connectionElement.querySelector('.f-connection-path');
if (!path) {
throw new Error(`FConnectionComponent with id ${connectionId} has no path element`);
}
return path;
}
}
/**
* Service for animating elements in the @foblex/flow-animator library.
* This service handles the orchestration of animations for elements.
*/
class FFlowAnimatorService {
/**
* Creates an instance of FFlowAnimatorService.
* @param {AnimationBuilder} animationBuilder - The AnimationBuilder service for creating animations.
* @param {Document} document - The DOM Document object to interact with the DOM.
*/
constructor(animationBuilder, document) {
this.animationBuilder = animationBuilder;
this.document = document;
}
/**
* Initiates the animation process for the specified flow.
* @param {any} flowId - The identifier of the flow to be animated.
* @param {IFAnimationConfiguration} configuration - The configuration settings for the animation.
* @return {Observable<IFAnimationResult>} An Observable that emits the result of the animation process.
*/
animate(flowId, configuration) {
return new Observable((observer) => {
setTimeout(() => {
const toAnimate = new GetAllAnimatedElementsHandler(this.document).handle(new GetAllAnimatedElementsRequest(flowId, configuration.items));
this.animateSequentially(toAnimate, configuration, observer);
}, 0);
});
}
/**
* Animates elements sequentially according to the provided configuration.
* @private
* @param {IFElementToAnimate[][]} rows - A two-dimensional array of elements to animate in sequence.
* @param {IFAnimationConfiguration} configuration - The configuration settings for the animation.
* @param {Subscriber<IFAnimationResult>} observer - The observer to emit the results to.
*/
animateSequentially(rows, configuration, observer) {
const singleDuration = configuration.duration / rows.length;
let toRemove = [];
from(rows).pipe(concatMap((row, index) => forkJoin(row.map(x => this.animateElement(x.element, x.duration || singleDuration).pipe(tap((overlayElement) => {
toRemove.push(overlayElement);
})))).pipe(finalize(() => {
if (configuration.removeOverlayAfterRowComplete) {
toRemove = this.removeCreatedElements(toRemove);
}
observer.next({ completeRowIndex: index });
}))), finalize(() => {
if (!configuration.removeOverlayAfterRowComplete) {
toRemove = this.removeCreatedElements(toRemove);
}
observer.next({ completeAll: true });
observer.complete();
})).subscribe();
}
/**
* Animates an individual element.
* @private
* @param {(HTMLElement | SVGPathElement)} element - The element to be animated.
* @param {number} duration - The duration of the animation in milliseconds.
* @return {Observable<HTMLElement | SVGPathElement>} An Observable that emits the animated element.
*/
animateElement(element, duration) {
return new AnimateElementHandler(this.animationBuilder).handle(new AnimateElementRequest(element, duration));
}
/**
* Removes the specified elements from the DOM.
* @private
* @param {(HTMLElement | SVGPathElement)[]} toRemove - The elements to be removed.
* @return {(HTMLElement | SVGPathElement)[]} An array of the removed elements.
*/
removeCreatedElements(toRemove) {
toRemove.forEach(x => x.remove());
return [];
}
}
FFlowAnimatorService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: FFlowAnimatorService, deps: [{ token: i1.AnimationBuilder }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
FFlowAnimatorService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: FFlowAnimatorService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: FFlowAnimatorService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () {
return [{ type: i1.AnimationBuilder }, { type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }];
} });
/*
* Public API Surface of @foblex/flow-animator
*
*/
/**
* Generated bundle index. Do not edit.
*/
export { FFlowAnimatorService };
//# sourceMappingURL=foblex-flow-animator.mjs.map