From 147dcdf2f1fea1096eed23aa086e5c312fc091c5 Mon Sep 17 00:00:00 2001 From: ergosteur <1992147+ergosteur@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:18:33 -0500 Subject: [PATCH] fix: restore missing UI handlers and finalize generic parser --- src/App.tsx | 197 +++++++++++++++++++--------------------------------- 1 file changed, 71 insertions(+), 126 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 1e7f47e..3a5dfd7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -142,7 +142,7 @@ const ArchiveDashboard = ({ )} - @@ -486,7 +486,7 @@ const VideoThumbnail = ({ url, className }: { url: string; className?: string }) }, [url, thumbnail, isInView]); if (!thumbnail) return ( -
+
); @@ -499,7 +499,7 @@ const MediaRenderer = ({ file, className, isFullView }: { file: MediaFile; class const sizingClass = isFullView ? "w-full h-auto block" : "w-full h-full object-cover"; const mediaStyle = { transform: 'translateZ(0)' }; - if (!file.url) return
; + if (!file.url) return
; if (file.type === 'video') { return ( @@ -579,7 +579,7 @@ const PostModal = ({
-
{profilePic ? : {post.username[0]}}
+
{profilePic ? : {post.username[0]}}
{post.username}{post.caption}
{format(parseISO(post.date), 'MMMM d, yyyy')}
@@ -646,110 +646,6 @@ export default function App() { const clearCache = async (name: string) => { await idb.del(name); await refreshCachedArchives(); }; const resetProfileState = useCallback(() => { setUsername(''); setFullName(''); setBio(''); setFollowerCount(0); setFollowingCount(0); setExternalUrl(''); setProfilePic(null); setAllProfilePics([]); }, []); - useEffect(() => { - fetch('/api/archives').then(res => res.json()).then(data => { if (Array.isArray(data) && data.length > 0) { setServerArchives(data); setIsServerMode(true); } }).catch(() => setIsServerMode(false)); - refreshCachedArchives(); - }, [refreshCachedArchives]); - - // --- Permalink Synchronization --- - - // Update URL based on state - useEffect(() => { - console.log('[Permalink] State changed:', { - currentArchive: currentArchive?.name, - username, - posts: allPosts.length, - activeTab, - selectedPost: selectedPost?.id - }); - - const params = new URLSearchParams(); - if (currentArchive) params.set('a', currentArchive.name); - else if (allPosts.length > 0 && username) params.set('a', username); // Handle local archives too - - if (activeTab !== 'posts') params.set('t', activeTab); - if (selectedPost) params.set('p', selectedPost.id); - - const newSearch = params.toString(); - const currentSearch = new URLSearchParams(window.location.search).toString(); - - if (newSearch !== currentSearch) { - console.log(`[Permalink] Updating URL to: ?${newSearch}`); - const newUrl = window.location.pathname + (newSearch ? `?${newSearch}` : ''); - window.history.replaceState(null, '', newUrl); - } - }, [currentArchive?.name, username, allPosts.length, activeTab, selectedPost?.id]); - - // Read state from URL on mount/initialization - const [hasInitialLoaded, setHasInitialLoaded] = useState(false); - useEffect(() => { - if (hasInitialLoaded || serverArchives.length === 0) return; - - const params = new URLSearchParams(window.location.search); - const archiveName = params.get('a'); - const tab = params.get('t'); - const postId = params.get('p'); - - console.log('[Permalink] Initial read from URL:', { archiveName, tab, postId }); - - if (archiveName) { - const archive = serverArchives.find(a => a.name === archiveName); - if (archive) { - console.log(`[Permalink] Auto-loading archive: ${archiveName}`); - loadServerArchive(archive); - if (tab && ['posts', 'reels', 'saved'].includes(tab)) { - setActiveTab(tab as any); - } - } - } - setHasInitialLoaded(true); - }, [serverArchives, hasInitialLoaded]); - - // Auto-open post if postId is in URL and posts are loaded - useEffect(() => { - const params = new URLSearchParams(window.location.search); - const postId = params.get('p'); - if (postId && allPosts.length > 0 && !selectedPost) { - const post = allPosts.find(p => p.id === postId); - if (post) setSelectedPost(post); - } - }, [allPosts, selectedPost]); - - // Intercept back button and page exit - useEffect(() => { - const hasData = allPosts.length > 0; - - const handleBeforeUnload = (e: BeforeUnloadEvent) => { - if (hasData) { - const message = 'Are you sure you want to leave? Your current archive session will be cleared.'; - e.preventDefault(); - e.returnValue = message; - return message; - } - }; - - const handlePopState = (e: PopStateEvent) => { - if (allPosts.length > 0) { - setAllPosts([]); - setAllStories([]); - setCurrentArchive(null); - resetProfileState(); - } - }; - - window.addEventListener('beforeunload', handleBeforeUnload); - window.addEventListener('popstate', handlePopState); - - if (hasData) { - window.history.pushState({ inApp: true }, ''); - } - - return () => { - window.removeEventListener('beforeunload', handleBeforeUnload); - window.removeEventListener('popstate', handlePopState); - }; - }, [allPosts.length, resetProfileState]); - const filteredPosts = useMemo(() => { if (activeTab === 'reels') return allPosts.filter(p => p.media.length === 1 && p.media[0].type === 'video'); if (activeTab === 'posts') return allPosts.filter(p => !(p.media.length === 1 && p.media[0].type === 'video')); @@ -769,7 +665,7 @@ export default function App() { const cycleProfilePic = () => { if (allProfilePics.length > 1) { const idx = allProfilePics.indexOf(profilePic || ''); setProfilePic(allProfilePics[(idx + 1) % allProfilePics.length]); } }; - const handleFiles = async (files: ArchiveFile[], archiveContext?: ServerArchive) => { + const handleFiles = useCallback(async (files: ArchiveFile[], archiveContext?: ServerArchive) => { if (!files || files.length === 0) return; setIsScanning(true); resetProfileState(); setScanningPhase('Indexing'); setScannedCount(0); setTotalFiles(files.length); setScannedFilesLog([]); setGridOffset(0); console.log(`[Scanner] Starting scan of ${files.length} files...`); @@ -822,6 +718,7 @@ export default function App() { let format: 'export' | 'instaloader' | 'json' | 'unknown' = 'unknown'; let jsonFiles: ArchiveFile[] = []; + // Pass 1: Indexing for (let i = 0; i < files.length; i++) { const file = files[i]; if (i % 50 === 0 || i === files.length - 1) { @@ -854,6 +751,7 @@ export default function App() { console.log(`[Scanner] Format Detection Complete. Result: ${format}. Media indexed: ${mediaFilesMap.size}`); + // Pass 2: JSON Parsing if (jsonFiles.length > 0 && (format === 'json' || format === 'instaloader')) { setScanningPhase('Parsing'); for (let i = 0; i < jsonFiles.length; i++) { @@ -911,6 +809,7 @@ export default function App() { } } + // Pass 3: Regex Parsing if (format === 'export' || format === 'instaloader') { setScanningPhase('Parsing'); const CHUNK_SIZE = 100; @@ -963,6 +862,7 @@ export default function App() { } } + // Pass 4: Generic Fallback if (postsMap.size === 0) { console.log(`[Scanner] No posts found with standard patterns. Using mediaFilesMap: ${mediaFilesMap.size}`); setScanningPhase('Parsing'); @@ -1058,10 +958,10 @@ export default function App() { await refreshCachedArchives(); } catch (e) { console.error(`[Cache] Save error:`, e); } } - } catch (err) { console.error(`[Scanner] Critical error:`, err); } finally { setIsScanning(false); } - }; + } catch (err) { console.error(`[Scanner] Critical error during scan:`, err); } finally { setIsScanning(false); } + }, [currentArchive, resetProfileState, refreshCachedArchives, setCurrentScanningImage, setBio, setExternalUrl, setFollowerCount, setFollowingCount, setFullName, setProfilePic, setUsername]); - const loadServerArchive = async (archive: ServerArchive) => { + const loadServerArchive = useCallback(async (archive: ServerArchive) => { console.log(`[Cache] Attempting to load archive: ${archive.name}`); setIsScanning(true); setCurrentArchive(archive); @@ -1110,12 +1010,57 @@ export default function App() { console.error('[Scanner] Failed to load server archive:', err); setIsScanning(false); } - }; + }, [handleFiles]); const handleLocalFiles = (files: FileList | null) => { if (!files) return; const archiveFiles = Array.from(files).map(f => new LocalArchiveFile(f)); handleFiles(archiveFiles); }; const triggerFileSelect = () => fileInputRef.current?.click(); const loadMore = () => setVisiblePostsCount(prev => prev + 90); + useEffect(() => { + const params = new URLSearchParams(); + if (currentArchive) params.set('a', currentArchive.name); + else if (allPosts.length > 0 && username) params.set('a', username); + if (activeTab !== 'posts') params.set('t', activeTab); + if (selectedPost) params.set('p', selectedPost.id); + const newSearch = params.toString(); + const currentSearch = new URLSearchParams(window.location.search).toString(); + if (newSearch !== currentSearch) { + console.log(`[Permalink] Updating URL to: ?${newSearch}`); + const newUrl = window.location.pathname + (newSearch ? `?${newSearch}` : ''); + window.history.replaceState(null, '', newUrl); + } + }, [currentArchive?.name, username, allPosts.length, activeTab, selectedPost?.id]); + + const [hasInitialLoaded, setHasInitialLoaded] = useState(false); + useEffect(() => { + if (hasInitialLoaded || serverArchives.length === 0) return; + const params = new URLSearchParams(window.location.search); + const archiveName = params.get('a'); + const tab = params.get('t'); + const postId = params.get('p'); + console.log('[Permalink] Initial read from URL:', { archiveName, tab, postId }); + if (archiveName) { + const archive = serverArchives.find(a => a.name === archiveName); + if (archive) { + console.log(`[Permalink] Auto-loading archive: ${archiveName}`); + loadServerArchive(archive); + if (tab && ['posts', 'reels', 'saved'].includes(tab)) { + setActiveTab(tab as any); + } + } + } + setHasInitialLoaded(true); + }, [serverArchives, hasInitialLoaded, loadServerArchive]); + + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const postId = params.get('p'); + if (postId && allPosts.length > 0 && !selectedPost) { + const post = allPosts.find(p => p.id === postId); + if (post) setSelectedPost(post); + } + }, [allPosts, selectedPost]); + return (
handleLocalFiles(e.target.files)} /> @@ -1152,7 +1097,7 @@ export default function App() { isScanning={isScanning} /> ) : ( -
+

No Archive Selected

Select a local archive folder to start browsing. Your files are processed locally in the browser and never uploaded.