Link guide cards to checklists and refine Aniimo ability filters
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s
This commit is contained in:
parent
b5901b31e3
commit
7b23a8601a
23 changed files with 193 additions and 51 deletions
|
|
@ -33,3 +33,12 @@ export function GuideChecklistControls({ checklist, list, id, open }) {
|
|||
export function GuideChecklistMark({ completed, label }) {
|
||||
return completed ? <span className="guide-checklist-mark nds-badge" data-variant="neutral" role="img" aria-label={label} title={label}>✓</span> : null;
|
||||
}
|
||||
|
||||
export function GuideChecklistCheckbox({ checklist, itemKey }) {
|
||||
if (!checklist.canToggle(itemKey)) return null;
|
||||
const completed = checklist.isCompleted(itemKey);
|
||||
const label = completed ? checklist.content.uncheckCard : checklist.content.checkCard;
|
||||
return <label className="guide-checklist-checkbox" title={label} onClick={(event) => event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()}>
|
||||
<input className="nds-checkbox" data-size="sm" type="checkbox" checked={completed} onChange={() => checklist.toggleCompleted(itemKey)} aria-label={label} />
|
||||
</label>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Rôle : affiche une carte Aniimo retournable et les onglets des variantes correspondantes.
|
||||
import { useId, useLayoutEffect, useRef, useState } from "react";
|
||||
import { GuideChecklistMark } from "../GuideChecklistControls.jsx";
|
||||
import { GuideChecklistCheckbox, GuideChecklistMark } from "../GuideChecklistControls.jsx";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { Tooltip } from "../../../components/Tooltip.jsx";
|
||||
import { ANIIMO_STATS, aniimoAsset } from "./utils.js";
|
||||
|
|
@ -15,7 +15,8 @@ function shortForm(label) {
|
|||
return (words.length === 1 ? words[0].slice(0, 3) : words.map((word) => word[0]).join("")).toLocaleUpperCase("fr");
|
||||
}
|
||||
|
||||
function CardFaces({ variant, t, content, completed, completedLabel, statsMax }) {
|
||||
function CardFaces({ variant, t, content, checklist, statsMax }) {
|
||||
const completed = checklist.isCompleted(variant.key);
|
||||
const [flipped, setFlipped] = useState(false);
|
||||
const frontButton = useRef(null);
|
||||
const backButton = useRef(null);
|
||||
|
|
@ -35,8 +36,7 @@ function CardFaces({ variant, t, content, completed, completedLabel, statsMax })
|
|||
<div className="aniimo-card-inner">
|
||||
<div className="aniimo-face aniimo-front nds-card" aria-hidden={flipped} inert={flipped}>
|
||||
{flipButton(false, frontButton)}
|
||||
<GuideChecklistMark completed={completed} label={completedLabel} />
|
||||
<div className="aniimo-portrait"><img src={variant.image} alt={t(variant.name)} width="512" height="840" loading="lazy" /></div>
|
||||
<div className="aniimo-portrait"><img src={variant.image} alt={t(variant.name)} width="512" height="840" loading="lazy" /><GuideChecklistCheckbox checklist={checklist} itemKey={variant.key} /></div>
|
||||
<div className="aniimo-card-body">
|
||||
<h2 title={t(variant.name)}>{t(variant.name)}</h2>
|
||||
<p className="aniimo-form" title={t(variant.form)}>{t(variant.form)}</p>
|
||||
|
|
@ -45,7 +45,7 @@ function CardFaces({ variant, t, content, completed, completedLabel, statsMax })
|
|||
</div>
|
||||
<div className="aniimo-face aniimo-back nds-card" aria-hidden={!flipped} inert={!flipped}>
|
||||
{flipButton(true, backButton)}
|
||||
<GuideChecklistMark completed={completed} label={completedLabel} />
|
||||
<GuideChecklistMark completed={completed} label={checklist.content.completed} />
|
||||
<div className="aniimo-card-body aniimo-back-heading">
|
||||
<h2 title={t(variant.name)}>{t(variant.name)}</h2>
|
||||
<p className="aniimo-form" title={t(variant.form)}>{t(variant.form)}</p>
|
||||
|
|
@ -79,7 +79,7 @@ function CardFaces({ variant, t, content, completed, completedLabel, statsMax })
|
|||
);
|
||||
}
|
||||
|
||||
export function AniimoCard({ variants, t, content, isCompleted, completedLabel, statsMax }) {
|
||||
export function AniimoCard({ variants, t, content, checklist, statsMax }) {
|
||||
const id = useId();
|
||||
const [selectedKey, setSelectedKey] = useState(variants[0].key);
|
||||
const variant = variants.find((item) => item.key === selectedKey) || variants[0];
|
||||
|
|
@ -91,7 +91,7 @@ export function AniimoCard({ variants, t, content, isCompleted, completedLabel,
|
|||
target.parentElement.children[index].focus();
|
||||
}
|
||||
return (
|
||||
<article className="aniimo-result" data-checklist-complete={isCompleted(variant.key) || undefined} data-aniimo={variant.aniimoId} data-form={variant.form} aria-label={t(variant.name)}>
|
||||
<article className="aniimo-result" data-checklist-complete={checklist.isCompleted(variant.key) || undefined} data-aniimo={variant.aniimoId} data-form={variant.form} aria-label={t(variant.name)}>
|
||||
<div className="aniimo-tabs-slot">
|
||||
{multiple && <div className="aniimo-tabs nds-radio-button-group" data-size="xs" role="tablist" aria-label={`${content.variants} — ${t(variant.name)}`}>
|
||||
{variants.map((item, index) => <Tooltip as="button" content={t(item.form)} aria-label={t(item.form)} key={item.key} type="button" role="tab" data-form={item.form} id={`${id}-${item.form}`} aria-controls={`${id}-panel`} aria-selected={item.key === variant.key} tabIndex={item.key === variant.key ? 0 : -1} className={item.key === variant.key ? "active" : ""} onClick={() => setSelectedKey(item.key)} onKeyDown={(event) => {
|
||||
|
|
@ -103,7 +103,7 @@ export function AniimoCard({ variants, t, content, isCompleted, completedLabel,
|
|||
</div>}
|
||||
</div>
|
||||
<div id={`${id}-panel`} role={multiple ? "tabpanel" : undefined} aria-labelledby={multiple ? `${id}-${variant.form}` : undefined}>
|
||||
<CardFaces variant={variant} t={t} content={content} completed={isCompleted(variant.key)} completedLabel={completedLabel} statsMax={statsMax} />
|
||||
<CardFaces variant={variant} t={t} content={content} checklist={checklist} statsMax={statsMax} />
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,16 @@ function LogicChoice({ label, value, onChange, content }) {
|
|||
);
|
||||
}
|
||||
|
||||
function LevelOperatorIcon({ exact = false }) {
|
||||
return (
|
||||
<svg className="aniimo-level-operator-icon" viewBox="0 0 20 20" aria-hidden="true" focusable="false">
|
||||
{exact
|
||||
? <path d="M4.5 7h11M4.5 13h11" />
|
||||
: <path d="m4.5 3.75 11 4.75-11 4.75M4.5 16.5h11" />}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function AniimoFilters({ filters, onChange, options, t, content }) {
|
||||
const [collapsed, setCollapsed] = useState({});
|
||||
const abilityOptions = [...options.abilities].sort((a, b) => Number(!options.elements.includes(a)) - Number(!options.elements.includes(b)));
|
||||
|
|
@ -46,8 +56,14 @@ export function AniimoFilters({ filters, onChange, options, t, content }) {
|
|||
<Icon name={collapsed[key] ? "chevron-down" : "chevron-up"} />
|
||||
</button></legend>
|
||||
<div id={`aniimo-filter-${key}`} hidden={collapsed[key]}>
|
||||
{key !== "roles" && <LogicChoice label={content[key]} content={content} value={filters[`${key}Logic`]} onChange={(logic) => onChange({ ...filters, [`${key}Logic`]: logic })} />}
|
||||
{key === "abilities" && <p className="aniimo-levels-hint" id="aniimo-levels-hint">{content.abilityLevelsHint}</p>}
|
||||
{key === "elements" && <LogicChoice label={content[key]} content={content} value={filters.elementsLogic} onChange={(logic) => onChange({ ...filters, elementsLogic: logic })} />}
|
||||
{key === "abilities" && <div className="filter-logic">
|
||||
<span>{content.levelMode}</span>
|
||||
<div className="filter-logic-group nds-radio-button-group" data-size="xs" role="group" aria-label={content.levelMode}>
|
||||
<button type="button" className={!filters.abilityExact ? "active" : ""} aria-label={content.minimum} title={content.minimum} aria-pressed={!filters.abilityExact} onClick={() => onChange({ ...filters, abilityExact: false })}><LevelOperatorIcon /></button>
|
||||
<button type="button" className={filters.abilityExact ? "active" : ""} aria-label={content.exactLevel} title={content.exactLevel} aria-pressed={filters.abilityExact} onClick={() => onChange({ ...filters, abilityExact: true })}><LevelOperatorIcon exact /></button>
|
||||
</div>
|
||||
</div>}
|
||||
<div className={`aniimo-filter-options ${key === "abilities" ? "aniimo-ability-options" : "aniimo-chip-options"}`} role="region" aria-label={content[key]}>
|
||||
{(key === "abilities" ? abilityOptions : options[key]).map((option) => {
|
||||
const isAbility = key === "abilities";
|
||||
|
|
@ -56,7 +72,7 @@ export function AniimoFilters({ filters, onChange, options, t, content }) {
|
|||
const level = filters.abilities[option] || 0;
|
||||
return <div key={option} className="aniimo-ability-filter" data-ability-group={options.elements.includes(option) ? "elements" : "other"} data-active={level > 0 || undefined}>
|
||||
<label htmlFor={rangeId}><img src={aniimoAsset(option)} alt="" width="18" height="18" /><span>{t(option)}</span></label>
|
||||
<input id={rangeId} className="nds-range" data-size="sm" type="range" min="0" max="4" step="1" value={level} aria-label={`${t(option)} — ${content.minimum}`} aria-describedby="aniimo-levels-hint" aria-valuetext={level ? `${content.minimum} ${level}` : content.abilityDisabled} onChange={(event) => {
|
||||
<input id={rangeId} className="nds-range" data-size="sm" type="range" min="0" max="4" step="1" value={level} aria-label={`${t(option)} — ${filters.abilityExact ? content.exactLevel : content.minimum}`} aria-valuetext={level ? `${filters.abilityExact ? content.exactLevel : content.minimum} ${level}` : content.abilityDisabled} onChange={(event) => {
|
||||
const abilities = { ...filters.abilities };
|
||||
const minimum = Number(event.target.value);
|
||||
if (minimum > 0) abilities[option] = minimum;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function AniimoListing({ game, aniimo, filters, setFilters, content }) {
|
|||
<section className="game-layout aniimo-layout">
|
||||
<aside className="game-filters"><AniimoFilters filters={active} onChange={update} options={aniimo.filterOptions} t={t} content={content} /></aside>
|
||||
<section className="game-results" aria-label={content.results}>
|
||||
{cards.length ? <div className="game-grid aniimo-grid">{cards.map((variants) => <AniimoCard key={active.grouped ? variants[0].aniimoId : variants[0].key} variants={variants} t={t} content={content} isCompleted={checklist.isCompleted} completedLabel={checklist.content.completed} statsMax={statsMax} />)}</div> : <div className="empty nds-empty-state"><h2>{content.emptyTitle}</h2><p>{content.emptyText}</p></div>}
|
||||
{cards.length ? <div className="game-grid aniimo-grid">{cards.map((variants) => <AniimoCard key={active.grouped ? variants[0].aniimoId : variants[0].key} variants={variants} t={t} content={content} checklist={checklist} statsMax={statsMax} />)}</div> : <div className="empty nds-empty-state"><h2>{content.emptyTitle}</h2><p>{content.emptyText}</p></div>}
|
||||
</section>
|
||||
</section>
|
||||
</>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Rôle : prépare les variantes Aniimo et applique les critères avant leur regroupement.
|
||||
export function createAniimoFilters() {
|
||||
return { name: "", prismanaOnly: false, elements: [], roles: [], abilities: {}, elementsLogic: "and", abilitiesLogic: "and", grouped: true };
|
||||
return { name: "", prismanaOnly: false, elements: [], roles: [], abilities: {}, elementsLogic: "and", abilityExact: false, grouped: true };
|
||||
}
|
||||
|
||||
export const ANIIMO_STATS = ["hp", "atk", "p_def", "m_def", "break", "regen"];
|
||||
|
|
@ -54,7 +54,7 @@ export function filterAniimoVariants(variants, filters) {
|
|||
&& (!filters.prismanaOnly || variant.form === "prismana_form")
|
||||
&& matchesSelection(filters.elements, filters.elementsLogic, (element) => variant.elements.includes(element))
|
||||
&& (!filters.roles.length || filters.roles.includes(variant.role))
|
||||
&& matchesSelection(abilities, filters.abilitiesLogic, ([type, minimum]) => variant.homeland_abilities.some((ability) => ability.type === type && ability.level >= minimum))
|
||||
&& abilities.every(([type, level]) => variant.homeland_abilities.some((ability) => ability.type === type && (filters.abilityExact ? ability.level === level : ability.level >= level)))
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// Rôle : affiche une carte d'affixe Diablo IV et ses catégories.
|
||||
import { GuideChecklistMark } from "../GuideChecklistControls.jsx";
|
||||
import { GuideChecklistCheckbox } from "../GuideChecklistControls.jsx";
|
||||
import { getCategoryIconStyle, getCategoryLabel } from "./utils.js";
|
||||
|
||||
export function Diablo4AffixCard({ affix, categoryMap, completed, completedLabel }) {
|
||||
export function Diablo4AffixCard({ affix, categoryMap, checklist }) {
|
||||
const completed = checklist.isCompleted(affix.id);
|
||||
return (
|
||||
<article className="compact-data-card nds-card diablo4-affix-card" data-variant="interactive" data-checklist-complete={completed || undefined}>
|
||||
<GuideChecklistMark completed={completed} label={completedLabel} />
|
||||
<GuideChecklistCheckbox checklist={checklist} itemKey={affix.id} />
|
||||
<div className="diablo4-affix-card-body">
|
||||
<h2>{affix.label}</h2>
|
||||
<div className="diablo4-category-list" aria-label="Catégories">
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function Diablo4Listing({ game, diablo4, filters, setFilters }) {
|
|||
<section className="game-results" aria-live="polite">
|
||||
<div className="game-grid diablo4-affix-grid">
|
||||
{visible.length ? visible.map((affix) => (
|
||||
<Diablo4AffixCard key={affix.id} affix={affix} categoryMap={diablo4.categoryMap} completed={checklist.isCompleted(affix.id)} completedLabel={checklist.content.completed} />
|
||||
<Diablo4AffixCard key={affix.id} affix={affix} categoryMap={diablo4.categoryMap} checklist={checklist} />
|
||||
)) : (
|
||||
<div className="empty nds-empty-state">
|
||||
<h2>Aucun résultat</h2>
|
||||
|
|
|
|||
|
|
@ -19,14 +19,29 @@ export function matchGuideChecklist(entries, data) {
|
|||
const findRows = (labels = []) => [...new Set(labels.map(normalizeGuideLabel))].flatMap((label) => byLabel.get(label) || []);
|
||||
const matched = new Set();
|
||||
const completed = new Set();
|
||||
const rowIds = new Map();
|
||||
for (const entry of entries) {
|
||||
const specific = findRows(entry.labels);
|
||||
const matches = specific.length ? specific : findRows(entry.fallbackLabels);
|
||||
if (!matches.length) continue;
|
||||
matched.add(entry.key);
|
||||
rowIds.set(entry.key, [...new Set(matches.map((row) => row.id).filter(Boolean))]);
|
||||
if (matches.every((row) => Number(row.qtyCurrent || 0) >= Math.max(1, Number(row.qtyTarget || 1)))) completed.add(entry.key);
|
||||
}
|
||||
return { count: matched.size, completed };
|
||||
return { count: matched.size, completed, rowIds };
|
||||
}
|
||||
|
||||
export function toggleGuideChecklistRows(data, ids, completed) {
|
||||
const targetIds = new Set(ids || []);
|
||||
if (!targetIds.size) return data;
|
||||
const update = (items = []) => items.map((item) => targetIds.has(item.id)
|
||||
? { ...item, qtyCurrent: completed ? Math.max(1, Number(item.qtyTarget || 1)) : 0 }
|
||||
: item);
|
||||
return {
|
||||
...data,
|
||||
...(Array.isArray(data?.items) ? { items: update(data.items) } : {}),
|
||||
...(Array.isArray(data?.sections) ? { sections: data.sections.map((section) => ({ ...section, items: update(section.items) })) } : {})
|
||||
};
|
||||
}
|
||||
|
||||
export function validGuideAssociations(value, toolboxes, links) {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ export function MhwildsListing({ category, game, mhwilds, filters, setFilters, t
|
|||
<div className={`game-grid ${isMonsters ? "monster-grid" : "endemic-grid"}`}>
|
||||
{visible.length ? visible.map((item) => (
|
||||
isMonsters
|
||||
? <MonsterCard key={item.name} monster={item} t={t} completed={checklist.isCompleted(item.name)} completedLabel={checklist.content.completed} />
|
||||
: <EndemicCard key={item.name} item={item} t={t} completed={checklist.isCompleted(item.name)} completedLabel={checklist.content.completed} />
|
||||
? <MonsterCard key={item.name} monster={item} t={t} checklist={checklist} />
|
||||
: <EndemicCard key={item.name} item={item} t={t} checklist={checklist} />
|
||||
)) : (
|
||||
<div className="empty nds-empty-state">
|
||||
<h2>Aucun résultat</h2>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// Rôle : affiche une carte de faune endémique Monster Hunter Wilds.
|
||||
import { GuideChecklistMark } from "../../GuideChecklistControls.jsx";
|
||||
import { GuideChecklistCheckbox } from "../../GuideChecklistControls.jsx";
|
||||
import { IconRow } from "./IconRow.jsx";
|
||||
import { assetPath, getConditionValues, normalizeText } from "../utils.js";
|
||||
|
||||
export function EndemicCard({ item, t, completed, completedLabel }) {
|
||||
export function EndemicCard({ item, t, checklist }) {
|
||||
const completed = checklist.isCompleted(item.name);
|
||||
const locations = getConditionValues(item.locations);
|
||||
const description = t(item.description);
|
||||
const normalizedDescription = normalizeText(description);
|
||||
|
|
@ -13,9 +14,9 @@ export function EndemicCard({ item, t, completed, completedLabel }) {
|
|||
|
||||
return (
|
||||
<article className="mhwilds-card endemic-card" data-checklist-complete={completed || undefined}>
|
||||
<GuideChecklistMark completed={completed} label={completedLabel} />
|
||||
<div className="mhwilds-card-art">
|
||||
<img src={assetPath(item.name)} alt={t(item.name, { capitalize: true })} loading="lazy" />
|
||||
<GuideChecklistCheckbox checklist={checklist} itemKey={item.name} />
|
||||
</div>
|
||||
<div className="mhwilds-card-body">
|
||||
<h2>{t(item.name, { capitalize: true })}</h2>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// Rôle : affiche une carte monstre retournable avec faiblesses et hitzones.
|
||||
import { GuideChecklistMark } from "../../GuideChecklistControls.jsx";
|
||||
import { GuideChecklistCheckbox, GuideChecklistMark } from "../../GuideChecklistControls.jsx";
|
||||
import { useState } from "react";
|
||||
import { DamageTable } from "./DamageTable.jsx";
|
||||
import { IconRow } from "./IconRow.jsx";
|
||||
import { assetPath, getConditionValues } from "../utils.js";
|
||||
|
||||
export function MonsterCard({ monster, t, completed, completedLabel }) {
|
||||
export function MonsterCard({ monster, t, checklist }) {
|
||||
const completed = checklist.isCompleted(monster.name);
|
||||
const [flipped, setFlipped] = useState(false);
|
||||
const weaknesses = getConditionValues(monster.weaknesses);
|
||||
const ailments = getConditionValues(monster.ailments).filter((value) => value !== "none");
|
||||
|
|
@ -23,16 +24,16 @@ export function MonsterCard({ monster, t, completed, completedLabel }) {
|
|||
tabIndex="0"
|
||||
role="button"
|
||||
aria-pressed={flipped}
|
||||
aria-label={`Afficher les dégâts de ${t(monster.name, { capitalize: true })}${completed ? ` — ${completedLabel}` : ""}`}
|
||||
aria-label={`Afficher les dégâts de ${t(monster.name, { capitalize: true })}${completed ? ` — ${checklist.content.completed}` : ""}`}
|
||||
>
|
||||
<div className="mhwilds-card-inner">
|
||||
<div className="mhwilds-card-face mhwilds-card-front">
|
||||
<GuideChecklistMark completed={completed} label={completedLabel} />
|
||||
<span className="mhwilds-flip-indicator" aria-hidden="true">
|
||||
<span className="ui-icon ui-icon-flip" />
|
||||
</span>
|
||||
<div className="mhwilds-card-art">
|
||||
<img src={assetPath(monster.name)} alt={t(monster.name, { capitalize: true })} loading="lazy" />
|
||||
<GuideChecklistCheckbox checklist={checklist} itemKey={monster.name} />
|
||||
</div>
|
||||
<div className="mhwilds-card-body">
|
||||
<p className="eyebrow">{t(monster.type, { capitalize: true })}</p>
|
||||
|
|
@ -42,7 +43,7 @@ export function MonsterCard({ monster, t, completed, completedLabel }) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mhwilds-card-face mhwilds-card-back">
|
||||
<GuideChecklistMark completed={completed} label={completedLabel} />
|
||||
<GuideChecklistMark completed={completed} label={checklist.content.completed} />
|
||||
<span className="mhwilds-flip-indicator" aria-hidden="true">
|
||||
<span className="ui-icon ui-icon-flip" />
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Rôle : partage la progression réactive et les commandes de liaison entre les guides.
|
||||
import { createContext, useContext, useId, useMemo, useState } from "react";
|
||||
import { getGuideChecklistEntries, matchGuideChecklist } from "./guideChecklist.js";
|
||||
import { getGuideChecklistEntries, matchGuideChecklist, toggleGuideChecklistRows } from "./guideChecklist.js";
|
||||
|
||||
export const GuideChecklistContext = createContext(null);
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ export function useGuideChecklistPanel(checklist, module) {
|
|||
}
|
||||
|
||||
export function useGuideChecklist(requestedGuideId) {
|
||||
const { toolboxes, links, moduleData, guideChecklistSession, siteContent, route, aniimo, mhwilds, diablo4 } = useContext(GuideChecklistContext);
|
||||
const { toolboxes, links, moduleData, guideChecklistSession, actions, siteContent, route, aniimo, mhwilds, diablo4 } = useContext(GuideChecklistContext);
|
||||
const guideId = requestedGuideId || route.replace(/^\/games\//, "");
|
||||
const entries = useMemo(() => getGuideChecklistEntries(guideId, { aniimo, mhwilds, diablo4 }), [guideId, aniimo, mhwilds, diablo4]);
|
||||
const gameId = guideId.split("/")[0];
|
||||
|
|
@ -26,8 +26,15 @@ export function useGuideChecklist(requestedGuideId) {
|
|||
const selected = lists.find((list) => list.id === association?.moduleId);
|
||||
const hideCompleted = Boolean(selected && association.hideCompleted);
|
||||
const isCompleted = (key) => Boolean(selected?.completed.has(key));
|
||||
const canToggle = (key) => Boolean(selected?.rowIds.get(key)?.length);
|
||||
function toggleCompleted(key) {
|
||||
const ids = selected?.rowIds.get(key);
|
||||
if (!ids?.length) return;
|
||||
const data = moduleData[`${toolbox.id}:${selected.id}`];
|
||||
actions.updateModuleData(toolbox.id, selected.id, toggleGuideChecklistRows(data, ids, !isCompleted(key)), "checklist");
|
||||
}
|
||||
return {
|
||||
content, toolbox, selected, hideCompleted, isCompleted, guideId, available: entries !== null,
|
||||
content, toolbox, selected, hideCompleted, isCompleted, canToggle, toggleCompleted, guideId, available: entries !== null,
|
||||
lists: lists.filter((list) => list.count || list.id === selected?.id),
|
||||
isVisible: (key) => !hideCompleted || !isCompleted(key),
|
||||
associate: (moduleId) => guideChecklistSession.setAssociation(guideId, moduleId ? { toolboxId: toolbox.id, moduleId, hideCompleted } : null),
|
||||
|
|
|
|||
|
|
@ -40,7 +40,18 @@
|
|||
&[data-active] .aniimo-minimum-value { color: var(--color-accent-gold); font-weight: 700; }
|
||||
}
|
||||
.aniimo-minimum-value { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.aniimo-levels-hint { margin: 4px 0; color: var(--color-text-muted); font-size: var(--font-size-xs); }
|
||||
.aniimo-level-operator-icon {
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
overflow: visible;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
shape-rendering: geometricPrecision;
|
||||
}
|
||||
.aniimo-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr)); }
|
||||
.aniimo-result { min-width: 0; max-width: 280px; width: 100%; }
|
||||
.aniimo-tabs-slot { height: 36px; min-width: 0; padding: 2px; }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,25 @@
|
|||
.guide-checklist-actions { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--space-2); padding-top: var(--space-2); }
|
||||
}
|
||||
.module-guide-button[data-linked] .ui-icon { color: var(--color-accent-gold); }
|
||||
.guide-checklist-checkbox {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
z-index: 3;
|
||||
display: grid;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
place-items: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(7, 10, 24, 0.82);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
.guide-checklist-checkbox:hover { border-color: var(--color-border-hover); }
|
||||
.aniimo-portrait,
|
||||
.mhwilds-card-art { position: relative; }
|
||||
.diablo4-affix-card > .guide-checklist-checkbox { right: 12px; bottom: 12px; }
|
||||
.guide-checklist-mark {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
|
@ -21,7 +40,7 @@
|
|||
.diablo4-affix-card .guide-checklist-mark,
|
||||
.aniimo-back .guide-checklist-mark { top: auto; left: auto; right: 10px; bottom: 10px; }
|
||||
.mhwilds-card-back .guide-checklist-mark { left: auto; right: 50px; }
|
||||
.diablo4-affix-card .diablo4-category-list { padding-right: 24px; }
|
||||
.diablo4-affix-card .diablo4-category-list { padding-right: 34px; }
|
||||
[data-checklist-complete="true"] {
|
||||
.aniimo-back-details.nds-scrollbox[data-variant] { padding-bottom: 36px; }
|
||||
.aniimo-portrait img, .mhwilds-card-art img { filter: grayscale(1); opacity: 0.55; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue