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 |