change json format & add diablo4
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-07-22 21:21:02 +02:00
parent e65c74ea0a
commit 34523e78b1
33 changed files with 5849 additions and 3431 deletions

View file

@ -0,0 +1,22 @@
import { getCategoryIconStyle, getCategoryLabel } from "./utils.js";
export function Diablo4AffixCard({ affix, categoryMap }) {
return (
<article className="mhwilds-card diablo4-affix-card">
<div className="diablo4-affix-card-body">
<h2>{affix.label}</h2>
<div className="diablo4-category-list" aria-label="Catégories">
{(affix.categories || []).map((categoryId) => {
const category = categoryMap[categoryId] || categoryId;
return (
<span className={`tone-${category.tone || "default"}`} key={categoryId}>
<span className="diablo4-category-icon" aria-hidden="true" style={getCategoryIconStyle(category)} />
{getCategoryLabel(category)}
</span>
);
})}
</div>
</div>
</article>
);
}

View file

@ -0,0 +1,58 @@
import { Icon } from "../../../components/Icon.jsx";
import { getCategoryIconStyle, getCategoryId, getCategoryLabel } from "./utils.js";
export function Diablo4Filters({ options, filters, setFilters }) {
const selected = new Set(filters.categories || []);
const logicLabel = filters.logic === "or" ? "Ou" : "Et";
return (
<div className="filter-panel">
<div className="filter-panel-head">
<div>
<p className="eyebrow">Filtres</p>
<h2>Catégories</h2>
</div>
<button
className="filter-reset-button"
onClick={() => setFilters((state) => ({ ...state, diablo4: { name: "", categories: [], logic: "and" } }))}
aria-label="Réinitialiser"
title="Réinitialiser"
>
<Icon name="rubber" />
</button>
</div>
<label className="field compact">
<span>Nom</span>
<input
value={filters.name}
placeholder="Rechercher..."
onChange={(event) => setFilters((state) => ({ ...state, diablo4: { ...state.diablo4, name: event.target.value } }))}
/>
</label>
<div className="filter-logic">
<span>Correspondance</span>
<button onClick={() => setFilters((state) => ({ ...state, diablo4: { ...state.diablo4, logic: state.diablo4.logic === "and" ? "or" : "and" } }))}>
{logicLabel}
</button>
</div>
<div className="filter-options legacy-scrollbar">
{options.map((option) => (
<label className={`filter-chip diablo4-filter-chip tone-${option.tone || "default"} ${selected.has(getCategoryId(option)) ? "active" : ""}`} key={getCategoryId(option)}>
<input
type="checkbox"
checked={selected.has(getCategoryId(option))}
onChange={(event) => setFilters((state) => {
const values = new Set(state.diablo4.categories || []);
if (event.target.checked) values.add(getCategoryId(option));
else values.delete(getCategoryId(option));
return { ...state, diablo4: { ...state.diablo4, categories: [...values] } };
})}
/>
<span className="diablo4-category-icon" aria-hidden="true" style={getCategoryIconStyle(option)} />
<span>{getCategoryLabel(option)}</span>
</label>
))}
</div>
</div>
);
}

View file

