UPDATE: New cdn, assets
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m21s

This commit is contained in:
2026-02-17 22:26:15 +07:00
parent cf5eaaa3d4
commit 8fbb27b5c1
69 changed files with 487057 additions and 1131 deletions

View File

@@ -93,7 +93,7 @@ export default function ActionBar() {
};
const actionMove = (path: string) => {
const actionMove = (path: string) => {
router.push(`/${path}`)
}
@@ -134,7 +134,7 @@ export default function ActionBar() {
}
}
}
const modalConfigs: ModalConfig[] = [
{
id: "update_profile_modal",
@@ -193,16 +193,16 @@ export default function ActionBar() {
// Handle ESC key to close modal
useEffect(() => {
for (const item of modalConfigs) {
if (!item?.isOpen) {
handleCloseModal(item?.id || "")
}
if (!item?.isOpen) {
handleCloseModal(item?.id || "")
}
}
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
for (const item of modalConfigs) {
handleCloseModal(item?.id || "")
}
if (event.key === 'Escape') {
for (const item of modalConfigs) {
handleCloseModal(item?.id || "")
}
}
};
window.addEventListener('keydown', handleEscKey);
@@ -223,6 +223,8 @@ export default function ActionBar() {
<Image
src={`/icon/${avatarSelected.damageType.toLowerCase()}.webp`}
alt={'fire'}
unoptimized
crossOrigin="anonymous"
className="h-10 w-10 object-contain"
width={100}
height={100}

View File

@@ -8,13 +8,13 @@ import { useTranslations } from "next-intl"
export default function AvatarBar({ onClose }: { onClose?: () => void }) {
const {
listAvatar,
setAvatarSelected,
setSkillSelected,
setFilter,
filter,
listElement,
const {
listAvatar,
setAvatarSelected,
setSkillSelected,
setFilter,
filter,
listElement,
listPath,
setListElement,
setListPath
@@ -22,10 +22,10 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore()
useEffect(() => {
setFilter({ ...filter, locale: locale, element: Object.keys(listElement).filter((key) => listElement[key]), path: Object.keys(listPath).filter((key) => listPath[key]) })
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [locale, listElement, listPath])
@@ -54,8 +54,11 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
style={{
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
}}>
<Image src={ `/icon/${key}.webp`}
<Image
src={`/icon/${key}.webp`}
alt={key}
unoptimized
crossOrigin="anonymous"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
width={200}
height={200} />
@@ -76,7 +79,10 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
}}
>
<Image src={`/icon/${key}.webp`}
<Image
src={`/icon/${key}.webp`}
unoptimized
crossOrigin="anonymous"
alt={key}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
width={200}
@@ -91,7 +97,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
<ul className="grid grid-cols-3 sm:grid-cols-2 lg:grid-cols-3 gap-2 w-full h-[65vh] overflow-y-scroll overflow-x-hidden">
{listAvatar.map((item, index) => (
<div key={index} onClick={() => {
setAvatarSelected(item);
setAvatarSelected(item);
setSkillSelected(null)
if (onClose) onClose()
}}>

View File

@@ -423,10 +423,12 @@ export default function AvatarInfo() {
<div className="lg:col-span-1">
<div className="">
<Image
unoptimized
crossOrigin="anonymous"
width={904}
height={1260}
priority
src={`https://api.hakush.in/hsr/UI/lightconemaxfigures/${lightconeDetail.id}.webp`}
src={`${process.env.CDN_URL}/${lightconeDetail.image}`}
className="w-full h-full rounded-lg object-cover shadow-lg"
alt="Lightcone"
/>

View File

@@ -29,19 +29,22 @@ export default function CharacterCard({ data }: CharacterCardProps) {
}`}
>
<div className="relative w-full h-full">
<div className="relative w-full h-32 lg:h-26 xl:h-36">
<Image
width={376}
height={512}
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${data.icon}`}
priority={true}
className="w-full h-full rounded-md object-cover"
className="rounded-md w-full h-full object-contain"
alt="ALT"
/>
<Image
width={32}
height={32}
unoptimized
crossOrigin="anonymous"
src={`/icon/${data.damageType.toLowerCase()}.webp`}
className="absolute top-0 left-0 w-6 h-6 rounded-full"
alt={data.damageType.toLowerCase()}
@@ -49,6 +52,8 @@ export default function CharacterCard({ data }: CharacterCardProps) {
<Image
width={32}
height={32}
unoptimized
crossOrigin="anonymous"
src={`/icon/${data.baseType.toLowerCase()}.webp`}
className="absolute top-0 right-0 w-6 h-6 rounded-full"
alt={data.baseType.toLowerCase()}

View File

@@ -29,15 +29,19 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div className="relative mb-4">
<div className="w-full h-48 rounded-lg overflow-hidden relative">
<Image
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${character.avatar_id}.webp`}
src={`${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${character.avatar_id}.png`}
alt={mapAvatarInfo[character.avatar_id.toString()]?.Name || ""}
width={376}
height={512}
unoptimized
crossOrigin="anonymous"
className="w-full h-full object-contain"
/>
<Image
width={48}
height={48}
unoptimized
crossOrigin="anonymous"
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}.webp`}
className="absolute top-0 left-0 w-10 h-10 rounded-full"
alt={mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}
@@ -45,6 +49,8 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<Image
width={48}
height={48}
unoptimized
crossOrigin="anonymous"
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}.webp`}
className="absolute top-0 right-0 w-10 h-10 rounded-full"
alt={mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}
@@ -71,8 +77,10 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div key={index} className="relative">
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
<Image
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`}
alt="Relic"
unoptimized
crossOrigin="anonymous"
width={124}
height={124}
className="w-14 h-14 object-contain"
@@ -91,7 +99,9 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div className="">
<div className="rounded-lg h-42 flex items-center justify-center">
<Image
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${character.lightcone.item_id}.webp`}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${character.lightcone.item_id}.png`}
alt={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
width={348}
height={408}

View File

@@ -30,7 +30,8 @@ export default function LightconeCard({ data }: LightconeCardProps) {
<div className="relative w-full h-full">
<Image
loading="lazy"
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${data.id}.webp`}
src={`${process.env.CDN_URL}/${data.thumbnail}`}
unoptimized={true}
width={348}
height={408}
className="w-full h-full rounded-md object-cover"

View File

@@ -27,7 +27,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
<div className="">
<div className="rounded-lg h-42 flex items-center justify-center">
<Image
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${profile.lightcone.item_id}.webp`}
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${profile.lightcone.item_id}.png`}
alt={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
width={348}
height={408}
@@ -55,7 +55,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
<div key={index} className="relative">
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
<Image
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
alt="Relic"
width={124}
height={124}

View File

@@ -128,8 +128,9 @@ export default function RelicCard({ slot, avatarId }: RelicCardProps) {
>
<span>
<Image
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relicDetail.relic_set_id}_${slot}.webp`}
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relicDetail.relic_set_id}_${slot}.png`}
alt="Relic"
unoptimized={true}
width={124}
height={124}
className="w-14 h-14 object-contain"

View File

@@ -47,7 +47,7 @@ export default function EidolonsInfo() {
>
<Image
className={`w-60 object-contain mb-2 ${Number(key) <= avatars[avatarSelected?.id || ""]?.data?.rank ? "" : "grayscale"}`}
src={`https://api.hakush.in/hsr/UI/rank/_dependencies/textures/${avatarSelected?.id}/${avatarSelected?.id}_Rank_${key}.webp`}
src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.id}/${avatarSelected?.id}_Rank_${key}.png`}
alt={`Rank ${key}`}
priority
width={240}

View File

@@ -344,7 +344,15 @@ export default function Header() {
<a className="hidden sm:grid sm:grid-cols-1 items-start justify-items-center text-left gap-0 hover:scale-105 px-2">
<div className="flex items-center justify-center gap-2">
<Image src="/ff-srtool.png" alt="Logo" width={250} height={250} className="w-10 h-10 xl:w-12 xl:h-12 object-contain" />
<Image
unoptimized
crossOrigin="anonymous"
src="/ff-srtool.png"
alt="Logo"
width={250}
height={250}
className="w-10 h-10 xl:w-12 xl:h-12 object-contain"
/>
<div className="flex flex-col justify-center items-start">
<h1 className="text-lg xl:text-xl font-bold leading-tight">
<span className="text-emerald-500">Firefly Sr</span>

View File

@@ -154,6 +154,8 @@ export default function CopyImport() {
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
}}>
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${key}.webp`}
alt={key}
className="h-8 w-8 object-contain rounded-md"
@@ -179,6 +181,8 @@ export default function CopyImport() {
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
}}>
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${key}.webp`}
alt={key}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
@@ -220,7 +224,7 @@ export default function CopyImport() {
customSet={listCopyAvatar.map((avatar) => ({
value: avatar.id.toString(),
label: getNameChar(locale, transI18n, avatar),
imageUrl: `https://api.hakush.in/hsr/UI/avatarshopicon/${avatar.id}.webp`
imageUrl: `${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${avatar.id}.png`
}))}
excludeSet={[]}
selectedCustomSet={avatarCopySelected?.id.toString() || ""}

