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:
@@ -26,7 +26,12 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "api.hakush.in",
|
||||
hostname: "cdn.kain.id.vn",
|
||||
pathname: "**",
|
||||
},
|
||||
{
|
||||
protocol: "http",
|
||||
hostname: "cdn.kain.id.vn",
|
||||
pathname: "**",
|
||||
}
|
||||
],
|
||||
@@ -34,6 +39,9 @@ const nextConfig: NextConfig = {
|
||||
compiler: {
|
||||
styledComponents: true,
|
||||
},
|
||||
env: {
|
||||
CDN_URL: "https://cdn.kain.id.vn/firefly/assets/asbres",
|
||||
},
|
||||
};
|
||||
|
||||
export default withBundleAnalyzer(withNextIntl(nextConfig));
|
||||
|
||||
File diff suppressed because one or more lines are too long
189
public/data/as.json
Normal file
189
public/data/as.json
Normal file
@@ -0,0 +1,189 @@
|
||||
[
|
||||
{
|
||||
"id": "3001",
|
||||
"begin": "2024-05-06 04:00:02",
|
||||
"end": "2024-07-29 04:00:00",
|
||||
"lang": {
|
||||
"en": "Stormwind Knight",
|
||||
"cn": "冽风骑士",
|
||||
"jp": "凛冽たる風の騎士",
|
||||
"kr": "한풍의 기사"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3002",
|
||||
"begin": "2024-06-17 04:00:00",
|
||||
"end": "2024-09-06 04:00:00",
|
||||
"lang": {
|
||||
"en": "Dominated Evils",
|
||||
"cn": "支配恶兽",
|
||||
"jp": "悪獣の支配",
|
||||
"kr": "괴수 지배"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3003",
|
||||
"begin": "2024-07-29 04:00:02",
|
||||
"end": "2024-10-21 04:00:00",
|
||||
"lang": {
|
||||
"en": "Gamer's Instigation",
|
||||
"cn": "煽动博弈",
|
||||
"jp": "博打煽動",
|
||||
"kr": "도박과 선동"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3004",
|
||||
"begin": "2024-09-06 04:00:02",
|
||||
"end": "2024-12-02 04:00:00",
|
||||
"lang": {
|
||||
"en": "Sovereign Control",
|
||||
"cn": "支配指挥",
|
||||
"jp": "指揮の支配",
|
||||
"kr": "지휘 지배"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3005",
|
||||
"begin": "2024-10-21 04:00:02",
|
||||
"end": "2025-01-13 04:00:00",
|
||||
"lang": {
|
||||
"en": "Gusty Primate",
|
||||
"cn": "冽风猢狲",
|
||||
"jp": "凛冽たる風のサル",
|
||||
"kr": "한풍의 원숭이"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3006",
|
||||
"begin": "2024-12-02 04:00:02",
|
||||
"end": "2025-02-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Locust's Instigation",
|
||||
"cn": "煽动螟蝗",
|
||||
"jp": "扇動螟蝗",
|
||||
"kr": "선동과 해충"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3007",
|
||||
"begin": "2025-01-12 04:00:02",
|
||||
"end": "2025-04-07 04:00:00",
|
||||
"lang": {
|
||||
"en": "Gambling Primate",
|
||||
"cn": "猢狲博弈",
|
||||
"jp": "博打打ちのサル",
|
||||
"kr": "도박과 원숭이"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3008",
|
||||
"begin": "2025-02-24 04:00:02",
|
||||
"end": "2025-05-19 04:00:00",
|
||||
"lang": {
|
||||
"en": "Warlord of the Locusts",
|
||||
"cn": "螟蝗战首",
|
||||
"jp": "螟蝗戦首",
|
||||
"kr": "해충과 수장"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3009",
|
||||
"begin": "2025-04-07 04:00:02",
|
||||
"end": "2025-06-30 04:00:00",
|
||||
"lang": {
|
||||
"en": "Cutting Mistral",
|
||||
"cn": "冽风支配",
|
||||
"jp": "凛冽たる風の支配",
|
||||
"kr": "한풍과 지배"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3010",
|
||||
"begin": "2025-05-19 04:00:02",
|
||||
"end": "2025-08-11 04:00:00",
|
||||
"lang": {
|
||||
"en": "Ichor Beast",
|
||||
"cn": "金血恶兽",
|
||||
"jp": "黄金の血の悪獣",
|
||||
"kr": "황금 피와 괴수"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3011",
|
||||
"begin": "2025-06-30 04:00:02",
|
||||
"end": "2025-09-22 04:00:00",
|
||||
"lang": {
|
||||
"en": "Lupine Warhead",
|
||||
"cn": "天狼战首",
|
||||
"jp": "天狼戦首",
|
||||
"kr": "천랑과 수장"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3012",
|
||||
"begin": "2025-08-11 04:00:02",
|
||||
"end": "2025-11-03 04:00:00",
|
||||
"lang": {
|
||||
"en": "Gale of Netherveil",
|
||||
"cn": "冥茫冽风",
|
||||
"jp": "苛烈なる寒風",
|
||||
"kr": "어둠과 한풍"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3013",
|
||||
"begin": "2025-09-22 04:00:02",
|
||||
"end": "2025-12-15 04:00:00",
|
||||
"lang": {
|
||||
"en": "Instigation of the Locusts",
|
||||
"cn": "螟蝗煽动",
|
||||
"jp": "螟蝗の扇動",
|
||||
"kr": "해충과 선동"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3014",
|
||||
"begin": "2025-11-03 04:00:02",
|
||||
"end": "2025-12-15 04:00:00",
|
||||
"lang": {
|
||||
"en": "Primate Knight",
|
||||
"cn": "猢狲骑士",
|
||||
"jp": "サルとナイト",
|
||||
"kr": "원숭이와 기사"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3015",
|
||||
"begin": "2025-12-15 04:00:00",
|
||||
"end": "2026-02-09 04:00:00",
|
||||
"lang": {
|
||||
"en": "Dominance of Netherveil",
|
||||
"cn": "支配冥茫",
|
||||
"jp": "幽暗の支配者",
|
||||
"kr": "지배와 어둠"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3016",
|
||||
"begin": "2026-01-05 04:00:02",
|
||||
"end": "2026-03-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Militant Lupine",
|
||||
"cn": "兵锋天狼",
|
||||
"jp": "兵鋒天狼",
|
||||
"kr": "창날과 천랑"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3017",
|
||||
"begin": "2026-03-16 04:00:00",
|
||||
"end": "2026-05-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Militant Lupine",
|
||||
"cn": "兵锋天狼",
|
||||
"jp": "兵鋒天狼",
|
||||
"kr": "창날과 천랑"
|
||||
}
|
||||
}
|
||||
]
|
||||
1133
public/data/character.json
Normal file
1133
public/data/character.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2056
public/data/lightcone.json
Normal file
2056
public/data/lightcone.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
71644
public/data/moc.en.json
71644
public/data/moc.en.json
File diff suppressed because one or more lines are too long
574
public/data/moc.json
Normal file
574
public/data/moc.json
Normal file
@@ -0,0 +1,574 @@
|
||||
[
|
||||
{
|
||||
"id": "100",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "The Last Vestiges of Towering Citadel",
|
||||
"cn": "永屹之城遗秘",
|
||||
"jp": "永屹の城の秘密",
|
||||
"kr": "영원히 굳건한 도시에 남겨진 비밀"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "The Voyage of Navis Astriger",
|
||||
"cn": "天艟求仙迷航录",
|
||||
"jp": "天艟求仙放浪記",
|
||||
"kr": "불사의 약 원정기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "101",
|
||||
"begin": "2023-02-06 04:00:00",
|
||||
"end": "2023-03-06 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "102",
|
||||
"begin": "2022-11-14 04:00:00",
|
||||
"end": "2022-11-28 04:00:00",
|
||||
"lang": {
|
||||
"en": "Frostscar Reverie",
|
||||
"cn": "霜痕旧梦",
|
||||
"jp": "霜の跡に旧夢",
|
||||
"kr": "서리 내린 꿈"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "103",
|
||||
"begin": "2022-11-28 04:00:00",
|
||||
"end": "2022-12-12 00:00:00",
|
||||
"lang": {
|
||||
"en": "Everwinter Trials",
|
||||
"cn": "永冬试炼",
|
||||
"jp": "常冬の試練",
|
||||
"kr": "영원한 겨울의 시련"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "104",
|
||||
"begin": "2023-03-06 04:00:00",
|
||||
"end": "2023-03-20 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "105",
|
||||
"begin": "2022-12-26 04:00:00",
|
||||
"end": "2023-01-09 04:00:00",
|
||||
"lang": {
|
||||
"en": "Frostscar Reverie",
|
||||
"cn": "霜痕旧梦",
|
||||
"jp": "霜の跡に旧夢",
|
||||
"kr": "서리 내린 꿈"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "106",
|
||||
"begin": "2023-01-09 04:00:00",
|
||||
"end": "2023-01-23 04:00:00",
|
||||
"lang": {
|
||||
"en": "Everwinter Trials",
|
||||
"cn": "永冬试炼",
|
||||
"jp": "常冬の試練",
|
||||
"kr": "영원한 겨울의 시련"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "107",
|
||||
"begin": "2023-03-20 04:00:00",
|
||||
"end": "2023-04-03 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "108",
|
||||
"begin": "2033-02-06 04:00:00",
|
||||
"end": "2033-02-20 04:00:00",
|
||||
"lang": {
|
||||
"en": "Frostscar Reverie",
|
||||
"cn": "霜痕旧梦",
|
||||
"jp": "霜の跡に旧夢",
|
||||
"kr": "서리 내린 꿈"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "109",
|
||||
"begin": "2033-02-20 04:00:00",
|
||||
"end": "2033-03-06 04:00:00",
|
||||
"lang": {
|
||||
"en": "Everwinter Trials",
|
||||
"cn": "永冬试炼",
|
||||
"jp": "常冬の試練",
|
||||
"kr": "영원한 겨울의 시련"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "110",
|
||||
"begin": "2000-06-12 04:00:00",
|
||||
"end": "2000-06-26 04:00:00",
|
||||
"lang": {
|
||||
"en": "Coldiron Tribulation",
|
||||
"cn": "寒铁砥砺",
|
||||
"jp": "寒鉄練磨",
|
||||
"kr": "한철 연마"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "111",
|
||||
"begin": "2000-06-26 04:00:00",
|
||||
"end": "2000-07-10 04:00:00",
|
||||
"lang": {
|
||||
"en": "Hyperborean Search for Warmth",
|
||||
"cn": "蹈冰寻火",
|
||||
"jp": "氷踏みて炎求む",
|
||||
"kr": "불을 찾는 얼음"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "112",
|
||||
"begin": "2000-07-10 04:00:00",
|
||||
"end": "2000-07-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Stormquell",
|
||||
"cn": "风暴止息",
|
||||
"jp": "止息せし嵐",
|
||||
"kr": "잦아든 폭풍"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "113",
|
||||
"begin": "2003-06-05 04:00:00",
|
||||
"end": "2003-06-12 04:00:00",
|
||||
"lang": {
|
||||
"en": "Adrift in Astral Seas",
|
||||
"cn": "孤航天海",
|
||||
"jp": "天海の孤航",
|
||||
"kr": "고독한 천공 항행"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "114",
|
||||
"begin": "2003-06-12 04:00:00",
|
||||
"end": "2003-06-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Raintear Strife",
|
||||
"cn": "泪雨长战",
|
||||
"jp": "涙雨戦争",
|
||||
"kr": "눈물의 전쟁"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "115",
|
||||
"begin": "2003-06-16 04:00:00",
|
||||
"end": "2003-07-05 04:00:00",
|
||||
"lang": {
|
||||
"en": "Traces of Sanctus Medicus",
|
||||
"cn": "药王垂迹",
|
||||
"jp": "薬王の垂迹",
|
||||
"kr": "약왕의 화신"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "116",
|
||||
"begin": "2023-04-03 04:00:00",
|
||||
"end": "2023-04-17 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "117",
|
||||
"begin": "2000-04-17 04:00:00",
|
||||
"end": "2000-05-15 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "118",
|
||||
"begin": "2000-05-15 04:00:00",
|
||||
"end": "2000-05-29 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "119",
|
||||
"begin": "2000-05-29 04:00:00",
|
||||
"end": "2000-06-12 04:00:00",
|
||||
"lang": {
|
||||
"en": "Favor of Amber",
|
||||
"cn": "琥珀恩赐",
|
||||
"jp": "琥珀の賜物",
|
||||
"kr": "앰버의 은혜"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1001",
|
||||
"begin": "2000-09-04 04:00:00",
|
||||
"end": "2000-09-18 04:00:00",
|
||||
"lang": {
|
||||
"en": "Ethereal Shipcraft",
|
||||
"cn": "迷梦造舸",
|
||||
"jp": "迷夢造舟",
|
||||
"kr": "공상으로 만든 배"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1002",
|
||||
"begin": "2023-07-17 04:00:00",
|
||||
"end": "2023-08-21 04:00:00",
|
||||
"lang": {
|
||||
"en": "A Shot From the Sky",
|
||||
"cn": "天裂一射",
|
||||
"jp": "天裂の一射",
|
||||
"kr": "하늘을 가르는 화살"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1003",
|
||||
"begin": "2000-10-02 04:00:00",
|
||||
"end": "2000-10-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Mara and Null",
|
||||
"cn": "魔阴空劫",
|
||||
"jp": "魔陰空劫",
|
||||
"kr": "마각의 공겁"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1004",
|
||||
"begin": "2000-10-16 04:00:00",
|
||||
"end": "2000-10-30 04:00:00",
|
||||
"lang": {
|
||||
"en": "Living and Flaming Catastrophes",
|
||||
"cn": "生劫火劫",
|
||||
"jp": "生劫火劫",
|
||||
"kr": "생겁과 화겁"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1005",
|
||||
"begin": "2023-08-27 04:00:00",
|
||||
"end": "2023-09-25 04:00:00",
|
||||
"lang": {
|
||||
"en": "Ambrosial Arbor's Arrival",
|
||||
"cn": "建木降临",
|
||||
"jp": "建木降臨",
|
||||
"kr": "불멸의 거목 강림"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1006",
|
||||
"begin": "2000-11-13 04:00:00",
|
||||
"end": "2000-11-27 04:00:00",
|
||||
"lang": {
|
||||
"en": "Divine Root Subdual",
|
||||
"cn": "镇伏玄根",
|
||||
"jp": "玄根鎮伏",
|
||||
"kr": "현근 제압"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1007",
|
||||
"begin": "2000-11-27 04:00:00",
|
||||
"end": "2000-12-11 04:00:00",
|
||||
"lang": {
|
||||
"en": "Oath of Eternal Alliance",
|
||||
"cn": "万载盟誓",
|
||||
"jp": "万年移ろわぬ盟約の誓い",
|
||||
"kr": "영원한 맹세"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1008",
|
||||
"begin": "2023-09-25 04:00:00",
|
||||
"end": "2023-11-13 04:00:00",
|
||||
"lang": {
|
||||
"en": "Sedition of Imbibitor Lunae",
|
||||
"cn": "饮月之乱",
|
||||
"jp": "飲月の乱",
|
||||
"kr": "음월의 난"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1009",
|
||||
"begin": "2023-10-30 04:00:03",
|
||||
"end": "2023-12-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Enigma in Deep Space",
|
||||
"cn": "藏于深空之秘",
|
||||
"jp": "深空に隠された秘密",
|
||||
"kr": "심우주에 숨겨진 비밀"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1010",
|
||||
"begin": "2023-10-30 04:00:01",
|
||||
"end": "2023-12-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Light of Reignition",
|
||||
"cn": "重燃之光",
|
||||
"jp": "再燃の光",
|
||||
"kr": "다시 빛나는 불꽃"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1011",
|
||||
"begin": "2023-12-04 04:00:03",
|
||||
"end": "2024-02-05 04:00:00",
|
||||
"lang": {
|
||||
"en": "Dreamland of Longing",
|
||||
"cn": "难舍梦乡",
|
||||
"jp": "醒めたくない夢",
|
||||
"kr": "애틋한 꿈세계"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1012",
|
||||
"begin": "2023-12-04 04:00:01",
|
||||
"end": "2024-03-27 04:00:00",
|
||||
"lang": {
|
||||
"en": "Eve of Wanton Feast",
|
||||
"cn": "一晌荒宴",
|
||||
"jp": "荒唐な宴",
|
||||
"kr": "찰나의 연회"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1013",
|
||||
"begin": "2024-02-05 04:00:00",
|
||||
"end": "2024-04-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "White Night Chronicles",
|
||||
"cn": "白夜梦国记",
|
||||
"jp": "白昼夢国記",
|
||||
"kr": "백야몽국 연대기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1014",
|
||||
"begin": "2024-03-11 04:00:00",
|
||||
"end": "2024-05-06 04:00:00",
|
||||
"lang": {
|
||||
"en": "Dream Within Dream",
|
||||
"cn": "梦中之梦",
|
||||
"jp": "夢の中の夢",
|
||||
"kr": "꿈속의 꿈"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1015",
|
||||
"begin": "2024-03-11 04:00:01",
|
||||
"end": "2024-06-17 04:00:00",
|
||||
"lang": {
|
||||
"en": "A Song's True Theme (<unbreak>2.2</unbreak>)",
|
||||
"cn": "弦外之声(<unbreak>2.2</unbreak>)",
|
||||
"jp": "弦外の音(<unbreak>2.2</unbreak>)",
|
||||
"kr": "현 외의 소리(<unbreak>2.2</unbreak>)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1016",
|
||||
"begin": "2024-05-06 04:00:00",
|
||||
"end": "2024-07-29 04:00:00",
|
||||
"lang": {
|
||||
"en": "Dissipation of Dreams (<unbreak>2.3</unbreak>)",
|
||||
"cn": "曲尽梦散(<unbreak>2.3</unbreak>)",
|
||||
"jp": "曲は尽き夢は散る(<unbreak>2.3</unbreak>)",
|
||||
"kr": "끝난 곡조와 흩어진 꿈(<unbreak>2.3</unbreak>)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1017",
|
||||
"begin": "2024-06-17 04:00:02",
|
||||
"end": "2024-09-06 04:00:00",
|
||||
"lang": {
|
||||
"en": "The Big Sleep",
|
||||
"cn": "长眠不醒",
|
||||
"jp": "大いなる眠り",
|
||||
"kr": "깨지 않는 깊은 잠"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1018",
|
||||
"begin": "2024-07-29 04:00:00",
|
||||
"end": "2024-10-21 04:00:00",
|
||||
"lang": {
|
||||
"en": "Scalegorge Tidalflow",
|
||||
"cn": "鳞渊潮动",
|
||||
"jp": "鱗淵の潮騒",
|
||||
"kr": "인연의 파도"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1019",
|
||||
"begin": "2024-09-06 04:00:00",
|
||||
"end": "2024-12-02 04:00:00",
|
||||
"lang": {
|
||||
"en": "Dancing with the Dreams",
|
||||
"cn": "与梦共舞",
|
||||
"jp": "夢と踊る",
|
||||
"kr": "꿈과 춤을"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1020",
|
||||
"begin": "2024-10-21 04:00:00",
|
||||
"end": "2025-01-13 04:00:00",
|
||||
"lang": {
|
||||
"en": "Troopship Mayhem",
|
||||
"cn": "舸舰迷津",
|
||||
"jp": "遭難した巨艦",
|
||||
"kr": "길 잃은 거함"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1021",
|
||||
"begin": "2024-12-02 04:00:00",
|
||||
"end": "2025-02-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Strife of Creation",
|
||||
"cn": "创世纷争",
|
||||
"jp": "創世の争い",
|
||||
"kr": "창세의 분쟁"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1022",
|
||||
"begin": "2025-01-12 04:00:00",
|
||||
"end": "2025-04-07 04:00:00",
|
||||
"lang": {
|
||||
"en": "Out of Home",
|
||||
"cn": "出故乡记",
|
||||
"jp": "故郷より旅立つ",
|
||||
"kr": "출향기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1023",
|
||||
"begin": "2025-02-24 04:00:00",
|
||||
"end": "2025-05-19 04:00:00",
|
||||
"lang": {
|
||||
"en": "Breath of the Othershore",
|
||||
"cn": "彼岸之息",
|
||||
"jp": "向こう岸の息吹",
|
||||
"kr": "피안의 숨결"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1024",
|
||||
"begin": "2025-04-07 04:00:00",
|
||||
"end": "2025-06-30 04:00:00",
|
||||
"lang": {
|
||||
"en": "Lupine Moon-Devourer",
|
||||
"cn": "赤月吞狼",
|
||||
"jp": "紅月を呑む狼",
|
||||
"kr": "늑대를 삼킨 붉은 달"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1025",
|
||||
"begin": "2025-05-19 04:00:00",
|
||||
"end": "2025-08-11 04:00:00",
|
||||
"lang": {
|
||||
"en": "Gambler's Plight",
|
||||
"cn": "博徒困境",
|
||||
"jp": "博打うちのジレンマ",
|
||||
"kr": "노름꾼의 곤경"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1026",
|
||||
"begin": "2025-06-30 04:00:00",
|
||||
"end": "2025-09-22 04:00:00",
|
||||
"lang": {
|
||||
"en": "Pillar of Genesis",
|
||||
"cn": "创世之柱",
|
||||
"jp": "創世の柱",
|
||||
"kr": "창세의 기둥"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1027",
|
||||
"begin": "2025-08-11 04:00:00",
|
||||
"end": "2025-11-03 04:00:00",
|
||||
"lang": {
|
||||
"en": "Category Mistake",
|
||||
"cn": "范畴错误",
|
||||
"jp": "カテゴリーエラー",
|
||||
"kr": "범주 오류"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1028",
|
||||
"begin": "2025-09-22 04:00:00",
|
||||
"end": "2025-12-15 04:00:00",
|
||||
"lang": {
|
||||
"en": "Monkey Business",
|
||||
"cn": "猴子把戏",
|
||||
"jp": "サルのトリック",
|
||||
"kr": "원숭이 트릭"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1029",
|
||||
"begin": "2025-11-03 04:00:00",
|
||||
"end": "2026-02-09 04:00:00",
|
||||
"lang": {
|
||||
"en": "Breached Nest",
|
||||
"cn": "堤溃蚁穴",
|
||||
"jp": "蟻の一穴",
|
||||
"kr": "제방을 허무는 개미굴"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1030",
|
||||
"begin": "2026-01-05 04:00:00",
|
||||
"end": "2026-03-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Cyber Mystery",
|
||||
"cn": "网络谜踪",
|
||||
"jp": "サイバーミステリー",
|
||||
"kr": "사이버 미스터리"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1031",
|
||||
"begin": "2026-02-08 04:00:00",
|
||||
"end": "2026-03-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Grand Finale",
|
||||
"cn": "演剧终焉",
|
||||
"jp": "演劇の終焉",
|
||||
"kr": "연극의 종결"
|
||||
}
|
||||
}
|
||||
]
|
||||
55232
public/data/monster.json
Normal file
55232
public/data/monster.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
57
public/data/peak.json
Normal file
57
public/data/peak.json
Normal file
@@ -0,0 +1,57 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "Intellitron Endgame",
|
||||
"cn": "智械残局",
|
||||
"jp": "オムニックの終局",
|
||||
"kr": "지능 기계 종반전"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "Illusory Realm of the Blazing Sun",
|
||||
"cn": "烈阳幻域",
|
||||
"jp": "烈日の幻域",
|
||||
"kr": "태양의 <unbreak>환상 영역</unbreak>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "Dissonance",
|
||||
"cn": "不协和音",
|
||||
"jp": "不協和音",
|
||||
"kr": "불협화음"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "Cyber Crisis",
|
||||
"cn": "网络风波",
|
||||
"jp": "ネット騒動",
|
||||
"kr": "네트워크 소동"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"begin": "",
|
||||
"end": "",
|
||||
"lang": {
|
||||
"en": "Don't Mess With Pom-Pom",
|
||||
"cn": "别惹帕姆",
|
||||
"jp": "パムを怒らせるな",
|
||||
"kr": "폼폼 조심"
|
||||
}
|
||||
}
|
||||
]
|
||||
244
public/data/pf.json
Normal file
244
public/data/pf.json
Normal file
@@ -0,0 +1,244 @@
|
||||
[
|
||||
{
|
||||
"id": "2001",
|
||||
"begin": "2023-10-30 04:00:04",
|
||||
"end": "2023-12-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Youci's Wandering Words",
|
||||
"cn": "游辞漫说",
|
||||
"jp": "遊辞漫談",
|
||||
"kr": "유사의 허황된 이야기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2002",
|
||||
"begin": "2023-10-30 04:00:02",
|
||||
"end": "2024-02-05 04:00:00",
|
||||
"lang": {
|
||||
"en": "Tales of a Tethered Bird",
|
||||
"cn": "羁鸟奇谭",
|
||||
"jp": "籠鳥奇譚",
|
||||
"kr": "새장 속 새 이야기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2003",
|
||||
"begin": "2023-12-04 04:00:02",
|
||||
"end": "2024-02-05 04:00:00",
|
||||
"lang": {
|
||||
"en": "An Expression of Eloquence",
|
||||
"cn": "舌灿莲花",
|
||||
"jp": "優れた弁舌",
|
||||
"kr": "찬란한 감언"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2004",
|
||||
"begin": "2023-02-05 04:00:00",
|
||||
"end": "2024-04-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Deceitful Chaos",
|
||||
"cn": "撒诈捣虚",
|
||||
"jp": "噓八百",
|
||||
"kr": "새빨간 거짓말"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2005",
|
||||
"begin": "2023-02-05 04:00:01",
|
||||
"end": "2024-04-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Fictitious Wordsmithing",
|
||||
"cn": "作言造语",
|
||||
"jp": "口から出まかせ",
|
||||
"kr": "꾸며낸 이야기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2006",
|
||||
"begin": "2024-03-11 04:00:00",
|
||||
"end": "2024-06-17 04:00:00",
|
||||
"lang": {
|
||||
"en": "Lexical Enigma (<unbreak>2.2</unbreak>)",
|
||||
"cn": "新词迷离(<unbreak>2.2</unbreak>)",
|
||||
"jp": "あやふやな新語(<unbreak>2.2</unbreak>)",
|
||||
"kr": "흐릿한 새말(<unbreak>2.2</unbreak>)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2007",
|
||||
"begin": "2024-05-06 04:00:01",
|
||||
"end": "2024-07-29 04:00:00",
|
||||
"lang": {
|
||||
"en": "Out of Thin Air (<unbreak>2.3</unbreak>)",
|
||||
"cn": "向壁虚造(<unbreak>2.3</unbreak>)",
|
||||
"jp": "向壁虚造(<unbreak>2.3</unbreak>)",
|
||||
"kr": "터무니없는 날조(<unbreak>2.3</unbreak>)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2008",
|
||||
"begin": "2024-06-17 04:00:01",
|
||||
"end": "2024-09-06 04:00:00",
|
||||
"lang": {
|
||||
"en": "Words of Deceit",
|
||||
"cn": "欺人虚言",
|
||||
"jp": "虚言",
|
||||
"kr": "거짓된 말마디"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2009",
|
||||
"begin": "2024-07-29 04:00:01",
|
||||
"end": "2024-10-21 04:00:00",
|
||||
"lang": {
|
||||
"en": "Volubility",
|
||||
"cn": "巧言如流",
|
||||
"jp": "巧言流水の如し",
|
||||
"kr": "청산유수"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2010",
|
||||
"begin": "2024-09-06 04:00:01",
|
||||
"end": "2024-12-02 04:00:00",
|
||||
"lang": {
|
||||
"en": "Rumor Mill",
|
||||
"cn": "论黄数黑",
|
||||
"jp": "数黒論黄",
|
||||
"kr": "허튼소리"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2011",
|
||||
"begin": "2024-10-21 04:00:01",
|
||||
"end": "2025-01-13 04:00:00",
|
||||
"lang": {
|
||||
"en": "Technicality Entrapment",
|
||||
"cn": "深文巧诋",
|
||||
"jp": "手練手管",
|
||||
"kr": "중상모략"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2012",
|
||||
"begin": "2024-12-02 04:00:01",
|
||||
"end": "2025-02-24 04:00:00",
|
||||
"lang": {
|
||||
"en": "Clichéd Sayings",
|
||||
"cn": "陈腔滥调",
|
||||
"jp": "陳腐な表現",
|
||||
"kr": "진부한 말"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2013",
|
||||
"begin": "2025-01-12 04:00:01",
|
||||
"end": "2025-04-07 04:00:00",
|
||||
"lang": {
|
||||
"en": "Self-Fulfilling Prophecy",
|
||||
"cn": "自证预言",
|
||||
"jp": "自己証明の預言",
|
||||
"kr": "자기실현적 예언"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2014",
|
||||
"begin": "2025-02-24 04:00:01",
|
||||
"end": "2025-05-19 04:00:00",
|
||||
"lang": {
|
||||
"en": "Structural Rules",
|
||||
"cn": "结构规律",
|
||||
"jp": "構造規則",
|
||||
"kr": "구조적 규율"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2015",
|
||||
"begin": "2025-04-07 04:00:01",
|
||||
"end": "2025-06-30 04:00:00",
|
||||
"lang": {
|
||||
"en": "Narrative Analysis",
|
||||
"cn": "叙事分析",
|
||||
"jp": "叙事分析",
|
||||
"kr": "내러티브 분석"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2016",
|
||||
"begin": "2025-05-19 04:00:01",
|
||||
"end": "2025-08-11 04:00:00",
|
||||
"lang": {
|
||||
"en": "Three Act Structure",
|
||||
"cn": "三幕架构",
|
||||
"jp": "三幕構成",
|
||||
"kr": "3막 구조"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2017",
|
||||
"begin": "2025-06-30 04:00:01",
|
||||
"end": "2025-09-22 04:00:00",
|
||||
"lang": {
|
||||
"en": "Subjective Narrative",
|
||||
"cn": "主观叙事",
|
||||
"jp": "主観的叙事",
|
||||
"kr": "주관적 서사"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2018",
|
||||
"begin": "2025-08-11 04:00:01",
|
||||
"end": "2025-11-03 04:00:00",
|
||||
"lang": {
|
||||
"en": "Iambic Pentameter",
|
||||
"cn": "五步抑扬",
|
||||
"jp": "弱強五歩格",
|
||||
"kr": "약강 오보격"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2019",
|
||||
"begin": "2025-09-22 04:00:01",
|
||||
"end": "2025-12-15 04:00:00",
|
||||
"lang": {
|
||||
"en": "Syntax Rule",
|
||||
"cn": "程式句法",
|
||||
"jp": "シンタックスルール",
|
||||
"kr": "프로그래밍 문법"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2020",
|
||||
"begin": "2025-11-03 04:00:01",
|
||||
"end": "2026-02-09 04:00:00",
|
||||
"lang": {
|
||||
"en": "Epic Collection",
|
||||
"cn": "史诗集群",
|
||||
"jp": "叙事詩集",
|
||||
"kr": "서사시 모음집"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2021",
|
||||
"begin": "2026-01-05 04:00:01",
|
||||
"end": "2026-03-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Wordless Novel",
|
||||
"cn": "无字小说",
|
||||
"jp": "ワードレスノベル",
|
||||
"kr": "글자 없는 소설"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2022",
|
||||
"begin": "2026-02-08 04:00:01",
|
||||
"end": "2026-03-16 04:00:00",
|
||||
"lang": {
|
||||
"en": "Virtual Made Manifest",
|
||||
"cn": "虚境成章",
|
||||
"jp": "仮想の章節",
|
||||
"kr": "허상으로 이룬 장"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -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