@@ -164,7 +261,7 @@ const Unit = ({ unit }) => {
style={{
fontSize: "1.1em",
padding: "1px 8px",
- backgroundColor: "#536766",
+ backgroundColor: "var(--primary-color)",
color: "#fff",
fontWeight: 600,
minHeight: 27,
@@ -184,11 +281,14 @@ const Unit = ({ unit }) => {
);
};
-const ModelStats = ({ modelStats, index, showName }) => {
+const ModelStats = ({ modelStats, index, showName, modelList }) => {
let { move, toughness, save, wounds, leadership, _name } = modelStats;
if (!wounds) {
wounds = "/";
}
+ const modelListMatches = modelList
+ .filter((model) => model.includes(_name))
+ .map((model) => "(" + model.split("(")[1]);
return (
@@ -197,9 +297,28 @@ const ModelStats = ({ modelStats, index, showName }) => {
{showName && (
-
- {_name}
-
+ <>
+
+ {_name}
+
+
+ {modelListMatches.map((model, index) => (
+
{model}
+ ))}
+
+ >
)}
);
@@ -215,7 +334,7 @@ const FactionIcon = () => {
width: 54,
height: 54,
transform: "rotate(-45deg)",
- border: "2px solid #536766",
+ border: "2px solid var(--primary-color)",
overflow: "hidden",
display: "flex",
alignItems: "center",
@@ -236,6 +355,9 @@ const FactionIcon = () => {
};
const Keywords = ({ keywords }) => {
+ keywords = Array.from(keywords).filter(
+ (keyword) => keyword !== "Configuration"
+ );
return (
{
display: "flex",
alignItems: "center",
backgroundColor: "#BCBCBE",
- border: "2px solid #536766",
+ border: "2px solid var(--primary-color)",
width: "calc(100% - 18px)",
backgroundSize: "contain",
minHeight: 54,
gap: 3,
- color: "#101010",
}}
>
KEYWORDS:
@@ -277,7 +398,7 @@ const Factions = ({ factions }) => {
flexDirection: "column",
background: `url(${factionBackground})`,
color: "#fff",
- border: "2px solid #536766",
+ border: "2px solid var(--primary-color)",
borderLeft: "none",
width: "calc(100% - 18px)",
backgroundSize: "contain",
@@ -295,14 +416,14 @@ const Factions = ({ factions }) => {
};
const Rules = ({ rules }) => {
+ if (!rules || rules.size === 0) return null;
return (
RULES:
@@ -314,29 +435,30 @@ const Rules = ({ rules }) => {
};
const Abilities = ({ abilities }) => {
+ if (!abilities) return null;
return (
- {[...abilities.keys()].map((ability) => (
-
- {ability}:{" "}
- {abilities.get(ability)}
-
- ))}
+ {abilities &&
+ [...abilities.keys()].map((ability) => (
+
+ {ability}:{" "}
+ {abilities.get(ability)}
+
+ ))}
);
};
@@ -358,9 +480,10 @@ const FancyBox = ({ children }) => {
return (
{
@@ -396,7 +519,7 @@ const Weapons = ({ title, weapons, modelStats }) => {
- {title} |
+ {title} |
RANGE |
A |
{isMelee ? "WS" : "BS"} |
@@ -428,17 +551,31 @@ const Weapons = ({ title, weapons, modelStats }) => {
const Weapon = ({ weapon, modelStats, isMelee, index }) => {
let { _name, _selectionName, _range, _type, str, _ap, _damage } = weapon;
- const [type, attacks] = _type.split(" ");
+ var lastWhiteSpace = _type.lastIndexOf(" ");
+ const type = _type.substring(0, lastWhiteSpace);
+ const attacks = _type.substring(lastWhiteSpace + 1);
const bs = modelStats[0]._bs;
const ws = modelStats[0]._ws;
const strModel = modelStats[0].str;
const meleeAttacks = modelStats[0]._attacks;
- const differentProfiles = _selectionName !== _name;
- const interestingType = type !== "Melee";
+ if (_name === "Krak grenades") {
+ _name = "Krak grenade";
+ }
+ const differentProfiles =
+ _selectionName !== _name &&
+ !_name.includes("(Shooting)") &&
+ !_name.includes("(Melee)");
+ const interestingType = type && type !== "Melee";
if (differentProfiles && _name.endsWith(" grenades")) {
_name = _name.replace(" grenades", "");
}
+ if (differentProfiles && _name.endsWith(" grenade")) {
+ _name = _name.replace(" grenade", "");
+ }
+ if (differentProfiles && _name.includes(" - ")) {
+ _name = _name.split(" - ")[1];
+ }
return (
{
{_name}
{interestingType && (
[{type}]
@@ -481,22 +622,89 @@ const Weapon = ({ weapon, modelStats, isMelee, index }) => {
};
const calculateWeaponStrength = (strModel, strWeapon) => {
+ if (strWeapon.startsWith("User")) return strModel;
if (strWeapon.startsWith("x"))
return strModel * parseInt(strWeapon.replace("x", ""));
return strModel + parseInt(strWeapon, 10);
};
+const Psykers = ({ title, psykers }) => {
+ return (
+ <>
+ {psykers.length > 0 && (
+
+
+
+
+ 
+
+ |
+ {title} |
+ Cast |
+ Deny |
+
+ Powers Known
+ |
+
+
+ )}
+
+ {psykers.map((psyker, index) => (
+
+ ))}
+ {psykers.length > 0 && (
+
+ |
+
+ )}
+
+ >
+ );
+};
+
+const Psyker = ({ psyker, index }) => {
+ let { _name, _cast, _deny, _powers } = psyker;
+
+ return (
+
+ |
+
+
+ {_name}
+
+ |
+ {_cast} |
+ {_deny} |
+
+ {_powers}
+ |
+
+ );
+};
+
const ForceRules = ({ rules }) => {
return (
{[...rules.keys()]
diff --git a/src/index.css b/src/index.css
index 4fa821a..4d3d478 100644
--- a/src/index.css
+++ b/src/index.css
@@ -17,6 +17,8 @@
* {
box-sizing: border-box;
+ print-color-adjust: exact;
+ -webkit-print-color-adjust: exact;
}
a {
@@ -74,9 +76,10 @@ button:focus-visible {
}
#root {
- max-width: 1000px;
+ max-width: 1100px;
margin-left: auto;
margin-right: auto;
+ color: #111113;
}
.App {
@@ -103,6 +106,7 @@ button:focus-visible {
padding: 1px 8px;
border-bottom: 1px dotted #9e9fa1;
text-align: center;
+ vertical-align: baseline;
}
.emptyRow {
@@ -120,3 +124,17 @@ button:focus-visible {
.differentProfiles + .differentProfiles {
border-top: 1px solid #dfe0e2;
}
+
+@media print {
+ .print-display-none {
+ display: none;
+ }
+
+ .avoid-page-break {
+ break-inside: avoid;
+ }
+
+ #root {
+ min-width: 1000px;
+ }
+}
diff --git a/src/roster40k.js b/src/roster40k.js
index 72945c1..6fc13a8 100644
--- a/src/roster40k.js
+++ b/src/roster40k.js
@@ -1,10 +1,8 @@
export class BaseNotes {
_name = "";
- _customName = "";
_customNotes = "";
name() {
- if (this._customName) return this._customName;
return this._name;
}
@@ -604,7 +602,6 @@ function ExpandBaseNotes(root, obj) {
element = root.parentElement.parentElement;
}
- obj._customName = element.getAttributeNode("customName")?.nodeValue;
let child = element.firstElementChild;
if (child && child.tagName === "customNotes") {
obj._customNotes = child.textContent;