Переглянути джерело

Lambda functions out of JSX

Alejandro Toledo 5 роки тому
батько
коміт
9040748df1
17 змінених файлів з 1037 додано та 719 видалено
  1. 4 3
      .prettierrc
  2. 23 21
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx
  3. 11 7
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx
  4. 203 55
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx
  5. 43 16
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx
  6. 80 14
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx
  7. 54 24
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx
  8. 91 34
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx
  9. 40 8
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx
  10. 85 85
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx
  11. 102 114
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx
  12. 19 25
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx
  13. 154 163
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx
  14. 34 36
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx
  15. 53 47
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx
  16. 12 14
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx
  17. 29 53
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

+ 4 - 3
.prettierrc

@@ -1,5 +1,6 @@
 {
-  "trailingComma": "es5",
-  "tabWidth": 4,
-  "printWidth": 300
+    "trailingComma": "es5",
+    "tabWidth": 4,
+    "printWidth": 120,
+    "jsxBracketSameLine": true
 }

+ 23 - 21
inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx

@@ -57,7 +57,7 @@ export class AddAnimation extends React.Component<
         }
     }
 
-    updateAnimation() {
+    updateAnimation = () => {
         if (this.props.selectedToUpdate !== undefined) {
             const oldNameValue = this.props.selectedToUpdate.name;
             this.props.selectedToUpdate.name = this.state.animationName;
@@ -73,7 +73,7 @@ export class AddAnimation extends React.Component<
 
             this.props.finishedUpdate();
         }
-    }
+    };
 
     getTypeAsString(type: number) {
         switch (type) {
@@ -96,7 +96,7 @@ export class AddAnimation extends React.Component<
         }
     }
 
-    addAnimation() {
+    addAnimation = () => {
         if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
             let matchTypeTargetProperty = this.state.animationTargetProperty.split(".");
             let animationDataType = this.state.animationType;
@@ -179,7 +179,7 @@ export class AddAnimation extends React.Component<
         } else {
             this.props.setNotificationMessage(`You need to provide a name and target property.`);
         }
-    }
+    };
 
     raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
         if (!this.props.onPropertyChangedObservable) {
@@ -207,49 +207,51 @@ export class AddAnimation extends React.Component<
         });
     }
 
-    handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
+    handlePathChange = (event: React.ChangeEvent<HTMLInputElement>) => {
         event.preventDefault();
-        this.setState({ animationName: event.target.value.trim() });
-    }
+        this.setState({ animationTargetPath: event.target.value.trim() });
+    };
 
-    handlePathChange(event: React.ChangeEvent<HTMLInputElement>) {
+    handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
         event.preventDefault();
-        this.setState({ animationTargetPath: event.target.value.trim() });
-    }
+        this.setState({ animationName: event.target.value.trim() });
+    };
 
-    handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
+    handleTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
         event.preventDefault();
         this.setState({ animationType: parseInt(event.target.value) });
-    }
+    };
 
-    handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
+    handlePropertyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
         event.preventDefault();
         this.setState({ animationTargetProperty: event.target.value });
-    }
+    };
 
-    handleLoopModeChange(event: React.ChangeEvent<HTMLSelectElement>) {
+    handleLoopModeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
         event.preventDefault();
         this.setState({ loopMode: parseInt(event.target.value) });
-    }
+    };
 
     render() {
+        const confirmLabel = this.state.isUpdating ? "Update" : "Create";
+        const confirmHandleOnClick = this.state.isUpdating ? this.updateAnimation : this.addAnimation;
         return (
             <div className="new-animation" style={{ display: this.props.isOpen ? "block" : "none" }}>
                 <div className="sub-content">
                     <div className="label-input">
                         <label>Display Name</label>
-                        <input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
+                        <input type="text" value={this.state.animationName} onChange={this.handleNameChange}></input>
                     </div>
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Property</label>
-                            <input type="text" value={this.state.animationTargetProperty} onChange={(e) => this.handlePropertyChange(e)}></input>
+                            <input type="text" value={this.state.animationTargetProperty} onChange={this.handlePropertyChange}></input>
                         </div>
                     )}
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Type</label>
-                            <select onChange={(e) => this.handleTypeChange(e)} value={this.state.animationType}>
+                            <select onChange={this.handleTypeChange} value={this.state.animationType}>
                                 {/* <option value={Animation.ANIMATIONTYPE_COLOR3}>Color3</option>
                 <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option> */}
                                 <option value={Animation.ANIMATIONTYPE_FLOAT}>Float</option>
@@ -263,14 +265,14 @@ export class AddAnimation extends React.Component<
                     )}
                     <div className="label-input">
                         <label>Loop Mode</label>
-                        <select onChange={(e) => this.handleLoopModeChange(e)} value={this.state.loopMode}>
+                        <select onChange={this.handleLoopModeChange} value={this.state.loopMode}>
                             <option value={Animation.ANIMATIONLOOPMODE_CYCLE}>Cycle</option>
                             <option value={Animation.ANIMATIONLOOPMODE_RELATIVE}>Relative</option>
                             <option value={Animation.ANIMATIONLOOPMODE_CONSTANT}>Constant</option>
                         </select>
                     </div>
                     <div className="confirm-buttons">
-                        <ButtonLineComponent label={this.state.isUpdating ? "Update" : "Create"} onClick={this.state.isUpdating ? () => this.updateAnimation() : () => this.addAnimation()} />
+                        <ButtonLineComponent label={confirmLabel} onClick={confirmHandleOnClick} />
                         {this.props.entity.animations?.length !== 0 ? <ButtonLineComponent label={"Cancel"} onClick={this.props.close} /> : null}
                     </div>
                 </div>

+ 11 - 7
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -23,27 +23,31 @@ export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, { visi
         }
     }
 
-    select() {
+    select = () => {
         this.props.selectControlPoint(this.props.type);
-    }
+    };
 
-    setVisiblePoint = () => {
+    setVisiblePoint() {
         const quarterDistance = 0.5;
         const distanceOnFlat = Math.abs(this.props.anchor.x - this.props.control.x);
         const currentDistance = Vector2.Distance(this.props.anchor, this.props.control);
         const percentageChange = ((currentDistance - distanceOnFlat) * 100) / currentDistance;
         const updateAmount = quarterDistance - (quarterDistance * percentageChange) / 100;
         return Vector2.Lerp(this.props.anchor, this.props.control, updateAmount);
-    };
+    }
 
     render() {
+        const visibleCircleClass = `draggable control-point ${this.props.active ? "active" : ""}`;
+        const nonVisibleCircleClass = `control-point ${this.props.active ? "active" : ""}`;
+        const strokeVisibleCircle = this.props.selected ? 1 : 0;
+        const visibleCircle = this.props.selected ? "red" : "#e9db1e";
         return (
             <>
-                <svg x={this.state.visiblePoint.x} y={this.state.visiblePoint.y} style={{ overflow: "visible" }} onClick={() => this.select()}>
-                    <circle type={this.props.type} data-id={this.props.index} className={`draggable control-point ${this.props.active ? "active" : ""}`} cx="0" cy="0" r="0.75%" stroke="aqua" strokeWidth={this.props.selected ? 1 : 0} fill={this.props.selected ? "red" : "#e9db1e"} />
+                <svg x={this.state.visiblePoint.x} y={this.state.visiblePoint.y} style={{ overflow: "visible" }} onClick={this.select}>
+                    <circle type={this.props.type} data-id={this.props.index} className={visibleCircleClass} cx="0" cy="0" r="0.75%" stroke="aqua" strokeWidth={strokeVisibleCircle} fill={visibleCircle} />
                 </svg>
                 <svg x={this.props.control.x} y={this.props.control.y} style={{ overflow: "visible", display: "none" }}>
-                    <circle type={this.props.type} data-id={this.props.index} className={`control-point ${this.props.active ? "active" : ""}`} cx="0" cy="0" r="0.7%" stroke="white" strokeWidth={this.props.selected ? 0 : 0} fill={this.props.active ? "#e9db1e" : "white"} />
+                    <circle type={this.props.type} data-id={this.props.index} className={nonVisibleCircleClass} cx="0" cy="0" r="0.7%" stroke="white" strokeWidth={0} fill={"white"} />
                 </svg>
                 <line className={`control-point ${this.props.active ? "active" : ""}`} x1={this.props.anchor.x} y1={this.props.anchor.y} x2={this.state.visiblePoint.x} y2={this.state.visiblePoint.y} strokeWidth="0.8%" />
             </>

+ 203 - 55
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -135,10 +135,16 @@ export class AnimationCurveEditorComponent extends React.Component<
         } else {
             this._isTargetedAnimation = false;
 
-            let hasAnimations = this.props.entity.animations !== undefined || this.props.entity.animations !== null ? this.props.entity.animations : false;
+            let hasAnimations =
+                this.props.entity.animations !== undefined || this.props.entity.animations !== null
+                    ? this.props.entity.animations
+                    : false;
             initialSelection = hasAnimations !== false ? hasAnimations && hasAnimations[0] : null;
 
-            initialLerpMode = initialSelection !== undefined ? this.analizeAnimationForLerp(this.props.entity.animations && initialSelection) : false;
+            initialLerpMode =
+                initialSelection !== undefined
+                    ? this.analizeAnimationForLerp(this.props.entity.animations && initialSelection)
+                    : false;
             initialPathData = initialSelection && this.getPathData(initialSelection);
             initialPathData = initialPathData === null || initialPathData === undefined ? undefined : initialPathData;
         }
@@ -159,7 +165,9 @@ export class AnimationCurveEditorComponent extends React.Component<
             isTangentMode: false,
             isBrokenMode: false,
             lerpMode: initialLerpMode,
-            playheadOffset: this._graphCanvas.current ? this._graphCanvas.current.children[0].clientWidth / (_canvasLength * 10) : 0,
+            playheadOffset: this._graphCanvas.current
+                ? this._graphCanvas.current.children[0].clientWidth / (_canvasLength * 10)
+                : 0,
             frameAxisLength: this.setFrameAxis(_canvasLength),
             valueAxisLength: new Array(10).fill(0).map((s, i) => {
                 return { value: i * 10, label: valueInd[i] };
@@ -304,7 +312,9 @@ export class AnimationCurveEditorComponent extends React.Component<
     resetPlayheadOffset() {
         if (this._graphCanvas && this._graphCanvas.current) {
             this.setState({
-                playheadOffset: this._graphCanvas.current.children[0].clientWidth / (this.state.canvasLength * 10 * this.state.scale),
+                playheadOffset:
+                    this._graphCanvas.current.children[0].clientWidth /
+                    (this.state.canvasLength * 10 * this.state.scale),
             });
         }
     }
@@ -401,7 +411,12 @@ export class AnimationCurveEditorComponent extends React.Component<
         });
     }
 
-    updateValuePerCoordinate(dataType: number, value: number | Vector2 | Vector3 | Color3 | Color4 | Size | Quaternion, newValue: number, coordinate?: number) {
+    updateValuePerCoordinate(
+        dataType: number,
+        value: number | Vector2 | Vector3 | Color3 | Color4 | Size | Quaternion,
+        newValue: number,
+        coordinate?: number
+    ) {
         if (dataType === Animation.ANIMATIONTYPE_FLOAT) {
             value = newValue;
         }
@@ -539,9 +554,15 @@ export class AnimationCurveEditorComponent extends React.Component<
             }
         }
 
-        let updatedValue = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * this._scaleFactor;
+        let updatedValue =
+            ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * this._scaleFactor;
 
-        const updatedValueInCoordinate = this.updateValuePerCoordinate(animation.dataType, keys[index].value, updatedValue, coordinate);
+        const updatedValueInCoordinate = this.updateValuePerCoordinate(
+            animation.dataType,
+            keys[index].value,
+            updatedValue,
+            coordinate
+        );
 
         keys[index].value = updatedValueInCoordinate;
 
@@ -557,7 +578,12 @@ export class AnimationCurveEditorComponent extends React.Component<
         this.selectAnimation(animation, coordinate);
     }
 
-    updateLeftControlPoint(updatedSvgKeyFrame: IKeyframeSvgPoint, key: IAnimationKey, dataType: number, coordinate: number) {
+    updateLeftControlPoint(
+        updatedSvgKeyFrame: IKeyframeSvgPoint,
+        key: IAnimationKey,
+        dataType: number,
+        coordinate: number
+    ) {
         if (updatedSvgKeyFrame.isLeftActive) {
             if (updatedSvgKeyFrame.leftControlPoint !== null) {
                 // Rotate Control Points
@@ -568,11 +594,16 @@ export class AnimationCurveEditorComponent extends React.Component<
 
                 let distanceAmplitudeOfX = updatedSvgKeyFrame.leftControlPoint.x - distanceWithPreviousKeyframe;
 
-                let slope = (updatedSvgKeyFrame.leftControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) / (updatedSvgKeyFrame.leftControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
+                let slope =
+                    (updatedSvgKeyFrame.leftControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) /
+                    (updatedSvgKeyFrame.leftControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
 
-                let newValueOfY = (distanceAmplitudeOfX - updatedSvgKeyFrame.leftControlPoint.x) * slope + updatedSvgKeyFrame.keyframePoint.y;
+                let newValueOfY =
+                    (distanceAmplitudeOfX - updatedSvgKeyFrame.leftControlPoint.x) * slope +
+                    updatedSvgKeyFrame.keyframePoint.y;
 
-                let updatedValue = ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
+                let updatedValue =
+                    ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
 
                 key.inTangent = this.updateValuePerCoordinate(dataType, key.inTangent, updatedValue, coordinate);
 
@@ -587,7 +618,12 @@ export class AnimationCurveEditorComponent extends React.Component<
         }
     }
 
-    updateRightControlPoint(updatedSvgKeyFrame: IKeyframeSvgPoint, key: IAnimationKey, dataType: number, coordinate: number) {
+    updateRightControlPoint(
+        updatedSvgKeyFrame: IKeyframeSvgPoint,
+        key: IAnimationKey,
+        dataType: number,
+        coordinate: number
+    ) {
         if (updatedSvgKeyFrame.isRightActive) {
             if (updatedSvgKeyFrame.rightControlPoint !== null) {
                 // Rotate Control Points
@@ -596,11 +632,16 @@ export class AnimationCurveEditorComponent extends React.Component<
 
                 let distanceAmplitudeOfX = updatedSvgKeyFrame.rightControlPoint.x + distanceWithNextKeyframe;
 
-                let slope = (updatedSvgKeyFrame.rightControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) / (updatedSvgKeyFrame.rightControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
+                let slope =
+                    (updatedSvgKeyFrame.rightControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) /
+                    (updatedSvgKeyFrame.rightControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
 
-                let newValueOfY = (distanceAmplitudeOfX - updatedSvgKeyFrame.rightControlPoint.x) * slope + updatedSvgKeyFrame.keyframePoint.y;
+                let newValueOfY =
+                    (distanceAmplitudeOfX - updatedSvgKeyFrame.rightControlPoint.x) * slope +
+                    updatedSvgKeyFrame.keyframePoint.y;
 
-                let updatedValue = ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
+                let updatedValue =
+                    ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
 
                 key.outTangent = this.updateValuePerCoordinate(dataType, key.outTangent, updatedValue, coordinate);
 
@@ -620,7 +661,8 @@ export class AnimationCurveEditorComponent extends React.Component<
             let indexOfKeyframe = this.state.svgKeyframes.indexOf(updatedSvgKeyFrame);
             let previousKeyframe = this.state.svgKeyframes[indexOfKeyframe - 1];
             if (previousKeyframe?.keyframePoint) {
-                distanceWithPreviousKeyframe = Vector2.Distance(updatedSvgKeyFrame.keyframePoint, previousKeyframe.keyframePoint) / 2;
+                distanceWithPreviousKeyframe =
+                    Vector2.Distance(updatedSvgKeyFrame.keyframePoint, previousKeyframe.keyframePoint) / 2;
             }
         }
 
@@ -629,7 +671,8 @@ export class AnimationCurveEditorComponent extends React.Component<
             let indexOfKeyframe = this.state.svgKeyframes.indexOf(updatedSvgKeyFrame);
             let nextKeyframe = this.state.svgKeyframes[indexOfKeyframe + 1];
             if (nextKeyframe?.keyframePoint) {
-                distanceWithNextKeyframe = Vector2.Distance(nextKeyframe.keyframePoint, updatedSvgKeyFrame.keyframePoint) / 2;
+                distanceWithNextKeyframe =
+                    Vector2.Distance(nextKeyframe.keyframePoint, updatedSvgKeyFrame.keyframePoint) / 2;
             }
         }
 
@@ -689,7 +732,12 @@ export class AnimationCurveEditorComponent extends React.Component<
     };
 
     setKeyframeValue = () => {
-        if (this.state.actionableKeyframe.frame !== "" && this.state.actionableKeyframe.frame !== undefined && this.state.actionableKeyframe.value !== "" && this.state.actionableKeyframe.value !== undefined) {
+        if (
+            this.state.actionableKeyframe.frame !== "" &&
+            this.state.actionableKeyframe.frame !== undefined &&
+            this.state.actionableKeyframe.value !== "" &&
+            this.state.actionableKeyframe.value !== undefined
+        ) {
             if (this.state.selected !== null) {
                 let currentSelected = this.state.svgKeyframes?.find((kf) => kf.selected);
                 if (currentSelected) {
@@ -755,7 +803,9 @@ export class AnimationCurveEditorComponent extends React.Component<
         const animation = this.state.selected;
         if (this.state.svgKeyframes && animation) {
             const keys = animation.getKeys();
-            const selectedControlPoint = this.state.svgKeyframes.find((keyframe: IKeyframeSvgPoint) => keyframe.selected && (keyframe.isLeftActive || keyframe.isRightActive));
+            const selectedControlPoint = this.state.svgKeyframes.find(
+                (keyframe: IKeyframeSvgPoint) => keyframe.selected && (keyframe.isLeftActive || keyframe.isRightActive)
+            );
             if (selectedControlPoint !== null && selectedControlPoint) {
                 const { order, coordinate } = this.decodeCurveId(selectedControlPoint.id);
                 const key = keys[order];
@@ -1031,7 +1081,15 @@ export class AnimationCurveEditorComponent extends React.Component<
         if (keyframes === undefined || keyframes.length === 0) {
             return undefined;
         } else {
-            const { easingMode, easingType, usesTangents, valueType, highestFrame, name, targetProperty } = this.getAnimationData(animation);
+            const {
+                easingMode,
+                easingType,
+                usesTangents,
+                valueType,
+                highestFrame,
+                name,
+                targetProperty,
+            } = this.getAnimationData(animation);
 
             //keyframes = this.flatTangents(keyframes, valueType); // This will break because we are using setState before mounted...
             const startKey = keyframes[0];
@@ -1045,7 +1103,9 @@ export class AnimationCurveEditorComponent extends React.Component<
 
                 const curveColor = valueType === Animation.ANIMATIONTYPE_FLOAT ? colors[4] : colors[d];
                 // START OF LINE/CURVE
-                let data: string | undefined = `M${startKey.frame * this._pixelFrameUnit}, ${this._heightScale - startValue[d] * middle}`; //
+                let data: string | undefined = `M${startKey.frame * this._pixelFrameUnit}, ${
+                    this._heightScale - startValue[d] * middle
+                }`; //
 
                 if (this.state) {
                     if (usesTangents) {
@@ -1085,7 +1145,11 @@ export class AnimationCurveEditorComponent extends React.Component<
         let framesPerSecond = animation.framePerSecond;
         let highestFrame = animation.getHighestFrame();
         //let serialized = animation.serialize();
-        let usesTangents = animation.getKeys().find((kf) => kf.hasOwnProperty("inTangent") || kf.hasOwnProperty("outTangent")) !== undefined ? true : false;
+        let usesTangents =
+            animation.getKeys().find((kf) => kf.hasOwnProperty("inTangent") || kf.hasOwnProperty("outTangent")) !==
+            undefined
+                ? true
+                : false;
         let valueType = animation.dataType;
         // easing properties
         let easingType, easingMode;
@@ -1113,7 +1177,14 @@ export class AnimationCurveEditorComponent extends React.Component<
         };
     }
 
-    curvePathWithTangents(keyframes: IAnimationKey[], data: string, middle: number, type: number, coordinate: number, animationName: string) {
+    curvePathWithTangents(
+        keyframes: IAnimationKey[],
+        data: string,
+        middle: number,
+        type: number,
+        coordinate: number,
+        animationName: string
+    ) {
         keyframes.forEach((key, i) => {
             // Create a unique id for curve
             const curveId = this.encodeCurveId(animationName, i);
@@ -1167,8 +1238,10 @@ export class AnimationCurveEditorComponent extends React.Component<
                 defaultTangent = null;
             }
 
-            var inT = key.inTangent === undefined ? defaultTangent : this.getValueAsArray(type, key.inTangent)[coordinate];
-            var outT = key.outTangent === undefined ? defaultTangent : this.getValueAsArray(type, key.outTangent)[coordinate];
+            var inT =
+                key.inTangent === undefined ? defaultTangent : this.getValueAsArray(type, key.inTangent)[coordinate];
+            var outT =
+                key.outTangent === undefined ? defaultTangent : this.getValueAsArray(type, key.outTangent)[coordinate];
 
             let y = this._heightScale - keyframe_valueAsArray * middle;
 
@@ -1188,7 +1261,10 @@ export class AnimationCurveEditorComponent extends React.Component<
 
             if (i === 0) {
                 svgKeyframe = {
-                    keyframePoint: new Vector2(key.frame * this._pixelFrameUnit, this._heightScale - keyframe_valueAsArray * middle),
+                    keyframePoint: new Vector2(
+                        key.frame * this._pixelFrameUnit,
+                        this._heightScale - keyframe_valueAsArray * middle
+                    ),
                     rightControlPoint: outTangent,
                     leftControlPoint: null,
                     id: curveId,
@@ -1201,7 +1277,10 @@ export class AnimationCurveEditorComponent extends React.Component<
                 }
             } else {
                 svgKeyframe = {
-                    keyframePoint: new Vector2(key.frame * this._pixelFrameUnit, this._heightScale - keyframe_valueAsArray * middle),
+                    keyframePoint: new Vector2(
+                        key.frame * this._pixelFrameUnit,
+                        this._heightScale - keyframe_valueAsArray * middle
+                    ),
                     rightControlPoint: outTangent,
                     leftControlPoint: inTangent,
                     id: curveId,
@@ -1297,7 +1376,14 @@ export class AnimationCurveEditorComponent extends React.Component<
                 let intermediatePoint75 = new Vector2((pointB.x - pointA.x) * v + pointA.x, yInt75);
 
                 // Gets the four control points of bezier curve
-                let controlPoints = this.interpolateControlPoints(pointA, intermediatePoint25, u, intermediatePoint75, v, pointB);
+                let controlPoints = this.interpolateControlPoints(
+                    pointA,
+                    intermediatePoint25,
+                    u,
+                    intermediatePoint75,
+                    v,
+                    pointB
+                );
 
                 if (controlPoints !== undefined) {
                     this.setKeyframePoint(controlPoints, i, keyframes.length);
@@ -1337,7 +1423,14 @@ export class AnimationCurveEditorComponent extends React.Component<
         this._svgKeyframes.push(svgKeyframe);
     }
 
-    interpolateControlPoints(p0: Vector2, p1: Vector2, u: number, p2: Vector2, v: number, p3: Vector2): Vector2[] | undefined {
+    interpolateControlPoints(
+        p0: Vector2,
+        p1: Vector2,
+        u: number,
+        p2: Vector2,
+        v: number,
+        p3: Vector2
+    ): Vector2[] | undefined {
         let a = 0.0;
         let b = 0.0;
         let c = 0.0;
@@ -1460,7 +1553,12 @@ export class AnimationCurveEditorComponent extends React.Component<
                 if (keys.length !== 0) {
                     const firstFrame = keys[0].frame;
                     const LastFrame = this.state.selected.getHighestFrame();
-                    this._mainAnimatable = this.props.scene.beginAnimation(target, firstFrame, LastFrame, this.state.isLooping);
+                    this._mainAnimatable = this.props.scene.beginAnimation(
+                        target,
+                        firstFrame,
+                        LastFrame,
+                        this.state.isLooping
+                    );
                     this._mainAnimatable.stop();
                 }
             }
@@ -1536,7 +1634,10 @@ export class AnimationCurveEditorComponent extends React.Component<
                 const currentPointInCurve = selectedCurve.getPointAtLength(frameValue);
                 const middle = this._heightScale / 2;
 
-                const offset = (currentPointInCurve?.y * this._heightScale - this._heightScale ** 2 / 2) / middle / this._heightScale;
+                const offset =
+                    (currentPointInCurve?.y * this._heightScale - this._heightScale ** 2 / 2) /
+                    middle /
+                    this._heightScale;
 
                 const unit = Math.sign(offset);
                 const currentValue = unit === -1 ? Math.abs(offset + unit) : unit - offset;
@@ -1560,7 +1661,9 @@ export class AnimationCurveEditorComponent extends React.Component<
             if (keyframe.value === null) {
                 value = this.state.panningY;
             } else {
-                value = this.getValueAsArray(this.state.selected.dataType, keyframe.value)[this.state.selectedCoordinate];
+                value = this.getValueAsArray(this.state.selected.dataType, keyframe.value)[
+                    this.state.selectedCoordinate
+                ];
             }
 
             const centerCanvas = this._heightScale / 2;
@@ -1620,10 +1723,20 @@ export class AnimationCurveEditorComponent extends React.Component<
                     let firstFrame = keys[0].frame;
                     let LastFrame = this.state.selected.getHighestFrame();
                     if (direction === 1) {
-                        this._mainAnimatable = this.props.scene.beginAnimation(target, firstFrame, LastFrame, this.state.isLooping);
+                        this._mainAnimatable = this.props.scene.beginAnimation(
+                            target,
+                            firstFrame,
+                            LastFrame,
+                            this.state.isLooping
+                        );
                     }
                     if (direction === -1) {
-                        this._mainAnimatable = this.props.scene.beginAnimation(target, LastFrame, firstFrame, this.state.isLooping);
+                        this._mainAnimatable = this.props.scene.beginAnimation(
+                            target,
+                            LastFrame,
+                            firstFrame,
+                            this.state.isLooping
+                        );
                     }
                     if (!this.state.isLooping && this._mainAnimatable) {
                         this._mainAnimatable.onAnimationEnd = () => this.playPause(0);
@@ -1679,7 +1792,11 @@ export class AnimationCurveEditorComponent extends React.Component<
     render() {
         return (
             <div ref={this._editor} id="animation-curve-editor">
-                <Notification message={this.state.notification} open={this.state.notification !== "" ? true : false} close={() => this.clearNotification()} />
+                <Notification
+                    message={this.state.notification}
+                    open={this.state.notification !== "" ? true : false}
+                    close={() => this.clearNotification()}
+                />
                 <GraphActionsBar
                     setKeyframeValue={this.setKeyframeValueFromInput}
                     enabled={this.state.selected === null || this.state.selected === undefined ? false : true}
@@ -1700,7 +1817,9 @@ export class AnimationCurveEditorComponent extends React.Component<
                     <div className="row">
                         <EditorControls
                             deselectAnimation={() => this.deselectAnimation()}
-                            selectAnimation={(animation: Animation, axis?: SelectedCoordinate) => this.selectAnimation(animation, axis)}
+                            selectAnimation={(animation: Animation, axis?: SelectedCoordinate) =>
+                                this.selectAnimation(animation, axis)
+                            }
                             isTargetedAnimation={this._isTargetedAnimation}
                             entity={this.props.entity}
                             selected={this.state.selected}
@@ -1719,14 +1838,22 @@ export class AnimationCurveEditorComponent extends React.Component<
                             {this.state.svgKeyframes && (
                                 <SvgDraggableArea
                                     ref={this._svgCanvas}
-                                    selectKeyframe={(id: string, multiselect: boolean) => this.selectKeyframe(id, multiselect)}
+                                    selectKeyframe={(id: string, multiselect: boolean) =>
+                                        this.selectKeyframe(id, multiselect)
+                                    }
                                     viewBoxScale={this.state.frameAxisLength.length}
                                     scale={this.state.scale}
                                     keyframeSvgPoints={this.state.svgKeyframes}
-                                    removeSelectedKeyframes={(points: IKeyframeSvgPoint[]) => this.removeKeyframes(points)}
-                                    selectedControlPoint={(type: string, id: string) => this.selectedControlPoint(type, id)}
+                                    removeSelectedKeyframes={(points: IKeyframeSvgPoint[]) =>
+                                        this.removeKeyframes(points)
+                                    }
+                                    selectedControlPoint={(type: string, id: string) =>
+                                        this.selectedControlPoint(type, id)
+                                    }
                                     deselectKeyframes={() => this.deselectKeyframes()}
-                                    updatePosition={(updatedSvgKeyFrame: IKeyframeSvgPoint, id: string) => this.renderPoints(updatedSvgKeyFrame, id)}
+                                    updatePosition={(updatedSvgKeyFrame: IKeyframeSvgPoint, id: string) =>
+                                        this.renderPoints(updatedSvgKeyFrame, id)
+                                    }
                                     panningY={(panningY: number) => {
                                         this.setState({ panningY: panningY });
                                     }}
@@ -1737,8 +1864,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                                     positionCanvas={new Vector2(this.state.panningX, this.state.panningY)}
                                     repositionCanvas={this.state.repositionCanvas}
                                     canvasPositionEnded={() => this.setState({ repositionCanvas: false })}
-                                    resetActionableKeyframe={() => this.resetActionableKeyframe()}
-                                >
+                                    resetActionableKeyframe={() => this.resetActionableKeyframe()}>
                                     {/* Multiple Curves  */}
                                     {this.state.selectedPathData?.map((curve, i) => (
                                         <path
@@ -1751,8 +1877,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                                                 stroke: curve.color,
                                                 fill: "none",
                                                 strokeWidth: "0.5",
-                                            }}
-                                        ></path>
+                                            }}></path>
                                     ))}
 
                                     {this.setValueLines(this.state.valueScale).map((line, i) => {
@@ -1768,27 +1893,46 @@ export class AnimationCurveEditorComponent extends React.Component<
                                                     fontSize: `${0.18 * this.state.scale}em`,
                                                     fontWeight: "bold",
                                                     textAlign: "center",
-                                                }}
-                                            >
+                                                }}>
                                                 {line.label}
                                             </text>
                                         );
                                     })}
 
                                     {this.setValueLines(this.state.valueScale).map((line, i) => {
-                                        return <line key={i} x1={-((this.state.frameAxisLength.length * 10) / 2)} y1={line.value} x2={this.state.frameAxisLength.length * 10} y2={line.value}></line>;
+                                        return (
+                                            <line
+                                                key={i}
+                                                x1={-((this.state.frameAxisLength.length * 10) / 2)}
+                                                y1={line.value}
+                                                x2={this.state.frameAxisLength.length * 10}
+                                                y2={line.value}></line>
+                                        );
                                     })}
 
-                                    <rect onClick={(e) => this.moveFrameTo(e)} x={-((this.state.frameAxisLength.length * 10) / 2)} y={91 + this.state.panningY + "%"} width={this.state.frameAxisLength.length * 10} height="9%" fill="#222" style={{ cursor: "pointer" }}></rect>
+                                    <rect
+                                        onClick={(e) => this.moveFrameTo(e)}
+                                        x={-((this.state.frameAxisLength.length * 10) / 2)}
+                                        y={91 + this.state.panningY + "%"}
+                                        width={this.state.frameAxisLength.length * 10}
+                                        height="9%"
+                                        fill="#222"
+                                        style={{ cursor: "pointer" }}></rect>
 
                                     {this.state.frameAxisLength.map((f, i) => (
                                         <svg key={i} x="0" y={96 + this.state.panningY + "%"} className="frame-contain">
-                                            <text x={f.value} y="1px" dx="2px" style={{ fontSize: `${0.2 * this.state.scale}em` }}>
+                                            <text
+                                                x={f.value}
+                                                y="1px"
+                                                dx="2px"
+                                                style={{ fontSize: `${0.2 * this.state.scale}em` }}>
                                                 {f.label}
                                             </text>
                                             <line x1={f.value} y1="0" x2={f.value} y2="5%"></line>
 
-                                            {f.value % this.state.fps === 0 && f.value !== 0 ? <line x1={f.value} y1="-100%" x2={f.value} y2="5%"></line> : null}
+                                            {f.value % this.state.fps === 0 && f.value !== 0 ? (
+                                                <line x1={f.value} y1="-100%" x2={f.value} y2="5%"></line>
+                                            ) : null}
 
                                             {this.state.selected && this.isCurrentFrame(f.label) ? (
                                                 <svg>
@@ -1803,7 +1947,13 @@ export class AnimationCurveEditorComponent extends React.Component<
                                                         }}
                                                     />
                                                     <svg x={f.value} y="-1">
-                                                        <circle className="svg-playhead" cx="0" cy="0" r="2%" fill="white" />
+                                                        <circle
+                                                            className="svg-playhead"
+                                                            cx="0"
+                                                            cy="0"
+                                                            r="2%"
+                                                            fill="white"
+                                                        />
                                                         <text
                                                             x="0"
                                                             y="1%"
@@ -1812,8 +1962,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                                                                 fontSize: `${0.17 * this.state.scale}em`,
                                                                 pointerEvents: "none",
                                                                 fontWeight: 600,
-                                                            }}
-                                                        >
+                                                            }}>
                                                             {f.label}
                                                         </text>
                                                     </svg>
@@ -1838,8 +1987,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                             keyframes={this.state.selected && this.state.selected.getKeys()}
                             selected={this.state.selected && this.state.selected.getKeys()[0]}
                             fps={this.state.fps}
-                            repositionCanvas={(keyframe: IAnimationKey) => this.setCanvasPosition(keyframe)}
-                        ></Timeline>
+                            repositionCanvas={(keyframe: IAnimationKey) => this.setCanvasPosition(keyframe)}></Timeline>
                     </div>
                 </div>
             </div>

+ 43 - 16
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx

@@ -11,18 +11,21 @@ import { LineContainerComponent } from "../../../lineContainerComponent";
 import { TextLineComponent } from "../../../lines/textLineComponent";
 import { SliderLineComponent } from "../../../lines/sliderLineComponent";
 import { LockObject } from "../lockObject";
-import { GlobalState } from '../../../../globalState';
-import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
+import { GlobalState } from "../../../../globalState";
+import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
 
 interface IAnimationGroupGridComponentProps {
     globalState: GlobalState;
-    animationGroup: AnimationGroup,
-    scene: Scene,
-    lockObject: LockObject,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    animationGroup: AnimationGroup;
+    scene: Scene;
+    lockObject: LockObject;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
-export class AnimationGroupGridComponent extends React.Component<IAnimationGroupGridComponentProps, { playButtonText: string, currentFrame: number }> {
+export class AnimationGroupGridComponent extends React.Component<
+    IAnimationGroupGridComponentProps,
+    { playButtonText: string; currentFrame: number }
+> {
     private _onAnimationGroupPlayObserver: Nullable<Observer<AnimationGroup>>;
     private _onAnimationGroupPauseObserver: Nullable<Observer<AnimationGroup>>;
     private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
@@ -44,7 +47,6 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
     }
 
     disconnect(animationGroup: AnimationGroup) {
-
         if (this._onAnimationGroupPlayObserver) {
             animationGroup.onAnimationGroupPlayObservable.remove(this._onAnimationGroupPlayObserver);
             this._onAnimationGroupPlayObserver = null;
@@ -105,7 +107,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
             animationGroup.pause();
         } else {
             this.setState({ playButtonText: "Pause" });
-            this.props.scene.animationGroups.forEach(grp => grp.pause());
+            this.props.scene.animationGroups.forEach((grp) => grp.pause());
             animationGroup.play(true);
         }
     }
@@ -127,21 +129,46 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
     render() {
         const animationGroup = this.props.animationGroup;
 
-        const playButtonText = animationGroup.isPlaying ? "Pause" : "Play"
+        const playButtonText = animationGroup.isPlaying ? "Pause" : "Play";
 
         return (
-            <div className="pane">                
+            <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
                     <TextLineComponent label="Class" value={animationGroup.getClassName()} />
-                    <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={animationGroup} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+                    <TextInputLineComponent
+                        lockObject={this.props.lockObject}
+                        label="Name"
+                        target={animationGroup}
+                        propertyName="name"
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                    />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="CONTROLS">
                     <ButtonLineComponent label={playButtonText} onClick={() => this.playOrPause()} />
-                    <SliderLineComponent label="Speed ratio" minimum={0} maximum={10} step={0.1} target={animationGroup} propertyName="speedRatio" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent ref={this.timelineRef} label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 1000.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
+                    <SliderLineComponent
+                        label="Speed ratio"
+                        minimum={0}
+                        maximum={10}
+                        step={0.1}
+                        target={animationGroup}
+                        propertyName="speedRatio"
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                    />
+                    <SliderLineComponent
+                        ref={this.timelineRef}
+                        label="Current frame"
+                        minimum={animationGroup.from}
+                        maximum={animationGroup.to}
+                        step={(animationGroup.to - animationGroup.from) / 1000.0}
+                        directValue={this.state.currentFrame}
+                        onInput={(value) => this.onCurrentFrameChange(value)}
+                    />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="INFOS">
-                    <TextLineComponent label="Animation count" value={animationGroup.targetedAnimations.length.toString()} />
+                    <TextLineComponent
+                        label="Animation count"
+                        value={animationGroup.targetedAnimations.length.toString()}
+                    />
                     <TextLineComponent label="From" value={animationGroup.from.toFixed(2)} />
                     <TextLineComponent label="To" value={animationGroup.to.toFixed(2)} />
                     <TextLineComponent label="Unique ID" value={animationGroup.uniqueId.toString()} />
@@ -149,4 +176,4 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
             </div>
         );
     }
-}
+}

+ 80 - 14
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx

@@ -63,7 +63,7 @@ export class AnimationListTree extends React.Component<
         };
     }
 
-    deleteAnimation() {
+    deleteAnimation = () => {
         let currentSelected = this.props.selected;
         if (this.props.entity instanceof TargetedAnimation) {
             console.log("no animation remove allowed");
@@ -76,7 +76,7 @@ export class AnimationListTree extends React.Component<
                 this.setState({ animationList: this.generateList() });
             }
         }
-    }
+    };
 
     generateList() {
         let animationList =
@@ -113,36 +113,102 @@ export class AnimationListTree extends React.Component<
         this.props.selectAnimation(animation, coordinate);
     }
 
-    coordinateItem(i: number, animation: Animation, coordinate: string, color: string, selectedCoordinate: SelectedCoordinate) {
+    coordinateItem(
+        i: number,
+        animation: Animation,
+        coordinate: string,
+        color: string,
+        selectedCoordinate: SelectedCoordinate
+    ) {
+        const setSelectedCoordinate = () => this.setSelectedCoordinate(animation, selectedCoordinate, i);
+        const handleClass = `handle-indicator ${
+            this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"
+        }`;
         return (
-            <li key={`${i}_${coordinate}`} id={`${i}_${coordinate}`} className="property" style={{ color: color }} onClick={() => this.setSelectedCoordinate(animation, selectedCoordinate, i)}>
-                <div className={`handle-indicator ${this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"}`}></div>
+            <li
+                key={`${i}_${coordinate}`}
+                id={`${i}_${coordinate}`}
+                className="property"
+                style={{ color: color }}
+                onClick={setSelectedCoordinate}>
+                <div className={handleClass}></div>
                 {animation.targetProperty} {coordinate.toUpperCase()}
             </li>
         );
     }
 
     typeAnimationItem(animation: Animation, i: number, childrenElements: ItemCoordinate[]) {
+        const editAnimation = () => this.props.editAnimation(animation);
+        const selectAnimation = () => this.props.selectAnimation(animation);
+        const toggle = () => this.toggleProperty(i);
         return (
-            <li className={this.props.selected && this.props.selected.name === animation.name ? "property sub active" : "property sub"} key={i}>
-                <div className={`animation-arrow ${this.state.animationList && this.state.animationList[i].open ? "" : "flip"}`} onClick={() => this.toggleProperty(i)}></div>
-                <p onClick={() => this.props.selectAnimation(animation)}>{animation.targetProperty}</p>
-                <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={() => this.props.editAnimation(animation)} />
-                {!(this.props.entity instanceof TargetedAnimation) ? this.props.selected && this.props.selected.name === animation.name ? <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={() => this.deleteAnimation()} /> : <div className="spacer"></div> : null}
-                <ul className={`sub-list ${this.state.animationList && this.state.animationList[i].open ? "" : "hidden"}`}>{childrenElements.map((c) => this.coordinateItem(i, animation, c.id, c.color, c.coordinate))}</ul>
+            <li
+                className={
+                    this.props.selected && this.props.selected.name === animation.name
+                        ? "property sub active"
+                        : "property sub"
+                }
+                key={i}>
+                <div
+                    className={`animation-arrow ${
+                        this.state.animationList && this.state.animationList[i].open ? "" : "flip"
+                    }`}
+                    onClick={toggle}></div>
+                <p onClick={selectAnimation}>{animation.targetProperty}</p>
+                <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />
+                {!(this.props.entity instanceof TargetedAnimation) ? (
+                    this.props.selected && this.props.selected.name === animation.name ? (
+                        <IconButtonLineComponent
+                            tooltip="Remove"
+                            icon="small animation-delete"
+                            onClick={this.deleteAnimation}
+                        />
+                    ) : (
+                        <div className="spacer"></div>
+                    )
+                ) : null}
+                <ul
+                    className={`sub-list ${
+                        this.state.animationList && this.state.animationList[i].open ? "" : "hidden"
+                    }`}>
+                    {childrenElements.map((c) => this.coordinateItem(i, animation, c.id, c.color, c.coordinate))}
+                </ul>
             </li>
         );
     }
 
     setListItem(animation: Animation, i: number) {
+        const editAnimation = () => this.props.editAnimation(animation);
+        const selectAnimation = () => this.props.selectAnimation(animation, 0);
         switch (animation.dataType) {
             case Animation.ANIMATIONTYPE_FLOAT:
                 return (
-                    <li className={this.props.selected && this.props.selected.name === animation.name ? "property active" : "property"} key={i} onClick={() => this.props.selectAnimation(animation, 0)}>
+                    <li
+                        className={
+                            this.props.selected && this.props.selected.name === animation.name
+                                ? "property active"
+                                : "property"
+                        }
+                        key={i}
+                        onClick={selectAnimation}>
                         <div className={`animation-bullet`}></div>
                         <p>{animation.targetProperty}</p>
-                        <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={() => this.props.editAnimation(animation)} />
-                        {!(this.props.entity instanceof TargetedAnimation) ? this.props.selected && this.props.selected.name === animation.name ? <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={() => this.deleteAnimation()} /> : <div className="spacer"></div> : null}
+                        <IconButtonLineComponent
+                            tooltip="Options"
+                            icon="small animation-options"
+                            onClick={editAnimation}
+                        />
+                        {!(this.props.entity instanceof TargetedAnimation) ? (
+                            this.props.selected && this.props.selected.name === animation.name ? (
+                                <IconButtonLineComponent
+                                    tooltip="Remove"
+                                    icon="small animation-delete"
+                                    onClick={this.deleteAnimation}
+                                />
+                            ) : (
+                                <div className="spacer"></div>
+                            )
+                        ) : null}
                     </li>
                 );
             case Animation.ANIMATIONTYPE_VECTOR2:

+ 54 - 24
inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx

@@ -22,24 +22,24 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
         }
     }
 
-    playBackwards() {
+    playBackwards = () => {
         this.setState({ playingType: "reverse" });
         this.props.playPause(-1);
-    }
+    };
 
-    play() {
+    play = () => {
         this.setState({ playingType: "forward" });
         this.props.playPause(1);
-    }
+    };
 
-    pause() {
+    pause = () => {
         if (this.props.isPlaying) {
             this.setState({ playingType: "" });
             this.props.playPause(0);
         }
-    }
+    };
 
-    moveToAnimationStart() {
+    moveToAnimationStart = () => {
         const startKeyframe = this.props.keyframes && this.props.keyframes[0];
         if (startKeyframe !== null) {
             if (startKeyframe.frame !== undefined && typeof startKeyframe.frame === "number") {
@@ -47,9 +47,9 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
             }
             this.props.repositionCanvas(startKeyframe);
         }
-    }
+    };
 
-    moveToAnimationEnd() {
+    moveToAnimationEnd = () => {
         const endKeyframe = this.props.keyframes && this.props.keyframes[this.props.keyframes.length - 1];
         if (endKeyframe !== null) {
             if (endKeyframe.frame !== undefined && typeof endKeyframe.frame === "number") {
@@ -57,9 +57,9 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
                 this.props.repositionCanvas(endKeyframe);
             }
         }
-    }
+    };
 
-    nextKeyframe() {
+    nextKeyframe = () => {
         if (this.props.keyframes !== null) {
             let first = this.props.keyframes.find((kf) => kf.frame > this.props.currentFrame);
             if (first) {
@@ -69,9 +69,9 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
                 (this.props.scrollable.current as HTMLDivElement).scrollLeft = first.frame * this._sizeOfKeyframe;
             }
         }
-    }
+    };
 
-    previousKeyframe() {
+    previousKeyframe = () => {
         if (this.props.keyframes !== null) {
             let keyframes = [...this.props.keyframes];
             let first = keyframes.reverse().find((kf) => kf.frame < this.props.currentFrame);
@@ -82,35 +82,65 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
                 (this.props.scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * this._sizeOfKeyframe);
             }
         }
-    }
+    };
 
     render() {
         return (
             <div className="controls">
-                <IconButtonLineComponent tooltip="Animation Start" icon="animation-start" onClick={() => this.moveToAnimationStart()}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Previous Keyframe" icon="animation-lastkey" onClick={() => this.previousKeyframe()}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Animation Start"
+                    icon="animation-start"
+                    onClick={this.moveToAnimationStart}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Previous Keyframe"
+                    icon="animation-lastkey"
+                    onClick={this.previousKeyframe}></IconButtonLineComponent>
                 {this.props.isPlaying ? (
                     <div className="stop-container">
                         {this.state.playingType === "reverse" ? (
                             <>
-                                <IconButtonLineComponent tooltip="Pause" icon="animation-stop" onClick={() => this.pause()}></IconButtonLineComponent>
-                                <IconButtonLineComponent tooltip="Play Forward" icon="animation-playfwd" onClick={() => this.play()}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Pause"
+                                    icon="animation-stop"
+                                    onClick={this.pause}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Play Forward"
+                                    icon="animation-playfwd"
+                                    onClick={this.play}></IconButtonLineComponent>
                             </>
                         ) : (
                             <>
-                                <IconButtonLineComponent tooltip="Play Reverse" icon="animation-playrev" onClick={() => this.playBackwards()}></IconButtonLineComponent>
-                                <IconButtonLineComponent tooltip="Pause" icon="animation-stop" onClick={() => this.pause()}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Play Reverse"
+                                    icon="animation-playrev"
+                                    onClick={this.playBackwards}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Pause"
+                                    icon="animation-stop"
+                                    onClick={this.pause}></IconButtonLineComponent>
                             </>
                         )}
                     </div>
                 ) : (
                     <div className="stop-container">
-                        <IconButtonLineComponent tooltip="Play Reverse" icon="animation-playrev" onClick={() => this.playBackwards()}></IconButtonLineComponent>
-                        <IconButtonLineComponent tooltip="Play Forward" icon="animation-playfwd" onClick={() => this.play()}></IconButtonLineComponent>
+                        <IconButtonLineComponent
+                            tooltip="Play Reverse"
+                            icon="animation-playrev"
+                            onClick={this.playBackwards}></IconButtonLineComponent>
+                        <IconButtonLineComponent
+                            tooltip="Play Forward"
+                            icon="animation-playfwd"
+                            onClick={this.play}></IconButtonLineComponent>
                     </div>
                 )}
-                <IconButtonLineComponent tooltip="Next Keyframe" icon="animation-nextkey" onClick={() => this.nextKeyframe()}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Animation End" icon="animation-end" onClick={() => this.moveToAnimationEnd()}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Next Keyframe"
+                    icon="animation-nextkey"
+                    onClick={this.nextKeyframe}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Animation End"
+                    icon="animation-end"
+                    onClick={this.moveToAnimationEnd}></IconButtonLineComponent>
             </div>
         );
     }

+ 91 - 34
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -66,33 +66,46 @@ export class EditorControls extends React.Component<
         }
     }
 
-    animationAdded(animation: Animation) {
+    animationAdded = (animation: Animation) => {
         this.setState({
             animationsCount: this.recountAnimations(),
             isEditTabOpen: true,
             isAnimationTabOpen: false,
         });
         this.props.selectAnimation(animation, undefined);
-    }
+    };
 
-    finishedUpdate() {
+    finishedUpdate = () => {
         this.setState({
             isEditTabOpen: true,
             isAnimationTabOpen: false,
             selected: undefined,
         });
-    }
+    };
 
     recountAnimations() {
         return (this.props.entity as IAnimatable).animations?.length ?? 0;
     }
 
-    changeLoopBehavior() {
+    changeLoopBehavior = () => {
         this.setState({
             isLoopActive: !this.state.isLoopActive,
         });
         this.props.setIsLooping();
-    }
+    };
+
+    handleFirstTab = () => {
+        this.handleTabs(0);
+    };
+    handleSecondTab = () => {
+        this.handleTabs(1);
+    };
+    handleThirdTab = () => {
+        this.handleTabs(2);
+    };
+    handleFourthTab = () => {
+        this.handleTabs(3);
+    };
 
     handleTabs(tab: number) {
         let state = {
@@ -140,23 +153,23 @@ export class EditorControls extends React.Component<
         this.setState(state);
     }
 
-    handleChangeFps(fps: number) {
+    handleChangeFps = (fps: number) => {
         this.props.setFps(fps);
         this.setState({ framesPerSecond: fps });
         if (this.props.selected) {
             this.props.selected.framePerSecond = fps;
         }
-    }
+    };
 
-    emptiedList() {
+    emptiedList = () => {
         this.setState({
             animationsCount: this.recountAnimations(),
             isEditTabOpen: false,
             isAnimationTabOpen: true,
         });
-    }
+    };
 
-    animationsLoaded(numberOfAnimations: number) {
+    animationsLoaded = (numberOfAnimations: number) => {
         this.setState({
             animationsCount: numberOfAnimations,
             isEditTabOpen: true,
@@ -164,9 +177,9 @@ export class EditorControls extends React.Component<
             isLoadTabOpen: false,
             isSaveTabOpen: false,
         });
-    }
+    };
 
-    editAnimation(selected: Animation) {
+    editAnimation = (selected: Animation) => {
         this.setState({
             selected: selected,
             isEditTabOpen: false,
@@ -174,67 +187,111 @@ export class EditorControls extends React.Component<
             isLoadTabOpen: false,
             isSaveTabOpen: false,
         });
-    }
+    };
+
+    setSnippetId = (id: string) => {
+        this.setState({ snippetId: id });
+    };
+
+    closeAddAnimation = () => {
+        this.setState({ isAnimationTabOpen: false, isEditTabOpen: true });
+    };
 
     render() {
         return (
             <div className="animation-list">
                 <div className="controls-header">
-                    {this.props.isTargetedAnimation ? null : <IconButtonLineComponent active={this.state.isAnimationTabOpen} tooltip="Add Animation" icon="medium add-animation" onClick={() => this.handleTabs(0)}></IconButtonLineComponent>}
-                    <IconButtonLineComponent active={this.state.isLoadTabOpen} tooltip="Load Animation" icon="medium load" onClick={() => this.handleTabs(1)}></IconButtonLineComponent>
-                    {this.state.animationsCount === 0 ? null : <IconButtonLineComponent active={this.state.isSaveTabOpen} tooltip="Save Animation" icon="medium save" onClick={() => this.handleTabs(2)}></IconButtonLineComponent>}
-                    {this.state.animationsCount === 0 ? null : <IconButtonLineComponent active={this.state.isEditTabOpen} tooltip="Edit Animations" icon="medium animation-edit" onClick={() => this.handleTabs(3)}></IconButtonLineComponent>}
+                    {this.props.isTargetedAnimation ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isAnimationTabOpen}
+                            tooltip="Add Animation"
+                            icon="medium add-animation"
+                            onClick={this.handleFirstTab}></IconButtonLineComponent>
+                    )}
+                    <IconButtonLineComponent
+                        active={this.state.isLoadTabOpen}
+                        tooltip="Load Animation"
+                        icon="medium load"
+                        onClick={this.handleSecondTab}></IconButtonLineComponent>
+                    {this.state.animationsCount === 0 ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isSaveTabOpen}
+                            tooltip="Save Animation"
+                            icon="medium save"
+                            onClick={this.handleThirdTab}></IconButtonLineComponent>
+                    )}
+                    {this.state.animationsCount === 0 ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isEditTabOpen}
+                            tooltip="Edit Animations"
+                            icon="medium animation-edit"
+                            onClick={this.handleFourthTab}></IconButtonLineComponent>
+                    )}
                     {this.state.isEditTabOpen ? (
                         <div className="input-fps">
-                            <NumericInputComponent label={""} precision={0} value={this.state.framesPerSecond} onChange={(framesPerSecond: number) => this.handleChangeFps(framesPerSecond)} />
+                            <NumericInputComponent
+                                label={""}
+                                precision={0}
+                                value={this.state.framesPerSecond}
+                                onChange={this.handleChangeFps}
+                            />
                             <p>fps</p>
                         </div>
                     ) : null}
-                    {this.state.isEditTabOpen ? <IconButtonLineComponent tooltip="Loop/Unloop" icon={`medium ${this.state.isLoopActive ? "loop-active last" : "loop-inactive last"}`} onClick={() => this.changeLoopBehavior()}></IconButtonLineComponent> : null}
+                    {this.state.isEditTabOpen ? (
+                        <IconButtonLineComponent
+                            tooltip="Loop/Unloop"
+                            icon={`medium ${this.state.isLoopActive ? "loop-active last" : "loop-inactive last"}`}
+                            onClick={this.changeLoopBehavior}></IconButtonLineComponent>
+                    ) : null}
                 </div>
                 {this.props.isTargetedAnimation ? null : (
                     <AddAnimation
                         isOpen={this.state.isAnimationTabOpen}
-                        close={() => {
-                            this.setState({ isAnimationTabOpen: false, isEditTabOpen: true });
-                        }}
+                        close={this.closeAddAnimation}
                         entity={this.props.entity as IAnimatable}
-                        setNotificationMessage={(message: string) => {
-                            this.props.setNotificationMessage(message);
-                        }}
-                        addedNewAnimation={(animation: Animation) => this.animationAdded(animation)}
+                        setNotificationMessage={this.props.setNotificationMessage}
+                        addedNewAnimation={this.animationAdded}
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable}
                         fps={this.state.framesPerSecond}
                         selectedToUpdate={this.state.selected}
-                        finishedUpdate={() => this.finishedUpdate()}
+                        finishedUpdate={this.finishedUpdate}
                     />
                 )}
 
                 {this.state.isLoadTabOpen ? (
                     <LoadSnippet
-                        animationsLoaded={(numberOfAnimations: number) => this.animationsLoaded(numberOfAnimations)}
+                        animationsLoaded={this.animationsLoaded}
                         lockObject={this.props.lockObject}
                         animations={[]}
                         snippetServer={this.props.snippetServer}
                         globalState={this.props.globalState}
-                        setSnippetId={(id: string) => this.setState({ snippetId: id })}
+                        setSnippetId={this.setSnippetId}
                         entity={this.props.entity}
                         setNotificationMessage={this.props.setNotificationMessage}
                     />
                 ) : null}
 
-                {this.state.isSaveTabOpen ? <SaveSnippet lockObject={this.props.lockObject} animations={(this.props.entity as IAnimatable).animations} snippetServer={this.props.snippetServer} globalState={this.props.globalState} snippetId={this.state.snippetId} /> : null}
+                {this.state.isSaveTabOpen ? (
+                    <SaveSnippet
+                        lockObject={this.props.lockObject}
+                        animations={(this.props.entity as IAnimatable).animations}
+                        snippetServer={this.props.snippetServer}
+                        globalState={this.props.globalState}
+                        snippetId={this.state.snippetId}
+                    />
+                ) : null}
 
                 {this.state.isEditTabOpen ? (
                     <AnimationListTree
-                        deselectAnimation={() => this.props.deselectAnimation()}
+                        deselectAnimation={this.props.deselectAnimation}
                         isTargetedAnimation={this.props.isTargetedAnimation}
                         entity={this.props.entity}
                         selected={this.props.selected}
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable}
-                        empty={() => this.emptiedList()}
+                        empty={this.emptiedList}
                         selectAnimation={this.props.selectAnimation}
-                        editAnimation={(selected: Animation) => this.editAnimation(selected)}
+                        editAnimation={this.editAnimation}
                     />
                 ) : null}
             </div>

+ 40 - 8
inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx

@@ -67,13 +67,13 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps, { fr
         }
     }
 
-    onBlur(event: React.FocusEvent<HTMLInputElement>) {
+    onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
         event.preventDefault();
         if (event.target.value !== "") {
             const actionableKeyframe: IActionableKeyFrame = { frame: this.getFrame(), value: this.getValue() };
             this.props.setKeyframeValue(actionableKeyframe);
         }
-    }
+    };
 
     getFrame() {
         let frame;
@@ -115,16 +115,48 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps, { fr
                 </div>
                 <div className="buttons-container" style={{ pointerEvents: this.props.enabled ? "all" : "none" }}>
                     <div className="action-input frame-input">
-                        <input ref={this._frameInput} type="number" onChange={this.handleFrameChange} value={this.state.frame} step="1" disabled={this.props.actionableKeyframe.frame === undefined} onBlur={(e) => this.onBlur(e)} />
+                        <input
+                            ref={this._frameInput}
+                            type="number"
+                            onChange={this.handleFrameChange}
+                            value={this.state.frame}
+                            step="1"
+                            disabled={this.props.actionableKeyframe.frame === undefined}
+                            onBlur={this.onBlur}
+                        />
                     </div>
                     <div className="action-input">
-                        <input ref={this._valueInput} type="number" value={this.state.value} onChange={this.handleValueChange} step="0.01" disabled={this.props.actionableKeyframe.value === undefined} onBlur={(e) => this.onBlur(e)} />
+                        <input
+                            ref={this._valueInput}
+                            type="number"
+                            value={this.state.value}
+                            onChange={this.handleValueChange}
+                            step="0.01"
+                            disabled={this.props.actionableKeyframe.value === undefined}
+                            onBlur={this.onBlur}
+                        />
                     </div>
                     <IconButtonLineComponent tooltip={"Add Keyframe"} icon="new-key" onClick={this.props.addKeyframe} />
-                    <IconButtonLineComponent tooltip={"Frame selected keyframes"} icon="frame" onClick={this.props.removeKeyframe} />
-                    <IconButtonLineComponent tooltip={"Flat Tangents"} icon="flat-tangent" onClick={this.props.flatTangent} />
-                    <IconButtonLineComponent tooltip={this.props.brokenMode ? "Broken Mode On" : "Broken Mode Off"} icon={this.props.brokenMode ? "break-tangent" : "unify-tangent"} onClick={this.props.brokeTangents} />
-                    <IconButtonLineComponent tooltip={"Linear"} icon="linear-tangent" onClick={this.props.setLerpToActiveControlPoint} />
+                    <IconButtonLineComponent
+                        tooltip={"Frame selected keyframes"}
+                        icon="frame"
+                        onClick={this.props.removeKeyframe}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={"Flat Tangents"}
+                        icon="flat-tangent"
+                        onClick={this.props.flatTangent}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={this.props.brokenMode ? "Broken Mode On" : "Broken Mode Off"}
+                        icon={this.props.brokenMode ? "break-tangent" : "unify-tangent"}
+                        onClick={this.props.brokeTangents}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={"Linear"}
+                        icon="linear-tangent"
+                        onClick={this.props.setLerpToActiveControlPoint}
+                    />
                 </div>
             </div>
         );

+ 85 - 85
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -1,101 +1,101 @@
-import * as React from 'react';
-import { Vector2 } from 'babylonjs/Maths/math.vector';
-import { AnchorSvgPoint } from './anchorSvgPoint';
+import * as React from "react";
+import { Vector2 } from "babylonjs/Maths/math.vector";
+import { AnchorSvgPoint } from "./anchorSvgPoint";
 
-const keyInactive = require('./assets/keyInactiveIcon.svg') as string;
+const keyInactive = require("./assets/keyInactiveIcon.svg") as string;
 //const keyActive = require("./assets/keyActiveIcon.svg") as string; uncomment when setting active multiselect
-const keySelected = require('./assets/keySelectedIcon.svg') as string;
+const keySelected = require("./assets/keySelectedIcon.svg") as string;
 
 export interface IKeyframeSvgPoint {
-  keyframePoint: Vector2;
-  rightControlPoint: Vector2 | null;
-  leftControlPoint: Vector2 | null;
-  id: string;
-  selected: boolean;
-  isLeftActive: boolean;
-  isRightActive: boolean;
-  curveId?: ICurveMetaData;
+    keyframePoint: Vector2;
+    rightControlPoint: Vector2 | null;
+    leftControlPoint: Vector2 | null;
+    id: string;
+    selected: boolean;
+    isLeftActive: boolean;
+    isRightActive: boolean;
+    curveId?: ICurveMetaData;
 }
 
 export interface ICurveMetaData {
-  id: number;
-  animationName: string;
-  property: string;
+    id: number;
+    animationName: string;
+    property: string;
 }
 
 interface IKeyframeSvgPointProps {
-  keyframePoint: Vector2;
-  leftControlPoint: Vector2 | null;
-  rightControlPoint: Vector2 | null;
-  id: string;
-  selected: boolean;
-  selectKeyframe: (id: string, multiselect: boolean) => void;
-  selectedControlPoint: (type: string, id: string) => void;
-  isLeftActive: boolean;
-  isRightActive: boolean;
+    keyframePoint: Vector2;
+    leftControlPoint: Vector2 | null;
+    rightControlPoint: Vector2 | null;
+    id: string;
+    selected: boolean;
+    selectKeyframe: (id: string, multiselect: boolean) => void;
+    selectedControlPoint: (type: string, id: string) => void;
+    isLeftActive: boolean;
+    isRightActive: boolean;
 }
 
 export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
-  constructor(props: IKeyframeSvgPointProps) {
-    super(props);
-  }
-
-  select(e: React.MouseEvent<SVGImageElement>) {
-    e.preventDefault();
-    let multiSelect = false;
-    if (e.buttons === 0 && e.ctrlKey) {
-      multiSelect = true;
+    constructor(props: IKeyframeSvgPointProps) {
+        super(props);
     }
-    this.props.selectKeyframe(this.props.id, multiSelect);
-  }
 
-  render() {
-    return (
-      <>
-        <svg
-          className='draggable'
-          x={this.props.keyframePoint.x}
-          y={this.props.keyframePoint.y}
-          style={{ overflow: 'visible', cursor: 'pointer' }}
-        >
-          <image
-            data-id={this.props.id}
-            className='draggable'
-            x='-1'
-            y='-1.5'
-            width='3'
-            height='3'
-            href={this.props.selected ? keySelected : keyInactive}
-            onClick={(e) => this.select(e)}
-          />
-        </svg>
-        {this.props.leftControlPoint && (
-          <AnchorSvgPoint
-            type='left'
-            index={this.props.id}
-            control={this.props.leftControlPoint}
-            anchor={this.props.keyframePoint}
-            active={this.props.selected}
-            selected={this.props.isLeftActive}
-            selectControlPoint={(type: string) =>
-              this.props.selectedControlPoint(type, this.props.id)
-            }
-          />
-        )}
-        {this.props.rightControlPoint && (
-          <AnchorSvgPoint
-            type='right'
-            index={this.props.id}
-            control={this.props.rightControlPoint}
-            anchor={this.props.keyframePoint}
-            active={this.props.selected}
-            selected={this.props.isRightActive}
-            selectControlPoint={(type: string) =>
-              this.props.selectedControlPoint(type, this.props.id)
-            }
-          />
-        )}
-      </>
-    );
-  }
+    select = (e: React.MouseEvent<SVGImageElement>) => {
+        e.preventDefault();
+        let multiSelect = false;
+        if (e.buttons === 0 && e.ctrlKey) {
+            multiSelect = true;
+        }
+        this.props.selectKeyframe(this.props.id, multiSelect);
+    };
+
+    selectedControlPointId = (type: string) => {
+        this.props.selectedControlPoint(type, this.props.id);
+    };
+
+    render() {
+        const svgImageIcon = this.props.selected ? keySelected : keyInactive;
+        return (
+            <>
+                <svg
+                    className="draggable"
+                    x={this.props.keyframePoint.x}
+                    y={this.props.keyframePoint.y}
+                    style={{ overflow: "visible", cursor: "pointer" }}>
+                    <image
+                        data-id={this.props.id}
+                        className="draggable"
+                        x="-1"
+                        y="-1.5"
+                        width="3"
+                        height="3"
+                        href={svgImageIcon}
+                        onClick={this.select}
+                    />
+                </svg>
+                {this.props.leftControlPoint && (
+                    <AnchorSvgPoint
+                        type="left"
+                        index={this.props.id}
+                        control={this.props.leftControlPoint}
+                        anchor={this.props.keyframePoint}
+                        active={this.props.selected}
+                        selected={this.props.isLeftActive}
+                        selectControlPoint={this.selectedControlPointId}
+                    />
+                )}
+                {this.props.rightControlPoint && (
+                    <AnchorSvgPoint
+                        type="right"
+                        index={this.props.id}
+                        control={this.props.rightControlPoint}
+                        anchor={this.props.keyframePoint}
+                        active={this.props.selected}
+                        selected={this.props.isRightActive}
+                        selectControlPoint={this.selectedControlPointId}
+                    />
+                )}
+            </>
+        );
+    }
 }

+ 102 - 114
inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx

@@ -1,126 +1,114 @@
-import * as React from 'react';
-import { Observable } from 'babylonjs/Misc/observable';
-import { PropertyChangedEvent } from '../../../../../components/propertyChangedEvent';
-import { Animation } from 'babylonjs/Animations/animation';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { FileButtonLineComponent } from '../../../lines/fileButtonLineComponent';
-import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
-import { LockObject } from '../lockObject';
-import { Tools } from 'babylonjs/Misc/tools';
-import { GlobalState } from '../../../../globalState';
-import { ReadFileError } from 'babylonjs/Misc/fileTools';
-import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
-import { TargetedAnimation } from 'babylonjs/Animations/animationGroup';
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { Animation } from "babylonjs/Animations/animation";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { FileButtonLineComponent } from "../../../lines/fileButtonLineComponent";
+import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
+import { LockObject } from "../lockObject";
+import { Tools } from "babylonjs/Misc/tools";
+import { GlobalState } from "../../../../globalState";
+import { ReadFileError } from "babylonjs/Misc/fileTools";
+import { IAnimatable } from "babylonjs/Animations/animatable.interface";
+import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
 
 interface ILoadSnippetProps {
-  animations: Animation[];
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
-  lockObject: LockObject;
-  globalState: GlobalState;
-  snippetServer: string;
-  setSnippetId: (id: string) => void;
-  entity: IAnimatable | TargetedAnimation;
-  setNotificationMessage: (message: string) => void;
-  animationsLoaded: (numberOfAnimations: number) => void;
+    animations: Animation[];
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    lockObject: LockObject;
+    globalState: GlobalState;
+    snippetServer: string;
+    setSnippetId: (id: string) => void;
+    entity: IAnimatable | TargetedAnimation;
+    setNotificationMessage: (message: string) => void;
+    animationsLoaded: (numberOfAnimations: number) => void;
 }
 
-export class LoadSnippet extends React.Component<
-  ILoadSnippetProps,
-  { snippetId: string }
-> {
-  private _serverAddress: string;
-  constructor(props: ILoadSnippetProps) {
-    super(props);
-    this._serverAddress = this.props.snippetServer;
-    this.state = { snippetId: '' };
-  }
+export class LoadSnippet extends React.Component<ILoadSnippetProps, { snippetId: string }> {
+    private _serverAddress: string;
+    constructor(props: ILoadSnippetProps) {
+        super(props);
+        this._serverAddress = this.props.snippetServer;
+        this.state = { snippetId: "" };
+    }
 
-  change(value: string) {
-    this.setState({ snippetId: value });
-    this.props.setSnippetId(value);
-  }
+    change = (value: string) => {
+        this.setState({ snippetId: value });
+        this.props.setSnippetId(value);
+    };
 
-  loadFromFile(file: File) {
-    Tools.ReadFile(
-      file,
-      (data) => {
-        let decoder = new TextDecoder('utf-8');
-        let jsonObject = JSON.parse(decoder.decode(data));
-        var result: Animation[] = [];
+    loadFromFile = (file: File) => {
+        Tools.ReadFile(
+            file,
+            (data) => {
+                let decoder = new TextDecoder("utf-8");
+                let jsonObject = JSON.parse(decoder.decode(data));
+                var result: Animation[] = [];
 
-        for (var i in jsonObject) {
-          result.push(Animation.Parse(jsonObject[i]));
-        }
+                for (var i in jsonObject) {
+                    result.push(Animation.Parse(jsonObject[i]));
+                }
 
-        if (this.props.entity) {
-          (this.props.entity as IAnimatable).animations = result;
-          var e = new PropertyChangedEvent();
-          e.object = this.props.entity;
-          e.property = 'animations';
-          e.value = (this.props.entity as IAnimatable).animations;
-          this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
-          this.props.animationsLoaded(result.length);
-        }
-      },
-      undefined,
-      true,
-      (error: ReadFileError) => {
-        console.log(error.message);
-      }
-    );
-  }
+                if (this.props.entity) {
+                    (this.props.entity as IAnimatable).animations = result;
+                    var e = new PropertyChangedEvent();
+                    e.object = this.props.entity;
+                    e.property = "animations";
+                    e.value = (this.props.entity as IAnimatable).animations;
+                    this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
+                    this.props.animationsLoaded(result.length);
+                }
+            },
+            undefined,
+            true,
+            (error: ReadFileError) => {
+                console.log(error.message);
+            }
+        );
+    };
 
-  loadFromSnippet() {
-    if (this.state.snippetId !== '') {
-      //How to dispose() previous animations;
-      //How to notify observers
-      Animation.CreateFromSnippetAsync(this.state.snippetId)
-        .then((newAnimations) => {
-          // Explore how observers are notified from snippet
-          if (newAnimations instanceof Array) {
-            (this.props.entity as IAnimatable).animations = newAnimations;
-          }
+    loadFromSnippet = () => {
+        if (this.state.snippetId !== "") {
+            //How to dispose() previous animations;
+            //How to notify observers
+            Animation.CreateFromSnippetAsync(this.state.snippetId)
+                .then((newAnimations) => {
+                    // Explore how observers are notified from snippet
+                    if (newAnimations instanceof Array) {
+                        (this.props.entity as IAnimatable).animations = newAnimations;
+                    }
 
-          if (newAnimations instanceof Animation) {
-            (this.props.entity as IAnimatable).animations?.push(newAnimations);
-          }
-        })
-        .catch((err) => {
-          this.props.setNotificationMessage(
-            `Unable to load your animations: ${err}`
-          );
-        });
-    } else {
-      this.props.setNotificationMessage(`You need to add an snippet id`);
-    }
-  }
+                    if (newAnimations instanceof Animation) {
+                        (this.props.entity as IAnimatable).animations?.push(newAnimations);
+                    }
+                })
+                .catch((err) => {
+                    this.props.setNotificationMessage(`Unable to load your animations: ${err}`);
+                });
+        } else {
+            this.props.setNotificationMessage(`You need to add an snippet id`);
+        }
+    };
 
-  render() {
-    return (
-      <div className='load-container'>
-        <TextInputLineComponent
-          label='Snippet Id'
-          lockObject={this.props.lockObject}
-          value={this.state.snippetId}
-          onChange={(value: string) => this.change(value)}
-        />
-        <ButtonLineComponent
-          label='Load from snippet server'
-          onClick={() => this.loadFromSnippet()}
-        />
-        <div className='load-browse'>
-          <p>Local File</p>
-          <FileButtonLineComponent
-            label='Load'
-            onClick={(file) => this.loadFromFile(file)}
-            accept='.json'
-          />
-        </div>
-        <div className='load-server'>
-          <p>Snippet Server: </p>&nbsp;
-          <p> {this._serverAddress ?? '-'}</p>
-        </div>
-      </div>
-    );
-  }
+    render() {
+        return (
+            <div className="load-container">
+                <TextInputLineComponent
+                    label="Snippet Id"
+                    lockObject={this.props.lockObject}
+                    value={this.state.snippetId}
+                    onChange={this.change}
+                />
+                <ButtonLineComponent label="Load from snippet server" onClick={this.loadFromSnippet} />
+                <div className="load-browse">
+                    <p>Local File</p>
+                    <FileButtonLineComponent label="Load" onClick={this.loadFromFile} accept=".json" />
+                </div>
+                <div className="load-server">
+                    <p>Snippet Server: </p>&nbsp;
+                    <p> {this._serverAddress ?? "-"}</p>
+                </div>
+            </div>
+        );
+    }
 }

+ 19 - 25
inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx

@@ -1,32 +1,26 @@
-import * as React from 'react';
+import * as React from "react";
 
 interface IPlayheadProps {
-  message: string;
-  open: boolean;
-  close: () => void;
+    message: string;
+    open: boolean;
+    close: () => void;
 }
 
 export class Notification extends React.Component<IPlayheadProps> {
-  constructor(props: IPlayheadProps) {
-    super(props);
-  }
+    constructor(props: IPlayheadProps) {
+        super(props);
+    }
 
-  render() {
-    return (
-      <div
-        className='notification-area'
-        style={{ display: this.props.open ? 'block' : 'none' }}>
-        <div className='alert alert-error'>
-          <button
-            type='button'
-            className='close'
-            data-dismiss='alert'
-            onClick={this.props.close}>
-            &times;
-          </button>
-          {this.props.message}
-        </div>
-      </div>
-    );
-  }
+    render() {
+        return (
+            <div className="notification-area" style={{ display: this.props.open ? "block" : "none" }}>
+                <div className="alert alert-error">
+                    <button type="button" className="close" data-dismiss="alert" onClick={this.props.close}>
+                        &times;
+                    </button>
+                    {this.props.message}
+                </div>
+            </div>
+        );
+    }
 }

+ 154 - 163
inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx

@@ -1,182 +1,173 @@
-import * as React from 'react';
-import { Observable } from 'babylonjs/Misc/observable';
-import { PropertyChangedEvent } from '../../../../../components/propertyChangedEvent';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { Tools } from 'babylonjs/Misc/tools';
-import { Animation } from 'babylonjs/Animations/animation';
-import { LockObject } from '../lockObject';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../../globalState';
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { Tools } from "babylonjs/Misc/tools";
+import { Animation } from "babylonjs/Animations/animation";
+import { LockObject } from "../lockObject";
+import { Nullable } from "babylonjs/types";
+import { GlobalState } from "../../../../globalState";
 
 interface ISaveSnippetProps {
-  animations: Nullable<Animation[]>;
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
-  lockObject: LockObject;
-  globalState: GlobalState;
-  snippetServer: string;
-  snippetId: string;
+    animations: Nullable<Animation[]>;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    lockObject: LockObject;
+    globalState: GlobalState;
+    snippetServer: string;
+    snippetId: string;
 }
 
 export interface Snippet {
-  url: string;
-  id: string;
+    url: string;
+    id: string;
 }
 interface SelectedAnimation {
-  id: string;
-  name: string;
-  index: number;
-  selected: boolean;
+    id: string;
+    name: string;
+    index: number;
+    selected: boolean;
 }
 
-export class SaveSnippet extends React.Component<
-  ISaveSnippetProps,
-  { selectedAnimations: SelectedAnimation[] }
-> {
-  constructor(props: ISaveSnippetProps) {
-    super(props);
-    let animList = this.props.animations?.map((animation, i) => {
-      return {
-        id: `${animation.name}_${animation.targetProperty}`,
-        name: animation.name,
-        index: i,
-        selected: false,
-      };
-    });
-    this.state = { selectedAnimations: animList ?? [] };
-  }
-
-  handleCheckboxChange(e: React.ChangeEvent<HTMLInputElement>) {
-    e.preventDefault();
-
-    let index = parseInt(e.target.id.replace('save_', ''));
-
-    let updated = this.state.selectedAnimations?.map((item) => {
-      if (item.index === index) {
-        item.selected = !item.selected;
-      }
-      return item;
-    });
-
-    this.setState({ selectedAnimations: updated });
-  }
-
-  stringifySelectedAnimations() {
-    const content: string[] = [];
-    this.state.selectedAnimations.forEach((animation) => {
-      if (animation.selected) {
-        const selected =
-          this.props.animations && this.props.animations[animation.index];
-        if (selected) {
-          content.push(selected.serialize());
-        }
-      }
-    });
-    return JSON.stringify(content);
-  }
-
-  saveToFile() {
-    const content = this.stringifySelectedAnimations();
-    Tools.Download(new Blob([content]), 'animations.json');
-  }
-
-  saveToSnippet() {
-    if (this.props.snippetId !== '') {
-      let serverId = this.props.snippetId;
-      const serverUrl = this.props.snippetServer;
-      const content = this.stringifySelectedAnimations();
-
-      var xmlHttp = new XMLHttpRequest();
-      xmlHttp.onreadystatechange = () => {
-        if (xmlHttp.readyState == 4) {
-          if (xmlHttp.status == 200) {
-            var snippet = JSON.parse(xmlHttp.responseText);
-            const oldId = serverId;
-            serverId = snippet.id;
-            if (snippet.version && snippet.version != '0') {
-              serverId += '#' + snippet.version;
-            }
-            this.forceUpdate();
-            if (navigator.clipboard) {
-              navigator.clipboard.writeText(serverId);
-            }
+export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAnimations: SelectedAnimation[] }> {
+    constructor(props: ISaveSnippetProps) {
+        super(props);
+        let animList = this.props.animations?.map((animation, i) => {
+            return {
+                id: `${animation.name}_${animation.targetProperty}`,
+                name: animation.name,
+                index: i,
+                selected: false,
+            };
+        });
+        this.state = { selectedAnimations: animList ?? [] };
+    }
+
+    handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        e.preventDefault();
 
-            let windowAsAny = window as any;
+        let index = parseInt(e.target.id.replace("save_", ""));
 
-            if (windowAsAny.Playground && oldId) {
-              windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers(
-                {
-                  regex: new RegExp(oldId, 'g'),
-                  replace: serverId,
+        let updated = this.state.selectedAnimations?.map((item) => {
+            if (item.index === index) {
+                item.selected = !item.selected;
+            }
+            return item;
+        });
+
+        this.setState({ selectedAnimations: updated });
+    };
+
+    stringifySelectedAnimations() {
+        const content: string[] = [];
+        this.state.selectedAnimations.forEach((animation) => {
+            if (animation.selected) {
+                const selected = this.props.animations && this.props.animations[animation.index];
+                if (selected) {
+                    content.push(selected.serialize());
                 }
-              );
             }
+        });
+        return JSON.stringify(content);
+    }
 
-            alert(
-              'Animations saved with ID: ' +
-                serverId +
-                ' (please note that the id was also saved to your clipboard)'
-            );
-          } else {
-            alert('Unable to save your animations');
-          }
-        }
-      };
+    saveToFile = () => {
+        const content = this.stringifySelectedAnimations();
+        Tools.Download(new Blob([content]), "animations.json");
+    };
+
+    saveToSnippet = () => {
+        if (this.props.snippetId !== "") {
+            let serverId = this.props.snippetId;
+            const serverUrl = this.props.snippetServer;
+            const content = this.stringifySelectedAnimations();
+
+            var xmlHttp = new XMLHttpRequest();
+            xmlHttp.onreadystatechange = () => {
+                if (xmlHttp.readyState == 4) {
+                    if (xmlHttp.status == 200) {
+                        var snippet = JSON.parse(xmlHttp.responseText);
+                        const oldId = serverId;
+                        serverId = snippet.id;
+                        if (snippet.version && snippet.version != "0") {
+                            serverId += "#" + snippet.version;
+                        }
+                        this.forceUpdate();
+                        if (navigator.clipboard) {
+                            navigator.clipboard.writeText(serverId);
+                        }
+
+                        let windowAsAny = window as any;
+
+                        if (windowAsAny.Playground && oldId) {
+                            windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers({
+                                regex: new RegExp(oldId, "g"),
+                                replace: serverId,
+                            });
+                        }
+
+                        alert(
+                            "Animations saved with ID: " +
+                                serverId +
+                                " (please note that the id was also saved to your clipboard)"
+                        );
+                    } else {
+                        alert("Unable to save your animations");
+                    }
+                }
+            };
 
-      xmlHttp.open('POST', serverUrl + (serverId ? '/' + serverId : ''), true);
-      xmlHttp.setRequestHeader('Content-Type', 'application/json');
+            xmlHttp.open("POST", serverUrl + (serverId ? "/" + serverId : ""), true);
+            xmlHttp.setRequestHeader("Content-Type", "application/json");
 
-      var dataToSend = {
-        payload: JSON.stringify({
-          animations: content,
-        }),
-        name: '',
-        description: '',
-        tags: '',
-      };
+            var dataToSend = {
+                payload: JSON.stringify({
+                    animations: content,
+                }),
+                name: "",
+                description: "",
+                tags: "",
+            };
 
-      xmlHttp.send(JSON.stringify(dataToSend));
+            xmlHttp.send(JSON.stringify(dataToSend));
+        }
+    };
+
+    render() {
+        return (
+            <div className="save-container">
+                <div className="item-list">
+                    <ul>
+                        {this.props.animations?.map((animation, i) => {
+                            return (
+                                <li key={i}>
+                                    <div>
+                                        <label>
+                                            <input
+                                                id={`save_${i}`}
+                                                name={`save_${animation?.name}`}
+                                                type="checkbox"
+                                                checked={this.state.selectedAnimations[i].selected}
+                                                onChange={this.handleCheckboxChange}
+                                            />
+                                            {animation?.name}
+                                        </label>
+                                    </div>
+                                </li>
+                            );
+                        })}
+                    </ul>
+                </div>
+                <div className="save-buttons">
+                    {this.props.snippetId !== "" ? (
+                        <ButtonLineComponent label="Save to snippet server" onClick={this.saveToSnippet} />
+                    ) : null}
+                    <ButtonLineComponent label="Save" onClick={this.saveToFile} />
+                </div>
+                <div className="save-server">
+                    <p>Snippet Server: </p>&nbsp;
+                    <p> {this.props.snippetServer ?? "-"}</p>
+                </div>
+            </div>
+        );
     }
-  }
-
-  render() {
-    return (
-      <div className='save-container'>
-        <div className='item-list'>
-          <ul>
-            {this.props.animations?.map((animation, i) => {
-              return (
-                <li key={i}>
-                  <div>
-                    <label>
-                      <input
-                        id={`save_${i}`}
-                        name={`save_${animation?.name}`}
-                        type='checkbox'
-                        checked={this.state.selectedAnimations[i].selected}
-                        onChange={(e) => this.handleCheckboxChange(e)}
-                      />
-                      {animation?.name}
-                    </label>
-                  </div>
-                </li>
-              );
-            })}
-          </ul>
-        </div>
-        <div className='save-buttons'>
-          {this.props.snippetId !== '' ? (
-            <ButtonLineComponent
-              label='Save to snippet server'
-              onClick={() => this.saveToSnippet()}
-            />
-          ) : null}
-          <ButtonLineComponent label='Save' onClick={() => this.saveToFile()} />
-        </div>
-        <div className='save-server'>
-          <p>Snippet Server: </p>&nbsp;
-          <p> {this.props.snippetServer ?? '-'}</p>
-        </div>
-      </div>
-    );
-  }
 }

+ 34 - 36
inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx

@@ -1,44 +1,42 @@
-import * as React from 'react';
-import { CurveScale } from './animationCurveEditorComponent';
+import * as React from "react";
+import { CurveScale } from "./animationCurveEditorComponent";
 
 interface ISwitchButtonProps {
-  current: CurveScale;
-  action?: (event: CurveScale) => void;
+    current: CurveScale;
+    action?: (event: CurveScale) => void;
 }
 
-export class ScaleLabel extends React.Component<
-  ISwitchButtonProps,
-  { current: CurveScale }
-> {
-  constructor(props: ISwitchButtonProps) {
-    super(props);
-    this.state = { current: this.props.current };
-  }
+export class ScaleLabel extends React.Component<ISwitchButtonProps, { current: CurveScale }> {
+    constructor(props: ISwitchButtonProps) {
+        super(props);
+        this.state = { current: this.props.current };
+    }
 
-  renderLabel(scale: CurveScale) {
-    switch (scale) {
-      case CurveScale.default:
-        return '';
-      case CurveScale.degrees:
-        return 'DEG';
-      case CurveScale.float:
-        return 'FLT';
-      case CurveScale.integers:
-        return 'INT';
-      case CurveScale.radians:
-        return 'RAD';
+    renderLabel(scale: CurveScale) {
+        switch (scale) {
+            case CurveScale.default:
+                return "";
+            case CurveScale.degrees:
+                return "DEG";
+            case CurveScale.float:
+                return "FLT";
+            case CurveScale.integers:
+                return "INT";
+            case CurveScale.radians:
+                return "RAD";
+        }
     }
-  }
 
-  render() {
-    return (
-      <div
-        className='switch-button'
-        onClick={() =>
-          this.props.action && this.props.action(this.state.current)
-        }>
-        <p>{this.renderLabel(this.state.current)}</p>
-      </div>
-    );
-  }
+    onClickHandle = () => {
+        this.props.action && this.props.action(this.state.current);
+    };
+
+    render() {
+        const label = this.renderLabel(this.state.current);
+        return (
+            <div className="switch-button" onClick={this.onClickHandle}>
+                <p>{label}</p>
+            </div>
+        );
+    }
 }

+ 53 - 47
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -62,58 +62,74 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
     }
 
     componentDidUpdate(prevProps: ISvgDraggableAreaProps) {
-        if (this.props.positionCanvas !== prevProps.positionCanvas && this.props.positionCanvas !== undefined && this.props.repositionCanvas) {
+        if (
+            this.props.positionCanvas !== prevProps.positionCanvas &&
+            this.props.positionCanvas !== undefined &&
+            this.props.repositionCanvas
+        ) {
             this.setState({ panX: this.props.positionCanvas.x, panY: this.props.positionCanvas.y }, () => {
                 this.props.canvasPositionEnded();
             });
         }
     }
 
-    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
-    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragStart(e: any): void {
+    dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
-        if (e.target.classList.contains("draggable")) {
+        if ((e.target as SVGSVGElement).classList.contains("draggable")) {
             this._active = true;
-            this._currentPointId = e.target.getAttribute("data-id");
+            const dataId = (e.target as SVGSVGElement).getAttribute("data-id");
+            if (dataId !== null) {
+                this._currentPointId = dataId;
+            }
 
-            if (e.target.classList.contains("control-point")) {
-                this._isCurrentPointControl = e.target.getAttribute("type");
+            if ((e.target as SVGSVGElement).classList.contains("control-point")) {
+                const type = (e.target as SVGSVGElement).getAttribute("type");
+                if (type !== null) {
+                    this._isCurrentPointControl = type;
+                }
             }
         }
 
-        if (e.target.classList.contains("svg-playhead")) {
+        if ((e.target as SVGSVGElement).classList.contains("svg-playhead")) {
             this._active = true;
             this._playheadSelected = true;
             this._playheadDrag = e.clientX - e.currentTarget.getBoundingClientRect().left;
         }
 
-        if (e.target.classList.contains("pannable")) {
+        if ((e.target as SVGSVGElement).classList.contains("pannable")) {
             if (this._isControlKeyPress) {
                 this._active = true;
-                this._panStart.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
+                this._panStart.set(
+                    e.clientX - e.currentTarget.getBoundingClientRect().left,
+                    e.clientY - e.currentTarget.getBoundingClientRect().top
+                );
             }
         }
-    }
+    };
 
-    drag(e: React.TouchEvent<SVGSVGElement>): void;
-    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    drag(e: any): void {
+    drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         if (this._active) {
             e.preventDefault();
 
             var coord = this.getMousePosition(e);
 
             if (coord !== undefined) {
-                if (e.target.classList.contains("pannable")) {
+                if ((e.target as SVGSVGElement).classList.contains("pannable")) {
                     if (this._isControlKeyPress) {
                         if (this._panStart.x !== 0 && this._panStart.y !== 0) {
-                            this._panStop.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
+                            this._panStop.set(
+                                e.clientX - e.currentTarget.getBoundingClientRect().left,
+                                e.clientY - e.currentTarget.getBoundingClientRect().top
+                            );
                             this.panDirection();
                         }
                     }
                 }
-                if (e.currentTarget.classList.contains("linear") && this._playheadDrag !== 0 && this._playheadSelected) {
+                if (
+                    e.currentTarget.classList.contains("linear") &&
+                    this._playheadDrag !== 0 &&
+                    this._playheadSelected
+                ) {
                     const moving = e.clientX - e.currentTarget.getBoundingClientRect().left;
 
                     const distance = moving - this._playheadDrag;
@@ -147,11 +163,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
                 }
             }
         }
-    }
+    };
 
-    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
-    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragEnd(e: any): void {
+    dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this._active = false;
         this._currentPointId = "";
@@ -162,15 +176,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         this._playheadSelected = false;
         this._movedX = 0;
         this._movedY = 0;
-    }
-
-    getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
-    getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined;
-    getMousePosition(e: any): Vector2 | undefined {
-        if (e.touches) {
-            e = e.touches[0];
-        }
+    };
 
+    getMousePosition = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined => {
         if (this._draggableArea.current) {
             var svg = this._draggableArea.current as SVGSVGElement;
             var CTM = svg.getScreenCTM();
@@ -182,7 +190,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         } else {
             return undefined;
         }
-    }
+    };
 
     panDirection() {
         let directionX = 1;
@@ -253,7 +261,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     }
 
-    focus(e: React.MouseEvent<SVGSVGElement>) {
+    focus = (e: React.MouseEvent<SVGSVGElement>) => {
         e.preventDefault();
         this._draggableArea.current?.focus();
 
@@ -264,7 +272,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
 
             this.props.resetActionableKeyframe();
         }
-    }
+    };
 
     isNotControlPointActive() {
         const activeControlPoints = this.props.keyframeSvgPoints.filter((x) => x.isLeftActive || x.isRightActive);
@@ -276,6 +284,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
     }
 
     render() {
+        const viewBoxScaling = `${this.state.panX} ${this.state.panY} ${Math.round(
+            this.props.scale * 200
+        )} ${Math.round(this.props.scale * 100)}`;
         return (
             <>
                 <svg
@@ -285,24 +296,19 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
                         position: "absolute",
                         zIndex: 1,
                         pointerEvents: "none",
-                    }}
-                >
+                    }}>
                     <rect x="0" y="0" width="38px" height="100%" fill="rgb(188 188 188 / 11%)"></rect>
                 </svg>
                 <svg
                     className="linear pannable"
                     ref={this._draggableArea}
                     tabIndex={0}
-                    onMouseMove={(e) => this.drag(e)}
-                    onTouchMove={(e) => this.drag(e)}
-                    onTouchStart={(e) => this.dragStart(e)}
-                    onTouchEnd={(e) => this.dragEnd(e)}
-                    onMouseDown={(e) => this.dragStart(e)}
-                    onMouseUp={(e) => this.dragEnd(e)}
-                    onMouseLeave={(e) => this.dragEnd(e)}
-                    onClick={(e) => this.focus(e)}
-                    viewBox={`${this.state.panX} ${this.state.panY} ${Math.round(this.props.scale * 200)} ${Math.round(this.props.scale * 100)}`}
-                >
+                    onMouseMove={this.drag}
+                    onMouseDown={this.dragStart}
+                    onMouseUp={this.dragEnd}
+                    onMouseLeave={this.dragEnd}
+                    onClick={this.focus}
+                    viewBox={viewBoxScaling}>
                     {this.props.children}
 
                     {this.props.keyframeSvgPoints.map((keyframe, i) => (
@@ -315,8 +321,8 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
                             isLeftActive={keyframe.isLeftActive}
                             isRightActive={keyframe.isRightActive}
                             selected={keyframe.selected}
-                            selectedControlPoint={(type: string, id: string) => this.props.selectedControlPoint(type, id)}
-                            selectKeyframe={(id: string, multiselect: boolean) => this.props.selectKeyframe(id, multiselect)}
+                            selectedControlPoint={this.props.selectedControlPoint}
+                            selectKeyframe={this.props.selectKeyframe}
                         />
                     ))}
                 </svg>

+ 12 - 14
inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx

@@ -1,9 +1,7 @@
 import * as React from "react";
-
 import { Observable } from "babylonjs/Misc/observable";
 import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
 import { Scene } from "babylonjs/scene";
-
 import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
 import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
 import { LineContainerComponent } from "../../../lineContainerComponent";
@@ -35,18 +33,18 @@ export class TargetedAnimationGridComponent extends React.Component<ITargetedAni
         });
     }
 
-    onOpenAnimationCurveEditor() {
+    onOpenAnimationCurveEditor = () => {
         this._isCurveEditorOpen = true;
-    }
+    };
 
-    onCloseAnimationCurveEditor(window: Window | null) {
+    onCloseAnimationCurveEditor = (window: Window | null) => {
         this._isCurveEditorOpen = false;
         if (window !== null) {
             window.close();
         }
-    }
+    };
 
-    playOrPause() {
+    playOrPause = () => {
         if (this._animationGroup) {
             if (this._animationGroup.isPlaying) {
                 this._animationGroup.stop();
@@ -55,9 +53,9 @@ export class TargetedAnimationGridComponent extends React.Component<ITargetedAni
             }
             this.forceUpdate();
         }
-    }
+    };
 
-    deleteAnimation() {
+    deleteAnimation = () => {
         if (this._animationGroup) {
             let index = this._animationGroup.targetedAnimations.indexOf(this.props.targetedAnimation);
 
@@ -71,7 +69,7 @@ export class TargetedAnimationGridComponent extends React.Component<ITargetedAni
                 }
             }
         }
-    }
+    };
 
     render() {
         const targetedAnimation = this.props.targetedAnimation;
@@ -82,13 +80,13 @@ export class TargetedAnimationGridComponent extends React.Component<ITargetedAni
                     <TextLineComponent label="Class" value={targetedAnimation.getClassName()} />
                     <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={targetedAnimation.animation} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {targetedAnimation.target.name && <TextLineComponent label="Target" value={targetedAnimation.target.name} onLink={() => this.props.globalState.onSelectionChangedObservable.notifyObservers(targetedAnimation)} />}
-                    <ButtonLineComponent label="Edit animation" onClick={() => this.onOpenAnimationCurveEditor()} />
+                    <ButtonLineComponent label="Edit animation" onClick={this.onOpenAnimationCurveEditor} />
                     {this._isCurveEditorOpen && (
-                        <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
-                            <AnimationCurveEditorComponent scene={this.props.scene} entity={targetedAnimation as any} playOrPause={() => this.playOrPause()} lockObject={this.props.lockObject} globalState={this.props.globalState} />
+                        <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={this.onCloseAnimationCurveEditor}>
+                            <AnimationCurveEditorComponent scene={this.props.scene} entity={targetedAnimation as any} playOrPause={this.playOrPause} lockObject={this.props.lockObject} globalState={this.props.globalState} />
                         </PopupComponent>
                     )}
-                    <ButtonLineComponent label="Dispose" onClick={() => this.deleteAnimation()} />
+                    <ButtonLineComponent label="Dispose" onClick={this.deleteAnimation} />
                 </LineContainerComponent>
             </div>
         );

+ 29 - 53
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -141,7 +141,7 @@ export class Timeline extends React.Component<
         }
     }
 
-    setCurrentFrame(event: React.MouseEvent<HTMLDivElement>) {
+    setCurrentFrame = (event: React.MouseEvent<HTMLDivElement>) => {
         event.preventDefault();
         if (this._scrollable.current) {
             const containerWidth = this._scrollable.current?.clientWidth;
@@ -149,7 +149,7 @@ export class Timeline extends React.Component<
             const frame = Math.round((event.clientX - 233) / unit) + this.state.start;
             this.props.onCurrentFrameChange(frame);
         }
-    }
+    };
 
     handleLimitChange(event: React.ChangeEvent<HTMLInputElement>) {
         event.preventDefault();
@@ -162,20 +162,16 @@ export class Timeline extends React.Component<
         });
     }
 
-    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
-    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragStart(e: any): void {
+    dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
-        this.setState({ activeKeyframe: parseInt(e.target.id.replace("kf_", "")) });
+        this.setState({ activeKeyframe: parseInt((e.target as SVGSVGElement).id.replace("kf_", "")) });
         this._direction = e.clientX;
-    }
+    };
 
