This commit is contained in:
parent
b8d1829bc7
commit
d359fd0a35
25 changed files with 1340 additions and 1023 deletions
|
|
@ -4,13 +4,14 @@ import { MhwildsPage } from "./mhwilds/MhwildsPage.jsx";
|
|||
export function GameRoute(props) {
|
||||
const { gameId, games } = props;
|
||||
const game = games.find((item) => item.id === gameId);
|
||||
const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined;
|
||||
|
||||
if (!game) return <GamesPage {...props} />;
|
||||
if (game.id === "mhwilds") return <MhwildsPage {...props} />;
|
||||
if (game.id === "mhwilds") return <MhwildsPage {...props} game={game} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="game-hero" style={{ background: game.cover }}>
|
||||
<section className="game-hero" style={heroStyle}>
|
||||
<div>
|
||||
<p className="eyebrow">{game.eyebrow || "Guide de jeu"}</p>
|
||||
<h1>{game.title}</h1>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export function GamesPage({ siteContent, games, gamesError }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<section className="page-heading">
|
||||
<section className="page-hero">
|
||||
<div>
|
||||
<p className="eyebrow">{content.eyebrow}</p>
|
||||
<h1>{content.title}</h1>
|
||||
|
|
@ -20,7 +20,7 @@ export function GamesPage({ siteContent, games, gamesError }) {
|
|||
title={`Ouvrir ${game.title}`}
|
||||
style={{ "--game-cover": game.cover || "var(--gradient-nebula)" }}
|
||||
>
|
||||
<img src={game.image || ""} alt={game.title} loading="lazy" />
|
||||
<img src={game.images?.cardCover || game.image || ""} alt={game.title} loading="lazy" />
|
||||
</a>
|
||||
<div className="card-body">
|
||||
<p className="eyebrow">{game.eyebrow || "Guide de jeu"}</p>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { assetPath } from "./utils.js";
|
||||
|
||||
export function MhwildsOverview() {
|
||||
export function MhwildsOverview({ game }) {
|
||||
const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="game-hero mhwilds-hero">
|
||||
<section className="game-hero" style={heroStyle}>
|
||||
<div>
|
||||
<p className="eyebrow">Guide de jeu</p>
|
||||
<h1>Monster Hunter: Wilds</h1>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { MhwildsListing } from "./MhwildsListing.jsx";
|
||||
import { MhwildsOverview } from "./MhwildsOverview.jsx";
|
||||
|
||||
export function MhwildsPage({ category, mhwilds, filters, setFilters, t }) {
|
||||
export function MhwildsPage({ category, game, mhwilds, filters, setFilters, t }) {
|
||||
const activeCategory = category === "monsters" || category === "endemic" ? category : "";
|
||||
const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined;
|
||||
|
||||
if (!mhwilds.loaded) {
|
||||
return (
|
||||
<>
|
||||
<section className="game-hero mhwilds-hero">
|
||||
<section className="game-hero" style={heroStyle}>
|
||||
<div>
|
||||
<p className="eyebrow">Monster Hunter Wilds</p>
|
||||
<h1>Monster Hunter: Wilds</h1>
|
||||
|
|
@ -31,7 +32,7 @@ export function MhwildsPage({ category, mhwilds, filters, setFilters, t }) {
|
|||
);
|
||||
}
|
||||
|
||||
if (!activeCategory) return <MhwildsOverview />;
|
||||
if (!activeCategory) return <MhwildsOverview game={game} />;
|
||||
|
||||
return (
|
||||
<MhwildsListing
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { ChecklistModule } from "./ChecklistModule.jsx";
|
||||
|
|
@ -15,11 +15,53 @@ const MODULE_COMPONENTS = {
|
|||
counters: { label: "Compteurs", icon: "abacus", Component: CountersModule, editable: true }
|
||||
};
|
||||
|
||||
const MODULE_COLUMN_GAP_PX = 16;
|
||||
|
||||
export const TOOLBOX_MODULES = Object.fromEntries(Object.entries(MODULE_COMPONENTS).map(([type, module]) => [
|
||||
type,
|
||||
{ label: module.label, icon: module.icon }
|
||||
]));
|
||||
|
||||
function getSequentialModuleColumns(modules, splitIndex) {
|
||||
return [
|
||||
modules.slice(0, splitIndex),
|
||||
modules.slice(splitIndex)
|
||||
];
|
||||
}
|
||||
|
||||
function getColumnHeight(heights) {
|
||||
if (!heights.length) return 0;
|
||||
return heights.reduce((total, height) => total + height, 0) + MODULE_COLUMN_GAP_PX * (heights.length - 1);
|
||||
}
|
||||
|
||||
function chooseMeasuredSplitIndex(modules, moduleHeights, fallbackSplitIndex) {
|
||||
if (modules.length <= 1) return modules.length;
|
||||
if (modules.some((module) => !moduleHeights.get(module.id))) return fallbackSplitIndex;
|
||||
|
||||
let bestSplitIndex = fallbackSplitIndex;
|
||||
let bestDiff = Number.POSITIVE_INFINITY;
|
||||
let bestLeftDominantSplitIndex = 0;
|
||||
let bestLeftDominantDiff = Number.POSITIVE_INFINITY;
|
||||
|
||||
for (let splitIndex = 1; splitIndex < modules.length; splitIndex += 1) {
|
||||
const leftHeight = getColumnHeight(modules.slice(0, splitIndex).map((module) => moduleHeights.get(module.id)));
|
||||
const rightHeight = getColumnHeight(modules.slice(splitIndex).map((module) => moduleHeights.get(module.id)));
|
||||
const diff = Math.abs(leftHeight - rightHeight);
|
||||
|
||||
if (leftHeight >= rightHeight && diff < bestLeftDominantDiff) {
|
||||
bestLeftDominantDiff = diff;
|
||||
bestLeftDominantSplitIndex = splitIndex;
|
||||
}
|
||||
|
||||
if (diff < bestDiff) {
|
||||
bestDiff = diff;
|
||||
bestSplitIndex = splitIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return bestLeftDominantSplitIndex || bestSplitIndex;
|
||||
}
|
||||
|
||||
export function AddToolControls({ onAdd }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [quickOpen, setQuickOpen] = useState(false);
|
||||
|
|
@ -134,6 +176,48 @@ export function AddToolControls({ onAdd }) {
|
|||
export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDelete, onMove }) {
|
||||
const [draggingModuleId, setDraggingModuleId] = useState("");
|
||||
const [dropTarget, setDropTarget] = useState({ id: "", placement: "before" });
|
||||
const [measuredSplitIndex, setMeasuredSplitIndex] = useState(() => Math.ceil(toolbox.modules.length / 2));
|
||||
const moduleElementsRef = useRef(new Map());
|
||||
const moduleIdSignature = useMemo(() => toolbox.modules.map((module) => module.id).join("|"), [toolbox.modules]);
|
||||
|
||||
useEffect(() => {
|
||||
setMeasuredSplitIndex(Math.ceil(toolbox.modules.length / 2));
|
||||
}, [toolbox.id, moduleIdSignature, toolbox.modules.length]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (moduleColumns !== 2 || toolbox.modules.length <= 1) return undefined;
|
||||
|
||||
let frameId = 0;
|
||||
function rebalanceColumns() {
|
||||
cancelAnimationFrame(frameId);
|
||||
frameId = requestAnimationFrame(() => {
|
||||
const moduleHeights = new Map();
|
||||
toolbox.modules.forEach((module) => {
|
||||
const element = moduleElementsRef.current.get(module.id);
|
||||
if (element) moduleHeights.set(module.id, element.getBoundingClientRect().height);
|
||||
});
|
||||
|
||||
setMeasuredSplitIndex((currentSplitIndex) => {
|
||||
const fallbackSplitIndex = Math.min(Math.max(currentSplitIndex, 1), toolbox.modules.length - 1);
|
||||
const nextSplitIndex = chooseMeasuredSplitIndex(toolbox.modules, moduleHeights, fallbackSplitIndex);
|
||||
return nextSplitIndex === currentSplitIndex ? currentSplitIndex : nextSplitIndex;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(rebalanceColumns);
|
||||
|
||||
toolbox.modules.forEach((module) => {
|
||||
const element = moduleElementsRef.current.get(module.id);
|
||||
if (element) observer.observe(element);
|
||||
});
|
||||
rebalanceColumns();
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frameId);
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [moduleColumns, toolbox.id, moduleIdSignature, toolbox.modules.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!draggingModuleId) return undefined;
|
||||
|
|
@ -180,6 +264,14 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe
|
|||
setDropTarget({ id: "", placement: "before" });
|
||||
}
|
||||
|
||||
function registerModuleElement(moduleId, element) {
|
||||
if (element) {
|
||||
moduleElementsRef.current.set(moduleId, element);
|
||||
} else {
|
||||
moduleElementsRef.current.delete(moduleId);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleColumns === 1) {
|
||||
return (
|
||||
<section className="modules" data-toolbox-id={toolbox.id} data-layout-cols={moduleColumns}>
|
||||
|
|
@ -195,14 +287,15 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe
|
|||
onRename={onRename}
|
||||
onDelete={onDelete}
|
||||
onMove={onMove}
|
||||
registerModuleElement={registerModuleElement}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const columns = [[], []];
|
||||
toolbox.modules.forEach((module, index) => columns[index % 2].push(module));
|
||||
const splitIndex = Math.min(Math.max(measuredSplitIndex, 1), Math.max(1, toolbox.modules.length - 1));
|
||||
const columns = getSequentialModuleColumns(toolbox.modules, splitIndex);
|
||||
|
||||
return (
|
||||
<section className="modules" data-toolbox-id={toolbox.id} data-layout-cols={moduleColumns}>
|
||||
|
|
@ -220,6 +313,7 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe
|
|||
onRename={onRename}
|
||||
onDelete={onDelete}
|
||||
onMove={onMove}
|
||||
registerModuleElement={registerModuleElement}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -228,7 +322,7 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe
|
|||
);
|
||||
}
|
||||
|
||||
function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, onDragStart, onRename, onDelete }) {
|
||||
function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, onDragStart, onRename, onDelete, registerModuleElement }) {
|
||||
const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad;
|
||||
const Component = definition.Component;
|
||||
const label = definition.label || module.type;
|
||||
|
|
@ -247,6 +341,7 @@ function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, o
|
|||
className={className}
|
||||
data-toolbox-id={toolbox.id}
|
||||
data-module-id={module.id}
|
||||
ref={(element) => registerModuleElement?.(module.id, element)}
|
||||
>
|
||||
<header>
|
||||
<div>
|
||||
|
|
@ -273,7 +368,7 @@ function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, o
|
|||
aria-pressed={editing}
|
||||
title={editing ? "Masquer l'ajout" : "Ajouter"}
|
||||
>
|
||||
<Icon name="edit" />
|
||||
<Icon name="add" />
|
||||
</button>
|
||||
)}
|
||||
<button className="module-delete-button danger" onClick={() => onDelete(module.id)} aria-label={`Retirer ${module.title || label}`} title="Retirer">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue