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; showCloseConfirm: Ref; showMigrationConfirm: Ref; searchQuery: Ref; selectedTaskKeys: Ref>; snapshot: Ref; filteredItems: ComputedRef; allSelected: ComputedRef; someSelected: ComputedRef; bind: () => void; unbind: () => void; open: () => Promise; refresh: () => Promise; ignoreItem: (packageName: string, newVersion: string) => Promise; unignoreItem: (packageName: string, newVersion: string) => Promise; toggleSelection: (taskKey: string) => void; toggleSelectAll: () => void; getSelectedItems: () => UpdateCenterItem[]; closeNow: () => void; startSelected: () => Promise; 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()); const snapshot = ref(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 => { resetSessionState(); const nextSnapshot = await window.updateCenter.open(); applySnapshot(nextSnapshot); isOpen.value = true; }; const refresh = async (): Promise => { const nextSnapshot = await window.updateCenter.refresh(); applySnapshot(nextSnapshot); }; const ignoreItem = async ( packageName: string, newVersion: string, ): Promise => { await window.updateCenter.ignore({ packageName, newVersion }); }; const unignoreItem = async ( packageName: string, newVersion: string, ): Promise => { 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 => { 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, }; };