-    drag(e: React.TouchEvent<SVGSVGElement>): void;
-    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    drag(e: any): void {
+    drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         if (this.props.keyframes) {
-            if (this.state.activeKeyframe === parseInt(e.target.id.replace("kf_", ""))) {
+            if (this.state.activeKeyframe === parseInt((e.target as SVGSVGElement).id.replace("kf_", ""))) {
                 let updatedKeyframe = this.props.keyframes[this.state.activeKeyframe];
                 if (this._direction > e.clientX) {
                     let used = this.isFrameBeingUsed(updatedKeyframe.frame - 1, -1);
@@ -192,7 +188,7 @@ export class Timeline extends React.Component<
                 this.props.dragKeyframe(updatedKeyframe.frame, this.state.activeKeyframe);
             }
         }
-    }
+    };
 
     isFrameBeingUsed(frame: number, direction: number) {
         let used = this.props.keyframes?.find((kf) => kf.frame === frame);
@@ -204,42 +200,36 @@ export class Timeline extends React.Component<
         }
     }
 
-    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
-    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragEnd(e: any): void {
+    dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this._direction = 0;
         this.setState({ activeKeyframe: null });
-    }
+    };
 
-    scrollDragStart(e: React.TouchEvent<HTMLDivElement>): void;
-    scrollDragStart(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-    scrollDragStart(e: any) {
+    scrollDragStart = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
-        if (e.target.className === "scrollbar") {
-            if ((e.target.class = "scrollbar") && this._scrollbarHandle.current) {
+        if ((e.target as HTMLDivElement).className === "scrollbar") {
+            if (this._scrollbarHandle.current) {
                 this._scrolling = true;
                 this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left;
                 this._scrollbarHandle.current.style.left = e.pageX - this._shiftX + "px";
             }
         }
 
-        if (e.target.className === "left-draggable" && this._scrollbarHandle.current) {
+        if ((e.target as HTMLDivElement).className === "left-draggable" && this._scrollbarHandle.current) {
             this._active = "leftDraggable";
             this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left;
         }
 
-        if (e.target.className === "right-draggable" && this._scrollbarHandle.current) {
+        if ((e.target as HTMLDivElement).className === "right-draggable" && this._scrollbarHandle.current) {
             this._active = "rightDraggable";
             this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left;
         }
-    }
+    };
 
-    scrollDrag(e: React.TouchEvent<HTMLDivElement>): void;
-    scrollDrag(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-    scrollDrag(e: any) {
+    scrollDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
-        if (e.target.className === "scrollbar") {
+        if ((e.target as HTMLDivElement).className === "scrollbar") {
             this.moveScrollbar(e.pageX);
         }
 
@@ -250,16 +240,14 @@ export class Timeline extends React.Component<
         if (this._active === "rightDraggable") {
             this.resizeScrollbarRight(e.clientX);
         }
-    }
+    };
 
-    scrollDragEnd(e: React.TouchEvent<HTMLDivElement>): void;
-    scrollDragEnd(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-    scrollDragEnd(e: any) {
+    scrollDragEnd = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
         this._scrolling = false;
         this._active = "";
         this._shiftX = 0;
-    }
+    };
 
     moveScrollbar(pageX: number) {
         if (this._scrolling && this._scrollbarHandle.current && this._scrollContainer.current) {
@@ -364,6 +352,8 @@ export class Timeline extends React.Component<
         }
     }
 
+    dragDomFalse = () => false;
+
     render() {
         return (
             <>
@@ -379,21 +369,17 @@ export class Timeline extends React.Component<
                         scrollable={this._scrollable}
                     />
                     <div className="timeline-wrapper">
-                        <div ref={this._scrollable} className="display-line" onClick={(e) => this.setCurrentFrame(e)}>
+                        <div ref={this._scrollable} className="display-line" onClick={this.setCurrentFrame}>
                             <svg
                                 style={{
                                     width: "100%",
                                     height: 40,
                                     backgroundColor: "#222222",
                                 }}
-                                onMouseMove={(e) => this.drag(e)}
-                                onTouchMove={(e) => this.drag(e)}
-                                onTouchStart={(e) => this.dragStart(e)}
-                                onTouchEnd={(e) => this.dragEnd(e)}
-                                onMouseDown={(e) => this.dragStart(e)}
-                                onMouseUp={(e) => this.dragEnd(e)}
-                                onMouseLeave={(e) => this.dragEnd(e)}
-                                onDragStart={() => false}
+                                onMouseMove={this.drag}
+                                onMouseDown={this.dragStart}
+                                onMouseUp={this.dragEnd}
+                                onMouseLeave={this.dragEnd}
                             >
                                 {this.state.selectionLength.map((frame, i) => {
                                     return (
@@ -436,17 +422,7 @@ export class Timeline extends React.Component<
                             </svg>
                         </div>
 
-                        <div
-                            className="timeline-scroll-handle"
-                            onMouseMove={(e) => this.scrollDrag(e)}
-                            onTouchMove={(e) => this.scrollDrag(e)}
-                            onTouchStart={(e) => this.scrollDragStart(e)}
-                            onTouchEnd={(e) => this.scrollDragEnd(e)}
-                            onMouseDown={(e) => this.scrollDragStart(e)}
-                            onMouseUp={(e) => this.scrollDragEnd(e)}
-                            onMouseLeave={(e) => this.scrollDragEnd(e)}
-                            onDragStart={() => false}
-                        >
+                        <div className="timeline-scroll-handle" onMouseMove={this.scrollDrag} onMouseDown={this.scrollDragStart} onMouseUp={this.scrollDragEnd} onMouseLeave={this.scrollDragEnd} onDragStart={this.dragDomFalse}>
                             <div className="scroll-handle" ref={this._scrollContainer}>
                                 <div className="handle" ref={this._scrollbarHandle} style={{ width: this.state.scrollWidth }}>
                                     <div className="left-grabber">