Skip to content

Commit

Permalink
feat: potential view with custom characters subset (#271)
Browse files Browse the repository at this point in the history
* feat: better loading spinner responsiveness

* refactor: shared card seleect components

* feat: add multiselect character select

* feat: applying custom characters to potential and saving state to storage

* fix: load save with default, expand heal column to 4 digits, fix tests

* feat: reorder cols, add better tooltips
  • Loading branch information
fribbels committed Apr 9, 2024
1 parent 67bd12d commit a1c6891
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 285 deletions.
79 changes: 55 additions & 24 deletions src/components/RelicFilterBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ export default function RelicFilterBar(props) {
})
}

let gradeData = generateGradeTags([2, 3, 4, 5])
let verifiedData = generateVerifiedTags([false, true])
let setsData = generateImageTags(Object.values(Constants.SetsRelics).concat(Object.values(Constants.SetsOrnaments)), (x) => Assets.getSetImage(x, Constants.Parts.PlanarSphere), true)
let partsData = generateImageTags(Object.values(Constants.Parts), (x) => Assets.getPart(x), false)
let mainStatsData = generateImageTags(Constants.MainStats, (x) => Assets.getStatIcon(x, true), true)
let subStatsData = generateImageTags(Constants.SubStats, (x) => Assets.getStatIcon(x, true), true)
let enhanceData = generateTextTags([[0, '+0'], [3, '+3'], [6, '+6'], [9, '+9'], [12, '+12'], [15, '+15']])
const gradeData = generateGradeTags([2, 3, 4, 5])
const verifiedData = generateVerifiedTags([false, true])
const setsData = generateImageTags(Object.values(Constants.SetsRelics).concat(Object.values(Constants.SetsOrnaments)), (x) => Assets.getSetImage(x, Constants.Parts.PlanarSphere), true)
const partsData = generateImageTags(Object.values(Constants.Parts), (x) => Assets.getPart(x), false)
const mainStatsData = generateImageTags(Constants.MainStats, (x) => Assets.getStatIcon(x, true), true)
const subStatsData = generateImageTags(Constants.SubStats, (x) => Assets.getStatIcon(x, true), true)
const enhanceData = generateTextTags([[0, '+0'], [3, '+3'], [6, '+6'], [9, '+9'], [12, '+12'], [15, '+15']])

window.refreshRelicsScore = () => {
// NOTE: the scoring modal (where this event is published) calls .submit() in the same block of code
Expand All @@ -130,36 +130,46 @@ export default function RelicFilterBar(props) {
}, [])

