add oc to table, small calc fix, add biome
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": false,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": false
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false,
|
||||||
|
"ignore": ["dist"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "tab"
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"suspicious": {
|
||||||
|
"noArrayIndexKey": "info"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"useSelfClosingElements": {
|
||||||
|
"level": "error",
|
||||||
|
"fix": "safe"
|
||||||
|
},
|
||||||
|
"useTemplate": "info"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"noDangerouslySetInnerHtml": "info"
|
||||||
|
},
|
||||||
|
"a11y": {
|
||||||
|
"noSvgWithoutTitle": "off",
|
||||||
|
"useAltText": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+165
@@ -15,6 +15,7 @@
|
|||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
@@ -424,6 +425,170 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@biomejs/biome": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"biome": "bin/biome"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/biome"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@biomejs/cli-darwin-arm64": "1.9.4",
|
||||||
|
"@biomejs/cli-darwin-x64": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-arm64": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-x64": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
||||||
|
"@biomejs/cli-win32-arm64": "1.9.4",
|
||||||
|
"@biomejs/cli-win32-x64": "1.9.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-darwin-x64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-arm64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-x64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-win32-arm64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-win32-x64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.2",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||||
|
|||||||
+4
-2
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"lint": "biome check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
@@ -26,7 +27,8 @@
|
|||||||
"prettier": "^3.5.2",
|
"prettier": "^3.5.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"vite": "^6.2.5"
|
"vite": "^6.2.5",
|
||||||
|
"@biomejs/biome": "^1.9.4"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
|
|||||||
+30
-25
@@ -54,7 +54,7 @@ export const Roster = ({
|
|||||||
|
|
||||||
const Force = ({ force, onePerPage, colorUserChoice }) => {
|
const Force = ({ force, onePerPage, colorUserChoice }) => {
|
||||||
const { units, factionRules, rules, catalog } = force;
|
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"
|
// units with role "Character" are sorted to the top, then "Battleline", then "Other"
|
||||||
const sortedUnits = units.sort((a, b) => {
|
const sortedUnits = units.sort((a, b) => {
|
||||||
if (a.role === "Character" && b.role !== "Character") return -1;
|
if (a.role === "Character" && b.role !== "Character") return -1;
|
||||||
@@ -295,7 +295,7 @@ const Unit = ({
|
|||||||
height: 80,
|
height: 80,
|
||||||
minWidth: "33%",
|
minWidth: "33%",
|
||||||
}}
|
}}
|
||||||
></div>
|
/>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: 80,
|
height: 80,
|
||||||
@@ -308,7 +308,7 @@ const Unit = ({
|
|||||||
backgroundColor: "var(--primary-color-transparent)",
|
backgroundColor: "var(--primary-color-transparent)",
|
||||||
height: 40,
|
height: 40,
|
||||||
}}
|
}}
|
||||||
></div>
|
/>
|
||||||
{wavyLine}
|
{wavyLine}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -317,7 +317,7 @@ const Unit = ({
|
|||||||
backgroundColor: "var(--primary-color-transparent)",
|
backgroundColor: "var(--primary-color-transparent)",
|
||||||
height: 40,
|
height: 40,
|
||||||
}}
|
}}
|
||||||
></div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -393,6 +393,7 @@ const Unit = ({
|
|||||||
<div className="absolute right-[1px] top-[3px] flex items-center gap-1.5">
|
<div className="absolute right-[1px] top-[3px] flex items-center gap-1.5">
|
||||||
{hasImage && !bgRemoved && (
|
{hasImage && !bgRemoved && (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
|
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
|
||||||
style={{
|
style={{
|
||||||
padding: "1px 4px",
|
padding: "1px 4px",
|
||||||
@@ -419,6 +420,7 @@ const Unit = ({
|
|||||||
}}
|
}}
|
||||||
href={`https://www.google.com/search?udm=2&q=${name}+warhammer+40k+miniature`}
|
href={`https://www.google.com/search?udm=2&q=${name}+warhammer+40k+miniature`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
Search for image
|
Search for image
|
||||||
</a>
|
</a>
|
||||||
@@ -439,15 +441,15 @@ const Unit = ({
|
|||||||
posthog?.capture?.("user_uploaded_image", {
|
posthog?.capture?.("user_uploaded_image", {
|
||||||
unit_name: name,
|
unit_name: name,
|
||||||
});
|
});
|
||||||
if (e.target.files && e.target.files[0]) {
|
if (e.target.files?.[0]) {
|
||||||
let reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (ev) {
|
reader.onload = ((ev) => {
|
||||||
setImage(ev.target.result);
|
setImage(ev.target.result);
|
||||||
setBgRemoved(false);
|
setBgRemoved(false);
|
||||||
// reset position of the image
|
// reset position of the image
|
||||||
trySettingLocalStorage(`positionX_${name}`, 0, () => {});
|
trySettingLocalStorage(`positionX_${name}`, 0, () => {});
|
||||||
trySettingLocalStorage(`positionY_${name}`, 0, () => {});
|
trySettingLocalStorage(`positionY_${name}`, 0, () => {});
|
||||||
}.bind(this);
|
}).bind(this);
|
||||||
reader.readAsDataURL(e.target.files[0]);
|
reader.readAsDataURL(e.target.files[0]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -459,6 +461,7 @@ const Unit = ({
|
|||||||
</label>
|
</label>
|
||||||
{hasImage && (
|
{hasImage && (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
|
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
|
||||||
style={{
|
style={{
|
||||||
padding: "1px 4px",
|
padding: "1px 4px",
|
||||||
@@ -514,7 +517,7 @@ const Unit = ({
|
|||||||
/>
|
/>
|
||||||
<OtherAbilities abilities={abilities} />
|
<OtherAbilities abilities={abilities} />
|
||||||
</table>
|
</table>
|
||||||
<div style={{ flex: "1" }}></div>
|
<div style={{ flex: "1" }} />
|
||||||
<table
|
<table
|
||||||
cellSpacing="0"
|
cellSpacing="0"
|
||||||
className="weapons-table"
|
className="weapons-table"
|
||||||
@@ -602,7 +605,7 @@ const backgrounds = {
|
|||||||
|
|
||||||
const checkAbilitiesForInvul = (abilitiesMap, name) => {
|
const checkAbilitiesForInvul = (abilitiesMap, name) => {
|
||||||
const abilities = [...(abilitiesMap?.keys?.() || [])];
|
const abilities = [...(abilitiesMap?.keys?.() || [])];
|
||||||
for (let ability of abilities) {
|
for (const ability of abilities) {
|
||||||
if (ability.includes(":")) {
|
if (ability.includes(":")) {
|
||||||
// if : then only match if the name is the same
|
// if : then only match if the name is the same
|
||||||
if (
|
if (
|
||||||
@@ -762,8 +765,8 @@ const InvulRow = ({ hasInvul }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex" style={{ gap: "1.2rem", left: 0, top: -16 }}>
|
<div className="flex" style={{ gap: "1.2rem", left: 0, top: -16 }}>
|
||||||
<FancyBox className={"invisible"}></FancyBox>
|
<FancyBox className={"invisible"} />
|
||||||
<FancyBox className={"invisible"}></FancyBox>
|
<FancyBox className={"invisible"} />
|
||||||
<FancyShield>{invulWithoutSpecial}</FancyShield>
|
<FancyShield>{invulWithoutSpecial}</FancyShield>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -817,7 +820,7 @@ const FactionIcon = ({ catalog }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{factionIcons[catalog] ||
|
{factionIcons[catalog] ||
|
||||||
(catalog.includes("Imperium") ? factionIcons["Imperium"] || "" : "")}
|
(catalog.includes("Imperium") ? factionIcons.Imperium || "" : "")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1044,7 +1047,7 @@ const Rules = ({ rules }) => {
|
|||||||
|
|
||||||
const removeInvulnsWithoutSpecialRules = (abilities) => {
|
const removeInvulnsWithoutSpecialRules = (abilities) => {
|
||||||
const filteredAbilities = new Map();
|
const filteredAbilities = new Map();
|
||||||
for (let [key, value] of abilities) {
|
for (const [key, value] of abilities) {
|
||||||
switch (value?.trim()?.toLowerCase().replaceAll(".", "")) {
|
switch (value?.trim()?.toLowerCase().replaceAll(".", "")) {
|
||||||
case "2+":
|
case "2+":
|
||||||
if (!key?.toLowerCase().includes("invulnerable save"))
|
if (!key?.toLowerCase().includes("invulnerable save"))
|
||||||
@@ -1151,8 +1154,8 @@ const Abilities = ({ abilities }) => {
|
|||||||
let filteredAbilities = removeInvulnsWithoutSpecialRules(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
|
// 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();
|
const abilitiesForEnd = new Map();
|
||||||
for (let [key, value] of filteredAbilities) {
|
for (const [key, value] of filteredAbilities) {
|
||||||
if (key.toLowerCase().includes("invulnerable save")) {
|
if (key.toLowerCase().includes("invulnerable save")) {
|
||||||
filteredAbilities.delete(key);
|
filteredAbilities.delete(key);
|
||||||
abilitiesForEnd.set(key, value);
|
abilitiesForEnd.set(key, value);
|
||||||
@@ -1176,11 +1179,11 @@ const Abilities = ({ abilities }) => {
|
|||||||
|
|
||||||
const replacedAbilities = new Map();
|
const replacedAbilities = new Map();
|
||||||
// in the value of the filtered abilites, make certain keywords bold
|
// 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));
|
replacedAbilities.set(key, makeKeywordsBold(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
let keys = [...filteredAbilities.keys()];
|
const keys = [...filteredAbilities.keys()];
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -1206,7 +1209,7 @@ const Abilities = ({ abilities }) => {
|
|||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: replacedAbilities.get(ability),
|
__html: replacedAbilities.get(ability),
|
||||||
}}
|
}}
|
||||||
></span>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -1215,7 +1218,7 @@ const Abilities = ({ abilities }) => {
|
|||||||
|
|
||||||
const makeKeywordsBold = (text) => {
|
const makeKeywordsBold = (text) => {
|
||||||
let newValue = text;
|
let newValue = text;
|
||||||
for (let keyword of boldKeywords) {
|
for (const keyword of boldKeywords) {
|
||||||
newValue = newValue.replace(
|
newValue = newValue.replace(
|
||||||
new RegExp(`\\b${keyword}\\b`, "gi"),
|
new RegExp(`\\b${keyword}\\b`, "gi"),
|
||||||
`<strong>${keyword.toUpperCase()}</strong>`,
|
`<strong>${keyword.toUpperCase()}</strong>`,
|
||||||
@@ -1223,7 +1226,7 @@ const makeKeywordsBold = (text) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// weaponKeywords could be with [] or without
|
// weaponKeywords could be with [] or without
|
||||||
for (let keyword of weaponKeywords) {
|
for (const keyword of weaponKeywords) {
|
||||||
newValue = newValue.replace(
|
newValue = newValue.replace(
|
||||||
new RegExp(`\\[${keyword}\\]`, "gi"),
|
new RegExp(`\\[${keyword}\\]`, "gi"),
|
||||||
`<span style="font-weight: 700; color: var(--primary-color); text-transform: uppercase; line-height: 1.05;">[${keyword.toUpperCase()}]</span>`,
|
`<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 }) => {
|
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 keys.map((key) => {
|
||||||
return (
|
return (
|
||||||
<Fragment key={key}>
|
<Fragment key={key}>
|
||||||
@@ -1333,7 +1338,7 @@ const OtherAbilities = ({ abilities }) => {
|
|||||||
color: "#fff",
|
color: "#fff",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<th></th>
|
<th />
|
||||||
<th colSpan="7" style={{ textAlign: "left" }}>
|
<th colSpan="7" style={{ textAlign: "left" }}>
|
||||||
{key}
|
{key}
|
||||||
</th>
|
</th>
|
||||||
@@ -1342,7 +1347,7 @@ const OtherAbilities = ({ abilities }) => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{[...abilities[key]].map(([name, value]) => (
|
{[...abilities[key]].map(([name, value]) => (
|
||||||
<tr key={name}>
|
<tr key={name}>
|
||||||
<td></td>
|
<td />
|
||||||
<td
|
<td
|
||||||
colSpan={7}
|
colSpan={7}
|
||||||
key={name}
|
key={name}
|
||||||
@@ -1413,7 +1418,7 @@ const ForceRules = ({ rules, onePerPage }) => {
|
|||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: makeKeywordsBold(rules.get(rule)),
|
__html: makeKeywordsBold(rules.get(rule)),
|
||||||
}}
|
}}
|
||||||
></span>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+156
-25
@@ -31,16 +31,6 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
return a.name.localeCompare(b.name);
|
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
|
// Prepare data for the bar chart, group by toughness and sum points cost
|
||||||
const groupedByToughness = units.reduce((acc, unit) => {
|
const groupedByToughness = units.reduce((acc, unit) => {
|
||||||
const toughness = unit.modelStats?.[0]?.toughness || 0;
|
const toughness = unit.modelStats?.[0]?.toughness || 0;
|
||||||
@@ -56,7 +46,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
|
|
||||||
const groupedChartDataToughness = Object.entries(groupedByToughness)
|
const groupedChartDataToughness = Object.entries(groupedByToughness)
|
||||||
.map(([toughness, { totalPoints, unitNames }]) => ({
|
.map(([toughness, { totalPoints, unitNames }]) => ({
|
||||||
toughness: parseInt(toughness, 10),
|
toughness: Number.parseInt(toughness, 10),
|
||||||
totalPoints,
|
totalPoints,
|
||||||
unitNames,
|
unitNames,
|
||||||
}))
|
}))
|
||||||
@@ -77,7 +67,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
|
|
||||||
const groupedChartDataMovement = Object.entries(groupedByMovement)
|
const groupedChartDataMovement = Object.entries(groupedByMovement)
|
||||||
.map(([movement, { totalPoints, unitNames }]) => ({
|
.map(([movement, { totalPoints, unitNames }]) => ({
|
||||||
movement: parseInt(movement, 10),
|
movement: Number.parseInt(movement, 10),
|
||||||
totalPoints,
|
totalPoints,
|
||||||
unitNames,
|
unitNames,
|
||||||
}))
|
}))
|
||||||
@@ -98,7 +88,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
|
|
||||||
const groupedChartDataSave = Object.entries(groupedBySave)
|
const groupedChartDataSave = Object.entries(groupedBySave)
|
||||||
.map(([save, { totalPoints, unitNames }]) => ({
|
.map(([save, { totalPoints, unitNames }]) => ({
|
||||||
save: parseInt(save, 10),
|
save: Number.parseInt(save, 10),
|
||||||
totalPoints,
|
totalPoints,
|
||||||
unitNames,
|
unitNames,
|
||||||
}))
|
}))
|
||||||
@@ -125,7 +115,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
<span className="print-display-none">Don't print this card.</span>
|
<span className="print-display-none">Don't print this card.</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={"flex flex-col " + (hide ? "print-display-none" : "")}>
|
<div className={`flex flex-col ${hide ? "print-display-none" : ""}`}>
|
||||||
<div
|
<div
|
||||||
className="flex justify-between"
|
className="flex justify-between"
|
||||||
style={{
|
style={{
|
||||||
@@ -170,6 +160,16 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
>
|
>
|
||||||
Wounds
|
Wounds
|
||||||
</div>
|
</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
|
<div
|
||||||
className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right"
|
className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right"
|
||||||
style={{
|
style={{
|
||||||
@@ -184,7 +184,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="table-row-group" style={{ lineHeight: 1.05 }}>
|
<div className="table-row-group" style={{ lineHeight: 1.05 }}>
|
||||||
{sortedUnits.map((unit, index) => {
|
{sortedUnits.map((unit, index) => {
|
||||||
let { name, cost, models } = unit;
|
const { name, cost, models } = unit;
|
||||||
const count =
|
const count =
|
||||||
models?.filter((model) => model.count > 0)?.length === 1
|
models?.filter((model) => model.count > 0)?.length === 1
|
||||||
? models?.[0]?.count || 1
|
? models?.[0]?.count || 1
|
||||||
@@ -195,7 +195,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
className={`table-row ${index % 2 === 0 ? "" : "bg-[#c4c4c480]"}`}
|
className={`table-row ${index % 2 === 0 ? "" : "bg-[#c4c4c480]"}`}
|
||||||
>
|
>
|
||||||
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1">
|
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1">
|
||||||
{count > 1 ? count + "x " : ""}
|
{count > 1 ? `${count}x ` : ""}
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
|
<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],
|
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;
|
return sum + (stat.wounds || 0) * modelCount;
|
||||||
}, 0)}
|
}, 0)}
|
||||||
</div>
|
</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">
|
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
|
||||||
{cost.points} pts
|
{cost.points} pts
|
||||||
</div>
|
</div>
|
||||||
@@ -241,7 +287,94 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
Total
|
Total
|
||||||
</div>
|
</div>
|
||||||
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
|
<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>
|
||||||
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
|
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
|
||||||
{sortedUnits.reduce((sum, unit) => sum + unit.cost.points, 0)}{" "}
|
{sortedUnits.reduce((sum, unit) => sum + unit.cost.points, 0)}{" "}
|
||||||
@@ -255,7 +388,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
<ChartComponent
|
<ChartComponent
|
||||||
data={{
|
data={{
|
||||||
labels: groupedChartDataMovement.map(
|
labels: groupedChartDataMovement.map(
|
||||||
(data) => data.movement + '"',
|
(data) => `${data.movement}"`,
|
||||||
),
|
),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
@@ -276,7 +409,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
<ChartComponent
|
<ChartComponent
|
||||||
data={{
|
data={{
|
||||||
labels: groupedChartDataToughness.map(
|
labels: groupedChartDataToughness.map(
|
||||||
(data) => data.toughness + "",
|
(data) => `${data.toughness}`,
|
||||||
),
|
),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
@@ -296,7 +429,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
|||||||
|
|
||||||
<ChartComponent
|
<ChartComponent
|
||||||
data={{
|
data={{
|
||||||
labels: groupedChartDataSave.map((data) => data.save + "+"),
|
labels: groupedChartDataSave.map((data) => `${data.save}+`),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: "Total Cost (pts)",
|
label: "Total Cost (pts)",
|
||||||
@@ -356,10 +489,8 @@ const ChartComponent = ({ data, title }) => {
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
displayColors: false,
|
displayColors: false,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: function (context) {
|
title: (context) => "",
|
||||||
return "";
|
label: (context) => {
|
||||||
},
|
|
||||||
label: function (context) {
|
|
||||||
// return unit names for the bar
|
// return unit names for the bar
|
||||||
return context.dataset.unitNames[context.dataIndex];
|
return context.dataset.unitNames[context.dataIndex];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ export const Weapons = ({ title, weapons, modelStats, forceRules }) => {
|
|||||||
))}
|
))}
|
||||||
{weapons.length > 0 && (
|
{weapons.length > 0 && (
|
||||||
<tr className="emptyRow">
|
<tr className="emptyRow">
|
||||||
<td style={{ borderTop: "none" }}></td>
|
<td style={{ borderTop: "none" }} />
|
||||||
<td colSpan={7}></td>
|
<td colSpan={7} />
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -158,7 +158,7 @@ const Weapon = ({ weapon, previousWeapon, nextWeapon, isMelee, className }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
{abilities && abilities !== "-" && (
|
{abilities && abilities !== "-" && (
|
||||||
<tr className={className + " noBorderTop"}>
|
<tr className={className + " noBorderTop"}>
|
||||||
<td style={{ backgroundColor: "#dfe0e2" }}></td>
|
<td style={{ backgroundColor: "#dfe0e2" }} />
|
||||||
<td
|
<td
|
||||||
colSpan="7"
|
colSpan="7"
|
||||||
style={{
|
style={{
|
||||||
@@ -180,7 +180,7 @@ const Weapon = ({ weapon, previousWeapon, nextWeapon, isMelee, className }) => {
|
|||||||
const getWeaponClassNames = (weapons, index) => {
|
const getWeaponClassNames = (weapons, index) => {
|
||||||
let differentColor = false;
|
let differentColor = false;
|
||||||
for (let i = 1; i <= index; i++) {
|
for (let i = 1; i <= index; i++) {
|
||||||
let { selectionName } = weapons[i];
|
const { selectionName } = weapons[i];
|
||||||
if (!selectionName && !weapons[i - 1].selectionName) {
|
if (!selectionName && !weapons[i - 1].selectionName) {
|
||||||
if (weapons[i].name !== weapons[i - 1].name) {
|
if (weapons[i].name !== weapons[i - 1].name) {
|
||||||
differentColor = !differentColor;
|
differentColor = !differentColor;
|
||||||
|
|||||||
Reference in New Issue
Block a user