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.
6 * TODO(hirono): Remove metadataCache and volumeManager dependencies from the UI
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.
15 function LocationLine(breadcrumbs, volumeIcon, metadataCache, volumeManager) {
16 this.breadcrumbs_ = breadcrumbs;
17 this.volumeIcon_ = volumeIcon;
18 this.metadataCache_ = metadataCache;
19 this.volumeManager_ = volumeManager;
23 * Sequence value to skip requests that are out of date.
27 this.showSequence_ = 0;
29 // Register events and seql the object.
30 breadcrumbs.addEventListener('click', this.onClick_.bind(this));
34 * Extends cr.EventTarget.
36 LocationLine.prototype.__proto__ = cr.EventTarget.prototype;
41 * @param {Entry} entry Target entry.
43 LocationLine.prototype.show = function(entry) {
44 if (entry === this.entry_)
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');
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);
66 this.volumeIcon_.setAttribute(
67 'volume-type-icon', locationInfo.rootType);
69 this.volumeIcon_.setAttribute(
70 'volume-type-icon', locationInfo.volumeInfo.volumeType);
71 this.volumeIcon_.setAttribute(
72 'volume-subtype', locationInfo.volumeInfo.deviceType || '');
75 var queue = new AsyncUtil.Queue();
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) {
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);
103 entries.unshift(currentEntry);
105 // Finishes traversal since the current is root.
111 entries.unshift(currentEntry);
112 if (!entryLocationInfo.isRootEntry) {
113 currentEntry.getParent(function(parentEntry) {
114 resolveParent(parentEntry, currentEntry, callback);
115 }.bind(this), function() {
124 queue.run(resolveParent.bind(this, entry, null));
126 queue.run(function(callback) {
127 // If an error occurred, just skip.
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);
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 = '';
149 this.updateInternal_(entries);
152 }.bind(this, this.showSequence_));
156 * Updates the breadcrumb display.
157 * @param {Array.<!Entry>} entries Entries on the target path.
160 LocationLine.prototype.updateInternal_ = function(entries) {
162 var doc = this.breadcrumbs_.ownerDocument;
163 for (var i = 0; i < entries.length; i++) {
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);
170 this.breadcrumbs_.appendChild(div);
172 // If this is the last component, break here.
173 if (i === entries.length - 1) {
174 div.classList.add('breadcrumb-last');
179 var separator = doc.createElement('div');
180 separator.className = 'separator';
181 this.breadcrumbs_.appendChild(separator);
188 * Updates breadcrumbs widths in order to truncate it properly.
190 LocationLine.prototype.truncate = function() {
191 if (!this.breadcrumbs_.firstChild)
194 // Assume style.width == clientWidth (items have no margins or paddings).
196 for (var item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) {
197 item.removeAttribute('style');
198 item.removeAttribute('collapsed');
201 var containerWidth = this.breadcrumbs_.clientWidth;
204 var currentWidth = 0;
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;
212 currentWidth += item.clientWidth;
215 if (pathWidth + currentWidth <= containerWidth)
217 if (!lastSeparator) {
218 this.breadcrumbs_.lastChild.style.width =
219 Math.min(currentWidth, containerWidth) + 'px';
222 var lastCrumbSeparatorWidth = lastSeparator.clientWidth;
223 // Current directory name may occupy up to 70% of space or even more if the
225 var maxPathWidth = Math.max(Math.round(containerWidth * 0.3),
226 containerWidth - currentWidth);
227 maxPathWidth = Math.min(pathWidth, maxPathWidth);
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';
241 lastSeparator = parentCrumb.previousSibling;
244 collapsedWidth += lastSeparator.clientWidth;
245 maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
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;
262 // Truncate the last visible crumb.
263 item.style.width = (maxPathWidth - pathWidth) + 'px';
264 pathWidth = maxPathWidth;
268 currentWidth = Math.min(currentWidth,
269 containerWidth - pathWidth - collapsedWidth);
270 this.breadcrumbs_.lastChild.style.width =
271 (currentWidth - lastCrumbSeparatorWidth) + 'px';
275 * Hide breadcrumbs div.
277 LocationLine.prototype.hide = function() {
278 this.breadcrumbs_.hidden = true;
282 * Handle a click event on a breadcrumb element.
283 * @param {Event} event The click event.
286 LocationLine.prototype.onClick_ = function(event) {
287 if (!event.target.classList.contains('breadcrumb-path') ||
288 event.target.classList.contains('breadcrumb-last'))
291 var newEvent = new Event('pathclick');
292 newEvent.entry = event.target.entry;
293 this.dispatchEvent(newEvent);