blob: 5195f867673925289f87e0910dd6c6e74efd891c [file] [log] [blame]
// Copyright (c) 2012 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.
/**
* Base item of NavigationListModel. Should not be created directly.
* @param {string} label Label.
* @constructor
*/
function NavigationModelItem(label) {
this.label_ = label;
}
NavigationModelItem.prototype = {
get label() { return this.label_; }
};
/**
* Check whether given two model items are same.
* @param {NavigationModelItem} item1 The first item to be compared.
* @param {NavigationModelItem} item2 The second item to be compared.
* @return {boolean} True if given two model items are same.
*/
NavigationModelItem.isSame = function(item1, item2) {
if (item1.isVolume != item2.isVolume)
return false;
if (item1.isVolume)
return item1.volumeInfo === item2.volumeInfo;
else
return util.isSameEntry(item1.entry, item2.entry);
};
/**
* Item of NavigationListModel for shortcuts.
*
* @param {string} label Label.
* @param {!DirectoryEntry} entry Entry. Cannot be null.
* @constructor
* @extends {NavigationModelItem}
*/
function NavigationModelShortcutItem(label, entry) {
NavigationModelItem.call(this, label);
this.entry_ = entry;
Object.freeze(this);
}
NavigationModelShortcutItem.prototype = {
__proto__: NavigationModelItem.prototype,
get entry() { return this.entry_; },
get isVolume() { return false; },
get isShortcut() { return true; }
};
/**
* Item of NavigationListModel for volumes.
*
* @param {string} label Label.
* @param {!VolumeInfo} volumeInfo Volume info for the volume. Cannot be null.
* @constructor
* @extends {NavigationModelItem}
*/
function NavigationModelVolumeItem(label, volumeInfo) {
NavigationModelItem.call(this, label);
this.volumeInfo_ = volumeInfo;
// Start resolving the display root because it is used
// for determining executability of commands.
this.volumeInfo_.resolveDisplayRoot(
function() {}, function() {});
Object.freeze(this);
}
NavigationModelVolumeItem.prototype = {
__proto__: NavigationModelItem.prototype,
get volumeInfo() { return this.volumeInfo_; },
get isVolume() { return true; },
get isShortcut() { return false; }
};
/**
* A navigation list model. This model combines the 2 lists.
* @param {VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance.
* @param {(cr.ui.ArrayDataModel|FolderShortcutsDataModel)} shortcutListModel
* The list of folder shortcut.
* @constructor
* @extends {cr.EventTarget}
*/
function NavigationListModel(volumeManager, shortcutListModel) {
cr.EventTarget.call(this);
this.volumeManager_ = volumeManager;
this.shortcutListModel_ = shortcutListModel;
var volumeInfoToModelItem = function(volumeInfo) {
return new NavigationModelVolumeItem(
volumeInfo.label,
volumeInfo);
}.bind(this);
var entryToModelItem = function(entry) {
var item = new NavigationModelShortcutItem(
entry.name,
entry);
return item;
}.bind(this);
/**
* Type of updated list.
* @enum {number}
* @const
*/
var ListType = {
VOLUME_LIST: 1,
SHORTCUT_LIST: 2
};
Object.freeze(ListType);
// Generates this.volumeList_ and this.shortcutList_ from the models.
this.volumeList_ =
this.volumeManager_.volumeInfoList.slice().map(volumeInfoToModelItem);
this.shortcutList_ = [];
for (var i = 0; i < this.shortcutListModel_.length; i++) {
var shortcutEntry = /** @type {Entry} */ (this.shortcutListModel_.item(i));
var volumeInfo = this.volumeManager_.getVolumeInfo(shortcutEntry);
this.shortcutList_.push(entryToModelItem(shortcutEntry));
}
// Generates a combined 'permuted' event from an event of either list.
var permutedHandler = function(listType, event) {
var permutation;
// Build the volumeList.
if (listType == ListType.VOLUME_LIST) {
// The volume is mounted or unmounted.
var newList = [];
// Use the old instances if they just move.
for (var i = 0; i < event.permutation.length; i++) {
if (event.permutation[i] >= 0)
newList[event.permutation[i]] = this.volumeList_[i];
}
// Create missing instances.
for (var i = 0; i < event.newLength; i++) {
if (!newList[i]) {
newList[i] = volumeInfoToModelItem(
this.volumeManager_.volumeInfoList.item(i));
}
}
this.volumeList_ = newList;
permutation = event.permutation.slice();
// shortcutList part has not been changed, so the permutation should be
// just identity mapping with a shift.
for (var i = 0; i < this.shortcutList_.length; i++) {
permutation.push(i + this.volumeList_.length);
}
} else {
// Build the shortcutList.
// volumeList part has not been changed, so the permutation should be
// identity mapping.
permutation = [];
for (var i = 0; i < this.volumeList_.length; i++) {
permutation[i] = i;
}
var modelIndex = 0;
var oldListIndex = 0;
var newList = [];
while (modelIndex < this.shortcutListModel_.length &&
oldListIndex < this.shortcutList_.length) {
var shortcutEntry = this.shortcutListModel_.item(modelIndex);
var cmp = this.shortcutListModel_.compare(
shortcutEntry, this.shortcutList_[oldListIndex].entry);
if (cmp > 0) {
// The shortcut at shortcutList_[oldListIndex] is removed.
permutation.push(-1);
oldListIndex++;
continue;
}
if (cmp === 0) {
// Reuse the old instance.
permutation.push(newList.length + this.volumeList_.length);
newList.push(this.shortcutList_[oldListIndex]);
oldListIndex++;
} else {
// We needs to create a new instance for the shortcut entry.
newList.push(entryToModelItem(shortcutEntry));
}
modelIndex++;
}
// Add remaining (new) shortcuts if necessary.
for (; modelIndex < this.shortcutListModel_.length; modelIndex++) {
var shortcutEntry = this.shortcutListModel_.item(modelIndex);
newList.push(entryToModelItem(shortcutEntry));
}
// Fill remaining permutation if necessary.
for (; oldListIndex < this.shortcutList_.length; oldListIndex++)
permutation.push(-1);
this.shortcutList_ = newList;
}
// Dispatch permuted event.
var permutedEvent = new Event('permuted');
permutedEvent.newLength =
this.volumeList_.length + this.shortcutList_.length;
permutedEvent.permutation = permutation;
this.dispatchEvent(permutedEvent);
};
this.volumeManager_.volumeInfoList.addEventListener(
'permuted', permutedHandler.bind(this, ListType.VOLUME_LIST));
this.shortcutListModel_.addEventListener(
'permuted', permutedHandler.bind(this, ListType.SHORTCUT_LIST));
// 'change' event is just ignored, because it is not fired neither in
// the folder shortcut list nor in the volume info list.
// 'splice' and 'sorted' events are not implemented, since they are not used
// in list.js.
}
/**
* NavigationList inherits cr.EventTarget.
*/
NavigationListModel.prototype = {
__proto__: cr.EventTarget.prototype,
get length() { return this.length_(); },
get folderShortcutList() { return this.shortcutList_; }
};
/**
* Returns the item at the given index.
* @param {number} index The index of the entry to get.
* @return {NavigationModelItem} The item at the given index.
*/
NavigationListModel.prototype.item = function(index) {
var offset = this.volumeList_.length;
if (index < offset)
return this.volumeList_[index];
return this.shortcutList_[index - offset];
};
/**
* Returns the number of items in the model.
* @return {number} The length of the model.
* @private
*/
NavigationListModel.prototype.length_ = function() {
return this.volumeList_.length + this.shortcutList_.length;
};
/**
* Returns the first matching item.
* @param {NavigationModelItem} modelItem The entry to find.
* @param {number=} opt_fromIndex If provided, then the searching start at
* the {@code opt_fromIndex}.
* @return {number} The index of the first found element or -1 if not found.
*/
NavigationListModel.prototype.indexOf = function(modelItem, opt_fromIndex) {
for (var i = opt_fromIndex || 0; i < this.length; i++) {
if (modelItem === this.item(i))
return i;
}
return -1;
};
/**
* Called externally when one of the items is not found on the filesystem.
* @param {NavigationModelItem} modelItem The entry which is not found.
*/
NavigationListModel.prototype.onItemNotFoundError = function(modelItem) {
if (modelItem.isVolume) {
// TODO(mtomasz, yoshiki): Implement when needed.
return;
}
if (modelItem.isShortcut) {
// For shortcuts, lets the shortcut model handle this situation.
this.shortcutListModel_.onItemNotFoundError(modelItem.entry);
}
};