UNPKG

@foblex/flow-animator

Version:
321 lines (304 loc) 12.1 kB
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