From e02cbd302752eb16f581ce43f4341201bca89b02 Mon Sep 17 00:00:00 2001
From: fingerpich <zarei.bs@gmail.com>
Date: Sun, 6 Jan 2019 15:34:45 +0330
Subject: [PATCH] #4 added option to change the play speed

---
 package-lock.json                          | 14 ++++++++++
 package.json                               |  1 +
 src/app/app.module.ts                      |  2 ++
 src/app/app.service.ts                     | 23 +++++++++++++---
 src/app/container/container.component.html | 10 +++++--
 src/app/container/container.component.scss | 31 ++++++++++++++++++----
 src/app/container/container.component.ts   | 14 +++++++++-
 src/app/node-types/create.ts               | 17 +++++++-----
 src/app/node-types/rxNode.ts               |  2 +-
 src/app/node-types/subscribe.ts            | 19 +++++++------
 src/app/scene/result-animator.ts           | 17 ++++++++----
 src/app/scene/result-path.ts               |  5 ++++
 src/app/scene/result.ts                    |  1 +
 13 files changed, 122 insertions(+), 34 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index b4e10f0..00d0b6e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3408,6 +3408,11 @@
       "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
       "dev": true
     },
+    "detect-passive-events": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/detect-passive-events/-/detect-passive-events-1.0.4.tgz",
+      "integrity": "sha1-btR35uW863kHlzXc01d4nTf5qRo="
+    },
     "di": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
@@ -7536,6 +7541,15 @@
       "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
       "dev": true
     },
+    "ng5-slider": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/ng5-slider/-/ng5-slider-1.1.11.tgz",
+      "integrity": "sha512-P8bPFvx6qC5ogvvdkSZILBkDEAQcAX3QxAi1E3Fwaw0/xe7P3JCbJzWW2Z6vCR96PN0Og4wy3cEqnW8l0VAwUg==",
+      "requires": {
+        "detect-passive-events": "^1.0.4",
+        "tslib": "^1.7.1"
+      }
+    },
     "ngx-clipboard": {
       "version": "11.1.9",
       "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-11.1.9.tgz",
diff --git a/package.json b/package.json
index 9911846..51ab671 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
     "@angular/service-worker": "^7.1.1",
     "core-js": "^2.4.1",
     "d3": "^5.7.0",
+    "ng5-slider": "^1.1.11",
     "ngx-clipboard": "^11.1.9",
     "rxjs": "^6.3.3",
     "rxjs-compat": "^6.0.0-rc.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index c695e92..67e0ac6 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -12,6 +12,7 @@ import { SceneComponent } from './scene/scene.component';
 import { FlexLayoutModule } from '@angular/flex-layout';
 import {AppService} from './app.service';
 import { ClipboardModule } from 'ngx-clipboard';
+import { Ng5SliderModule } from 'ng5-slider';
 import { PropertyComponentComponent } from './property-inspector/property-component/property-component.component';
 import { ContainerComponent } from './container/container.component';
 import { HeaderComponent } from './container/header/header.component';
@@ -36,6 +37,7 @@ import { environment } from '../environments/environment';
     FormsModule,
     HttpModule,
     ClipboardModule,
+    Ng5SliderModule,
     FlexLayoutModule,
     ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
   ],
diff --git a/src/app/app.service.ts b/src/app/app.service.ts
index d96d01b..e9206cd 100644
--- a/src/app/app.service.ts
+++ b/src/app/app.service.ts
@@ -5,12 +5,17 @@ import {Operator} from './operator';
 import {DiagramNode} from './scene/diagram-node';
 import resultAnimator from './scene/result-animator';
 import {DiagramEdge} from './scene/diagram-edge';
