Skip to content

Commit

Permalink
Support changing a gear to titan gear & input random stat titan (augm…
Browse files Browse the repository at this point in the history
…ent) values
  • Loading branch information
apache1123 committed Jan 5, 2024
1 parent 2d199bd commit d629c8e
Show file tree
Hide file tree
Showing 32 changed files with 461 additions and 268 deletions.
Binary file added public/icons/gear/titan/armor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/belt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/boots.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/bracers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/combat-engine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/exoskeleton.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/eyepiece.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/gloves.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/helmet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/legguards.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/microreactor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/gear/titan/spaulders.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions src/components/GearTypeIcon/GearTypeIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ import type { GearName } from '../../constants/gear-types';

export interface GearTypeIconProps {
gearName: GearName | undefined;
isTitan?: boolean;
size?: number;
}

export const GearTypeIcon = ({ gearName, size = 80 }: GearTypeIconProps) => {
export const GearTypeIcon = ({
gearName,
isTitan = false,
size = 80,
}: GearTypeIconProps) => {
if (gearName) {
const imageName = gearName.toLowerCase().replaceAll(' ', '-');
const imagePath = `/icons/gear/${imageName}.png`;
const imagePath = isTitan
? `/icons/gear/titan/${imageName}.png`
: `/icons/gear/${imageName}.png`;

return (
<Image src={imagePath} alt={gearName} width={size} height={size}></Image>
Expand Down
22 changes: 16 additions & 6 deletions src/components/GearTypeSelector/GearTypeSelector.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ const meta: Meta<typeof GearTypeSelector> = {
export default meta;
type Story = StoryObj<typeof GearTypeSelector>;

const selectedGearType = {
id: 'Helmet',
displayName: 'Helmet',
} as GearType;
const selectedValue = {
gearType: {
id: 'Helmet',
displayName: 'Helmet',
} as GearType,
isTitan: true,
};

export const NoGearTypeSelected: Story = {};

export const SelectedGearType: Story = {
export const SelectedValue: Story = {
args: {
selectedValue,
},
};

export const DisableGearTypeChange: Story = {
args: {
selectedGearType,
selectedValue,
disableGearTypeChange: true,
},
};
76 changes: 62 additions & 14 deletions src/components/GearTypeSelector/GearTypeSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,84 @@
import { Autocomplete, TextField } from '@mui/material';
import type { SyntheticEvent } from 'react';
import { Autocomplete, Stack, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';

import { gearTypesLookup } from '../../constants/gear-types';
import type { GearType } from '../../models/gear-type';
import { GearTypeIcon } from '../GearTypeIcon/GearTypeIcon';

type Value = { gearType: GearType; isTitan: boolean };
export interface GearTypeSelectorProps {
selectedGearType: GearType | undefined;
onChange(value: GearType): void;
selectedValue: Value | undefined;
onChange?(value: Value): void;
/** Disable gear type change (e.g. from a Helmet to an Eyepiece) if a gear type is already selected, but still allow the change to a Titan/non-Titan variation of that gear type */
disableGearTypeChange?: boolean;
disabled?: boolean;
}

const options = gearTypesLookup.allIds.map((id) => gearTypesLookup.byId[id]);

export const GearTypeSelector = ({
selectedGearType,
selectedValue,
onChange,
disableGearTypeChange,
disabled,
}: GearTypeSelectorProps) => {
const handleChange = (_: SyntheticEvent, value: GearType) => {
onChange(value);
};
const [options, setOptions] = useState<Value[]>([]);

// When there is a selected gear type and it cannot be changed e.g. in loadouts where the gear type is fixed, only allow two options - the non-titan gear of that type and a titan gear of that type.
// Other cases, show all gear types and their titan variants
useEffect(() => {
setOptions(
selectedValue && disableGearTypeChange
? gearTypesLookup.allIds
.filter((id) => selectedValue.gearType.id === id)
.reduce((acc, id) => {
const gearType = gearTypesLookup.byId[id];
return acc.concat([
{ gearType, isTitan: false },
{ gearType, isTitan: true },
]);
}, [] as Value[])
: gearTypesLookup.allIds
.map((id) => ({
gearType: gearTypesLookup.byId[id],
isTitan: false,
}))
.concat(
gearTypesLookup.allIds.map((id) => ({
gearType: gearTypesLookup.byId[id],
isTitan: true,
}))
)
);
}, [selectedValue, disableGearTypeChange]);

return (
<Autocomplete
options={options}
getOptionLabel={(option) => option.displayName}
getOptionLabel={(option) =>
option.isTitan
? `Titan ${option.gearType.displayName}`
: option.gearType.displayName
}
renderInput={(params) => (
<TextField {...params} label="Select gear type" variant="standard" />
)}
value={selectedGearType}
onChange={handleChange}
isOptionEqualToValue={(option, value) => option.id === value.id}
value={selectedValue}
onChange={(_, { gearType, isTitan }) => {
if (onChange) {
onChange({ gearType, isTitan });
}
}}
isOptionEqualToValue={(option, value) =>
option.gearType.id === value.gearType.id &&
option.isTitan === value.isTitan
}
renderOption={(props, { gearType, isTitan }) => (
<Stack direction="row" spacing={1} component="li" {...props}>
<GearTypeIcon gearName={gearType.id} isTitan={isTitan} size={30} />
<Typography>
{isTitan ? `Titan ${gearType.displayName}` : gearType.displayName}
</Typography>
</Stack>
)}
disableClearable
size="small"
fullWidth
Expand Down
2 changes: 1 addition & 1 deletion src/components/NumericInput/NumericInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getNumberSeparators } from '../../utils/locale-utils';
export interface NumericInputProps {
value?: number;
onChange?: (value: number) => unknown;
label?: string;
label?: ReactNode;
name?: string;
id?: string;
variant?: TextFieldVariants;
Expand Down
16 changes: 13 additions & 3 deletions src/features/GearOCRModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ReactNode } from 'react';
import { useState } from 'react';
import { useSnapshot } from 'valtio';

import { GearTypeSelector } from '../components/GearTypeSelector/GearTypeSelector';
import { ImageOCR } from '../components/ImageOCR/ImageOCR';
import { ButtonModal } from '../components/Modal/ButtonModal';
import {
Expand All @@ -21,6 +22,7 @@ import { Gear } from '../models/gear';
import type { GearType } from '../models/gear-type';
import { RandomStat } from '../models/random-stat';
import type { StatType } from '../models/stat-type';
import type { OcrState } from '../states/ocr-temp-gear';
import {
ocrState,
removeOCRTempGear,
Expand Down Expand Up @@ -53,7 +55,7 @@ export const GearOCRModal = ({
iconButton,
}: GearOCRModalProps) => {
const { tempGear: tempGearState } = ocrState;
const { tempGear: tempGearSnap } = useSnapshot(ocrState);
const { tempGear: tempGearSnap } = useSnapshot(ocrState) as OcrState;

const [imageURL, setImageURL] = useState<string>();
const handleImageURLChange = (imageURL: string) => {
Expand Down Expand Up @@ -145,9 +147,17 @@ export const GearOCRModal = ({
)}
{!errorMessage && tempGearSnap && tempGearState && (
<GearPiece
gearSnap={tempGearSnap as Gear}
gearSnap={tempGearSnap}
gearState={tempGearState}
disableGearTypeChange
gearTypeSelector={
<GearTypeSelector
selectedValue={{
gearType: tempGearSnap.type,
isTitan: tempGearSnap.isAugmented,
}}
disabled
/>
}
/>
)}
</Grid>
Expand Down
70 changes: 20 additions & 50 deletions src/features/GearPiece.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,23 @@ import Grid from '@mui/material/Unstable_Grid2';
import type { ReactNode } from 'react';

import { GearTypeIcon } from '../components/GearTypeIcon/GearTypeIcon';
import { GearTypeSelector } from '../components/GearTypeSelector/GearTypeSelector';
import type { CoreElementalType } from '../constants/elemental-type';
import type { Gear } from '../models/gear';
import type { GearType } from '../models/gear-type';
import { getPossibleRandomStatTypes } from '../models/gear-type';
import { RandomStat } from '../models/random-stat';
import type { SaveGearModalProps } from './gear-comparer/SaveGearModal';
import { SaveGearModal } from './gear-comparer/SaveGearModal';
import { GearAttackStatsSummary } from './GearAttackStatsSummary';
import { GearOCRModal } from './GearOCRModal';
import { GearRollBreakdown } from './GearRollBreakdown';
import { GearStars } from './GearStars';
import { EmptyStatEditor, StatEditor } from './StatEditor';

export interface GearPieceProps {
gearSnap: Gear;
gearState: Gear;
showGearOCRButton?: { onGearChangeFromOCR: (gearFromOCR: Gear) => void };
disableGearTypeChange?: boolean;
onGearTypeChange?: (gearType: GearType) => void;
showSaveGearButton?: Pick<SaveGearModalProps, 'targetLoadout'>;
// TODO: Explore potentially moving `gearTypeSelector` and `actions` out of this component (need to rethink layout)
/** Gear type selector is defined as a slot, as changing a gear instance's gear type is forbidden. Changing the gear type is essentially switching to another gear instance. Kept here for layout purposes */
gearTypeSelector?: ReactNode;
/** External actions such as `Import gear` & `Save gear` that make no sense to be orchestrated by a `Gear` instance, but can be slotted here (for layout purposes) */
actions?: ReactNode;
showStatSummary?: CoreElementalType;
maxTitanStatsContent?: ReactNode;
additionalAccordions?: ReactNode;
Expand All @@ -42,35 +38,26 @@ export interface GearPieceProps {
export const GearPiece = ({
gearSnap,
gearState,
showGearOCRButton,
disableGearTypeChange,
onGearTypeChange,
showSaveGearButton,
gearTypeSelector,
actions,
showStatSummary,
maxTitanStatsContent,
additionalAccordions,
'data-testid': dataTestId,
}: GearPieceProps) => {
const gearType = gearSnap.type;
const isTitan = gearSnap.isAugmented;
const possibleRandomStatTypes = getPossibleRandomStatTypes(gearType);

return (
<Layout
typeIcon={<GearTypeIcon gearName={gearType.id} size={70} />}
typeSelector={
<GearTypeSelector
selectedGearType={gearType}
onChange={(gearType) => {
if (onGearTypeChange && !disableGearTypeChange) {
onGearTypeChange(gearType);
}
}}
disabled={disableGearTypeChange}
/>
typeIcon={
<GearTypeIcon gearName={gearType.id} isTitan={isTitan} size={70} />
}
typeSelector={gearTypeSelector}
starsSelector={<GearStars gearSnap={gearSnap} gearState={gearState} />}
randomStats={
<>
<Stack spacing={2}>
{gearSnap.randomStats.map((randomStatSnap, i) => {
return randomStatSnap && gearState.randomStats[i] ? (
<StatEditor
Expand All @@ -79,39 +66,22 @@ export const GearPiece = ({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
statState={gearState.randomStats[i]!}
possibleStatTypes={possibleRandomStatTypes}
isAugmented={gearSnap.isAugmented}
/>
) : (
<EmptyStatEditor
key={i}
possibleStatTypes={possibleRandomStatTypes}
isAugmented={gearSnap.isAugmented}
onStatTypeChange={(statType) => {
gearState.randomStats[i] = new RandomStat(statType);
}}
/>
);
})}
</>
}
additionalActions={
<>
{showGearOCRButton && (
<GearOCRModal
onFinalizeGear={(replacementGear) => {
showGearOCRButton.onGearChangeFromOCR(replacementGear);
}}
enforceGearType={
disableGearTypeChange ? gearSnap.type.id : undefined
}
/>
)}
{showSaveGearButton && showSaveGearButton.targetLoadout && (
<SaveGearModal
gear={gearState}
targetLoadout={showSaveGearButton.targetLoadout}
/>
)}
</>
</Stack>
}
actions={actions}
summary={
<>
{showStatSummary && (
Expand Down Expand Up @@ -167,15 +137,15 @@ function Layout({
typeSelector,
starsSelector,
randomStats,
additionalActions,
actions,
summary,
'data-testid': dataTestId,
}: {
typeIcon: ReactNode;
typeSelector: ReactNode;
starsSelector: ReactNode;
randomStats: ReactNode;
additionalActions: ReactNode;
actions: ReactNode;
summary: ReactNode;
['data-testid']?: string;
}) {
Expand All @@ -190,8 +160,8 @@ function Layout({
<Box mt={1}>{starsSelector}</Box>
</Grid>
</Grid>
<Stack direction="row-reverse" spacing={1} mb={1}>
{additionalActions}
<Stack direction="row-reverse" spacing={1} mb={2}>
{actions}
</Stack>
<Box mb={3}>{randomStats}</Box>
<Box>{summary}</Box>
Expand Down
Loading

0 comments on commit d629c8e

Please sign in to comment.