changeset 287:6c47605ebd75

Merge remote-tracking branch 'origin/master'
author Chris Cannam <cannam@all-day-breakfast.com>
date Fri, 05 May 2017 09:48:02 +0100
parents bb44ef9deb42 (current diff) 6c51fd776008 (diff)
children 0c0fd90320e7
files
diffstat 7 files changed, 92 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/analysis-item/analysis-item.component.html	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/analysis-item/analysis-item.component.html	Fri May 05 09:48:02 2017 +0100
@@ -18,6 +18,7 @@
         [isSubscribedToExtractionService]="isActive && !item.isRoot"
         [isOneShotExtractor]="true"
         [isSeeking]="isActive"
+        [width]="contentWidth"
       ></ugly-waveform>
     </ng-template>
   </md-card-content>
--- a/src/app/analysis-item/analysis-item.component.ts	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/analysis-item/analysis-item.component.ts	Fri May 05 09:48:02 2017 +0100
@@ -30,6 +30,7 @@
   @Input() timeline: Timeline;
   @Input() isActive: boolean;
   @Input() item: AnalysisItem;
+  @Input() contentWidth: number;
   private hasProgressOnInit = false;
 
   ngOnInit(): void {
--- a/src/app/app.module.ts	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/app.module.ts	Fri May 05 09:48:02 2017 +0100
@@ -28,6 +28,7 @@
 import {AnalysisItemComponent} from './analysis-item/analysis-item.component';
 import {ProgressBarComponent} from './progress-bar/progress-bar';
 import {UglyMaterialModule} from './ugly-material.module';
+import {Observable} from 'rxjs/Observable';
 
 export function createAudioContext(): AudioContext {
   return new (
@@ -92,6 +93,16 @@
   };
 }
 
+export interface Dimension {
+  width: number;
+  height: number;
+}
+export function createWindowDimensionObservable(): Observable<Dimension> {
+  return Observable.fromEvent(window, 'resize', () => ({
+    height: window.innerHeight,
+    width: window.innerWidth
+  })).share();
+}
 @NgModule({
   declarations: [
     AppComponent,
@@ -121,7 +132,8 @@
     {provide: 'MediaRecorderFactory', useFactory: createMediaRecorderFactory},
     {provide: 'PiperRepoUri', useValue: 'assets/remote-extractors.json'},
     {provide: 'UrlResourceLifetimeManager', useFactory: createUrlResourceManager},
-    {provide: 'ResourceReader', useFactory: createResourceReader}
+    {provide: 'ResourceReader', useFactory: createResourceReader},
+    {provide: 'DimensionObservable', useFactory: createWindowDimensionObservable}
   ],
   bootstrap: [AppComponent]
 })
--- a/src/app/notebook-feed/notebook-feed.component.css	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/notebook-feed/notebook-feed.component.css	Fri May 05 09:48:02 2017 +0100
@@ -1,3 +1,7 @@
 .break {
   margin-bottom: 32px;
 }
+
+.feed {
+  width: 100%;
+}
--- a/src/app/notebook-feed/notebook-feed.component.html	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/notebook-feed/notebook-feed.component.html	Fri May 05 09:48:02 2017 +0100
@@ -1,10 +1,12 @@
-
-<ng-template ngFor let-item [ngForOf]="analyses">
-  <div [class.break]="item.isRoot">
-    <ugly-analysis-item
-      [timeline]="item.hasSharedTimeline ? sharedTimeline : undefined"
-      [isActive]="rootAudioUri === item.rootAudioUri"
-      [item]="item"
-    ></ugly-analysis-item>
-  </div>
-</ng-template>
+<div class="feed">
+  <ng-template ngFor let-item [ngForOf]="analyses">
+    <div [class.break]="item.isRoot">
+      <ugly-analysis-item
+        [timeline]="getOrCreateTimeline(item)"
+        [isActive]="rootAudioUri === item.rootAudioUri"
+        [item]="item"
+        [contentWidth]="width"
+      ></ugly-analysis-item>
+    </div>
+  </ng-template>
+</div>
--- a/src/app/notebook-feed/notebook-feed.component.ts	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/notebook-feed/notebook-feed.component.ts	Fri May 05 09:48:02 2017 +0100
@@ -3,11 +3,17 @@
  */
 import {
   ChangeDetectionStrategy,
+  ChangeDetectorRef,
   Component,
-  Input
+  Inject,
+  Input,
+  OnDestroy
 } from '@angular/core';
 import Waves from 'waves-ui';
 import {AnalysisItem} from '../analysis-item/analysis-item.component';
+import {Observable} from 'rxjs/Observable';
+import {Dimension} from '../app.module';
+import {Subscription} from 'rxjs/Subscription';
 
 @Component({
   selector: 'ugly-notebook-feed',
@@ -15,24 +21,61 @@
   styleUrls: ['./notebook-feed.component.css'],
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class NotebookFeedComponent {
-  sharedTimeline: Timeline;
+export class NotebookFeedComponent implements OnDestroy {
   @Input() analyses: AnalysisItem[];
   @Input() set rootAudioUri(uri: string) {
     this._rootAudioUri = uri;
-
-    // TODO is this safe? will the fact references are held elsewhere
-    // keep the previous instance alive? Or will it get garbage collected in
-    // screw previous layers up?
-    this.sharedTimeline = new Waves.core.Timeline();
   }
 
   get rootAudioUri(): string {
     return this._rootAudioUri;
   }
   private _rootAudioUri: string;
+  private resizeSubscription: Subscription;
+  private width: number;
+  private lastWidth: number;
+  private timelines: Map<string, Timeline>;
 
-  constructor() {
-    this.sharedTimeline = new Waves.core.Timeline();
+  constructor(
+    private ref: ChangeDetectorRef,
+    @Inject('DimensionObservable') private onResize: Observable<Dimension>
+  ) {
+    this.timelines = new Map();
+    this.onResize.subscribe(dim => {
+      this.lastWidth = this.width;
+      this.width = dim.width;
+    });
+
+    // the use of requestAnimationFrame here is to leave the dom updates
+    // to a time convenient for the browser, and avoid a cascade / waterfall
+    // of DOM changes for rapid resize events in the event handler above.
+    // ..I'm not convinced this is particularly beneficial here // TODO
+    const triggerChangeDetectionOnResize = () => {
+      requestAnimationFrame(triggerChangeDetectionOnResize);
+      if (this.width !== this.lastWidth) {
+        ref.markForCheck(); // only trigger change detection if width changed
+      }
+    };
+    requestAnimationFrame(triggerChangeDetectionOnResize);
+  }
+
+  getOrCreateTimeline(item: AnalysisItem): Timeline | void {
+    if (!item.hasSharedTimeline) {
+      return;
+    }
+
+    if (this.timelines.has(item.rootAudioUri)) {
+      return this.timelines.get(item.rootAudioUri);
+    } else {
+      const timeline = new Waves.core.Timeline();
+      this.timelines.set(item.rootAudioUri, timeline);
+      return timeline;
+    }
+  }
+
+  ngOnDestroy(): void {
+    if (this.resizeSubscription) {
+      this.resizeSubscription.unsubscribe();
+    }
   }
 }
--- a/src/app/waveform/waveform.component.ts	Wed May 03 17:03:55 2017 +0100
+++ b/src/app/waveform/waveform.component.ts	Fri May 05 09:48:02 2017 +0100
@@ -60,7 +60,14 @@
 export class WaveformComponent implements OnInit, AfterViewInit, OnDestroy {
 
   @ViewChild('track') trackDiv: ElementRef;
-
+  @Input() set width(width: number) {
+    if (this.timeline) {
+      requestAnimationFrame(() => {
+        this.timeline.timeContext.visibleWidth = width;
+        this.timeline.tracks.update();
+      });
+    }
+  }
   @Input() timeline: Timeline;
   @Input() trackIdPrefix: string;
   @Input() set isSubscribedToExtractionService(isSubscribed: boolean) {