mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-14 10:48:20 +02:00
Improve media loading with infinite scrolling on upload and new post pages
Integrates IntersectionObserver for infinite scrolling in MediaUpload and NewPost components, enhancing performance by loading media items in batches. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/lEm79pj
This commit is contained in:
1 parent
ca3acd2df4
commit
5bfff22bab
2 files changed
+110
-46
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback, useRef, useEffect } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
@@ -11,6 +11,8 @@ import { SiFacebook, SiInstagram } from "react-icons/si";
|
||||
export default function MediaUpload() {
|
||||
const { toast } = useToast();
|
||||
const [selectedFile, setSelectedFile] = useState<any>(null);
|
||||
const [visibleCount, setVisibleCount] = useState(5);
|
||||
const loadMoreRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data: mediaList } = useQuery({
|
||||
queryKey: ["/api/media"],
|
||||
@@ -69,6 +71,28 @@ export default function MediaUpload() {
|
||||
},
|
||||
});
|
||||
|
||||
// Scroll infini - IntersectionObserver
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && mediaList && visibleCount < (mediaList as any[]).length) {
|
||||
setVisibleCount(prev => Math.min(prev + 5, (mediaList as any[]).length));
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (loadMoreRef.current) {
|
||||
observer.observe(loadMoreRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (loadMoreRef.current) {
|
||||
observer.unobserve(loadMoreRef.current);
|
||||
}
|
||||
};
|
||||
}, [mediaList, visibleCount]);
|
||||
|
||||
return (
|
||||
<div className="bg-card rounded-2xl border border-border/50 overflow-hidden shadow-lg">
|
||||
<div className="border-b border-border/50 p-6 bg-gradient-to-r from-primary/5 to-secondary/5">
|
||||
@@ -123,8 +147,8 @@ export default function MediaUpload() {
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
<div className="space-y-3">
|
||||
{(mediaList as any[])?.slice(0, 5).map((media: any) => (
|
||||
<div className="space-y-3 max-h-[500px] overflow-y-auto">
|
||||
{(mediaList as any[])?.slice(0, visibleCount).map((media: any) => (
|
||||
<div
|
||||
key={media.id}
|
||||
onClick={() => setSelectedFile(media)}
|
||||
@@ -165,6 +189,13 @@ export default function MediaUpload() {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Élément sentinelle pour le scroll infini */}
|
||||
{mediaList && visibleCount < (mediaList as any[]).length && (
|
||||
<div ref={loadMoreRef} className="flex justify-center py-4">
|
||||
<Loader2 className="w-5 h-5 text-primary animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { useState, useCallback, useRef, useEffect } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { useLocation } from "wouter";
|
||||
import { Send, Sparkles, Image as ImageIcon, Calendar, Upload, Camera, GripVertical, Loader2 } from "lucide-react";
|
||||
@@ -112,6 +112,8 @@ export default function NewPost() {
|
||||
const [postText, setPostText] = useState('');
|
||||
const [postType, setPostType] = useState<'feed' | 'story' | 'both'>('feed');
|
||||
const [previewModalOpen, setPreviewModalOpen] = useState(false);
|
||||
const [visibleMediaCount, setVisibleMediaCount] = useState(12);
|
||||
const loadMoreMediaRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data: pages = [] } = useQuery<SocialPage[]>({
|
||||
queryKey: ['/api/pages'],
|
||||
@@ -310,6 +312,28 @@ export default function NewPost() {
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll infini - IntersectionObserver pour la grille de médias
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && mediaList && visibleMediaCount < mediaList.length) {
|
||||
setVisibleMediaCount(prev => Math.min(prev + 12, mediaList.length));
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (loadMoreMediaRef.current) {
|
||||
observer.observe(loadMoreMediaRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (loadMoreMediaRef.current) {
|
||||
observer.unobserve(loadMoreMediaRef.current);
|
||||
}
|
||||
};
|
||||
}, [mediaList, visibleMediaCount]);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-background">
|
||||
{sidebarOpen && (
|
||||
@@ -451,48 +475,57 @@ export default function NewPost() {
|
||||
<div className="text-sm font-medium text-muted-foreground mb-2">
|
||||
Toutes les photos
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{mediaList.map((media) => {
|
||||
const isSelected = selectedMedia.includes(media.id);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={media.id}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
// Remove from selection
|
||||
setSelectedMedia(prev => prev.filter(id => id !== media.id));
|
||||
} else if (selectedMedia.length < 10) {
|
||||
// Add to selection (max 10)
|
||||
setSelectedMedia(prev => [...prev, media.id]);
|
||||
} else {
|
||||
toast({
|
||||
title: "Limite atteinte",
|
||||
description: "Maximum 10 photos par publication",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`relative aspect-square rounded-lg overflow-hidden border-2 transition-all ${
|
||||
isSelected
|
||||
? 'border-primary ring-2 ring-primary opacity-50'
|
||||
: 'border-transparent hover:border-muted-foreground'
|
||||
}`}
|
||||
data-testid={`button-select-media-${media.id}`}
|
||||
>
|
||||
<img
|
||||
src={media.originalUrl}
|
||||
alt={media.fileName}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{isSelected && (
|
||||
<div className="absolute inset-0 bg-primary/10 flex items-center justify-center">
|
||||
<div className="text-xs font-semibold text-primary">Sélectionnée</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<div className="max-h-[500px] overflow-y-auto">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{mediaList.slice(0, visibleMediaCount).map((media) => {
|
||||
const isSelected = selectedMedia.includes(media.id);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={media.id}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
// Remove from selection
|
||||
setSelectedMedia(prev => prev.filter(id => id !== media.id));
|
||||
} else if (selectedMedia.length < 10) {
|
||||
// Add to selection (max 10)
|
||||
setSelectedMedia(prev => [...prev, media.id]);
|
||||
} else {
|
||||
toast({
|
||||
title: "Limite atteinte",
|
||||
description: "Maximum 10 photos par publication",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`relative aspect-square rounded-lg overflow-hidden border-2 transition-all ${
|
||||
isSelected
|
||||
? 'border-primary ring-2 ring-primary opacity-50'
|
||||
: 'border-transparent hover:border-muted-foreground'
|
||||
}`}
|
||||
data-testid={`button-select-media-${media.id}`}
|
||||
>
|
||||
<img
|
||||
src={media.originalUrl}
|
||||
alt={media.fileName}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{isSelected && (
|
||||
<div className="absolute inset-0 bg-primary/10 flex items-center justify-center">
|
||||
<div className="text-xs font-semibold text-primary">Sélectionnée</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Élément sentinelle pour le scroll infini */}
|
||||
{mediaList && visibleMediaCount < mediaList.length && (
|
||||
<div ref={loadMoreMediaRef} className="flex justify-center py-4 col-span-3">
|
||||
<Loader2 className="w-5 h-5 text-primary animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user