UPDATE: Extract category thumbnail handling to separate function

This commit is contained in:
Chris-1010
2025-02-27 14:01:37 +00:00
parent cba900f8e0
commit ff422f06f7
8 changed files with 58 additions and 45 deletions

View File

@@ -4,6 +4,7 @@ import { useAuth } from "../context/AuthContext";
import { StreamType } from "../types/StreamType"; import { StreamType } from "../types/StreamType";
import { CategoryType } from "../types/CategoryType"; import { CategoryType } from "../types/CategoryType";
import { UserType } from "../types/UserType"; import { UserType } from "../types/UserType";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
// Helper function to process API data into our consistent types // Helper function to process API data into our consistent types
const processStreamData = (data: any[]): StreamType[] => { const processStreamData = (data: any[]): StreamType[] => {
@@ -14,12 +15,8 @@ const processStreamData = (data: any[]): StreamType[] => {
username: stream.username, username: stream.username,
streamCategory: stream.category_name, streamCategory: stream.category_name,
viewers: stream.num_viewers, viewers: stream.num_viewers,
thumbnail: thumbnail: getCategoryThumbnail(stream.category_name, stream.thumbnail),
stream.thumbnail || }))
`/images/category_thumbnails/${stream.category_name
.toLowerCase()
.replace(/ /g, "_")}.webp`,
}));
}; };
const processCategoryData = (data: any[]): CategoryType[] => { const processCategoryData = (data: any[]): CategoryType[] => {
@@ -28,9 +25,7 @@ const processCategoryData = (data: any[]): CategoryType[] => {
id: category.category_id, id: category.category_id,
title: category.category_name, title: category.category_name,
viewers: category.num_viewers, viewers: category.num_viewers,
thumbnail: `/images/category_thumbnails/${category.category_name thumbnail: getCategoryThumbnail(category.category_name)
.toLowerCase()
.replace(/ /g, "_")}.webp`,
})); }));
}; };

View File

@@ -5,6 +5,7 @@ import DynamicPageContent from "../components/Layout/DynamicPageContent";
import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll"; import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll";
import LoadingScreen from "../components/Layout/LoadingScreen"; import LoadingScreen from "../components/Layout/LoadingScreen";
import { CategoryType } from "../types/CategoryType"; import { CategoryType } from "../types/CategoryType";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
const AllCategoriesPage: React.FC = () => { const AllCategoriesPage: React.FC = () => {
const [categories, setCategories] = useState<CategoryType[]>([]); const [categories, setCategories] = useState<CategoryType[]>([]);
@@ -41,9 +42,7 @@ const AllCategoriesPage: React.FC = () => {
id: category.category_id, id: category.category_id,
title: category.category_name, title: category.category_name,
viewers: category.num_viewers, viewers: category.num_viewers,
thumbnail: `/images/category_thumbnails/${category.category_name thumbnail: getCategoryThumbnail(category.category_name)
.toLowerCase()
.replace(/ /g, "_")}.webp`,
})); }));
setCategories(prev => [...prev, ...processedCategories]); setCategories(prev => [...prev, ...processedCategories]);

View File

@@ -8,6 +8,7 @@ import { useAuth } from "../context/AuthContext";
import { useCategoryFollow } from "../hooks/useCategoryFollow"; import { useCategoryFollow } from "../hooks/useCategoryFollow";
import LoadingScreen from "../components/Layout/LoadingScreen"; import LoadingScreen from "../components/Layout/LoadingScreen";
import { StreamType } from "../types/StreamType"; import { StreamType } from "../types/StreamType";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
const CategoryPage: React.FC = () => { const CategoryPage: React.FC = () => {
const { categoryName } = useParams<{ categoryName: string }>(); const { categoryName } = useParams<{ categoryName: string }>();
@@ -57,12 +58,7 @@ const CategoryPage: React.FC = () => {
username: stream.username, username: stream.username,
streamCategory: categoryName, streamCategory: categoryName,
viewers: stream.num_viewers, viewers: stream.num_viewers,
thumbnail: thumbnail: getCategoryThumbnail(categoryName, stream.thumbnail),
stream.thumbnail ||
(categoryName &&
`/images/category_thumbnails/${categoryName
.toLowerCase()
.replace(/ /g, "_")}.webp`),
})); }));
setStreams((prev) => [...prev, ...processedStreams]); setStreams((prev) => [...prev, ...processedStreams]);

View File

