style: add documentation comments for all main window react components
This commit is contained in:
@@ -28,6 +28,10 @@ import {
|
||||
} from "../selectors/traditionSelectors.ts"
|
||||
import { selectTotalAdventurePoints } from "../slices/characterSlice.ts"
|
||||
|
||||
/**
|
||||
* Returns the contents for the adventure points tooltip that displays which
|
||||
* entry groups adventure points were spent on.
|
||||
*/
|
||||
export const AdventurePointsTooltip: FC = () => {
|
||||
const translate = useTranslate()
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ import { NavigationBarWrapper } from "./NavigationBarWrapper.tsx"
|
||||
const showSettings = () => ExternalAPI.showSettings()
|
||||
const toggleDevTools = () => ExternalAPI.toggleDevTools()
|
||||
|
||||
/**
|
||||
* Returns a top navigation bar.
|
||||
*/
|
||||
export const NavigationBar: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -5,6 +5,9 @@ interface Props {
|
||||
handleSetTab(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a back button for the beginning of the navigation bar.
|
||||
*/
|
||||
export const NavigationBarBack: FC<Props> = props => {
|
||||
const { handleSetTab } = props
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FCC } from "../../shared/utils/react.js"
|
||||
import "./NavigationBarLeft.scss"
|
||||
|
||||
/**
|
||||
* Returns a grouping element for the left section of the navigation bar.
|
||||
*/
|
||||
export const NavigationBarLeft: FCC = ({ children }) => (
|
||||
<div className="navigationbar-left">{children}</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FCC } from "../../shared/utils/react.js"
|
||||
import "./NavigationBarRight.scss"
|
||||
|
||||
/**
|
||||
* Returns a grouping element for the right section of the navigation bar.
|
||||
*/
|
||||
export const NavigationBarRight: FCC = ({ children }) => (
|
||||
<div className="navigationbar-right">{children}</div>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,10 @@ interface Props {
|
||||
tabs: RoutePath[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a section underneath the main navigation tabs section for switching
|
||||
* between subtabs.
|
||||
*/
|
||||
export const NavigationBarSubTabs: FC<Props> = props => {
|
||||
const { tabs } = props
|
||||
|
||||
@@ -16,7 +20,7 @@ export const NavigationBarSubTabs: FC<Props> = props => {
|
||||
<NavigationBarTab
|
||||
key={displayRoute.at(-1)}
|
||||
displayRoute={{ type: "single", route: displayRoute }}
|
||||
/>
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
|
||||
@@ -97,6 +97,9 @@ type Props = {
|
||||
displayRoute: DisplayRoute
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single navigation tab.
|
||||
*/
|
||||
export const NavigationBarTab: FC<Props> = props => {
|
||||
const { className, displayRoute } = props
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ type Props = {
|
||||
tabs: DisplayRoute[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a navigation bar section for displaying navigation tabs.
|
||||
*/
|
||||
export const NavigationBarTabs: FC<Props> = props => {
|
||||
const { tabs } = props
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FCC } from "../../shared/utils/react.js"
|
||||
import "./NavigationBarWrapper.scss"
|
||||
|
||||
/**
|
||||
* Returns a wrapper element for the complete navigation bar.
|
||||
*/
|
||||
export const NavigationBarWrapper: FCC = ({ children }) => (
|
||||
<div className="navigationbar">
|
||||
<div className="navigationbar-inner">{children}</div>
|
||||
|
||||
@@ -16,6 +16,9 @@ const onRestore = () => ExternalAPI.restore()
|
||||
const onClose = () => ExternalAPI.close()
|
||||
const isMaximized = () => ExternalAPI.isMaximized()
|
||||
|
||||
/**
|
||||
* Returns the React application for the main window.
|
||||
*/
|
||||
export const Root: FC = () => {
|
||||
const language = useAppSelector(selectLocale)
|
||||
const areAnimationsEnabled = useAppSelector(selectAreAnimationsEnabled)
|
||||
|
||||
@@ -17,6 +17,9 @@ import { Skills } from "./characters/character/skills/Skills.tsx"
|
||||
import { SpecialAbilities } from "./characters/character/specialAbilities/SpecialAbilities.tsx"
|
||||
import { Spells } from "./characters/character/spells/Spells.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page based on the currently selected route.
|
||||
*/
|
||||
export const Router: FC = () => {
|
||||
const route = useAppSelector(selectRoute)
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import { Page } from "../../../shared/components/page/Page.tsx"
|
||||
import { Scroll } from "../../../shared/components/scroll/Scroll.tsx"
|
||||
import { useTranslate } from "../../../shared/hooks/translate.ts"
|
||||
|
||||
/**
|
||||
* Returns a page containing the imprint.
|
||||
*/
|
||||
export const Imprint: FC = () => {
|
||||
const translate = useTranslate()
|
||||
return (
|
||||
|
||||
@@ -7,6 +7,9 @@ import { ExternalAPI } from "../../external.ts"
|
||||
|
||||
let savedText: string | undefined = undefined
|
||||
|
||||
/**
|
||||
* Returns a page that shows the changelog.
|
||||
*/
|
||||
export const LastChanges: FC = () => {
|
||||
const [text, setText] = useState(savedText ?? "...")
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ import "./ThirdPartyLicenses.scss"
|
||||
let savedVersion: string | undefined = undefined
|
||||
let savedText: string | undefined = undefined
|
||||
|
||||
/**
|
||||
* Returns a page that shows licenses of third-party software.
|
||||
*/
|
||||
export const ThirdPartyLicenses: FC = () => {
|
||||
const [version, setVersion] = useState(savedVersion ?? "0.0.0")
|
||||
const [text, setText] = useState(savedText ?? "...")
|
||||
|
||||
@@ -19,6 +19,9 @@ import { changeCharactersSortOrder, selectCharactersSortOrder } from "../../slic
|
||||
import "./Characters.scss"
|
||||
import { CharactersItem } from "./CharactersItem.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page for managing characters.
|
||||
*/
|
||||
export const Characters: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const localeCompare = useLocaleCompare()
|
||||
|
||||
@@ -26,11 +26,14 @@ import {
|
||||
} from "../../slices/databaseSlice.ts"
|
||||
import { goToTab } from "../../slices/routeSlice.ts"
|
||||
|
||||
export type HerolistItemProps = {
|
||||
type Props = {
|
||||
character: CharacterState
|
||||
}
|
||||
|
||||
export const CharactersItem: FC<HerolistItemProps> = props => {
|
||||
/**
|
||||
* Returns a single character item.
|
||||
*/
|
||||
export const CharactersItem: FC<Props> = props => {
|
||||
const { character } = props
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -24,6 +24,9 @@ import {
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
import { AdventurePointsSpent } from "./AdventurePointsSpent.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page for managing advantages.
|
||||
*/
|
||||
export const Advantages: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
+4
@@ -12,6 +12,10 @@ import {
|
||||
} from "../../../../selectors/adventurePointSelectors.ts"
|
||||
import { selectMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages } from "../../../../selectors/traditionSelectors.ts"
|
||||
|
||||
/**
|
||||
* Returns a sidebar widget for showing spent and available adventure points for
|
||||
* advantages or disadvantages.
|
||||
*/
|
||||
export const AdventurePointsSpent: FC = () => {
|
||||
const translate = useTranslate()
|
||||
|
||||
|
||||
+3
@@ -24,6 +24,9 @@ import {
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
import { AdventurePointsSpent } from "./AdventurePointsSpent.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page for managing disadvantages.
|
||||
*/
|
||||
export const Disadvantages: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -10,6 +10,9 @@ import { selectAttributeAdjustmentId } from "../../../../slices/characterSlice.t
|
||||
import { changeAttributeAdjustmentId } from "../../../../slices/raceSlice.ts"
|
||||
import "./AttributeAdjustment.scss"
|
||||
|
||||
/**
|
||||
* Returns a widget for changing the attribute adjustment set for the race.
|
||||
*/
|
||||
export const AttributesAdjustment: FC = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const translate = useTranslate()
|
||||
|
||||
@@ -11,6 +11,9 @@ type Props = {
|
||||
value: number | string
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an attribute value block.
|
||||
*/
|
||||
export const AttributeBorder: FC<Props> = props => {
|
||||
const { children, className, label, tooltip, tooltipMargin, value } = props
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ import "./Attributes.scss"
|
||||
import { AttributeList } from "./AttributesList.tsx"
|
||||
import { DerivedCharacteristicsList } from "./DerivedCharacteristicsList.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page for managing attributes.
|
||||
*/
|
||||
export const Attributes: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const totalPoints = useAppSelector(selectTotalPoints)
|
||||
|
||||
@@ -9,6 +9,9 @@ type Props = {
|
||||
isRemovingEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of attribute values.
|
||||
*/
|
||||
export const AttributeList: FC<Props> = props => {
|
||||
const attributes = useAppSelector(selectVisibleAttributes)
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ type Props = {
|
||||
isRemovingEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single attribute value item with buttons to adjust the value.
|
||||
*/
|
||||
export const AttributeListItem: FC<Props> = props => {
|
||||
const { attribute, isInCharacterCreation, isRemovingEnabled } = props
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ type Props = {
|
||||
isRemovingEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of derived characteristic values.
|
||||
*/
|
||||
export const DerivedCharacteristicsList: FC<Props> = props => {
|
||||
const { isInCharacterCreation, isRemovingEnabled } = props
|
||||
|
||||
|
||||
+4
@@ -27,6 +27,10 @@ type Props = {
|
||||
isRemovingEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single derived characterstics value item with optional buttons to
|
||||
* manage the value.
|
||||
*/
|
||||
export const DerivedCharacteristicsListItem: FC<Props> = props => {
|
||||
const { attribute, isInCharacterCreation, isRemovingEnabled } = props
|
||||
|
||||
|
||||
+4
@@ -30,6 +30,10 @@ type Props = {
|
||||
isRemovingEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value item and buttons to adjust the value for a supplementary
|
||||
* derived characteristic value.
|
||||
*/
|
||||
export const DerivedCharacteristicsListItemPermanent: FC<Props> = props => {
|
||||
const { attribute, isRemovingEnabled } = props
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ type Props = {
|
||||
remove(value: number): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sheet for managing permanent energy loss.
|
||||
*/
|
||||
export const PermanentLossSheet: FC<Props> = props => {
|
||||
const { remove, isOpen, close } = props
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ type Props = {
|
||||
close(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sheet for managing lost and redeemed energy points.
|
||||
*/
|
||||
export const PermanentPointsSheet: FC<Props> = props => {
|
||||
const {
|
||||
id,
|
||||
|
||||
+3
@@ -115,6 +115,9 @@ const CloseCombatTechniquesListItem: FC<Props> = props => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a close combat technique.
|
||||
*/
|
||||
const MemoCloseCombatTechniquesListItem = memo(CloseCombatTechniquesListItem)
|
||||
|
||||
export { MemoCloseCombatTechniquesListItem as CloseCombatTechniquesListItem }
|
||||
|
||||
@@ -47,6 +47,9 @@ const isTopMarginNeeded = (
|
||||
mprev: DisplayedCombatTechnique | undefined,
|
||||
) => sortOrder === "group" && mprev !== undefined && curr.kind !== mprev.kind
|
||||
|
||||
/**
|
||||
* Returns a page for managing combat techniques.
|
||||
*/
|
||||
export const CombatTechniques: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
|
||||
+3
@@ -113,6 +113,9 @@ const RangedCombatTechniquesListItem: FC<Props> = props => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a ranged combat technique.
|
||||
*/
|
||||
const MemoRangedCombatTechniquesListItem = memo(RangedCombatTechniquesListItem)
|
||||
|
||||
export { MemoRangedCombatTechniquesListItem as RangedCombatTechniquesListItem }
|
||||
|
||||
@@ -12,29 +12,28 @@ type Props = {
|
||||
close(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sheet for adding adventure points to the character.
|
||||
*/
|
||||
export const OverviewAddAP: FC<Props> = props => {
|
||||
const { addAdventurePoints, isRemovingEnabled, isOpen, close } = props
|
||||
|
||||
const translate = useTranslate()
|
||||
const [ value, setValue ] = useState("")
|
||||
const [ prevIsOpen, setPrevIsOpen ] = useState(false)
|
||||
const [value, setValue] = useState("")
|
||||
const [prevIsOpen, setPrevIsOpen] = useState(false)
|
||||
|
||||
if (prevIsOpen !== isOpen) {
|
||||
setValue("")
|
||||
setPrevIsOpen(isOpen)
|
||||
}
|
||||
|
||||
const addAP =
|
||||
useCallback(
|
||||
() => {
|
||||
const mvalue = parseInt(value)
|
||||
const addAP = useCallback(() => {
|
||||
const mvalue = parseInt(value)
|
||||
|
||||
if (mvalue !== undefined) {
|
||||
addAdventurePoints(mvalue)
|
||||
}
|
||||
},
|
||||
[ addAdventurePoints, value ]
|
||||
)
|
||||
if (mvalue !== undefined) {
|
||||
addAdventurePoints(mvalue)
|
||||
}
|
||||
}, [addAdventurePoints, value])
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -44,7 +43,7 @@ export const OverviewAddAP: FC<Props> = props => {
|
||||
{
|
||||
disabled: isRemovingEnabled
|
||||
? !isInteger(value)
|
||||
: (!isNaturalNumber(value) || value === "0"),
|
||||
: !isNaturalNumber(value) || value === "0",
|
||||
label: translate("Add"),
|
||||
onClick: addAP,
|
||||
},
|
||||
@@ -54,14 +53,14 @@ export const OverviewAddAP: FC<Props> = props => {
|
||||
]}
|
||||
close={close}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
>
|
||||
<TextField
|
||||
hint={translate("How many Adventure Points do you want to add?")}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
fullWidth
|
||||
valid={isRemovingEnabled ? isInteger(value) : isNaturalNumber(value) && value !== "0"}
|
||||
/>
|
||||
/>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,9 +9,47 @@ import { minus, plusMinus, signStr } from "../../../../../shared/utils/math.ts"
|
||||
import { isEmptyOr, isFloat, isNaturalNumber } from "../../../../../shared/utils/regex.ts"
|
||||
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { selectAvailableEyeColors, selectAvailableEyeColorsIdDice, selectAvailableHairColors, selectAvailableHairColorsIdDice, selectAvailableSocialStatuses, selectRandomHeightCalculation, selectRandomWeightCalculation } from "../../../../selectors/personalDataSelectors.ts"
|
||||
import { selectAge, selectCharacteristics, selectDateOfBirth, selectFamily, selectOtherInfo, selectPlaceOfBirth, selectPredefinedEyeColor, selectPredefinedHairColor, selectSize, selectSocialStatusId, selectTitle, selectWeight } from "../../../../slices/characterSlice.ts"
|
||||
import { rerollEyeColor, rerollHairColor, rerollSize, rerollWeight, setAge, setCharacteristics, setDateOfBirth, setFamily, setOtherInfo, setPlaceOfBirth, setPredefinedEyeColor, setPredefinedHairColor, setSize, setSocialStatus, setTitle, setWeight } from "../../../../slices/personalDataSlice.ts"
|
||||
import {
|
||||
selectAvailableEyeColors,
|
||||
selectAvailableEyeColorsIdDice,
|
||||
selectAvailableHairColors,
|
||||
selectAvailableHairColorsIdDice,
|
||||
selectAvailableSocialStatuses,
|
||||
selectRandomHeightCalculation,
|
||||
selectRandomWeightCalculation,
|
||||
} from "../../../../selectors/personalDataSelectors.ts"
|
||||
import {
|
||||
selectAge,
|
||||
selectCharacteristics,
|
||||
selectDateOfBirth,
|
||||
selectFamily,
|
||||
selectOtherInfo,
|
||||
selectPlaceOfBirth,
|
||||
selectPredefinedEyeColor,
|
||||
selectPredefinedHairColor,
|
||||
selectSize,
|
||||
selectSocialStatusId,
|
||||
selectTitle,
|
||||
selectWeight,
|
||||
} from "../../../../slices/characterSlice.ts"
|
||||
import {
|
||||
rerollEyeColor,
|
||||
rerollHairColor,
|
||||
rerollSize,
|
||||
rerollWeight,
|
||||
setAge,
|
||||
setCharacteristics,
|
||||
setDateOfBirth,
|
||||
setFamily,
|
||||
setOtherInfo,
|
||||
setPlaceOfBirth,
|
||||
setPredefinedEyeColor,
|
||||
setPredefinedHairColor,
|
||||
setSize,
|
||||
setSocialStatus,
|
||||
setTitle,
|
||||
setWeight,
|
||||
} from "../../../../slices/personalDataSlice.ts"
|
||||
import { PersonalDataDropdown } from "./PersonalDataDropdown.tsx"
|
||||
import { PersonalDataDropdownWithReroll } from "./PersonalDataDropdownWithReroll.tsx"
|
||||
import { PersonalDataTextField } from "./PersonalDataTextField.tsx"
|
||||
@@ -23,6 +61,9 @@ const validateEmptyOrNaturalNumber = (value: string | undefined) =>
|
||||
const validateEmptyOrFloat = (value: string | undefined) =>
|
||||
value === undefined || isEmptyOr(isFloat, value)
|
||||
|
||||
/**
|
||||
* Returns a page for managing personal data of the character.
|
||||
*/
|
||||
export const PersonalData: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
@@ -34,23 +75,27 @@ export const PersonalData: FC = () => {
|
||||
const hairColorOptions = useMemo(
|
||||
() =>
|
||||
availableHairColors
|
||||
.map((hairColor): DropdownOption<number> => ({
|
||||
id: hairColor.id,
|
||||
name: translateMap(hairColor.translations)?.name ?? hairColor.id.toFixed(),
|
||||
}))
|
||||
.map(
|
||||
(hairColor): DropdownOption<number> => ({
|
||||
id: hairColor.id,
|
||||
name: translateMap(hairColor.translations)?.name ?? hairColor.id.toFixed(),
|
||||
}),
|
||||
)
|
||||
.sort(reduceCompare(compareAt(x => x.name, localeCompare))),
|
||||
[ availableHairColors, localeCompare, translateMap ],
|
||||
[availableHairColors, localeCompare, translateMap],
|
||||
)
|
||||
|
||||
const eyeColorOptions = useMemo(
|
||||
() =>
|
||||
availableEyeColors
|
||||
.map((eyeColor): DropdownOption<number> => ({
|
||||
id: eyeColor.id,
|
||||
name: translateMap(eyeColor.translations)?.name ?? eyeColor.id.toFixed(),
|
||||
}))
|
||||
.map(
|
||||
(eyeColor): DropdownOption<number> => ({
|
||||
id: eyeColor.id,
|
||||
name: translateMap(eyeColor.translations)?.name ?? eyeColor.id.toFixed(),
|
||||
}),
|
||||
)
|
||||
.sort(reduceCompare(compareAt(x => x.name, localeCompare))),
|
||||
[ availableEyeColors, localeCompare, translateMap ],
|
||||
[availableEyeColors, localeCompare, translateMap],
|
||||
)
|
||||
|
||||
const randomHeightCalculation = useAppSelector(selectRandomHeightCalculation)
|
||||
@@ -58,13 +103,12 @@ export const PersonalData: FC = () => {
|
||||
const heightCalculationString = useMemo(() => {
|
||||
const dice = translate("D")
|
||||
|
||||
const randomPart =
|
||||
randomHeightCalculation.random
|
||||
.map(({ number, sides }) => ` ${signStr(sides)} ${number}${dice}${sides}`)
|
||||
.join("")
|
||||
const randomPart = randomHeightCalculation.random
|
||||
.map(({ number, sides }) => ` ${signStr(sides)} ${number}${dice}${sides}`)
|
||||
.join("")
|
||||
|
||||
return ` (${randomHeightCalculation.base}${randomPart})`
|
||||
}, [ randomHeightCalculation.base, randomHeightCalculation.random, translate ])
|
||||
}, [randomHeightCalculation.base, randomHeightCalculation.random, translate])
|
||||
|
||||
const randomWeightCalculation = useAppSelector(selectRandomWeightCalculation)
|
||||
|
||||
@@ -74,36 +118,40 @@ export const PersonalData: FC = () => {
|
||||
|
||||
const signStrWeight = (offsetStrategy: WeightDiceOffsetStrategy) => {
|
||||
switch (offsetStrategy) {
|
||||
case "Add": return "+"
|
||||
case "Subtract": return minus
|
||||
case "AddEvenSubtractOdd": return plusMinus
|
||||
default: return assertExhaustive(offsetStrategy)
|
||||
case "Add":
|
||||
return "+"
|
||||
case "Subtract":
|
||||
return minus
|
||||
case "AddEvenSubtractOdd":
|
||||
return plusMinus
|
||||
default:
|
||||
return assertExhaustive(offsetStrategy)
|
||||
}
|
||||
}
|
||||
|
||||
const randomPart =
|
||||
randomWeightCalculation.random
|
||||
.map(({ number, sides, offset_strategy }) => ` ${signStrWeight(offset_strategy)} ${number}${dice}${sides}`)
|
||||
.join("")
|
||||
const randomPart = randomWeightCalculation.random
|
||||
.map(
|
||||
({ number, sides, offset_strategy }) =>
|
||||
` ${signStrWeight(offset_strategy)} ${number}${dice}${sides}`,
|
||||
)
|
||||
.join("")
|
||||
|
||||
return ` (${size} ${minus} ${randomWeightCalculation.base}${randomPart})`
|
||||
}, [
|
||||
randomWeightCalculation.base,
|
||||
randomWeightCalculation.random,
|
||||
translate,
|
||||
])
|
||||
}, [randomWeightCalculation.base, randomWeightCalculation.random, translate])
|
||||
|
||||
const availableSocialStatuses = useAppSelector(selectAvailableSocialStatuses)
|
||||
|
||||
const socialStatusOptions = useMemo(
|
||||
() =>
|
||||
availableSocialStatuses
|
||||
.map((socialStatus): DropdownOption<number> => ({
|
||||
id: socialStatus.id,
|
||||
name: translateMap(socialStatus.translations)?.name ?? socialStatus.id.toFixed(),
|
||||
}))
|
||||
.map(
|
||||
(socialStatus): DropdownOption<number> => ({
|
||||
id: socialStatus.id,
|
||||
name: translateMap(socialStatus.translations)?.name ?? socialStatus.id.toFixed(),
|
||||
}),
|
||||
)
|
||||
.sort(reduceCompare(compareAt(x => x.name, localeCompare))),
|
||||
[ availableSocialStatuses, localeCompare, translateMap ],
|
||||
[availableSocialStatuses, localeCompare, translateMap],
|
||||
)
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -112,22 +160,22 @@ export const PersonalData: FC = () => {
|
||||
|
||||
const handleRerollHairColor = useCallback(
|
||||
() => dispatch(rerollHairColor(availableHairColorsIdDice)),
|
||||
[ dispatch, availableHairColorsIdDice ]
|
||||
[dispatch, availableHairColorsIdDice],
|
||||
)
|
||||
|
||||
const handleRerollEyeColor = useCallback(
|
||||
() => dispatch(rerollEyeColor(availableEyeColorsIdDice)),
|
||||
[ dispatch, availableEyeColorsIdDice ]
|
||||
[dispatch, availableEyeColorsIdDice],
|
||||
)
|
||||
|
||||
const handleRerollSize = useCallback(
|
||||
() => dispatch(rerollSize(randomHeightCalculation)),
|
||||
[ dispatch, randomHeightCalculation ]
|
||||
[dispatch, randomHeightCalculation],
|
||||
)
|
||||
|
||||
const handleRerollWeight = useCallback(
|
||||
() => dispatch(rerollWeight(randomWeightCalculation, randomHeightCalculation)),
|
||||
[ dispatch, randomWeightCalculation, randomHeightCalculation ]
|
||||
[dispatch, randomWeightCalculation, randomHeightCalculation],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -136,23 +184,23 @@ export const PersonalData: FC = () => {
|
||||
label={translate("Family")}
|
||||
selector={selectFamily}
|
||||
action={setFamily}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextField
|
||||
label={translate("Place of Birth")}
|
||||
selector={selectPlaceOfBirth}
|
||||
action={setPlaceOfBirth}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextField
|
||||
label={translate("Date of Birth")}
|
||||
selector={selectDateOfBirth}
|
||||
action={setDateOfBirth}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextField
|
||||
label={translate("Age")}
|
||||
selector={selectAge}
|
||||
action={setAge}
|
||||
validator={validateEmptyOrNaturalNumber}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataDropdownWithReroll
|
||||
label={translate("Hair Color")}
|
||||
rerollLabel={translate("Reroll Hair Color")}
|
||||
@@ -160,7 +208,7 @@ export const PersonalData: FC = () => {
|
||||
selector={selectPredefinedHairColor}
|
||||
action={setPredefinedHairColor}
|
||||
onReroll={handleRerollHairColor}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataDropdownWithReroll
|
||||
label={translate("Eye Color")}
|
||||
rerollLabel={translate("Reroll Eye Color")}
|
||||
@@ -168,7 +216,7 @@ export const PersonalData: FC = () => {
|
||||
selector={selectPredefinedEyeColor}
|
||||
action={setPredefinedEyeColor}
|
||||
onReroll={handleRerollEyeColor}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextFieldWithReroll
|
||||
label={`${translate("Size")}${heightCalculationString}`}
|
||||
rerollLabel={translate("Reroll Size")}
|
||||
@@ -176,7 +224,7 @@ export const PersonalData: FC = () => {
|
||||
action={setSize}
|
||||
onReroll={handleRerollSize}
|
||||
validator={validateEmptyOrFloat}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextFieldWithReroll
|
||||
label={`${translate("Weight")}${weightCalculationString}`}
|
||||
rerollLabel={translate("Reroll Weight")}
|
||||
@@ -184,28 +232,24 @@ export const PersonalData: FC = () => {
|
||||
action={setWeight}
|
||||
onReroll={handleRerollWeight}
|
||||
validator={validateEmptyOrNaturalNumber}
|
||||
/>
|
||||
<PersonalDataTextField
|
||||
label={translate("Title")}
|
||||
selector={selectTitle}
|
||||
action={setTitle}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextField label={translate("Title")} selector={selectTitle} action={setTitle} />
|
||||
<PersonalDataDropdown
|
||||
label={translate("Social Status")}
|
||||
selector={selectSocialStatusId}
|
||||
action={setSocialStatus}
|
||||
options={socialStatusOptions}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextField
|
||||
label={translate("Characteristics")}
|
||||
selector={selectCharacteristics}
|
||||
action={setCharacteristics}
|
||||
/>
|
||||
/>
|
||||
<PersonalDataTextField
|
||||
label={translate("Other Information")}
|
||||
selector={selectOtherInfo}
|
||||
action={setOtherInfo}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,20 +5,18 @@ import { DropdownOption } from "../../../../../shared/components/dropdown/Dropdo
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { RootState } from "../../../../store.ts"
|
||||
|
||||
export type Props = {
|
||||
type Props = {
|
||||
label: string
|
||||
selector: (state: RootState) => number | undefined
|
||||
options: DropdownOption<number>[]
|
||||
action: ActionCreatorWithPayload<number>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a dropdown for use with personal data.
|
||||
*/
|
||||
export const PersonalDataDropdown: FC<Props> = props => {
|
||||
const {
|
||||
label,
|
||||
selector,
|
||||
options,
|
||||
action,
|
||||
} = props
|
||||
const { label, selector, options, action } = props
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const value = useAppSelector(selector)
|
||||
@@ -27,7 +25,7 @@ export const PersonalDataDropdown: FC<Props> = props => {
|
||||
(newValue: number) => {
|
||||
dispatch(action(newValue))
|
||||
},
|
||||
[ dispatch, action ]
|
||||
[dispatch, action],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -38,7 +36,7 @@ export const PersonalDataDropdown: FC<Props> = props => {
|
||||
onChange={handleSetValue}
|
||||
options={options}
|
||||
disabled={options.length === 1 && value === options[0]!.id}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-12
@@ -7,7 +7,7 @@ import { InputButtonGroup } from "../../../../../shared/components/inputButtonGr
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { RootState } from "../../../../store.ts"
|
||||
|
||||
export type Props = {
|
||||
type Props = {
|
||||
label: string
|
||||
rerollLabel: string
|
||||
selector: (state: RootState) => number | undefined
|
||||
@@ -16,15 +16,11 @@ export type Props = {
|
||||
onReroll: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a dropdown with a reroll option for use with personal data.
|
||||
*/
|
||||
export const PersonalDataDropdownWithReroll: FC<Props> = props => {
|
||||
const {
|
||||
label,
|
||||
rerollLabel,
|
||||
selector,
|
||||
options,
|
||||
action,
|
||||
onReroll,
|
||||
} = props
|
||||
const { label, rerollLabel, selector, options, action, onReroll } = props
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const value = useAppSelector(selector)
|
||||
@@ -33,7 +29,7 @@ export const PersonalDataDropdownWithReroll: FC<Props> = props => {
|
||||
(newValue: number) => {
|
||||
dispatch(action(newValue))
|
||||
},
|
||||
[ dispatch, action ]
|
||||
[dispatch, action],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -44,13 +40,13 @@ export const PersonalDataDropdownWithReroll: FC<Props> = props => {
|
||||
onChange={handleSetValue}
|
||||
options={options}
|
||||
disabled={options.length === 1 && value === options[0]!.id}
|
||||
/>
|
||||
/>
|
||||
<IconButton
|
||||
icon=""
|
||||
label={rerollLabel}
|
||||
onClick={onReroll}
|
||||
disabled={options.length === 1}
|
||||
/>
|
||||
/>
|
||||
</InputButtonGroup>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,20 +4,18 @@ import { TextFieldLazy } from "../../../../../shared/components/textField/TextFi
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { RootState } from "../../../../store.ts"
|
||||
|
||||
export type Props = {
|
||||
type Props = {
|
||||
label: string
|
||||
selector: (state: RootState) => string | undefined
|
||||
action: ActionCreatorWithPayload<string>
|
||||
validator?: (value: string | undefined) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a textfield for use with personal data.
|
||||
*/
|
||||
export const PersonalDataTextField: FC<Props> = props => {
|
||||
const {
|
||||
label,
|
||||
selector,
|
||||
action,
|
||||
validator,
|
||||
} = props
|
||||
const { label, selector, action, validator } = props
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const value = useAppSelector(selector)
|
||||
@@ -26,7 +24,7 @@ export const PersonalDataTextField: FC<Props> = props => {
|
||||
(newValue: string) => {
|
||||
dispatch(action(newValue))
|
||||
},
|
||||
[ dispatch, action ]
|
||||
[dispatch, action],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -36,7 +34,7 @@ export const PersonalDataTextField: FC<Props> = props => {
|
||||
value={value}
|
||||
onChange={handleSetValue}
|
||||
valid={validator ? validator(value) : true}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-16
@@ -6,7 +6,7 @@ import { TextFieldLazy } from "../../../../../shared/components/textField/TextFi
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { RootState } from "../../../../store.ts"
|
||||
|
||||
export type Props = {
|
||||
type Props = {
|
||||
label: string
|
||||
rerollLabel: string
|
||||
selector: (state: RootState) => string | undefined
|
||||
@@ -15,15 +15,11 @@ export type Props = {
|
||||
validator?: (value: string | undefined) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a textfield with a reroll option for use with personal data.
|
||||
*/
|
||||
export const PersonalDataTextFieldWithReroll: FC<Props> = props => {
|
||||
const {
|
||||
label,
|
||||
rerollLabel,
|
||||
selector,
|
||||
action,
|
||||
onReroll,
|
||||
validator,
|
||||
} = props
|
||||
const { label, rerollLabel, selector, action, onReroll, validator } = props
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const value = useAppSelector(selector)
|
||||
@@ -32,7 +28,7 @@ export const PersonalDataTextFieldWithReroll: FC<Props> = props => {
|
||||
(newValue: string) => {
|
||||
dispatch(action(newValue))
|
||||
},
|
||||
[ dispatch, action ]
|
||||
[dispatch, action],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -42,12 +38,8 @@ export const PersonalDataTextFieldWithReroll: FC<Props> = props => {
|
||||
value={value}
|
||||
onChange={handleSetValue}
|
||||
valid={validator ? validator(value) : true}
|
||||
/>
|
||||
<IconButton
|
||||
icon=""
|
||||
label={rerollLabel}
|
||||
onClick={onReroll}
|
||||
/>
|
||||
/>
|
||||
<IconButton icon="" label={rerollLabel} onClick={onReroll} />
|
||||
</InputButtonGroup>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,20 +18,39 @@ import { useProfessionName } from "../../../../hooks/professionName.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { selectCanAddAdventurePoints } from "../../../../selectors/characterSelectors.ts"
|
||||
import { selectCurrentCulture } from "../../../../selectors/cultureSelectors.ts"
|
||||
import { selectCurrentExperienceLevel, selectStartExperienceLevel } from "../../../../selectors/experienceLevelSelectors.ts"
|
||||
import {
|
||||
selectCurrentExperienceLevel,
|
||||
selectStartExperienceLevel,
|
||||
} from "../../../../selectors/experienceLevelSelectors.ts"
|
||||
import { selectCurrentProfession } from "../../../../selectors/professionSelectors.ts"
|
||||
import { selectCanDefineCustomProfessionName, selectCanFinishCharacterCreation, selectShowFinishCharacterCreation } from "../../../../selectors/profileSelectors.ts"
|
||||
import {
|
||||
selectCanDefineCustomProfessionName,
|
||||
selectCanFinishCharacterCreation,
|
||||
selectShowFinishCharacterCreation,
|
||||
} from "../../../../selectors/profileSelectors.ts"
|
||||
import { selectCurrentRace, selectCurrentRaceVariant } from "../../../../selectors/raceSelectors.ts"
|
||||
import { deleteAvatar, finishCharacterCreation, selectAvatar, selectName, selectSex, selectTotalAdventurePoints, setName } from "../../../../slices/characterSlice.ts"
|
||||
import {
|
||||
deleteAvatar,
|
||||
finishCharacterCreation,
|
||||
selectAvatar,
|
||||
selectName,
|
||||
selectSex,
|
||||
selectTotalAdventurePoints,
|
||||
setName,
|
||||
} from "../../../../slices/characterSlice.ts"
|
||||
import { setCustomProfessionName } from "../../../../slices/professionSlice.ts"
|
||||
import { PersonalData } from "./PersonalData.tsx"
|
||||
import "./ProfileOverview.scss"
|
||||
|
||||
/**
|
||||
* Returns a page for managing the character’s profile, including the name,
|
||||
* adventure points and personal data.
|
||||
*/
|
||||
export const ProfileOverview: FC = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const name = useAppSelector(selectName)
|
||||
const [ isEditingName, setIsEditingName ] = useState(false)
|
||||
const [ isEditingProfessionName, setIsEditingProfessionName ] = useState(false)
|
||||
const [isEditingName, setIsEditingName] = useState(false)
|
||||
const [isEditingProfessionName, setIsEditingProfessionName] = useState(false)
|
||||
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
@@ -55,7 +74,7 @@ export const ProfileOverview: FC = () => {
|
||||
dispatch(setName(newName))
|
||||
setIsEditingName(false)
|
||||
},
|
||||
[ dispatch ]
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleEditProfessionName = useCallback(
|
||||
@@ -63,79 +82,62 @@ export const ProfileOverview: FC = () => {
|
||||
dispatch(setCustomProfessionName(newName))
|
||||
setIsEditingProfessionName(false)
|
||||
},
|
||||
[ dispatch ]
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleStartEditName = useCallback(
|
||||
() => setIsEditingName(true),
|
||||
[ setIsEditingName ]
|
||||
)
|
||||
const handleStartEditName = useCallback(() => setIsEditingName(true), [setIsEditingName])
|
||||
|
||||
const handleCancelEditName = useCallback(
|
||||
() => setIsEditingName(false),
|
||||
[ setIsEditingName ]
|
||||
)
|
||||
const handleCancelEditName = useCallback(() => setIsEditingName(false), [setIsEditingName])
|
||||
|
||||
const handleStartEditProfessionName = useCallback(
|
||||
() => setIsEditingProfessionName(true),
|
||||
[ setIsEditingProfessionName ]
|
||||
[setIsEditingProfessionName],
|
||||
)
|
||||
|
||||
const handleCancelEditProfessionName = useCallback(
|
||||
() => setIsEditingProfessionName(false),
|
||||
[ setIsEditingProfessionName ]
|
||||
[setIsEditingProfessionName],
|
||||
)
|
||||
|
||||
const handleDeleteAvatar = useCallback(
|
||||
() => dispatch(deleteAvatar()),
|
||||
[ dispatch ]
|
||||
)
|
||||
const handleDeleteAvatar = useCallback(() => dispatch(deleteAvatar()), [dispatch])
|
||||
|
||||
const handlFinishCharacterCreation = useCallback(
|
||||
() => dispatch(finishCharacterCreation()),
|
||||
[ dispatch ]
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const nameElement = isEditingName
|
||||
? (
|
||||
<EditText
|
||||
className="change-name"
|
||||
cancel={handleCancelEditName}
|
||||
submit={handleEditName}
|
||||
text={name}
|
||||
autoFocus
|
||||
submitLabel={translate("Edit Name")}
|
||||
cancelLabel={translate("Cancel")}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<h1 className="confirm-edit">
|
||||
{name}
|
||||
<IconButton label={translate("Edit Name")} icon="" onClick={handleStartEditName} />
|
||||
</h1>
|
||||
)
|
||||
const nameElement = isEditingName ? (
|
||||
<EditText
|
||||
className="change-name"
|
||||
cancel={handleCancelEditName}
|
||||
submit={handleEditName}
|
||||
text={name}
|
||||
autoFocus
|
||||
submitLabel={translate("Edit Name")}
|
||||
cancelLabel={translate("Cancel")}
|
||||
/>
|
||||
) : (
|
||||
<h1 className="confirm-edit">
|
||||
{name}
|
||||
<IconButton label={translate("Edit Name")} icon="" onClick={handleStartEditName} />
|
||||
</h1>
|
||||
)
|
||||
|
||||
const professionNameElement =
|
||||
canDefineCustomProfessionName
|
||||
? (isEditingProfessionName
|
||||
? (
|
||||
<EditText
|
||||
submit={handleEditProfessionName}
|
||||
submitLabel={translate("Edit Profession Name")}
|
||||
cancel={handleCancelEditProfessionName}
|
||||
cancelLabel={translate("Cancel")}
|
||||
text={professionName}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
className="edit-profession-name-btn"
|
||||
onClick={handleStartEditProfessionName}
|
||||
>
|
||||
{translate("Edit Profession Name")}
|
||||
</Button>
|
||||
))
|
||||
: null
|
||||
const professionNameElement = canDefineCustomProfessionName ? (
|
||||
isEditingProfessionName ? (
|
||||
<EditText
|
||||
submit={handleEditProfessionName}
|
||||
submitLabel={translate("Edit Profession Name")}
|
||||
cancel={handleCancelEditProfessionName}
|
||||
cancelLabel={translate("Cancel")}
|
||||
text={professionName}
|
||||
/>
|
||||
) : (
|
||||
<Button className="edit-profession-name-btn" onClick={handleStartEditProfessionName}>
|
||||
{translate("Edit Profession Name")}
|
||||
</Button>
|
||||
)
|
||||
) : null
|
||||
|
||||
return (
|
||||
<Page id="personal-data">
|
||||
@@ -146,103 +148,89 @@ export const ProfileOverview: FC = () => {
|
||||
<AvatarWrapper src={avatar ?? ""} /* TODO: onClick={openEditCharacterAvatar} */ />
|
||||
<div className="text-wrapper">
|
||||
{nameElement}
|
||||
{
|
||||
currentProfession === undefined
|
||||
? null
|
||||
: (
|
||||
<VerticalList className="rcp">
|
||||
<span>
|
||||
{((): string => {
|
||||
switch (sex.type) {
|
||||
case "Male": return translate("Male")
|
||||
case "Female": return translate("Female")
|
||||
case "BalThani": return translate("Bal’Thani")
|
||||
case "Tsajana": return translate("Tsajana")
|
||||
case "Custom": return sex.name
|
||||
default: return assertExhaustive(sex)
|
||||
}
|
||||
})()}
|
||||
</span>
|
||||
<span className="race">
|
||||
{currentRace === undefined
|
||||
? ""
|
||||
: getFullRaceName(translateMap, currentRace, currentRaceVariant)}
|
||||
</span>
|
||||
<span className="culture">
|
||||
{currentCulture === undefined
|
||||
? ""
|
||||
: getFullCultureName(translateMap, currentCulture)}
|
||||
</span>
|
||||
<span className="profession">
|
||||
{fullProfessionName}
|
||||
</span>
|
||||
</VerticalList>
|
||||
)
|
||||
}
|
||||
{currentProfession === undefined ? null : (
|
||||
<VerticalList className="rcp">
|
||||
<span>
|
||||
{((): string => {
|
||||
switch (sex.type) {
|
||||
case "Male":
|
||||
return translate("Male")
|
||||
case "Female":
|
||||
return translate("Female")
|
||||
case "BalThani":
|
||||
return translate("Bal’Thani")
|
||||
case "Tsajana":
|
||||
return translate("Tsajana")
|
||||
case "Custom":
|
||||
return sex.name
|
||||
default:
|
||||
return assertExhaustive(sex)
|
||||
}
|
||||
})()}
|
||||
</span>
|
||||
<span className="race">
|
||||
{currentRace === undefined
|
||||
? ""
|
||||
: getFullRaceName(translateMap, currentRace, currentRaceVariant)}
|
||||
</span>
|
||||
<span className="culture">
|
||||
{currentCulture === undefined
|
||||
? ""
|
||||
: getFullCultureName(translateMap, currentCulture)}
|
||||
</span>
|
||||
<span className="profession">{fullProfessionName}</span>
|
||||
</VerticalList>
|
||||
)}
|
||||
<VerticalList className="el">
|
||||
<span>
|
||||
{
|
||||
startExpLevel === undefined || currentExpLevel === undefined
|
||||
{startExpLevel === undefined || currentExpLevel === undefined
|
||||
? "—"
|
||||
: currentExpLevel.id === startExpLevel.id
|
||||
? (translateMap(startExpLevel.translations)?.name ?? "—")
|
||||
: `${(translateMap(currentExpLevel.translations)?.name ?? "—")} (${(translateMap(startExpLevel.translations)?.name ?? "—")})`
|
||||
}
|
||||
</span>
|
||||
<span>
|
||||
{translate("{0} AP", totalAdventurePoints ?? 0)}
|
||||
? translateMap(startExpLevel.translations)?.name ?? "—"
|
||||
: `${translateMap(currentExpLevel.translations)?.name ?? "—"} (${
|
||||
translateMap(startExpLevel.translations)?.name ?? "—"
|
||||
})`}
|
||||
</span>
|
||||
<span>{translate("{0} AP", totalAdventurePoints ?? 0)}</span>
|
||||
</VerticalList>
|
||||
</div>
|
||||
</GridItem>
|
||||
<GridItem className="main-profile-actions">
|
||||
{
|
||||
canAddAdventurePoints
|
||||
? (
|
||||
<Button
|
||||
className="add-ap"
|
||||
disabled // TODO: onClick={openAddAdventurePoints}
|
||||
>
|
||||
{translate("Add AP")}
|
||||
</Button>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{canAddAdventurePoints ? (
|
||||
<Button
|
||||
className="add-ap"
|
||||
disabled // TODO: onClick={openAddAdventurePoints}
|
||||
>
|
||||
{translate("Add AP")}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
className="delete-avatar"
|
||||
onClick={handleDeleteAvatar}
|
||||
disabled={avatar === undefined}
|
||||
>
|
||||
>
|
||||
{translate("Delete Avatar")}
|
||||
</Button>
|
||||
{professionNameElement}
|
||||
</GridItem>
|
||||
{
|
||||
currentProfession === undefined
|
||||
? null
|
||||
: (
|
||||
<GridItem>
|
||||
<h3>{translate("Personal Data")}</h3>
|
||||
<PersonalData />
|
||||
</GridItem>
|
||||
)
|
||||
}
|
||||
{
|
||||
showFinishCharacterCreation
|
||||
? (
|
||||
<GridItem>
|
||||
<Button
|
||||
className="end-char-creation"
|
||||
onClick={handlFinishCharacterCreation}
|
||||
primary
|
||||
disabled={!canFinishCharacterCreation}
|
||||
>
|
||||
{translate("Finish Character Creation")}
|
||||
</Button>
|
||||
</GridItem>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{currentProfession === undefined ? null : (
|
||||
<GridItem>
|
||||
<h3>{translate("Personal Data")}</h3>
|
||||
<PersonalData />
|
||||
</GridItem>
|
||||
)}
|
||||
{showFinishCharacterCreation ? (
|
||||
<GridItem>
|
||||
<Button
|
||||
className="end-char-creation"
|
||||
onClick={handlFinishCharacterCreation}
|
||||
primary
|
||||
disabled={!canFinishCharacterCreation}
|
||||
>
|
||||
{translate("Finish Character Creation")}
|
||||
</Button>
|
||||
</GridItem>
|
||||
) : null}
|
||||
{
|
||||
// TODO: Maybe.elem(3)(phase)
|
||||
// ? (
|
||||
|
||||
@@ -11,6 +11,9 @@ import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts
|
||||
import { selectStaticFocusRules } from "../../../../slices/databaseSlice.ts"
|
||||
import { FocusRulesItem } from "./FocusRulesItem.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page section for managing focus rules.
|
||||
*/
|
||||
export const FocusRules: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
|
||||
@@ -17,7 +17,7 @@ type Props = {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a single focus rule item.
|
||||
*/
|
||||
export const FocusRulesItem: FC<Props> = props => {
|
||||
const {
|
||||
|
||||
@@ -14,6 +14,9 @@ import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts
|
||||
import { selectStaticOptionalRules } from "../../../../slices/databaseSlice.ts"
|
||||
import { OptionalRulesItem } from "./OptionalRulesItem.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page section for managing optional rules.
|
||||
*/
|
||||
export const OptionalRules: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
|
||||
@@ -22,7 +22,7 @@ type Props = {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a single optional rule item.
|
||||
*/
|
||||
export const OptionalRulesItem: FC<Props> = props => {
|
||||
const {
|
||||
|
||||
@@ -14,6 +14,9 @@ import { selectStaticPublications } from "../../../../slices/databaseSlice.ts"
|
||||
import { switchIncludeAllPublications } from "../../../../slices/rulesSlice.ts"
|
||||
import { RuleSourcesListItem } from "./RuleSourcesListItem.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page section for managing the publications to use for a character.
|
||||
*/
|
||||
export const RuleSources: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Publication, PublicationTranslation } from "optolith-database-schema/types/source/Publication"
|
||||
import {
|
||||
Publication,
|
||||
PublicationTranslation,
|
||||
} from "optolith-database-schema/types/source/Publication"
|
||||
import { FC, useCallback } from "react"
|
||||
import { Checkbox } from "../../../../../shared/components/checkbox/Checkbox.tsx"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { selectIncludeAllPublications, selectIncludePublications } from "../../../../slices/characterSlice.ts"
|
||||
import {
|
||||
selectIncludeAllPublications,
|
||||
selectIncludePublications,
|
||||
} from "../../../../slices/characterSlice.ts"
|
||||
import { switchIncludePublication } from "../../../../slices/rulesSlice.ts"
|
||||
|
||||
type Props = {
|
||||
@@ -12,8 +18,13 @@ type Props = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single publication item.
|
||||
*/
|
||||
export const RuleSourcesListItem: FC<Props> = props => {
|
||||
const { publication: { publication, publicationTranslation } } = props
|
||||
const {
|
||||
publication: { publication, publicationTranslation },
|
||||
} = props
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const includeAllPublications = useAppSelector(selectIncludeAllPublications) === true
|
||||
@@ -21,23 +32,23 @@ export const RuleSourcesListItem: FC<Props> = props => {
|
||||
|
||||
const handleSwitch = useCallback(
|
||||
() => dispatch(switchIncludePublication(publication.id)),
|
||||
[ dispatch, publication.id ],
|
||||
[dispatch, publication.id],
|
||||
)
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
className={publication.contains_adult_content ? "adult-content" : undefined}
|
||||
checked={
|
||||
publication.category === "CoreRules"
|
||||
|| (includePublications?.includes(publication.id) ?? false)
|
||||
|| (!publication.contains_adult_content && includeAllPublications)
|
||||
publication.category === "CoreRules" ||
|
||||
(includePublications?.includes(publication.id) ?? false) ||
|
||||
(!publication.contains_adult_content && includeAllPublications)
|
||||
}
|
||||
onClick={handleSwitch}
|
||||
label={publicationTranslation.name}
|
||||
disabled={
|
||||
(includeAllPublications && !publication.contains_adult_content)
|
||||
|| publication.category === "CoreRules"
|
||||
(includeAllPublications && !publication.contains_adult_content) ||
|
||||
publication.category === "CoreRules"
|
||||
}
|
||||
/>
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import { OptionalRules } from "./OptionalRules.tsx"
|
||||
import { RuleSources } from "./RuleSources.tsx"
|
||||
import "./Rules.scss"
|
||||
|
||||
/**
|
||||
* Returns a page for managing a character’s ruleset.
|
||||
*/
|
||||
export const Rules: FC = () => (
|
||||
<Page id="rules">
|
||||
<Main>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { FC } from "react"
|
||||
|
||||
/**
|
||||
* An additional value for a skill row is defined by a value and a class name
|
||||
* that can be used to style the value.
|
||||
*/
|
||||
export type AdditionalValue = {
|
||||
className: string
|
||||
value?: string | number
|
||||
@@ -9,6 +13,9 @@ type Props = {
|
||||
addValues?: AdditionalValue[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of additional values for the skill row.
|
||||
*/
|
||||
export const SkillAdditionalValues: FC<Props> = props => {
|
||||
const { addValues } = props
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ type Props = {
|
||||
selectForInfo(id: number): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of buttons for a skill row.
|
||||
*/
|
||||
export const SkillButtons: React.FC<Props> = props => {
|
||||
const {
|
||||
activateDisabled,
|
||||
|
||||
@@ -7,6 +7,9 @@ type Props = {
|
||||
getGroupName?: (id: number) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a row section that display a skill group.
|
||||
*/
|
||||
export const SkillGroup: FC<Props> = props => {
|
||||
const { addText, group, getGroupName } = props
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ type Props = {
|
||||
ic?: ImprovementCost
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a row section that displays an improvement cost.
|
||||
*/
|
||||
export const SkillImprovementCost: FC<Props> = props => {
|
||||
const { ic } = props
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ type Props = {
|
||||
addPoint?(id: number): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a row section that displays a skill rating.
|
||||
*/
|
||||
export const SkillRating: FC<Props> = props => {
|
||||
const { isNotActive, noIncrease, sr, addPoint } = props
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ const isTopMarginNeeded = (
|
||||
mprev !== undefined &&
|
||||
curr.static.group.id.skill_group !== mprev.static.group.id.skill_group
|
||||
|
||||
/**
|
||||
* Returns a page for managing skills.
|
||||
*/
|
||||
export const Skills: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
|
||||
@@ -19,6 +19,9 @@ import {
|
||||
selectSpecialAbilitiesSortOrder,
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
|
||||
/**
|
||||
* Returns a page for managing special abilities.
|
||||
*/
|
||||
export const SpecialAbilities: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
Reference in New Issue
Block a user