diff --git a/src/components/resources/FavoritesTab.tsx b/src/components/resources/FavoritesTab.tsx index b921218..a4695d6 100644 --- a/src/components/resources/FavoritesTab.tsx +++ b/src/components/resources/FavoritesTab.tsx @@ -1,14 +1,18 @@ import { motion } from 'framer-motion'; import { useHeartedResources } from '@/hooks/useHeartedResources'; import { useResources } from '@/hooks/useResources'; -import { getResourceUrl } from '@/types/resources'; +import { getResourceUrl, Resource } from '@/types/resources'; import ResourceCard from './ResourceCard'; import ResourceCardSkeleton from './ResourceCardSkeleton'; import { IconHeart } from '@tabler/icons-react'; -const FavoritesTab = () => { +interface FavoritesTabProps { + onSelectResource: (resource: Resource) => void; +} + +const FavoritesTab = ({ onSelectResource }: FavoritesTabProps) => { const { heartedResources, isLoading: favoritesLoading } = useHeartedResources(); - const { resources, isLoading: resourcesLoading, setSelectedResource } = useResources(); + const { resources, isLoading: resourcesLoading } = useResources(); const isLoading = favoritesLoading || resourcesLoading; @@ -51,7 +55,7 @@ const FavoritesTab = () => { ); })} diff --git a/src/components/resources/McSoundsBrowser.tsx b/src/components/resources/McSoundsBrowser.tsx new file mode 100644 index 0000000..b223c6e --- /dev/null +++ b/src/components/resources/McSoundsBrowser.tsx @@ -0,0 +1,403 @@ +import { useState, useMemo, useCallback, useEffect } from 'react'; +import type { MouseEvent } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { IconChevronRight, IconChevronDown, IconFolder, IconFolderOpen, IconSearch, IconVolume, IconX } from '@tabler/icons-react'; + +interface McSoundsBrowserProps { + subcategories: string[]; + selectedSubcategory: string | null; + onSubcategoryChange: (subcategory: string | null) => void; + resourceCount?: Record; +} + +interface CategoryNode { + name: string; + fullPath: string; + children: CategoryNode[]; + isLeaf: boolean; + count?: number; +} + +const formatCategoryName = (name: string): string => { + return name + .replace(/_/g, ' ') + .replace(/\b\w/g, l => l.toUpperCase()); +}; + +const buildCategoryTree = (subcategories: string[], resourceCount?: Record): CategoryNode[] => { + const root: CategoryNode = { name: '', fullPath: '', children: [], isLeaf: false }; + + subcategories.forEach(sub => { + const parts = sub.split('/'); + let current = root; + let path = ''; + + parts.forEach((part, index) => { + path = path ? `${path}/${part}` : part; + let child = current.children.find(c => c.name === part); + + if (!child) { + child = { + name: part, + fullPath: path, + children: [], + isLeaf: index === parts.length - 1, + count: index === parts.length - 1 ? resourceCount?.[sub] : undefined, + }; + current.children.push(child); + } + + current = child; + }); + }); + + const sortChildren = (node: CategoryNode) => { + node.children.sort((a, b) => { + if (a.isLeaf !== b.isLeaf) return a.isLeaf ? 1 : -1; + return a.name.localeCompare(b.name); + }); + node.children.forEach(sortChildren); + }; + + root.children.forEach(sortChildren); + + return root.children; +}; + +const CategoryItem = ({ + node, + level, + selectedPath, + expandedPaths, + onToggle, + onSelect, +}: { + node: CategoryNode; + level: number; + selectedPath: string | null; + expandedPaths: Set; + onToggle: (path: string) => void; + onSelect: (path: string | null) => void; +}) => { + const hasChildren = node.children.length > 0; + const isExpanded = expandedPaths.has(node.fullPath); + const isSelected = selectedPath === node.fullPath; + const paddingLeft = level * 16 + 8; + + const handleClick = useCallback(() => { + if (hasChildren) { + onToggle(node.fullPath); + } else { + onSelect(node.fullPath); + } + }, [hasChildren, node.fullPath, onToggle, onSelect]); + + const handleSelectClick = useCallback((e: MouseEvent) => { + e.stopPropagation(); + onSelect(node.fullPath); + }, [node.fullPath, onSelect]); + + return ( +
+ + {hasChildren ? ( + + + + ) : ( +
+ )} + +
+ {hasChildren ? ( + isExpanded ? ( + + ) : ( + + ) + ) : ( + + )} +
+ + {formatCategoryName(node.name)} + + {node.count !== undefined && ( + + {node.count} + + )} + + {hasChildren && ( + + )} + + + + {hasChildren && isExpanded && ( + + {node.children.map(child => ( + + ))} + + )} + +
+ ); +}; + +const McSoundsBrowser = ({ + subcategories, + selectedSubcategory, + onSubcategoryChange, + resourceCount, +}: McSoundsBrowserProps) => { + const [searchQuery, setSearchQuery] = useState(''); + const [expandedPaths, setExpandedPaths] = useState>(() => { + if (selectedSubcategory) { + const parts = selectedSubcategory.split('/'); + const paths = new Set(); + let path = ''; + parts.slice(0, -1).forEach(part => { + path = path ? `${path}/${part}` : part; + paths.add(path); + }); + return paths; + } + return new Set(); + }); + + useEffect(() => { + if (selectedSubcategory) { + const parts = selectedSubcategory.split('/'); + const paths: string[] = []; + let path = ''; + parts.slice(0, -1).forEach(part => { + path = path ? `${path}/${part}` : part; + paths.push(path); + }); + if (paths.length > 0) { + setExpandedPaths(prev => new Set([...prev, ...paths])); + } + } + }, [selectedSubcategory]); + + const categoryTree = useMemo(() => + buildCategoryTree(subcategories, resourceCount), + [subcategories, resourceCount] + ); + + const filteredTree = useMemo(() => { + if (!searchQuery.trim()) return categoryTree; + + const query = searchQuery.toLowerCase(); + + const filterNodes = (nodes: CategoryNode[]): CategoryNode[] => { + return nodes.reduce((acc, node) => { + const nameMatches = node.name.toLowerCase().includes(query); + const filteredChildren = filterNodes(node.children); + + if (nameMatches || filteredChildren.length > 0) { + acc.push({ + ...node, + children: nameMatches ? node.children : filteredChildren, + }); + } + + return acc; + }, []); + }; + + return filterNodes(categoryTree); + }, [categoryTree, searchQuery]); + + const handleToggle = useCallback((path: string) => { + setExpandedPaths(prev => { + const next = new Set(prev); + if (next.has(path)) { + next.delete(path); + } else { + next.add(path); + } + return next; + }); + }, []); + + const handleSelect = useCallback((path: string | null) => { + onSubcategoryChange(path); + }, [onSubcategoryChange]); + + const handleClearSelection = useCallback(() => { + onSubcategoryChange(null); + }, [onSubcategoryChange]); + + const expandAll = useCallback(() => { + const getAllPaths = (nodes: CategoryNode[]): string[] => { + return nodes.flatMap(node => + node.children.length > 0 + ? [node.fullPath, ...getAllPaths(node.children)] + : [] + ); + }; + setExpandedPaths(new Set(getAllPaths(categoryTree))); + }, [categoryTree]); + + const collapseAll = useCallback(() => { + setExpandedPaths(new Set()); + }, []); + + const totalResources = useMemo(() => { + return subcategories.reduce((sum, sub) => sum + (resourceCount?.[sub] || 0), 0); + }, [subcategories, resourceCount]); + + return ( +
+
+

+ + MC Sounds Browser +

+ +
+ + setSearchQuery(e.target.value)} + className="pl-8 h-8 text-sm pixel-input" + /> + {searchQuery && ( + + )} +
+ +
+ + +
+
+ + {selectedSubcategory && ( +
+ + Selected: {formatCategoryName(selectedSubcategory.split('/').pop() || '')} + + +
+ )} + + +
+ handleSelect(null)} + whileHover={{ x: 2 }} + > +
+ + All Sounds + + {subcategories.length} categories + + + + {filteredTree.map(node => ( + + ))} + + {filteredTree.length === 0 && searchQuery && ( +
+ No categories match your search +
+ )} +
+ +
+ ); +}; + +export default McSoundsBrowser; diff --git a/src/components/resources/ResourceCard.tsx b/src/components/resources/ResourceCard.tsx index 558806f..0e6156a 100644 --- a/src/components/resources/ResourceCard.tsx +++ b/src/components/resources/ResourceCard.tsx @@ -110,6 +110,8 @@ const ResourceCard = ({ resource, onClick }: ResourceCardProps) => { return ; case "minecraft-icons": return ; + case "mcsounds": + return ; default: return ; } @@ -131,6 +133,8 @@ const ResourceCard = ({ resource, onClick }: ResourceCardProps) => { return "bg-gray-500/10 text-gray-500"; case "minecraft-icons": return "bg-green-500/10 text-green-600"; + case "mcsounds": + return "bg-teal-500/10 text-teal-500"; default: return "bg-gray-500/10 text-gray-500"; } @@ -216,6 +220,19 @@ const ResourceCard = ({ resource, onClick }: ResourceCardProps) => { />
); + case "mcsounds": + return ( +
+ +
+ ); case "animations": return (isHovered && hoverToPlayEnabled) ? (
; case 'presets': return ; + case 'mcsounds': + return ; default: return ; } @@ -129,6 +131,8 @@ const ResourceDetailDialog = ({ return 'bg-green-500/10 text-green-500'; case 'presets': return 'bg-gray-500/10 text-gray-500'; + case 'mcsounds': + return 'bg-teal-500/10 text-teal-500'; default: return 'bg-gray-500/10 text-gray-500'; } @@ -231,8 +235,8 @@ const ResourceDetailDialog = ({ - {!isFavoritesView && ( -
+
+ {!isFavoritesView && ( + )} - + + {!isFavoritesView && ( -
- )} + )} +

By downloading, you agree to our terms of use. Crediting diff --git a/src/components/resources/ResourceFilters.tsx b/src/components/resources/ResourceFilters.tsx index 4979efe..9f8122a 100644 --- a/src/components/resources/ResourceFilters.tsx +++ b/src/components/resources/ResourceFilters.tsx @@ -73,6 +73,27 @@ const groupMciconsSubcategories = (subcategories: string[]): { parent: string; l return result; }; +const groupMcsoundsSubcategories = (subcategories: string[]): { parent: string; label: string; value: string }[] => { + const result: { parent: string; label: string; value: string }[] = []; + + const formatLabel = (sub: string): string => { + return sub.split('/').map(part => + part.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) + ).join(' > '); + }; + + subcategories.sort().forEach(sub => { + const parts = sub.split('/'); + result.push({ + parent: parts.length > 1 ? parts[0].replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : '', + label: formatLabel(sub), + value: sub, + }); + }); + + return result; +}; + const ResourceFilters = ({ searchQuery, selectedCategory, @@ -153,11 +174,16 @@ const MobileFilters = ({ onCategoryChange: (category: string | null) => void; onSubcategoryChange: (subcategory: string | null) => void; }) => { - const groupedSubcategories = useMemo(() => + const groupedMciconsSubcategories = useMemo(() => selectedCategory === 'minecraft-icons' ? groupMciconsSubcategories(availableSubcategories) : [], [availableSubcategories, selectedCategory] ); + const groupedMcsoundsSubcategories = useMemo(() => + selectedCategory === 'mcsounds' ? groupMcsoundsSubcategories(availableSubcategories) : [], + [availableSubcategories, selectedCategory] + ); + return ( @@ -235,6 +261,13 @@ const MobileFilters = ({ MCI { e.currentTarget.style.display = 'none' }} /> Minecraft Icons + {selectedCategory === 'presets' && ( @@ -255,7 +288,7 @@ const MobileFilters = ({

)} - {selectedCategory === 'minecraft-icons' && groupedSubcategories.length > 0 && ( + {selectedCategory === 'minecraft-icons' && groupedMciconsSubcategories.length > 0 && (
)} + + {selectedCategory === 'mcsounds' && groupedMcsoundsSubcategories.length > 0 && ( +
+ +
+ )}
@@ -295,11 +349,16 @@ const DesktopFilters = ({ onCategoryChange: (category: string | null) => void; onSubcategoryChange: (subcategory: string | null) => void; }) => { - const groupedSubcategories = useMemo(() => + const groupedMciconsSubcategories = useMemo(() => selectedCategory === 'minecraft-icons' ? groupMciconsSubcategories(availableSubcategories) : [], [availableSubcategories, selectedCategory] ); + const groupedMcsoundsSubcategories = useMemo(() => + selectedCategory === 'mcsounds' ? groupMcsoundsSubcategories(availableSubcategories) : [], + [availableSubcategories, selectedCategory] + ); + return (
+ @@ -385,7 +452,7 @@ const DesktopFilters = ({ )} - {selectedCategory === 'minecraft-icons' && groupedSubcategories.length > 0 && ( + {selectedCategory === 'minecraft-icons' && groupedMciconsSubcategories.length > 0 && ( )} + + {selectedCategory === 'mcsounds' && groupedMcsoundsSubcategories.length > 0 && ( + + )}
); }; diff --git a/src/components/resources/ResourcePreview.tsx b/src/components/resources/ResourcePreview.tsx index 82acde1..9075ebe 100644 --- a/src/components/resources/ResourcePreview.tsx +++ b/src/components/resources/ResourcePreview.tsx @@ -52,7 +52,7 @@ const ResourcePreview = ({ resource }: ResourcePreviewProps) => { return

No preview available

; } - if (resource.category === 'music' || resource.category === 'sfx') { + if (resource.category === 'music' || resource.category === 'sfx' || resource.category === 'mcsounds') { return ; } diff --git a/src/hooks/useResources.ts b/src/hooks/useResources.ts index 1d5de09..3d90a79 100644 --- a/src/hooks/useResources.ts +++ b/src/hooks/useResources.ts @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from "react"; +import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { Resource } from "@/types/resources"; import { useDownloadCounts } from "@/hooks/useDownloadCounts"; import { @@ -32,34 +32,47 @@ export const useResources = () => { const [lastAction, setLastAction] = useState(""); const [loadedFonts, setLoadedFonts] = useState([]); const [availableCategories, setAvailableCategories] = useState([]); + const [refreshKey, setRefreshKey] = useState(0); + const fetchIdRef = useRef(0); - const fetchResources = useCallback(async () => { - try { + useEffect(() => { + const currentFetchId = ++fetchIdRef.current; + + const loadResources = async () => { setIsLoading(true); + setResources([]); - const categories = getAvailableCategories(); - if (categories.length > 0) { - setAvailableCategories(categories); - } + try { + const categories = getAvailableCategories(); + if (categories.length > 0) { + setAvailableCategories(categories); + } - if (selectedCategory === null || selectedCategory === "favorites") { - const all = await fetchAllResources(); - setResources(all); - return; - } + let data: Resource[]; + + if (selectedCategory === null || selectedCategory === "favorites") { + data = await fetchAllResources(); + } else { + data = await fetchCategory(selectedCategory as string); + } - const categoryItems = await fetchCategory(selectedCategory as string); - setResources(categoryItems); - } catch (error) { - console.error("Error fetching resources:", error); - } finally { - setIsLoading(false); - } - }, [selectedCategory]); + if (currentFetchId === fetchIdRef.current) { + setResources(data); + } + } catch (error) { + console.error("Error fetching resources:", error); + if (currentFetchId === fetchIdRef.current) { + setResources([]); + } + } finally { + if (currentFetchId === fetchIdRef.current) { + setIsLoading(false); + } + } + }; - useEffect(() => { - fetchResources(); - }, [fetchResources]); + loadResources(); + }, [selectedCategory, refreshKey]); const handleSearchSubmit = useCallback((e?: React.FormEvent) => { e?.preventDefault(); @@ -75,6 +88,7 @@ export const useResources = () => { const handleCategoryChange = useCallback( (category: Category | null | "favorites") => { + setIsLoading(true); setSelectedCategory(category); setSelectedSubcategory(null); setLastAction("category"); @@ -140,7 +154,7 @@ export const useResources = () => { if (selectedCategory && selectedCategory !== "favorites") { result = result.filter((r) => r.category === selectedCategory); } else if (selectedCategory === null) { - result = result.filter((r) => r.category !== "minecraft-icons"); + result = result.filter((r) => r.category !== "minecraft-icons" && r.category !== "mcsounds"); } if (selectedCategory === "minecraft-icons") { @@ -150,7 +164,12 @@ export const useResources = () => { } if (selectedSubcategory && selectedSubcategory !== "all") { - if (availableSubcategories.includes(selectedSubcategory)) { + if (selectedCategory === "mcsounds") { + result = result.filter((r) => + r.subcategory === selectedSubcategory || + r.subcategory?.startsWith(selectedSubcategory + "/") + ); + } else if (availableSubcategories.includes(selectedSubcategory)) { result = result.filter((r) => r.subcategory === selectedSubcategory); } } @@ -275,6 +294,10 @@ export const useResources = () => { loadWaveform, loadCachedAudio, loadCachedImage, - refreshResources: fetchResources, + refreshResources: () => { + fetchIdRef.current++; + setRefreshKey(prev => prev + 1); + setSelectedCategory(null); + }, }; }; diff --git a/src/lib/api.ts b/src/lib/api.ts index 7c0abf3..f99da77 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,4 +1,4 @@ -import { readCache, writeCache } from "@/lib/cache"; +import { readCache, writeCache, clearCache } from "@/lib/cache"; import { Resource } from "@/types/resources"; const API_BASE = "https://hamburger-api.powernplant101-c6b.workers.dev"; @@ -36,21 +36,27 @@ const fetchJson = async (url: string): Promise => { Accept: "application/json", }, }); - if (!res.ok) return null; + if (!res.ok) { + console.error(`API error: ${res.status} ${res.statusText} for ${url}`); + return null; + } return (await res.json()) as T; - } catch { + } catch (error) { + console.error(`Fetch error for ${url}:`, error); return null; } }; const normalizeCategory = (category: string): Resource["category"] => { if (category === "mcicons") return "minecraft-icons"; + if (category === "mcsounds") return "mcsounds"; if (category === "resources") return "images"; return category as Resource["category"]; }; const toApiCategory = (category: string): string => { if (category === "minecraft-icons") return "mcicons"; + if (category === "mcsounds") return "mcsounds"; if (category === "images") return "images"; return category; }; @@ -102,7 +108,7 @@ export const fetchCategories = async (): Promise => { export const fetchCategory = async (category: string): Promise => { const cacheKey = `${CATEGORY_CACHE_PREFIX}${category}`; const cached = readCache(cacheKey); - if (cached) { + if (cached && cached.length > 0) { return cached.map(item => normalizeApiResource(item, category)); } @@ -110,28 +116,60 @@ export const fetchCategory = async (category: string): Promise => { const data = await fetchJson<{ category: string; files: ApiResource[] }>( `${API_BASE}/category/${apiCategory}` ); - if (data?.files) { - writeCache(cacheKey, data.files); + if (data?.files && data.files.length > 0) { + try { + writeCache(cacheKey, data.files); + } catch (e) { + console.warn(`Failed to cache category ${category}:`, e); + } return data.files.map(item => normalizeApiResource(item, category)); } + console.error(`Failed to fetch category ${category} from API`); return []; }; export const fetchAllResources = async (): Promise => { const cached = readCache(ALL_RESOURCES_CACHE_KEY); - if (cached) { + if (cached && cached.categories && Object.keys(cached.categories).length > 0) { + const categoryNames = Object.keys(cached.categories); + const existingCategoriesCache = readCache(CATEGORIES_CACHE_KEY); + if (!existingCategoriesCache) { + writeCache(CATEGORIES_CACHE_KEY, { + categories: categoryNames, + total: categoryNames.length + }); + } return Object.entries(cached.categories).flatMap(([category, items]) => items.map(item => normalizeApiResource(item, category)) ); } const data = await fetchJson(`${API_BASE}/all`); - if (data?.categories) { - writeCache(ALL_RESOURCES_CACHE_KEY, data); + if (data?.categories && Object.keys(data.categories).length > 0) { + try { + writeCache(ALL_RESOURCES_CACHE_KEY, data); + const categoryNames = Object.keys(data.categories); + writeCache(CATEGORIES_CACHE_KEY, { + categories: categoryNames, + total: categoryNames.length + }); + Object.entries(data.categories).forEach(([category, items]) => { + if (items && items.length > 0) { + try { + writeCache(`${CATEGORY_CACHE_PREFIX}${normalizeCategory(category)}`, items); + } catch { + // Ignore quota errors for individual category caches + } + } + }); + } catch (e) { + console.warn("Failed to cache all resources (likely quota exceeded):", e); + } return Object.entries(data.categories).flatMap(([category, items]) => items.map(item => normalizeApiResource(item, category)) ); } + console.error("Failed to fetch all resources from API"); return []; }; @@ -162,3 +200,16 @@ export const getAvailableCategories = (): string[] => { const cached = readCache(CATEGORIES_CACHE_KEY); return cached?.categories || []; }; + +export const clearResourceCache = (): void => { + clearCache(ALL_RESOURCES_CACHE_KEY); + clearCache(CATEGORIES_CACHE_KEY); + clearCache(`${CATEGORY_CACHE_PREFIX}mcsounds`); + clearCache(`${CATEGORY_CACHE_PREFIX}minecraft-icons`); + clearCache(`${CATEGORY_CACHE_PREFIX}music`); + clearCache(`${CATEGORY_CACHE_PREFIX}sfx`); + clearCache(`${CATEGORY_CACHE_PREFIX}images`); + clearCache(`${CATEGORY_CACHE_PREFIX}animations`); + clearCache(`${CATEGORY_CACHE_PREFIX}fonts`); + clearCache(`${CATEGORY_CACHE_PREFIX}presets`); +}; diff --git a/src/pages/ResourcesHub.tsx b/src/pages/ResourcesHub.tsx index f4652c6..52ef3ff 100644 --- a/src/pages/ResourcesHub.tsx +++ b/src/pages/ResourcesHub.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect, useState, lazy, Suspense } from 'react'; +import { useRef, useEffect, useState, lazy, Suspense, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import Navbar from '@/components/Navbar'; import Footer from '@/components/Footer'; @@ -10,6 +10,7 @@ import ResourceFilters from '@/components/resources/ResourceFilters'; import SortSelector from '@/components/resources/SortSelector'; import ResourcesList from '@/components/resources/ResourcesList'; import FavoritesTab from '@/components/resources/FavoritesTab'; +import McSoundsBrowser from '@/components/resources/McSoundsBrowser'; import AuthDialog from '@/components/auth/AuthDialog'; import { Button } from '@/components/ui/button'; import { IconArrowUp, IconHeart, IconSearch } from '@tabler/icons-react'; @@ -58,6 +59,19 @@ const ResourcesHub = () => { const inputRef = useRef(null); const isMobile = useIsMobile(); + const isMcSoundsView = selectedCategory === 'mcsounds'; + + const mcsoundsResourceCount = useMemo(() => { + if (!isMcSoundsView) return {}; + const countMap: Record = {}; + resources.forEach(r => { + if (r.subcategory) { + countMap[r.subcategory] = (countMap[r.subcategory] || 0) + 1; + } + }); + return countMap; + }, [resources, isMcSoundsView]); + useEffect(() => { const handleScroll = () => { const scrolled = window.pageYOffset > 400; @@ -77,7 +91,6 @@ const ResourcesHub = () => { }; }, []); - // Check URL params for favorites tab useEffect(() => { const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('tab') === 'favorites') { @@ -104,6 +117,45 @@ const ResourcesHub = () => { } }; + const renderContent = () => ( + <> + + + {(selectedCategory === 'minecraft-icons' || selectedCategory === 'mcsounds') && ( +

+ Powered by Hamburger API +

+ )} + + + + ); + return (
@@ -121,102 +173,86 @@ const ResourcesHub = () => {
-
- -

Resources Hub

-

Discover and download a wide range of resources to enhance your RenderDragon experience.

-
- - + +

Resources Hub

+

Discover and download a wide range of resources to enhance your RenderDragon experience.

+
- -
- + +
+ + {showFavorites ? ( + - - Resources - - -
- - {showFavorites ? ( - - - - ) : ( - - - - {selectedCategory === 'minecraft-icons' && ( -

- Powered by Hamburger API -

- )} - - -
- )} -
- -
+ {isMcSoundsView && !isMobile ? ( +
+
+ {renderContent()} +
+
+
+ +
+
+
+ ) : ( +
+ {renderContent()} +
+ )} + + )} + +
diff --git a/src/types/resources.ts b/src/types/resources.ts index f6bdd7b..dcb45cf 100644 --- a/src/types/resources.ts +++ b/src/types/resources.ts @@ -3,7 +3,7 @@ export interface Resource { id: number | string; title: string; - category: 'music' | 'sfx' | 'images' | 'animations' | 'fonts' | 'presets' | 'minecraft-icons'; + category: 'music' | 'sfx' | 'images' | 'animations' | 'fonts' | 'presets' | 'minecraft-icons' | 'mcsounds'; subcategory?: string; credit?: string; filetype?: string;