annotate src/app/notebook-feed/notebook-feed.component.ts @ 464:50f61d1945db

Hook up some buttons for navigating history (undo / redo). Some refactoring to allow for the audio player to get updated as a consequence of a state change (the audio related to the current top of the stack is used).
author Lucas Thompson <dev@lucas.im>
date Fri, 30 Jun 2017 14:01:22 +0100
parents ccce2c09502e
children 3a76205e06b6
rev   line source
dev@171 1 /**
dev@171 2 * Created by lucast on 21/03/2017.
dev@171 3 */
dev@232 4 import {
dev@232 5 ChangeDetectionStrategy,
dev@285 6 ChangeDetectorRef,
dev@456 7 Component, EventEmitter,
dev@285 8 Inject,
dev@285 9 Input,
dev@456 10 OnDestroy, Output
dev@236 11 } from '@angular/core';
dev@289 12 import Waves from 'waves-ui-piper';
dev@350 13 import {
dev@350 14 getRootUri,
dev@460 15 isLoadedRootAudioItem,
dev@350 16 Item
dev@460 17 } from '../analysis-item/AnalysisItem';
dev@285 18 import {Observable} from 'rxjs/Observable';
dev@285 19 import {Dimension} from '../app.module';
dev@285 20 import {Subscription} from 'rxjs/Subscription';
dev@348 21 import {OnSeekHandler} from '../playhead/PlayHeadHelpers';
dev@464 22 import {AudioPlayerService} from '../services/audio-player/audio-player.service';
dev@171 23
dev@171 24 @Component({
dev@171 25 selector: 'ugly-notebook-feed',
dev@171 26 templateUrl: './notebook-feed.component.html',
dev@232 27 styleUrls: ['./notebook-feed.component.css'],
dev@232 28 changeDetection: ChangeDetectionStrategy.OnPush
dev@171 29 })
dev@285 30 export class NotebookFeedComponent implements OnDestroy {
dev@464 31 @Input() set analyses(analyses: Item[]) {
dev@464 32 const front = analyses[0];
dev@464 33 if (analyses !== this.mAnalyses) {
dev@464 34 if (front && getRootUri(front) !== this.currentAudioUri) {
dev@464 35 this.audioService.unload();
dev@464 36 this.audioService.loadAudioFromUri(getRootUri(front));
dev@464 37 }
dev@464 38 }
dev@464 39 this.mAnalyses = analyses;
dev@464 40 if (front) {
dev@464 41 this.currentAudioUri = this.getCurrentAudioUri();
dev@464 42 }
dev@171 43 }
dev@464 44
dev@464 45 get analyses(): Item[] {
dev@464 46 return this.mAnalyses;
dev@464 47 }
dev@464 48
dev@348 49 @Input() onSeek: OnSeekHandler;
dev@456 50 @Output() removeItem: EventEmitter<Item>;
dev@171 51
dev@285 52 private resizeSubscription: Subscription;
dev@285 53 private width: number;
dev@285 54 private lastWidth: number;
dev@282 55 private timelines: Map<string, Timeline>;
dev@464 56 private mAnalyses: Item[];
dev@464 57 private currentAudioUri: string;
dev@181 58
dev@285 59 constructor(
dev@285 60 private ref: ChangeDetectorRef,
dev@464 61 @Inject('DimensionObservable') private onResize: Observable<Dimension>,
dev@464 62 private audioService: AudioPlayerService
dev@285 63 ) {
dev@456 64 this.removeItem = new EventEmitter<Item>();
dev@282 65 this.timelines = new Map();
dev@285 66 this.onResize.subscribe(dim => {
dev@285 67 this.lastWidth = this.width;
dev@285 68 this.width = dim.width;
dev@285 69 });
dev@285 70
dev@285 71 // the use of requestAnimationFrame here is to leave the dom updates
dev@285 72 // to a time convenient for the browser, and avoid a cascade / waterfall
dev@285 73 // of DOM changes for rapid resize events in the event handler above.
dev@285 74 // ..I'm not convinced this is particularly beneficial here // TODO
dev@285 75 const triggerChangeDetectionOnResize = () => {
dev@285 76 requestAnimationFrame(triggerChangeDetectionOnResize);
dev@285 77 if (this.width !== this.lastWidth) {
dev@285 78 ref.markForCheck(); // only trigger change detection if width changed
dev@285 79 }
dev@285 80 };
dev@285 81 requestAnimationFrame(triggerChangeDetectionOnResize);
dev@282 82 }
dev@282 83
dev@350 84 getOrCreateTimeline(item: Item): Timeline | void {
dev@282 85 if (!item.hasSharedTimeline) {
dev@282 86 return;
dev@282 87 }
dev@350 88 const uri = getRootUri(item);
dev@350 89 if (this.timelines.has(uri)) {
dev@350 90 return this.timelines.get(uri);
dev@282 91 } else {
dev@282 92 const timeline = new Waves.core.Timeline();
dev@350 93 this.timelines.set(uri, timeline);
dev@282 94 return timeline;
dev@282 95 }
dev@181 96 }
dev@285 97
dev@350 98 isAudioItem(item: Item): boolean {
dev@460 99 return isLoadedRootAudioItem(item);
dev@348 100 }
dev@348 101
dev@350 102 isActiveItem(item: Item): boolean {
dev@464 103 return this.getCurrentAudioUri() === getRootUri(item);
dev@350 104 }
dev@350 105
dev@350 106 getOnSeekForItem(item: Item): (timeSeconds: number) => any {
dev@348 107 return this.isActiveItem(item) ? this.onSeek : () => {};
dev@348 108 }
dev@348 109
dev@285 110 ngOnDestroy(): void {
dev@285 111 if (this.resizeSubscription) {
dev@285 112 this.resizeSubscription.unsubscribe();
dev@285 113 }
dev@285 114 }
dev@464 115
dev@464 116 private getCurrentAudioUri(): string {
dev@464 117 if (this.analyses.length === 0) {
dev@464 118 return '';
dev@464 119 }
dev@464 120 try {
dev@464 121 return getRootUri(this.analyses[0]);
dev@464 122 } catch (e) {
dev@464 123 return '';
dev@464 124 }
dev@464 125 }
dev@171 126 }