function characterSelectorChange(id) {
let relics = Object.values(DB.getRelicsById())
const relics = Object.values(DB.getRelicsById())
console.log('idChange', id)

setScoringAlgorithmFocusCharacter(id)
setCurrentlySelectedCharacterId(id)

let allCharacters = characterOptions.map((val) => val.id)
const allCharacters = characterOptions.map((val) => val.id)
const excludedCharacters = window.store.getState().excludedRelicPotentialCharacters

let relicScorer = new RelicScorer()
const relicScorer = new RelicScorer()

// NOTE: we cannot cache these results between renders by keying on the relic/char id because
// both relic stats and char weights can be edited
for (let relic of relics) {
for (const relic of relics) {
relic.weights = id ? relicScorer.scoreRelic(relic, id) : { current: 0, best: 0, average: 0 }
relic.weights.potentialSelected = id ? relicScorer.scoreRelicPct(relic, id) : { bestPct: 0, averagePct: 0 }
relic.weights.potentialAllAll = 0
relic.weights.potentialAllCustom = 0

for (let cid of allCharacters) {
let pct = relicScorer.scoreRelicPct(relic, cid).bestPct
for (const cid of allCharacters) {
const pct = relicScorer.scoreRelicPct(relic, cid).bestPct
relic.weights.potentialAllAll = Math.max(pct, relic.weights.potentialAllAll)

if (!excludedCharacters.includes(cid)) {
relic.weights.potentialAllCustom = Math.max(pct, relic.weights.potentialAllCustom)
}
}
}

DB.setRelics(relics)

if (id && window.relicsGrid?.current?.api) {
window.relicsGrid.current.api.applyColumnState({
state: [{ colId: 'weights.current', sort: 'desc' }],
defaultState: { sort: null },
})
const isSorted = window.relicsGrid.current.columnApi.getColumnState().filter((s) => s.sort !== null)

if (!isSorted) {
window.relicsGrid.current.api.applyColumnState({
state: [{ colId: 'weights.current', sort: 'desc' }],
defaultState: { sort: null },
})
}
}

DB.refreshRelics()
Expand Down Expand Up @@ -226,8 +236,9 @@ export default function RelicFilterBar(props) {
<HeaderText>Substats</HeaderText>
<FilterRow name="subStats" tags={subStatsData} />
</Flex>

<Flex gap={10}>
<Flex vertical flex={1}>
<Flex vertical flex={0.5}>
<HeaderText>Relic recommendation character</HeaderText>
<Flex gap={10}>
<CharacterSelect
Expand All @@ -252,8 +263,9 @@ export default function RelicFilterBar(props) {
</Button>
</Flex>
</Flex>
<Flex flex={1} gap={10}>
<Flex vertical flex={1}>

<Flex vertical flex={0.25} gap={10}>
<Flex vertical>
<Flex justify="space-between" align="center">
<HeaderText>Relic ratings</HeaderText>
<TooltipImage type={Hint.valueColumns()} />
Expand All @@ -271,6 +283,25 @@ export default function RelicFilterBar(props) {
</Flex>
</Flex>
</Flex>

<Flex vertical flex={0.25}>
<HeaderText>Custom potential characters</HeaderText>
<CharacterSelect
value={window.store.getState().excludedRelicPotentialCharacters}
selectStyle={{ flex: 1 }}
onChange={(x) => {
const excludedCharacterIds = (x || new Map()).entries()
.filter((entry) => entry[1] == true)
.map((entry) => entry[0])
.toArray()
window.store.getState().setExcludedRelicPotentialCharacters(excludedCharacterIds)
SaveState.save()
setTimeout(() => rescoreClicked(), 100)
console.debug(x, excludedCharacterIds)
}}
multipleSelect={true}
/>
</Flex>
</Flex>
</Flex>
)
Expand All @@ -282,17 +313,17 @@ RelicFilterBar.propTypes = {
}

function FilterRow(props) {
let relicTabFilters = window.store((s) => s.relicTabFilters)
let setRelicTabFilters = window.store((s) => s.setRelicTabFilters)
const relicTabFilters = window.store((s) => s.relicTabFilters)
const setRelicTabFilters = window.store((s) => s.setRelicTabFilters)

let selectedTags = relicTabFilters[props.name]
const selectedTags = relicTabFilters[props.name]

const handleChange = (tag, checked) => {
const nextSelectedTags = checked
? [...selectedTags, tag]
: selectedTags.filter((t) => t != tag)

let clonedFilters = Utils.clone(relicTabFilters)
const clonedFilters = Utils.clone(relicTabFilters)
clonedFilters[props.name] = nextSelectedTags
console.log('Relic tab filters', props.name, clonedFilters)

Expand Down
9 changes: 6 additions & 3 deletions src/components/RelicsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,12 @@ export default function RelicsTab() {
{ column: 'Selected Char\nScore', value: 'weights.current', label: 'Selected character: Score' },
{ column: 'Selected Char\nAvg Potential', value: 'weights.potentialSelected.averagePct', label: 'Selected character: Average potential', percent: true },
{ column: 'Selected Char\nMax Potential', value: 'weights.potentialSelected.bestPct', label: 'Selected character: Max potential', percent: true },
{ column: 'All Characters\nMax Potential', value: 'weights.potentialAllAll', label: 'All characters: Max potential', percent: true },
{ column: 'All Characters\nMax Potential + Sets', disabled: true, value: 'weights.potentialAllSets', label: 'All characters: Max potential + Sets (Coming soon)', percent: true },
{ column: 'Custom Chars\nMax Potential', value: 'weights.potentialAllCustom', label: 'Custom characters: Max potential', percent: true },
{ column: 'All Chars\nMax Potential', value: 'weights.potentialAllAll', label: 'All characters: Max potential', percent: true },
{ column: 'All Chars\nMax Potential + Sets', disabled: true, value: 'weights.potentialAllSets', label: 'All characters: Max potential + Sets (Coming soon)', percent: true },
], [])

const [valueColumns, setValueColumns] = useState(['weights.current', 'weights.potentialSelected.averagePct', 'weights.potentialSelected.bestPct', 'weights.potentialAllAll'])
const [valueColumns, setValueColumns] = useState(['weights.current', 'weights.potentialSelected.averagePct', 'weights.potentialSelected.bestPct', 'weights.potentialAllCustom'])

const columnDefs = useMemo(() => [
{ field: 'equippedBy', headerName: 'Owner', width: 45, cellRenderer: Renderer.characterIcon },
Expand Down Expand Up @@ -326,7 +327,9 @@ export default function RelicsTab() {
let scoreBuckets = null
if (selectedRelic) {
const chars = DB.getMetadata().characters
const excluded = window.store.getState().excludedRelicPotentialCharacters
const allScores = Object.keys(chars)
.filter((id) => !excluded.includes(id))
.map((id) => ({
cid: id,
name: chars[id].displayName,
Expand Down
11 changes: 6 additions & 5 deletions src/components/optimizerTab/OptimizerForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export default function OptimizerForm() {

// On first load, display the first character from the roster
useEffect(() => {
let characters = DB.getCharacters() || []
const characters = DB.getCharacters() || []
OptimizerTabController.updateCharacter(characters[0]?.id)
}, [])

const onValuesChange = (changedValues, allValues, bypass) => {
if (!changedValues || !allValues || !allValues.characterId) return
let keys = Object.keys(changedValues)
const keys = Object.keys(changedValues)

if (bypass) {
// Only allow certain values to refresh permutations.
Expand Down Expand Up @@ -99,18 +99,19 @@ export default function OptimizerForm() {
return
}

window.store.getState().setOptimizationInProgress(true)

DB.addFromForm(form)
SaveState.save()

let optimizationId = Utils.randomId()
const optimizationId = Utils.randomId()
window.store.getState().setOptimizationId(optimizationId)
form.optimizationId = optimizationId
form.statDisplay = window.store.getState().statDisplay

console.log('Form finished', form)

window.store.getState().setOptimizationInProgress(true)
Optimizer.optimize(form)
setTimeout(() => Optimizer.optimize(form), 50)
}
window.optimizerStartClicked = startClicked

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Flex, Typography } from 'antd'
import { Utils } from 'lib/utils.js'
import { Assets } from 'lib/assets.js'
import CheckableTag from 'antd/lib/tag/CheckableTag'
import { ElementToDamage, PathToClass } from 'lib/constants.ts'
import { ReactElement } from 'react'

const { Paragraph } = Typography

const parentW = 100
const parentH = 150

export const CardGridItemContent = (props: {
imgSrc: string
text: string
innerW: number
innerH: number
rows: number
}) => {
return (
<div>
<img
width={props.innerW}
src={props.imgSrc}
style={{
transform: `translate(${(props.innerW - parentW) / 2 / props.innerW * -100}%, ${(props.innerH - parentH) / 2 / props.innerH * -100}%)`,
}}
/>
<Paragraph
ellipsis={{ rows: props.rows }}
style={{
position: 'absolute',
bottom: 0,
left: 0,
width: '110%',
textAlign: 'center',
background: '#282B31',
color: '#D0D0D2',
marginLeft: '-5%',
paddingLeft: 10,
paddingRight: 10,
lineHeight: '16px',
height: 18 * props.rows,
alignItems: 'center',
marginBottom: 0,
fontSize: 13,
}}
>
<div
style={{
position: 'relative',
top: '50%',
transform: 'translateY(-50%)',
maxHeight: 18 * props.rows,
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{props.text}
</div>
</Paragraph>
</div>
)
}

export function generatePathTags() {
return Object.keys(PathToClass).map((x) => {
return {
key: x,
display: <img style={{ width: 32 }} src={Assets.getPath(x)} />,
}
})
}

export function generateRarityTags() {
return [5, 4, 3].map((x) => {
const stars: ReactElement[] = []
for (let i = 0; i < x; i++) {
stars.push(<img key={i} style={{ width: 16 }} src={Assets.getStar()} />)
}
return {
key: x,
display: (
<Flex flex={1} justify="center" align="center" style={{ marginTop: 1 }}>
{stars}
</Flex>
),
}
})
}

export function generateElementTags() {
return Object.keys(ElementToDamage).map((x) => {
return {
key: x,
display: <img style={{ width: 30 }} src={Assets.getElement(x)} />,
}
})
}

export function CardGridFilterRow({ currentFilters, name, flexBasis, tags, setCurrentFilters }) {
const selectedTags = currentFilters[name]

const handleChange = (tag, checked) => {
const nextSelectedTags = checked
? [...selectedTags, tag]
: selectedTags.filter((t) => t != tag)

const clonedFilters = Utils.clone(currentFilters)
clonedFilters[name] = nextSelectedTags
console.log('filters', name, clonedFilters)

setCurrentFilters(clonedFilters)
}

return (
<Flex
style={{
flexWrap: 'wrap',
flexGrow: 1,
backgroundColor: '#243356',
boxShadow: '0px 0px 0px 1px #3F5A96 inset',
borderRadius: 6,
overflow: 'hidden',
height: 40,
}}
>
{tags.map((tag) => (
<CheckableTag
key={tag.key}
checked={selectedTags.includes(tag.key)}
onChange={(checked) => handleChange(tag.key, checked)}
style={{
flex: 1,
flexBasis: flexBasis,
boxShadow: '1px 1px 0px 0px #3F5A96',
}}
>
<Flex align="center" justify="space-around" style={{ height: '100%' }}>
{tag.display}
</Flex>
</CheckableTag>
))}
</Flex>
)
}
Loading

0 comments on commit a1c6891

Please sign in to comment.