Refactor the way enhanced bookmark gets large icons
[chromium-blink-merge.git] / ui / file_manager / audio_player / elements / track_list.js
blobb53fa038a24500ece0c6bc258ca40147834804dc
1 // Copyright 2014 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.
5 (function() {
6 'use strict';
8 /**
9 * @constructor
10 * @extends {PolymerElement}
12 var TrackListElement = function() {};
14 TrackListElement.prototype = {
15 is: 'track-list',
17 properties: {
18 /**
19 * List of tracks.
20 * @type {Array<AudioPlayer.TrackInfo>}
22 tracks: {
23 type: Array,
24 value: [],
25 observer: 'tracksChanged'
28 /**
29 * Track index of the current track.
30 * If the tracks property is empty, it should be -1. Otherwise, be a valid
31 * track number.
33 currentTrackIndex: {
34 type: Number,
35 value: -1,
36 observer: 'currentTrackIndexChanged',
37 notify: true
40 /**
41 * Whether shuffling play order is enabled or not.
43 shuffle: {
44 type: Boolean,
45 value: false,
46 observer: 'shuffleChanged'
50 /**
51 * Initializes an element. This method is called automatically when the
52 * element is ready.
54 ready: function() {
55 this.observeTrackList();
57 window.addEventListener('resize', this.onWindowResize_.bind(this));
60 observeTrackList: function() {
61 // Unobserve the previous track list.
62 if (this.unobserveTrackList_)
63 this.unobserveTrackList_();
65 // Observe the new track list.
66 var observer = this.tracksValueChanged_.bind(this);
67 Array.observe(this.tracks, observer);
69 // Set the function to unobserve it.
70 this.unobserveTrackList_ = function(tracks, observer) {
71 Array.unobserve(tracks, observer);
72 }.bind(null, this.tracks, observer);
75 /**
76 * Play order of the tracks. Each value is the index of 'this.tracks'.
77 * @type {Array<number>}
79 playOrder: [],
81 /**
82 * Invoked when 'shuffle' property is changed.
83 * @param {boolean} newValue New value.
84 * @param {boolean} oldValue Old value.
86 shuffleChanged: function(newValue, oldValue) {
87 this.generatePlayOrder(true /* keep the current track */);
90 /**
91 * Invoked when the current track index is changed.
92 * @param {number} newValue new value.
93 * @param {number} oldValue old value.
95 currentTrackIndexChanged: function(newValue, oldValue) {
96 if (oldValue === newValue)
97 return;
99 if (!isNaN(oldValue) && 0 <= oldValue && oldValue < this.tracks.length)
100 this.set('tracks.' + oldValue + '.active', false);
102 if (0 <= newValue && newValue < this.tracks.length) {
103 var currentPlayOrder = this.playOrder.indexOf(newValue);
104 if (currentPlayOrder !== -1) {
105 // Success
106 this.set('tracks.' + newValue + '.active', true);
108 this.ensureTrackInViewport_(newValue /* trackIndex */);
109 return;
113 // Invalid index
114 if (this.tracks.length === 0)
115 this.currentTrackIndex = -1;
116 else
117 this.generatePlayOrder(false /* no need to keep the current track */);
121 * Invoked when 'tracks' property is changed.
122 * @param {Array<AudioPlayer.TrackInfo>} newValue New value.
123 * @param {Array<AudioPlayer.TrackInfo>} oldValue Old value.
125 tracksChanged: function(newValue, oldValue) {
126 // Note: Sometimes both oldValue and newValue are null though the actual
127 // values are not null. Maybe it's a bug of Polymer.
129 // Re-register the observer of 'this.tracks'.
130 this.observeTrackList();
132 if (this.tracks.length !== 0) {
133 // Restore the active track.
134 if (this.currentTrackIndex !== -1 &&
135 this.currentTrackIndex < this.tracks.length) {
136 this.set('tracks.' + this.currentTrackIndex + '.active', true);
139 // Reset play order and current index.
140 this.generatePlayOrder(false /* no need to keep the current track */);
141 } else {
142 this.playOrder = [];
143 this.currentTrackIndex = -1;
148 * Invoked when the value in the 'tracks' is changed.
149 * @param {Array<Object>} changes The detail of the change.
151 tracksValueChanged_: function(changes) {
152 if (this.tracks.length === 0)
153 this.currentTrackIndex = -1;
154 else
155 this.set('tracks.' + this.currentTrackIndex + '.active', true);
159 * Invoked when the track element is clicked.
160 * @param {Event} event Click event.
162 trackClicked: function(event) {
163 var index = ~~event.currentTarget.getAttribute('index');
164 var track = this.tracks[index];
165 if (track)
166 this.selectTrack(track);
170 * Invoked when the window is resized.
171 * @private
173 onWindowResize_: function() {
174 this.ensureTrackInViewport_(this.currentTrackIndex);
178 * Scrolls the track list to ensure the given track in the viewport.
179 * @param {number} trackIndex The index of the track to be in the viewport.
180 * @private
182 ensureTrackInViewport_: function(trackIndex) {
183 var trackSelector = '::shadow .track[index="' + trackIndex + '"]';
184 var trackElement = this.querySelector(trackSelector);
185 if (trackElement) {
186 var viewTop = this.scrollTop;
187 var viewHeight = this.clientHeight;
188 var elementTop = trackElement.offsetTop;
189 var elementHeight = trackElement.offsetHeight;
191 if (elementTop < viewTop) {
192 // Adjust the tops.
193 this.scrollTop = elementTop;
194 } else if (elementTop + elementHeight <= viewTop + viewHeight) {
195 // The entire element is in the viewport. Do nothing.
196 } else {
197 // Adjust the bottoms.
198 this.scrollTop = Math.max(0,
199 (elementTop + elementHeight - viewHeight));
205 * Invoked when the track element is clicked.
206 * @param {boolean} keepCurrentTrack Keep the current track or not.
208 generatePlayOrder: function(keepCurrentTrack) {
209 console.assert((keepCurrentTrack !== undefined),
210 'The argument "forward" is undefined');
212 if (this.tracks.length === 0) {
213 this.playOrder = [];
214 return;
217 // Creates sequenced array.
218 this.playOrder =
219 this.tracks.
220 map(function(unused, index) { return index; });
222 if (this.shuffle) {
223 // Randomizes the play order array (Schwarzian-transform algorithm).
224 this.playOrder = this.playOrder
225 .map(function(a) {
226 return {weight: Math.random(), index: a};
228 .sort(function(a, b) { return a.weight - b.weight })
229 .map(function(a) { return a.index });
231 if (keepCurrentTrack) {
232 // Puts the current track at the beginning of the play order.
233 this.playOrder = this.playOrder
234 .filter(function(value) {
235 return this.currentTrackIndex !== value;
236 }, this);
237 this.playOrder.splice(0, 0, this.currentTrackIndex);
241 if (!keepCurrentTrack)
242 this.currentTrackIndex = this.playOrder[0];
246 * Sets the current track.
247 * @param {AudioPlayer.TrackInfo} track TrackInfo to be set as the current
248 * track.
250 selectTrack: function(track) {
251 var index = -1;
252 for (var i = 0; i < this.tracks.length; i++) {
253 if (this.tracks[i].url === track.url) {
254 index = i;
255 break;
258 if (index >= 0) {
259 // TODO(yoshiki): Clean up the flow and the code around here.
260 if (this.currentTrackIndex == index)
261 this.replayCurrentTrack();
262 else
263 this.currentTrackIndex = index;
268 * Request to replay the current music.
270 replayCurrentTrack: function() {
271 this.fire('replay');
275 * Returns the current track.
276 * @return {AudioPlayer.TrackInfo} track TrackInfo of the current track.
278 getCurrentTrack: function() {
279 if (this.tracks.length === 0)
280 return null;
282 return this.tracks[this.currentTrackIndex];
286 * Returns the next (or previous) track in the track list. If there is no
287 * next track, returns -1.
289 * @param {boolean} forward Specify direction: forward or previous mode.
290 * True: forward mode, false: previous mode.
291 * @param {boolean} cyclic Specify if cyclically or not: It true, the first
292 * track is succeeding to the last track, otherwise no track after the
293 * last.
294 * @return {number} The next track index.
296 getNextTrackIndex: function(forward, cyclic) {
297 if (this.tracks.length === 0)
298 return -1;
300 var defaultTrackIndex =
301 forward ? this.playOrder[0] : this.playOrder[this.tracks.length - 1];
303 var currentPlayOrder = this.playOrder.indexOf(this.currentTrackIndex);
304 console.assert(
305 (0 <= currentPlayOrder && currentPlayOrder < this.tracks.length),
306 'Insufficient TrackList.playOrder. The current track is not on the ' +
307 'track list.');
309 var newPlayOrder = currentPlayOrder + (forward ? +1 : -1);
310 if (newPlayOrder === -1 || newPlayOrder === this.tracks.length)
311 return cyclic ? defaultTrackIndex : -1;
313 var newTrackIndex = this.playOrder[newPlayOrder];
314 console.assert(
315 (0 <= newTrackIndex && newTrackIndex < this.tracks.length),
316 'Insufficient TrackList.playOrder. New Play Order: ' + newPlayOrder);
318 return newTrackIndex;
320 }; // TrackListElement.prototype for 'track-list'
322 Polymer(TrackListElement.prototype);
323 })(); // Anonymous closure