+import {Subscribe} from './node-types';
+import {zip} from 'rxjs';
+import {take} from 'rxjs/operators';
+import {Observable} from 'rxjs/Observable';
 
 @Injectable()
 export class AppService {
   private selectedCreationOption: any;
   private selectItemSubject: Subject<Operator>;
   private controlSubject: Subject<string>;
+  private animationHasFinished: Boolean = false;
 
   private nodesList: Array<DiagramNode> = [];
   private edgeList: Array<DiagramEdge> = [];
@@ -62,7 +67,7 @@ export class AppService {
     const xLoc = window.innerWidth / (window.innerWidth < 600 ? 2 : 3);
     const yLoc = 100;
     const nodes = [
-      {id: 0, x: xLoc, y: yLoc, node_type: 'Create', properties: {list: [{time: 0, value: 1}]}},
+      {id: 0, x: xLoc, y: yLoc, node_type: 'Create', properties: {items: [{time: 0, value: 1}]}},
       {id: 1, x: xLoc, y: yLoc + 200, node_type: 'Subscribe', properties: {}}
     ];
     const edges = [{source: 0, target: 1}];
@@ -74,13 +79,15 @@ export class AppService {
   }
   public set delay(value) {
     GraphCreator.animateTime = value;
-    this.refreshRxObjects();
+    if (this.animationHasFinished) {
+      this.refreshRxObjects();
+    }
   }
 
-
   public refreshRxObjects() {
     const nodes = this.nodesList;
     const edges = this.edgeList;
+    this.animationHasFinished = false;
 
     // DISPOSE created rx objects
     for (const node of nodes) {
@@ -115,5 +122,15 @@ export class AppService {
         }
       }
     }
+
+    const finishSubjects: Array<Subject<object>> = nodes
+      .filter(n => n.data.title === 'Subscribe')
+      .map(subscribeNode => (subscribeNode.data as Subscribe).finishSubject);
+
+    zip(...finishSubjects).pipe(take(1)).subscribe(() => {
+      resultAnimator.hasFinished.pipe(take(1)).subscribe(() => {
+        this.animationHasFinished = true;
+      });
+    });
   }
 }
diff --git a/src/app/container/container.component.html b/src/app/container/container.component.html
index 32dc1d1..c629ece 100644
--- a/src/app/container/container.component.html
+++ b/src/app/container/container.component.html
@@ -4,12 +4,18 @@
     <div fxFlex class="sceneContainer">
       <button (click)="showCreationMenuToggle()" [class.active]="showCreationMenu" title="Tools (shortcut : Shift)" class="add">
         <svg id="Layer_1" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44.74 44.75"><title>Tools (shortcut : Shift)</title><path d="M682.37,362a22.38,22.38,0,1,0,22.37,22.37A22.4,22.4,0,0,0,682.37,362Zm0,40.59a18.22,18.22,0,1,1,18.22-18.22,18.24,18.24,0,0,1-18.22,18.22Zm0,0" transform="translate(-660 -362)"/><path d="M692.64,382.47h-8.37V374.1a1.9,1.9,0,1,0-3.8,0v8.37H672.1a1.9,1.9,0,0,0,0,3.8h8.37v8.37a1.9,1.9,0,0,0,3.8,0v-8.37h8.37a1.9,1.9,0,0,0,0-3.8Zm0,0" transform="translate(-660 -362)"/></svg>
-        <!--<i class="material-icons">add</i>-->
       </button>
       <button (click)="replay()" title="Replay(shortcut : Enter)" class="replay">
         <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 226 226"><title>Replay(shortcut : Enter)</title><path d="M150.27,108.56,103.34,74.48a5.63,5.63,0,0,0-9,4.56v68.2a5.58,5.58,0,0,0,5.66,5.65,5.59,5.59,0,0,0,3.3-1.1l46.93-34.08a5.56,5.56,0,0,0,2.35-4.55,5.67,5.67,0,0,0-2.35-4.6Zm0,0" transform="translate(0)"/><path d="M113,0A113,113,0,1,0,226,113,113,113,0,0,0,113,0Zm0,207.13A94.13,94.13,0,1,1,207.12,113,94.12,94.12,0,0,1,113,207.13Zm0,0" transform="translate(0)"/></svg>
-        <!--<i class="material-icons">play_circle_outline</i>-->
       </button>
+      <button (click)="showSlider = true" title="Time Setting" class="showTimeSetting">
+        <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 29"><title>Play speed</title><path d="M299,286a3,3,0,0,0-2,5.18,12,12,0,1,0,8.46,1.7l.51-.89.43.25.5-.86-.43-.25-.86-.5-.43-.25-.5.86.43.25-.51.88a12.09,12.09,0,0,0-3.52-1.19,3,3,0,0,0-2-5.18Zm0,1a2,2,0,0,1,.5,3.93V290h.5v-1h-2v1h.5v.93A2,2,0,0,1,299,287Zm0,5a10.93,10.93,0,0,1,5.22,1.32l.09.05A11,11,0,1,1,299,292Zm0,1a10,10,0,1,0,10,10,10,10,0,0,0-10-10Zm0,1a9,9,0,1,1-9,9,9,9,0,0,1,9-9Zm0,1a.5.5,0,1,0,.5.5.5.5,0,0,0-.5-.5Zm-3.76,1a.5.5,0,0,0-.24.06.5.5,0,0,0-.18.68.5.5,0,1,0,.42-.75Zm7.49,0a.48.48,0,0,0-.41.24.5.5,0,1,0,.86.5.5.5,0,0,0-.18-.68.43.43,0,0,0-.27-.06Zm-4.23,1v4.59a1.5,1.5,0,1,0,1,0V297Zm-6,1.75a.51.51,0,0,0-.41.25.5.5,0,0,0,.18.68.5.5,0,1,0,.5-.86.52.52,0,0,0-.27-.07Zm13,0a.39.39,0,0,0-.23.07.5.5,0,1,0,.68.18.52.52,0,0,0-.45-.25Zm-14,3.75a.5.5,0,1,0,.5.5.5.5,0,0,0-.5-.5Zm7.5,0a.5.5,0,1,1-.5.5.5.5,0,0,1,.5-.5Zm7.5,0a.5.5,0,1,0,.5.5.5.5,0,0,0-.5-.5Zm-14,3.75a.44.44,0,0,0-.24.07.5.5,0,1,0,.24-.07Zm13,0a.5.5,0,0,0-.22.93.5.5,0,1,0,.5-.86.55.55,0,0,0-.28-.07ZM295.23,309a.5.5,0,0,0-.23.93.5.5,0,0,0,.68-.18.49.49,0,0,0-.45-.75Zm7.51,0a.5.5,0,1,0,.26.93.5.5,0,0,0,.18-.68.48.48,0,0,0-.44-.25ZM299,310a.5.5,0,1,0,.5.5.5.5,0,0,0-.5-.5Zm0,0" transform="translate(-287 -286)"/></svg>
+      </button>
+      <div class="slider-container" *ngIf="showSlider">
+        <span class="close" (click)="showSlider=false">&times;</span>
+        <b>Player speed</b>
+        <ng5-slider (userChange)="sliderChanged()" [(value)]="speed.value" [options]="speed.options"></ng5-slider>
+      </div>
       <rxstudio-scene fxFlex></rxstudio-scene>
       <rxstudio-creation-menu (onSelect)="showCreationMenu=false" *ngIf="showCreationMenu"></rxstudio-creation-menu>
     </div>
diff --git a/src/app/container/container.component.scss b/src/app/container/container.component.scss
index f5e94ee..013014e 100644
--- a/src/app/container/container.component.scss
+++ b/src/app/container/container.component.scss
@@ -4,7 +4,10 @@
   height:100%;
   padding:0;
   overflow: hidden;
-
+  .close{
+    cursor: pointer;
+    float: right;
+  }
   .sceneContainer {
     position: relative;
     border:1px solid $borderColor;
@@ -13,6 +16,14 @@
       height: 100%;
       position: absolute;
     }
+    .slider-container{
+      width: 300px;
+      right: 0;
+      position: absolute;
+      top: 0;
+      background: white;
+      z-index: 1;
+    }
     button {
       border: none;
       border-radius: 50%;
@@ -29,10 +40,14 @@
       -ms-transition: padding 0.3s;
       -o-transition: padding 0.3s;
       transition: padding 0.3s;
-      &:hover{
-        //color:lighten($primaryColor, 10%);
-        fill:$primaryColor;
-        padding: 0rem;
+      &.showTimeSetting{
+        right: 0;
+        top: 0;
+        padding: 4px;
+        height: 1.6rem;
+        box-sizing: border-box;
+        width: 1.6rem;
+        border-radius: 16px;
       }
       &.replay{
         right: 1rem;
@@ -45,6 +60,12 @@
         right: 1rem;
         bottom: 1rem;
       }
+      &:hover{
+        //color:lighten($primaryColor, 10%);
+        fill:$primaryColor;
+        padding: 0rem;
+      }
+
     }
   }
   rxstudio-creation-menu{
diff --git a/src/app/container/container.component.ts b/src/app/container/container.component.ts
index 193a4a1..03dff97 100644
--- a/src/app/container/container.component.ts
+++ b/src/app/container/container.component.ts
@@ -15,8 +15,20 @@ export class ContainerComponent implements OnInit {
 
   showColdStream;
   showCreationMenu = false;
-
+  showSlider = false;
+  speed = {
+    value: 6,
+    options: {
+      floor: 1,
+      ceil: 10
+    }
+  }
   constructor(private appService: AppService, private route: ActivatedRoute) {
+    this.sliderChanged();
+  }
+
+  sliderChanged () {
+    this.appService.delay = Math.ceil(Math.pow(1.24, this.speed.value * 3 + 10));
   }
 
   @HostListener('window:keyup', ['$event'])
diff --git a/src/app/node-types/create.ts b/src/app/node-types/create.ts
index 04fe4ca..bb9e1b1 100644
--- a/src/app/node-types/create.ts
+++ b/src/app/node-types/create.ts
@@ -9,8 +9,7 @@ export class Create extends RxNode {
   protected static maxInput = 0;
   protected static minInput = 0;
 
-  // protected static propertiesType = [{name:'list',type: 'list', params:[{name:'time',type:'Number'},{name:'value',type:'Number'}]}];
-  protected static propertiesType = new PropertyType('list', PropertyTypeEnum.List,
+  protected static propertiesType = new PropertyType('items', PropertyTypeEnum.List,
     new PropertyType('', PropertyTypeEnum.Object, [
       new PropertyType('time', PropertyTypeEnum.Number),
       new PropertyType('value', PropertyTypeEnum.Number)
@@ -18,26 +17,30 @@ export class Create extends RxNode {
   , '');
 
   public properties = {
-    list: [
+    items: [
       {time: 0, value: 1}
     ]
   };
   public graphInputs = [];
 
   public runner = () => {
-    const delay = (observer, delayTime, value) => {
+    const delay = (observer, delayTime, value, isLastOne) => {
       setTimeout(() => {
         observer.next(value);
+        if (isLastOne) {
+          observer.complete();
+        }
       }, delayTime || 0);
     };
     return Observable.create((observer) => {
-      for (const l of this.properties.list) {
-        delay(observer, l.time, l.value);
+      const maxTimeItem = this.properties.items.reduce((max, item) => max.time <= item.time ? item : max, {time: 0});
+      for (const item of this.properties.items) {
+        delay(observer, item.time, item.value, maxTimeItem === item);
       }
     });
   }
   public toString = () => {
-    const list = this.properties.list;
+    const list = this.properties.items;
     const getNext = ({value, time}) => {
       return time ? `setTimeout(function(){ observer.next(${value});}, ${time})` : `observer.next(${value});`;
     };
diff --git a/src/app/node-types/rxNode.ts b/src/app/node-types/rxNode.ts
index 754093d..31882a6 100644
--- a/src/app/node-types/rxNode.ts
+++ b/src/app/node-types/rxNode.ts
@@ -55,7 +55,7 @@ export class RxNode implements Operator {
           timeoutStep = level;
         }
       }
-      resultAnimator.add(<Result>{node, numberInfo: xx, timeoutStep});
+      resultAnimator.add(<Result>{node, numberInfo: xx, timeoutStep, });
       return xx;
     }));
     this.level = level;
diff --git a/src/app/node-types/subscribe.ts b/src/app/node-types/subscribe.ts
index 52720e0..ecfba3c 100644
--- a/src/app/node-types/subscribe.ts
+++ b/src/app/node-types/subscribe.ts
@@ -1,6 +1,7 @@
 import {RxNode} from './rxNode';
 import {map} from 'rxjs/operators';
 import {NumberInfo} from '../scene/number-info';
+import {Subject} from 'rxjs/Subject';
 
 export class Subscribe extends RxNode {
   protected static title = 'Subscribe';
@@ -12,6 +13,7 @@ export class Subscribe extends RxNode {
 
   public properties = {};
   public graphInputs = [];
+  public finishSubject = new Subject();
 
   public runner = () => {
     // const thisObservable = this.graphInputs[0];
@@ -20,16 +22,13 @@ export class Subscribe extends RxNode {
       return x;
     }));
     setTimeout(() => {
-      this.rxo = this.rx.subscribe(
-        function (x) {
-          console.log('Next: %s', x);
-        }, /* on next*/
-        function (err) {
-          console.log('Error: %s', err);
-        }, /* on error*/
-        function () {
-          console.log('Completed');
-          /* on complete*/
+      this.rxo = this.rx.subscribe((x) => {
+          console.log('asc');
+        },
+        (err) => {
+          // TODO: show errors in properties
+        }, () => {
+          this.finishSubject.next({finished: true});
         });
     });
     return thisObservable;
diff --git a/src/app/scene/result-animator.ts b/src/app/scene/result-animator.ts
index 818d8fc..f611919 100644
--- a/src/app/scene/result-animator.ts
+++ b/src/app/scene/result-animator.ts
@@ -1,18 +1,19 @@
 import {ResultPath} from './result-path';
 import {Result} from './result';
-import {timer} from 'rxjs';
 import {Subject} from 'rxjs/Subject';
 
 class ResultAnimator {
   resultPathArray: Array<ResultPath>;
   resultChanged = new Subject();
-  subscription;
+  hasFinished = new Subject();
+  timeoutRef;
 
   constructor() {
     this.reset();
   }
 
   add (res: Result) {
+    res.lastTicks = 0;
     const matchedNumInfo = this.resultPathArray.find(resPath => (resPath.id === res.numberInfo.id));
     if (matchedNumInfo) {
       matchedNumInfo.add(res);
@@ -35,12 +36,18 @@ class ResultAnimator {
       .map(resultPath => resultPath.getThisClockResult())
       .filter(result => !!result);
     this.resultChanged.next(resultArray);
-    this.subscription = setTimeout(() => this.tick(true, delay), delay);
+    this.timeoutRef = setTimeout(() => this.tick(true, delay), delay);
+    const hasFinished = this.resultPathArray.length && this.resultPathArray.reduce((acc, result) => {
+      return result.hasFinished() && acc;
+    }, true);
+    if (hasFinished) {
+      this.hasFinished.next({finished: true});
+    }
   }
 
   stop() {
-    if (this.subscription) {
-      clearTimeout(this.subscription);
+    if (this.timeoutRef) {
+      clearTimeout(this.timeoutRef);
     }
   }
 }
diff --git a/src/app/scene/result-path.ts b/src/app/scene/result-path.ts
index 6253aa5..37337f5 100644
--- a/src/app/scene/result-path.ts
+++ b/src/app/scene/result-path.ts
@@ -13,6 +13,10 @@ export class ResultPath {
     this.path.push(next);
   }
 
+  hasFinished() {
+    return this.path.length === 1 && this.path[0].lastTicks > 1;
+  }
+
   getThisClockResult() {
     if (this.path.length) {
       if (this.path[0].timeoutStep > 1) {
@@ -21,6 +25,7 @@ export class ResultPath {
         if (this.path.length > 1) {
           return this.path.shift();
         } else {
+          this.path[0].lastTicks++;
           return this.path[0];
         }
       }
diff --git a/src/app/scene/result.ts b/src/app/scene/result.ts
index 51d5925..1a55e7b 100644
--- a/src/app/scene/result.ts
+++ b/src/app/scene/result.ts
@@ -5,4 +5,5 @@ export interface Result {
   numberInfo: NumberInfo;
   node: DiagramNode;
   timeoutStep: number;
+  lastTicks: number;
 }