VulpesOS/gaia/apps/video/js/thumbnail_list.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

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(); });
};