Add mobile image display and optimize gallery loading
Update gallery component to conditionally load mobile-optimized images for smaller screens and refactor Dropbox integration to fetch images from separate desktop and mobile folders. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1f7e7e89-a520-4970-9645-37daadc466dc Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 047c052f-7045-4b74-a78d-c75e84aaddd0 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/1f7e7e89-a520-4970-9645-37daadc466dc/5JDNRWP Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
23149965d7
commit
dcf8512631
@ -7,6 +7,7 @@ interface GalleryImage {
|
|||||||
fileName: string;
|
fileName: string;
|
||||||
thumb: string;
|
thumb: string;
|
||||||
large: string;
|
large: string;
|
||||||
|
mobile?: string;
|
||||||
artist?: string;
|
artist?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,8 +29,7 @@ function cloudinaryTransform(src: string, transform: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function thumbUrl(src: string) {
|
function thumbUrl(src: string) {
|
||||||
if (src.includes("res.cloudinary.com")) return src;
|
return src;
|
||||||
return `/api/gallery/thumb?src=${encodeURIComponent(src)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function LazyImage({ src, alt, className, onClick }: { src: string; alt: string; className?: string; onClick?: () => void }) {
|
function LazyImage({ src, alt, className, onClick }: { src: string; alt: string; className?: string; onClick?: () => void }) {
|
||||||
@ -289,6 +289,7 @@ export function PhotoGalleryWidget({ reverseOrder = false }: { reverseOrder?: bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function GalleryPage() {
|
export default function GalleryPage() {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
const { data: images, isLoading } = useQuery<GalleryImage[]>({
|
const { data: images, isLoading } = useQuery<GalleryImage[]>({
|
||||||
queryKey: ["/api/gallery"],
|
queryKey: ["/api/gallery"],
|
||||||
});
|
});
|
||||||
@ -337,9 +338,9 @@ export default function GalleryPage() {
|
|||||||
className="group relative rounded-lg overflow-hidden bg-card border border-card-border cursor-pointer flex flex-col"
|
className="group relative rounded-lg overflow-hidden bg-card border border-card-border cursor-pointer flex flex-col"
|
||||||
data-testid={`button-gallery-image-${i}`}
|
data-testid={`button-gallery-image-${i}`}
|
||||||
>
|
>
|
||||||
<div className="relative w-full aspect-[3/2] md:aspect-[9/16]">
|
<div className="relative w-full aspect-[16/9] md:aspect-[9/16]">
|
||||||
<LazyImage
|
<LazyImage
|
||||||
src={thumbUrl(img.thumb)}
|
src={isMobile && img.mobile ? img.mobile : thumbUrl(img.thumb)}
|
||||||
alt={img.artist || img.fileName}
|
alt={img.artist || img.fileName}
|
||||||
className="absolute inset-0 w-full h-full transition-transform duration-500 group-hover:scale-110"
|
className="absolute inset-0 w-full h-full transition-transform duration-500 group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ interface GalleryImage {
|
|||||||
fileName: string;
|
fileName: string;
|
||||||
thumb: string;
|
thumb: string;
|
||||||
large: string;
|
large: string;
|
||||||
|
mobile: string;
|
||||||
full: string;
|
full: string;
|
||||||
artist: string;
|
artist: string;
|
||||||
}
|
}
|
||||||
@ -215,6 +216,10 @@ async function getTemporaryLinks(accessToken: string, paths: string[]): Promise<
|
|||||||
const galleryCache: { data: GalleryImage[]; timestamp: number } = { data: [], timestamp: 0 };
|
const galleryCache: { data: GalleryImage[]; timestamp: number } = { data: [], timestamp: 0 };
|
||||||
const CACHE_DURATION = 30 * 60 * 1000;
|
const CACHE_DURATION = 30 * 60 * 1000;
|
||||||
|
|
||||||
|
const THUMB_FOLDER = GALLERY_ROOT + "/Foto 1x1";
|
||||||
|
const LARGE_FOLDER = GALLERY_ROOT + "/Foto 9x16";
|
||||||
|
const MOBILE_FOLDER = GALLERY_ROOT + "/Foto 16x9";
|
||||||
|
|
||||||
export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
|
export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
|
||||||
if (galleryCache.data.length > 0 && Date.now() - galleryCache.timestamp < CACHE_DURATION) {
|
if (galleryCache.data.length > 0 && Date.now() - galleryCache.timestamp < CACHE_DURATION) {
|
||||||
return galleryCache.data;
|
return galleryCache.data;
|
||||||
@ -224,37 +229,50 @@ export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
|
|||||||
if (!accessToken) return [];
|
if (!accessToken) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const topEntries = await listFolder(accessToken, GALLERY_ROOT);
|
const [thumbEntries, largeEntries, mobileEntries] = await Promise.all([
|
||||||
const folders = topEntries.filter((e: any) => e[".tag"] === "folder");
|
listFolder(accessToken, THUMB_FOLDER.toLowerCase()),
|
||||||
|
listFolder(accessToken, LARGE_FOLDER.toLowerCase()),
|
||||||
|
listFolder(accessToken, MOBILE_FOLDER.toLowerCase()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const imgFilter = (e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name);
|
||||||
|
const thumbFiles = thumbEntries.filter(imgFilter);
|
||||||
|
const largeFiles = largeEntries.filter(imgFilter);
|
||||||
|
const mobileFiles = mobileEntries.filter(imgFilter);
|
||||||
|
|
||||||
|
const [thumbLinks, largeLinks, mobileLinks] = await Promise.all([
|
||||||
|
getTemporaryLinks(accessToken, thumbFiles.map((f: any) => f.path_lower)),
|
||||||
|
getTemporaryLinks(accessToken, largeFiles.map((f: any) => f.path_lower)),
|
||||||
|
getTemporaryLinks(accessToken, mobileFiles.map((f: any) => f.path_lower)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const largeMap = new Map<string, string>();
|
||||||
|
for (const file of largeFiles) {
|
||||||
|
const link = largeLinks.get(file.path_lower) || "";
|
||||||
|
if (link) largeMap.set(file.name.toLowerCase(), link);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mobileMap = new Map<string, string>();
|
||||||
|
for (const file of mobileFiles) {
|
||||||
|
const link = mobileLinks.get(file.path_lower) || "";
|
||||||
|
if (link) mobileMap.set(file.name.toLowerCase(), link);
|
||||||
|
}
|
||||||
|
|
||||||
const images: GalleryImage[] = [];
|
const images: GalleryImage[] = [];
|
||||||
|
for (const file of thumbFiles) {
|
||||||
for (const folder of folders) {
|
const thumbLink = thumbLinks.get(file.path_lower) || "";
|
||||||
try {
|
if (!thumbLink) continue;
|
||||||
const folderName = folder.name;
|
const largeLink = largeMap.get(file.name.toLowerCase()) || thumbLink;
|
||||||
const folderEntries = await listFolder(accessToken, folder.path_lower);
|
const mobileLink = mobileMap.get(file.name.toLowerCase()) || thumbLink;
|
||||||
const imageFiles = folderEntries.filter(
|
images.push({
|
||||||
(e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name)
|
folder: "Foto All",
|
||||||
);
|
fileName: file.name,
|
||||||
|
thumb: thumbLink,
|
||||||
const paths = imageFiles.map((f: any) => f.path_lower);
|
large: largeLink,
|
||||||
const tempLinks = await getTemporaryLinks(accessToken, paths);
|
mobile: mobileLink,
|
||||||
|
full: largeLink,
|
||||||
for (const file of imageFiles) {
|
artist: extractArtistFromFileName(file.name),
|
||||||
const link = tempLinks.get(file.path_lower) || "";
|
});
|
||||||
if (link) {
|
|
||||||
images.push({
|
|
||||||
folder: folderName,
|
|
||||||
fileName: file.name,
|
|
||||||
thumb: link,
|
|
||||||
large: link,
|
|
||||||
full: link,
|
|
||||||
artist: extractArtistFromFileName(file.name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(`Folder ${folder.name} error:`, err.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (images.length > 0) {
|
if (images.length > 0) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -353,26 +353,19 @@ export async function registerRoutes(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gallery API - serves photos from Cloudinary (gallery-data.json)
|
// Gallery API - serves optimized photos from Dropbox
|
||||||
const folderArtists: Record<string, string> = {
|
|
||||||
"2": "Folx Stadl",
|
|
||||||
"3": "Folx Stadl",
|
|
||||||
"4": "Folx Stadl",
|
|
||||||
"5": "Folx Stadl",
|
|
||||||
"6": "Folx Stadl",
|
|
||||||
};
|
|
||||||
|
|
||||||
app.get("/api/gallery", async (req, res) => {
|
app.get("/api/gallery", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const galleryPath = path.join(process.cwd(), "server/gallery-data.json");
|
let data: any[] = [];
|
||||||
let data: any[] = JSON.parse(fs.readFileSync(galleryPath, "utf-8"));
|
|
||||||
|
|
||||||
data = data.map((img: any) => {
|
if (isConnected()) {
|
||||||
if (!img.artist && folderArtists[img.folder]) {
|
data = await fetchGalleryFromDropbox();
|
||||||
return { ...img, artist: folderArtists[img.folder] };
|
}
|
||||||
}
|
|
||||||
return img;
|
if (data.length === 0) {
|
||||||
});
|
const galleryPath = path.join(process.cwd(), "server/gallery-data.json");
|
||||||
|
data = JSON.parse(fs.readFileSync(galleryPath, "utf-8"));
|
||||||
|
}
|
||||||
|
|
||||||
const shuffled = [...data];
|
const shuffled = [...data];
|
||||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user