Update manager and add features for App Installer.

This commit is contained in:
Bruce
2026-01-31 22:03:03 +08:00
parent 0c87a2cdcd
commit d91948eaff
37 changed files with 3645 additions and 923 deletions
+357 -27
View File
@@ -13,6 +13,7 @@
var childAnimeDuration = 120;
var parentAnimeDuration = 400;
function showItemAmine(node) {
return Windows.UI.Animation.runAsync(node, [
Windows.UI.Animation.Keyframes.Scale.up,
@@ -27,6 +28,23 @@
], childAnimeDuration);
}
function noAnime(node) {
return Promise.resolve(node);
}
function runShowAnime(node, enable) {
if (enable === void 0) enable = true;
if (!enable) return noAnime(node);
return showItemAmine(node);
}
function runHideAnime(node, enable) {
if (enable === void 0) enable = true;
if (!enable) return noAnime(node);
return hideItemAmine(node);
}
function updateItemAmine(node, updateCallback) {
return Windows.UI.Animation.runAsync(node, [
Windows.UI.Animation.Keyframes.Opacity.hidden,
@@ -53,6 +71,43 @@
function PMDataSource() {
var _list = [];
var _listeners = [];
var _keySelector = null;
var _autoKeySeed = 1;
this.setKeySelector = function(fn) {
_keySelector = (typeof fn === "function") ? fn : null;
};
function getKey(item) {
if (!item) return null;
// 用户提供
if (_keySelector) {
return _keySelector(item);
}
// 自动注入(对象)
if (typeof item === "object") {
if (item.__pm_key !== void 0) {
return item.__pm_key;
}
try {
Object.defineProperty(item, "__pm_key", {
value: "pm_" + (_autoKeySeed++),
enumerable: false
});
} catch (e) {
// IE10 兜底
item.__pm_key = "pm_" + (_autoKeySeed++);
}
return item.__pm_key;
}
// 原始类型兜底
return typeof item + ":" + item;
}
this.subscribe = function(fn) {
if (typeof fn === "function") {
_listeners.push(fn);
@@ -64,26 +119,47 @@
_listeners[i](evt);
}
}
this.indexOf = function(item) {
var key = getKey(item);
for (var i = 0; i < _list.length; i++) {
if (getKey(_list[i]) === key) {
return i;
}
}
return -1;
};
this.add = function(item) {
_list.push(item);
emit(new PMChangeEvent(
DataView.ChangeType.add, [item], { index: _list.length - 1 }
DataView.ChangeType.add, [{ item: item, index: _list.length - 1, key: getKey(item) }]
));
};
this.removeAt = function(index) {
if (index < 0 || index >= _list.length) return;
var item = _list.splice(index, 1)[0];
emit(new PMChangeEvent(
DataView.ChangeType.remove, [item], { index: index }
DataView.ChangeType.remove, [{ item: item, index: index, key: getKey(item) }]
));
};
this.remove = function(item) {
var index = this.indexOf(item);
if (index >= 0) {
this.removeAt(index);
}
};
this.changeAt = function(index, newItem) {
if (index < 0 || index >= _list.length) return;
_list[index] = newItem;
emit(new PMChangeEvent(
DataView.ChangeType.change, [newItem], { index: index }
DataView.ChangeType.change, [{ item: newItem, index: index, key: getKey(newItem) }]
));
};
this.change = function(oldItem, newItem) {
var index = this.indexOf(oldItem);
if (index >= 0) {
this.changeAt(index, newItem);
}
};
this.clear = function() {
_list.length = 0;
emit(new PMChangeEvent(
@@ -224,7 +300,6 @@
var changed = [];
var removed = [];
// 1️⃣ 找 remove
for (i = oldList.length - 1; i >= 0; i--) {
var oldItem = oldList[i];
var oldKey = getKey(oldItem);
@@ -238,7 +313,6 @@
}
}
// 2️⃣ 找 add / change
for (i = 0; i < newList.length; i++) {
var newItem = newList[i];
var newKey = getKey(newItem);
@@ -263,7 +337,6 @@
}
}
// 3️⃣ 执行 remove(从后往前)
if (removed.length > 0) {
for (i = 0; i < removed.length; i++) {
_list.splice(removed[i].index, 1);
@@ -274,7 +347,6 @@
));
}
// 4️⃣ 执行 add / change(重建顺序)
_list = newList.slice(0);
if (added.length > 0) {
@@ -291,25 +363,50 @@
));
}
};
this._getKey = getKey;
}
var MAX_ANIMATE_COUNT = 100;
function PMDataListView(container, templateFn) {
this.container = container;
this.templateFn = templateFn;
this.listViewControl = this;
this._emptyView = null;
// === 新增 ===
this._filter = null;
this._searchHandler = null;
this._searchText = null;
this._searchSuggestProvider = null;
this.onSearchSuggest = null; // function(text, list)
this._isSearching = false;
this.onsearchstart = null;
this.onsearchend = null;
}
PMDataListView.prototype.bind = function(ds) {
var self = this;
var items = ds.get();
this._ds = ds;
self.container.innerHTML = "";
var items = ds.get();
// 动画队列,保证异步操作不会乱序
var queue = Promise.resolve();
function renderItem(data, index) {
var el = self.templateFn(data, index);
var key = ds && ds._getKey ? ds._getKey(data) : null;
el.__pm_item = data;
el.__pm_key = key;
if (key != null) {
el.setAttribute("data-pm-key", key);
}
el.addEventListener("click", function() {
self._toggleSelect(el);
});
@@ -317,10 +414,13 @@
return el;
}
// 初始化渲染
for (var i = 0; i < items.length; i++) {
self.container.appendChild(renderItem(items[i], i));
}
// 初始化 emptyView 状态
self._updateEmptyView();
ds.subscribe(function(evt) {
@@ -337,37 +437,58 @@
var nodes = [];
for (var i = 0; i < datas.length; i++) {
var n = renderItem(datas[i].item, datas[i].index);
n.style.display = "none";
nodes.push(n);
self.container.appendChild(n);
}
// 如果数量>=20,动画并行,否则串行
if (datas.length >= 20) {
var enableAnime = datas.length <= MAX_ANIMATE_COUNT;
if (!enableAnime) {
return Promise.resolve();
}
// 如果数量>=20,动画串行,否则并行
if (datas.length <= 20) {
var promises = [];
for (var j = 0; j < nodes.length; j++) {
promises.push(showItemAmine(nodes[j]));
promises.push((function(node) {
node.style.display = "";
return showItemAmine(node);
})(nodes[j]));
}
return Promise.all(promises);
} else {
// 串行
var p = Promise.resolve();
var group = [];
for (var k = 0; k < nodes.length; k++) {
(function(node) {
p = p.then(function() {
group.push((function(node) {
node.style.display = "";
return showItemAmine(node);
});
})(nodes[k]);
})
(nodes[k]));
if (group.length === 20 || k === nodes.length - 1) {
(function(g) {
p = p.then(function() {
return Promise.join(g);
});
})(group);
group = [];
}
}
(function(g) {
p = p.then(function() {
return Promise.join(g);
});
})(group);
return p;
}
}
case DataView.ChangeType.remove:
{
var node = self.container.children[evt.detail.index];
var info = evt.datas[0];
var node = self._findNodeByKey(info.key);
if (!node) return;
// 隐藏动画完成后再移除
return hideItemAmine(node).then(function() {
self.container.removeChild(node);
});
@@ -375,29 +496,30 @@
case DataView.ChangeType.change:
{
var oldNode = self.container.children[evt.detail.index];
var info = evt.datas[0];
var oldNode = self._findNodeByKey(info.key);
if (!oldNode) return;
// 先淡出旧节点
return hideItemAmine(oldNode).then(function() {
// 替换节点
var newNode = renderItem(evt.datas[0], evt.detail.index);
var newNode = renderItem(info.item);
self.container.replaceChild(newNode, oldNode);
// 再淡入新节点
return showItemAmine(newNode);
});
}
case DataView.ChangeType.clear:
self.container.innerHTML = "";
return Promise.resolve();
case DataView.ChangeType.move:
{
var node = self.container.children[evt.detail.from];
var info = evt.datas[0];
var node = self._findNodeByKey(info.key);
if (!node) return;
var ref = self.container.children[evt.detail.to] || null;
if (node) self.container.insertBefore(node, ref);
self.container.insertBefore(node, ref);
return Promise.resolve();
}
@@ -410,9 +532,22 @@
return Promise.resolve();
}
}
promises.push(self._refreshVisibility());
return Promise.join(promises);
});
});
};
PMDataListView.prototype._findNodeByKey = function(key) {
if (key == null) return null;
var children = this.container.children;
for (var i = 0; i < children.length; i++) {
if (children[i].__pm_key === key) {
return children[i];
}
}
return null;
};
PMDataListView.prototype._toggleSelect = function(ele) {
// 如果选择模式为 none,则不处理
@@ -472,7 +607,202 @@
return Array.prototype.slice.call(this.container.querySelectorAll(".selected"));
}
});
PMDataListView.prototype._updateEmptyView = function() {
if (!this._emptyView) return;
// container 中是否还有 item
var hasItem = this.container.children.length > 0;
if (hasItem) {
if (this._emptyView.parentNode) {
this._emptyView.style.display = "none";
}
} else {
if (!this._emptyView.parentNode) {
this.container.appendChild(this._emptyView);
}
this._emptyView.style.display = "";
}
};
Object.defineProperty(PMDataListView.prototype, "emptyView", {
get: function() {
return this._emptyView;
},
set: function(value) {
// 只接受 HTMLElement 或 null / undefined
if (value !== null && value !== void 0 && !(value instanceof HTMLElement)) {
return;
}
// 移除旧的
if (this._emptyView && this._emptyView.parentNode) {
this._emptyView.parentNode.removeChild(this._emptyView);
}
this._emptyView = value || null;
// 设置后立刻刷新一次
this._updateEmptyView();
}
});
PMDataListView.prototype._isItemVisible = function(item) {
// 1️⃣ filter
if (this._filter) {
if (!this._filter(item)) return false;
}
// 2️⃣ search(自动启用 / 禁用)
var handler = this._searchHandler;
var text = this._searchText;
if (typeof handler === "function") {
if (text != null) {
text = ("" + text).replace(/^\s+|\s+$/g, "");
}
if (text && text.length > 0) {
if (!handler(text, item)) {
return false;
}
}
}
return true;
};
PMDataListView.prototype._refreshVisibility = function() {
var self = this;
var children = self.container.children;
var animes = [];
for (var i = 0; i < children.length; i++) {
(function(node) {
var item = node.__pm_item;
if (!item) return;
var visible = self._isItemVisible(item);
var enableAnime = animes.length < MAX_ANIMATE_COUNT;
if (visible) {
if (node.style.display === "none") {
node.style.display = "";
animes.push(runShowAnime(node, enableAnime));
}
} else {
if (node.style.display !== "none") {
// 移除选择状态
node.classList.remove("selected");
animes.push(runHideAnime(node, enableAnime).then(function() {
node.style.display = "none";
}));
}
}
})(children[i]);
}
return Promise.join(animes);
};
Object.defineProperty(PMDataListView.prototype, "filter", {
get: function() {
return this._filter;
},
set: function(fn) {
this._filter = (typeof fn === "function") ? fn : null;
this._refreshVisibility();
}
});
Object.defineProperty(PMDataListView.prototype, "searchHandler", {
get: function() {
return this._searchHandler;
},
set: function(fn) {
this._searchHandler = (typeof fn === "function") ? fn : null;
this._refreshVisibility();
}
});
Object.defineProperty(PMDataListView.prototype, "searchText", {
get: function() {
return this._searchText;
},
set: function(text) {
var oldText = this._searchText;
this._searchText = text;
var oldActive = !!(oldText && oldText.trim());
var newActive = !!(text && ("" + text).trim());
//if (!oldActive && newActive) {
this._isSearching = true;
this._emitSearchEvent("searchstart");
//}
var handler = this._searchHandler;
var provider = this._searchSuggestProvider;
var cb = this.onSearchSuggest;
var t = text;
if (t != null) {
t = ("" + t).replace(/^\s+|\s+$/g, "");
}
// 搜索建议
if (
typeof handler === "function" &&
t &&
t.length > 0 &&
typeof provider === "function" &&
typeof cb === "function"
) {
var list = provider(t);
if (list && list.length) {
cb(t, list.slice(0, 10));
}
}
var self = this;
var func = function() {
//if (oldActive && !newActive) {
self._isSearching = false;
self._emitSearchEvent("searchend");
//}
};
this._refreshVisibility().done(func, func);
}
});
Object.defineProperty(PMDataListView.prototype, "searchSuggestProvider", {
get: function() {
return this._searchSuggestProvider;
},
set: function(fn) {
this._searchSuggestProvider = (typeof fn === "function") ? fn : null;
}
});
Object.defineProperty(PMDataListView.prototype, "findItemLength", {
get: function() {
var count = 0;
var children = this.container.children;
for (var i = 0; i < children.length; i++) {
var item = children[i].__pm_item;
if (this._isItemVisible(item)) {
count++;
}
}
return count;
}
});
PMDataListView.prototype._emitSearchEvent = function(type) {
if (typeof this["on" + type] === "function") {
try {
this["on" + type].call(this);
} catch (e) {}
}
try {
var ev = document.createEvent("Event");
ev.initEvent(type, true, true);
this.container.dispatchEvent(ev);
} catch (e) {}
};
PMDataListView.prototype.refresh = function() {
this._refreshVisibility();
};
global.DataView.ChangeEvent = PMChangeEvent;
global.DataView.DataSource = PMDataSource;
global.DataView.ListView = PMDataListView;