skeleton cards and infinite scroll

This commit is contained in:
BFlat committed 2023-11-27 22:09:45 +07:00
1 parent 3fac95abf0
commit 6eb563935e
9 files changed
+384 -42

No files matched your search

+2
View File
@@ -28,6 +28,7 @@
"@types/gtag.js": "^0.0.12",
"@types/howler": "^2.1.2",
"@types/imagesloaded": "^4.1.6",
"@types/infinite-scroll": "^4.0.3",
"@types/jest": "^24.9.1",
"@types/js-cookie": "^3.0.1",
"@types/masonry-layout": "^4.2.7",
@@ -46,6 +47,7 @@
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.1",
"imagesloaded": "^5.0.0",
"infinite-scroll": "^4.0.1",
"js-cookie": "^3.0.1",
"masonry-layout": "^4.2.2",
"posthog-js": "^1.73.1",
@@ -17,6 +17,15 @@
max-height: 500px;
}
.card-video {
width: 100%;
transition: 0.4s ease;
object-fit: cover;
object-position: center;
min-height: 240px;
max-height: 240px;
}
.card-img-overlay {
position: absolute;
bottom: 0;
@@ -65,6 +74,10 @@
transform: scale(1.05);
}
&:hover > .card-video {
transform: scale(1.05);
}
&:hover .card-video-play {
color: rgba($white, 0.8);
}
@@ -0,0 +1,37 @@
import React from "react";
import Card from "../Card";
import Skeleton from "components/common/Skeleton";
interface SkeletonCardProps {}
export default function SkeletonCard(props: SkeletonCardProps) {
return (
<div className="col-12 col-sm-6 col-xl-4">
<Card padding={true}>
<div className="mb-3">
<div className="d-flex align-items-center">
<div className="flex-grow-1">
<Skeleton type="short" />
</div>
<div>
<Skeleton type="short" />
</div>
</div>
<h6 className="fw-semibold text-white mb-1 mt-3">
<Skeleton type="medium" />
</h6>
<p className="fs-7 opacity-75">
<Skeleton type="short" />
</p>
</div>
<div>
<h2 className="mb-0">
<Skeleton type="full" />
</h2>
</div>
</Card>
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./SkeletonCard";
@@ -33,7 +33,7 @@ export default function VideoCard({ data }: VideoCardProps) {
<img
src={data.public_bucket_path}
alt={data.weight_name}
className="card-img"
className="card-video"
/>
<div className="card-img-overlay">
<div className="card-img-gradient" />
@@ -1,17 +1,28 @@
import Masonry from "masonry-layout"; //Refer to: https://github.com/desandro/masonry
import imagesLoaded from "imagesloaded"; //Refer to: https://github.com/desandro/imagesloaded
import React, { useEffect, useRef } from "react";
import React, { useEffect } from "react";
interface MasonryGridProps {
children: React.ReactNode;
onLayoutComplete?: () => void;
gridRef: React.RefObject<HTMLDivElement>;
}
export default function MasonryGrid({ children }: MasonryGridProps) {
const gridRef = useRef<HTMLDivElement>(null);
export default function MasonryGrid({
children,
onLayoutComplete,
gridRef,
}: MasonryGridProps) {
useEffect(() => {
let masonryInstance: Masonry | null = null;
const updateLayout = () => {
//Needs this type guard to check that it is not undefined.
if (masonryInstance && typeof masonryInstance.layout === "function") {
masonryInstance.layout();
}
};
if (gridRef.current) {
masonryInstance = new Masonry(gridRef.current, {
itemSelector: ".grid-item",
@@ -20,10 +31,8 @@ export default function MasonryGrid({ children }: MasonryGridProps) {
//Needs this or images will overflow masonry card. It checks if image is loaded then after it is loaded, it resizes the card.
imagesLoaded(gridRef.current, function () {
//Needs this type guard to check that it is not undefined.
if (masonryInstance && typeof masonryInstance.layout === "function") {
masonryInstance.layout();
}
updateLayout();
onLayoutComplete?.();
});
}
@@ -33,7 +42,7 @@ export default function MasonryGrid({ children }: MasonryGridProps) {
masonryInstance.destroy();
}
};
}, []);
}, [gridRef, onLayoutComplete]);
return (
<div
@@ -1,18 +1,41 @@
// import { Panel } from "components/common";
import React, { useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
import mockData from "./mockData";
import AudioCard from "components/common/Card/AudioCard";
import ImageCard from "components/common/Card/ImageCard";
import VideoCard from "components/common/Card/VideoCard";
// import InfiniteScroll from "react-infinite-scroll-component";
import Panel from "components/common/Panel";
import { Select } from "components/common/Inputs/Inputs";
import { faFilter } from "@fortawesome/pro-solid-svg-icons";
import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContext";
import SkeletonCard from "components/common/Card/SkeletonCard";
import InfiniteScroll from "react-infinite-scroll-component";
export default function MediaTab() {
const [data, setData] = useState(mockData);
const itemsPerPage = 10; // Items per page
const initialData = mockData.slice(0, itemsPerPage); // Initial 10 items
const [data, setData] = useState(initialData);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1); //start from second page
const [isLoading, setIsLoading] = useState(false);
const gridContainerRef = useRef<HTMLDivElement | null>(null);
const fetchMoreData = () => {
if (data.length >= mockData.length) {
setHasMore(false);
return;
}
// Simulate an API fetch
setTimeout(() => {
const startIndex = page * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const nextItems = mockData.slice(startIndex, endIndex);
setData(data.concat(nextItems));
setPage(page + 1);
}, 1500);
};
const options = [
{ value: "all", label: "All Media" },
@@ -32,32 +55,52 @@ export default function MediaTab() {
/>
</div>
<AudioPlayerProvider>
<MasonryGrid>
{data.map((data, index) => {
let card;
switch (data.media_type) {
case "audio":
card = <AudioCard key={index} data={data} />;
break;
case "image":
card = <ImageCard key={index} data={data} />;
break;
case "video":
card = <VideoCard key={index} data={data} />;
break;
default:
card = <div key={index}>Unsupported media type</div>;
}
return (
<div
key={index}
className="col-12 col-sm-6 col-xl-4 col-xxl-3 grid-item"
>
{card}
</div>
);
})}
</MasonryGrid>
{isLoading ? (
<div className="row gx-3 gy-3">
{Array.from({ length: 6 }).map((_, index) => (
<SkeletonCard key={index} />
))}
</div>
) : (
<InfiniteScroll
dataLength={data.length}
next={fetchMoreData}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
endMessage={<p style={{ textAlign: "center" }}>No more media</p>}
className="overflow-hidden"
>
<MasonryGrid
gridRef={gridContainerRef}
onLayoutComplete={() => console.log("Layout complete!")}
>
{data.map((data, index) => {
let card;
switch (data.media_type) {
case "audio":
card = <AudioCard key={index} data={data} />;
break;
case "image":
card = <ImageCard key={index} data={data} />;
break;
case "video":
card = <VideoCard key={index} data={data} />;
break;
default:
card = <div key={index}>Unsupported media type</div>;
}
return (
<div
key={index}
className="col-12 col-sm-6 col-xl-4 grid-item"
>
{card}
</div>
);
})}
</MasonryGrid>
</InfiniteScroll>
)}
</AudioPlayerProvider>
</Panel>
);
@@ -94,6 +94,101 @@ let mockData = [
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token11",
weight_name: "Queen Elizabeth",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token12",
weight_name: "Monster House",
media_type: "image",
public_bucket_path: "/images/dummy-image.jpg",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token13",
weight_name: "Ronaldo",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token14",
weight_name: "Desert",
media_type: "video",
public_bucket_path: "/images/dummy-video.jpg",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token15",
weight_name: "3D Cat",
media_type: "image",
public_bucket_path: "/images/dummy-image-2.jpg",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token16",
weight_name: "Superman",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token17",
weight_name: "Son Goku",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token18",
weight_name: "Astronaut in Space",
media_type: "image",
public_bucket_path: "/images/dummy-image-3.jpg",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token19",
weight_name: "Monokuma",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token20",
weight_name: "Watery Landscape",
media_type: "image",
public_bucket_path: "/images/dummy-image-4.jpg",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token1",
weight_name: "Queen Elizabeth",
@@ -189,6 +284,101 @@ let mockData = [
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token11",
weight_name: "Queen Elizabeth",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token12",
weight_name: "Monster House",
media_type: "image",
public_bucket_path: "/images/dummy-image.jpg",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token13",
weight_name: "Ronaldo",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token14",
weight_name: "Desert",
media_type: "video",
public_bucket_path: "/images/dummy-video.jpg",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token15",
weight_name: "3D Cat",
media_type: "image",
public_bucket_path: "/images/dummy-image-2.jpg",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token16",
weight_name: "Superman",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 200,
isLiked: false,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token17",
weight_name: "Son Goku",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token18",
weight_name: "Astronaut in Space",
media_type: "image",
public_bucket_path: "/images/dummy-image-3.jpg",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token19",
weight_name: "Monokuma",
media_type: "audio",
public_bucket_path:
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
{
token: "token20",
weight_name: "Watery Landscape",
media_type: "image",
public_bucket_path: "/images/dummy-image-4.jpg",
likes: 150,
isLiked: true,
created_at: "2023-08-10T09:00:00.000Z",
},
];
export default mockData;
+49 -2
View File
@@ -4433,6 +4433,7 @@ __metadata:
"@types/gtag.js": ^0.0.12
"@types/howler": ^2.1.2
"@types/imagesloaded": ^4.1.6
"@types/infinite-scroll": ^4.0.3
"@types/jest": ^24.9.1
"@types/js-cookie": ^3.0.1
"@types/masonry-layout": ^4.2.7
@@ -4455,6 +4456,7 @@ __metadata:
i18next-browser-languagedetector: ^7.1.0
i18next-http-backend: ^2.2.1
imagesloaded: ^5.0.0
infinite-scroll: ^4.0.1
js-cookie: ^3.0.1
masonry-layout: ^4.2.2
posthog-js: ^1.73.1
@@ -5261,6 +5263,25 @@ __metadata:
languageName: node
linkType: hard
"@types/infinite-scroll@npm:^4.0.3":
version: 4.0.3
resolution: "@types/infinite-scroll@npm:4.0.3"
dependencies:
"@types/isotope-layout": "*"
"@types/jquery": "*"
"@types/masonry-layout": "*"
"@types/packery": "*"
checksum: c7c9a2db7eb6a26c646a63c36fb1b04b381b9bcbae1b5fbc97d36f05eea36d6b17749b52cb906be578087aa203bfce72541b175fd0529df7c71961a7e7608668
languageName: node
linkType: hard
"@types/isotope-layout@npm:*":
version: 3.0.12
resolution: "@types/isotope-layout@npm:3.0.12"
checksum: 7ae477fad4017a2e7ebbd0b5202e26d6febc58bc0604688d99d2ff822157d48eab77c8a89d942afc21ad1b0d51967491d07c9619d2e5722b626964c2c03e71b9
languageName: node
linkType: hard
"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1":
version: 2.0.4
resolution: "@types/istanbul-lib-coverage@npm:2.0.4"
@@ -5345,7 +5366,7 @@ __metadata:
languageName: node
linkType: hard
"@types/masonry-layout@npm:^4.2.7":
"@types/masonry-layout@npm:*, @types/masonry-layout@npm:^4.2.7":
version: 4.2.7
resolution: "@types/masonry-layout@npm:4.2.7"
dependencies:
@@ -5403,6 +5424,15 @@ __metadata:
languageName: node
linkType: hard
"@types/packery@npm:*":
version: 1.4.37
resolution: "@types/packery@npm:1.4.37"
dependencies:
"@types/jquery": "*"
checksum: 8350af4392e89988f4d8ec57827b47503be54365f5486c34ef36af3c0f48f1dc18332805eec7d04b06a6c22c2ef7db6cae03bc840058a9972d08fca21b0bc957
languageName: node
linkType: hard
"@types/parse-json@npm:^4.0.0":
version: 4.0.0
resolution: "@types/parse-json@npm:4.0.0"
@@ -10438,7 +10468,7 @@ __metadata:
languageName: node
linkType: hard
"ev-emitter@npm:^2.1.2":
"ev-emitter@npm:^2.1.0, ev-emitter@npm:^2.1.2":
version: 2.1.2
resolution: "ev-emitter@npm:2.1.2"
checksum: 1102823ef040684869256100e19a6c6aeeecd422d09a267d7c74e77023136eede2ee86ee475ad1019d30d1f7917557d74542d5665e9f91c398bd5814260af6b3
@@ -10917,6 +10947,13 @@ __metadata:
languageName: node
linkType: hard
"fizzy-ui-utils@npm:^3.0.0":
version: 3.0.0
resolution: "fizzy-ui-utils@npm:3.0.0"
checksum: 73b53e5dc89e033bad3eb9cf111d220502afae083c84fbc842d9453202673284de270f7bbe6cc77a806678e62cf8068da1b4280f0d3a1bf1310110248dfa96cd
languageName: node
linkType: hard
"flat-cache@npm:^3.0.4":
version: 3.0.4
resolution: "flat-cache@npm:3.0.4"
@@ -12275,6 +12312,16 @@ __metadata:
languageName: node
linkType: hard
"infinite-scroll@npm:^4.0.1":
version: 4.0.1
resolution: "infinite-scroll@npm:4.0.1"
dependencies:
ev-emitter: ^2.1.0
fizzy-ui-utils: ^3.0.0
checksum: e6000c0b82eace9df15ffb007447ecd12b806df60a41c6a06cdc0f931ae419e360be1e713f02e1047e088c75e0060198b25d19de6c3f88bd5fe0f987e326050a
languageName: node
linkType: hard
"inflight@npm:^1.0.4":
version: 1.0.6
resolution: "inflight@npm:1.0.6"