@ -0,0 +1,45 @@
import { useMemo } from "react";
import { Diablo4AffixCard } from "./Diablo4AffixCard.jsx";
import { Diablo4Filters } from "./Diablo4Filters.jsx";
import { getCategoryLabel, getFilteredDiablo4Affixes } from "./utils.js";
export function Diablo4Listing({ diablo4, filters, setFilters }) {
const activeFilters = filters.diablo4;
const options = [...(diablo4.filterOptions.affixes || [])].sort((a, b) => getCategoryLabel(a).localeCompare(getCategoryLabel(b), "fr"));
const visible = useMemo(
() => getFilteredDiablo4Affixes({ affixes: diablo4.affixes, filters: activeFilters }),
[diablo4.affixes, activeFilters]
);
return (
<>
<section className="page-heading mhwilds-heading">
<div>
<p className="eyebrow">Diablo IV</p>
<div className="mhwilds-title-row">
<h1>Affixes</h1>
<span className="results-count">{visible.length} / {diablo4.affixes.length}</span>
</div>
<p>Filtrez les affixes par nom et catégories pour retrouver rapidement les statistiques utiles à votre build.</p>
</div>
</section>
<section className="mhwilds-layout diablo4-layout" data-game-category="diablo4-affixes">
<aside className="mhwilds-filters">
<Diablo4Filters options={options} filters={activeFilters} setFilters={setFilters} />
</aside>
<section className="mhwilds-results" aria-live="polite">
<div className="mhwilds-grid diablo4-affix-grid">
{visible.length ? visible.map((affix) => (
<Diablo4AffixCard key={affix.id} affix={affix} categoryMap={diablo4.categoryMap} />
)) : (
<div className="empty">
<h2>Aucun résultat</h2>
<p>Ajustez la recherche ou réinitialisez les filtres actifs.</p>
</div>
)}
</div>
</section>
</section>
</>
);
}

View file

@ -0,0 +1,22 @@
export function Diablo4Overview({ game }) {
const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined;
return (
<>
<section className="game-hero" style={heroStyle}>
<div>
<p className="eyebrow">Guide de jeu</p>
<h1>Diablo IV</h1>
<p>Consultez rapidement les affixes et leurs catégories pour préparer vos builds sans quitter votre session.</p>
</div>
</section>
<section className="mhwilds-home-grid diablo4-home-grid">
<a className="feature mhwilds-home-card diablo4-home-card" href="#/games/diablo4/affixes">
<span className="diablo4-home-mark" aria-hidden="true" />
<strong>Affixes</strong>
<span>Recherche et filtres par catégorie pour retrouver les statistiques utiles.</span>
</a>
</section>
</>
);
}

View file

@ -0,0 +1,38 @@
import { Diablo4Listing } from "./Diablo4Listing.jsx";
import { Diablo4Overview } from "./Diablo4Overview.jsx";
export function Diablo4Page({ category, game, diablo4, filters, setFilters }) {
const activeCategory = category === "affixes" ? category : "";
const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined;
if (!diablo4.loaded) {
return (
<>
<section className="game-hero" style={heroStyle}>
<div>
<p className="eyebrow">Diablo IV</p>
<h1>Diablo IV</h1>
<p>Chargement des affixes et filtres associés.</p>
</div>
</section>
<section className="empty">
<h2>Chargement</h2>
<p>Préparation des données locales...</p>
</section>
</>
);
}
if (diablo4.error) {
return (
<section className="empty">
<h1>Impossible de charger Diablo IV</h1>
<p>{diablo4.error}</p>
</section>
);
}
if (!activeCategory) return <Diablo4Overview game={game} />;
return <Diablo4Listing diablo4={diablo4} filters={filters} setFilters={setFilters} />;
}

View file

@ -0,0 +1,36 @@
export function normalizeText(value) {
return String(value || "").trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, " ");
}
export function formatCategory(value) {
return String(value || "").replace(/_/g, " ");
}
export function getCategoryId(category) {
return typeof category === "string" ? category : category?.id || "";
}
export function getCategoryLabel(category) {
return typeof category === "string" ? formatCategory(category) : category?.label || formatCategory(category?.id);
}
export function getCategoryIconStyle(category) {
const icon = typeof category === "string" ? "" : category?.icon;
return icon ? { "--diablo4-category-icon": `url("/static/img/diablo4/${icon}.svg")` } : undefined;
}
export function getFilteredDiablo4Affixes({ affixes, filters }) {
const selected = filters.categories || [];
const search = normalizeText(filters.name);
return affixes.filter((affix) => {
const nameMatches = !search || normalizeText(affix.label).includes(search) || normalizeText(affix.id).includes(search);
if (!nameMatches) return false;
if (!selected.length) return true;
const categories = affix.categories || [];
return filters.logic === "or" && selected.length >= 2
? selected.some((category) => categories.includes(category))
: selected.every((category) => categories.includes(category));
});
}