Commit 03839c50 authored by Joe Ziemba's avatar Joe Ziemba
Browse files

33 regression test statblock

parent 96d73699
......@@ -19,7 +19,10 @@
"test:watch-cover": "npm run test -- --coverage --onlyChanged=false",
"test": "craco test",
"test:all": "craco test --watchAll=false --onlyChanged=false",
"regress": "testcafe chrome ./testing/main.spec.js -r spec"
"e2e:chrome": "testcafe chrome:headless testing/main.e2e.js -r spec,json:testing/reports/chrome.json",
"e2e:ff": "testcafe firefox:headless testing/main.e2e.js -r spec,json:testing/reports/ff.json",
"e2e:edge": "testcafe edge testing/main.e2e.js -r spec,json:testing/reports/edge.json",
"e2e:all": "npm run e2e:chrome && npm run e2e:ff"
},
"author": "Joe Ziemba",
"license": "ISC",
......
......@@ -50,6 +50,6 @@
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root" class="bg-gray-100"></div>
<div id="root" class="bg-gray-100" />
</body>
</html>
Start-Process cmd -ArgumentList '/C npm run e2e:chrome && PAUSE'
Start-Process cmd -ArgumentList '/C npm run e2e:ff && PAUSE'
Start-Process cmd -ArgumentList '/C npm run e2e:edge && PAUSE'
\ No newline at end of file
import React, { useContext } from "react"
import { SBG_Select } from "components"
import { StatblockContext } from "context"
import { CheckboxButton } from "components/sbg/CheckboxButton"
export const AbilityScoresForm = () => {
const abilities = ["str", "dex", "con", "int", "wis", "cha"]
const { stats, updateAbility } = useContext(StatblockContext)
const { stats, updateAbility, toggleSave } = useContext(StatblockContext)
return (
<div className="grid grid-rows-1 grid-cols-6 gap-4">
{abilities.map((ability) => {
return (
<div className="col-span-1" key={ability}>
<SBG_Select
id={ability + "-select"}
label={ability.toUpperCase()}
options={global.abilityScores}
value={stats.abilities[ability]}
......@@ -18,6 +20,13 @@ export const AbilityScoresForm = () => {
onChange={(e) => updateAbility(ability, e.target.value)}
center
/>
<CheckboxButton
id={ability + "-save"}
fieldName={ability}
label="save"
checked={stats.saves[ability]}
onClick={() => toggleSave(ability)}
/>
</div>
)
})}
......
......@@ -11,10 +11,11 @@ export const ActionsForm = ({ legendary }) => {
return (
<React.Fragment>
{stats.actions.map((action, i) => {
if (action) {
if (["Melee", "Ranged"].includes(action.type)) {
return (
<AttackForm
action={action}
index={i}
key={i}
updateAction={updateAction}
deleteAction={deleteAction}
......@@ -34,6 +35,7 @@ export const ActionsForm = ({ legendary }) => {
})}
<div className="mt-4">
<NavButton
id="add-action"
color="red"
className="mr-2"
onClick={() => addAction("General")}
......@@ -42,6 +44,7 @@ export const ActionsForm = ({ legendary }) => {
Action
</NavButton>
<NavButton
id="add-melee-attack"
color="red"
className="mr-2"
onClick={() => addAction("Melee")}
......@@ -50,6 +53,7 @@ export const ActionsForm = ({ legendary }) => {
Melee Attack
</NavButton>
<NavButton
id="add-ranged-attack"
color="red"
className=""
onClick={() => addAction("Ranged")}
......
......@@ -8,6 +8,7 @@ export const BasicsForm = () => {
return (
<>
<SBG_Input
id="name-input"
type="text"
label={"Name"}
placeholder={"Creature Name"}
......@@ -19,6 +20,7 @@ export const BasicsForm = () => {
<div className="flex mt-2">
<div className="flex-1 mr-4">
<SBG_Select
id="size-select"
label={"Size"}
options={["Small", "Medium", "Large", "Huge", "Gargantuan"]}
value={stats.size}
......@@ -28,6 +30,7 @@ export const BasicsForm = () => {
</div>
<div className="flex-1 mr-4">
<SBG_Select
id="type-select"
label={"Creature Type"}
options={global.creatureTypes}
value={stats.creatureType}
......@@ -37,6 +40,7 @@ export const BasicsForm = () => {
</div>
<div className="flex-1">
<SBG_Input
id="prof-input"
type="number"
label={"Proficiency"}
placeholder={""}
......@@ -51,6 +55,7 @@ export const BasicsForm = () => {
<div className="flex-1 mr-4">
<h4 className="form-header mb-1 font-bold">Armor Class</h4>
<SBG_Input
id="ac-input"
label={"Score"}
placeholder={""}
value={stats.ac.score}
......@@ -59,6 +64,7 @@ export const BasicsForm = () => {
type="number"
/>
<SBG_Input
id="ac-support-input"
type="text"
label={"Support"}
placeholder={""}
......@@ -73,6 +79,7 @@ export const BasicsForm = () => {
<h4 className="form-header mb-1">Hit Points</h4>
<SBG_Select
id="hit-die-select"
label={"Hit Die"}
options={[4, 6, 8, 10, 12, 20]}
value={stats.hp.hitDie}
......@@ -80,6 +87,7 @@ export const BasicsForm = () => {
onChange={updateHP}
/>
<SBG_Input
id="number-hit-die-input"
type="number"
label={"Number of Dice"}
placeholder={""}
......@@ -92,6 +100,7 @@ export const BasicsForm = () => {
<div className="flex-1 flex flex-wrap">
<h4 className="form-header flex-full mb-1">Speed</h4>
<SBG_Input
id="base-speed"
type="number"
label={"Base Speed"}
placeholder={""}
......@@ -102,6 +111,7 @@ export const BasicsForm = () => {
<div className="flex">
<div className="flex-1 mr-4 mt-2">
<SBG_Input
id="fly-speed"
type="number"
label={"Fly"}
placeholder={""}
......@@ -112,6 +122,7 @@ export const BasicsForm = () => {
</div>
<div className="flex-1 mt-2">
<SBG_Input
id="swim-speed"
type="number"
label={"Swim"}
placeholder={""}
......
......@@ -11,13 +11,14 @@ export const FeatureForm = () => {
return (
<FeatureBlock
key={i}
index={i}
feature={feature}
deleteFunc={deleteFeature}
updateFunc={updateFeature}
/>
)
})}
<NavButton color="red" onClick={addFeature}>
<NavButton id="add-feature" color="red" onClick={addFeature}>
<i className="fa fa-plus mr-2" />
Feature
</NavButton>
......
......@@ -3,8 +3,13 @@ import { SBG_Input, NavButton, FeatureBlock } from "components"
import { StatblockContext } from "context"
export const LegendaryActionsForm = () => {
const { stats, updateAction, deleteAction, updateState, addAction } =
useContext(StatblockContext)
const {
stats,
updateAction,
deleteAction,
updateState,
addLegendaryAction,
} = useContext(StatblockContext)
return (
<>
......@@ -31,6 +36,7 @@ export const LegendaryActionsForm = () => {
return (
<FeatureBlock
key={i}
index={i}
typeText="Action"
updateFunc={(e) => updateAction(e, action.id, true)}
deleteFunc={() => deleteAction(action.id, true)}
......@@ -40,9 +46,10 @@ export const LegendaryActionsForm = () => {
})}
<NavButton
id="add-legendary-action"
color="red"
className="mr-2 mt-2"
onClick={() => addAction("General")}
onClick={() => addLegendaryAction("General")}
>
<i className="fa fa-plus mr-2" />
Action
......
......@@ -7,55 +7,64 @@ export const PropertyForm = () => {
const { stats, updatePropertyList } = useContext(StatblockContext)
return (
<div className="text-sm">
<label>Condition Immunities</label>
<Typeahead
multiple
options={global.conditions}
selected={stats.conditionImmune}
onChange={(selected) =>
updatePropertyList(selected, "conditionImmune")
}
/>
<label>Damage Immunities</label>
<Typeahead
multiple
options={global.damageTypes}
selected={stats.immune}
onChange={(selected) => updatePropertyList(selected, "immune")}
/>
<label>Resistances</label>
<Typeahead
multiple
options={global.damageTypes}
selected={stats.resists}
onChange={(selected) => updatePropertyList(selected, "resists")}
/>
<label>Vulnerabilities</label>
<Typeahead
multiple
options={global.damageTypes}
selected={stats.vulnerable}
onChange={(selected) => updatePropertyList(selected, "vulnerable")}
/>
<label>Skill Proficiencies</label>
<Typeahead
multiple
options={global.skills.map((skill) => skill.name)}
selected={stats.skills}
onChange={(selected) => updatePropertyList(selected, "skills")}
/>
<label>Languages</label>
<Typeahead
multiple
options={global.languages}
selected={stats.langs}
onChange={(selected) => updatePropertyList(selected, "langs")}
/>
<div id="skills-input">
Skill Proficiencies
<Typeahead
multiple
options={global.skills.map((skill) => skill.name)}
selected={stats.skills}
onChange={(selected) => updatePropertyList(selected, "skills")}
/>
</div>
<div id="condition-immunities-input">
Condition Immunities
<Typeahead
multiple
options={global.conditions}
selected={stats.conditionImmune}
onChange={(selected) =>
updatePropertyList(selected, "conditionImmune")
}
/>
</div>
<div id="damage-immunities-input">
Damage Immunities
<Typeahead
multiple
options={global.damageTypes}
selected={stats.immune}
onChange={(selected) => updatePropertyList(selected, "immune")}
/>
</div>
<div id="damage-resistances-input">
Resistances
<Typeahead
multiple
options={global.damageTypes}
selected={stats.resists}
onChange={(selected) => updatePropertyList(selected, "resists")}
/>
</div>
<div id="damage-vulnerabilities-input">
Vulnerabilities
<Typeahead
multiple
options={global.damageTypes}
selected={stats.vulnerable}
onChange={(selected) =>
updatePropertyList(selected, "vulnerable")
}
/>
</div>
<div id="languages-input">
Languages
<Typeahead
multiple
options={global.languages}
selected={stats.langs}
onChange={(selected) => updatePropertyList(selected, "langs")}
/>
</div>
</div>
)
}
import React from "react"
import { DisplayProperty, DisplayAttack } from "components"
import { DisplayProperty } from "components"
const orderedAbilities = ["str", "dex", "con", "int", "wis", "cha"]
export const StatBlockDisplay = ({ stats, exportView }) => {
let dieNum = +stats.hp.dieNum
let mod = dieNum * stats.abilities.conMod
export class StatBlockDisplay extends React.Component {
renderAbilities() {
let { abilities } = this.props.stats
let orderedAbilities = ["str", "dex", "con", "int", "wis", "cha"]
return orderedAbilities.map((ability) => {
let score = abilities[ability]
let mod = abilities[ability + "Mod"]
let hitPoints = `${stats.calculatedHP} (${dieNum}d${stats.hp.hitDie} ${
mod < 0 ? "" : "+"
} ${mod})`
// Build save string
let saveText = orderedAbilities.map((ability) => {
if (stats.saves[ability])
return (
<div className="ability" key={ability}>
<h4>{ability}</h4>
<span>
{score} ({mod < 0 ? "" : "+"}
{mod})
</span>
</div>
)
})
}
renderFeatures() {
let { features } = this.props.stats
return features.map((feature) => {
return (
<DisplayProperty
key={feature.title}
title={feature.title}
content={feature.content}
block
/>
ability.toUpperCase() +
" +" +
(+stats.abilities[ability + "Mod"] + +stats.proficiency)
)
})
}
renderActions(legendary) {
let { actions } = this.props.stats
if (legendary) {
actions = this.props.stats.legendaryActions
}
return actions.map((action, i) => {
if (["Melee", "Ranged"].includes(action.type)) {
let { dieNum, dmgDie, prof, dex, reach, targets, dmgType } = action
// Get hit mod
let toHit = this.props.stats.abilities.strMod
if (dex) {
toHit = this.props.stats.abilities.dexMod
}
// Get Damage Mod
let avg = (dieNum * dmgDie) / 2 + toHit
let operator = "+"
let dmgMod = toHit
if (toHit < 0) {
operator = "-"
dmgMod = toHit *= -1
}
let damage = `${avg} (${dieNum}d${dmgDie} ${operator} ${dmgMod}) ${dmgType.toLowerCase()}.`
return null
})
saveText = saveText.filter((save) => !!save)
return (
<div
id="StatBlockDisplay"
className={`statblock shadow ${
exportView ? "statblock--export" : ""
}`}
>
<div id="creature-heading" className="statblock__header">
<h1 id="creature-name">{stats.name}</h1>
<h2 id="creature-details">
{stats.size} {stats.creatureType}
</h2>
</div>
if (prof) toHit += parseInt(this.props.stats.proficiency)
<div className="statblock__section red">
<div id="armor-class" className="statblock__property">
<span className="statblock__property-name">Armor Class</span>{" "}
{stats.ac.score}
{stats.ac.support && ` (${stats.ac.support})`}
</div>
<div id="hit-points" className="statblock__property">
<span className="statblock__property-name">Hit Points</span>{" "}
{hitPoints}
</div>
<div id="speed" className="statblock__property">
<span className="statblock__property-name">Speed</span>{" "}
{stats.speed}ft
{stats.flySpeed > 0 ? `, ${stats.flySpeed}ft (Fly)` : ""}
{stats.swimSpeed > 0 ? `, ${stats.swimSpeed}ft (Swim)` : ""}
</div>
</div>
return (
<div
className="statblock__property statblock__property--block"
key={i}
>
<span className="statblock__property-name italic">
{action.title}.{" "}
</span>
<span className="italic">{action.type} Weapon Attack. </span>
{`${toHit >= 0 ? "+" : ""}${toHit}`} to Hit.&ensp;
{action.type === "Ranged" ? "Range" : "Reach"} {reach}
ft.&ensp;
{targets} target{targets > 1 ? "s" : ""}.&ensp; Damage:&ensp;
{damage}
</div>
)
}
<div className="statblock__section red">
{orderedAbilities.map((ability) => {
let score = stats.abilities[ability]
let mod = stats.abilities[ability + "Mod"]
return (
<div className="ability" key={ability}>
<h4>{ability}</h4>
<span id={ability + "-display"}>
{score} ({mod < 0 ? "" : "+"}
{mod})
</span>
</div>
)
})}
</div>
if (!action) {
return (
<div className="statblock__section red">
{saveText.length > 0 && (
<DisplayProperty
block
key={i}
title={action.title}
content={action.content}
id="saving-throw-display"
title="Saving Throws"
content={saveText.join(", ")}
/>
)
}
return null
})
}
render() {
let {
abilities,
hp: { hitDie, dieNum },
calculatedHP,
} = this.props.stats
let conMod = Math.floor((abilities.con - 10) / 2)
dieNum = parseInt(dieNum)
let mod = dieNum * conMod
let hitPoints = `${calculatedHP} (${dieNum}d${hitDie} ${
mod < 0 ? "-" : "+"
} ${mod < 0 ? mod * -1 : mod})`
return (
<div
id="StatBlockDisplay"
className={`statblock ${
this.props.export ? "statblock--export" : ""
}`}
>
<div id="creatureHeading" className="statblock__header">
<h1 id="monsterName">{this.props.stats.name}</h1>
<h2 id="monsterDetails">
{this.props.stats.size} {this.props.stats.creatureType}
</h2>
</div>
<div className="statblock__section red">
<div className="statblock__property">
<span className="statblock__property-name">Armor Class</span>
{this.props.stats.ac.score}
{this.props.stats.ac.support &&
` (${this.props.stats.ac.support})`}
</div>
<div className="statblock__property">
<span className="statblock__property-name">Hit Points</span>{" "}
{hitPoints}
</div>
<div className="statblock__property">
<span className="statblock__property-name">Speed</span>
{this.props.stats.speed}ft
{this.props.stats.flySpeed > 0
? `, ${this.props.stats.flySpeed}ft (Fly)`
: ""}
{this.props.stats.swimSpeed > 0
? `, ${this.props.stats.swimSpeed}ft (Swim)`
: ""}
</div>
</div>
<div className="statblock__section red">
{this.renderAbilities()}
</div>
)}
{stats.skills.length > 0 && (
<DisplayProperty
id="skills-display"
title="Skills"
content={stats.skills.join(", ")}
/>
)}
{stats.conditionImmune.length > 0 && (
<DisplayProperty
id="condition-immunities-display"
title="Condition Immunities"
content={stats.conditionImmune.join(", ")}
/>
)}
{stats.immune.length > 0 && (
<DisplayProperty
id="damage-immunities-display"
title="Damage Immunities"
content={stats.immune.join(", ")}
/>
)}
{stats.resists.length > 0 && (
<DisplayProperty
id="damage-resistances-display"
title="Damage Resistances"
content={stats.resists.join(", ")}
/>
)}
{stats.vulnerable.length > 0 && (
<DisplayProperty
id="damage-vulnerabilities-display"
title="Damage Vulnerabilities"
content={stats.vulnerable.join(", ")}
/>
)}
{stats.senses.length > 0 && (
<DisplayProperty
id="senses-display"
title="Senses"
content={stats.senses.join(", ")}
/>
)}
{stats.langs.length > 0 && (
<DisplayProperty
id="languages-display"
title="Languages"
content={stats.langs.join(", ")}
/>
)}
</div>
<div className="statblock__section red">
{this.props.stats.conditionImmune.length > 0 && (
<DisplayProperty
title="Condition Immunities"
content={this.props.stats.conditionImmune.join(", ")}
/>
)}
{this.props.stats.immune.length > 0 && (
<DisplayProperty
title="Damage Immunities"
content={this.props.stats.immune.join(", ")}
/>
)}
{this.props.stats.resists.length > 0 && (
<DisplayProperty
title="Damage Resistances"
content={this.props.stats.resists.join(", ")}
/>
)}
{this.props.stats.vulnerable.length > 0 && (
<DisplayProperty
title="Damage Vulnerabilities"
content={this.props.stats.vulnerable.join(", ")}
/>
)}
{this.props.stats.skills.length > 0 && (
<DisplayProperty
title="Skills"
content={this.props.stats.skills.join(", ")}
/>
)}
{this.props.stats.senses.length > 0 && (
<div className="statblock__section">
{stats.features.map((feature, i) => {
return (
<DisplayProperty
title="Senses"
content={this.props.stats.senses.join(", ")}
id={"display-feature-" + i}
key={feature.title}
title={feature.title}
content={feature.content}
block
/>
)}
{this.props.stats.langs.length > 0 && (
)
})}
</div>
<div className="statblock__section statblock__section--with-heading">
<h3>Actions</h3>
{stats.actions.map((action, i) => {
if (["Melee", "Ranged"].includes(action.type)) {
return (
<DisplayAttack
id={"display-action-" + i}
action={action}
key={action.title + i}
/>
)
}
return (
<DisplayProperty
title="Languages"
content={this.props.stats.langs.join(", ")}
block
id={"display-action-" + i}
key={action.title + i}
title={action.title}
content={action.content}
/>
)}
</div>
<div className="statblock__section">{this.renderFeatures()}</div>
)
})}
</div>
{stats.legendaryActions.length > 0 && (
<div className="statblock__section statblock__section--with-heading">
<h3>Actions</h3>
{this.renderActions()}
<h3>Legendary Actions</h3>
<p className="mb-1 text-sm">
{stats.name} can take <b>{stats.legendaryActPerRound}</b>{" "}
legendary action
{stats.legendaryActPerRound === 1 ? "" : "s"}, choosing from
the options below. Only one legendary action option can be used
at a time and only at the end of another creature&apos;s turn.
They regain spent legendary actions at the start of their turn.
</p>
{stats.legendaryActions.map((action, i) => (
<DisplayProperty
block
id={"display-legendary-action-" + i}
key={i + action.title}
title={action.title}
content={action.content}
/>
))}
</div>
{this.props.stats.legendaryActions.length > 0 && (
<div className="statblock__section statblock__section--with-heading">
<h3>Legendary Actions</h3>
<p className="mb-1 text-sm">
{this.props.stats.name} can take{" "}
<b>{this.props.stats.legendaryActPerRound}</b> legendary
action
{this.props.stats.legendaryActPerRound === 1 ? "" : "s"},
choosing from the options below. Only one legendary action
option can be used at a time and only at the end of another
creature&apos;s turn. They regain spent legendary actions at
the start of their turn.
</p>
{this.props.stats.legendaryActions.map((action, i) => (
<DisplayProperty
block
key={i + action.title}
title={action.title}
content={action.content}
/>
))}
</div>
)}
</div>
)
}
)}
</div>
)
}
export default StatBlockDisplay
......@@ -4,11 +4,13 @@ import "./Accordion.scss"
export const Accordion = ({ children, open, title }) => {
const [isOpen, setOpen] = useState(open)
const toggleOpen = () => setOpen(!isOpen)
return (
<div className="accordion">
<div className="accordion bg-white border shadow-md">
<button
onClick={() => setOpen(!isOpen)}
id={`accordion-` + title.replace(" ", "-").toLowerCase()}
onClick={toggleOpen}
className="accordion__button"
>
{title}
......@@ -27,7 +29,11 @@ export const Accordion = ({ children, open, title }) => {
in={isOpen}
timeout={300}
>
<div data-testid="accordion__inner" className="accordion__inner">
<div
data-testid="accordion__inner"
className="accordion__inner"
id={`accordion-content-` + title.replace(" ", "-").toLowerCase()}
>
{children}
</div>
</CSSTransition>
......
.accordion {
margin: 0.5rem 0;
position: relative;
background-color: white;
border: 1px solid #eeeeee;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
.accordion__inner {
......
import React from "react"
import cn from "classnames"
export const NavButton = ({
className,
children,
onClick,
className,
color = "",
id,
onClick,
}) => {
let bg, bgHover
if (color === "red") {
bg = "bg-red-900"
bgHover = "hover:bg-red-500"
}
if (color === "navy") {
bg = "bg-navy-700"
bgHover = "hover:bg-navy-500"
}
const classes = cn("px-4 py-2 rounded-sm transition-colors text-white", {
"bg-red-900": color === "red",
"hover:bg-red-900": color === "red",
"bg-navy-900": color === "navy",
"hover:bg-navy-900": color === "navy",
[className]: className,
})
return (
<button
className={
`px-4 py-2 rounded-sm ${bg} ${bgHover} transition-colors text-white ` +
className
}
onClick={onClick}
>
<button id={id} className={classes} onClick={onClick}>
{children}
</button>
)
......
......@@ -13,10 +13,10 @@ export const Textarea = ({
}) => {
return (
<div className="form-group">
{!hideLabel && <label htmlFor={fieldName}>{label}</label>}
{!hideLabel && <label htmlFor={id}>{label}</label>}
<textarea
className="p-2 border rounded-md border-gray-300 block w-full"
id={fieldName}
id={id}
aria-describedby={`${id}Help`}
placeholder={placeholder}
onChange={onChange}
......
......@@ -15,6 +15,7 @@ export { SBG_Select } from "./sbg/SBG_Select"
export { GeneratorNav } from "./sbg/GeneratorNav"
export { FeatureBlock } from "./sbg/FeatureBlock"
export { AttackForm } from "./sbg/AttackForm"
export { DisplayAttack } from "./sbg/DisplayAttack"
// Pathfinder Character Builder (PFCB)
export { AbilityScoreSection } from "./pfcb/AbilityScoresSection"
......
import React from "react"
import { SBG_Input, SBG_Select } from "components"
import { CheckboxButton } from "./CheckboxButton"
export const AttackForm = (props) => {
return (
......@@ -10,6 +11,7 @@ export const AttackForm = (props) => {
>
<div className="col-span-2">
<SBG_Input
id={"attack-title-input-" + props.index}
type="text"
label={"Title"}
placeholder={""}
......@@ -22,8 +24,8 @@ export const AttackForm = (props) => {
</div>
<div className="col-span-2">
<SBG_Select
id={"attack-type-select-" + props.index}
label={"Attack"}
id={"attack-type"}
fieldName={"type"}
options={["Melee", "Ranged"]}
value={props.action.type}
......@@ -34,6 +36,7 @@ export const AttackForm = (props) => {
</div>
<div className="col-span-1">
<SBG_Input
id={"attack-targets-input-" + props.index}
label={"Targets"}
fieldName={"targets"}
type="number"
......@@ -45,6 +48,7 @@ export const AttackForm = (props) => {
</div>
<div className="col-span-1">
<SBG_Input
id={"attack-reach-input-" + props.index}
label={props.action.type === "Ranged" ? "Range" : "Reach"}
fieldName={"reach"}
type="number"
......@@ -57,7 +61,8 @@ export const AttackForm = (props) => {
<div className="col-span-1">
<SBG_Input
label={"#"}
id={"attack-die-num-input-" + props.index}
label={"# "}
type="number"
fieldName={"dieNum"}
value={props.action.dieNum}
......@@ -68,6 +73,7 @@ export const AttackForm = (props) => {
</div>
<div className="col-span-1">
<SBG_Select
id={"attack-dmg-die-select-" + props.index}
label={"Dmg Die"}
fieldName={"dmgDie"}
options={[4, 6, 8, 10, 12]}
......@@ -79,6 +85,7 @@ export const AttackForm = (props) => {
</div>
<div className="col-span-2">
<SBG_Select
id={"attack-dmg-type-select-" + props.index}
label={"Dmg Type"}
fieldName={"dmgType"}
options={global.damageTypes}
......@@ -89,31 +96,19 @@ export const AttackForm = (props) => {
/>
</div>
<div className="col-span-1">
<div className="form-group text-center">
<label className="" htmlFor="dex">
Dex?
</label>
<div
className={`dex-check ${props.action.dex ? "checked" : ""}`}
name={"dex-" + props.action.id}
value={!props.action.dex}
onClick={() =>
props.updateAction(
{
target: {
name: "dex",
value: !props.action.dex,
},
},
props.action.id,
props.legendary
)
}
></div>
</div>
<CheckboxButton
id={"attack-type-select-" + props.index}
label="Dex?"
fieldName="dex"
checked={props.action.dex}
onClick={(e) =>
props.updateAction(e, props.action.id, props.legendary)
}
/>
</div>
<div className="col-span-1">
<button
id={"delete-action-" + props.index}
onClick={() =>
props.deleteAction(props.action.id, props.legendary)
}
......
import React from "react"
export const CheckboxButton = ({
checked,
fieldName,
id,
label,
onClick,
}) => {
return (
<>
<div className="form-group text-center">
<label className="" htmlFor={id}>
{label}
</label>
<input
id={id}
className={`dex-check ${checked ? "checked" : ""}`}
name={fieldName}
onChange={onClick}
onClick={onClick}
/>
</div>
</>
)
}
import { StatblockContext } from "context"
import React, { useContext } from "react"
export const DisplayAttack = ({ action, id }) => {
const {
stats: { abilities, proficiency },
} = useContext(StatblockContext)
let { dieNum, dmgDie, dex, reach, targets, dmgType } = action
// Get hit mod
const toHit = (dex ? abilities.dexMod : abilities.strMod) + proficiency
// Get Damage Mod
let avg = (dieNum * dmgDie) / 2 + toHit
let operator = "+"
let dmgMod = toHit
if (toHit < 0) {
operator = "-"
dmgMod = toHit * -1
}
let damageString = `${avg} (${dieNum}d${dmgDie} ${operator} ${dmgMod}) ${dmgType.toLowerCase()}.`
return (
<div
className="statblock__property statblock__property--block"
id={id}
>
<span className="statblock__property-name italic">
{action.title}.&ensp;
</span>
<span className="italic">{action.type} Weapon Attack. </span>
{`${toHit >= 0 ? "+" : ""}${toHit}`} to hit,&ensp;
{action.type === "Ranged" ? "Range" : "Reach"} {reach}
ft.,&ensp;
{targets} target{targets > 1 ? "s" : ""}.&ensp; Damage:&ensp;
{damageString}
</div>
)
}
import React from "react"
export const DisplayProperty = ({ title, content, block }) => {
export const DisplayProperty = ({ id, title, content, block }) => {
if (block) {
return (
<div className="statblock__property statblock__property--block">
<span className="statblock__property-name italic">{title}.</span>
<div
id={id}
className="statblock__property statblock__property--block"
>
<span className="statblock__property-name italic">{title}.</span>{" "}
{content}
</div>
)
}
return (
<div className="statblock__property">
<span className="statblock__property-name">{title}</span>
{content}
<div id={id} className="statblock__property">
<span className="statblock__property-name">{title}</span> {content}
</div>
)
}
......@@ -7,11 +7,13 @@ export const FeatureBlock = ({
feature,
typeText = "Feature",
updateFunc,
index,
}) => {
return (
<div className="feature-block relative grid grid-rows-1 grid-cols-12 gap-2 mx-1 mb-2">
<div className="col-span-4">
<SBG_Input
id={"feature-title-" + index}
type="text"
label={typeText}
value={feature.title}
......@@ -21,6 +23,7 @@ export const FeatureBlock = ({
</div>
<div className="col-span-7">
<Textarea
id={"feature-content-" + index}
type="text"
label={"Description"}
value={feature.content}
......@@ -32,8 +35,9 @@ export const FeatureBlock = ({
<button
className="col-span-1 group text-right"
onClick={() => deleteFunc(feature.id)}
title="delete"
>
<span style={{ visibility: "hidden", fontSize: "1px" }}>
<span style={{ visibility: "hidden", fontSize: "0px" }}>
Delete
</span>
<i className="fas fa-minus-square text-xl text-gray-400 group-hover:text-red-900 transition-colors" />
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment