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