OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 /** | |
5 * @unrestricted | |
6 */ | |
7 Components.FilmStripView = class extends UI.HBox { | |
8 constructor() { | |
9 super(true); | |
10 this.registerRequiredCSS('components_lazy/filmStripView.css'); | |
11 this.contentElement.classList.add('film-strip-view'); | |
12 this._statusLabel = this.contentElement.createChild('div', 'label'); | |
13 this.reset(); | |
14 this.setMode(Components.FilmStripView.Modes.TimeBased); | |
15 } | |
16 | |
17 /** | |
18 * @param {!Element} imageElement | |
19 * @param {?string} data | |
20 */ | |
21 static _setImageData(imageElement, data) { | |
22 if (data) | |
23 imageElement.src = 'data:image/jpg;base64,' + data; | |
24 } | |
25 | |
26 /** | |
27 * @param {string} mode | |
28 */ | |
29 setMode(mode) { | |
30 this._mode = mode; | |
31 this.contentElement.classList.toggle('time-based', mode === Components.FilmS
tripView.Modes.TimeBased); | |
32 this.update(); | |
33 } | |
34 | |
35 /** | |
36 * @param {!Components.FilmStripModel} filmStripModel | |
37 * @param {number} zeroTime | |
38 * @param {number} spanTime | |
39 */ | |
40 setModel(filmStripModel, zeroTime, spanTime) { | |
41 this._model = filmStripModel; | |
42 this._zeroTime = zeroTime; | |
43 this._spanTime = spanTime; | |
44 var frames = filmStripModel.frames(); | |
45 if (!frames.length) { | |
46 this.reset(); | |
47 return; | |
48 } | |
49 this.update(); | |
50 } | |
51 | |
52 /** | |
53 * @param {!Components.FilmStripModel.Frame} frame | |
54 * @return {!Promise<!Element>} | |
55 */ | |
56 createFrameElement(frame) { | |
57 var time = frame.timestamp; | |
58 var element = createElementWithClass('div', 'frame'); | |
59 element.title = Common.UIString('Doubleclick to zoom image. Click to view pr
eceding requests.'); | |
60 element.createChild('div', 'time').textContent = Number.millisToString(time
- this._zeroTime); | |
61 var imageElement = element.createChild('div', 'thumbnail').createChild('img'
); | |
62 element.addEventListener( | |
63 'mousedown', this._onMouseEvent.bind(this, Components.FilmStripView.Even
ts.FrameSelected, time), false); | |
64 element.addEventListener( | |
65 'mouseenter', this._onMouseEvent.bind(this, Components.FilmStripView.Eve
nts.FrameEnter, time), false); | |
66 element.addEventListener( | |
67 'mouseout', this._onMouseEvent.bind(this, Components.FilmStripView.Event
s.FrameExit, time), false); | |
68 element.addEventListener('dblclick', this._onDoubleClick.bind(this, frame),
false); | |
69 | |
70 return frame.imageDataPromise() | |
71 .then(Components.FilmStripView._setImageData.bind(null, imageElement)) | |
72 .then(returnElement); | |
73 /** | |
74 * @return {!Element} | |
75 */ | |
76 function returnElement() { | |
77 return element; | |
78 } | |
79 } | |
80 | |
81 /** | |
82 * @param {number} time | |
83 * @return {!Components.FilmStripModel.Frame} | |
84 */ | |
85 frameByTime(time) { | |
86 /** | |
87 * @param {number} time | |
88 * @param {!Components.FilmStripModel.Frame} frame | |
89 * @return {number} | |
90 */ | |
91 function comparator(time, frame) { | |
92 return time - frame.timestamp; | |
93 } | |
94 // Using the first frame to fill the interval between recording start | |
95 // and a moment the frame is taken. | |
96 var frames = this._model.frames(); | |
97 var index = Math.max(frames.upperBound(time, comparator) - 1, 0); | |
98 return frames[index]; | |
99 } | |
100 | |
101 update() { | |
102 if (!this._model) | |
103 return; | |
104 var frames = this._model.frames(); | |
105 if (!frames.length) | |
106 return; | |
107 | |
108 if (this._mode === Components.FilmStripView.Modes.FrameBased) { | |
109 Promise.all(frames.map(this.createFrameElement.bind(this))).then(appendEle
ments.bind(this)); | |
110 return; | |
111 } | |
112 | |
113 var width = this.contentElement.clientWidth; | |
114 var scale = this._spanTime / width; | |
115 this.createFrameElement(frames[0]).then( | |
116 continueWhenFrameImageLoaded.bind(this)); // Calculate frame width basi
ng on the first frame. | |
117 | |
118 /** | |
119 * @this {Components.FilmStripView} | |
120 * @param {!Element} element0 | |
121 */ | |
122 function continueWhenFrameImageLoaded(element0) { | |
123 var frameWidth = Math.ceil(UI.measurePreferredSize(element0, this.contentE
lement).width); | |
124 if (!frameWidth) | |
125 return; | |
126 | |
127 var promises = []; | |
128 for (var pos = frameWidth; pos < width; pos += frameWidth) { | |
129 var time = pos * scale + this._zeroTime; | |
130 promises.push(this.createFrameElement(this.frameByTime(time)).then(fixWi
dth)); | |
131 } | |
132 Promise.all(promises).then(appendElements.bind(this)); | |
133 /** | |
134 * @param {!Element} element | |
135 * @return {!Element} | |
136 */ | |
137 function fixWidth(element) { | |
138 element.style.width = frameWidth + 'px'; | |
139 return element; | |
140 } | |
141 } | |
142 | |
143 /** | |
144 * @param {!Array.<!Element>} elements | |
145 * @this {Components.FilmStripView} | |
146 */ | |
147 function appendElements(elements) { | |
148 this.contentElement.removeChildren(); | |
149 for (var i = 0; i < elements.length; ++i) | |
150 this.contentElement.appendChild(elements[i]); | |
151 } | |
152 } | |
153 | |
154 /** | |
155 * @override | |
156 */ | |
157 onResize() { | |
158 if (this._mode === Components.FilmStripView.Modes.FrameBased) | |
159 return; | |
160 this.update(); | |
161 } | |
162 | |
163 /** | |
164 * @param {string} eventName | |
165 * @param {number} timestamp | |
166 */ | |
167 _onMouseEvent(eventName, timestamp) { | |
168 this.dispatchEventToListeners(eventName, timestamp); | |
169 } | |
170 | |
171 /** | |
172 * @param {!Components.FilmStripModel.Frame} filmStripFrame | |
173 */ | |
174 _onDoubleClick(filmStripFrame) { | |
175 new Components.FilmStripView.Dialog(filmStripFrame, this._zeroTime); | |
176 } | |
177 | |
178 reset() { | |
179 this._zeroTime = 0; | |
180 this.contentElement.removeChildren(); | |
181 this.contentElement.appendChild(this._statusLabel); | |
182 } | |
183 | |
184 /** | |
185 * @param {string} text | |
186 */ | |
187 setStatusText(text) { | |
188 this._statusLabel.textContent = text; | |
189 } | |
190 }; | |
191 | |
192 /** @enum {symbol} */ | |
193 Components.FilmStripView.Events = { | |
194 FrameSelected: Symbol('FrameSelected'), | |
195 FrameEnter: Symbol('FrameEnter'), | |
196 FrameExit: Symbol('FrameExit'), | |
197 }; | |
198 | |
199 Components.FilmStripView.Modes = { | |
200 TimeBased: 'TimeBased', | |
201 FrameBased: 'FrameBased' | |
202 }; | |
203 | |
204 | |
205 /** | |
206 * @unrestricted | |
207 */ | |
208 Components.FilmStripView.Dialog = class extends UI.VBox { | |
209 /** | |
210 * @param {!Components.FilmStripModel.Frame} filmStripFrame | |
211 * @param {number=} zeroTime | |
212 */ | |
213 constructor(filmStripFrame, zeroTime) { | |
214 super(true); | |
215 this.registerRequiredCSS('components_lazy/filmStripDialog.css'); | |
216 this.contentElement.classList.add('filmstrip-dialog'); | |
217 this.contentElement.tabIndex = 0; | |
218 | |
219 this._frames = filmStripFrame.model().frames(); | |
220 this._index = filmStripFrame.index; | |
221 this._zeroTime = zeroTime || filmStripFrame.model().zeroTime(); | |
222 | |
223 this._imageElement = this.contentElement.createChild('img'); | |
224 var footerElement = this.contentElement.createChild('div', 'filmstrip-dialog
-footer'); | |
225 footerElement.createChild('div', 'flex-auto'); | |
226 var prevButton = | |
227 UI.createTextButton('\u25C0', this._onPrevFrame.bind(this), undefined, C
ommon.UIString('Previous frame')); | |
228 footerElement.appendChild(prevButton); | |
229 this._timeLabel = footerElement.createChild('div', 'filmstrip-dialog-label')
; | |
230 var nextButton = | |
231 UI.createTextButton('\u25B6', this._onNextFrame.bind(this), undefined, C
ommon.UIString('Next frame')); | |
232 footerElement.appendChild(nextButton); | |
233 footerElement.createChild('div', 'flex-auto'); | |
234 | |
235 this.contentElement.addEventListener('keydown', this._keyDown.bind(this), fa
lse); | |
236 this.setDefaultFocusedElement(this.contentElement); | |
237 this._render(); | |
238 } | |
239 | |
240 _resize() { | |
241 if (!this._dialog) { | |
242 this._dialog = new UI.Dialog(); | |
243 this.show(this._dialog.element); | |
244 this._dialog.setWrapsContent(true); | |
245 this._dialog.show(); | |
246 } | |
247 this._dialog.contentResized(); | |
248 } | |
249 | |
250 /** | |
251 * @param {!Event} event | |
252 */ | |
253 _keyDown(event) { | |
254 switch (event.key) { | |
255 case 'ArrowLeft': | |
256 if (Host.isMac() && event.metaKey) | |
257 this._onFirstFrame(); | |
258 else | |
259 this._onPrevFrame(); | |
260 break; | |
261 | |
262 case 'ArrowRight': | |
263 if (Host.isMac() && event.metaKey) | |
264 this._onLastFrame(); | |
265 else | |
266 this._onNextFrame(); | |
267 break; | |
268 | |
269 case 'Home': | |
270 this._onFirstFrame(); | |
271 break; | |
272 | |
273 case 'End': | |
274 this._onLastFrame(); | |
275 break; | |
276 } | |
277 } | |
278 | |
279 _onPrevFrame() { | |
280 if (this._index > 0) | |
281 --this._index; | |
282 this._render(); | |
283 } | |
284 | |
285 _onNextFrame() { | |
286 if (this._index < this._frames.length - 1) | |
287 ++this._index; | |
288 this._render(); | |
289 } | |
290 | |
291 _onFirstFrame() { | |
292 this._index = 0; | |
293 this._render(); | |
294 } | |
295 | |
296 _onLastFrame() { | |
297 this._index = this._frames.length - 1; | |
298 this._render(); | |
299 } | |
300 | |
301 /** | |
302 * @return {!Promise<undefined>} | |
303 */ | |
304 _render() { | |
305 var frame = this._frames[this._index]; | |
306 this._timeLabel.textContent = Number.millisToString(frame.timestamp - this._
zeroTime); | |
307 return frame.imageDataPromise() | |
308 .then(Components.FilmStripView._setImageData.bind(null, this._imageEleme
nt)) | |
309 .then(this._resize.bind(this)); | |
310 } | |
311 }; | |
OLD | NEW |