feat: mobile opens posts as a scrolling feed instead of a modal
Docker Build and Publish / build-and-push (push) Failing after 10s
Docker Build and Publish / build-and-push (push) Failing after 10s
Tapping a post on a phone now opens a real feed page — header, media, actions, caption, next post peeking in below — scrolled with the browser's own vertical scrolling rather than swipe gestures. Desktop keeps the modal, where a centred sheet with side arrows suits a pointer. Only a window of posts is mounted: a profile here holds up to 1129 posts and mounting them all would mean as many full-size images. The window grows in both directions as you scroll. Growing upwards shifts everything below it, so the scroll offset is corrected in the same frame, before paint — measured against the real archive, an anchored post moves exactly one screen per scroll with no jump. Only the post crossing the viewport centre plays its video; the rest stay paused, so a feed of reels doesn't play ten at once. The URL tracks that same post, so scrolling updates /<archive>/p/<shortcode>/ the way Instagram does, and the back button returns to the grid with its scroll position intact. Feed video sizes to the container width rather than its intrinsic size: a <video> reports 300x150 until metadata loads, which made it render narrow and then jump to full width. It also gets a taller height ceiling than the modal so ordinary portrait media fills the width instead of sitting in side bars. The carousel is extracted into a shared MediaCarousel used by both surfaces, so horizontal paging behaves identically; touch-action keeps vertical scrolling passing through to the feed. PostModal loses its now-dead mobile swipe branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uBWhwV3wFQ5MBCcMHHem7
This commit is contained in:
co-authored by
Claude Opus 5
parent
877d21ff1f
commit
c54f8d5b09
+33
-15
@@ -38,6 +38,8 @@ import { CacheData, Post, ServerArchive, ServerArchiveFile } from './types';
|
||||
import { ArchiveDashboard } from './components/ArchiveDashboard';
|
||||
import { StoryViewer } from './components/StoryViewer';
|
||||
import { PostModal } from './components/PostModal';
|
||||
import { PostFeed } from './components/PostFeed';
|
||||
import { useIsMobile } from './hooks/useIsMobile';
|
||||
import { PostThumbnail } from './components/PostThumbnail';
|
||||
import { useArchiveScanner } from './hooks/useArchiveScanner';
|
||||
import { useThumbnailQueue } from './hooks/useThumbnailQueue';
|
||||
@@ -70,6 +72,7 @@ export default function App() {
|
||||
*/
|
||||
const initialRouteRef = useRef(parseRoute(window.location.pathname, window.location.search));
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const profilePicInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -549,21 +552,36 @@ export default function App() {
|
||||
)}
|
||||
</main>
|
||||
|
||||
<AnimatePresence>
|
||||
{selectedPost && (
|
||||
<PostModal
|
||||
post={selectedPost}
|
||||
nextPost={postIndex < filteredPosts.length - 1 ? filteredPosts[postIndex + 1] : undefined}
|
||||
prevPost={postIndex > 0 ? filteredPosts[postIndex - 1] : undefined}
|
||||
onClose={() => setSelectedPost(null)}
|
||||
onNextPost={onNextPost}
|
||||
onPrevPost={onPrevPost}
|
||||
hasNextPost={postIndex < filteredPosts.length - 1}
|
||||
hasPrevPost={postIndex > 0}
|
||||
profilePic={profilePic}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{/*
|
||||
Mobile opens a real scrolling feed page, the way Instagram does; desktop
|
||||
keeps the modal, where a centred sheet with side arrows fits the pointer.
|
||||
*/}
|
||||
{selectedPost && isMobile ? (
|
||||
<PostFeed
|
||||
posts={filteredPosts}
|
||||
initialPostId={selectedPost.id}
|
||||
profilePic={profilePic}
|
||||
title={activeTab === 'reels' ? 'Reels' : 'Posts'}
|
||||
onClose={() => setSelectedPost(null)}
|
||||
onActivePostChange={setSelectedPost}
|
||||
/>
|
||||
) : (
|
||||
<AnimatePresence>
|
||||
{selectedPost && (
|
||||
<PostModal
|
||||
post={selectedPost}
|
||||
nextPost={postIndex < filteredPosts.length - 1 ? filteredPosts[postIndex + 1] : undefined}
|
||||
prevPost={postIndex > 0 ? filteredPosts[postIndex - 1] : undefined}
|
||||
onClose={() => setSelectedPost(null)}
|
||||
onNextPost={onNextPost}
|
||||
onPrevPost={onPrevPost}
|
||||
hasNextPost={postIndex < filteredPosts.length - 1}
|
||||
hasPrevPost={postIndex > 0}
|
||||
profilePic={profilePic}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
<AnimatePresence>{showStoryViewer && allStories.length > 0 && <StoryViewer stories={allStories} onClose={() => setShowStoryViewer(false)} profilePic={profilePic} />}</AnimatePresence>
|
||||
<AnimatePresence>
|
||||
{activeHighlight && (
|
||||
|
||||
Reference in New Issue
Block a user