UPDATE: New cdn, assets
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m21s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m21s
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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()
|
||||
}}>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() || ""}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -64,6 +64,8 @@ export default function MonsterBar() {
|
||||
}
|
||||
>
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${item.icon}.webp`}
|
||||
alt={item.name}
|
||||
width={24}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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%"
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./api";
|
||||
export * from "./hakushi";
|
||||
export * from "./api";
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }),
|
||||
}));
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface RankType {
|
||||
Id: number;
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Icon: string;
|
||||
ParamList: number[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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[]
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user