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.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,235 @@
/**
* 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(); });
};