Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GO: Builds indicator for artifacts #2620

Merged
merged 10 commits into from
Jan 26, 2025
3 changes: 3 additions & 0 deletions libs/gi/db/src/Database/DataManagers/BuildDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { defaultInitialWeaponKey, initialWeapon } from './WeaponDataManager'
export interface Build {
name: string
description: string
id: string

weaponId?: string
artifactIds: Record<ArtifactSlotKey, string | undefined>
Expand All @@ -29,6 +30,7 @@ export class BuildDataManager extends DataManager<
}
override validate(obj: unknown): Build | undefined {
let { name, description, weaponId, artifactIds } = obj as Build
const { id } = obj as Build
if (typeof name !== 'string') name = 'Build Name'
if (typeof description !== 'string') description = ''
if (weaponId && !this.database.weapons.get(weaponId)) weaponId = undefined
Expand Down Expand Up @@ -62,6 +64,7 @@ export class BuildDataManager extends DataManager<
description,
weaponId,
artifactIds,
id,
}
}

Expand Down
5 changes: 4 additions & 1 deletion libs/gi/localization/assets/locales/en/artifact.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,8 @@
"rvSliderBtn": {
"maximum": "MRV",
"current": "RV"
}
},
"builds_one": "{{count}} Build",
"builds_other": "{{count}} Builds",
"artifactUsage": "Artifact is used in the following loadouts and builds: "
}
124 changes: 118 additions & 6 deletions libs/gi/ui/src/components/artifact/ArtifactCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'
// use client due to hydration difference between client rendering and server in translation
import { useBoolState } from '@genshin-optimizer/common/react-util'
import { iconInlineProps } from '@genshin-optimizer/common/svgicons'
import {
BootstrapTooltip,
Expand All @@ -8,13 +9,16 @@ import {
ConditionalWrapper,
InfoTooltip,
InfoTooltipInline,
ModalWrapper,
NextImage,
SqBadge,
StarsDisplay,
} from '@genshin-optimizer/common/ui'
import { clamp, clamp01, getUnitStr } from '@genshin-optimizer/common/util'
import { artifactAsset } from '@genshin-optimizer/gi/assets'
import type {
ArtifactRarity,
CharacterKey,
LocationKey,
SubstatKey,
} from '@genshin-optimizer/gi/consts'
Expand All @@ -23,7 +27,7 @@ import {
allSubstatKeys,
} from '@genshin-optimizer/gi/consts'
import type { ICachedArtifact, ICachedSubstat } from '@genshin-optimizer/gi/db'
import { useArtifact } from '@genshin-optimizer/gi/db-ui'
import { useArtifact, useDatabase } from '@genshin-optimizer/gi/db-ui'
import { SlotIcon, StatIcon } from '@genshin-optimizer/gi/svgicons'
import {
artDisplayValue,
Expand All @@ -40,18 +44,23 @@ import {
Button,
CardActionArea,
CardContent,
CardHeader,
Chip,
IconButton,
List,
ListItem,
ListItemIcon,
ListItemText,
Skeleton,
SvgIcon,
Typography,
} from '@mui/material'
import type { ReactNode } from 'react'
import { Suspense, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ExcludeIcon } from '../../consts'
import { CloseIcon, ExcludeIcon, LoadoutIcon } from '../../consts'
import { PercentBadge } from '../PercentBadge'
import { LocationAutocomplete, LocationName } from '../character'
import { CharIconSide, LocationAutocomplete, LocationName } from '../character'
import { ArtifactSetTooltipContent } from './ArtifactSetTooltip'
import {
ArtifactSetName,
Expand Down Expand Up @@ -96,7 +105,7 @@ export function ArtifactCardObj({
} & Data) {
const { t } = useTranslation(['artifact', 'ui'])
const { t: tk } = useTranslation('statKey_gen')

const [showUsage, onShowUsage, onHideUsage] = useBoolState(false)
const wrapperFunc = useCallback(
(children: ReactNode) => (
<CardActionArea
Expand Down Expand Up @@ -153,7 +162,29 @@ export function ArtifactCardObj({
Math.min(mainStatAssumptionLevel, rarity * 4),
level
)

const database = useDatabase()
const builds: {
loadoutName: string
buildName: string
charKey: CharacterKey
}[] = useMemo(() => {
return database.builds.values
.filter(
({ artifactIds }) => artifactIds[artifact.slotKey] === artifact.id
)
.flatMap(({ id, name }) => {
const buildName = name
return database.teamChars.values
.filter(({ buildIds }) => buildIds.includes(id))
.map(({ key, name }) => {
return {
charKey: key,
buildName,
loadoutName: name,
}
})
})
}, [database.builds, database.teamChars, artifact.slotKey, artifact.id])
const artifactValid = maxEfficiency !== 0
const slotName = <ArtifactSetSlotName setKey={setKey} slotKey={slotKey} />
const slotDesc = <ArtifactSetSlotDesc setKey={setKey} slotKey={slotKey} />
Expand All @@ -178,6 +209,14 @@ export function ArtifactCardObj({
/>
}
>
<Suspense fallback={false}>
<ArtifactBuildUsageModal
show={showUsage}
onHide={onHideUsage}
usageText={t('artifact:artifactUsage')}
builds={builds}
/>
</Suspense>
<CardThemed
bgt="light"
sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}
Expand Down Expand Up @@ -350,14 +389,32 @@ export function ArtifactCardObj({
</Typography>
)}
<Box flexGrow={1} />
<Typography color="success.main">
<Typography
color="success.main"
sx={{
display: 'flex',
gap: 1,
alignItems: 'center',
mt: 1,
}}
>
{(setKey && <ArtifactSetName setKey={setKey} />) ||
'Artifact Set'}{' '}
{setKey && (
<InfoTooltipInline
title={<ArtifactSetTooltipContent setKey={setKey} />}
/>
)}
<SqBadge
sx={{
ml: 'auto',
cursor: builds.length ? 'pointer' : 'default',
}}
color={builds.length ? 'success' : 'secondary'}
onClick={builds.length ? onShowUsage : undefined}
>
{t('builds', { count: builds.length })}
</SqBadge>
</Typography>
</CardContent>
</ConditionalWrapper>
Expand Down Expand Up @@ -488,3 +545,58 @@ function SubstatDisplay({
</Box>
)
}

function ArtifactBuildUsageModal({
show,
onHide,
usageText,
builds,
}: {
show: boolean
onHide: () => void
usageText: string
builds: {
loadoutName: string
buildName: string
charKey: CharacterKey
}[]
}) {
return (
<ModalWrapper open={show} onClose={onHide}>
<CardThemed>
<CardHeader
title={
<Typography
variant="h6"
flexGrow={1}
display="flex"
alignItems="center"
>
{usageText}
</Typography>
}
action={
<IconButton onClick={onHide}>
<CloseIcon />
</IconButton>
}
/>
<List>
{builds.map((build, index) => (
<ListItem key={index}>
<ListItemIcon>
<CharIconSide characterKey={build.charKey} />
</ListItemIcon>
<LoadoutIcon titleAccess="Loadout" fontSize="small" />
<ListItemText
disableTypography={true}
sx={{ display: 'flex', alignItems: 'center' }}
primary={`${build.loadoutName}: ${build.buildName}`}
/>
</ListItem>
))}
</List>
</CardThemed>
</ModalWrapper>
)
}
Loading