mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
Update
This commit is contained in:
@@ -52,6 +52,9 @@ export function PasswordGate({ children, hasEnvPassword: initialHasEnvPassword }
|
||||
try {
|
||||
const res = await fetch('/api/config');
|
||||
const data = await res.json();
|
||||
// Note: envPasswordSet is not directly used for immediate locking if we want to rely on real-time settings
|
||||
// But we should respect the server config.
|
||||
// However, for the subscription fix, we mainly care about 'settings.passwordAccess' updating in real-time.
|
||||
const envPasswordSet = data.hasEnvPassword;
|
||||
|
||||
// Updated lock state check
|
||||
@@ -64,6 +67,32 @@ export function PasswordGate({ children, hasEnvPassword: initialHasEnvPassword }
|
||||
}
|
||||
};
|
||||
|
||||
// Subscribe to settings changes (real-time updates)
|
||||
useEffect(() => {
|
||||
// Function to handle updates from the store
|
||||
const handleSettingsUpdate = () => {
|
||||
const settings = settingsStore.getSettings();
|
||||
const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true';
|
||||
|
||||
// We can't easily check env password synchronously here, but we can check local settings.
|
||||
// If local setting says lock, and we are not unlocked, we lock.
|
||||
// If local setting says unlock (and no env password known yet), we unlock.
|
||||
// To be safe, we might just re-run checkLockStatus() but that's async.
|
||||
// For immediate UI feedback on "Enable/Disable Password" toggle in settings:
|
||||
|
||||
// If password access is disabled in settings, and we assume no env password for a moment (or rely on previous state):
|
||||
if (!settings.passwordAccess && !hasEnvPassword) {
|
||||
setIsLocked(false);
|
||||
} else if (settings.passwordAccess && !isUnlocked) {
|
||||
setIsLocked(true);
|
||||
}
|
||||
};
|
||||
|
||||
const unsubscribe = settingsStore.subscribe(handleSettingsUpdate);
|
||||
return () => unsubscribe();
|
||||
}, [hasEnvPassword]);
|
||||
|
||||
|
||||
|
||||
const handleUnlock = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -43,10 +43,20 @@ export function VideoPlayer({
|
||||
|
||||
// Get showModeIndicator setting
|
||||
const [showModeIndicator, setShowModeIndicator] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Initial value
|
||||
setShowModeIndicator(settingsStore.getSettings().showModeIndicator);
|
||||
|
||||
// Subscribe to changes
|
||||
const unsubscribe = settingsStore.subscribe(() => {
|
||||
setShowModeIndicator(settingsStore.getSettings().showModeIndicator);
|
||||
});
|
||||
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
|
||||
// Use reactive hook to subscribe to history updates
|
||||
// This ensures the component re-renders when history is hydrated from localStorage
|
||||
const { viewingHistory, addToHistory } = useHistory(isPremium);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useInfiniteScroll } from '@/lib/hooks/useInfiniteScroll';
|
||||
import { settingsStore } from '@/lib/store/settings-store';
|
||||
|
||||
@@ -19,6 +19,9 @@ export function usePremiumContent(categoryValue: string) {
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
// Track source count to detect meaningful updates
|
||||
const sourceCountRef = useRef(0);
|
||||
|
||||
const loadVideos = useCallback(async (pageNum: number, append = false) => {
|
||||
if (loading) return;
|
||||
|
||||
@@ -34,6 +37,11 @@ export function usePremiumContent(categoryValue: string) {
|
||||
...settings.subscriptions.filter(s => (s as any).group === 'premium')
|
||||
].filter(s => (s as any).enabled !== false);
|
||||
|
||||
if (premiumSources.length === 0) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Should we include normal subscriptions too if categoryValue requests them?
|
||||
// The API handles filtering by categoryValue map.
|
||||
// If categoryValue is empty (Recommend), we use all enabled premium sources.
|
||||
@@ -64,13 +72,44 @@ export function usePremiumContent(categoryValue: string) {
|
||||
}
|
||||
}, [loading, categoryValue]);
|
||||
|
||||
// Initial load and category change
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
setVideos([]);
|
||||
setHasMore(true);
|
||||
|
||||
// Initial check for sources
|
||||
const settings = settingsStore.getSettings();
|
||||
const sourcesCount = settings.premiumSources.length + settings.subscriptions.length;
|
||||
sourceCountRef.current = sourcesCount;
|
||||
|
||||
loadVideos(1, false);
|
||||
}, [categoryValue]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Subscribe to settings changes to handle async source loading
|
||||
useEffect(() => {
|
||||
const handleSettingsUpdate = () => {
|
||||
const settings = settingsStore.getSettings();
|
||||
const premiumSources = [
|
||||
...settings.premiumSources,
|
||||
...settings.subscriptions.filter(s => (s as any).group === 'premium')
|
||||
].filter(s => (s as any).enabled !== false);
|
||||
|
||||
const currentSourceCount = premiumSources.length;
|
||||
|
||||
// If we have 0 videos and suddenly gain sources, we should retry loading
|
||||
// OR if the number of sources significantly changed (e.g. from 0 to N)
|
||||
if (videos.length === 0 && currentSourceCount > 0 && !loading) {
|
||||
// Determine if we should reload.
|
||||
// Mostly needed when initial load failed due to no sources.
|
||||
loadVideos(1, false);
|
||||
}
|
||||
};
|
||||
|
||||
const unsubscribe = settingsStore.subscribe(handleSettingsUpdate);
|
||||
return () => unsubscribe();
|
||||
}, [loadVideos, videos.length, loading]);
|
||||
|
||||
const { prefetchRef, loadMoreRef } = useInfiniteScroll({
|
||||
hasMore,
|
||||
loading,
|
||||
|
||||
@@ -2,23 +2,24 @@ import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useSearchCache } from '@/lib/hooks/useSearchCache';
|
||||
import { useParallelSearch } from '@/lib/hooks/useParallelSearch';
|
||||
import { useSubscriptionSync } from '@/lib/hooks/useSubscriptionSync';
|
||||
import { settingsStore } from '@/lib/store/settings-store';
|
||||
import { VideoSource } from '@/lib/types';
|
||||
|
||||
export function usePremiumHomePage() {
|
||||
useSubscriptionSync();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { loadFromCache, saveToCache } = useSearchCache();
|
||||
const hasLoadedCache = useRef(false);
|
||||
const hasSearchedWithSourcesRef = useRef(false);
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [hasSearched, setHasSearched] = useState(false);
|
||||
const [currentSortBy, setCurrentSortBy] = useState('default');
|
||||
|
||||
// Get premium sources from settings store (supports user customization)
|
||||
const enabledPremiumSources = useMemo(() => {
|
||||
const settings = settingsStore.getSettings();
|
||||
return settings.premiumSources.filter(s => s.enabled);
|
||||
}, []);
|
||||
// Use state for sources to trigger re-renders when they update
|
||||
const [enabledPremiumSources, setEnabledPremiumSources] = useState<VideoSource[]>([]);
|
||||
|
||||
const onUrlUpdate = useCallback((q: string) => {
|
||||
router.replace(`/premium?q=${encodeURIComponent(q)}`, { scroll: false });
|
||||
@@ -40,6 +41,19 @@ export function usePremiumHomePage() {
|
||||
onUrlUpdate
|
||||
);
|
||||
|
||||
// Core search execution function - extracted to eliminate duplication
|
||||
const executeSearch = useCallback((searchQuery: string, sources: VideoSource[]) => {
|
||||
if (!searchQuery.trim()) return false;
|
||||
|
||||
if (sources.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
performSearch(searchQuery, sources, currentSortBy as any);
|
||||
hasSearchedWithSourcesRef.current = true;
|
||||
return true;
|
||||
}, [performSearch, currentSortBy]);
|
||||
|
||||
// Re-sort results when sort preference changes
|
||||
useEffect(() => {
|
||||
if (hasSearched && results.length > 0) {
|
||||
@@ -47,34 +61,71 @@ export function usePremiumHomePage() {
|
||||
}
|
||||
}, [currentSortBy, applySorting, hasSearched, results.length]);
|
||||
|
||||
// Load sources and subscribe to changes
|
||||
useEffect(() => {
|
||||
const updateSettings = () => {
|
||||
const settings = settingsStore.getSettings();
|
||||
|
||||
// Update sort preference if changed
|
||||
// Note: settings.sortBy might be shared or we might want specific one,
|
||||
// but for now we follow the store or keep local state if we want independence.
|
||||
// The original hook had local state 'default'.
|
||||
|
||||
const newPremiumSources = settings.premiumSources.filter(s => s.enabled);
|
||||
setEnabledPremiumSources(newPremiumSources);
|
||||
|
||||
// Check if we need to re-trigger search due to new sources being loaded
|
||||
const hasSources = newPremiumSources.length > 0;
|
||||
|
||||
// If we have a query, and we haven't searched with sources yet,
|
||||
// and we suddenly have sources, trigger the search.
|
||||
if (query && hasSources && !hasSearchedWithSourcesRef.current && !loading) {
|
||||
if (executeSearch(query, newPremiumSources)) {
|
||||
setHasSearched(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initial load
|
||||
updateSettings();
|
||||
|
||||
// Subscribe to changes
|
||||
const unsubscribe = settingsStore.subscribe(updateSettings);
|
||||
return () => unsubscribe();
|
||||
}, [query, loading, executeSearch]);
|
||||
|
||||
// Load cached results on mount
|
||||
useEffect(() => {
|
||||
if (hasLoadedCache.current) return;
|
||||
hasLoadedCache.current = true;
|
||||
|
||||
const urlQuery = searchParams.get('q');
|
||||
// Note: We might want to separate cache for premium mode, but for now sharing or not using cache might be safer.
|
||||
// However, useSearchCache uses localStorage which is shared.
|
||||
// If we want to avoid leaking premium searches to normal history, we might want to disable cache or use a different key.
|
||||
// For simplicity and "hidden" nature, maybe we don't load cache from normal mode?
|
||||
// But the user asked for "same as original page".
|
||||
|
||||
if (urlQuery) {
|
||||
setQuery(urlQuery);
|
||||
handleSearch(urlQuery);
|
||||
// We need to wait for sources to be available, which is handled by the subscription effect
|
||||
// But if sources are already available (e.g. navigation), execute immediately
|
||||
const currentSettings = settingsStore.getSettings();
|
||||
const currentSources = currentSettings.premiumSources.filter(s => s.enabled);
|
||||
|
||||
if (currentSources.length > 0) {
|
||||
handleSearch(urlQuery);
|
||||
}
|
||||
// If no sources yet, the useEffect above will catch it when they load
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const handleSearch = (searchQuery: string) => {
|
||||
setQuery(searchQuery);
|
||||
setHasSearched(true);
|
||||
// Use enabled premium sources from settings
|
||||
performSearch(searchQuery, enabledPremiumSources, currentSortBy as any);
|
||||
// Use current state of sources
|
||||
executeSearch(searchQuery, enabledPremiumSources);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setHasSearched(false);
|
||||
setQuery('');
|
||||
hasSearchedWithSourcesRef.current = false;
|
||||
resetSearch();
|
||||
router.replace('/premium', { scroll: false });
|
||||
};
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "3.9.3",
|
||||
"version": "3.9.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kvideo",
|
||||
"version": "3.9.3",
|
||||
"version": "3.9.4",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "3.9.3",
|
||||
"version": "3.9.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
Reference in New Issue
Block a user