diff --git a/libs/gi/db-ui/src/hooks/index.ts b/libs/gi/db-ui/src/hooks/index.ts index 2f7466761c..9174a2e5f7 100644 --- a/libs/gi/db-ui/src/hooks/index.ts +++ b/libs/gi/db-ui/src/hooks/index.ts @@ -8,6 +8,7 @@ export * from './useDatabase' export * from './useDBMeta' export * from './useDisplayArtifact' export * from './useEquippedInTeam' +export * from './useGeneratedBuildList' export * from './useOptConfig' export * from './useTeam' export * from './useTeamChar' diff --git a/libs/gi/db-ui/src/hooks/useGeneratedBuildList.tsx b/libs/gi/db-ui/src/hooks/useGeneratedBuildList.tsx new file mode 100644 index 0000000000..f001855917 --- /dev/null +++ b/libs/gi/db-ui/src/hooks/useGeneratedBuildList.tsx @@ -0,0 +1,8 @@ +import { useDataManagerBase } from '@genshin-optimizer/common/database-ui' +import { useDatabase } from './useDatabase' + +export function useGeneratedBuildList(listId: string) { + const database = useDatabase() + + return useDataManagerBase(database.generatedBuildList, listId) +} diff --git a/libs/gi/db/src/Database/ArtCharDatabase.ts b/libs/gi/db/src/Database/ArtCharDatabase.ts index a4b5107a0d..04b9f1e0ac 100644 --- a/libs/gi/db/src/Database/ArtCharDatabase.ts +++ b/libs/gi/db/src/Database/ArtCharDatabase.ts @@ -14,6 +14,7 @@ import { BuildDataManager } from './DataManagers/BuildDataManager' import { BuildTcDataManager } from './DataManagers/BuildTcDataManager' import { CharMetaDataManager } from './DataManagers/CharMetaDataManager' import { CharacterDataManager } from './DataManagers/CharacterDataManager' +import { GeneratedBuildListDataManager } from './DataManagers/GeneratedBuildListDataManager' import { OptConfigDataManager } from './DataManagers/OptConfigDataManager' import { TeamCharacterDataManager } from './DataManagers/TeamCharacterDataManager' import { TeamDataManager } from './DataManagers/TeamDataManager' @@ -27,6 +28,7 @@ export class ArtCharDatabase extends Database { buildTcs: BuildTcDataManager weapons: WeaponDataManager optConfigs: OptConfigDataManager + generatedBuildList: GeneratedBuildListDataManager charMeta: CharMetaDataManager builds: BuildDataManager teamChars: TeamCharacterDataManager @@ -63,6 +65,8 @@ export class ArtCharDatabase extends Database { this.weapons.ensureEquipments() // Depends on arts + this.generatedBuildList = new GeneratedBuildListDataManager(this) + // Depends on arts and generatedBuildList this.optConfigs = new OptConfigDataManager(this) this.buildTcs = new BuildTcDataManager(this) @@ -112,6 +116,7 @@ export class ArtCharDatabase extends Database { this.chars, this.weapons, this.arts, + this.generatedBuildList, this.optConfigs, this.buildTcs, this.charMeta, diff --git a/libs/gi/db/src/Database/DataManagers/GeneratedBuildListDataManager.ts b/libs/gi/db/src/Database/DataManagers/GeneratedBuildListDataManager.ts new file mode 100644 index 0000000000..6358e9d441 --- /dev/null +++ b/libs/gi/db/src/Database/DataManagers/GeneratedBuildListDataManager.ts @@ -0,0 +1,72 @@ +import { objKeyMap } from '@genshin-optimizer/common/util' +import { + allArtifactSlotKeys, + type ArtifactSlotKey, +} from '@genshin-optimizer/gi/consts' +import type { ArtCharDatabase } from '../ArtCharDatabase' +import { DataManager } from '../DataManager' + +export interface GeneratedBuild { + weaponId?: string + artifactIds: Record +} + +export interface GeneratedBuildList { + builds: GeneratedBuild[] + buildDate: number +} + +export class GeneratedBuildListDataManager extends DataManager< + string, + 'generatedBuildList', + GeneratedBuildList, + GeneratedBuildList, + ArtCharDatabase +> { + constructor(database: ArtCharDatabase) { + super(database, 'generatedBuildList') + } + + override validate(obj: unknown): GeneratedBuildList | undefined { + if (typeof obj !== 'object' || obj === null) return undefined + let { builds, buildDate } = obj as GeneratedBuildList + + if (!Array.isArray(builds)) { + builds = [] + buildDate = 0 + } else { + builds = builds + .map((build) => { + if (typeof build !== 'object' || build === null) return undefined + const { artifactIds: artifactIdsRaw } = build as GeneratedBuild + if (typeof artifactIdsRaw !== 'object' || artifactIdsRaw === null) + return undefined + let { weaponId } = build as GeneratedBuild + if (weaponId && !this.database.weapons.get(weaponId)) + weaponId = undefined + + const artifactIds = objKeyMap(allArtifactSlotKeys, (slotKey) => + this.database.arts.get(artifactIdsRaw[slotKey])?.slotKey === slotKey + ? artifactIdsRaw[slotKey] + : undefined + ) + + return { artifactIds, weaponId } + }) + .filter((build) => build !== undefined) + + if (!Number.isInteger(buildDate)) buildDate = 0 + } + + return { + buildDate, + builds, + } + } + + new(data: GeneratedBuildList) { + const id = this.generateKey() + this.set(id, { ...data }) + return id + } +} diff --git a/libs/gi/db/src/Database/DataManagers/OptConfigDataManager.ts b/libs/gi/db/src/Database/DataManagers/OptConfigDataManager.ts index 2c49c6d4f2..d9b9e20672 100644 --- a/libs/gi/db/src/Database/DataManagers/OptConfigDataManager.ts +++ b/libs/gi/db/src/Database/DataManagers/OptConfigDataManager.ts @@ -2,23 +2,21 @@ import { clamp, deepClone, deepFreeze, - objKeyMap, validateArr, } from '@genshin-optimizer/common/util' import type { ArtifactSetKey, - ArtifactSlotKey, LocationCharacterKey, MainStatKey, } from '@genshin-optimizer/gi/consts' import { allArtifactSetKeys, - allArtifactSlotKeys, allLocationCharacterKeys, artSlotMainKeys, } from '@genshin-optimizer/gi/consts' import type { ArtCharDatabase } from '../ArtCharDatabase' import { DataManager } from '../DataManager' +import type { GeneratedBuildList } from './GeneratedBuildListDataManager' export const maxBuildsToShowList = [1, 2, 3, 4, 5, 8, 10] as const export const maxBuildsToShowDefault = 5 @@ -53,10 +51,6 @@ export interface StatFilterSetting { } export type StatFilters = Record -export type GeneratedBuild = { - weaponId?: string - artifactIds: Record -} export interface OptConfig { artSetExclusion: ArtSetExclusion statFilters: StatFilters @@ -81,8 +75,7 @@ export interface OptConfig { useTeammateBuild: boolean // teammates loadout exclusion flag //generated opt builds - builds: Array - buildDate: number + generatedBuildListId?: string // upOpt upOptLevelLow: number @@ -119,8 +112,7 @@ export class OptConfigDataManager extends DataManager< compareBuild, levelLow, levelHigh, - builds, - buildDate, + generatedBuildListId, useTeammateBuild, upOptLevelLow, upOptLevelHigh, @@ -200,27 +192,11 @@ export class OptConfigDataManager extends DataManager< .filter(([_, a]) => a.length) ) - if (!Array.isArray(builds)) { - builds = [] - buildDate = 0 - } else { - builds = builds - .map((build) => { - if (typeof build !== 'object') return undefined - const { weaponId, artifactIds: artifactIdsRaw } = - build as GeneratedBuild - if (!this.database.weapons.get(weaponId)) return undefined - const artifactIds = objKeyMap(allArtifactSlotKeys, (slotKey) => - this.database.arts.get(artifactIdsRaw[slotKey])?.slotKey === slotKey - ? artifactIdsRaw[slotKey] - : undefined - ) - - return { artifactIds, weaponId } - }) - .filter((b) => b) as GeneratedBuild[] - if (!Number.isInteger(buildDate)) buildDate = 0 - } + if ( + generatedBuildListId && + !this.database.generatedBuildList.get(generatedBuildListId) + ) + generatedBuildListId = undefined if (typeof useTeammateBuild !== 'boolean') useTeammateBuild = false return { @@ -238,8 +214,7 @@ export class OptConfigDataManager extends DataManager< compareBuild, levelLow, levelHigh, - builds, - buildDate, + generatedBuildListId, useTeammateBuild, upOptLevelLow, upOptLevelHigh, @@ -263,8 +238,7 @@ export class OptConfigDataManager extends DataManager< useExcludedArts, excludedLocations, artExclusion, - buildDate, - builds, + generatedBuildListId, ...rest } = optConfig if (!overrideOptTarget) return rest @@ -278,6 +252,26 @@ export class OptConfigDataManager extends DataManager< if (!this.set(id, data)) return '' return id } + newOrSetGeneratedBuildList(optConfigId: string, list: GeneratedBuildList) { + const optConfig = this.get(optConfigId) + + if (!optConfig) { + console.warn(`OptConfig not found for ID: ${optConfigId}`) + return false + } + + const listId = optConfig.generatedBuildListId + const generatedBuildList = + listId && this.database.generatedBuildList.get(listId) + + if (listId && generatedBuildList) { + return this.database.generatedBuildList.set(listId, list) + } else { + return this.database.optConfigs.set(optConfigId, { + generatedBuildListId: this.database.generatedBuildList.new(list), + }) + } + } } const initialBuildSettings: OptConfig = deepFreeze({ @@ -302,9 +296,7 @@ const initialBuildSettings: OptConfig = deepFreeze({ useTeammateBuild: false, upOptLevelLow: 0, upOptLevelHigh: 19, - - builds: [], - buildDate: 0, + generatedBuildListId: undefined, }) export function handleArtSetExclusion( diff --git a/libs/gi/db/src/Database/DataManagers/index.ts b/libs/gi/db/src/Database/DataManagers/index.ts index 2c7e4f9357..cdfb4d4316 100644 --- a/libs/gi/db/src/Database/DataManagers/index.ts +++ b/libs/gi/db/src/Database/DataManagers/index.ts @@ -7,10 +7,10 @@ import { initCustomTarget, validateCustomMultiTarget, } from './CustomMultiTarget' +import type { GeneratedBuild } from './GeneratedBuildListDataManager' import type { ArtSetExclusion, ArtSetExclusionKey, - GeneratedBuild, OptConfig, StatFilterSetting, StatFilters, diff --git a/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/Components/ChartCard/index.tsx b/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/Components/ChartCard/index.tsx index 6b6788c8f8..cd56f99c27 100644 --- a/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/Components/ChartCard/index.tsx +++ b/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/Components/ChartCard/index.tsx @@ -12,7 +12,11 @@ import { } from '@genshin-optimizer/common/util' import { allArtifactSlotKeys } from '@genshin-optimizer/gi/consts' import type { GeneratedBuild } from '@genshin-optimizer/gi/db' -import { TeamCharacterContext, useOptConfig } from '@genshin-optimizer/gi/db-ui' +import { + TeamCharacterContext, + useGeneratedBuildList, + useOptConfig, +} from '@genshin-optimizer/gi/db-ui' import { DataContext, GraphContext, @@ -83,9 +87,12 @@ export default function ChartCard({ const { teamChar: { optConfigId }, } = useContext(TeamCharacterContext) - const { builds: generatedBuilds } = useOptConfig(optConfigId) ?? { - builds: [] as GeneratedBuild[], + const { generatedBuildListId } = useOptConfig(optConfigId) ?? { + generatedBuildListId: undefined, } + const { builds: generatedBuilds } = useGeneratedBuildList( + generatedBuildListId ?? '' + ) ?? { builds: [] as GeneratedBuild[] } const [sliderLow, setSliderLow] = useState(-Infinity) const [sliderHigh, setSliderHigh] = useState(Infinity) diff --git a/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/index.tsx b/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/index.tsx index 23363433e6..ab1a8aa89a 100644 --- a/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/index.tsx +++ b/libs/gi/page-team/src/CharacterDisplay/Tabs/TabOptimize/index.tsx @@ -30,6 +30,7 @@ import { TeamCharacterContext, useDBMeta, useDatabase, + useGeneratedBuildList, useOptConfig, useTeammateArtifactIds, } from '@genshin-optimizer/gi/db-ui' @@ -170,10 +171,12 @@ export default function TabBuild() { maxBuildsToShow, levelLow, levelHigh, - builds: buildsDb, - buildDate, + generatedBuildListId, useTeammateBuild, } = buildSetting + const { builds: buildsDb, buildDate } = useGeneratedBuildList( + generatedBuildListId ?? '' + ) ?? { builds: [] as GeneratedBuild[] } const builds = useConstObj(buildsDb) const optimizationTarget = useConstObj(optimizationTargetDb) @@ -254,8 +257,8 @@ export default function TabBuild() { const { levelLow, levelHigh, excludedLocations, artExclusion } = deferredArtsDirty && deferredBuildSetting if (level >= levelLow && level <= levelHigh) { - ctMap.levelTotal.in.total++ - if (filteredArtIdMap[id]) ctMap.levelTotal.in.current++ + ctMap['levelTotal']['in'].total++ + if (filteredArtIdMap[id]) ctMap['levelTotal']['in'].current++ } const locKey = charKeyToLocCharKey(characterKey) if ( @@ -263,16 +266,17 @@ export default function TabBuild() { location !== locKey && !excludedLocations.includes(location) ) { - ctMap.allowListTotal.in.total++ - if (filteredArtIdMap[id]) ctMap.allowListTotal.in.current++ + ctMap['allowListTotal']['in'].total++ + if (filteredArtIdMap[id]) ctMap['allowListTotal']['in'].current++ } if (artExclusion.includes(id)) { - ctMap.excludedTotal.in.total++ - if (filteredArtIdMap[id]) ctMap.excludedTotal.in.current++ + ctMap['excludedTotal']['in'].total++ + if (filteredArtIdMap[id]) ctMap['excludedTotal']['in'].current++ } if (teammateArtifactIds.includes(id)) { - ctMap.teammateBuildTotal.in.total++ - if (filteredArtIdMap[id]) ctMap.teammateBuildTotal.in.current++ + ctMap['teammateBuildTotal']['in'].total++ + if (filteredArtIdMap[id]) + ctMap['teammateBuildTotal']['in'].current++ } }) ) @@ -437,7 +441,7 @@ export default function TabBuild() { if (process.env['NODE_ENV'] === 'development') console.log('Build Result', builds) - database.optConfigs.set(optConfigId, { + database.optConfigs.newOrSetGeneratedBuildList(optConfigId, { builds: builds.map((build) => ({ artifactIds: objKeyMap(allArtifactSlotKeys, (slotKey) => build.artifactIds.find( @@ -563,7 +567,7 @@ export default function TabBuild() { > {/* Level Filter */} {/*Minimum Final Stat Filter */} @@ -808,7 +812,7 @@ export default function TabBuild() { color="error" onClick={() => { setGraphBuilds(undefined) - database.optConfigs.set(optConfigId, { + database.optConfigs.newOrSetGeneratedBuildList(optConfigId, { builds: [], buildDate: 0, })