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

SRO preliminary buildTC and UI #2536

Merged
merged 11 commits into from
Nov 15, 2024
1 change: 1 addition & 0 deletions libs/common/ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './useConstObj'
export * from './useInfScroll'
export * from './useIsMount'
export * from './useOnScreen'
export * from './useRefOverflow'
export * from './useRefSize'
export * from './useTitle'
export * from './useWindowScrollPos'
30 changes: 30 additions & 0 deletions libs/common/ui/src/hooks/useRefOverflow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'
import { useEffect, useRef, useState } from 'react'

export function useRefOverflow() {
const ref = useRef<HTMLElement>()

const [isOverflowX, setIsOverflowX] = useState(false)
const [isOverflowY, setIsOverflowY] = useState(false)

useEffect(() => {
const handleResize = () => {
const ele = ref.current
setIsOverflowX(ele ? isOverflowedX(ele) : false)
setIsOverflowY(ele ? isOverflowedY(ele) : false)
}
handleResize() // Check on mount and whenever the window resizes
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, []) // Safe to keep empty as we only want mount/unmount behavior
return { isOverflowX, isOverflowY, ref }
}
function isOverflowedX(ref: HTMLElement) {
return ref.scrollWidth > ref.clientWidth
}

function isOverflowedY(ref: HTMLElement) {
return ref.scrollHeight > ref.clientHeight
}
15 changes: 8 additions & 7 deletions libs/common/ui/src/hooks/useRefSize.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
'use client'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'

/**
* NOTE: the values of `width` & `height` starts at 0, since ref takes a rendering cycle to attach.
* @returns
*/
export function useRefSize() {
const ref = useRef<HTMLElement>()
const [width, setWidth] = useState(0)
Expand All @@ -11,14 +15,11 @@ export function useRefSize() {
setWidth(ref.current?.clientWidth ?? 0)
setHeight(ref.current?.clientHeight ?? 0)
}
if (ref.current) window.addEventListener('resize', handleResize)
handleResize() // Check on mount and whenever the window resizes
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
useLayoutEffect(() => {
setWidth(ref.current?.clientWidth ?? 0)
setHeight(ref.current?.clientHeight ?? 0)
}, [])
}, []) // Safe to keep empty as we only want mount/unmount behavior
return { width, height, ref }
}
7 changes: 7 additions & 0 deletions libs/sr/consts/src/relic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ export type RelicMainStatKey = (typeof allRelicMainStatKeys)[number]
export const allRelicRarityKeys = [2, 3, 4, 5] as const
export type RelicRarityKey = (typeof allRelicRarityKeys)[number]

export function isRelicRarityKey(rarity: unknown): rarity is RelicRarityKey {
return (
typeof rarity === 'number' &&
allRelicRarityKeys.includes(rarity as RelicRarityKey)
)
}

export const allRelicSetCountKeys = [2, 4] as const
export type RelicSetCountKey = (typeof allRelicSetCountKeys)[number]

