| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| /** |
| * @extends cr.EventTarget |
| * @param {HTMLDivElement} div Div container for breadcrumbs. |
| * @param {MetadataCache} metadataCache To retrieve metadata. |
| * @constructor |
| */ |
| function BreadcrumbsController(div, metadataCache) { |
| this.metadataCache_ = metadataCache; |
| this.bc_ = div; |
| this.hideLast_ = false; |
| this.rootPath_ = null; |
| this.path_ = null; |
| this.rootPathOverride_ = null; |
| div.addEventListener('click', this.onClick_.bind(this)); |
| } |
| |
| /** |
| * Extends cr.EventTarget. |
| */ |
| BreadcrumbsController.prototype.__proto__ = cr.EventTarget.prototype; |
| |
| /** |
| * Whether to hide the last part of the path. |
| * @param {boolean} value True if hide. |
| */ |
| BreadcrumbsController.prototype.setHideLast = function(value) { |
| this.hideLast_ = value; |
| }; |
| |
| /** |
| * Update the breadcrumb display. |
| * TODO(haruki): Remove |rootPath|. It only needs |path|, as |rootPath| can be |
| * derived. |
| * |
| * @param {string} rootPath Path to root. |
| * @param {string} path Path to directory. |
| */ |
| BreadcrumbsController.prototype.update = function(rootPath, path) { |
| if (path == this.path_) |
| return; |
| |
| // Clear the content and store |path|. |
| this.bc_.textContent = ''; |
| this.rootPath_ = rootPath; |
| this.path_ = path; |
| |
| // determineRootPathOverride_() may call MetadataCache to retrieve |
| // shared-with-me property and call the callback asynchronously. |
| this.determineRootPathOverride_( |
| rootPath, path, function(requestedPath, rootPathOverride) { |
| if (requestedPath != path) { |
| // Another update() is called. Ignore this old callback. |
| return; |
| } |
| this.rootPathOverride_ = rootPathOverride; |
| this.updateInternal_(rootPath, path, rootPathOverride); |
| }.bind(this)); |
| }; |
| |
| /** |
| * Check the given path and determines if the breadcrumb should show "Shared |
| * with me" as the first crumb. RootDirectory.DRIVE_SHARED_WITH_ME is used as |
| * the first crumb if |path| is a shared-with-me directory outside "My Drive". |
| * See updateInternal_(). |
| * |
| * @param {string} rootPath Path to root. |
| * @param {string} path Path to directory. |
| * @param {function(string, !string)} callback Function to call with |path| and |
| * the determined root path override. |
| * @private |
| */ |
| BreadcrumbsController.prototype.determineRootPathOverride_ = |
| function(rootPath, path, callback) { |
| if (rootPath != RootDirectory.DRIVE + '/' + DriveSubRootDirectory.OTHER || |
| rootPath == path) { |
| callback(path, null); // No need for Drive properties. |
| return; |
| } |
| |
| var delimiterPos = path.indexOf('/', rootPath.length + 1); |
| var firstDirPath = delimiterPos > 0 ? path.substring(0, delimiterPos) : path; |
| var entryUrl = util.makeFilesystemUrl(firstDirPath); |
| |
| this.metadataCache_.getOne(entryUrl, 'drive', function(result) { |
| callback(path, |
| result && result.sharedWithMe ? |
| RootDirectory.DRIVE_SHARED_WITH_ME : |
| null); |
| }); |
| }; |
| |
| /** |
| * Update the breadcrumb display. |
| * @private |
| */ |
| BreadcrumbsController.prototype.updateInternal_ = function() { |
| var relativePath = |
| this.path_.substring(this.rootPath_.length).replace(/\/$/, ''); |
| var pathNames = relativePath.split('/'); |
| if (pathNames[0] == '') |
| pathNames.splice(0, 1); |
| |
| // We need a first breadcrumb for root, so placing last name from |
| // rootPath as first name of relativePath. |
| var rootPathNames = this.rootPath_.replace(/\/$/, '').split('/'); |
| pathNames.splice(0, 0, rootPathNames[rootPathNames.length - 1]); |
| rootPathNames.splice(rootPathNames.length - 1, 1); |
| var path = rootPathNames.join('/') + '/'; |
| |
| var doc = this.bc_.ownerDocument; |
| |
| var divAdded = false; |
| for (var i = 0; |
| i < (this.hideLast_ ? pathNames.length - 1 : pathNames.length); |
| i++) { |
| if (divAdded) { |
| var spacer = doc.createElement('div'); |
| spacer.className = 'separator'; |
| this.bc_.appendChild(spacer); |
| } |
| var pathName = pathNames[i]; |
| path += pathName; |
| |
| // We have a special case for the root crumb /drive/other, which contains |
| // the Drive entries not in "My Drive". It is hidden as this path is not |
| // meaningful for the user. |
| if (i == 0 && |
| path === RootDirectory.DRIVE + '/' + DriveSubRootDirectory.OTHER && |
| this.rootPathOverride_ == null) { |
| continue; |
| } |
| |
| // Create a crumb. |
| var div = doc.createElement('div'); |
| div.className = 'breadcrumb-path'; |
| div.textContent = i == 0 ? |
| PathUtil.getRootLabel(this.rootPathOverride_ || path) : |
| pathName; |
| |
| path = path + '/'; |
| |
| this.bc_.appendChild(div); |
| divAdded = true; |
| |
| if (i == pathNames.length - 1) |
| div.classList.add('breadcrumb-last'); |
| } |
| this.truncate(); |
| }; |
| |
| /** |
| * Updates breadcrumbs widths in order to truncate it properly. |
| */ |
| BreadcrumbsController.prototype.truncate = function() { |
| if (!this.bc_.firstChild) |
| return; |
| |
| // Assume style.width == clientWidth (items have no margins or paddings). |
| |
| for (var item = this.bc_.firstChild; item; item = item.nextSibling) { |
| item.removeAttribute('style'); |
| item.removeAttribute('collapsed'); |
| } |
| |
| var containerWidth = this.bc_.clientWidth; |
| |
| var pathWidth = 0; |
| var currentWidth = 0; |
| var lastSeparator; |
| for (var item = this.bc_.firstChild; item; item = item.nextSibling) { |
| if (item.className == 'separator') { |
| pathWidth += currentWidth; |
| currentWidth = item.clientWidth; |
| lastSeparator = item; |
| } else { |
| currentWidth += item.clientWidth; |
| } |
| } |
| if (pathWidth + currentWidth <= containerWidth) |
| return; |
| if (!lastSeparator) { |
| this.bc_.lastChild.style.width = Math.min(currentWidth, containerWidth) + |
| 'px'; |
| return; |
| } |
| var lastCrumbSeparatorWidth = lastSeparator.clientWidth; |
| // Current directory name may occupy up to 70% of space or even more if the |
| // path is short. |
| var maxPathWidth = Math.max(Math.round(containerWidth * 0.3), |
| containerWidth - currentWidth); |
| maxPathWidth = Math.min(pathWidth, maxPathWidth); |
| |
| var parentCrumb = lastSeparator.previousSibling; |
| var collapsedWidth = 0; |
| if (parentCrumb && pathWidth - maxPathWidth > parentCrumb.clientWidth) { |
| // At least one crumb is hidden completely (or almost completely). |
| // Show sign of hidden crumbs like this: |
| // root > some di... > ... > current directory. |
| parentCrumb.setAttribute('collapsed', ''); |
| collapsedWidth = Math.min(maxPathWidth, parentCrumb.clientWidth); |
| maxPathWidth -= collapsedWidth; |
| if (parentCrumb.clientWidth != collapsedWidth) |
| parentCrumb.style.width = collapsedWidth + 'px'; |
| |
| lastSeparator = parentCrumb.previousSibling; |
| if (!lastSeparator) |
| return; |
| collapsedWidth += lastSeparator.clientWidth; |
| maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth); |
| } |
| |
| pathWidth = 0; |
| for (var item = this.bc_.firstChild; item != lastSeparator; |
| item = item.nextSibling) { |
| // TODO(serya): Mixing access item.clientWidth and modifying style and |
| // attributes could cause multiple layout reflows. |
| if (pathWidth + item.clientWidth <= maxPathWidth) { |
| pathWidth += item.clientWidth; |
| } else if (pathWidth == maxPathWidth) { |
| item.style.width = '0'; |
| } else if (item.classList.contains('separator')) { |
| // Do not truncate separator. Instead let the last crumb be longer. |
| item.style.width = '0'; |
| maxPathWidth = pathWidth; |
| } else { |
| // Truncate the last visible crumb. |
| item.style.width = (maxPathWidth - pathWidth) + 'px'; |
| pathWidth = maxPathWidth; |
| } |
| } |
| |
| currentWidth = Math.min(currentWidth, |
| containerWidth - pathWidth - collapsedWidth); |
| this.bc_.lastChild.style.width = |
| (currentWidth - lastCrumbSeparatorWidth) + 'px'; |
| }; |
| |
| /** |
| * Show breadcrumbs. |
| * @param {string} rootPath Path to root. |
| * @param {string} path Path to directory. |
| */ |
| BreadcrumbsController.prototype.show = function(rootPath, path) { |
| this.bc_.hidden = false; |
| this.update(rootPath, path); |
| }; |
| |
| /** |
| * Hide breadcrumbs div. |
| */ |
| BreadcrumbsController.prototype.hide = function() { |
| this.bc_.hidden = true; |
| }; |
| |
| /** |
| * Handle a click event on a breadcrumb element. |
| * @param {Event} event The click event. |
| * @private |
| */ |
| BreadcrumbsController.prototype.onClick_ = function(event) { |
| var path = this.getTargetPath(event); |
| if (!path) |
| return; |
| |
| var newEvent = new Event('pathclick'); |
| newEvent.path = path; |
| this.dispatchEvent(newEvent); |
| }; |
| |
| /** |
| * Returns path associated with the event target. Returns empty string for |
| * inactive elements: separators, empty space and the last chunk. |
| * @param {Event} event The UI event. |
| * @return {string} Full path or empty string. |
| */ |
| BreadcrumbsController.prototype.getTargetPath = function(event) { |
| if (!event.target.classList.contains('breadcrumb-path') || |
| event.target.classList.contains('breadcrumb-last')) { |
| return ''; |
| } |
| |
| var items = this.bc_.querySelectorAll('.breadcrumb-path'); |
| var path = this.rootPath_; |
| |
| if (event.target == items[0]) { |
| // The first crumb can be overridden. |
| return this.rootPathOverride_ || path; |
| } else { |
| for (var i = 1; items[i - 1] != event.target; i++) { |
| path += '/' + items[i].textContent; |
| } |
| } |
| return path; |
| }; |
| |
| /** |
| * Returns the breadcrumbs container. |
| * @return {HTMLElement} Breadcumbs container HTML element. |
| */ |
| BreadcrumbsController.prototype.getContainer = function() { |
| return this.bc_; |
| }; |