feat: use new db site, cache requests to avoid repeated unneeded fetch calls

This commit is contained in:
2026-06-18 23:20:12 -03:00
parent 9f5b8cd1fa
commit 6a064d295e
8 changed files with 64 additions and 35 deletions
+3 -5
View File
@@ -4,6 +4,7 @@ import { useState, useEffect, useMemo } from 'react';
import { useInventoryStore } from '@/store/useInventoryStore'; import { useInventoryStore } from '@/store/useInventoryStore';
import { isKiller } from '../../lib/utils'; import { isKiller } from '../../lib/utils';
import { DB_BASE_URL, fetchCharacters } from '../../lib/db';
import shared from '../../styles/shared.module.css'; import shared from '../../styles/shared.module.css';
import styles from '../../styles/Characters.module.css'; import styles from '../../styles/Characters.module.css';
@@ -16,7 +17,7 @@ type Character = {
const getIconUrl = (iconFilePath: string) => { const getIconUrl = (iconFilePath: string) => {
const fileName = iconFilePath.split('/').pop()?.split('.')[0]; const fileName = iconFilePath.split('/').pop()?.split('.')[0];
return `/icons/character-icons/${fileName}.png`; return `${DB_BASE_URL}/icons/character-icons/${fileName}.png`;
}; };
type RoleFilter = 'all' | 'survivors' | 'killers'; type RoleFilter = 'all' | 'survivors' | 'killers';
@@ -29,10 +30,7 @@ export default function CharactersPage() {
const [role, setRole] = useState<RoleFilter>('all'); const [role, setRole] = useState<RoleFilter>('all');
useEffect(() => { useEffect(() => {
fetch('/data/characters.json') fetchCharacters().then(setCharacters);
.then(r => r.json())
.then(setCharacters)
.catch(() => []);
}, []); }, []);
const filtered = useMemo(() => { const filtered = useMemo(() => {
+3 -4
View File
@@ -7,6 +7,7 @@ import shared from '../../styles/shared.module.css';
import styles from '../../styles/Customizations.module.css'; import styles from '../../styles/Customizations.module.css';
import { getFileName, cleanFolderName, isKiller } from '../../lib/utils'; import { getFileName, cleanFolderName, isKiller } from '../../lib/utils';
import { fetchCharacters, fetchCustomizations } from '../../lib/db';
import { Character, CustomizationItem, RoleFilter, Tab, TAB_CATEGORIES, CATEGORY_ORDER } from './types'; import { Character, CustomizationItem, RoleFilter, Tab, TAB_CATEGORIES, CATEGORY_ORDER } from './types';
import CharacterPicker from './CharacterPicker'; import CharacterPicker from './CharacterPicker';
import CharacterCosmetics from './CharacterCosmetics'; import CharacterCosmetics from './CharacterCosmetics';
@@ -31,10 +32,8 @@ export default function CustomizationsPage() {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
useEffect(() => { useEffect(() => {
Promise.all([ Promise.all([fetchCustomizations(), fetchCharacters()])
fetch('/data/customization_items.json').then(r => r.json()).catch(() => []), .then(([items, chars]) => {
fetch('/data/characters.json').then(r => r.json()).catch(() => []),
]).then(([items, chars]) => {
setAllItems(items); setAllItems(items);
setCharacters(chars); setCharacters(chars);
}); });
+8 -5
View File
@@ -26,17 +26,20 @@ export const TAB_CATEGORIES: Partial<Record<Tab, number>> = {
charms: 8, badges: 9, banners: 10, portraits: 11, charms: 8, badges: 9, banners: 10, portraits: 11,
}; };
import { DB_BASE_URL } from '../../lib/db';
export const getCosmeticIconUrl = ( export const getCosmeticIconUrl = (
item: CustomizationItem, item: CustomizationItem,
characterMap: Map<number, string> characterMap: Map<number, string>
): string => { ): string => {
const file = (item.iconFilePath.split('/').pop() ?? '').split('.')[0]; const file = (item.iconFilePath.split('/').pop() ?? '').split('.')[0];
const base = DB_BASE_URL;
switch (item.category) { switch (item.category) {
case 8: return `/icons/customization/charms/${file}.png`; case 8: return `${base}/icons/customization/charms/${file}.png`;
case 9: return `/icons/customization/badges/${file}.png`; case 9: return `${base}/icons/customization/badges/${file}.png`;
case 10: return `/icons/customization/banners/${file}.png`; case 10: return `${base}/icons/customization/banners/${file}.png`;
case 11: return `/icons/customization/portrait-backgrounds/${file}.png`; case 11: return `${base}/icons/customization/portrait-backgrounds/${file}.png`;
} }
const subfolder = const subfolder =
@@ -50,5 +53,5 @@ export const getCosmeticIconUrl = (
const charFolder = (charName ?? item.associatedCharacter.toString()) const charFolder = (charName ?? item.associatedCharacter.toString())
.replace(/[\\/:*?"<>|]/g, '_'); .replace(/[\\/:*?"<>|]/g, '_');
return `/icons/customization/characters/${charFolder}/${subfolder}/${file}.png`; return `${base}/icons/customization/characters/${charFolder}/${subfolder}/${file}.png`;
}; };
+3 -4
View File
@@ -6,6 +6,7 @@ import shared from '../../styles/shared.module.css';
import styles from '../../styles/Dlcs.module.css'; import styles from '../../styles/Dlcs.module.css';
import { PlatformFilter, isOnSteam, isOnEpic, isOnXbox, matchesPlatform, } from './types'; import { PlatformFilter, isOnSteam, isOnEpic, isOnXbox, matchesPlatform, } from './types';
import { DLC, isNamedDLC } from '@/lib/utils'; import { DLC, isNamedDLC } from '@/lib/utils';
import { fetchDLCs } from '@/lib/db';
const PLATFORM_FILTER_LABELS: Record<PlatformFilter, string> = { all: 'All', steam: 'Steam', epic: 'Epic', xbox: 'Xbox' }; const PLATFORM_FILTER_LABELS: Record<PlatformFilter, string> = { all: 'All', steam: 'Steam', epic: 'Epic', xbox: 'Xbox' };
@@ -18,10 +19,8 @@ export default function DlcsPage() {
const [statusFilter, setStatusFilter] = useState<'all' | 'unlocked' | 'locked'>('all'); const [statusFilter, setStatusFilter] = useState<'all' | 'unlocked' | 'locked'>('all');
useEffect(() => { useEffect(() => {
fetch('/data/dlcs.json') fetchDLCs()
.then(r => r.json()) .then((data: DLC[]) => setAllDlcs(data.filter(isNamedDLC)));
.then((data: DLC[]) => setAllDlcs(data.filter(isNamedDLC)))
.catch(() => []);
}, []); }, []);
const filtered = useMemo(() => { const filtered = useMemo(() => {
+3 -4
View File
@@ -7,6 +7,7 @@ import shared from '../../styles/shared.module.css';
import styles from '../../styles/Items.module.css'; import styles from '../../styles/Items.module.css';
import { Item, Offering } from './types'; import { Item, Offering } from './types';
import { fetchItems, fetchOfferings } from '../../lib/db';
import ItemGrid from './ItemGrid'; import ItemGrid from './ItemGrid';
import OfferingGrid from './OfferingGrid'; import OfferingGrid from './OfferingGrid';
@@ -23,10 +24,8 @@ export default function ItemsPage() {
const [randMax, setRandMax] = useState(200); const [randMax, setRandMax] = useState(200);
useEffect(() => { useEffect(() => {
Promise.all([ Promise.all([fetchItems(), fetchOfferings()])
fetch('/data/items.json').then(r => r.json()).catch(() => []), .then(([i, o]) => {
fetch('/data/offerings.json').then(r => r.json()).catch(() => []),
]).then(([i, o]) => {
setItems(i); setItems(i);
setOfferings(o); setOfferings(o);
}); });
+4 -2
View File
@@ -1,3 +1,5 @@
import { DB_BASE_URL } from '../../lib/db';
export type Item = { export type Item = {
id: string; id: string;
name: string; name: string;
@@ -31,12 +33,12 @@ export const getItemType = (id: string): ItemType => {
export const getItemIconUrl = (iconFilePath: string) => { export const getItemIconUrl = (iconFilePath: string) => {
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0]; const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
return `/icons/item-icons/${file}.png`; return `${DB_BASE_URL}/icons/item-icons/${file}.png`;
}; };
export const getOfferingIconUrl = (iconFilePath: string) => { export const getOfferingIconUrl = (iconFilePath: string) => {
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0]; const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
return `/icons/offering-icons/${file}.png`; return `${DB_BASE_URL}/icons/offering-icons/${file}.png`;
}; };
export const randInRange = (min: number, max: number) => { export const randInRange = (min: number, max: number) => {
+6 -5
View File
@@ -4,6 +4,7 @@ import { useState, useEffect, useMemo } from 'react';
import { useInventoryStore } from '@/store/useInventoryStore'; import { useInventoryStore } from '@/store/useInventoryStore';
import styles from '../styles/Home.module.css'; import styles from '../styles/Home.module.css';
import { isNamedDLC } from '@/lib/utils'; import { isNamedDLC } from '@/lib/utils';
import { fetchCharacters, fetchItems, fetchOfferings, fetchCustomizations, fetchDLCs } from '@/lib/db';
export default function Home() { export default function Home() {
const store = useInventoryStore(); const store = useInventoryStore();
@@ -18,11 +19,11 @@ export default function Home() {
useEffect(() => { useEffect(() => {
Promise.all([ Promise.all([
fetch('/data/characters.json').then(r => r.json()).catch(() => []), fetchCharacters(),
fetch('/data/items.json').then(r => r.json()).catch(() => []), fetchItems(),
fetch('/data/offerings.json').then(r => r.json()).catch(() => []), fetchOfferings(),
fetch('/data/customization_items.json').then(r => r.json()).catch(() => []), fetchCustomizations(),
fetch('/data/dlcs.json').then(r => r.json()).catch(() => []) fetchDLCs()
]).then(([chars, items, offerings, customizations, dlcs]) => { ]).then(([chars, items, offerings, customizations, dlcs]) => {
setCharCount(chars.length); setCharCount(chars.length);
setItemsCount(items.length); setItemsCount(items.length);
+28
View File
@@ -0,0 +1,28 @@
export const DB_BASE_URL = 'https://dbd-db.neru.rip';
const _cache = new Map<string, Promise<any>>();
export function fetchDB<T = any>(path: string): Promise<T> {
if (!_cache.has(path)) {
_cache.set(
path,
fetch(`${DB_BASE_URL}${path}`)
.then(r => {
if (!r.ok) throw new Error(`[db] ${r.status} ${path}`);
return r.json();
})
.catch(err => {
_cache.delete(path);
console.error(err);
return [];
})
);
}
return _cache.get(path)!;
}
export const fetchCharacters = () => fetchDB('/data/characters.json');
export const fetchCustomizations = () => fetchDB('/data/customization_items.json');
export const fetchItems = () => fetchDB('/data/items.json');
export const fetchOfferings = () => fetchDB('/data/offerings.json');
export const fetchDLCs = () => fetchDB('/data/dlcs.json');