From 5e9af1b89ef8ebbf917ad1c10723a48960cb250b Mon Sep 17 00:00:00 2001 From: kuekhaoyang Date: Sun, 23 Nov 2025 11:41:48 +0800 Subject: [PATCH] feat: centralize homepage logic into a new hook and refactor settings management with subscription and helper functions. --- app/page.tsx | 72 ++----------------- components/player/PlayerNavbar.tsx | 12 +++- components/settings/SortSettings.tsx | 3 +- lib/hooks/useHomePage.ts | 101 +++++++++++++++++++++++++++ lib/store/settings-helpers.ts | 56 +++++++++++++++ lib/store/settings-store.ts | 66 +++++------------ 6 files changed, 196 insertions(+), 114 deletions(-) create mode 100644 lib/hooks/useHomePage.ts create mode 100644 lib/store/settings-helpers.ts diff --git a/app/page.tsx b/app/page.tsx index eba8c0f..27d40ff 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,84 +1,26 @@ 'use client'; -import { useState, useRef, useEffect, Suspense } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { Suspense } from 'react'; import { SearchForm } from '@/components/search/SearchForm'; import { NoResults } from '@/components/search/NoResults'; import { PopularFeatures } from '@/components/home/PopularFeatures'; import { WatchHistorySidebar } from '@/components/history/WatchHistorySidebar'; -import { useSearchCache } from '@/lib/hooks/useSearchCache'; -import { useParallelSearch } from '@/lib/hooks/useParallelSearch'; -import { settingsStore } from '@/lib/store/settings-store'; import { Navbar } from '@/components/layout/Navbar'; import { SearchResults } from '@/components/home/SearchResults'; +import { useHomePage } from '@/lib/hooks/useHomePage'; function HomePage() { - const router = useRouter(); - const searchParams = useSearchParams(); - const { loadFromCache, saveToCache } = useSearchCache(); - const hasLoadedCache = useRef(false); - - const [query, setQuery] = useState(''); - const [hasSearched, setHasSearched] = useState(false); - const [currentSortBy, setCurrentSortBy] = useState('default'); - - // Search stream hook const { + query, + hasSearched, loading, results, availableSources, completedSources, totalSources, - performSearch, - resetSearch, - loadCachedResults, - } = useParallelSearch( - saveToCache, - (q: string) => router.replace(`/?q=${encodeURIComponent(q)}`, { scroll: false }) - ); - - // Load sort preference on mount - useEffect(() => { - const settings = settingsStore.getSettings(); - setCurrentSortBy(settings.sortBy); - }, []); - - // Load cached results on mount - useEffect(() => { - if (hasLoadedCache.current) return; - hasLoadedCache.current = true; - - const urlQuery = searchParams.get('q'); - const cached = loadFromCache(); - - if (urlQuery) { - setQuery(urlQuery); - if (cached && cached.query === urlQuery && cached.results.length > 0) { - - setHasSearched(true); - loadCachedResults(cached.results, cached.availableSources); - } else { - - setTimeout(() => handleSearch(urlQuery), 100); - } - } - }, [searchParams, loadFromCache, loadCachedResults]); - - const handleSearch = (searchQuery: string) => { - setQuery(searchQuery); - setHasSearched(true); - const settings = settingsStore.getSettings(); - // Filter enabled sources - const enabledSources = settings.sources.filter(s => s.enabled); - performSearch(searchQuery, enabledSources, currentSortBy as any); - }; - - const handleReset = () => { - setHasSearched(false); - setQuery(''); - resetSearch(); - router.replace('/', { scroll: false }); - }; + handleSearch, + handleReset, + } = useHomePage(); return (
diff --git a/components/player/PlayerNavbar.tsx b/components/player/PlayerNavbar.tsx index b168f97..3611590 100644 --- a/components/player/PlayerNavbar.tsx +++ b/components/player/PlayerNavbar.tsx @@ -1,4 +1,5 @@ import { useRouter } from 'next/navigation'; +import Link from 'next/link'; import Image from 'next/image'; import { Button } from '@/components/ui/Button'; import { ThemeSwitcher } from '@/components/ThemeSwitcher'; @@ -34,7 +35,16 @@ export function PlayerNavbar() { 返回
-
+
+ + + + +
diff --git a/components/settings/SortSettings.tsx b/components/settings/SortSettings.tsx index e20cdc2..5467332 100644 --- a/components/settings/SortSettings.tsx +++ b/components/settings/SortSettings.tsx @@ -1,4 +1,5 @@ -import { sortOptions, type SortOption } from '@/lib/store/settings-store'; +import { type SortOption } from '@/lib/store/settings-store'; +import { sortOptions } from '@/lib/store/settings-helpers'; interface SortSettingsProps { sortBy: SortOption; diff --git a/lib/hooks/useHomePage.ts b/lib/hooks/useHomePage.ts new file mode 100644 index 0000000..95f0a5e --- /dev/null +++ b/lib/hooks/useHomePage.ts @@ -0,0 +1,101 @@ +import { useState, useRef, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useSearchCache } from '@/lib/hooks/useSearchCache'; +import { useParallelSearch } from '@/lib/hooks/useParallelSearch'; +import { settingsStore } from '@/lib/store/settings-store'; + +export function useHomePage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { loadFromCache, saveToCache } = useSearchCache(); + const hasLoadedCache = useRef(false); + + const [query, setQuery] = useState(''); + const [hasSearched, setHasSearched] = useState(false); + const [currentSortBy, setCurrentSortBy] = useState('default'); + + // Search stream hook + const { + loading, + results, + availableSources, + completedSources, + totalSources, + performSearch, + resetSearch, + loadCachedResults, + applySorting, + } = useParallelSearch( + saveToCache, + (q: string) => router.replace(`/?q=${encodeURIComponent(q)}`, { scroll: false }) + ); + + // Re-sort results when sort preference changes + useEffect(() => { + if (hasSearched && results.length > 0) { + applySorting(currentSortBy as any); + } + }, [currentSortBy, applySorting, hasSearched, results.length]); + + // Load sort preference on mount and subscribe to changes + useEffect(() => { + const updateSettings = () => { + const settings = settingsStore.getSettings(); + setCurrentSortBy(settings.sortBy); + }; + + // Initial load + updateSettings(); + + // Subscribe to changes + const unsubscribe = settingsStore.subscribe(updateSettings); + return () => unsubscribe(); + }, []); + + // Load cached results on mount + useEffect(() => { + if (hasLoadedCache.current) return; + hasLoadedCache.current = true; + + const urlQuery = searchParams.get('q'); + const cached = loadFromCache(); + + if (urlQuery) { + setQuery(urlQuery); + if (cached && cached.query === urlQuery && cached.results.length > 0) { + setHasSearched(true); + loadCachedResults(cached.results, cached.availableSources); + } else { + setTimeout(() => handleSearch(urlQuery), 100); + } + } + }, [searchParams, loadFromCache, loadCachedResults]); + + const handleSearch = (searchQuery: string) => { + setQuery(searchQuery); + setHasSearched(true); + const settings = settingsStore.getSettings(); + // Filter enabled sources + const enabledSources = settings.sources.filter(s => s.enabled); + performSearch(searchQuery, enabledSources, currentSortBy as any); + }; + + const handleReset = () => { + setHasSearched(false); + setQuery(''); + resetSearch(); + router.replace('/', { scroll: false }); + }; + + return { + query, + hasSearched, + loading, + results, + availableSources, + completedSources, + totalSources, + handleSearch, + handleReset, + }; +} diff --git a/lib/store/settings-helpers.ts b/lib/store/settings-helpers.ts new file mode 100644 index 0000000..2f8663d --- /dev/null +++ b/lib/store/settings-helpers.ts @@ -0,0 +1,56 @@ +import type { AppSettings } from './settings-store'; + +export const SEARCH_HISTORY_KEY = 'kvideo-search-history'; +export const WATCH_HISTORY_KEY = 'kvideo-watch-history'; + +export const sortOptions = { + 'default': '默认排序', + 'relevance': '按相关性', + 'latency-asc': '延迟低到高', + 'date-desc': '发布时间(新到旧)', + 'date-asc': '发布时间(旧到新)', + 'rating-desc': '按评分(高到低)', + 'name-asc': '按名称(A-Z)', + 'name-desc': '按名称(Z-A)', +} as const; + +export function exportSettings(settings: AppSettings, includeHistory: boolean = true): string { + const exportData: Record = { + settings, + }; + + if (includeHistory && typeof window !== 'undefined') { + const searchHistory = localStorage.getItem(SEARCH_HISTORY_KEY); + const watchHistory = localStorage.getItem(WATCH_HISTORY_KEY); + + if (searchHistory) exportData.searchHistory = JSON.parse(searchHistory); + if (watchHistory) exportData.watchHistory = JSON.parse(watchHistory); + } + + return JSON.stringify(exportData, null, 2); +} + +export function importSettings( + jsonString: string, + saveSettings: (settings: AppSettings) => void +): boolean { + try { + const data = JSON.parse(jsonString); + + if (data.settings) { + saveSettings(data.settings); + } + + if (data.searchHistory && typeof window !== 'undefined') { + localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(data.searchHistory)); + } + + if (data.watchHistory && typeof window !== 'undefined') { + localStorage.setItem(WATCH_HISTORY_KEY, JSON.stringify(data.watchHistory)); + } + + return true; + } catch { + return false; + } +} diff --git a/lib/store/settings-store.ts b/lib/store/settings-store.ts index 89e00d8..a37ec4c 100644 --- a/lib/store/settings-store.ts +++ b/lib/store/settings-store.ts @@ -15,27 +15,16 @@ export type SortOption = | 'name-asc' | 'name-desc'; -interface AppSettings { +export interface AppSettings { sources: VideoSource[]; sortBy: SortOption; searchHistory: boolean; watchHistory: boolean; } -const SETTINGS_KEY = 'kvideo-settings'; -const SEARCH_HISTORY_KEY = 'kvideo-search-history'; -const WATCH_HISTORY_KEY = 'kvideo-watch-history'; +import { exportSettings, importSettings, SEARCH_HISTORY_KEY, WATCH_HISTORY_KEY } from './settings-helpers'; -export const sortOptions: Record = { - 'default': '默认排序', - 'relevance': '按相关性', - 'latency-asc': '延迟低到高', - 'date-desc': '发布时间(新到旧)', - 'date-asc': '发布时间(旧到新)', - 'rating-desc': '按评分(高到低)', - 'name-asc': '按名称(A-Z)', - 'name-desc': '按名称(Z-A)', -}; +const SETTINGS_KEY = 'kvideo-settings'; export const getDefaultSources = (): VideoSource[] => DEFAULT_SOURCES; @@ -79,49 +68,32 @@ export const settingsStore = { } }, + listeners: new Set<() => void>(), + + subscribe(listener: () => void): () => void { + this.listeners.add(listener); + return () => { + this.listeners.delete(listener); + }; + }, + + notifyListeners(): void { + this.listeners.forEach((listener) => listener()); + }, + saveSettings(settings: AppSettings): void { if (typeof window !== 'undefined') { localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); + this.notifyListeners(); } }, exportSettings(includeHistory: boolean = true): string { - const settings = this.getSettings(); - const exportData: Record = { - settings, - }; - - if (includeHistory && typeof window !== 'undefined') { - const searchHistory = localStorage.getItem(SEARCH_HISTORY_KEY); - const watchHistory = localStorage.getItem(WATCH_HISTORY_KEY); - - if (searchHistory) exportData.searchHistory = JSON.parse(searchHistory); - if (watchHistory) exportData.watchHistory = JSON.parse(watchHistory); - } - - return JSON.stringify(exportData, null, 2); + return exportSettings(this.getSettings(), includeHistory); }, importSettings(jsonString: string): boolean { - try { - const data = JSON.parse(jsonString); - - if (data.settings) { - this.saveSettings(data.settings); - } - - if (data.searchHistory && typeof window !== 'undefined') { - localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(data.searchHistory)); - } - - if (data.watchHistory && typeof window !== 'undefined') { - localStorage.setItem(WATCH_HISTORY_KEY, JSON.stringify(data.watchHistory)); - } - - return true; - } catch { - return false; - } + return importSettings(jsonString, (s) => this.saveSettings(s)); }, resetToDefaults(): void {