dev@354
|
1 /**
|
dev@354
|
2 * Created by lucast on 26/05/2017.
|
dev@354
|
3 */
|
dev@384
|
4 import {AfterViewInit, ElementRef, Input, ViewChild} from '@angular/core';
|
dev@354
|
5 import {OnSeekHandler} from '../playhead/PlayHeadHelpers';
|
dev@354
|
6 import {attachTouchHandlerBodges} from './WavesJunk';
|
dev@354
|
7 import Waves from 'waves-ui-piper';
|
dev@376
|
8 import {countingIdProvider} from 'piper/client-stubs/WebWorkerStreamingClient';
|
dev@383
|
9 import {ShapedFeatureData} from './FeatureUtilities';
|
dev@376
|
10
|
dev@376
|
11 const trackIdGenerator = countingIdProvider(0);
|
dev@354
|
12
|
dev@389
|
13 // has to be an abstract class vs as interface for Angular's DI
|
dev@389
|
14 export abstract class VerticallyBounded {
|
dev@389
|
15 abstract get range(): [number, number];
|
dev@389
|
16 abstract renderScale(range: [number, number]): void;
|
dev@389
|
17 }
|
dev@389
|
18
|
dev@383
|
19 export abstract class WavesComponent<T extends ShapedFeatureData | AudioBuffer>
|
dev@383
|
20 implements AfterViewInit {
|
dev@384
|
21 @ViewChild('track') trackContainer: ElementRef;
|
dev@354
|
22 @Input() set width(width: number) {
|
dev@354
|
23 if (this.timeline) {
|
dev@354
|
24 requestAnimationFrame(() => {
|
dev@354
|
25 this.timeline.timeContext.visibleWidth = width;
|
dev@354
|
26 this.timeline.tracks.update();
|
dev@354
|
27 });
|
dev@354
|
28 }
|
dev@354
|
29 }
|
dev@354
|
30 @Input() timeline: Timeline;
|
dev@354
|
31 @Input() onSeek: OnSeekHandler;
|
dev@379
|
32 @Input() colour: string;
|
dev@383
|
33 @Input() set feature(feature: T) {
|
dev@383
|
34 this.mFeature = feature;
|
dev@383
|
35 this.update();
|
dev@383
|
36 }
|
dev@383
|
37
|
dev@383
|
38 get feature(): T {
|
dev@383
|
39 return this.mFeature;
|
dev@383
|
40 }
|
dev@354
|
41
|
dev@387
|
42 private layers: Layer[];
|
dev@387
|
43 private zoomOnMouseDown: number;
|
dev@387
|
44 private offsetOnMouseDown: number;
|
dev@387
|
45 private waveTrack: Track;
|
dev@387
|
46 private mFeature: T;
|
dev@387
|
47 private id: string;
|
dev@383
|
48 protected abstract get featureLayers(): Layer[];
|
dev@383
|
49 protected postAddMap: (value: Layer, index: number, array: Layer[]) => void;
|
dev@383
|
50 protected height: number;
|
dev@383
|
51 protected duration: number;
|
dev@354
|
52
|
dev@354
|
53 constructor() {
|
dev@354
|
54 this.layers = [];
|
dev@376
|
55 this.id = trackIdGenerator.next().value;
|
dev@354
|
56 }
|
dev@354
|
57
|
dev@383
|
58 ngAfterViewInit(): void {
|
dev@384
|
59 this.height =
|
dev@384
|
60 this.trackContainer.nativeElement.getBoundingClientRect().height;
|
dev@386
|
61 this.renderTimeline();
|
dev@383
|
62 this.update();
|
dev@383
|
63 }
|
dev@383
|
64
|
dev@386
|
65 private update(): void {
|
dev@383
|
66 if (!this.waveTrack || !this.mFeature) {
|
dev@383
|
67 return;
|
dev@383
|
68 }
|
dev@386
|
69 this.clearTimeline();
|
dev@383
|
70 const layers = this.featureLayers;
|
dev@383
|
71 for (const layer of layers) {
|
dev@389
|
72 this.addLayer(layer);
|
dev@383
|
73 }
|
dev@383
|
74 if (this.postAddMap) {
|
dev@383
|
75 layers.forEach(this.postAddMap);
|
dev@383
|
76 }
|
dev@383
|
77 }
|
dev@383
|
78
|
dev@383
|
79
|
dev@386
|
80 private renderTimeline(): Timeline {
|
dev@386
|
81 const track: HTMLElement = this.trackContainer.nativeElement;
|
dev@354
|
82 track.innerHTML = '';
|
dev@383
|
83 if (this.duration >= 0) {
|
dev@364
|
84 const width: number = track.getBoundingClientRect().width;
|
dev@383
|
85 this.timeline.pixelsPerSecond = width / this.duration;
|
dev@364
|
86 this.timeline.visibleWidth = width;
|
dev@364
|
87 }
|
dev@354
|
88 this.waveTrack = this.timeline.createTrack(
|
dev@354
|
89 track,
|
dev@383
|
90 this.height,
|
dev@376
|
91 this.id
|
dev@354
|
92 );
|
dev@354
|
93
|
dev@354
|
94 if ('ontouchstart' in window) {
|
dev@354
|
95 attachTouchHandlerBodges(
|
dev@386
|
96 track,
|
dev@354
|
97 this.timeline
|
dev@354
|
98 );
|
dev@354
|
99 }
|
dev@383
|
100 this.resetTimelineState();
|
dev@354
|
101 }
|
dev@354
|
102
|
dev@354
|
103 // TODO can likely be removed, or use waves-ui methods
|
dev@386
|
104 private clearTimeline(): void {
|
dev@354
|
105 // loop through layers and remove them, waves-ui provides methods for this but it seems to not work properly
|
dev@354
|
106 const timeContextChildren = this.timeline.timeContext._children;
|
dev@354
|
107 for (const track of this.timeline.tracks) {
|
dev@354
|
108 if (track.layers.length === 0) { continue; }
|
dev@354
|
109 const trackLayers: Layer[] = Array.from(track.layers as Layer[]);
|
dev@354
|
110 while (trackLayers.length) {
|
dev@354
|
111 const layer: Layer = trackLayers.pop();
|
dev@354
|
112 if (this.layers.includes(layer)) {
|
dev@354
|
113 track.remove(layer);
|
dev@354
|
114 this.layers.splice(this.layers.indexOf(layer), 1);
|
dev@354
|
115 const index = timeContextChildren.indexOf(layer.timeContext);
|
dev@354
|
116 if (index >= 0) {
|
dev@354
|
117 timeContextChildren.splice(index, 1);
|
dev@354
|
118 }
|
dev@354
|
119 layer.destroy();
|
dev@354
|
120 }
|
dev@354
|
121 }
|
dev@354
|
122 }
|
dev@383
|
123 this.resetTimelineState();
|
dev@356
|
124 }
|
dev@356
|
125
|
dev@383
|
126 private resetTimelineState(): void {
|
dev@356
|
127 // time axis
|
dev@356
|
128 const timeAxis = new Waves.helpers.TimeAxisLayer({
|
dev@384
|
129 height: this.height,
|
dev@356
|
130 color: '#b0b0b0'
|
dev@356
|
131 });
|
dev@389
|
132 this.addLayer(timeAxis, true);
|
dev@356
|
133 this.timeline.state = new Waves.states.CenteredZoomState(this.timeline);
|
dev@364
|
134 this.timeline.tracks.update(); // TODO this is problematic, shared state across components
|
dev@354
|
135 }
|
dev@354
|
136
|
dev@354
|
137
|
dev@354
|
138 // TODO can likely use methods in waves-ui directly
|
dev@389
|
139 protected addLayer(layer: Layer,
|
dev@389
|
140 isAxis: boolean = false): void {
|
dev@389
|
141 const timeContext = this.timeline.timeContext;
|
dev@354
|
142 if (!layer.timeContext) {
|
dev@354
|
143 layer.setTimeContext(isAxis ?
|
dev@354
|
144 timeContext : new Waves.core.LayerTimeContext(timeContext));
|
dev@354
|
145 }
|
dev@389
|
146 this.waveTrack.add(layer);
|
dev@354
|
147 this.layers.push(layer);
|
dev@354
|
148 layer.render();
|
dev@354
|
149 layer.update();
|
dev@354
|
150 }
|
dev@354
|
151
|
dev@354
|
152 seekStart(): void {
|
dev@354
|
153 this.zoomOnMouseDown = this.timeline.timeContext.zoom;
|
dev@354
|
154 this.offsetOnMouseDown = this.timeline.timeContext.offset;
|
dev@354
|
155 }
|
dev@354
|
156
|
dev@354
|
157 seekEnd(x: number): void {
|
dev@354
|
158 const hasSameZoom: boolean = this.zoomOnMouseDown ===
|
dev@354
|
159 this.timeline.timeContext.zoom;
|
dev@354
|
160 const hasSameOffset: boolean = this.offsetOnMouseDown ===
|
dev@354
|
161 this.timeline.timeContext.offset;
|
dev@354
|
162 if (hasSameZoom && hasSameOffset) {
|
dev@354
|
163 this.seek(x);
|
dev@354
|
164 }
|
dev@354
|
165 }
|
dev@354
|
166
|
dev@354
|
167 seek(x: number): void {
|
dev@354
|
168 if (this.timeline) {
|
dev@354
|
169 const timeContext: any = this.timeline.timeContext;
|
dev@354
|
170 if (this.onSeek) {
|
dev@354
|
171 this.onSeek(timeContext.timeToPixel.invert(x) - timeContext.offset);
|
dev@354
|
172 }
|
dev@354
|
173 }
|
dev@354
|
174 }
|
dev@354
|
175 }
|
dev@389
|
176
|
dev@389
|
177 export abstract class VerticallyBoundedWavesComponent
|
dev@389
|
178 <T extends ShapedFeatureData> extends WavesComponent<T>
|
dev@389
|
179 implements VerticallyBounded {
|
dev@389
|
180 abstract range: [number, number];
|
dev@389
|
181
|
dev@389
|
182 renderScale(range: [number, number]): void {
|
dev@389
|
183 this.addLayer(new Waves.helpers.ScaleLayer({
|
dev@389
|
184 tickColor: this.colour,
|
dev@389
|
185 textColor: this.colour,
|
dev@389
|
186 height: this.height,
|
dev@389
|
187 yDomain: range
|
dev@389
|
188 }));
|
dev@389
|
189 }
|
dev@389
|
190 }
|