View File

@@ -12,15 +12,15 @@ import { useTranslations } from "next-intl";
export default function LightconeBar() {
const { locale } = useLocaleStore()
const {
listLightcone,
filter,
setFilter,
defaultFilter,
listPath,
listRank,
setListPath,
setListRank
const {
listLightcone,
filter,
setFilter,
defaultFilter,
listPath,
listRank,
setListPath,
setListRank
} = useLightconeStore()
const { setAvatar, avatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore()
@@ -86,6 +86,8 @@ export default function LightconeBar() {
}}
>
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${key}.webp`}
alt={key}
className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md"

View File

@@ -10,11 +10,10 @@ import Image from "next/image";
import { MonsterStore } from "@/types";
import useMazeStore from "@/stores/mazeStore";
import { useTranslations } from "next-intl";
import { listCurrentLanguageApi } from "@/constant/constant";
export default function AsBar() {
const { ASEvent, mapASInfo } = useEventStore()
const { listMonster } = useMonsterStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore()
const {
as_config,
@@ -151,7 +150,7 @@ export default function AsBar() {
<div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full">
<SelectCustomText
customSet={ASEvent.filter(as => as.lang.get(listCurrentLanguageApi[locale])).map((as) => ({
customSet={ASEvent.map((as) => ({
id: as.id,
name: getLocaleName(locale, as),
time: `${as.begin} - ${as.end}`,
@@ -259,8 +258,10 @@ export default function AsBar() {
>
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[waveValue.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -271,18 +272,18 @@ export default function AsBar() {
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(waveValue))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>
@@ -309,8 +310,10 @@ export default function AsBar() {
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[waveValue.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -321,18 +324,18 @@ export default function AsBar() {
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(waveValue))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[waveValue.toString()].weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>

View File

@@ -17,7 +17,6 @@ import { getLocaleName } from "@/helper";
import Image from "next/image";
import { MonsterBasic } from "@/types";
import { useTranslations } from "next-intl";
import { listCurrentLanguageApi } from "@/constant/constant";
import useGlobalStore from "@/stores/globalStore";
@@ -26,7 +25,7 @@ export default function CeBar() {
const [showSearchWaveId, setShowSearchWaveId] = useState<number | null>(null);
const { Stage } = useMazeStore()
const { ce_config, setCeConfig } = useUserDataStore()
const { mapMonsterInfo } = useMonsterStore()
const { listMonster, mapMonster } = useMonsterStore()
const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage")
const [showSearchStage, setShowSearchStage] = useState(false)
@@ -39,33 +38,9 @@ export default function CeBar() {
const pageSizeMonsters = 30
const [monsterPage, setMonsterPage] = useState(1)
const listMonsterDetail = useMemo(() => {
const result: MonsterBasic[] = []
for (const monster of Object.values(mapMonsterInfo)) {
for (const monsterChild of monster.Child) {
result.push({
id: monsterChild.Id.toString(),
rank: monster.Rank,
camp: null,
icon: monster.ImagePath,
weak: monsterChild.StanceWeakList,
desc: monster.Desc,
child: [],
lang: new Map<string, string>([
[listCurrentLanguageApi[locale], monster.Name],
]),
})
}
}
return result
}, [mapMonsterInfo, locale])
const filteredMonsters = useMemo(() => {
const newlistMonster = new Set<MonsterBasic>()
for (const monster of listMonsterDetail) {
for (const monster of listMonster) {
if (getLocaleName(locale, monster).toLowerCase().includes(searchTerm.toLowerCase())) {
newlistMonster.add(monster)
}
@@ -74,7 +49,7 @@ export default function CeBar() {
}
}
return Array.from(newlistMonster)
}, [listMonsterDetail, locale, searchTerm]);
}, [listMonster, locale, searchTerm]);
const paginatedMonsters = useMemo(() =>
filteredMonsters.slice((monsterPage - 1) * pageSizeMonsters, monsterPage * pageSizeMonsters),
@@ -114,12 +89,12 @@ export default function CeBar() {
useEffect(() => {
if (!ce_config) return
if (!extraData || !extraData.theory_craft?.mode) return
const newExtraData = structuredClone(extraData)
if (!newExtraData?.theory_craft?.hp) {
newExtraData.theory_craft!.hp = {}
}
for (let i = 0; i < ce_config.monsters.length; i++) {
const waveKey = (i + 1).toString()
if (!newExtraData.theory_craft!.hp[waveKey]) {
@@ -132,9 +107,9 @@ export default function CeBar() {
}
}
setExtraData(newExtraData)
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ce_config])
return (
<div className="z-4 py-8 h-full w-full" onClick={() => {
@@ -337,8 +312,10 @@ export default function CeBar() {
</button>
<div className="flex justify-center">
{listMonsterDetail.find((monster2) => monster2.id === member.monster_id.toString())?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonsterDetail.find((monster2) => monster2.id === member.monster_id.toString())?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[member.monster_id.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[member.monster_id.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -347,22 +324,22 @@ export default function CeBar() {
</div>
<div className="flex flex-wrap justify-center gap-1 mb-2">
{listMonsterDetail
.find((monster) => monster.id === member.monster_id.toString())
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[member.monster_id.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
<div className="text-center flex flex-col items-center justify-center">
<div className="text-sm font-medium">
{getLocaleName(locale, listMonsterDetail.find((monster) => monster.id === member.monster_id.toString()))} {`(${member.monster_id})`}
{getLocaleName(locale, mapMonster?.[member.monster_id.toString()])} {`(${member.monster_id})`}
</div>
<div className="flex items-center gap-1 mt-1 mx-2">
<span className="text-sm">LV.</span>
@@ -393,15 +370,15 @@ export default function CeBar() {
onChange={(e) => {
const val = Number(e.target.value)
if (isNaN(val) || val < 0) return
const newData = structuredClone(extraData)
if (!newData?.theory_craft?.hp?.[(waveIndex + 1).toString()]) {
newData.theory_craft!.hp![(waveIndex + 1).toString()] = []
newData.theory_craft!.hp![(waveIndex + 1).toString()] = []
}
newData.theory_craft!.hp![(waveIndex + 1).toString()][memberIndex] = val
setExtraData(newData)
}}
/>
@@ -415,8 +392,8 @@ export default function CeBar() {
))}
{/* Add Member Button + Search */}
<div
className="relative flex items-start justify-center w-full h-full"
<div
className="relative flex items-start justify-center w-full h-full"
onClick={(e) => e.stopPropagation()}
>
<button
@@ -481,9 +458,11 @@ export default function CeBar() {
}}
>
<div className="relative w-8 h-8 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonsterDetail.find((monster2) => monster2.id === monster.id)?.icon?.split("/")?.pop()?.replace(".png", "") && (
{mapMonster?.[monster.id.toString()]?.icon && (
<Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonsterDetail.find((monster2) => monster2.id === monster.id)?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monster.id.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}

View File

@@ -64,6 +64,8 @@ export default function MonsterBar() {
}
>
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${item.icon}.webp`}
alt={item.name}
width={24}

View File

@@ -14,7 +14,7 @@ import { MonsterStore } from "@/types";
export default function MocBar() {
const { MOCEvent, mapMOCInfo } = useEventStore()
const { listMonster } = useMonsterStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore()
const {
moc_config,
@@ -239,8 +239,10 @@ export default function MocBar() {
>
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[waveValue.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -251,18 +253,18 @@ export default function MocBar() {
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(waveValue))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>
@@ -289,8 +291,10 @@ export default function MocBar() {
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[waveValue.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -301,18 +305,18 @@ export default function MocBar() {
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(waveValue))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>

View File

@@ -12,7 +12,7 @@ import { MonsterStore } from "@/types";
export default function PeakBar() {
const { PEAKEvent, mapPEAKInfo } = useEventStore()
const { listMonster } = useMonsterStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore()
const {
peak_config,
@@ -216,7 +216,7 @@ export default function PeakBar() {
{/* Enemy Waves */}
{(peak_config?.challenge_id ?? 0) !== 0 && (
{(peak_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 gap-4">
<div className="rounded-xl p-4 mt-2 border border-warning">
@@ -234,8 +234,10 @@ export default function PeakBar() {
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[monsterId.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -246,18 +248,18 @@ export default function PeakBar() {
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(monsterId))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>

View File

@@ -13,7 +13,7 @@ import { useTranslations } from "next-intl";
export default function PfBar() {
const { PFEvent, mapPFInfo } = useEventStore()
const { listMonster } = useMonsterStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore()
const {
pf_config,
@@ -248,8 +248,10 @@ export default function PfBar() {
>
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[monsterId.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
@@ -258,20 +260,20 @@ export default function PfBar() {
</div>
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="text-sm font-semibold">{mapMonster?.[monsterId.toString()]?.id} | Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(monsterId))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>
@@ -298,11 +300,13 @@ export default function PfBar() {
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
{mapMonster?.[monsterId.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
width={400}
height={300}
className="w-full h-full object-cover"
/>}
</div>
@@ -310,18 +314,18 @@ export default function PfBar() {
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{listMonster
.find((monster) => monster.child.includes(monsterId))
?.weak?.map((icon, iconIndex) => (
<Image
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
/>
))}
</div>
</div>
</div>

View File

@@ -70,7 +70,7 @@ export default function QuickView() {
const subAffixMap = mapSubAffix["5"]
if (!mainAffixMap || !subAffixMap) return
return {
img: `https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${value.relic_set_id}_${key}.webp`,
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
mainAffix: {
property: mainAffixMap?.[value?.main_affix_id]?.property,
level: value?.level,
@@ -408,7 +408,15 @@ export default function QuickView() {
return (
<div key={index} className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<NextImage src={stat?.icon || ""} alt="Stat Icon" width={40} height={40} className="h-auto w-10 p-1 mx-1 bg-black/20 rounded-full" />
<NextImage
src={stat?.icon || ""}
unoptimized
crossOrigin="anonymous"
alt="Stat Icon"
width={40}
height={40}
className="h-auto w-10 p-1 mx-1 bg-black/20 rounded-full"
/>
<div className="font-bold">{stat.name}</div>
</div>
<div className="ml-3 mr-3 grow border rounded opacity-50" />

View File

@@ -276,7 +276,7 @@ export default function RelicMaker() {
customSet={Object.entries(relicSets).map(([key, value]) => ({
value: key,
label: value.Name,
imageUrl: `https://api.hakush.in/hsr/UI/itemfigures/${value.Icon.match(/\d+/)?.[0]}.webp`
imageUrl: `${process.env.CDN_URL}/spriteoutput/itemfigures/${value.Icon.match(/\d+/)?.[0]}.png`
}))}
excludeSet={[]}
selectedCustomSet={selectedRelicSet}

View File

@@ -78,7 +78,15 @@ export default function SelectCustomImage({ customSet, excludeSet, selectedCusto
const formatOptionLabel = (option: SelectOption) => (
<div className="flex items-center gap-1 w-full h-full">
<Image src={option.imageUrl} alt="" width={125} height={125} className="w-8 h-8 object-contain bg-warning-content rounded-full" />
<Image
unoptimized
crossOrigin="anonymous"
src={option.imageUrl}
alt=""
width={125}
height={125}
className="w-8 h-8 object-contain bg-warning-content rounded-full"
/>
<ParseText className='font-bold' text={option.label} locale={locale} />
</div>
)

View File

@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
import useAvatarStore from "@/stores/avatarStore";
import { FastAverageColor, FastAverageColorResult } from 'fast-average-color';
import { FastAverageColor } from 'fast-average-color';
import NextImage from 'next/image';
import ParseText from '../parseText';
import useLocaleStore from '@/stores/localeStore';
@@ -41,10 +41,8 @@ export default function ShowCaseInfo() {
html2canvas(cardRef.current!, {
scale: 2,
backgroundColor: "#000000",
logging: true,
useCORS: true,
allowTaint: false,
imageTimeout: 30000,
useCORS: true
})
)
.then((canvas: HTMLCanvasElement) => {
@@ -63,13 +61,20 @@ export default function ShowCaseInfo() {
if (!avatarSelected?.id) return;
const fac = new FastAverageColor();
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = `https://api.hakush.in/hsr/UI/avatardrawcard/${avatarSelected.id}.webp`;
img.src = `${process.env.CDN_URL}/spriteoutput/avatardrawcard/${avatarSelected?.id}.png`;
img.onload = () => {
fac.getColorAsync(img).then((color: FastAverageColorResult) => {
setAvgColor(color.hex); // #RRGGBB
});
fac.getColorAsync(img)
.then((color) => {
setAvgColor(color.hex);
})
.catch(e => console.error("Vẫn lỗi CORS:", e));
};
return () => {
fac.destroy();
img.onload = null;
};
}, [avatarSelected]);
@@ -153,7 +158,7 @@ export default function ShowCaseInfo() {
const subAffixMap = mapSubAffix["5"]
if (!mainAffixMap || !subAffixMap) return
return {
img: `https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${value.relic_set_id}_${key}.webp`,
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
mainAffix: {
property: mainAffixMap?.[value?.main_affix_id]?.property,
level: value?.level,
@@ -185,7 +190,6 @@ export default function ShowCaseInfo() {
}, 0)
}, 0)
}, [relicStats, avatarInfo])
const characterStats = useMemo(() => {
if (!avatarSelected || !avatarData) return
@@ -507,15 +511,15 @@ export default function ShowCaseInfo() {
const getImageSkill = useCallback((icon: string | undefined, status: StatusAddType | undefined) => {
if (!icon) return
if (icon.startsWith("SkillIcon")) {
return `https://api.hakush.in/hsr/UI/skillicons/${icon.replace(".png", ".webp")}`
return `${process.env.CDN_URL}/spriteoutput/skillicons/avatar/${avatarSelected?.id}/${icon}`
} else if (status && mappingStats[status.PropertyType]) {
return mappingStats[status.PropertyType].icon
}
else if (icon.startsWith("Icon")) {
return `https://api.hakush.in/hsr/UI/trace/${icon.replace(".png", ".webp")}`
return `${process.env.CDN_URL}/spriteoutput/trace/${icon}`
}
return ""
}, [])
}, [avatarSelected?.id])
return (
<div className="flex flex-col justify-start m-1 text-white">
@@ -544,8 +548,10 @@ export default function ShowCaseInfo() {
{avatarSelected && (
<NextImage
ref={imgRef}
src={`https://api.hakush.in/hsr/UI/avatardrawcard/${avatarSelected?.id}.webp`}
className="object-cover scale-[2] overflow-hidden"
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/avatardrawcard/${avatarSelected?.id}.png`}
className="object-contain scale-[2] overflow-hidden"
alt="Character Preview"
width={1024}
height={1024}
@@ -582,7 +588,7 @@ export default function ShowCaseInfo() {
transition: "transform 0.3s ease, filter 0.3s ease",
}}
>
{isActive && (
<div
className="absolute inset-0 rounded-full pointer-events-none"
@@ -617,9 +623,11 @@ export default function ShowCaseInfo() {
<NextImage
src={src ?? null}
alt="Rank Icon"
width={48}
height={48}
className="block rounded-full object-cover"
width={125}
height={125}
unoptimized
crossOrigin="anonymous"
className="block rounded-full object-contain"
style={{
width: "44px",
height: "44px",
@@ -653,8 +661,23 @@ export default function ShowCaseInfo() {
{avatarSelected && (
<div className="flex gap-1">
<NextImage src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`} alt="Path Icon" width={32} height={32} className="h-auto w-8" />
<NextImage src={`/icon/${avatarSelected?.damageType.toLowerCase()}.webp`} alt="Element Icon" width={32} height={32} className="h-auto w-8" />
<NextImage
unoptimized
crossOrigin="anonymous"
src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`}
alt="Path Icon"
width={32}
height={32}
className="h-auto w-8"
/>
<NextImage
unoptimized
crossOrigin="anonymous"
src={`/icon/${avatarSelected?.damageType.toLowerCase()}.webp`}
alt="Element Icon"
width={32}
height={32}
className="h-auto w-8" />
</div>
)}
@@ -665,7 +688,15 @@ export default function ShowCaseInfo() {
<div className="relative flex h-56.25 w-auto flex-row items-center">
{avatarSelected && (
<div className="absolute inset-0 flex items-center justify-center">
<NextImage src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`} alt="Path Icon" width={160} height={160} className="h-40 w-40 opacity-20" />
<NextImage
unoptimized
crossOrigin="anonymous"
src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`}
alt="Path Icon"
width={160}
height={160}
className="h-40 w-40 opacity-20"
/>
</div>
)}
@@ -721,10 +752,12 @@ export default function ShowCaseInfo() {
return skillImg ? (
<NextImage
src={skillImg}
unoptimized
crossOrigin="anonymous"
alt={btn.id}
width={32}
height={32}
className={`h-auto ${imageSize} ${filterClass}`}
width={125}
height={125}
className={`h-full ${imageSize} ${filterClass}`}
/>
) : null;
})()
@@ -777,8 +810,10 @@ export default function ShowCaseInfo() {
{/* Card Image */}
<NextImage
className="absolute object-cover rounded-xl z-9 w-[95%]"
src={`https://api.hakush.in/hsr/UI/lightconemaxfigures/${avatarProfile?.lightcone.item_id}.webp`}
className="absolute object-contain rounded-xl z-9 w-[95%]"
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${avatarProfile?.lightcone.item_id}.png`}
alt="Lightcone Image"
width={904}
height={1206}
@@ -872,19 +907,43 @@ export default function ShowCaseInfo() {
<div className="flex justify-center items-center flex-col gap-1 mt-1 ">
<div className="flex gap-1 text-sm ">
<div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1">
<NextImage src="/icon/hp.webp" alt="HP" width={16} height={16} className="w-4 h-4" />
<NextImage
unoptimized
crossOrigin="anonymous"
src="/icon/hp.webp"
alt="HP"
width={16}
height={16}
className="w-4 h-4"
/>
<span>{
lightconeStats?.hp
}</span>
</div>
<div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1">
<NextImage src="/icon/attack.webp" alt="ATK" width={16} height={16} className="w-4 h-4" />
<NextImage
src="/icon/attack.webp"
unoptimized
crossOrigin="anonymous"
alt="ATK"
width={16}
height={16}
className="w-4 h-4"
/>
<span>{lightconeStats?.attack}</span>
</div>
</div>
<div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1">
<NextImage src="/icon/defence.webp" alt="DEF" width={16} height={16} className="w-4 h-4" />
<NextImage
unoptimized
crossOrigin="anonymous"
src="/icon/defence.webp"
alt="DEF"
width={16}
height={16}
className="w-4 h-4"
/>
<span>{lightconeStats?.def}</span>
</div>
</div>
@@ -905,7 +964,14 @@ export default function ShowCaseInfo() {
return (
<div key={index} className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<NextImage src={stat?.icon || ""} alt="Stat Icon" width={40} height={40} className="h-auto w-10 p-2" />
<NextImage src={stat?.icon || ""}
unoptimized
crossOrigin="anonymous"
alt="Stat Icon"
width={40}
height={40}
className="h-auto w-10 p-2"
/>
<span className="font-bold">{stat.name}</span>
</div>
<div className="ml-3 mr-3 grow border rounded opacity-50" />

View File

@@ -23,6 +23,8 @@ export default function RelicShowcase({
<div className="absolute inset-0 rounded-lg blur-lg -z-10"></div>
<NextImage
src={relic?.img || ""}
unoptimized
crossOrigin="anonymous"
width={78}
height={78}
alt="Relic Icon"
@@ -47,6 +49,8 @@ export default function RelicShowcase({
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
<NextImage
src={relic?.mainAffix?.detail?.icon || ""}
unoptimized
crossOrigin="anonymous"
width={35}
height={35}
alt="Main Affix Icon"
@@ -72,6 +76,8 @@ export default function RelicShowcase({
{subAffix?.detail?.icon ? (
<NextImage
src={subAffix?.detail?.icon || ""}
unoptimized
crossOrigin="anonymous"
width={32}
height={32}
alt="Sub Affix Icon"
@@ -86,11 +92,11 @@ export default function RelicShowcase({
+{subAffix?.valueAffix + subAffix?.detail?.unit}
</span>
{
(avatarInfo?.Relics?.SubAffixPropertyList.findIndex((item) => item === subAffix?.property) !== -1) && (
<span className="ml-1 bg-yellow-600/20 text-yellow-400 rounded-full px-1 py-0.5 text-[10px] font-semibold border border-yellow-600/30 shrink-0 leading-none">
{subAffix?.count}
</span>
)}
(avatarInfo?.Relics?.SubAffixPropertyList.findIndex((item) => item === subAffix?.property) !== -1) && (
<span className="ml-1 bg-yellow-600/20 text-yellow-400 rounded-full px-1 py-0.5 text-[10px] font-semibold border border-yellow-600/30 shrink-0 leading-none">
{subAffix?.count}
</span>
)}
</div>
</div>
)

View File

@@ -48,14 +48,13 @@ export default function SkillsInfo() {
const getImageSkill = (icon: string | undefined, status: StatusAddType | undefined) => {
if (!icon) return
const urlPrefix = "https://api.hakush.in/hsr/UI/skillicons/";
const urlPrefix = `${process.env.CDN_URL}/spriteoutput/skillicons/avatar/${avatarSelected?.id}/`;
if (icon.startsWith("SkillIcon")) {
return `${urlPrefix}${icon.replace(".png", ".webp")}`
return `${urlPrefix}${icon}`
} else if (status && mappingStats[status.PropertyType]) {
return mappingStats[status.PropertyType].icon
}
else if (icon.startsWith("Icon")) {
return `https://api.hakush.in/hsr/UI/trace/${icon.replace(".png", ".webp")}`
} else if (icon.startsWith("Icon")) {
return `${process.env.CDN_URL}/spriteoutput/trace/${icon}`
}
}
@@ -147,6 +146,8 @@ export default function SkillsInfo() {
{traceButtons && avatarInfo && (
<div className="grid col-span-4 relative w-full aspect-square">
<Image
unoptimized
crossOrigin="anonymous"
src={`/skilltree/${avatarSelected?.baseType?.toUpperCase()}.webp`}
alt=""
width={312}
@@ -179,8 +180,8 @@ export default function SkillsInfo() {
${btn.size === "elation" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
${skillSelected === btn.id ? "border-4 border-primary" : ""}
${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]
? "opacity-50 cursor-not-allowed"
: ""}
? "opacity-50 cursor-not-allowed"
: ""}
`}
onClick={() => {
setSkillSelected(btn.id === skillSelected ? null : btn.id)
@@ -195,13 +196,14 @@ export default function SkillsInfo() {
src={getImageSkill(avatarInfo?.SkillTrees?.[btn.id]?.["1"]?.Icon, avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0]) || ""}
alt={btn.id.replaceAll("Point", "")}
priority={true}
unoptimized={true}
width={124}
height={124}
style={{
filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : ""
}}
/>
{(btn.size === "big" || btn.size === "memory" || btn.size === "elation") && (
{(btn.size === "big" || btn.size === "memory" || btn.size === "elation") && (
<p className="
z-12 text-sm sm:text-xs lg:text-sm xl:text-base 2xl:text-2xl
font-bold text-center rounded-full absolute
@@ -252,12 +254,12 @@ export default function SkillsInfo() {
{btn.size === "big" && (
<div
style={{
position: "absolute",
inset: 0,
backgroundColor: "#f5e4b0",
mixBlendMode: "screen",
opacity: 0.3,
borderRadius: "50%"
position: "absolute",
inset: 0,
backgroundColor: "#f5e4b0",
mixBlendMode: "screen",
opacity: 0.3,
borderRadius: "50%"
}}
/>
)}

View File

@@ -1,130 +1,3 @@
import { CharacterBasic, CharacterBasicRaw, EventBasic, EventBasicRaw, LightConeBasic, LightConeBasicRaw, MonsterBasic, MonsterBasicRaw, RelicBasic, RelicBasicEffect, RelicBasicRaw } from "@/types";
export function convertRelicSet(id: string, item: RelicBasicRaw): RelicBasic {
const lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const setRelic = new Map<string, RelicBasicEffect>();
Object.entries(item.set).forEach(([key, value]) => {
setRelic.set(key, {
ParamList: value.ParamList,
lang: new Map<string, string>([
['en', value.en],
['kr', value.kr],
['cn', value.cn],
['jp', value.jp]
])
});
});
const result: RelicBasic = {
icon: item.icon,
lang: lang,
id: id,
set: setRelic
};
return result;
}
export function convertLightcone(id: string, item: LightConeBasicRaw): LightConeBasic {
const lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const result: LightConeBasic = {
rank: item.rank,
baseType: item.baseType,
desc: item.desc,
lang: lang,
id: id
};
return result;
}
export function convertAvatar(id: string, item: CharacterBasicRaw): CharacterBasic {
const lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
let text = ""
if (Number(id) % 2 === 0 && Number(id) > 8000) {
text = `Female ${item.damageType} MC`
} else if (Number(id) > 8000) {
text = `Male ${item.damageType} MC`
}
if (text !== "") {
lang.set("en", text)
lang.set("kr", text)
lang.set("cn", text)
lang.set("jp", text)
}
const result: CharacterBasic = {
release: item.release,
icon: item.icon,
rank: item.rank,
baseType: item.baseType,
damageType: item.damageType,
desc: item.desc,
lang: lang,
id: id
};
return result;
}
export function convertEvent(id: string, item: EventBasicRaw): EventBasic {
const lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const result: EventBasic = {
lang: lang,
id: id,
begin: item.begin,
end: item.end,
live_begin: item.live_begin,
live_end: item.live_end,
param: item.param,
};
return result;
}
export function convertMonster(id: string, item: MonsterBasicRaw): MonsterBasic {
const lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const result: MonsterBasic = {
id: id,
rank: item.rank,
camp: item.camp,
icon: item.icon,
child: item.child,
weak: item.weak,
desc: item.desc,
lang: lang
};
return result;
}
export function convertToRoman(num: number): string {
const roman: [number, string][] = [
[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],

View File

@@ -4,35 +4,47 @@ import { useTranslations } from "next-intl"
type TFunc = ReturnType<typeof useTranslations>
export function getNameChar(locale: string, t: TFunc, data: CharacterBasic | undefined): string {
if (!data) {
return ""
}
if (!listCurrentLanguage.hasOwnProperty(locale)) {
return ""
}
export function getNameChar(
locale: string,
t: TFunc,
data: CharacterBasic | undefined
): string {
if (!data) return "";
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
if (!text) {
text = data.lang.get("en") ?? "";
}
if (Number(data.id) > 8000) {
text = `${t("trailblazer")}${t(data?.baseType?.toLowerCase() ?? "")}`;
}
return text
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) {
return "";
}
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
let text = data.lang[langKey] ?? "";
if (!text) {
text = data.lang["en"] ?? "";
}
if (Number(data.id) > 8000) {
text = `${t("trailblazer")}${t(data?.baseType?.toLowerCase() ?? "")}`;
}
return text;
}
export function getLocaleName(locale: string, data: LightConeBasic | EventBasic | MonsterBasic | undefined): string {
if (!data) {
return ""
}
if (!listCurrentLanguage.hasOwnProperty(locale)) {
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) {
return ""
}
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
let text = data.lang[langKey] ?? "";
if (!text) {
text = data.lang.get("en") ?? "";
text = data.lang["en"] ?? "";
}
return text
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AffixDetail, ASDetail, ChangelogItemType, CharacterDetail, ConfigMaze, FreeSRJson, LightConeDetail, MocDetail, MonsterDetail, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
import { AffixDetail, ASDetail, ChangelogItemType, CharacterBasic, CharacterDetail, ConfigMaze, EventBasic, FreeSRJson, LightConeBasic, LightConeDetail, MocDetail, MonsterBasic, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
import axios from 'axios';
import { psResponseSchema } from "@/zod";
import { ExtraData } from "@/types";
@@ -142,16 +142,6 @@ export async function fetchPeakEventApi(locale: string): Promise<Record<string,
}
}
export async function fetchMonstersApi(locale: string): Promise<Record<string, MonsterDetail> | null> {
try {
const res = await axios.get<Record<string, MonsterDetail>>(`/data/monsters.${locale}.json`);
return res.data;
} catch (error) {
console.error('Failed to fetch monster:', error);
return null;
}
}
export async function fetchChangelog(): Promise<ChangelogItemType[] | null> {
try {
const res = await axios.get<ChangelogItemType[]>(`/data/changelog.json`);
@@ -162,6 +152,80 @@ export async function fetchChangelog(): Promise<ChangelogItemType[] | null> {
}
}
export async function getCharacterListApi(): Promise<CharacterBasic[]> {
try {
const res = await axios.get<CharacterBasic[]>('/data/character.json');
return res.data;
} catch (error) {
console.error('Failed to fetch character list:', error);
return [];
}
}
export async function getLightconeListApi(): Promise<LightConeBasic[]> {
try {
const res = await axios.get<LightConeBasic[]>('/data/lightcone.json');
return res.data
} catch (error) {
console.error('Failed to fetch lightcone list:', error);
return [];
}
}
export async function getMOCEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<EventBasic[]>('/data/moc.json');
return res.data
} catch (error) {
console.error('Failed to fetch moc list:', error);
return [];
}
}
export async function getASEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<EventBasic[]>('/data/as.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch as list:', error);
return [];
}
}
export async function getPFEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<EventBasic[]>('/data/pf.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch pf list:', error);
return [];
}
}
export async function getPEAKEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<EventBasic[]>('/data/peak.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch peak list:', error);
return [];
}
}
export async function getMonsterListApi(): Promise<MonsterBasic[]> {
try {
const res = await axios.get<MonsterBasic[]>('/data/monster.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch peak list:', error);
return [];
}
}
export async function SendDataToServer(
username: string,
password: string,

View File

@@ -1,399 +0,0 @@
import { convertAvatar, convertEvent, convertLightcone, convertMonster, convertRelicSet } from "@/helper";
import { ASDetail, CharacterBasic, CharacterBasicRaw, CharacterDetail, EventBasic, EventBasicRaw, LightConeBasic, LightConeBasicRaw, LightConeDetail, MocDetail, MonsterBasic, MonsterBasicRaw, MonsterDetail, MonsterValue, PeakDetail, PFDetail, RelicBasic, RelicBasicRaw, RelicDetail } from "@/types";
import axios from "axios";
export async function getLightconeInfoApi(lightconeId: number, locale: string): Promise<LightConeDetail | null> {
try {
const res = await axios.get<LightConeDetail>(
`https://api.hakush.in/hsr/data/${locale}/lightcone/${lightconeId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as LightConeDetail;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getRelicInfoApi(relicId: number, locale: string): Promise<RelicDetail | null> {
try {
const res = await axios.get<RelicDetail>(
`https://api.hakush.in/hsr/data/${locale}/relicset/${relicId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as RelicDetail;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getCharacterInfoApi(avatarId: number, locale: string): Promise<CharacterDetail | null> {
try {
const res = await axios.get<CharacterDetail>(
`https://api.hakush.in/hsr/data/${locale}/character/${avatarId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as CharacterDetail;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getMOCEventInfoApi(eventId: number, locale: string): Promise<MocDetail[] | null> {
try {
const res = await axios.get<MocDetail[]>(
`https://api.hakush.in/hsr/data/${locale}/maze/${eventId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as MocDetail[];
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getASEventInfoApi(eventId: number, locale: string): Promise<ASDetail | null> {
try {
const res = await axios.get<ASDetail>(
`https://api.hakush.in/hsr/data/${locale}/boss/${eventId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as ASDetail;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getPFEventInfoApi(eventId: number, locale: string): Promise<PFDetail | null> {
try {
const res = await axios.get<PFDetail>(
`https://api.hakush.in/hsr/data/${locale}/story/${eventId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as PFDetail;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getPeakEventInfoApi(eventId: number, locale: string): Promise<PeakDetail | null> {
try {
const res = await axios.get<PeakDetail>(
`https://api.hakush.in/hsr/data/${locale}/peak/${eventId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data as PeakDetail;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getCharacterListApi(): Promise<CharacterBasic[]> {
try {
const res = await axios.get<Record<string, CharacterBasicRaw>>(
'https://api.hakush.in/hsr/data/character.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertAvatar(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getLightconeListApi(): Promise<LightConeBasic[]> {
try {
const res = await axios.get<Record<string, LightConeBasicRaw>>(
'https://api.hakush.in/hsr/data/lightcone.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertLightcone(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getRelicSetListApi(): Promise<RelicBasic[]> {
try {
const res = await axios.get<Record<string, RelicBasicRaw>>(
'https://api.hakush.in/hsr/data/relicset.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertRelicSet(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getMOCEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<Record<string, EventBasicRaw>>(
'https://api.hakush.in/hsr/data/maze.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertEvent(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getASEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<Record<string, EventBasicRaw>>(
'https://api.hakush.in/hsr/data/maze_boss.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertEvent(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getPFEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<Record<string, EventBasicRaw>>(
'https://api.hakush.in/hsr/data/maze_extra.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertEvent(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getPEAKEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<Record<string, EventBasicRaw>>(
'https://api.hakush.in/hsr/data/maze_peak.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertEvent(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
export async function getMonsterListApi(): Promise<{list: MonsterBasic[], map: Record<string, MonsterBasic>}> {
try {
const res = await axios.get<Record<string, MonsterBasicRaw>>(
'https://api.hakush.in/hsr/data/monster.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const dataArr = Array.from(Object.entries(res.data)).map(([id, it]) => convertMonster(id, it));
const dataMap = Object.fromEntries(Object.entries(res.data).map(([id, it]) => [id, convertMonster(id, it)]));
return {
list: dataArr,
map: dataMap
};
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return {list: [], map: {}};
}
}
export async function getMonsterValueApi(): Promise<Record<string, MonsterValue> | null> {
try {
const res = await axios.get<Record<string, MonsterValue>>(
`https://api.hakush.in/hsr/data/monstervalue.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}
export async function getMonsterDetailApi(monsterId: number, locale: string): Promise<MonsterDetail | null> {
try {
const res = await axios.get<MonsterDetail>(
`https://api.hakush.in/hsr/data/${locale}/monster/${monsterId}.json`,
{
headers: {
'Content-Type': 'application/json',
},
}
);
return res.data;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return null;
}
}

View File

@@ -1,2 +1 @@
export * from "./api";
export * from "./hakushi";
export * from "./api";

View File

@@ -17,14 +17,6 @@ export const useFetchAvatarData = () => {
const { data: dataAvatar, error: errorAvatar } = useQuery({
queryKey: ['avatarData'],
queryFn: getCharacterListApi,
select: (data) => data.sort((a, b) => {
const aHasRelease = typeof a.release === 'number';
const bHasRelease = typeof b.release === 'number';
if (!aHasRelease && !bHasRelease) return 0;
if (!aHasRelease) return -1;
if (!bHasRelease) return 1;
return b.release! - a.release!;
}),
staleTime: 1000 * 60 * 5,
})

View File

@@ -1,59 +1,29 @@
"use client"
import { useQuery } from '@tanstack/react-query'
import { getMonsterValueApi, getMonsterListApi, fetchMonstersApi } from '@/lib/api'
import { getMonsterListApi } from '@/lib/api'
import { useEffect } from 'react'
import { toast } from 'react-toastify'
import useMonsterStore from '@/stores/monsterStore'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import { MonsterBasic } from '@/types'
export const useFetchMonsterData = () => {
const { setAllMapMonster, setListMonster, setAllMapMonsterValue, setAllMapMonsterInfo } = useMonsterStore()
const { locale } = useLocaleStore()
const { setAllMapMonster, setListMonster } = useMonsterStore()
const { data: dataMonster, error: errorMonster } = useQuery({
queryKey: ['monsterData'],
queryFn: getMonsterListApi,
staleTime: 1000 * 60 * 5,
})
const { data: dataMonsterValue, error: errorMonsterValue } = useQuery({
queryKey: ['monsterValueData'],
queryFn: getMonsterValueApi,
staleTime: 1000 * 60 * 5,
})
const { data: dataMonsterDetail, error: errorMonsterDetail } = useQuery({
queryKey: ['monsterDetailData', locale],
queryFn: () =>
fetchMonstersApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
enabled: !!dataMonster,
});
useEffect(() => {
if (dataMonster && !errorMonster) {
setListMonster(dataMonster.list.sort((a, b) => Number(b.id) - Number(a.id)))
setAllMapMonster(dataMonster.map)
setListMonster(dataMonster.sort((a, b) => Number(b.id) - Number(a.id)))
const monsterMap = dataMonster.reduce<Record<string, MonsterBasic>>((acc, m) => {
acc[m.id] = m
return acc
}, {})
setAllMapMonster(monsterMap)
} else if (errorMonster) {
toast.error("Failed to load monster data")
}
}, [dataMonster, errorMonster, setAllMapMonster, setListMonster])
useEffect(() => {
if (dataMonsterValue && !errorMonsterValue) {
setAllMapMonsterValue(dataMonsterValue)
} else if (errorMonsterValue) {
toast.error("Failed to load monster value data")
}
}, [dataMonsterValue, errorMonsterValue, setAllMapMonsterValue])
useEffect(() => {
if (dataMonsterDetail && !errorMonsterDetail) {
setAllMapMonsterInfo(dataMonsterDetail)
} else if (errorMonsterDetail) {
toast.error("Failed to load monster detail data")
}
}, [dataMonsterDetail, errorMonsterDetail, setAllMapMonsterInfo])
}

View File

@@ -1,6 +1,6 @@
"use client"
import { useQuery } from '@tanstack/react-query'
import { fetchRelicsApi, getRelicSetListApi } from '@/lib/api'
import { fetchRelicsApi } from '@/lib/api'
import { useEffect } from 'react'
import useRelicStore from '@/stores/relicStore'
import { listCurrentLanguageApi } from '@/constant/constant'
@@ -8,13 +8,8 @@ import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify'
export const useFetchRelicData = () => {
const { setListRelic, setAllMapRelicInfo } = useRelicStore()
const { setAllMapRelicInfo } = useRelicStore()
const { locale } = useLocaleStore()
const { data: dataRelic, error: errorRelic } = useQuery({
queryKey: ['relicData'],
queryFn: getRelicSetListApi,
staleTime: 1000 * 60 * 5,
})
const { data: dataRelicInfo, error: errorRelicInfo } = useQuery({
queryKey: ['relicInfoData', locale],
@@ -23,17 +18,8 @@ export const useFetchRelicData = () => {
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
enabled: !!dataRelic,
});
useEffect(() => {
if (dataRelic && !errorRelic) {
setListRelic(dataRelic)
} else if (errorRelic) {
toast.error("Failed to load relic data")
}
}, [dataRelic, errorRelic, setListRelic])
useEffect(() => {
if (dataRelicInfo && !errorRelicInfo) {
setAllMapRelicInfo(dataRelicInfo)

View File

@@ -49,7 +49,7 @@ const useAvatarStore = create<AvatarState>((set, get) => ({
let filteredList = get().listRawAvatar;
if (newFilter.name) {
filteredList = filteredList.filter((avatar) => {
return avatar.lang?.get(newFilter.locale)?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
return avatar.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
});
}
if (newFilter.path.length > 0) {

View File

@@ -47,7 +47,7 @@ const useCopyProfileStore = create<CopyProfileState>((set, get) => ({
let filteredList = get().listRawCopyAvatar;
if (newFilter.name) {
filteredList = filteredList.filter((avatar) => {
return avatar.lang?.get(newFilter.locale)?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
return avatar.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
});
}
if (newFilter.path.length > 0) {

View File

@@ -45,7 +45,7 @@ const useLightconeStore = create<LightconeState>((set, get) => ({
let filteredList = get().listRawLightcone;
if (newFilter.name && newFilter.locale) {
filteredList = filteredList.filter((lightcone) => {
return lightcone.lang?.get(newFilter.locale)?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
return lightcone.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
});
}
if (newFilter.path && newFilter.path.length > 0) {

View File

@@ -1,25 +1,17 @@
import { MonsterBasic, MonsterDetail, MonsterValue } from '@/types'
import { MonsterBasic } from '@/types'
import { create } from 'zustand'
interface MonsterState {
listMonster: MonsterBasic[]
mapMonster: Record<string, MonsterBasic>
mapMonsterInfo: Record<string, MonsterDetail>
mapMonsterValue: Record<string, MonsterValue>
setListMonster: (newListMonster: MonsterBasic[]) => void
setMapMonsterInfo: (monsterId: string, newMonster: MonsterDetail) => void
setAllMapMonsterInfo: (newMonster: Record<string, MonsterDetail>) => void
setMapMonsterValue: (monsterId: string, newMonster: MonsterValue) => void
setAllMapMonsterValue: (newMonster: Record<string, MonsterValue>) => void
setMapMonster: (monsterId: string, newMonster: MonsterBasic) => void
setAllMapMonster: (newMonster: Record<string, MonsterBasic>) => void
setMapMonster: (monsterId: string, newMonster: MonsterBasic) => void
}
const useMonsterStore = create<MonsterState>((set) => ({
listMonster: [],
mapMonster: {},
mapMonsterInfo: {},
mapMonsterValue: {},
setListMonster: (newListMonster) =>
set({ listMonster: newListMonster }),
@@ -31,22 +23,6 @@ const useMonsterStore = create<MonsterState>((set) => ({
setAllMapMonster: (newMonster) =>
set({ mapMonster: newMonster }),
setMapMonsterInfo: (monsterId, newMonster) =>
set((state) => ({
mapMonsterInfo: { ...state.mapMonsterInfo, [monsterId]: newMonster },
})),
setAllMapMonsterInfo: (newMonster) =>
set({ mapMonsterInfo: newMonster }),
setMapMonsterValue: (monsterId, newMonster) =>
set((state) => ({
mapMonsterValue: { ...state.mapMonsterValue, [monsterId]: newMonster },
})),
setAllMapMonsterValue: (newMonster) =>
set({ mapMonsterValue: newMonster }),
}))
export default useMonsterStore

View File

@@ -1,42 +1,14 @@
import { FilterRelicType, RelicDetail, RelicBasic } from '@/types';
import { RelicDetail } from '@/types';
import { create } from 'zustand'
interface RelicState {
listRelic: RelicBasic[];
listRawRelic: RelicBasic[];
filter: FilterRelicType;
mapRelicInfo: Record<string, RelicDetail>;
setListRelic: (newListRelic: RelicBasic[]) => void;
setFilter: (newFilter: FilterRelicType) => void;
setMapRelicInfo: (lightconeId: string, newRelic: RelicDetail) => void;
setAllMapRelicInfo: (newRelic: Record<string, RelicDetail>) => void;
}
const useRelicStore = create<RelicState>((set, get) => ({
listRelic: [],
listRawRelic: [],
mapRelicInfo: {},
filter: {
name: "",
type: [],
locale: "",
rarity: [],
},
setListRelic: (newListRelic: RelicBasic[]) => set({ listRelic: newListRelic, listRawRelic: newListRelic }),
setFilter: (newFilter: FilterRelicType) => {
set({ filter: newFilter })
if (newFilter.locale === "") {
return
}
let filteredList = get().listRawRelic;
if (newFilter.name && newFilter.locale) {
filteredList = filteredList.filter((relic) => {
return relic.lang?.get(newFilter.locale)?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
});
}
set({ listRelic: filteredList });
},
setMapRelicInfo: (lightconeId: string, newRelic: RelicDetail) => set((state) => ({ mapRelicInfo: { ...state.mapRelicInfo, [lightconeId]: newRelic } })),
setAllMapRelicInfo: (newRelic: Record<string, RelicDetail>) => set({ mapRelicInfo: newRelic }),
}));

View File

@@ -1,23 +1,8 @@
export interface CharacterBasicRaw {
release: number;
icon: string;
rank: string;
baseType: string;
damageType: string;
en: string;
desc: string;
kr: string;
cn: string;
jp: string;
}
export interface CharacterBasic {
id: string;
release?: number;
icon: string;
rank: string;
baseType: string;
damageType: string;
desc: string;
lang: Map<string, string>;
lang: Record<string, string>;
}

View File

@@ -46,6 +46,7 @@ export interface RankType {
Id: number;
Name: string;
Desc: string;
Icon: string;
ParamList: number[];
}

View File

@@ -1,23 +1,8 @@
export interface EventBasicRaw {
param?: number[];
en: string;
id: string;
begin: string;
end: string;
live_begin: string;
live_end: string;
kr: string;
cn: string;
jp: string;
}
export interface EventBasic {
param?: number[];
id: string;
begin: string;
end: string;
live_begin: string;
live_end: string;
lang: Map<string, string>;
lang: Record<string, string>;
}

View File

@@ -6,7 +6,6 @@ export * from "./mics"
export * from "./config_maze"
export * from "./lightconeBasic"
export * from "./lightconeDetail"
export * from "./relicBasic"
export * from "./relicDetail"
export * from "./affix"
export * from "./enka"
@@ -16,9 +15,7 @@ export * from "./monsterBasic"
export * from "./pfDetail"
export * from "./asDetail"
export * from "./mocDetail"
export * from "./monsterValue"
export * from "./peakDetail"
export * from "./monsterDetail"
export * from "./extraData"
export * from "./showcase"
export * from "./srtools"

View File

@@ -1,17 +1,8 @@
export interface LightConeBasicRaw {
rank: string;
baseType: string;
en: string;
desc: string;
kr: string;
cn: string;
jp: string;
}
export interface LightConeBasic {
id: string;
rank: string;
thumbnail: string
image: string,
baseType: string;
desc: string;
lang: Map<string, string>;
lang: Record<string, string>;
}

View File

@@ -1,25 +1,12 @@
export interface MonsterBasicRaw {
rank: string;
camp: string | null;
icon: string;
child: number[];
weak: string[];
en: string;
desc: string;
kr: string;
cn: string;
jp: string;
}
export interface MonsterBasic {
id: string;
rank: string;
camp: string | null;
icon: string;
child: number[];
image: string;
weak: string[];
desc: string;
lang: Map<string, string>;
desc: Record<string, string>;
lang: Record<string, string>;
}

View File

@@ -1,61 +0,0 @@
export interface MonsterDetail {
Id: number
Name: string
Desc: string
MonsterCampID: number | null
AttackBase: number
CriticalDamageBase: number
DefenceBase: number
HPBase: number
InitialDelayRatio: number
ImagePath: string
MinimumFatigueRatio: number
Rank: string
SpeedBase: number
StanceBase: number
StanceCount: number
StatusResistanceBase: number
Child: MonsterDetailChild[]
Drop: MonsterDetailDrop[]
}
export interface MonsterDetailChild {
Id: number
AttackModifyRatio: number
DefenceModifyRatio: number
EliteGroup: number
HPModifyRatio: number
SpeedModifyRatio: number
SpeedModifyValue: number | null
StanceModifyRatio: number
StanceWeakList: string[]
HardLevelGroup: number
DamageTypeResistance: MonsterDetailElementResistance[]
SkillList: MonsterDetailSkill[]
}
export interface MonsterDetailElementResistance {
$type: string
DamageType: string
Value: number
}
export interface MonsterDetailSkill {
Id: number
SkillName: string | null
SkillDesc: string | null
DamageType: string
SPHitBase: number | string
}
export interface MonsterDetailDrop {
MonsterTemplateID: number
WorldLevel?: number
AvatarExpReward: number
DisplayItemList: MonsterDetailDropItem[]
}
export interface MonsterDetailDropItem {
$type: string
ID: number
}

View File

@@ -1,23 +0,0 @@
export interface MonsterChild {
Id: number
AttackModifyRatio: number
DefenceModifyRatio: number
EliteGroup: number
HPModifyRatio: number
SpeedModifyRatio: number
SpeedModifyValue: number | null
StanceModifyRatio: number
HardLevelGroup: number
StanceWeakList: string[]
}
export interface MonsterValue {
Rank: string
AttackBase: number
DefenceBase: number
HPBase: number
SpeedBase: number
StanceBase: number
StatusResistanceBase: number
child: MonsterChild[]
}

View File

@@ -1,28 +0,0 @@
export interface RelicBasicRawEffect {
en: string;
ParamList: number[];
kr: string;
cn: string;
jp: string;
}
export interface RelicBasicRaw {
icon: string;
en: string;
kr: string;
cn: string;
jp: string;
set: Map<string, RelicBasicRawEffect>;
}
export interface RelicBasicEffect {
ParamList: number[];
lang: Map<string, string>;
}
export interface RelicBasic {
id: string;
icon: string;
lang: Map<string, string>;
set: Map<string, RelicBasicEffect>;
}