add oc to table, small calc fix, add biome

This commit is contained in:
NilsUeter
2025-04-07 19:25:39 +02:00
parent 17309ece0d
commit 8a7cbf7f14
6 changed files with 406 additions and 56 deletions
+30 -25
View File
@@ -54,7 +54,7 @@ export const Roster = ({
const Force = ({ force, onePerPage, colorUserChoice }) => {
const { units, factionRules, rules, catalog } = force;
var mergedRules = new Map([...factionRules, ...rules]);
const mergedRules = new Map([...factionRules, ...rules]);
// units with role "Character" are sorted to the top, then "Battleline", then "Other"
const sortedUnits = units.sort((a, b) => {
if (a.role === "Character" && b.role !== "Character") return -1;
@@ -295,7 +295,7 @@ const Unit = ({
height: 80,
minWidth: "33%",
}}
></div>
/>
<div
style={{
height: 80,
@@ -308,7 +308,7 @@ const Unit = ({
backgroundColor: "var(--primary-color-transparent)",
height: 40,
}}
></div>
/>
{wavyLine}
</div>
<div
@@ -317,7 +317,7 @@ const Unit = ({
backgroundColor: "var(--primary-color-transparent)",
height: 40,
}}
></div>
/>
</div>
<div
style={{
@@ -393,6 +393,7 @@ const Unit = ({
<div className="absolute right-[1px] top-[3px] flex items-center gap-1.5">
{hasImage && !bgRemoved && (
<button
type="button"
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
style={{
padding: "1px 4px",
@@ -419,6 +420,7 @@ const Unit = ({
}}
href={`https://www.google.com/search?udm=2&q=${name}+warhammer+40k+miniature`}
target="_blank"
rel="noreferrer"
>
Search for image
</a>
@@ -439,15 +441,15 @@ const Unit = ({
posthog?.capture?.("user_uploaded_image", {
unit_name: name,
});
if (e.target.files && e.target.files[0]) {
let reader = new FileReader();
reader.onload = function (ev) {
if (e.target.files?.[0]) {
const reader = new FileReader();
reader.onload = ((ev) => {
setImage(ev.target.result);
setBgRemoved(false);
// reset position of the image
trySettingLocalStorage(`positionX_${name}`, 0, () => {});
trySettingLocalStorage(`positionY_${name}`, 0, () => {});
}.bind(this);
}).bind(this);
reader.readAsDataURL(e.target.files[0]);
}
}}
@@ -459,6 +461,7 @@ const Unit = ({
</label>
{hasImage && (
<button
type="button"
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
style={{
padding: "1px 4px",
@@ -514,7 +517,7 @@ const Unit = ({
/>
<OtherAbilities abilities={abilities} />
</table>
<div style={{ flex: "1" }}></div>
<div style={{ flex: "1" }} />
<table
cellSpacing="0"
className="weapons-table"
@@ -602,7 +605,7 @@ const backgrounds = {
const checkAbilitiesForInvul = (abilitiesMap, name) => {
const abilities = [...(abilitiesMap?.keys?.() || [])];
for (let ability of abilities) {
for (const ability of abilities) {
if (ability.includes(":")) {
// if : then only match if the name is the same
if (
@@ -762,8 +765,8 @@ const InvulRow = ({ hasInvul }) => {
}}
>
<div className="flex" style={{ gap: "1.2rem", left: 0, top: -16 }}>
<FancyBox className={"invisible"}></FancyBox>
<FancyBox className={"invisible"}></FancyBox>
<FancyBox className={"invisible"} />
<FancyBox className={"invisible"} />
<FancyShield>{invulWithoutSpecial}</FancyShield>
</div>
<span
@@ -817,7 +820,7 @@ const FactionIcon = ({ catalog }) => {
}}
>
{factionIcons[catalog] ||
(catalog.includes("Imperium") ? factionIcons["Imperium"] || "" : "")}
(catalog.includes("Imperium") ? factionIcons.Imperium || "" : "")}
</div>
</div>
);
@@ -1044,7 +1047,7 @@ const Rules = ({ rules }) => {
const removeInvulnsWithoutSpecialRules = (abilities) => {
const filteredAbilities = new Map();
for (let [key, value] of abilities) {
for (const [key, value] of abilities) {
switch (value?.trim()?.toLowerCase().replaceAll(".", "")) {
case "2+":
if (!key?.toLowerCase().includes("invulnerable save"))
@@ -1151,8 +1154,8 @@ const Abilities = ({ abilities }) => {
let filteredAbilities = removeInvulnsWithoutSpecialRules(abilities);
// sort abilites so that "Leader" is always at the bottom, "Supreme Commander" also bottom but before "Leader", same for any invulnerable save
let abilitiesForEnd = new Map();
for (let [key, value] of filteredAbilities) {
const abilitiesForEnd = new Map();
for (const [key, value] of filteredAbilities) {
if (key.toLowerCase().includes("invulnerable save")) {
filteredAbilities.delete(key);
abilitiesForEnd.set(key, value);
@@ -1176,11 +1179,11 @@ const Abilities = ({ abilities }) => {
const replacedAbilities = new Map();
// in the value of the filtered abilites, make certain keywords bold
for (let [key, value] of filteredAbilities) {
for (const [key, value] of filteredAbilities) {
replacedAbilities.set(key, makeKeywordsBold(value));
}
let keys = [...filteredAbilities.keys()];
const keys = [...filteredAbilities.keys()];
return (
<div
style={{
@@ -1206,7 +1209,7 @@ const Abilities = ({ abilities }) => {
dangerouslySetInnerHTML={{
__html: replacedAbilities.get(ability),
}}
></span>
/>
</div>
))}
</div>
@@ -1215,7 +1218,7 @@ const Abilities = ({ abilities }) => {
const makeKeywordsBold = (text) => {
let newValue = text;
for (let keyword of boldKeywords) {
for (const keyword of boldKeywords) {
newValue = newValue.replace(
new RegExp(`\\b${keyword}\\b`, "gi"),
`<strong>${keyword.toUpperCase()}</strong>`,
@@ -1223,7 +1226,7 @@ const makeKeywordsBold = (text) => {
}
// weaponKeywords could be with [] or without
for (let keyword of weaponKeywords) {
for (const keyword of weaponKeywords) {
newValue = newValue.replace(
new RegExp(`\\[${keyword}\\]`, "gi"),
`<span style="font-weight: 700; color: var(--primary-color); text-transform: uppercase; line-height: 1.05;">[${keyword.toUpperCase()}]</span>`,
@@ -1322,7 +1325,9 @@ const FancyShield = ({ children, className, style }) => {
};
const OtherAbilities = ({ abilities }) => {
let keys = [...Object.keys(abilities)?.filter((key) => key !== "Abilities")];
const keys = abilities
? [...Object.keys(abilities).filter((key) => key !== "Abilities")]
: [];
return keys.map((key) => {
return (
<Fragment key={key}>
@@ -1333,7 +1338,7 @@ const OtherAbilities = ({ abilities }) => {
color: "#fff",
}}
>
<th></th>
<th />
<th colSpan="7" style={{ textAlign: "left" }}>
{key}
</th>
@@ -1342,7 +1347,7 @@ const OtherAbilities = ({ abilities }) => {
<tbody>
{[...abilities[key]].map(([name, value]) => (
<tr key={name}>
<td></td>
<td />
<td
colSpan={7}
key={name}
@@ -1413,7 +1418,7 @@ const ForceRules = ({ rules, onePerPage }) => {
dangerouslySetInnerHTML={{
__html: makeKeywordsBold(rules.get(rule)),
}}
></span>
/>
</div>
))}
</div>
+156 -25
View File
@@ -31,16 +31,6 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
return a.name.localeCompare(b.name);
});
// Calculate total wound count
const totalWoundCount = units.reduce((sum, unit) => {
const modelCounts = unit.models?.map((model) => model.count) || [1];
const totalWounds = unit.modelStats?.reduce((woundSum, stat, index) => {
const count = modelCounts[index] || 1;
return woundSum + (stat.wounds || 0) * count;
}, 0);
return sum + totalWounds;
}, 0);
// Prepare data for the bar chart, group by toughness and sum points cost
const groupedByToughness = units.reduce((acc, unit) => {
const toughness = unit.modelStats?.[0]?.toughness || 0;
@@ -56,7 +46,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
const groupedChartDataToughness = Object.entries(groupedByToughness)
.map(([toughness, { totalPoints, unitNames }]) => ({
toughness: parseInt(toughness, 10),
toughness: Number.parseInt(toughness, 10),
totalPoints,
unitNames,
}))
@@ -77,7 +67,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
const groupedChartDataMovement = Object.entries(groupedByMovement)
.map(([movement, { totalPoints, unitNames }]) => ({
movement: parseInt(movement, 10),
movement: Number.parseInt(movement, 10),
totalPoints,
unitNames,
}))
@@ -98,7 +88,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
const groupedChartDataSave = Object.entries(groupedBySave)
.map(([save, { totalPoints, unitNames }]) => ({
save: parseInt(save, 10),
save: Number.parseInt(save, 10),
totalPoints,
unitNames,
}))
@@ -125,7 +115,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
<span className="print-display-none">Don't print this card.</span>
</label>
</div>
<div className={"flex flex-col " + (hide ? "print-display-none" : "")}>
<div className={`flex flex-col ${hide ? "print-display-none" : ""}`}>
<div
className="flex justify-between"
style={{
@@ -170,6 +160,16 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
>
Wounds
</div>
<div
className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right"
style={{
fontSize: "1.1em",
color: "#fff",
fontWeight: 600,
}}
>
OC
</div>
<div
className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right"
style={{
@@ -184,7 +184,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
</div>
<div className="table-row-group" style={{ lineHeight: 1.05 }}>
{sortedUnits.map((unit, index) => {
let { name, cost, models } = unit;
const { name, cost, models } = unit;
const count =
models?.filter((model) => model.count > 0)?.length === 1
? models?.[0]?.count || 1
@@ -195,7 +195,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
className={`table-row ${index % 2 === 0 ? "" : "bg-[#c4c4c480]"}`}
>
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1">
{count > 1 ? count + "x " : ""}
{count > 1 ? `${count}x ` : ""}
{name}
</div>
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
@@ -226,10 +226,56 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
unit.models[0],
);
const modelCount = modelName?.count || 1;
const modelCount =
unit.modelStats?.length === 1
? // sum of all counts of the models
models?.reduce(
(sum, model) => sum + model.count,
0,
) || 1
: modelName?.count || 1;
return sum + (stat.wounds || 0) * modelCount;
}, 0)}
</div>
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
{unit.modelStats?.reduce((sum, stat, index) => {
const modelName = unit.models?.reduce(
(bestMatch, model) => {
const similarity = (str1, str2) => {
let matches = 0;
for (
let i = 0;
i < Math.min(str1.length, str2.length);
i++
) {
if (str1[i] === str2[i]) matches++; // Match only if characters are at the same position
}
return matches;
};
const currentSimilarity = similarity(
stat.name,
model.name,
);
return currentSimilarity >
similarity(stat.name, bestMatch.name)
? model
: bestMatch;
},
unit.models[0],
);
const modelCount =
unit.modelStats?.length === 1
? // sum of all counts of the models
models?.reduce(
(sum, model) => sum + model.count,
0,
) || 1
: modelName?.count || 1;
return sum + (stat.oc || 0) * modelCount;
}, 0)}
</div>
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
{cost.points} pts
</div>
@@ -241,7 +287,94 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
Total
</div>
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
{totalWoundCount}
{/* Sum of all wound values */}
{sortedUnits.reduce((sum, unit) => {
return (
sum +
unit.modelStats?.reduce((woundSum, stat, index) => {
const modelName = unit.models?.reduce(
(bestMatch, model) => {
const similarity = (str1, str2) => {
let matches = 0;
for (
let i = 0;
i < Math.min(str1.length, str2.length);
i++
) {
if (str1[i] === str2[i]) matches++; // Match only if characters are at the same position
}
return matches;
};
const currentSimilarity = similarity(
stat.name,
model.name,
);
return currentSimilarity >
similarity(stat.name, bestMatch.name)
? model
: bestMatch;
},
unit.models[0],
);
const modelCount =
unit.modelStats?.length === 1
? // sum of all counts of the models
unit?.models?.reduce(
(sum, model) => sum + model.count,
0,
) || 1
: modelName?.count || 1;
return woundSum + (stat.wounds || 0) * modelCount;
}, 0)
);
}, 0)}
</div>
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
{/* Sum of all OC values */}
{sortedUnits.reduce((sum, unit) => {
return (
sum +
unit.modelStats?.reduce((ocSum, stat, index) => {
const modelName = unit.models?.reduce(
(bestMatch, model) => {
const similarity = (str1, str2) => {
let matches = 0;
for (
let i = 0;
i < Math.min(str1.length, str2.length);
i++
) {
if (str1[i] === str2[i]) matches++; // Match only if characters are at the same position
}
return matches;
};
const currentSimilarity = similarity(
stat.name,
model.name,
);
return currentSimilarity >
similarity(stat.name, bestMatch.name)
? model
: bestMatch;
},
unit.models[0],
);
const modelCount =
unit.modelStats?.length === 1
? // sum of all counts of the models
unit?.models?.reduce(
(sum, model) => sum + model.count,
0,
) || 1
: modelName?.count || 1;
return ocSum + (stat.oc || 0) * modelCount;
}, 0)
);
}, 0)}
</div>
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
{sortedUnits.reduce((sum, unit) => sum + unit.cost.points, 0)}{" "}
@@ -255,7 +388,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
<ChartComponent
data={{
labels: groupedChartDataMovement.map(
(data) => data.movement + '"',
(data) => `${data.movement}"`,
),
datasets: [
{
@@ -276,7 +409,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
<ChartComponent
data={{
labels: groupedChartDataToughness.map(
(data) => data.toughness + "",
(data) => `${data.toughness}`,
),
datasets: [
{
@@ -296,7 +429,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
<ChartComponent
data={{
labels: groupedChartDataSave.map((data) => data.save + "+"),
labels: groupedChartDataSave.map((data) => `${data.save}+`),
datasets: [
{
label: "Total Cost (pts)",
@@ -356,10 +489,8 @@ const ChartComponent = ({ data, title }) => {
tooltip: {
displayColors: false,
callbacks: {
title: function (context) {
return "";
},
label: function (context) {
title: (context) => "",
label: (context) => {
// return unit names for the bar
return context.dataset.unitNames[context.dataIndex];
},
+4 -4
View File
@@ -50,8 +50,8 @@ export const Weapons = ({ title, weapons, modelStats, forceRules }) => {
))}
{weapons.length > 0 && (
<tr className="emptyRow">
<td style={{ borderTop: "none" }}></td>
<td colSpan={7}></td>
<td style={{ borderTop: "none" }} />
<td colSpan={7} />
</tr>
)}
</tbody>
@@ -158,7 +158,7 @@ const Weapon = ({ weapon, previousWeapon, nextWeapon, isMelee, className }) => {
</tr>
{abilities && abilities !== "-" && (
<tr className={className + " noBorderTop"}>
<td style={{ backgroundColor: "#dfe0e2" }}></td>
<td style={{ backgroundColor: "#dfe0e2" }} />
<td
colSpan="7"
style={{
@@ -180,7 +180,7 @@ const Weapon = ({ weapon, previousWeapon, nextWeapon, isMelee, className }) => {
const getWeaponClassNames = (weapons, index) => {
let differentColor = false;
for (let i = 1; i <= index; i++) {
let { selectionName } = weapons[i];
const { selectionName } = weapons[i];
if (!selectionName && !weapons[i - 1].selectionName) {
if (weapons[i].name !== weapons[i - 1].name) {
differentColor = !differentColor;