import { useRef, useState, useEffect } from "react"; import factionBackground from "./assets/factionBackground.png"; import adeptusAstartesIcon from "./assets/adeptusAstartesIcon.png"; import rangedIcon from "./assets/rangedIcon.png"; import { Arrow, wavyLine } from "./assets/icons"; import { Weapons, hasDifferentProfiles } from "./Weapons"; export const Roster = ({ roster, onePerPage }) => { if (!roster) { return null; } const { name, cost, forces } = roster; return (
{name} [{cost.commandPoints} CP, {cost.points} pts]
{forces.map((force, index) => ( ))}
); }; const Force = ({ force, onePerPage }) => { const { units, factionRules, rules, catalog } = force; var mergedRules = new Map([...factionRules, ...rules]); return (
{units.map((unit, index) => ( ))}
); }; const Unit = ({ unit, index, catalog, onePerPage, forceRules }) => { const [hide, setHide] = useState(false); const uploadRef = useRef(); const [image, setImage] = useState(null); let { name, weapons, abilities, keywords, factions, rules, modelStats, modelList, psykers, spells, cost, woundTracker, } = unit; const meleeWeapons = weapons .filter( (weapon) => (weapon.range === "Melee" || weapon.type === "Melee") && weapon.range !== "" ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); if (meleeWeapons.length === 0) { meleeWeapons.push({ name: "Close combat weapon", selectionName: "Close combat weapon", range: "Melee", damage: "1", type: "Melee", str: "+0", ap: "0", }); } const rangedWeapons = weapons .filter( (weapon) => weapon.range !== "Melee" && weapon.range !== "-" && weapon.range !== "" ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); const weaponDescriptions = weapons .filter( (weapon) => (weapon.range === "-" || weapon.range === "") && weapon.abilities ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); const modelsWithDifferentProfiles = weapons.filter((weapon, index) => { const { selectionName, name } = weapon; const previousWeapon = weapons[index - 1]; const nextWeapon = weapons[index + 1]; return ( hasDifferentProfiles(selectionName, name) && ((previousWeapon && hasDifferentProfiles( previousWeapon.selectionName, previousWeapon.name ) && selectionName === previousWeapon.selectionName) || (nextWeapon && hasDifferentProfiles(nextWeapon.selectionName, nextWeapon.name) && selectionName === nextWeapon.selectionName)) ); }); const overridePrimary = getOverridePrimary(factions, keywords); useEffect(() => { // Check if the browser is Safari, and if so, remove the accept attribute // from the file input element. This is because Safari doesn't support // extensions on the accept attribute for input type=file // (https://caniuse.com/input-file-accept). If set, they will not allow any // file to be selected. if ( navigator.userAgent.match(/AppleWebKit.*Safari/) && !navigator.userAgent.includes("Chrome") ) { uploadRef.current?.removeAttribute("accept"); } }, []); return (
{wavyLine}
{name} {cost.points}pts
{modelStats.map((model, index) => ( 1} showWeapons={ modelList.length > 1 || modelList[0].includes("x ") // show Weapons when multiple weapons of same type, for example (2x Atomiser Beam, Reanimator's Claws) } /> ))}
{image && ( )}
{weaponDescriptions.map((weapon, index) => ( ))} {modelsWithDifferentProfiles.length > 0 && !weaponDescriptions.length && ( )}
{Arrow} {weapon.name} - {weapon.abilities}
{Arrow} Before selecting targets for this weapon, select one of its profiles to make attacks with.
ABILITIES
); }; const ModelStats = ({ modelStat, index, showName, showWeapons, modelList }) => { let { move, toughness, save, wounds, leadership, name, bs, ws, attacks } = modelStat; if (!wounds) { wounds = "/"; } if (!name.includes("Sgt")) { modelList = modelList.filter((model) => !model.includes("Sgt")); } if (!name.includes("Sergeant")) { modelList = modelList.filter((model) => !model.includes("Sergeant")); } let modelListMatches = modelList.filter((model) => model.includes(name + " w") ); if (modelListMatches.length === 0) { modelListMatches = modelList.filter((model) => model.includes(name)); } modelListMatches = modelListMatches.map((model) => model.replaceAll(name, "") ); return (
{showName && (
{name}
)} {showWeapons && (
{modelListMatches.map((model, index) => (
{model}
))}
)}
); }; const FactionIcon = ({ catalog }) => { return (
); }; const Keywords = ({ keywords }) => { return (
KEYWORDS: {[...keywords].join(", ")}
); }; const Factions = ({ factions }) => { return (
FACTION KEYWORDS: {[...factions].join(", ")}
); }; const Rules = ({ rules }) => { if (!rules || rules.size === 0) return null; return (
RULES: {[...rules.keys()].map((rule) => rule).join(", ")}
); }; const Abilities = ({ abilities }) => { if (!abilities) return null; let keys = [...abilities.keys()]; keys = keys.filter( (key) => key !== "Stratagem: Warlord Trait" && key !== "Stratagem: Relic" ); return (
{abilities && keys.map((ability) => (
{ability}:{" "} {abilities.get(ability)}
))}
); }; const Characteristic = ({ title, characteristic, index }) => { return (
{index === 0 && (
{title}
)} {characteristic}
); }; const FancyBox = ({ children }) => { return (
{children}
); }; const Psykers = ({ title, psykers }) => { return ( {psykers.length > 0 && ( )} {psykers.map((psyker, index) => ( ))} {psykers.length > 0 && ( )}
PSYKER CAST DENY POWERS KNOWN
); }; const Psyker = ({ psyker, index }) => { let { name, cast, deny, powers } = psyker; return ( {cast} {deny} {powers} ); }; const WoundTracker = ({ woundTracker }) => { if (!woundTracker || woundTracker.length === 0) return null; const uniqueWoundTracker = woundTracker.filter((woundTrack, index) => { const firstIndex = woundTracker.findIndex( (woundTrack2) => woundTrack2.name === woundTrack.name ); return firstIndex === index; }); return (
{[...uniqueWoundTracker[0].table.keys()].map((key) => ( ))} {uniqueWoundTracker.map((woundTrack, index) => ( {[...woundTrack.table.values()].map((entry) => ( ))} ))}
{key}
{entry}
); }; const Spells = ({ title, spells }) => { return ( {spells.length > 0 && ( )} {spells.map((spell, index) => ( ))} {spells.length > 0 && ( )}
{title} RANGE WARP CHARGE
); }; const Spell = ({ spell, index, className }) => { let { name, range, manifest, details } = spell; return ( <>
{name}
{range} {manifest} {details} ); }; const getSpellClassNames = (spells, index) => { let differentColor = false; for (let i = 1; i <= index; i++) { let { name } = spells[i]; if (name !== spells[i - 1].name) { differentColor = !differentColor; } } const classes = []; if (differentColor) classes.push("rowOtherColor"); if (index === 0) classes.push("noBorderTop"); if (index > 0 && spells[index].name === spells[index - 1].name) classes.push("noBorderTop"); return classes.join(" "); }; const ForceRules = ({ rules, onePerPage }) => { const [hide, setHide] = useState(false); return (
{[...rules.keys()].map((rule) => (
{rule}: {rules.get(rule)}
))}
); }; const getOverridePrimary = (factions, keywords) => { if ( factions.has("Khorne") && factions.has("Tzeentch") && factions.has("Nurgle") && factions.has("Slaanesh") ) return; if (factions.has("Khorne") || keywords.has("Khorne")) return "#883531"; if (factions.has("Tzeentch") || keywords.has("Tzeentch")) return "#015d68"; if (factions.has("Nurgle") || keywords.has("Nurgle")) return "#5c672b"; if (factions.has("Slaanesh") || keywords.has("Slaanesh")) return "#634c74"; };