changeset 383:1241ca979fd9

Refactor based on pattern which emerged when implementing multiple components. Still some very obvious dupe regarding the ElementRef stuff, I don't think ViewChild decorated props are inherited.. but I haven't actually verified that.
author Lucas Thompson <dev@lucas.im>
date Wed, 31 May 2017 19:14:46 +0100
parents c1cedba9557b
children 7119d62121f0
files src/app/visualisations/grid/grid.component.ts src/app/visualisations/instants/instants.component.ts src/app/visualisations/notes/notes.component.ts src/app/visualisations/tracks/tracks.components.ts src/app/visualisations/waveform/waveform.component.ts src/app/visualisations/waves-base.component.ts
diffstat 6 files changed, 134 insertions(+), 158 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/visualisations/grid/grid.component.ts	Wed May 31 17:33:23 2017 +0100
+++ b/src/app/visualisations/grid/grid.component.ts	Wed May 31 19:14:46 2017 +0100
@@ -21,37 +21,29 @@
   styleUrls: ['../waves-template.css'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class GridComponent extends WavesComponent implements AfterViewInit {
+export class GridComponent extends WavesComponent<MatrixFeature> {
+
   @ViewChild('track') trackDiv: ElementRef;
 
-  private mFeature: MatrixFeature;
-  private height: number; // As it stands, height is fixed. Store once onInit.
-
   @Input() set grid(grid: MatrixFeature) {
-    this.mFeature = grid;
-    this.update();
+    this.feature = grid;
   }
 
-  get grid(): MatrixFeature {
-    return this.mFeature;
+  protected get containerHeight(): number {
+    return this.trackDiv.nativeElement.getBoundingClientRect().height;
   }
 
-  ngAfterViewInit(): void {
-    this.height = this.trackDiv.nativeElement.getBoundingClientRect().height;
-    this.renderTimeline(this.trackDiv);
-    this.update();
+  protected get trackContainer(): ElementRef {
+    return this.trackDiv;
   }
 
-  update(): void {
-    if (!this.waveTrack || !this.grid) { return; }
-    this.clearTimeline(this.trackDiv);
-
-    const startTime = this.grid.startTime; // !!! + make use of
-    const stepDuration = this.grid.stepDuration;
-    const matrixData = this.grid.data;
+  protected get featureLayers(): Layer[] {
+    const startTime = this.feature.startTime; // !!! + make use of
+    const stepDuration = this.feature.stepDuration;
+    const matrixData = this.feature.data;
 
     if (matrixData.length === 0) {
-      return;
+      return [];
     }
 
     const targetValue = estimatePercentile(matrixData, 95);
@@ -62,7 +54,7 @@
       stepDuration
     );
 
-    this.addLayer(
+    return [
       new Waves.helpers.MatrixLayer(
         matrixEntity,
         {
@@ -71,9 +63,7 @@
           normalise: 'none',
           mapper: iceMapper()
         }
-      ),
-      this.waveTrack,
-      this.timeline.timeContext
-    );
+      )
+    ];
   }
 }
--- a/src/app/visualisations/instants/instants.component.ts	Wed May 31 17:33:23 2017 +0100
+++ b/src/app/visualisations/instants/instants.component.ts	Wed May 31 19:14:46 2017 +0100
@@ -19,43 +19,33 @@
   styleUrls: ['../waves-template.css'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class InstantsComponent extends WavesComponent implements AfterViewInit {
+export class InstantsComponent extends WavesComponent<Instant[]> {
+
   @ViewChild('track') trackDiv: ElementRef;
 
-  private mFeature: Instant[];
-  private height: number; // As it stands, height is fixed. Store once onInit.
-
   @Input() set instants(instants: Instant[]) {
-    this.mFeature = instants;
-    this.update();
+    this.feature = instants;
   }
 
-  get instants(): Instant[] {
-    return this.mFeature;
+  protected get containerHeight(): number {
+    return this.trackDiv.nativeElement.getBoundingClientRect().height;
   }
 
-  ngAfterViewInit(): void {
-    this.height = this.trackDiv.nativeElement.getBoundingClientRect().height;
-    this.renderTimeline(this.trackDiv);
-    this.update();
+  protected get trackContainer(): ElementRef {
+    return this.trackDiv;
   }
 
-  update(): void {
-    if (!this.waveTrack || !this.instants) { return; }
-    this.clearTimeline(this.trackDiv);
-
-    this.addLayer(
+  protected get featureLayers(): Layer[] {
+    return [
       new Waves.helpers.TickLayer(
-        this.instants,
+        this.feature,
         {
           height: this.height,
           color: this.colour,
           labelPosition: 'bottom',
           shadeSegments: true
         }
-      ),
-      this.waveTrack,
-      this.timeline.timeContext
-    );
+      )
+    ];
   }
 }
--- a/src/app/visualisations/notes/notes.component.ts	Wed May 31 17:33:23 2017 +0100
+++ b/src/app/visualisations/notes/notes.component.ts	Wed May 31 19:14:46 2017 +0100
@@ -19,44 +19,32 @@
   styleUrls: ['../waves-template.css'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class NotesComponent extends WavesComponent implements AfterViewInit {
-
+export class NotesComponent extends WavesComponent<Note[]> {
   @ViewChild('track') trackDiv: ElementRef;
 
-  private mFeature: Note[];
-  private height: number; // As it stands, height is fixed. Store once onInit.
-
   @Input() set notes(notes: Note[]) {
-    this.mFeature = notes;
-    this.update();
+    this.feature = notes;
   }
 
-  get notes(): Note[] {
-    return this.mFeature;
+  protected get containerHeight(): number {
+    return this.trackDiv.nativeElement.getBoundingClientRect().height;
   }
 
-  ngAfterViewInit(): void {
-    this.height = this.trackDiv.nativeElement.getBoundingClientRect().height;
-    this.renderTimeline(this.trackDiv);
-    this.update();
+  protected get trackContainer(): ElementRef {
+    return this.trackDiv;
   }
 
-  update(): void {
-    if (!this.waveTrack || !this.notes) { return; }
-    this.clearTimeline(this.trackDiv);
-
-    this.addLayer(
+  protected get featureLayers(): Layer[] {
+    return [
       new Waves.helpers.PianoRollLayer(
-        this.notes,
+        this.feature,
         {
           height: this.height,
           color: this.colour,
-          yDomain: findVerticalRange(this.notes)
+          yDomain: findVerticalRange(this.feature)
         }
-      ),
-      this.waveTrack,
-      this.timeline.timeContext
-    );
+      )
+    ];
   }
 }
 
--- a/src/app/visualisations/tracks/tracks.components.ts	Wed May 31 17:33:23 2017 +0100
+++ b/src/app/visualisations/tracks/tracks.components.ts	Wed May 31 19:14:46 2017 +0100
@@ -20,51 +20,40 @@
   styleUrls: ['../waves-template.css'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class TracksComponent extends WavesComponent implements AfterViewInit {
+export class TracksComponent extends WavesComponent<TracksFeature> {
 
   @ViewChild('track') trackDiv: ElementRef;
 
-  private mFeature: TracksFeature;
   private currentState: PlotLayerData[];
-  private height: number; // As it stands, height is fixed. Store once onInit.
 
   @Input() set tracks(input: TracksFeature) {
-    this.mFeature = input;
-    this.currentState = generatePlotData(input);
-    this.update();
+    this.feature = input;
   }
 
-  get tracks(): TracksFeature {
-    return this.mFeature;
+  protected get containerHeight(): number {
+    return this.trackDiv.nativeElement.getBoundingClientRect().height;
   }
 
-  ngAfterViewInit(): void {
-    this.height = this.trackDiv.nativeElement.getBoundingClientRect().height;
-    this.renderTimeline(this.trackDiv);
-    this.update();
+  protected get trackContainer(): ElementRef {
+    return this.trackDiv;
   }
 
-  update(): void {
-    if (this.waveTrack) {
-      this.clearTimeline(this.trackDiv);
-      for (const feature of this.currentState) {
-        const lineLayer = new Waves.helpers.LineLayer(feature.data, {
-          color: this.colour,
-          height: this.height,
-          yDomain: feature.yDomain
-        });
-        this.addLayer(
-          lineLayer,
-          this.waveTrack,
-          this.timeline.timeContext
-        );
+  protected get featureLayers(): Layer[] {
+    this.currentState = generatePlotData(this.feature);
+    return this.currentState.map(feature => new Waves.helpers.LineLayer(
+      feature.data, {
+        color: this.colour,
+        height: this.height,
+        yDomain: feature.yDomain
+      })
+    );
+  }
 
-        // Set start and duration so that the highlight layer can use
-        // them to determine which line to draw values from
-        lineLayer.start = feature.startTime;
-        lineLayer.duration = feature.duration;
-        lineLayer.update(); // TODO probably better to update after all added
-      }
-    }
+  protected get postAddMap() {
+    return (layer, index) => {
+      layer.start = this.currentState[index].startTime;
+      layer.duration = this.currentState[index].duration;
+      layer.update();
+    };
   }
 }
--- a/src/app/visualisations/waveform/waveform.component.ts	Wed May 31 17:33:23 2017 +0100
+++ b/src/app/visualisations/waveform/waveform.component.ts	Wed May 31 19:14:46 2017 +0100
@@ -1,7 +1,6 @@
 import {
   Component,
   Input,
-  ChangeDetectorRef,
   ElementRef,
   ViewChild,
   ChangeDetectionStrategy
@@ -16,54 +15,38 @@
   styleUrls: ['../waves-template.css'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class WaveformComponent extends WavesComponent {
+export class WaveformComponent extends WavesComponent<AudioBuffer> {
+
   @ViewChild('track') trackDiv: ElementRef;
   @Input() set audioBuffer(buffer: AudioBuffer) {
-    this._audioBuffer = buffer || undefined;
-    if (this.audioBuffer) {
-      this.renderWaveform(this.audioBuffer);
-    }
+    this.duration = buffer.duration;
+    this.timeline.pixelsPerSecond = this.timeline.visibleWidth / buffer.duration;
+    this.feature = buffer;
   }
 
-  get audioBuffer(): AudioBuffer {
-    return this._audioBuffer;
+  protected get containerHeight(): number {
+    return this.trackDiv.nativeElement.getBoundingClientRect().height;
   }
 
-  private _audioBuffer: AudioBuffer;
-
-  constructor(private ref: ChangeDetectorRef) {
-    super();
+  protected get trackContainer(): ElementRef {
+    return this.trackDiv;
   }
 
-  renderWaveform(buffer: AudioBuffer): void {
-    const height = this.trackDiv.nativeElement.getBoundingClientRect().height;
-    if (this.timeline && this.waveTrack) {
-      // resize
-      const width = this.trackDiv.nativeElement.getBoundingClientRect().width;
-      this.clearTimeline(this.trackDiv);
-      this.timeline.visibleWidth = width;
-      this.timeline.pixelsPerSecond = width / buffer.duration;
-      this.waveTrack.height = height;
-    } else {
-      this.renderTimeline(this.trackDiv, buffer.duration);
+  protected get featureLayers(): Layer[] {
+    const nChannels = this.feature.numberOfChannels;
+    const totalWaveHeight = this.height * 0.9;
+    const waveHeight = totalWaveHeight / nChannels;
+
+    const channelLayers: Layer[] = [];
+    for (let ch = 0; ch < nChannels; ++ch) {
+      channelLayers.push(new wavesUI.helpers.WaveformLayer(this.feature, {
+          top: (this.height - totalWaveHeight) / 2 + waveHeight * ch,
+          height: waveHeight,
+          color: this.colour,
+          channel: ch
+        })
+      );
     }
-
-    const nchannels = buffer.numberOfChannels;
-    const totalWaveHeight = height * 0.9;
-    const waveHeight = totalWaveHeight / nchannels;
-
-    for (let ch = 0; ch < nchannels; ++ch) {
-      const waveformLayer = new wavesUI.helpers.WaveformLayer(buffer, {
-        top: (height - totalWaveHeight) / 2 + waveHeight * ch,
-        height: waveHeight,
-        color: this.colour,
-        channel: ch
-      });
-      this.addLayer(waveformLayer, this.waveTrack, this.timeline.timeContext);
-    }
-
-    this.waveTrack.render();
-    this.waveTrack.update();
-    this.ref.markForCheck();
+    return channelLayers;
   }
 }
--- a/src/app/visualisations/waves-base.component.ts	Wed May 31 17:33:23 2017 +0100
+++ b/src/app/visualisations/waves-base.component.ts	Wed May 31 19:14:46 2017 +0100
@@ -1,15 +1,18 @@
 /**
  * Created by lucast on 26/05/2017.
  */
-import {ElementRef, Input} from '@angular/core';
+import {AfterViewInit, ElementRef, Input} from '@angular/core';
 import {OnSeekHandler} from '../playhead/PlayHeadHelpers';
 import {attachTouchHandlerBodges} from './WavesJunk';
 import Waves from 'waves-ui-piper';
 import {countingIdProvider} from 'piper/client-stubs/WebWorkerStreamingClient';
+import {ShapedFeatureData} from './FeatureUtilities';
 
 const trackIdGenerator = countingIdProvider(0);
 
-export abstract class WavesComponent {
+export abstract class WavesComponent<T extends ShapedFeatureData | AudioBuffer>
+  implements AfterViewInit {
+
   @Input() set width(width: number) {
     if (this.timeline) {
       requestAnimationFrame(() => {
@@ -21,11 +24,26 @@
   @Input() timeline: Timeline;
   @Input() onSeek: OnSeekHandler;
   @Input() colour: string;
+  @Input() set feature(feature: T) {
+    this.mFeature = feature;
+    this.update();
+  }
+
+  get feature(): T {
+    return this.mFeature;
+  }
 
   protected layers: Layer[];
   protected zoomOnMouseDown: number;
   protected offsetOnMouseDown: number;
   protected waveTrack: Track;
+  protected abstract get containerHeight(): number;
+  protected abstract get trackContainer(): ElementRef;
+  protected abstract get featureLayers(): Layer[];
+  protected postAddMap: (value: Layer, index: number, array: Layer[]) => void;
+  protected height: number;
+  protected duration: number;
+  private mFeature: T;
   private id: string;
 
   constructor() {
@@ -33,18 +51,38 @@
     this.id = trackIdGenerator.next().value;
   }
 
-  protected renderTimeline($el: ElementRef, duration?: number): Timeline {
+  ngAfterViewInit(): void {
+    this.height = this.containerHeight;
+    this.renderTimeline(this.trackContainer);
+    this.update();
+  }
+
+  update(): void {
+    if (!this.waveTrack || !this.mFeature) {
+      return;
+    }
+    this.clearTimeline(this.trackContainer);
+    const layers = this.featureLayers;
+    for (const layer of layers) {
+      this.addLayer(layer, this.waveTrack, this.timeline.timeContext);
+    }
+    if (this.postAddMap) {
+      layers.forEach(this.postAddMap);
+    }
+  }
+
+
+  protected renderTimeline($el: ElementRef): Timeline {
     const track: HTMLElement = $el.nativeElement;
     track.innerHTML = '';
-    const height: number = track.getBoundingClientRect().height;
-    if (duration >= 0) {
+    if (this.duration >= 0) {
       const width: number = track.getBoundingClientRect().width;
-      this.timeline.pixelsPerSecond = width / duration;
+      this.timeline.pixelsPerSecond = width / this.duration;
       this.timeline.visibleWidth = width;
     }
     this.waveTrack = this.timeline.createTrack(
       track,
-      height,
+      this.height,
       this.id
     );
 
@@ -54,7 +92,7 @@
         this.timeline
       );
     }
-    this.resetTimelineState($el);
+    this.resetTimelineState();
   }
 
   // TODO can likely be removed, or use waves-ui methods
@@ -77,15 +115,13 @@
         }
       }
     }
-    this.resetTimelineState($el);
+    this.resetTimelineState();
   }
 
-  private resetTimelineState($el: ElementRef): void {
-    const height = $el.nativeElement.getBoundingClientRect().height;
-
+  private resetTimelineState(): void {
     // time axis
     const timeAxis = new Waves.helpers.TimeAxisLayer({
-      height: height,
+      height: this.containerHeight,
       color: '#b0b0b0'
     });
     this.addLayer(timeAxis, this.waveTrack, this.timeline.timeContext, true);