@@ -8,6 +8,7 @@ import Button from "../components/Input/Button";
import { useCategoryFollow } from "../hooks/useCategoryFollow"; import { useCategoryFollow } from "../hooks/useCategoryFollow";
import { ListItemProps as StreamData } from "../components/Layout/ListItem"; import { ListItemProps as StreamData } from "../components/Layout/ListItem";
import LoadingScreen from "../components/Layout/LoadingScreen"; import LoadingScreen from "../components/Layout/LoadingScreen";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
interface Category { interface Category {
isFollowing: any; isFollowing: any;
@@ -130,7 +131,7 @@ const FollowedCategories: React.FC<FollowedCategoryProps> = ({ extraClasses = ""
</Button> </Button>
<img <img
src={`/images/category_thumbnails/${category.category_name.toLowerCase().replace(/ /g, "_")}.webp`} src={getCategoryThumbnail(category.category_name)}
alt={category.category_name} alt={category.category_name}
className="w-full h-28 object-cover" className="w-full h-28 object-cover"
/> />

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import Button from "../components/Input/Button";
import SearchBar from "../components/Input/SearchBar"; import SearchBar from "../components/Input/SearchBar";
import ListRow from "../components/Layout/ListRow"; import ListRow from "../components/Layout/ListRow";
import DynamicPageContent from "../components/Layout/DynamicPageContent"; import DynamicPageContent from "../components/Layout/DynamicPageContent";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
const ResultsPage: React.FC = ({ }) => { const ResultsPage: React.FC = ({ }) => {
const [overflow, setOverflow] = useState(false); const [overflow, setOverflow] = useState(false);
@@ -65,9 +65,7 @@ const ResultsPage: React.FC = ({ }) => {
type: "category", type: "category",
title: category.category_name, title: category.category_name,
viewers: 0, viewers: 0,
thumbnail: `/images/category_thumbnails/${category.category_name thumbnail: getCategoryThumbnail(category.category_name),
.toLowerCase()
.replace(/ /g, "_")}.webp`,
}))} }))}
title="Categories" title="Categories"
onItemClick={(category_name: string) => onItemClick={(category_name: string) =>

View File

@@ -3,12 +3,17 @@ import DynamicPageContent from "../components/Layout/DynamicPageContent";
import Button from "../components/Input/Button"; import Button from "../components/Input/Button";
import Input from "../components/Input/Input"; import Input from "../components/Input/Input";
import { useCategories } from "../hooks/useContent"; import { useCategories } from "../hooks/useContent";
import { X as CloseIcon, Eye as ShowIcon, EyeOff as HideIcon } from "lucide-react"; import {
X as CloseIcon,
Eye as ShowIcon,
EyeOff as HideIcon,
} from "lucide-react";
import { useAuth } from "../context/AuthContext"; import { useAuth } from "../context/AuthContext";
import { debounce } from "lodash"; import { debounce } from "lodash";
import VideoPlayer from "../components/Stream/VideoPlayer"; import VideoPlayer from "../components/Stream/VideoPlayer";
import { CategoryType } from "../types/CategoryType"; import { CategoryType } from "../types/CategoryType";
import { StreamListItem } from "../components/Layout/ListItem"; import { StreamListItem } from "../components/Layout/ListItem";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
interface StreamData { interface StreamData {
title: string; title: string;
@@ -31,7 +36,9 @@ const StreamDashboardPage: React.FC = () => {
const [streamDetected, setStreamDetected] = useState(false); const [streamDetected, setStreamDetected] = useState(false);
const [timeStarted, setTimeStarted] = useState(""); const [timeStarted, setTimeStarted] = useState("");
const [isCategoryFocused, setIsCategoryFocused] = useState(false); const [isCategoryFocused, setIsCategoryFocused] = useState(false);
const [filteredCategories, setFilteredCategories] = useState<CategoryType[]>([]); const [filteredCategories, setFilteredCategories] = useState<CategoryType[]>(
[]
);
const [thumbnail, setThumbnail] = useState<File | null>(null); const [thumbnail, setThumbnail] = useState<File | null>(null);
const [thumbnailPreview, setThumbnailPreview] = useState<{ const [thumbnailPreview, setThumbnailPreview] = useState<{
url: string; url: string;
@@ -40,10 +47,10 @@ const StreamDashboardPage: React.FC = () => {
const [debouncedCheck, setDebouncedCheck] = useState<Function | null>(null); const [debouncedCheck, setDebouncedCheck] = useState<Function | null>(null);
const [showKey, setShowKey] = useState(false); const [showKey, setShowKey] = useState(false);
const { const {
categories, categories,
isLoading: categoriesLoading, isLoading: categoriesLoading,
error: categoriesError error: categoriesError,
} = useCategories("/api/categories/popular/100"); } = useCategories("/api/categories/popular/100");
useEffect(() => { useEffect(() => {
@@ -60,9 +67,7 @@ const StreamDashboardPage: React.FC = () => {
); );
if (isValidCategory && !thumbnailPreview.isCustom) { if (isValidCategory && !thumbnailPreview.isCustom) {
const defaultThumbnail = `/images/thumbnails/categories/${categoryName const defaultThumbnail = getCategoryThumbnail(categoryName);
.toLowerCase()
.replace(/ /g, "_")}.webp`;
setThumbnailPreview({ url: defaultThumbnail, isCustom: false }); setThumbnailPreview({ url: defaultThumbnail, isCustom: false });
} }
}, 300); }, 300);
@@ -82,7 +87,7 @@ const StreamDashboardPage: React.FC = () => {
const checkStreamStatus = async () => { const checkStreamStatus = async () => {
if (!username) return; if (!username) return;
try { try {
const response = await fetch(`/api/user/${username}/status`); const response = await fetch(`/api/user/${username}/status`);
const data = await response.json(); const data = await response.json();
@@ -179,9 +184,7 @@ const StreamDashboardPage: React.FC = () => {
console.log( console.log(
"Clearing thumbnail as category is set and default category thumbnail will be used" "Clearing thumbnail as category is set and default category thumbnail will be used"
); );
const defaultThumbnail = `/images/thumbnails/categories/${streamData.category_name const defaultThumbnail = getCategoryThumbnail(streamData.category_name);
.toLowerCase()
.replace(/ /g, "_")}.webp`;
setThumbnailPreview({ url: defaultThumbnail, isCustom: false }); setThumbnailPreview({ url: defaultThumbnail, isCustom: false });
} else { } else {
setThumbnailPreview({ url: "", isCustom: false }); setThumbnailPreview({ url: "", isCustom: false });
@@ -194,8 +197,7 @@ const StreamDashboardPage: React.FC = () => {
streamData.category_name.trim() !== "" && streamData.category_name.trim() !== "" &&
categories.some( categories.some(
(cat) => (cat) =>
cat.title.toLowerCase() === cat.title.toLowerCase() === streamData.category_name.toLowerCase()
streamData.category_name.toLowerCase()
) && ) &&
streamDetected streamDetected
); );
@@ -328,9 +330,7 @@ const StreamDashboardPage: React.FC = () => {
<div <div
key={category.title} key={category.title}
className="px-4 py-2 hover:bg-gray-600 cursor-pointer text-white" className="px-4 py-2 hover:bg-gray-600 cursor-pointer text-white"
onClick={() => onClick={() => handleCategorySelect(category.title)}
handleCategorySelect(category.title)
}
> >
{category.title} {category.title}
</div> </div>

View File

@@ -10,6 +10,7 @@ import DynamicPageContent from "../components/Layout/DynamicPageContent";
import LoadingScreen from "../components/Layout/LoadingScreen"; import LoadingScreen from "../components/Layout/LoadingScreen";
import { StreamListItem } from "../components/Layout/ListItem"; import { StreamListItem } from "../components/Layout/ListItem";
import { CameraIcon } from "lucide-react"; import { CameraIcon } from "lucide-react";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
interface UserProfileData { interface UserProfileData {
id: number; id: number;
@@ -78,11 +79,10 @@ const UserPage: React.FC = () => {
currentStreamCategory: streamData.category_id, currentStreamCategory: streamData.category_id,
currentStreamViewers: streamData.num_viewers, currentStreamViewers: streamData.num_viewers,
currentStreamStartTime: streamData.start_time, currentStreamStartTime: streamData.start_time,
currentStreamThumbnail: currentStreamThumbnail: getCategoryThumbnail(
streamData.thumbnail || streamData.category_name,
`/images/category_thumbnails/${streamData.category_name streamData.thumbnail
.toLowerCase() ),
.replace(/ /g, "_")}.webp`,
}; };
}); });
let variant: "user" | "streamer" | "personal" | "admin"; let variant: "user" | "streamer" | "personal" | "admin";

View File

@@ -0,0 +1,24 @@
/**
* Generates a thumbnail path for a given category name
*
* @param categoryName - The name of the category
* @param customThumbnail - Optional custom thumbnail path that takes precedence if provided
* @returns The path to the category thumbnail image
*/
export function getCategoryThumbnail(categoryName?: string, customThumbnail?: string): string {
if (customThumbnail) {
return customThumbnail;
}
if (!categoryName) {
return '/images/category_thumbnails/default.webp';
}
// Convert to lowercase, replace spaces with underscores, and remove all other special characters
const formattedName = categoryName
.toLowerCase()
.replace(/ /g, '_') // Replace spaces with underscores
.replace(/[^a-z0-9_]/g, ''); // Remove all other non-alphanumeric characters except underscores
return `/images/category_thumbnails/${formattedName}.webp`;
}