Expand Down
1 change: 1 addition & 0 deletions libs/sr/db-ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './useBuild'
export * from './useBuildTc'
export * from './useCharacter'
export * from './useLightCone'
export * from './useRelic'
Expand Down
6 changes: 6 additions & 0 deletions libs/sr/db-ui/src/hooks/useBuildTc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useDataManagerBase } from '@genshin-optimizer/common/database-ui'
import { useDatabaseContext } from '../context'
export function useBuildTc(buildTcId: string | undefined) {
const { database } = useDatabaseContext()
return useDataManagerBase(database.buildTcs, buildTcId ?? '')
}
2 changes: 1 addition & 1 deletion libs/sr/db/src/Database/DataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class DataManager<
// Delete it from storage
for (const key of this.database.storage.keys)
if (
key.startsWith(this.goKeySingle) &&
key.startsWith(`${this.goKeySingle}_`) &&
!this.set(this.toCacheKey(key), {})
) {
this.database.storage.remove(key)
Expand Down
5 changes: 0 additions & 5 deletions libs/sr/db/src/Database/DataManagers/BuildDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,6 @@ export class BuildDataManager extends DataManager<
if (!build) return ''
return this.new(structuredClone(build))
}
override remove(key: string, notify?: boolean): Build | undefined {
const build = super.remove(key, notify)
// TODO: remove builds from teams
return build
}
getBuildIds(characterKey: CharacterKey) {
return this.keys.filter(
(key) => this.get(key)?.characterKey === characterKey
Expand Down
54 changes: 36 additions & 18 deletions libs/sr/db/src/Database/DataManagers/BuildTcDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { clamp, objKeyMap, objMap } from '@genshin-optimizer/common/util'
import type {
CharacterKey,
RelicMainStatKey,
RelicRarityKey,
RelicSetKey,
RelicSlotKey,
} from '@genshin-optimizer/sr/consts'
Expand All @@ -10,11 +11,16 @@ import {
allLightConeKeys,
allRelicSlotKeys,
allRelicSubStatKeys,
isRelicRarityKey,
relicMaxLevel,
relicSubstatTypeKeys,
} from '@genshin-optimizer/sr/consts'
import { validateLevelAsc } from '@genshin-optimizer/sr/util'
import type { ICachedLightCone, ICachedRelic } from '../../Interfaces'
import type {
BuildTcRelicSlot,
ICachedLightCone,
ICachedRelic,
} from '../../Interfaces'
import { type IBuildTc } from '../../Interfaces/IBuildTc'
import { DataManager } from '../DataManager'
import type { SroDatabase } from '../Database'
Expand All @@ -30,13 +36,14 @@ export class BuildTcDataManager extends DataManager<
}
override validate(obj: unknown): IBuildTc | undefined {
if (!obj || typeof obj !== 'object') return undefined
const { characterKey } = obj as IBuildTc
const { characterKey, teamId } = obj as IBuildTc
if (!allCharacterKeys.includes(characterKey)) return undefined

let { name, teamId, description } = obj as IBuildTc
let { name, description } = obj as IBuildTc
const { lightCone, relic, optimization } = obj as IBuildTc

if (teamId && !this.database.teams.get(teamId)) teamId = undefined
// Cannot validate teamId, since on db init database.teams do not exist yet.
// if (teamId && !this.database.teams.get(teamId)) teamId = undefined

if (typeof name !== 'string') name = 'Build(TC) Name'
if (typeof description !== 'string') description = 'Build(TC) Description'
Expand Down Expand Up @@ -66,12 +73,6 @@ export class BuildTcDataManager extends DataManager<
if (!buildTc) return ''
return this.new(structuredClone(buildTc))
}
override remove(key: string, notify?: boolean): IBuildTc | undefined {
const buildTc = super.remove(key, notify)
// TODO: remove tcbuild from teams?

return buildTc
}
export(buildTcId: string): object | undefined {
const buildTc = this.database.buildTcs.get(buildTcId)
if (!buildTc) return undefined
Expand All @@ -82,6 +83,11 @@ export class BuildTcDataManager extends DataManager<
if (!this.set(id, data)) return ''
return id
}
getBuildTcIds(characterKey: CharacterKey) {
return this.keys.filter(
(key) => this.get(key)?.characterKey === characterKey
)
}
}

