This commit is contained in:
parent
34523e78b1
commit
5c86fd2ceb
19 changed files with 724 additions and 519 deletions
|
|
@ -4,7 +4,9 @@ import "./styles/main.scss";
|
|||
import { Icon } from "./components/Icon.jsx";
|
||||
import { GameRoute } from "./features/games/GameRoute.jsx";
|
||||
import { GamesPage } from "./features/games/GamesPage.jsx";
|
||||
import { INITIAL_DIABLO4_STATE, INITIAL_MHWILDS_STATE, loadDiablo4Data, loadMhwildsData } from "./features/games/loaders.js";
|
||||
import { AddToolControls, ToolboxModules, TOOLBOX_MODULES } from "./features/toolboxes/modules/index.jsx";
|
||||
import { usePointerReorder } from "./hooks/usePointerReorder.js";
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
registry: "sokkog:toolboxes",
|
||||
|
|
@ -594,25 +596,8 @@ function App() {
|
|||
const [siteContent, setSiteContent] = useState(DEFAULT_SITE_CONTENT);
|
||||
const [games, setGames] = useState([]);
|
||||
const [gamesError, setGamesError] = useState("");
|
||||
const [mhwilds, setMhwilds] = useState({
|
||||
loaded: false,
|
||||
loading: false,
|
||||
error: "",
|
||||
translations: {},
|
||||
monsters: [],
|
||||
endemic: [],
|
||||
filterOptions: { monsters: [], endemic: [] },
|
||||
filterOptionKeys: { monsters: "", endemic: "" }
|
||||
});
|
||||
const [diablo4, setDiablo4] = useState({
|
||||
loaded: false,
|
||||
loading: false,
|
||||
error: "",
|
||||
affixes: [],
|
||||
filterOptions: { affixes: [] },
|
||||
categoryMap: {},
|
||||
filterOptionKeys: { affixes: "" }
|
||||
});
|
||||
const [mhwilds, setMhwilds] = useState(INITIAL_MHWILDS_STATE);
|
||||
const [diablo4, setDiablo4] = useState(INITIAL_DIABLO4_STATE);
|
||||
const [filters, setFilters] = useState({
|
||||
monsters: { name: "", weaknesses: [], logic: "and" },
|
||||
endemic: { name: "", locations: [], logic: "and" },
|
||||
|
|
@ -642,77 +627,17 @@ function App() {
|
|||
useEffect(() => {
|
||||
if (!route.startsWith("/games/mhwilds") || mhwilds.loaded || mhwilds.loading) return;
|
||||
setMhwilds((state) => ({ ...state, loading: true, error: "" }));
|
||||
Promise.all([
|
||||
fetch("/data/mhwilds/monsters.json"),
|
||||
fetch("/data/mhwilds/endemic_life.json"),
|
||||
fetch("/data/mhwilds/i18n/fr.json")
|
||||
]).then(async ([monstersResponse, endemicResponse, translationsResponse]) => {
|
||||
if (!monstersResponse.ok || !endemicResponse.ok || !translationsResponse.ok) {
|
||||
throw new Error("Impossible de charger les données Monster Hunter Wilds.");
|
||||
}
|
||||
const [monstersJson, endemicJson, translations] = await Promise.all([
|
||||
monstersResponse.json(),
|
||||
endemicResponse.json(),
|
||||
translationsResponse.json()
|
||||
]);
|
||||
const monsterFilterKey = Object.keys(monstersJson).find((key) => key !== "monsters") || "";
|
||||
const endemicFilterKey = Object.keys(endemicJson).find((key) => key !== "endemicLife" && key !== "aquaticLife") || "";
|
||||
setMhwilds({
|
||||
loaded: true,
|
||||
loading: false,
|
||||
error: "",
|
||||
translations,
|
||||
monsters: monstersJson.monsters || [],
|
||||
endemic: [...(endemicJson.endemicLife || []), ...(endemicJson.aquaticLife || [])],
|
||||
filterOptions: {
|
||||
monsters: monstersJson[monsterFilterKey] || [],
|
||||
endemic: endemicJson[endemicFilterKey] || []
|
||||
},
|
||||
filterOptionKeys: {
|
||||
monsters: monsterFilterKey,
|
||||
endemic: endemicFilterKey
|
||||
}
|
||||
});
|
||||
}).catch((error) => setMhwilds({
|
||||
loaded: true,
|
||||
loading: false,
|
||||
error: error.message,
|
||||
translations: {},
|
||||
monsters: [],
|
||||
endemic: [],
|
||||
filterOptions: { monsters: [], endemic: [] },
|
||||
filterOptionKeys: { monsters: "", endemic: "" }
|
||||
}));
|
||||
loadMhwildsData()
|
||||
.then(setMhwilds)
|
||||
.catch((error) => setMhwilds({ ...INITIAL_MHWILDS_STATE, loaded: true, error: error.message }));
|
||||
}, [route, mhwilds.loaded, mhwilds.loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!route.startsWith("/games/diablo4") || diablo4.loaded || diablo4.loading) return;
|
||||
setDiablo4((state) => ({ ...state, loading: true }));
|
||||
fetch("/data/diablo4/affixes_types.json")
|
||||
.then(async (response) => {
|
||||
if (!response.ok) throw new Error("Impossible de charger les données Diablo IV.");
|
||||
const payload = await response.json();
|
||||
const filterKey = Object.keys(payload).find((key) => key !== "affixes") || "";
|
||||
const categories = payload[filterKey] || [];
|
||||
setDiablo4({
|
||||
loaded: true,
|
||||
loading: false,
|
||||
error: "",
|
||||
affixes: payload.affixes || [],
|
||||
filterOptions: { affixes: categories },
|
||||
categoryMap: Object.fromEntries(categories.map((category) => [category.id, category])),
|
||||
filterOptionKeys: { affixes: filterKey }
|
||||
});
|
||||
})
|
||||
.catch((error) => setDiablo4({
|
||||
loaded: true,
|
||||
loading: false,
|
||||
error: error.message,
|
||||
affixes: [],
|
||||
filterOptions: { affixes: [] },
|
||||
categoryMap: {},
|
||||
filterOptionKeys: { affixes: "" }
|
||||
}));
|
||||
setDiablo4((state) => ({ ...state, loading: true, error: "" }));
|
||||
loadDiablo4Data()
|
||||
.then(setDiablo4)
|
||||
.catch((error) => setDiablo4({ ...INITIAL_DIABLO4_STATE, loaded: true, error: error.message }));
|
||||
}, [route, diablo4.loaded, diablo4.loading]);
|
||||
|
||||
const t = (key, { capitalize = false } = {}) => {
|
||||
|
|
@ -852,6 +777,10 @@ function App() {
|
|||
setScreenshot,
|
||||
addScreenshotFiles,
|
||||
updateModuleData: store.updateModuleData,
|
||||
updateToolboxOrder: (orderedIds) => {
|
||||
const order = new Map(orderedIds.map((id, index) => [id, index]));
|
||||
store.setToolboxes([...store.toolboxes].sort((a, b) => (order.get(a.id) ?? 9999) - (order.get(b.id) ?? 9999)));
|
||||
},
|
||||
importToolbox: async (file, gameId = "") => {
|
||||
try {
|
||||
return await importToolbox(file, gameId);
|
||||
|
|
@ -1070,6 +999,26 @@ function RichText({ text }) {
|
|||
|
||||
function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions }) {
|
||||
const content = siteContent.toolboxes;
|
||||
const updateToolboxOrder = actions.updateToolboxOrder;
|
||||
const {
|
||||
draggingId: draggingToolboxId,
|
||||
dropTarget,
|
||||
startDrag: startToolboxDrag
|
||||
} = usePointerReorder({
|
||||
targetSelector: ".toolbox-card",
|
||||
getTargetId: (target) => target.dataset.toolboxId,
|
||||
getPlacement: (event, target) => {
|
||||
const rect = target.getBoundingClientRect();
|
||||
return event.clientY > rect.top + rect.height / 2 || event.clientX > rect.left + rect.width / 2 ? "after" : "before";
|
||||
},
|
||||
onMove: (draggingId, targetId, placement) => {
|
||||
const nextIds = toolboxes.map((toolbox) => toolbox.id).filter((id) => id !== draggingId);
|
||||
const targetIndex = nextIds.indexOf(targetId);
|
||||
nextIds.splice(placement === "after" ? targetIndex + 1 : targetIndex, 0, draggingId);
|
||||
updateToolboxOrder(nextIds);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="toolbox-page">
|
||||
<section className="page-hero">
|
||||
|
|
@ -1091,7 +1040,17 @@ function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions }) {
|
|||
</section>
|
||||
<section className="cards">
|
||||
<StorageHelpCard help={content.storageHelp} />
|
||||
{toolboxes.length ? toolboxes.map((toolbox) => <ToolboxCard key={toolbox.id} toolbox={toolbox} game={getToolboxGame(toolbox)} actions={actions} />) : (
|
||||
{toolboxes.length ? toolboxes.map((toolbox) => (
|
||||
<ToolboxCard
|
||||
key={toolbox.id}
|
||||
toolbox={toolbox}
|
||||
game={getToolboxGame(toolbox)}
|
||||
actions={actions}
|
||||
draggingToolboxId={draggingToolboxId}
|
||||
dropTarget={dropTarget}
|
||||
onDragStart={startToolboxDrag}
|
||||
/>
|
||||
)) : (
|
||||
<div className="empty"><h2>{content.emptyTitle}</h2><p>{content.emptyText}</p></div>
|
||||
)}
|
||||
</section>
|
||||
|
|
@ -1117,11 +1076,30 @@ function StorageHelpCard({ help }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ToolboxCard({ toolbox, game, actions }) {
|
||||
function ToolboxCard({ toolbox, game, actions, draggingToolboxId, dropTarget, onDragStart }) {
|
||||
const gameCoverImage = getGameCardCover(game);
|
||||
const coverImage = gameCoverImage || toolbox.icon || DEFAULT_TOOLBOX_ICON;
|
||||
const isDragging = draggingToolboxId === toolbox.id;
|
||||
const isDropTarget = dropTarget.id === toolbox.id;
|
||||
const className = [
|
||||
"card",
|
||||
"toolbox-card",
|
||||
isDragging ? "is-dragging" : "",
|
||||
isDropTarget ? "is-drop-target" : "",
|
||||
isDropTarget && dropTarget.placement === "after" ? "drop-after" : ""
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<article className="card toolbox-card">
|
||||
<article className={className} data-toolbox-id={toolbox.id}>
|
||||
<button
|
||||
className="toolbox-card-drag-handle"
|
||||
type="button"
|
||||
onPointerDown={(event) => onDragStart(event, toolbox.id)}
|
||||
aria-label={`Déplacer ${toolbox.name}`}
|
||||
title="Déplacer"
|
||||
>
|
||||
<Icon name="drag" />
|
||||
</button>
|
||||
<a
|
||||
className={`card-cover toolbox-card-cover-link ${gameCoverImage ? "toolbox-card-cover" : "toolbox-icon-cover"}`}
|
||||
href={`#/toolbox/${toolbox.id}`}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue