mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-29 19:00:16 +08:00
289 lines
7.6 KiB
TypeScript
289 lines
7.6 KiB
TypeScript
import { computed, ref, type ComputedRef, type Ref } from "vue";
|
|
|
|
import type {
|
|
UpdateCenterItem,
|
|
UpdateCenterSnapshot,
|
|
DownloadItem,
|
|
UpdateCenterStartTask,
|
|
} from "@/global/typedefinition";
|
|
import { downloads, getNextUpdateDownloadId } from "@/global/downloadStatus";
|
|
import { APM_STORE_BASE_URL } from "@/global/storeConfig";
|
|
|
|
const EMPTY_SNAPSHOT: UpdateCenterSnapshot = {
|
|
items: [],
|
|
tasks: [],
|
|
warnings: [],
|
|
hasRunningTasks: false,
|
|
};
|
|
|
|
export interface UpdateCenterStore {
|
|
isOpen: Ref<boolean>;
|
|
showCloseConfirm: Ref<boolean>;
|
|
showMigrationConfirm: Ref<boolean>;
|
|
searchQuery: Ref<string>;
|
|
selectedTaskKeys: Ref<Set<string>>;
|
|
snapshot: Ref<UpdateCenterSnapshot>;
|
|
filteredItems: ComputedRef<UpdateCenterItem[]>;
|
|
allSelected: ComputedRef<boolean>;
|
|
someSelected: ComputedRef<boolean>;
|
|
bind: () => void;
|
|
unbind: () => void;
|
|
open: () => Promise<void>;
|
|
refresh: () => Promise<void>;
|
|
ignoreItem: (packageName: string, newVersion: string) => Promise<void>;
|
|
unignoreItem: (packageName: string, newVersion: string) => Promise<void>;
|
|
toggleSelection: (taskKey: string) => void;
|
|
toggleSelectAll: () => void;
|
|
getSelectedItems: () => UpdateCenterItem[];
|
|
closeNow: () => void;
|
|
startSelected: () => Promise<void>;
|
|
requestClose: () => void;
|
|
}
|
|
|
|
const matchesSearch = (item: UpdateCenterItem, query: string): boolean => {
|
|
if (query.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
const normalizedQuery = query.toLowerCase();
|
|
return [item.displayName, item.packageName, item.taskKey].some((value) =>
|
|
value.toLowerCase().includes(normalizedQuery),
|
|
);
|
|
};
|
|
|
|
export const createUpdateCenterStore = (): UpdateCenterStore => {
|
|
const isOpen = ref(false);
|
|
const showCloseConfirm = ref(false);
|
|
const showMigrationConfirm = ref(false);
|
|
const searchQuery = ref("");
|
|
const selectedTaskKeys = ref(new Set<string>());
|
|
const snapshot = ref<UpdateCenterSnapshot>(EMPTY_SNAPSHOT);
|
|
|
|
const resetSessionState = (): void => {
|
|
showCloseConfirm.value = false;
|
|
showMigrationConfirm.value = false;
|
|
searchQuery.value = "";
|
|
selectedTaskKeys.value = new Set();
|
|
};
|
|
|
|
const applySnapshot = (nextSnapshot: UpdateCenterSnapshot): void => {
|
|
const selectableTaskKeys = new Set(
|
|
nextSnapshot.items
|
|
.filter((item) => item.ignored !== true)
|
|
.map((item) => item.taskKey),
|
|
);
|
|
selectedTaskKeys.value = new Set(
|
|
[...selectedTaskKeys.value].filter((taskKey) =>
|
|
selectableTaskKeys.has(taskKey),
|
|
),
|
|
);
|
|
snapshot.value = nextSnapshot;
|
|
};
|
|
|
|
const selectableItems = computed(() =>
|
|
snapshot.value.items.filter((item) => item.ignored !== true),
|
|
);
|
|
|
|
const filteredItems = computed(() => {
|
|
const query = searchQuery.value.trim();
|
|
return snapshot.value.items.filter((item) => matchesSearch(item, query));
|
|
});
|
|
|
|
const allSelected = computed(() => {
|
|
const selectable = selectableItems.value;
|
|
return (
|
|
selectable.length > 0 &&
|
|
selectable.every((item) => selectedTaskKeys.value.has(item.taskKey))
|
|
);
|
|
});
|
|
|
|
const someSelected = computed(() => {
|
|
const selectable = selectableItems.value;
|
|
return (
|
|
selectable.length > 0 &&
|
|
selectable.some((item) => selectedTaskKeys.value.has(item.taskKey))
|
|
);
|
|
});
|
|
|
|
const handleState = (nextSnapshot: UpdateCenterSnapshot): void => {
|
|
applySnapshot(nextSnapshot);
|
|
};
|
|
|
|
let isBound = false;
|
|
|
|
const bind = (): void => {
|
|
if (isBound) {
|
|
return;
|
|
}
|
|
|
|
window.updateCenter.onState(handleState);
|
|
isBound = true;
|
|
};
|
|
|
|
const unbind = (): void => {
|
|
if (!isBound) {
|
|
return;
|
|
}
|
|
|
|
window.updateCenter.offState(handleState);
|
|
isBound = false;
|
|
};
|
|
|
|
const open = async (): Promise<void> => {
|
|
resetSessionState();
|
|
const nextSnapshot = await window.updateCenter.open();
|
|
applySnapshot(nextSnapshot);
|
|
isOpen.value = true;
|
|
};
|
|
|
|
const refresh = async (): Promise<void> => {
|
|
const nextSnapshot = await window.updateCenter.refresh();
|
|
applySnapshot(nextSnapshot);
|
|
};
|
|
|
|
const ignoreItem = async (
|
|
packageName: string,
|
|
newVersion: string,
|
|
): Promise<void> => {
|
|
await window.updateCenter.ignore({ packageName, newVersion });
|
|
};
|
|
|
|
const unignoreItem = async (
|
|
packageName: string,
|
|
newVersion: string,
|
|
): Promise<void> => {
|
|
await window.updateCenter.unignore({ packageName, newVersion });
|
|
};
|
|
|
|
const toggleSelection = (taskKey: string): void => {
|
|
const item = snapshot.value.items.find(
|
|
(entry) => entry.taskKey === taskKey,
|
|
);
|
|
if (!item || item.ignored === true) {
|
|
return;
|
|
}
|
|
|
|
const nextSelection = new Set(selectedTaskKeys.value);
|
|
if (nextSelection.has(taskKey)) {
|
|
nextSelection.delete(taskKey);
|
|
} else {
|
|
nextSelection.add(taskKey);
|
|
}
|
|
|
|
selectedTaskKeys.value = nextSelection;
|
|
};
|
|
|
|
const toggleSelectAll = (): void => {
|
|
const selectable = selectableItems.value;
|
|
if (allSelected.value) {
|
|
selectedTaskKeys.value = new Set();
|
|
} else {
|
|
selectedTaskKeys.value = new Set(selectable.map((item) => item.taskKey));
|
|
}
|
|
};
|
|
|
|
const getSelectedItems = (): UpdateCenterItem[] => {
|
|
return snapshot.value.items.filter(
|
|
(item) =>
|
|
selectedTaskKeys.value.has(item.taskKey) && item.ignored !== true,
|
|
);
|
|
};
|
|
|
|
const closeNow = (): void => {
|
|
resetSessionState();
|
|
isOpen.value = false;
|
|
};
|
|
|
|
const startSelected = async (): Promise<void> => {
|
|
const selectedItems = getSelectedItems();
|
|
if (selectedItems.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// 在前端创建下载项,这样用户能在下载列表中看到更新任务
|
|
const arch = window.apm_store.arch || "amd64";
|
|
const startTasks: UpdateCenterStartTask[] = [];
|
|
|
|
selectedItems.forEach((item) => {
|
|
// 检查任务是否已存在
|
|
if (
|
|
!downloads.value.find(
|
|
(d) =>
|
|
d.pkgname === item.packageName &&
|
|
d.origin === (item.source === "apm" ? "apm" : "spark"),
|
|
)
|
|
) {
|
|
const finalArch =
|
|
item.source === "apm" ? `${arch}-apm` : `${arch}-store`;
|
|
const icon =
|
|
item.remoteIcon ||
|
|
`${APM_STORE_BASE_URL}/${finalArch}/unknown/${item.packageName}/icon.png`;
|
|
const downloadId = getNextUpdateDownloadId();
|
|
const download: DownloadItem = {
|
|
id: downloadId,
|
|
name: item.displayName,
|
|
pkgname: item.packageName,
|
|
version: item.newVersion,
|
|
icon,
|
|
origin: item.source === "apm" ? "apm" : "spark",
|
|
status: "queued",
|
|
progress: 0,
|
|
downloadedSize: 0,
|
|
totalSize: item.size || 0,
|
|
speed: 0,
|
|
timeRemaining: 0,
|
|
startTime: Date.now(),
|
|
logs: [{ time: Date.now(), message: "开始更新..." }],
|
|
source: "Update Center",
|
|
retry: false,
|
|
upgradeOnly: true,
|
|
filename: item.fileName,
|
|
metalinkUrl: item.downloadUrl
|
|
? `${item.downloadUrl}.metalink`
|
|
: undefined,
|
|
};
|
|
downloads.value.push(download);
|
|
startTasks.push({
|
|
taskKey: item.taskKey,
|
|
id: downloadId,
|
|
});
|
|
}
|
|
});
|
|
|
|
if (startTasks.length === 0) {
|
|
return;
|
|
}
|
|
|
|
await window.updateCenter.start(startTasks);
|
|
};
|
|
|
|
const requestClose = (): void => {
|
|
// 直接关闭,不需要确认,因为任务在主下载队列中执行
|
|
closeNow();
|
|
};
|
|
|
|
return {
|
|
isOpen,
|
|
showCloseConfirm,
|
|
showMigrationConfirm,
|
|
searchQuery,
|
|
selectedTaskKeys,
|
|
snapshot,
|
|
filteredItems,
|
|
allSelected,
|
|
someSelected,
|
|
bind,
|
|
unbind,
|
|
open,
|
|
refresh,
|
|
ignoreItem,
|
|
unignoreItem,
|
|
toggleSelection,
|
|
toggleSelectAll,
|
|
getSelectedItems,
|
|
closeNow,
|
|
startSelected,
|
|
requestClose,
|
|
};
|
|
};
|