export function initCharTC(characterKey: CharacterKey): IBuildTc {
Expand All @@ -94,6 +100,7 @@ export function initCharTC(characterKey: CharacterKey): IBuildTc {
substats: {
type: 'max',
stats: objKeyMap(allRelicSubStatKeys, () => 0),
rarity: 5,
},
sets: {},
},
Expand All @@ -103,14 +110,15 @@ export function initCharTC(characterKey: CharacterKey): IBuildTc {
},
}
}
function initCharTCRelicSlots() {
function initCharTCRelicSlots(): Record<RelicSlotKey, BuildTcRelicSlot> {
return objKeyMap(allRelicSlotKeys, (s) => ({
level: 20,
statKey: (s === 'head'
? 'hp'
: s === 'hands'
? 'atk'
: 'atk_') as RelicMainStatKey,
rarity: 5,
}))
}

Expand All @@ -137,7 +145,7 @@ function validateCharTCRelic(relic: unknown): IBuildTc['relic'] | undefined {
if (typeof relic !== 'object') return undefined
let {
slots,
substats: { type, stats },
substats: { type, stats, rarity },
sets,
} = relic as IBuildTc['relic']
const _slots = validateCharTCRelicSlots(slots)
Expand All @@ -148,11 +156,16 @@ function validateCharTCRelic(relic: unknown): IBuildTc['relic'] | undefined {
stats = objKeyMap(allRelicSubStatKeys, (k) =>
typeof stats[k] === 'number' ? stats[k] : 0
)
rarity = validateRelicRarity(rarity)

if (typeof sets !== 'object') sets = {}
// TODO: validate sets

return { slots, substats: { type, stats }, sets }
return { slots, substats: { type, stats, rarity }, sets }
}

function validateRelicRarity(rarity: unknown): RelicRarityKey {
return isRelicRarityKey(rarity) ? rarity : 5
}
function validateCharTCRelicSlots(
slots: unknown
Expand All @@ -167,12 +180,17 @@ function validateCharTCRelicSlots(
)
)
return initCharTCRelicSlots()
return objMap(slots as IBuildTc['relic']['slots'], ({ level, ...rest }) => {
return {
level: clamp(level, 0, relicMaxLevel[5]),
...rest,
return objMap(
slots as IBuildTc['relic']['slots'],
({ level, rarity, ...rest }) => {
rarity = validateRelicRarity(rarity)
return {
level: clamp(level, 0, relicMaxLevel[rarity]),
rarity,
...rest,
}
}
})
)
}
function validateCharTcOptimization(
optimization: unknown
Expand Down
15 changes: 6 additions & 9 deletions libs/sr/db/src/Interfaces/IBuildTc.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
import type {
AscensionKey,
CharacterKey,
LightConeKey,
RelicMainStatKey,
RelicRarityKey,
RelicSetKey,
RelicSlotKey,
RelicSubStatKey,
RelicSubstatTypeKey,
SuperimposeKey,
} from '@genshin-optimizer/sr/consts'
import type { ILightCone } from '@genshin-optimizer/sr/srod'

export type BuildTcRelicSlot = {
level: number
statKey: RelicMainStatKey
rarity: RelicRarityKey
}
export type BuildTCLightCone = Omit<ILightCone, 'location' | 'lock'>
export interface IBuildTc {
name: string
description: string
characterKey: CharacterKey
teamId?: string
lightCone?: {
key: LightConeKey
level: number
ascension: AscensionKey
superimpose: SuperimposeKey
}
lightCone?: BuildTCLightCone
relic: {
slots: Record<RelicSlotKey, BuildTcRelicSlot>
substats: {
type: RelicSubstatTypeKey
stats: Record<RelicSubStatKey, number>
rarity: RelicRarityKey
}
sets: Partial<Record<RelicSetKey, 2 | 4>>
}
Expand Down
1 change: 1 addition & 0 deletions libs/sr/db/src/Interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './IBuildTc'
export * from './ISroCharacter'
export * from './ISroDatabase'
export * from './ISroLightCone'
Expand Down
2 changes: 1 addition & 1 deletion libs/sr/formula/src/data/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const data: TagMapNodeEntries = [
reader.withTag({ sheet: 'agg', et: 'own' }).reread(reader.sheet('custom')),

// convert sheet:<char/lightCone> to sheet:agg for accumulation
// sheet:<relic> is reread in src/util.ts:relicsData()
// sheet:<relic> is reread in src/util.ts:relicTagMapNodeEntries()
reader.sheet('agg').reread(reader.sheet('char')),

// add all light cones by default
Expand Down
30 changes: 10 additions & 20 deletions libs/sr/formula/src/formula.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
type LightConeKey,
} from '@genshin-optimizer/sr/consts'
import { fail } from 'assert'
import { charData, lightConeData, withMember } from '.'
import {
charTagMapNodeEntries,
lightConeTagMapNodeEntries,
withMember,
} from '.'
import { Calculator } from './calculator'
import { data, keys, values } from './data'
import {
Expand All @@ -37,7 +41,7 @@ describe('character test', () => {
const data: TagMapNodeEntries = [
...withMember(
'March7th',
...charData({
...charTagMapNodeEntries({
level: lvl,
ascension: ascension as AscensionKey,
key: charKey,
Expand Down Expand Up @@ -71,7 +75,7 @@ describe('lightCone test', () => {
const data: TagMapNodeEntries = [
...withMember(
'March7th',
...charData({
...charTagMapNodeEntries({
level: 1,
ascension: 0,
key: 'March7th',
Expand All @@ -83,14 +87,7 @@ describe('lightCone test', () => {
bonusAbilities: {},
statBoosts: {},
}),
...lightConeData({
key: lcKey,
level: lvl,
ascension: ascension as AscensionKey,
superimpose: 1,
lock: false,
location: 'March7th',
})
...lightConeTagMapNodeEntries(lcKey, lvl, ascension as AscensionKey, 1)
),
]
const calc = new Calculator(keys, values, compileTagMapValues(keys, data))
Expand All @@ -114,7 +111,7 @@ describe('char+lightCone test', () => {
const data: TagMapNodeEntries = [
...withMember(
'March7th',
...charData({
...charTagMapNodeEntries({
level: 1,
ascension: 0,
key: charKey,
Expand All @@ -126,14 +123,7 @@ describe('char+lightCone test', () => {
bonusAbilities: {},
statBoosts: {},
}),
...lightConeData({
key: lcKey,
level: 1,
ascension: 0,
superimpose: 1,
lock: false,
location: 'March7th',
})
...lightConeTagMapNodeEntries(lcKey, 1, 0, 1)
),
]
const calc = new Calculator(keys, values, compileTagMapValues(keys, data))
Expand Down
Loading
Loading