import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { FontInfo, FontTreeNode } from '../types/font' import { loadFontWithProgress } from '../utils/font-loader' export const useFontStore = defineStore('font', () => { function readSet(key: string): Set { try { const raw = localStorage.getItem(key) if (!raw) return new Set() const parsed = JSON.parse(raw) return Array.isArray(parsed) ? new Set(parsed.map(String)) : new Set() } catch { return new Set() } } function writeSet(key: string, value: Set) { try { localStorage.setItem(key, JSON.stringify(Array.from(value))) } catch { // ignore storage errors } } function filterSetByValidIds(source: Set, validIds: Set): Set { return new Set(Array.from(source).filter(id => validIds.has(id))) } // 状态 const fonts = ref([]) const selectedFontIds = ref>(new Set()) const favoriteFontIds = ref>(readSet('font.favoriteFontIds')) const previewFontIds = ref>(readSet('font.previewFontIds')) const expandedCategoryNames = ref>(readSet('font.expandedCategories')) const fontTree = ref([]) const isLoadingFonts = ref(false) // 计算属性 const selectedFonts = computed(() => { return fonts.value.filter(f => selectedFontIds.value.has(f.id)) }) const favoriteFonts = computed(() => { return fonts.value.filter(f => favoriteFontIds.value.has(f.id)) }) const previewFonts = computed(() => { return fonts.value.filter(f => previewFontIds.value.has(f.id)) }) const favoriteTree = computed(() => { return buildFontTree(favoriteFonts.value) }) // 方法 function addFont(fontInfo: FontInfo) { fonts.value.push(fontInfo) } function replaceFonts(nextFonts: FontInfo[]) { const validIds = new Set(nextFonts.map(font => font.id)) selectedFontIds.value = filterSetByValidIds(selectedFontIds.value, validIds) favoriteFontIds.value = filterSetByValidIds(favoriteFontIds.value, validIds) previewFontIds.value = filterSetByValidIds(previewFontIds.value, validIds) writeSet('font.favoriteFontIds', favoriteFontIds.value) writeSet('font.previewFontIds', previewFontIds.value) fonts.value = nextFonts.map(font => ({ ...font, isFavorite: favoriteFontIds.value.has(font.id), })) updateFontTree() } function removeFont(fontId: string) { const index = fonts.value.findIndex(f => f.id === fontId) if (index !== -1) { fonts.value.splice(index, 1) } selectedFontIds.value.delete(fontId) favoriteFontIds.value.delete(fontId) writeSet('font.favoriteFontIds', favoriteFontIds.value) previewFontIds.value.delete(fontId) writeSet('font.previewFontIds', previewFontIds.value) } function selectFont(fontId: string) { selectedFontIds.value.add(fontId) } function unselectFont(fontId: string) { selectedFontIds.value.delete(fontId) } function toggleSelectFont(fontId: string) { if (selectedFontIds.value.has(fontId)) { unselectFont(fontId) } else { selectFont(fontId) } } function clearSelection() { selectedFontIds.value.clear() } function favoriteFont(fontId: string) { const font = fonts.value.find(f => f.id === fontId) if (font) { font.isFavorite = true favoriteFontIds.value.add(fontId) writeSet('font.favoriteFontIds', favoriteFontIds.value) } } function unfavoriteFont(fontId: string) { const font = fonts.value.find(f => f.id === fontId) if (font) { font.isFavorite = false favoriteFontIds.value.delete(fontId) writeSet('font.favoriteFontIds', favoriteFontIds.value) } } function toggleFavorite(fontId: string) { if (favoriteFontIds.value.has(fontId)) { unfavoriteFont(fontId) } else { favoriteFont(fontId) } } function addToPreview(fontId: string) { previewFontIds.value.add(fontId) writeSet('font.previewFontIds', previewFontIds.value) } function removeFromPreview(fontId: string) { previewFontIds.value.delete(fontId) writeSet('font.previewFontIds', previewFontIds.value) } function togglePreview(fontId: string) { if (previewFontIds.value.has(fontId)) { removeFromPreview(fontId) } else { addToPreview(fontId) } } function clearPreview() { previewFontIds.value.clear() writeSet('font.previewFontIds', previewFontIds.value) } function setCategoryExpanded(categoryName: string, expanded: boolean) { const next = new Set(expandedCategoryNames.value) if (expanded) { next.add(categoryName) } else { next.delete(categoryName) } expandedCategoryNames.value = next writeSet('font.expandedCategories', expandedCategoryNames.value) } async function loadFont(fontInfo: FontInfo) { if (fontInfo.loaded || !fontInfo.path) { return } try { const font = await loadFontWithProgress(fontInfo.path, (progress) => { fontInfo.progress = progress }) fontInfo.font = font fontInfo.loaded = true fontInfo.progress = 100 } catch (error) { console.error(`Failed to load font ${fontInfo.name}:`, error) throw error } } function buildFontTree(fontList: FontInfo[]): FontTreeNode[] { const tree: FontTreeNode[] = [] const categoryMap = new Map() for (const font of fontList) { let categoryNode = categoryMap.get(font.category) if (!categoryNode) { categoryNode = { name: font.category, type: 'category', children: [], expanded: expandedCategoryNames.value.has(font.category), selected: false, } categoryMap.set(font.category, categoryNode) tree.push(categoryNode) } const fontNode: FontTreeNode = { name: font.name, type: 'font', fontInfo: font, expanded: false, selected: selectedFontIds.value.has(font.id), } categoryNode.children!.push(fontNode) } // 按类别名称排序 tree.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN')) // 每个类别内的字体按名称排序 for (const category of tree) { if (category.children) { category.children.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN')) } } return tree } function updateFontTree() { fontTree.value = buildFontTree(fonts.value) } return { // 状态 fonts, selectedFontIds, favoriteFontIds, previewFontIds, expandedCategoryNames, fontTree, isLoadingFonts, // 计算属性 selectedFonts, favoriteFonts, previewFonts, favoriteTree, // 方法 addFont, replaceFonts, removeFont, selectFont, unselectFont, toggleSelectFont, clearSelection, favoriteFont, unfavoriteFont, toggleFavorite, addToPreview, removeFromPreview, togglePreview, clearPreview, setCategoryExpanded, loadFont, updateFontTree, } })