Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / ui / location_line.js
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.
4
5 /**
6  * TODO(hirono): Remove metadataCache and volumeManager dependencies from the UI
7  * class.
8  * @extends {cr.EventTarget}
9  * @param {!Element} breadcrumbs Container element for breadcrumbs.
10  * @param {!Element} volumeIcon Volume icon.
11  * @param {MetadataCache} metadataCache To retrieve metadata.
12  * @param {VolumeManagerWrapper} volumeManager Volume manager.
13  * @constructor
14  */
15 function LocationLine(breadcrumbs, volumeIcon, metadataCache, volumeManager) {
16   this.breadcrumbs_ = breadcrumbs;
17   this.volumeIcon_ = volumeIcon;
18   this.metadataCache_ = metadataCache;
19   this.volumeManager_ = volumeManager;
20   this.entry_ = null;
21
22   /**
23    * Sequence value to skip requests that are out of date.
24    * @type {number}
25    * @private
26    */
27   this.showSequence_ = 0;
28
29   // Register events and seql the object.
30   breadcrumbs.addEventListener('click', this.onClick_.bind(this));
31 }
32
33 /**
34  * Extends cr.EventTarget.
35  */
36 LocationLine.prototype.__proto__ = cr.EventTarget.prototype;
37
38 /**
39  * Shows breadcrumbs.
40  *
41  * @param {Entry} entry Target entry.
42  */
43 LocationLine.prototype.show = function(entry) {
44   if (entry === this.entry_)
45     return;
46
47   this.entry_ = entry;
48   this.showSequence_++;
49
50   // Clear the background image for the icon and the sub type (if any).
51   this.volumeIcon_.removeAttribute('style');
52   this.volumeIcon_.removeAttribute('volume-subtype');
53
54   // Updates volume icon.
55   var locationInfo = this.volumeManager_.getLocationInfo(entry);
56   if (locationInfo && locationInfo.rootType && locationInfo.isRootEntry) {
57     if (locationInfo.volumeInfo.volumeType ===
58             VolumeManagerCommon.VolumeType.PROVIDED) {
59       var extensionId = locationInfo.volumeInfo.extensionId;
60       var backgroundImage = '-webkit-image-set(' +
61           'url(chrome://extension-icon/' + extensionId + '/16/1) 1x, ' +
62           'url(chrome://extension-icon/' + extensionId + '/32/1) 2x);';
63       this.volumeIcon_.setAttribute(
64           'style', 'background-image: ' + backgroundImage);
65     }
66     this.volumeIcon_.setAttribute(
67         'volume-type-icon', locationInfo.rootType);
68   } else {
69     this.volumeIcon_.setAttribute(
70         'volume-type-icon', locationInfo.volumeInfo.volumeType);
71     this.volumeIcon_.setAttribute(
72         'volume-subtype', locationInfo.volumeInfo.deviceType || '');
73   }
74
75   var queue = new AsyncUtil.Queue();
76   var entries = [];
77   var error = false;
78
79   // Obtain entries from the target entry to the root.
80   var resolveParent = function(currentEntry, previousEntry, callback) {
81     var entryLocationInfo = this.volumeManager_.getLocationInfo(currentEntry);
82     if (!entryLocationInfo) {
83       error = true;
84       callback();
85       return;
86     }
87
88     if (entryLocationInfo.isRootEntry &&
89         entryLocationInfo.rootType ===
90             VolumeManagerCommon.RootType.DRIVE_OTHER) {
91       this.metadataCache_.getOne(previousEntry, 'external', function(result) {
92         if (result && result.sharedWithMe) {
93           // Adds the shared-with-me entry instead.
94           var driveVolumeInfo = entryLocationInfo.volumeInfo;
95           var sharedWithMeEntry =
96               driveVolumeInfo.fakeEntries[
97                   VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME];
98           if (sharedWithMeEntry)
99             entries.unshift(sharedWithMeEntry);
100           else
101             error = true;
102         } else {
103           entries.unshift(currentEntry);
104         }
105         // Finishes traversal since the current is root.
106         callback();
107       });
108       return;
109     }
110
111     entries.unshift(currentEntry);
112     if (!entryLocationInfo.isRootEntry) {
113       currentEntry.getParent(function(parentEntry) {
114         resolveParent(parentEntry, currentEntry, callback);
115       }.bind(this), function() {
116         error = true;
117         callback();
118       });
119     } else {
120       callback();
121     }
122   }.bind(this);
123
124   queue.run(resolveParent.bind(this, entry, null));
125
126   queue.run(function(callback) {
127     // If an error occurred, just skip.
128     if (error) {
129       callback();
130       return;
131     }
132
133     // If the path is not under the drive other root, it is not needed to
134     // override root type.
135     var locationInfo = this.volumeManager_.getLocationInfo(entry);
136     if (!locationInfo)
137       error = true;
138
139     callback();
140   }.bind(this));
141
142   // Update DOM element.
143   queue.run(function(sequence, callback) {
144     // Check the sequence number to skip requests that are out of date.
145     if (this.showSequence_ === sequence) {
146       this.breadcrumbs_.hidden = false;
147       this.breadcrumbs_.textContent = '';
148       if (!error)
149         this.updateInternal_(entries);
150     }
151     callback();
152   }.bind(this, this.showSequence_));
153 };
154
155 /**
156  * Updates the breadcrumb display.
157  * @param {Array.<!Entry>} entries Entries on the target path.
158  * @private
159  */
160 LocationLine.prototype.updateInternal_ = function(entries) {
161   // Make elements.
162   var doc = this.breadcrumbs_.ownerDocument;
163   for (var i = 0; i < entries.length; i++) {
164     // Add a component.
165     var entry = entries[i];
166     var div = doc.createElement('div');
167     div.className = 'breadcrumb-path entry-name';
168     div.textContent = util.getEntryLabel(this.volumeManager_, entry);
169     div.entry = entry;
170     this.breadcrumbs_.appendChild(div);
171
172     // If this is the last component, break here.
173     if (i === entries.length - 1) {
174       div.classList.add('breadcrumb-last');
175       break;
176     }
177
178     // Add a separator.
179     var separator = doc.createElement('div');
180     separator.className = 'separator';
181     this.breadcrumbs_.appendChild(separator);
182   }
183
184   this.truncate();
185 };
186
187 /**
188  * Updates breadcrumbs widths in order to truncate it properly.
189  */
190 LocationLine.prototype.truncate = function() {
191   if (!this.breadcrumbs_.firstChild)
192     return;
193
194   // Assume style.width == clientWidth (items have no margins or paddings).
195
196   for (var item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) {
197     item.removeAttribute('style');
198     item.removeAttribute('collapsed');
199   }
200
201   var containerWidth = this.breadcrumbs_.clientWidth;
202
203   var pathWidth = 0;
204   var currentWidth = 0;
205   var lastSeparator;
206   for (var item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) {
207     if (item.className == 'separator') {
208       pathWidth += currentWidth;
209       currentWidth = item.clientWidth;
210       lastSeparator = item;
211     } else {
212       currentWidth += item.clientWidth;
213     }
214   }
215   if (pathWidth + currentWidth <= containerWidth)
216     return;
217   if (!lastSeparator) {
218     this.breadcrumbs_.lastChild.style.width =
219         Math.min(currentWidth, containerWidth) + 'px';
220     return;
221   }
222   var lastCrumbSeparatorWidth = lastSeparator.clientWidth;
223   // Current directory name may occupy up to 70% of space or even more if the
224   // path is short.
225   var maxPathWidth = Math.max(Math.round(containerWidth * 0.3),
226                               containerWidth - currentWidth);
227   maxPathWidth = Math.min(pathWidth, maxPathWidth);
228
229   var parentCrumb = lastSeparator.previousSibling;
230   var collapsedWidth = 0;
231   if (parentCrumb && pathWidth - maxPathWidth > parentCrumb.clientWidth) {
232     // At least one crumb is hidden completely (or almost completely).
233     // Show sign of hidden crumbs like this:
234     // root > some di... > ... > current directory.
235     parentCrumb.setAttribute('collapsed', '');
236     collapsedWidth = Math.min(maxPathWidth, parentCrumb.clientWidth);
237     maxPathWidth -= collapsedWidth;
238     if (parentCrumb.clientWidth != collapsedWidth)
239       parentCrumb.style.width = collapsedWidth + 'px';
240
241     lastSeparator = parentCrumb.previousSibling;
242     if (!lastSeparator)
243       return;
244     collapsedWidth += lastSeparator.clientWidth;
245     maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
246   }
247
248   pathWidth = 0;
249   for (var item = this.breadcrumbs_.firstChild; item != lastSeparator;
250        item = item.nextSibling) {
251     // TODO(serya): Mixing access item.clientWidth and modifying style and
252     // attributes could cause multiple layout reflows.
253     if (pathWidth + item.clientWidth <= maxPathWidth) {
254       pathWidth += item.clientWidth;
255     } else if (pathWidth == maxPathWidth) {
256       item.style.width = '0';
257     } else if (item.classList.contains('separator')) {
258       // Do not truncate separator. Instead let the last crumb be longer.
259       item.style.width = '0';
260       maxPathWidth = pathWidth;
261     } else {
262       // Truncate the last visible crumb.
263       item.style.width = (maxPathWidth - pathWidth) + 'px';
264       pathWidth = maxPathWidth;
265     }
266   }
267
268   currentWidth = Math.min(currentWidth,
269                           containerWidth - pathWidth - collapsedWidth);
270   this.breadcrumbs_.lastChild.style.width =
271       (currentWidth - lastCrumbSeparatorWidth) + 'px';
272 };
273
274 /**
275  * Hide breadcrumbs div.
276  */
277 LocationLine.prototype.hide = function() {
278   this.breadcrumbs_.hidden = true;
279 };
280
281 /**
282  * Handle a click event on a breadcrumb element.
283  * @param {Event} event The click event.
284  * @private
285  */
286 LocationLine.prototype.onClick_ = function(event) {
287   if (!event.target.classList.contains('breadcrumb-path') ||
288       event.target.classList.contains('breadcrumb-last'))
289     return;
290
291   var newEvent = new Event('pathclick');
292   newEvent.entry = event.target.entry;
293   this.dispatchEvent(newEvent);
294 };