annotate src/app/analysis-item/analysis-item.component.ts @ 408:f2d43724a578

LifeCycle handling for paging animations. Additionally, only add a pager to audio items as the timeline only needs to be updated once and other components can be driven from that.
author Lucas Thompson <dev@lucas.im>
date Sun, 04 Jun 2017 20:19:41 +0100
parents 6fe8ef9687de
children 523858455430
rev   line source
dev@170 1 /**
dev@170 2 * Created by lucast on 21/03/2017.
dev@170 3 */
dev@231 4 import {
dev@231 5 ChangeDetectionStrategy,
dev@231 6 Component,
dev@231 7 Input,
dev@408 8 OnDestroy,
dev@231 9 OnInit
dev@236 10 } from '@angular/core';
dev@348 11 import {naivePagingMapper} from '../visualisations/WavesJunk';
dev@408 12 import {OnSeekHandler} from '../playhead/PlayHeadHelpers';
dev@361 13 import {
dev@381 14 defaultColourGenerator,
dev@361 15 HigherLevelFeatureShape,
dev@361 16 KnownShapedFeature
dev@361 17 } from '../visualisations/FeatureUtilities';
dev@408 18 import {
dev@408 19 RenderLoopService,
dev@408 20 TaskRemover
dev@408 21 } from '../services/render-loop/render-loop.service';
dev@170 22
dev@350 23 export interface Item {
dev@350 24 id: string;
dev@200 25 hasSharedTimeline: boolean;
dev@200 26 title?: string;
dev@200 27 description?: string;
dev@224 28 progress?: number;
dev@350 29 }
dev@350 30
dev@350 31 export interface PendingRootAudioItem extends Item {
dev@350 32 uri: string;
dev@350 33 }
dev@378 34 export interface RootAudioItem extends PendingRootAudioItem {
dev@350 35 audioData: AudioBuffer;
dev@350 36 }
dev@350 37
dev@350 38 export interface PendingAnalysisItem extends Item {
dev@350 39 parent: RootAudioItem;
dev@350 40 extractorKey: string;
dev@350 41 }
dev@350 42
dev@396 43 export type AnalysisItem = PendingAnalysisItem & KnownShapedFeature & {
dev@396 44 unit?: string
dev@396 45 };
dev@361 46
dev@361 47 export function isItem(item: Item): item is Item {
dev@361 48 return item.id != null && item.hasSharedTimeline != null;
dev@350 49 }
dev@350 50
dev@350 51 export function isPendingRootAudioItem(item: Item): item is PendingRootAudioItem {
dev@361 52 return isItem(item) && typeof (item as RootAudioItem).uri === 'string';
dev@350 53 }
dev@350 54
dev@350 55 export function isRootAudioItem(item: Item): item is RootAudioItem {
dev@350 56 return isPendingRootAudioItem(item) &&
dev@351 57 (item as RootAudioItem).audioData instanceof AudioBuffer;
dev@350 58 }
dev@350 59
dev@350 60 export function isPendingAnalysisItem(item: Item): item is AnalysisItem {
dev@350 61 const downcast = (item as AnalysisItem);
dev@350 62 return isRootAudioItem(downcast.parent)
dev@350 63 && typeof downcast.extractorKey === 'string';
dev@350 64 }
dev@350 65
dev@350 66 export function isAnalysisItem(item: Item): item is AnalysisItem {
dev@350 67 const downcast = (item as AnalysisItem);
dev@361 68 return isPendingAnalysisItem(item) &&
dev@361 69 downcast.shape != null &&
dev@361 70 downcast.collected != null;
dev@350 71 }
dev@350 72
dev@350 73 // these should probably be actual concrete types with their own getUri methods
dev@350 74 export function getRootUri(item: Item): string {
dev@350 75 if (isPendingRootAudioItem(item)) {
dev@350 76 return item.uri;
dev@350 77 }
dev@350 78 if (isPendingAnalysisItem(item)) {
dev@350 79 return item.parent.uri;
dev@350 80 }
dev@350 81 throw new Error('Invalid item: No URI property set.');
dev@170 82 }
dev@170 83
dev@170 84 @Component({
dev@170 85 selector: 'ugly-analysis-item',
dev@170 86 templateUrl: './analysis-item.component.html',
dev@231 87 styleUrls: ['./analysis-item.component.css'],
dev@231 88 changeDetection: ChangeDetectionStrategy.OnPush
dev@170 89 })
dev@408 90 export class AnalysisItemComponent implements OnInit, OnDestroy {
dev@224 91
dev@408 92 // TODO should be TimelineTimeContext?
dev@408 93 @Input() set timeline(timeline: Timeline) {
dev@408 94 this.mTimeline = timeline;
dev@408 95 this.resetRemoveAnimation();
dev@408 96 }
dev@408 97
dev@408 98 get timeline(): Timeline {
dev@408 99 return this.mTimeline;
dev@408 100 }
dev@408 101
dev@408 102 @Input() set isActive(isActive: boolean) {
dev@408 103 this.removeAnimation();
dev@408 104 this.mIsActive = isActive;
dev@408 105 if (isActive) {
dev@408 106 this.resetRemoveAnimation();
dev@408 107 }
dev@408 108 }
dev@408 109
dev@408 110 get isActive() {
dev@408 111 return this.mIsActive;
dev@408 112 }
dev@408 113
dev@350 114 @Input() item: Item;
dev@285 115 @Input() contentWidth: number;
dev@348 116 @Input() onSeek: OnSeekHandler;
dev@408 117 // TODO move / re-think - naivePagingMapper feels like a big ol' bodge
dev@408 118 private removeAnimation: TaskRemover;
dev@224 119 private hasProgressOnInit = false;
dev@408 120 private mIsActive: boolean;
dev@408 121 private mTimeline: Timeline;
dev@224 122
dev@408 123 constructor(private renderLoop: RenderLoopService) {}
dev@348 124
dev@224 125 ngOnInit(): void {
dev@408 126 this.resetRemoveAnimation();
dev@231 127 this.hasProgressOnInit = this.item.progress != null;
dev@224 128 }
dev@224 129
dev@224 130 isLoading(): boolean {
dev@231 131 return this.hasProgressOnInit && this.item.progress < 100;
dev@224 132 }
dev@348 133
dev@348 134 isAudioItem(): boolean {
dev@408 135 return this.item && isRootAudioItem(this.item);
dev@348 136 }
dev@361 137
dev@361 138 getFeatureShape(): HigherLevelFeatureShape | null {
dev@361 139 return !isPendingRootAudioItem(this.item) &&
dev@361 140 isAnalysisItem(this.item) ? this.item.shape : null;
dev@361 141 }
dev@381 142
dev@381 143 getNextColour(): string {
dev@381 144 return defaultColourGenerator.next().value;
dev@381 145 }
dev@408 146
dev@408 147 ngOnDestroy(): void {
dev@408 148 this.removeAnimation();
dev@408 149 }
dev@408 150
dev@408 151 private resetRemoveAnimation(): void {
dev@408 152 if (this.removeAnimation) {
dev@408 153 this.removeAnimation();
dev@408 154 }
dev@408 155 const createPagingTask = () => {
dev@408 156 const pagingMapper = naivePagingMapper(this.timeline);
dev@408 157 return this.renderLoop.addPlayingTask(currentTime => {
dev@408 158 pagingMapper(currentTime);
dev@408 159 });
dev@408 160 };
dev@408 161 // only add a pager to audio items, it can drive the feature items
dev@408 162 const remover = this.timeline && this.isAudioItem() ?
dev@408 163 createPagingTask() : () => {};
dev@408 164 this.removeAnimation = () => {
dev@408 165 remover();
dev@408 166 this.removeAnimation = () => {};
dev@408 167 };
dev@408 168 }
dev@170 169 }