Skip to content

Commit

Permalink
UI: Add css animations to SCADA symbol API.
Browse files Browse the repository at this point in the history
  • Loading branch information
ikulikov committed Sep 13, 2024
1 parent f80a62a commit 5be11f4
Showing 1 changed file with 302 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
///

import { ValueType } from '@shared/models/constants';
import { Box, Element, Runner, SVG, Svg, Text, Timeline } from '@svgdotjs/svg.js';
import { Box, Element, Runner, Style, SVG, Svg, Text, Timeline } from '@svgdotjs/svg.js';
import '@svgdotjs/svg.panzoom.js';
import {
DataToValueType,
Expand Down Expand Up @@ -58,6 +58,7 @@ export interface ScadaSymbolApi {
enable: (element: Element | Element[]) => void;
callAction: (event: Event, behaviorId: string, value?: any, observer?: Partial<Observer<void>>) => void;
setValue: (valueId: string, value: any) => void;
cssAnimate: CssScadaSymbolAnimations;
}

export interface ScadaSymbolContext {
Expand Down Expand Up @@ -621,7 +622,8 @@ export class ScadaSymbolObject {
disable: this.disableElement.bind(this),
enable: this.enableElement.bind(this),
callAction: this.callAction.bind(this),
setValue: this.setValue.bind(this)
setValue: this.setValue.bind(this),
cssAnimate: new CssScadaSymbolAnimations(this.svgShape)
},
tags: {},
properties: {},
Expand Down Expand Up @@ -1008,3 +1010,301 @@ export class ScadaSymbolObject {
}
}
}

const scadaSymbolAnimationId = 'scadaSymbolAnimation';

class CssScadaSymbolAnimations {
constructor(private svgShape: Svg) {}

public rotate(element: Element, rotation: number, duration = 1000, loop = false): CssScadaSymbolAnimation {
this.checkOldAnimation(element);
return this.setupAnimation(element,
new RotateCssScadaSymbolAnimation(this.svgShape, element, loop, rotation || 0)).duration(duration);
}

public move(element: Element, deltaX: number, deltaY: number, duration = 1000, loop = false): CssScadaSymbolAnimation {
this.checkOldAnimation(element);
return this.setupAnimation(element,
new MoveCssScadaSymbolAnimation(this.svgShape, element, loop, deltaX, deltaY).duration(duration));
}

public attr(element: Element, attrName: string, value: any, duration = 1000, loop = false): CssScadaSymbolAnimation {
this.checkOldAnimation(element);
return this.setupAnimation(element,
new AttrsCssScadaSymbolAnimation(this.svgShape, element, loop, {[attrName]: value}).duration(duration));
}

public attrs(element: Element, attr: any, duration = 1000, loop = false): CssScadaSymbolAnimation {
this.checkOldAnimation(element);
return this.setupAnimation(element,
new AttrsCssScadaSymbolAnimation(this.svgShape, element, loop, attr).duration(duration));
}

public animation(element: Element): CssScadaSymbolAnimation | undefined {
return element.remember(scadaSymbolAnimationId);
}

private checkOldAnimation(element: Element) {
const previousAnimation: CssScadaSymbolAnimation = element.remember(scadaSymbolAnimationId);
if (previousAnimation) {
previousAnimation.destroy();
}
}

private setupAnimation(element: Element, animation: CssScadaSymbolAnimation): CssScadaSymbolAnimation {
animation.init();
element.remember(scadaSymbolAnimationId, animation);
return animation;
}
}

interface ScadaSymbolAnimationKeyframe {
stop: string;
style: any;
}

