Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
235 lines
7.5 KiB
JavaScript
Executable file
235 lines
7.5 KiB
JavaScript
Executable file
/**
|
|
* ThumbnailList is the class reponsible for rendering all video data in to a
|
|
* list. It uses GroupClass to group and sort video data as ThumbnailItem.
|
|
*
|
|
* Constructor:
|
|
* groupClass: the grouping class. We only have ThumbnailDataGroup, now.
|
|
* container: the HTML DOM object for containing everything.
|
|
*
|
|
* API:
|
|
* addItem: add a video data and render it.
|
|
* item: the video data to add.
|
|
* removeItem: remove a ThumbnailItem.
|
|
* filename: the filename of video data.
|
|
* getCount: returns the total video data in this list.
|
|
* isPickMode: returns if ThumbnailList is in pick mode.
|
|
* setPickMode: set ThumbnailList as pick mode or not.
|
|
* isSelectMode: returns if ThumbnailList is in selection mode.
|
|
* setSelectMode: set ThumbnailList as selection mode or not.
|
|
* reset: clears all the internal data structure.
|
|
* updateAllThumbnailTitles: update all thumbnail title text.
|
|
* findNextFocused: find the next focused item after the file name.
|
|
* filename: the referenced filename.
|
|
*
|
|
* Properties:
|
|
* thumbnailMap: a mapping of filename to ThumbnailItem.
|
|
* groupMap: a mapping of filename to thumbnail group.
|
|
* itemGroups: an sorted array of thumbnail group.
|
|
* count: the total ThumbnailItem in this list.
|
|
* groupClass: the grouping class this list used.
|
|
* container: the HTML DOM element containing this list.
|
|
*/
|
|
'use strict';
|
|
|
|
function ThumbnailList(groupClass, container) {
|
|
if (!groupClass || !container) {
|
|
throw new Error('group class or container cannot be null or undefined');
|
|
}
|
|
// the filename to ThumbnailItem map
|
|
this.thumbnailMap = {};
|
|
// the filename to group
|
|
this.groupMap = {};
|
|
// the group list
|
|
this.itemGroups = [];
|
|
this.count = 0;
|
|
this.groupClass = groupClass;
|
|
this.container = container;
|
|
|
|
// If the locale changes while we're running, then we need to
|
|
// re-localize the dates in the group headers
|
|
document.addEventListener('DOMRetranslated', this.localize.bind(this));
|
|
}
|
|
|
|
ThumbnailList.prototype.addItem = function(item) {
|
|
if (!item) {
|
|
return null;
|
|
}
|
|
|
|
if (this.thumbnailMap[item.name]) {
|
|
return this.thumbnailMap[item.name];
|
|
}
|
|
|
|
var _this = this;
|
|
|
|
function createItemGroup(item, before) {
|
|
var group = new _this.groupClass(item);
|
|
|
|
_this.container.insertBefore(group.htmlNode,
|
|
before ? before.htmlNode : null);
|
|
return group;
|
|
}
|
|
|
|
function getItemGroup(item) {
|
|
var groupID = _this.groupClass.getGroupID(item);
|
|
var i;
|
|
for (i = 0; i < _this.itemGroups.length; i++) {
|
|
if (_this.itemGroups[i].groupID === groupID) {
|
|
return _this.itemGroups[i];
|
|
} else if (_this.groupClass.compareGroupID(_this.itemGroups[i].groupID,
|
|
groupID) < 0) {
|
|
// existing ID is less than current groupID, stop searching/
|
|
break;
|
|
}
|
|
}
|
|
var createdGroup = createItemGroup(item, _this.itemGroups[i]);
|
|
_this.itemGroups.splice(i, 0, createdGroup);
|
|
return createdGroup;
|
|
}
|
|
|
|
var group = getItemGroup(item);
|
|
var thumbnail = group.addItem(item);
|
|
this.groupMap[item.name] = group;
|
|
this.thumbnailMap[item.name] = thumbnail;
|
|
this.count++;
|
|
return thumbnail;
|
|
};
|
|
|
|
ThumbnailList.prototype.removeItem = function(filename) {
|
|
if (!this.thumbnailMap[filename]) {
|
|
return;
|
|
}
|
|
|
|
var group = this.groupMap[filename];
|
|
group.removeItem(this.thumbnailMap[filename]);
|
|
if (!group.getCount()) {
|
|
this.container.removeChild(group.htmlNode);
|
|
this.itemGroups.splice(this.itemGroups.indexOf(group), 1);
|
|
}
|
|
this.count--;
|
|
delete this.groupMap[filename];
|
|
delete this.thumbnailMap[filename];
|
|
};
|
|
|
|
ThumbnailList.prototype.reset = function() {
|
|
for (var name in this.thumbnailMap) {
|
|
this.groupMap[name].removeItem(this.thumbnailMap[name]);
|
|
}
|
|
this.container.innerHTML = '';
|
|
this.thumbnailMap = {};
|
|
this.itemGroups = [];
|
|
this.groupMap = {};
|
|
this.count = 0;
|
|
};
|
|
|
|
ThumbnailList.prototype.isPickMode = function() {
|
|
return this.container.classList.contains('pick');
|
|
};
|
|
|
|
ThumbnailList.prototype.setPickMode = function(pick) {
|
|
if (pick) {
|
|
this.container.classList.add('pick');
|
|
} else {
|
|
this.container.classList.remove('pick');
|
|
}
|
|
};
|
|
|
|
ThumbnailList.prototype.isSelectMode = function() {
|
|
return this.container.classList.contains('select');
|
|
};
|
|
|
|
ThumbnailList.prototype.setSelectMode = function(select) {
|
|
if (select) {
|
|
this.container.classList.add('select');
|
|
} else {
|
|
this.container.classList.remove('select');
|
|
}
|
|
};
|
|
|
|
// Asynchronously call updateTitleText on all of the thumbnails
|
|
ThumbnailList.prototype.updateAllThumbnailTitles = function() {
|
|
var self = this;
|
|
|
|
// Cancel any pending title text update. Note that we cancel not by
|
|
// deleting the array but by setting its length to zero. Previous
|
|
// invocations will see this new length in their closure and will
|
|
// terminate. This handles the case where the user is switching
|
|
// orientations back and forth. We need to be sure that we don't
|
|
// keep adding work in this case.
|
|
if (this.pendingTitleTextUpdate && this.pendingTitleTextUpdate.length) {
|
|
this.pendingTitleTextUpdate.length = 0;
|
|
}
|
|
|
|
// Get a list of thumbnails that need to be updated
|
|
var thumbnailKeys = Object.keys(this.thumbnailMap);
|
|
|
|
// Remember the list in case we need to cancel it.
|
|
this.pendingTitleTextUpdate = thumbnailKeys;
|
|
|
|
setTimeout(updateNextThumbnailBatch);
|
|
|
|
function updateNextThumbnailBatch() {
|
|
// If we just do one at a time, it takes quite a bit longer
|
|
for (var i = 0; i < 10; i++) {
|
|
// If there aren't any thumbnails left, we're done
|
|
if (thumbnailKeys.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Otherwise, update the next thumbnail
|
|
var nextKey = thumbnailKeys.shift();
|
|
self.thumbnailMap[nextKey].updateTitleText();
|
|
}
|
|
// And schedule another call ASAP
|
|
setTimeout(updateNextThumbnailBatch);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* To find the next thumbnail item after the filename. There are multiple cases:
|
|
* 1. filename is not existed: returns the first thumbnail.
|
|
* 2. filename is at last position, returns the previous thumbnail of filename.
|
|
* 3. filename is at other position, returns the next thumbnail of filename.
|
|
*
|
|
* @param {string} filename the file name of video
|
|
*/
|
|
ThumbnailList.prototype.findNextThumbnail = function(filename) {
|
|
var currentGroup = this.groupMap[filename];
|
|
if (!currentGroup) {
|
|
// the file is not in this list, return the first one.
|
|
return this.itemGroups.length ? this.itemGroups[0].thumbnails[0] : null;
|
|
}
|
|
|
|
var currentThumbnail = this.thumbnailMap[filename];
|
|
|
|
var idx;
|
|
if (currentGroup.thumbnails.length === 1) {
|
|
// only the deleted one, back to previous/next group;
|
|
idx = this.itemGroups.indexOf(currentGroup);
|
|
if (idx === 0) {
|
|
// the delete group is the first one, set target video to first item of
|
|
// next group.
|
|
if (this.itemGroups.length > 1) {
|
|
currentThumbnail = this.itemGroups[1].thumbnails[0];
|
|
} else {
|
|
// no more videos
|
|
currentThumbnail = null;
|
|
}
|
|
} else {
|
|
// focus previous video
|
|
var thumbnails = this.itemGroups[idx - 1].thumbnails;
|
|
currentThumbnail = thumbnails[thumbnails.length - 1];
|
|
}
|
|
} else {
|
|
idx = currentGroup.thumbnails.indexOf(currentThumbnail);
|
|
if (idx === 0) {
|
|
currentThumbnail = currentGroup.thumbnails[1];
|
|
} else {
|
|
currentThumbnail = currentGroup.thumbnails[idx - 1];
|
|
}
|
|
}
|
|
return currentThumbnail;
|
|
};
|
|
|
|
ThumbnailList.prototype.localize = function() {
|
|
this.itemGroups.forEach(function(group) { group.localize(); });
|
|
};
|