From 9c445b628026a196a2ea8d492490fb12c310aef5 Mon Sep 17 00:00:00 2001 From: frzyc Date: Wed, 27 Nov 2024 04:24:47 -0500 Subject: [PATCH] split up combo editor into the teammate view --- libs/common/ui/src/hooks/index.ts | 1 + libs/common/ui/src/hooks/useScrollRef.tsx | 10 ++ .../Tabs/TabOverview/index.tsx | 10 +- libs/sr/formula-ui/src/lightCone/util.tsx | 8 +- libs/sr/page-team/src/BonusStats.tsx | 74 +++----- libs/sr/page-team/src/ComboEditor.tsx | 157 ++--------------- .../page-team/src/LightConeSheetDisplay.tsx | 3 +- .../page-team/src/TeamCharacterSelector.tsx | 21 ++- libs/sr/page-team/src/TeamHeader.tsx | 41 +++++ libs/sr/page-team/src/TeamNameCardHeader.tsx | 1 + libs/sr/page-team/src/TeammateDisplay.tsx | 161 +++++++++++++++++- libs/sr/page-team/src/index.tsx | 50 +++--- 12 files changed, 295 insertions(+), 242 deletions(-) create mode 100644 libs/common/ui/src/hooks/useScrollRef.tsx create mode 100644 libs/sr/page-team/src/TeamHeader.tsx diff --git a/libs/common/ui/src/hooks/index.ts b/libs/common/ui/src/hooks/index.ts index 71019af6c2..35a2b955b6 100644 --- a/libs/common/ui/src/hooks/index.ts +++ b/libs/common/ui/src/hooks/index.ts @@ -4,5 +4,6 @@ export * from './useIsMount' export * from './useOnScreen' export * from './useRefOverflow' export * from './useRefSize' +export * from './useScrollRef' export * from './useTitle' export * from './useWindowScrollPos' diff --git a/libs/common/ui/src/hooks/useScrollRef.tsx b/libs/common/ui/src/hooks/useScrollRef.tsx new file mode 100644 index 0000000000..ee1e478b00 --- /dev/null +++ b/libs/common/ui/src/hooks/useScrollRef.tsx @@ -0,0 +1,10 @@ +import { useCallback, useRef } from 'react' + +export function useScrollRef() { + const scrollRef = useRef() + const onScroll = useCallback( + () => scrollRef.current?.scrollIntoView({ behavior: 'smooth' }), + [scrollRef] + ) + return [scrollRef, onScroll] as const +} diff --git a/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOverview/index.tsx b/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOverview/index.tsx index 69c2cedfd6..3e4ced6a81 100644 --- a/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOverview/index.tsx +++ b/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOverview/index.tsx @@ -1,4 +1,4 @@ -import { CardThemed } from '@genshin-optimizer/common/ui' +import { CardThemed, useScrollRef } from '@genshin-optimizer/common/ui' import { allArtifactSlotKeys } from '@genshin-optimizer/gi/consts' import { ArtifactCardNano, @@ -10,18 +10,14 @@ import { } from '@genshin-optimizer/gi/ui' import { uiInput as input } from '@genshin-optimizer/gi/wr' import { Box, Grid, Stack } from '@mui/material' -import { useCallback, useContext, useMemo, useRef } from 'react' +import { useContext, useMemo } from 'react' import CharacterProfileCard from '../../../CharProfileCard' import useCompareData from '../../../useCompareData' import CompareBtn from '../../CompareBtn' import EquipmentSection from './EquipmentSection' export default function TabOverview() { - const scrollRef = useRef() - const onScroll = useCallback( - () => scrollRef?.current?.scrollIntoView?.({ behavior: 'smooth' }), - [scrollRef] - ) + const [scrollRef, onScroll] = useScrollRef() const data = useContext(DataContext) const compareData = useCompareData() diff --git a/libs/sr/formula-ui/src/lightCone/util.tsx b/libs/sr/formula-ui/src/lightCone/util.tsx index afcd3d2f6c..7f33e822ac 100644 --- a/libs/sr/formula-ui/src/lightCone/util.tsx +++ b/libs/sr/formula-ui/src/lightCone/util.tsx @@ -1,5 +1,3 @@ -import { own } from '@genshin-optimizer/sr/formula' -import { useSrCalcContext } from '@genshin-optimizer/sr/ui' import type { ReactNode } from 'react' export function SuperImposeWrapper({ @@ -7,7 +5,9 @@ export function SuperImposeWrapper({ }: { children: (superimpose: number) => ReactNode }) { - const calc = useSrCalcContext() - const superimpose = calc?.compute(own.lightCone.superimpose).val ?? 1 + // const calc = useSrCalcContext() + + // TODO: FIXME: a character without a lightcone will cause an error + const superimpose = 1 // calc?.compute(own.lightCone.superimpose).val ?? 1 return children(superimpose) } diff --git a/libs/sr/page-team/src/BonusStats.tsx b/libs/sr/page-team/src/BonusStats.tsx index 259ca0ddbc..e9eaac3b6a 100644 --- a/libs/sr/page-team/src/BonusStats.tsx +++ b/libs/sr/page-team/src/BonusStats.tsx @@ -1,8 +1,6 @@ -import { useBoolState } from '@genshin-optimizer/common/react-util' import { CardThemed, DropdownButton, - ModalWrapper, NumberInputLazy, SqBadge, } from '@genshin-optimizer/common/ui' @@ -13,7 +11,6 @@ import type { Member, Tag } from '@genshin-optimizer/sr/formula' import { tagFieldMap } from '@genshin-optimizer/sr/formula-ui' import { DeleteForever } from '@mui/icons-material' import { - Button, CardContent, CardHeader, Divider, @@ -25,23 +22,7 @@ import { import { useContext } from 'react' import { PresetContext, useTeamContext, useTeammateContext } from './context' -export function BonusStats() { - const [open, onOpen, onClose] = useBoolState() - return ( - <> - - - - ) -} - -function BonusStatsModal({ - open, - onClose, -}: { - open: boolean - onClose: () => void -}) { +export function BonusStatsSection() { const { database } = useDatabaseContext() const { presetIndex } = useContext(PresetContext) const { @@ -53,34 +34,33 @@ function BonusStatsModal({ const tag = newTag(q, characterKey) database.teams.setBonusStat(teamId, tag, 0, presetIndex) } + if (!presetIndex) return null return ( - - - - - - - {bonusStats.map(({ tag, values }, i) => ( - - database.teams.setBonusStat(teamId, tag, value, presetIndex) - } - onDelete={() => - database.teams.setBonusStat(teamId, tag, 0, presetIndex) - } - setTag={(tag) => - database.teams.setBonusStat(teamId, tag, 0, presetIndex) - } - /> - ))} - - - - - + + + + + + {bonusStats.map(({ tag, values }, i) => ( + + database.teams.setBonusStat(teamId, tag, value, presetIndex) + } + onDelete={() => + database.teams.setBonusStat(teamId, tag, 0, presetIndex) + } + setTag={(tag) => + database.teams.setBonusStat(teamId, tag, 0, presetIndex) + } + /> + ))} + + + + ) } function newTag(q: Tag['q'], member: Member): Tag { diff --git a/libs/sr/page-team/src/ComboEditor.tsx b/libs/sr/page-team/src/ComboEditor.tsx index 797e7b9e24..056e7ff9eb 100644 --- a/libs/sr/page-team/src/ComboEditor.tsx +++ b/libs/sr/page-team/src/ComboEditor.tsx @@ -1,67 +1,31 @@ -import { useBoolState } from '@genshin-optimizer/common/react-util' -import { - CardThemed, - ModalWrapper, - NumberInputLazy, -} from '@genshin-optimizer/common/ui' +import { CardThemed } from '@genshin-optimizer/common/ui' import { getUnitStr, valueString } from '@genshin-optimizer/common/util' import { TagContext } from '@genshin-optimizer/pando/ui-sheet' import type { Frame } from '@genshin-optimizer/sr/db' import { useDatabaseContext } from '@genshin-optimizer/sr/db-ui' import { Read } from '@genshin-optimizer/sr/formula' import { useSrCalcContext } from '@genshin-optimizer/sr/ui' -import CloseIcon from '@mui/icons-material/Close' -import Delete from '@mui/icons-material/Delete' -import { - Box, - Button, - CardActionArea, - CardContent, - CardHeader, - Divider, - IconButton, - InputAdornment, - Stack, - Typography, -} from '@mui/material' +import { Box, CardActionArea, Divider, Typography } from '@mui/material' import { useCallback, useContext, useMemo } from 'react' import { PresetContext, useTeamContext } from './context' -import { LightConeSheetsDisplay } from './LightConeSheetsDisplay' import { OptimizationTargetDisplay } from './Optimize/OptimizationTargetDisplay' import { OptimizationTargetSelector } from './Optimize/OptimizationTargetSelector' -import { RelicSheetsDisplay } from './RelicSheetsDisplay' export function ComboEditor() { const { database } = useDatabaseContext() const { team, teamId } = useTeamContext() return ( - {team.frames.map((frame, i) => ( - - database.teams.set(teamId, (team) => { - team.frames = [...team.frames] - team.frames[i] = { - ...team.frames[i], - ...frame, - } - }) - } - removeFrame={() => - database.teams.set(teamId, (team) => { - team.frames = team.frames.filter((_, index) => index !== i) - }) - } - /> + ))} - + ) } -function Combo({ - frame, - index, - setFrame, - removeFrame, -}: { - frame: Frame - index: number - setFrame(frame: Partial): void - removeFrame(): void -}) { +function Combo({ frame, index }: { frame: Frame; index: number }) { const { presetIndex, setPresetIndex } = useContext(PresetContext) const calc = useSrCalcContext() const tagcontext = useContext(TagContext) @@ -101,16 +55,10 @@ function Combo({ [calc, frame.tag, tagcontext] ) const unit = getUnitStr(frame.tag.q ?? '') - const [open, onOpen, onClose] = useBoolState() const handleClick = useCallback(() => { - if (presetIndex === index) { - onOpen() - } else { - onClose() - setPresetIndex(index) - } - }, [index, onClose, onOpen, presetIndex, setPresetIndex]) + setPresetIndex(index) + }, [index, setPresetIndex]) return ( - {index + 1} @@ -149,80 +89,3 @@ function Combo({ ) } - -function ComboEditorModal({ - index, - frame, - setFrame, - removeFrame, - show, - onClose, -}: { - frame: Frame - index: number - setFrame(frame: Partial): void - removeFrame(): void - show: boolean - onClose: () => void -}) { - return ( - - - {/* TODO: translation */} - - - - } - /> - - - - - setFrame({ multiplier: v })} - size="small" - InputProps={{ - startAdornment: ( - Multi x - ), - }} - /> - - setFrame({ - tag, - }) - } - /> - - - - Relic Conditionals - - - - Lightcone Conditionals - - - - - - - ) -} diff --git a/libs/sr/page-team/src/LightConeSheetDisplay.tsx b/libs/sr/page-team/src/LightConeSheetDisplay.tsx index 6f37befd52..10c0b65364 100644 --- a/libs/sr/page-team/src/LightConeSheetDisplay.tsx +++ b/libs/sr/page-team/src/LightConeSheetDisplay.tsx @@ -1,6 +1,5 @@ import { CardThemed, NextImage } from '@genshin-optimizer/common/ui' -import type { UISheetElement } from '@genshin-optimizer/pando/ui-sheet' -import { DocumentDisplay } from '@genshin-optimizer/pando/ui-sheet' +import { DocumentDisplay, type UISheetElement } from '@genshin-optimizer/pando/ui-sheet' import { lightConeAsset } from '@genshin-optimizer/sr/assets' import type { LightConeKey } from '@genshin-optimizer/sr/consts' import { lightConeUiSheets } from '@genshin-optimizer/sr/formula-ui' diff --git a/libs/sr/page-team/src/TeamCharacterSelector.tsx b/libs/sr/page-team/src/TeamCharacterSelector.tsx index 7c3c2e7480..d9503934b3 100644 --- a/libs/sr/page-team/src/TeamCharacterSelector.tsx +++ b/libs/sr/page-team/src/TeamCharacterSelector.tsx @@ -1,3 +1,8 @@ +import { ImgIcon } from '@genshin-optimizer/common/ui' +import { + characterAsset, + characterKeyToGenderedKey, +} from '@genshin-optimizer/sr/assets' import type { CharacterKey } from '@genshin-optimizer/sr/consts' import { useTeam } from '@genshin-optimizer/sr/db-ui' import PersonIcon from '@mui/icons-material/Person' @@ -38,6 +43,7 @@ export function TeamCharacterSelector({ height: '4px', backgroundColor: 'rgb(200,200,200,0.5)', //team settings }, + minHeight: '1em', } }} > @@ -45,7 +51,19 @@ export function TeamCharacterSelector({ const characterKey = teammateDatum?.characterKey return ( } + icon={ + characterKey ? ( + + ) : ( + + ) + } iconPosition="start" value={characterKey ?? ind} key={ind} @@ -61,6 +79,7 @@ export function TeamCharacterSelector({ // conserve the current tab when switching to another character characterKey && navigate(`/teams/${teamId}/${characterKey}`) } + sx={{ p: 1, minHeight: '1em' }} /> ) })} diff --git a/libs/sr/page-team/src/TeamHeader.tsx b/libs/sr/page-team/src/TeamHeader.tsx new file mode 100644 index 0000000000..e66104b504 --- /dev/null +++ b/libs/sr/page-team/src/TeamHeader.tsx @@ -0,0 +1,41 @@ +import { CardThemed } from '@genshin-optimizer/common/ui' +import type { CharacterKey } from '@genshin-optimizer/sr/consts' +import { Box, Divider } from '@mui/material' +import { createContext, type MutableRefObject } from 'react' +import { ComboEditor } from './ComboEditor' +import { TeamCharacterSelector } from './TeamCharacterSelector' +import TeamNameCardHeader from './TeamNameCardHeader' +export const DEFAULT_HEADER_HEIGHT_PX = 210 +export const HEADER_TOP_PX = 2 +export const TeamHeaderHeightContext = createContext(DEFAULT_HEADER_HEIGHT_PX) +export function TeamHeader({ + teamId, + characterKey, + headerRef, +}: { + teamId: string + characterKey?: CharacterKey + headerRef: MutableRefObject +}) { + return ( + + ({ outline: `solid ${theme.palette.secondary.main}` })} + > + + + + + + + + ) +} diff --git a/libs/sr/page-team/src/TeamNameCardHeader.tsx b/libs/sr/page-team/src/TeamNameCardHeader.tsx index 80bf2e7ab7..20917828b1 100644 --- a/libs/sr/page-team/src/TeamNameCardHeader.tsx +++ b/libs/sr/page-team/src/TeamNameCardHeader.tsx @@ -60,6 +60,7 @@ export default function TeamNameCardHeader() { '&:hover': { color: 'info.light', }, + py: 1, }} > = + useMemo(() => { + const frame = team.frames[presetIndex] + if (!frame) return [['char', 'Character', ]] + return [ + ['combo', `Edit Combo ${presetIndex + 1}`, ], + ['char', 'Character', ], + ['talent', 'Talent', ], + ['relicCond', 'Relic Conditionals', ], + [ + 'lightConeCond', + 'Light Cone Conditionals', + , + ], + ['opt', 'Optimize', ], + ] as const + }, [team, presetIndex]) + + return ( + + + {sections.map(([key, title, content], i) => ( +
+ {content} +
+ ))} +
+
+ ) +} +function Section({ + index, + title, + children, +}: { + index: number + title: React.ReactNode + children: React.ReactNode +}) { + const [charScrollRef, onScroll] = useScrollRef() + const numSections = useContext(SectionNumContext) + const headerHeight = useContext(TeamHeaderHeightContext) + return ( + <> + ({ + outline: `solid ${theme.palette.secondary.main}`, + position: 'sticky', + top: headerHeight + index * SECTION_SPACING_PX, + bottom: BOT_PX + (numSections - 1 - index) * SECTION_SPACING_PX, + zIndex: 100, + })} + > + + {title} + + + + {children} + + + ) +} +function ComboEditorSection() { + const { database } = useDatabaseContext() + const { presetIndex } = useContext(PresetContext) + const { team, teamId } = useTeamContext() + const frame = useMemo(() => team.frames[presetIndex], [team, presetIndex]) + const setFrame = (frame: Partial) => { + database.teams.set(teamId, (team) => { + team.frames = [...team.frames] + team.frames[presetIndex] = { + ...team.frames[presetIndex], + ...frame, + } + }) + } + const removeFrame = () => { + database.teams.set(teamId, (team) => { + team.frames = team.frames.filter((_, index) => index !== presetIndex) + }) + } + if (!frame) return null + return ( + + setFrame({ multiplier: v })} + size="small" + InputProps={{ + startAdornment: ( + Multi x + ), + }} + /> + + setFrame({ + tag, + }) + } + /> + + + ) +} +function CharacterSection() { + const { characterKey } = useTeammateContext() const character = useCharacterContext() const [editorKey, setCharacterKey] = useState( undefined ) - return ( - + @@ -59,12 +198,18 @@ export default function TeammateDisplay() { + - - {buildType !== 'tc' && } ) } + +function OptimizeSection() { + const { buildType } = useTeammateContext() + if (buildType === 'tc') return null + + return +} function CurrentBuildDisplay() { const teammateDatum = useTeammateContext() const { database } = useDatabaseContext() diff --git a/libs/sr/page-team/src/index.tsx b/libs/sr/page-team/src/index.tsx index 210b1504be..17cf822638 100644 --- a/libs/sr/page-team/src/index.tsx +++ b/libs/sr/page-team/src/index.tsx @@ -1,4 +1,4 @@ -import { CardThemed, useTitle } from '@genshin-optimizer/common/ui' +import { useRefSize, useTitle } from '@genshin-optimizer/common/ui' import { moveToFront, notEmpty, @@ -27,7 +27,7 @@ import { type Tag, } from '@genshin-optimizer/sr/formula' import { CharacterName } from '@genshin-optimizer/sr/ui' -import { Box, Divider, Skeleton } from '@mui/material' +import { Box, Skeleton } from '@mui/material' import { Suspense, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { @@ -38,14 +38,17 @@ import { useNavigate, useParams, } from 'react-router-dom' -import { ComboEditor } from './ComboEditor' import type { PresetContextObj } from './context' import { PresetContext, TeamContext } from './context' import { TeammateContext, useTeammateContext } from './context/TeammateContext' import { TeamCalcProvider } from './TeamCalcProvider' -import { TeamCharacterSelector } from './TeamCharacterSelector' +import { + DEFAULT_HEADER_HEIGHT_PX, + HEADER_TOP_PX, + TeamHeader, + TeamHeaderHeightContext, +} from './TeamHeader' import TeammateDisplay from './TeammateDisplay' -import TeamNameCardHeader from './TeamNameCardHeader' const fallback = @@ -183,6 +186,7 @@ function Page({ teamId }: { teamId: string }) { }), [characterKey, presetIndex] ) + const { height, ref } = useRefSize() return ( @@ -200,28 +204,22 @@ function Page({ teamId }: { teamId: string }) { mt: 2, }} > - + - - - - - - - {teammateDatum && ( - - - - )} + {teammateDatum && ( + + + + )} +