abstract class CssScadaSymbolAnimation {

private _animationName: string;
private _animationStyle: Style;

private _running = true;
private _speed = 1;
private _duration = 1000;
private _easing = 'linear';

protected constructor(protected svgShape: Svg,
protected element: Element,
protected loop: boolean) {
}

public init() {
this.prepareAnimation();
}

public start() {
if (!this._running) {
this.updateAnimationStyle('animation-play-state', 'running');
this._running = true;
}
}

public pause() {
if (this._running) {
this.updateAnimationStyle('animation-play-state', 'paused');
this._running = false;
}
}

public running(): boolean {
return this._running;
}

public destroy() {
if (this._animationStyle) {
this._animationStyle.remove();
this.element.removeClass(this._animationName);
this._animationStyle = null;
this._animationName = null;
}
}

public duration(duration: number): CssScadaSymbolAnimation {
this._duration = duration;
this.updateAnimationStyle(this.loop ? 'animation-duration' : 'transition-duration',
Math.round(this._duration / this._speed) + 'ms');
return this;
}

public speed(speed: number): CssScadaSymbolAnimation {
this._speed = speed;
this.updateAnimationStyle(this.loop ? 'animation-duration' : 'transition-duration',
Math.round(this._duration / this._speed) + 'ms');
return this;
}

public easing(easing: string): CssScadaSymbolAnimation {
this._easing = easing;
this.updateAnimationStyle(this.loop ? 'animation-timing-function' : 'transition-timing-function', this._easing);
return this;
}

private prepareAnimation() {
this._animationName = 'animation_' + generateElementId();
this._animationStyle = this.svgShape.style();
let styles: any;
if (this.loop) {
styles = {
'animation-name': this._animationName,
'animation-duration': this._duration + 'ms',
'animation-timing-function': this._easing,
'animation-iteration-count': 'infinite',
'animation-fill-mode': 'forwards',
'animation-play-state': 'running',
...this.animationStyles()
};
} else {
styles = {
'transition-property': this.transitionProperties(),
'transition-duration': this._duration + 'ms',
'transition-timing-function': this._easing,
...this.animationStyles()
};
}
this._animationStyle.rule('.' + this._animationName, styles);

if (this.loop) {
const keyframes = this.animationKeyframes();
let keyframesCss = `\n@keyframes ${this._animationName} {\n`;
for (const keyframe of keyframes) {
let keyframeCss = ` ${keyframe.stop} {\n`;
for (const i of Object.keys(keyframe.style)) {
keyframeCss += ' ' + i + ':' + keyframe.style[i] + ';\n';
}
keyframeCss += ' }\n';
keyframesCss += keyframeCss;
}
keyframesCss += '}';
this._animationStyle.addText(keyframesCss);
}
this.element.addClass(this._animationName);
if (!this.loop) {
this.doTransform();
}
}

private updateAnimationStyle(attrName: string, value: any) {
if (this._animationStyle) {
const styleText = this._animationStyle.node.innerHTML;
const attrValueRegex = new RegExp(`${attrName}:([^;]+);`);
this._animationStyle.node.innerHTML = styleText.replace(attrValueRegex, `${attrName}:${value};`);
}
}

protected animationStyles(): any {
return {};
}

protected abstract animationKeyframes(): ScadaSymbolAnimationKeyframe[];

protected abstract transitionProperties(): string;

protected doTransform() {
}

}

class RotateCssScadaSymbolAnimation extends CssScadaSymbolAnimation {

constructor(protected svgShape: Svg,
protected element: Element,
protected loop: boolean,
private rotation: number) {
super(svgShape, element, loop);
}

protected animationStyles(): any {
return {
'transform-origin': `${this.element.cx()}px ${this.element.cy()}px`
};
}

protected animationKeyframes(): ScadaSymbolAnimationKeyframe[] {
const transform = this.element.transform();
return [
{
stop: '0%',
style: {
transform: `translate(${transform.translateX}px, ${transform.translateY}px) rotate(${transform.rotate}deg)`
}
},
{
stop: '100%',
style: {
transform: `translate(${transform.translateX}px, ${transform.translateY}px) rotate(${this.rotation}deg)`
}
}
];
}

protected transitionProperties(): string {
return 'transform';
}

protected doTransform() {
const transform = this.element.transform();
this.element.attr({transform: `translate(${transform.translateX} ${transform.translateY}) rotate(${this.rotation})`});
}

}

class MoveCssScadaSymbolAnimation extends CssScadaSymbolAnimation {

constructor(protected svgShape: Svg,
protected element: Element,
protected loop: boolean,
private deltaX: number,
private deltaY: number) {
super(svgShape, element, loop);
}

protected animationStyles(): any {
return {
'transform-origin': `${this.element.cx()}px ${this.element.cy()}px`
};
}

protected animationKeyframes(): ScadaSymbolAnimationKeyframe[] {
const transform = this.element.transform();
return [
{
stop: '0%',
style: {
transform: `translate(${transform.translateX}px, ${transform.translateY}px)`
}
},
{
stop: '100%',
style: {
transform: `translate(${transform.translateX+this.deltaX}px, ${transform.translateY+this.deltaY}px)`
}
}
];
}

protected transitionProperties(): string {
return 'transform';
}

protected doTransform() {
this.element.relative(this.deltaX, this.deltaY);
}

}

class AttrsCssScadaSymbolAnimation extends CssScadaSymbolAnimation {

constructor(protected svgShape: Svg,
protected element: Element,
protected loop: boolean,
private attr: any) {
super(svgShape, element, loop);
}

protected animationStyles(): any {
return {};
}

protected animationKeyframes(): ScadaSymbolAnimationKeyframe[] {
return [];
}

protected transitionProperties(): string {
return Object.keys(this.attr).join(' ');
}

protected doTransform() {
this.element.attr(this.attr);
}

}

0 comments on commit 5be11f4

Please sign in to comment.