UPDATE: New cdn, assets
Gitea Auto Deploy / Deploy-Container (push) Successful in 39s

This commit is contained in:
2026-02-17 23:27:25 +07:00
parent b5713c3138
commit f5541c9527
66 changed files with 56590 additions and 237 deletions
+18 -14
View File
@@ -2,7 +2,7 @@
import useAvatarDataStore from "@/stores/avatarDataStore";
import useBattleDataStore from "@/stores/battleDataStore";
import useLocaleStore from "@/stores/localeStore";
import {attackTypeToString, AvatarHakushiType} from "@/types";
import { attackTypeToString, CharacterBasic } from "@/types";
import { SkillBattleInfo } from "@/types/mics";
import { useEffect, useState, useRef } from "react";
import { useTranslations } from "next-intl";
@@ -13,7 +13,7 @@ import NameAvatar from "../nameAvatar";
export default function ActionBar() {
const [selectTurn, setSelectTurn] = useState<SkillBattleInfo | null>(null);
const [selectAvatar, setSelectAvatar] = useState<AvatarHakushiType | null>(null);
const [selectAvatar, setSelectAvatar] = useState<CharacterBasic | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore();
const { listAvatar } = useAvatarDataStore();
@@ -29,7 +29,7 @@ export default function ActionBar() {
alignItems: 'center',
};
const handleShow = (modalId: string, avatar: AvatarHakushiType, turn: SkillBattleInfo) => {
const handleShow = (modalId: string, avatar: CharacterBasic, turn: SkillBattleInfo) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
setSelectAvatar(avatar);
@@ -105,13 +105,13 @@ export default function ActionBar() {
skillHistory.map((turn, index) => {
const data = listAvatar.find(it => it.id === turn.avatarId.toString());
if (!data) return null;
const text = getNameChar(locale, data);
const text = getNameChar(locale, transI18n, data);
return (
<div key={index} className="h-full md:w-full">
<div
onClick={() => handleShow("action_detail_modal", data, turn)}
className="h-full grid grid-cols-2 gap-2 border bg-base-100 w-full hover:bg-base-200 transition-colors duration-200 border-cyan-400 border-l-4 cursor-pointer min-w-[200px] sm:min-w-[250px] md:min-w-0"
className="h-full grid grid-cols-2 gap-2 border bg-base-100 w-full hover:bg-base-200 transition-colors duration-200 border-cyan-400 border-l-4 cursor-pointer min-w-50 sm:min-w-62.5 md:min-w-0"
>
<div
style={contentStyle}
@@ -119,21 +119,23 @@ export default function ActionBar() {
>
<div className="avatar">
<div className="w-12 h-12 rounded-full border-2 flex items-center justify-center bg-base-300 border-cyan-400 border-l-4">
<Image
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`}
src={`${process.env.CDN_URL}/${data.icon}`}
unoptimized
crossOrigin="anonymous"
alt={text}
width={32}
height={32}
width={125}
height={125}
className="w-8 h-8 object-contain"
/>
</div>
</div>
<NameAvatar
locale={locale}
text={getNameChar(locale, data)}
text={getNameChar(locale, transI18n, data)}
className="text-base-content text-center text-sm mt-1 font-medium"
/>
/>
</div>
<div className="grid grid-cols-1 justify-center gap-2 py-2 w-full">
<div className="bg-local text-primary text-xs max-w-full">
@@ -195,15 +197,17 @@ export default function ActionBar() {
<span className="font-medium text-base-content/70">{transI18n("character")}:</span>
<NameAvatar
locale={locale}
text={getNameChar(locale, selectAvatar)}
text={getNameChar(locale, transI18n, selectAvatar)}
className="font-bold"
/>
</div>
</div>
<div className="flex justify-center items-center">
<Image
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectAvatar.id}.webp`}
alt={getNameChar(locale, selectAvatar)}
src={`${process.env.CDN_URL}/${selectAvatar?.icon}`}
unoptimized
crossOrigin="anonymous"
alt={getNameChar(locale, transI18n, selectAvatar)}
width={80}
height={80}
className="h-20 w-20 object-cover rounded-full border-2 border-purple-500 shadow-lg shadow-purple-500/20"
+14 -8
View File
@@ -2,21 +2,21 @@
import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { AvatarHakushiType } from '@/types';
import { CharacterBasic } from '@/types';
import NameAvatar from '../nameAvatar';
import useBattleDataStore from '@/stores/battleDataStore';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
interface CharacterCardProps {
data: AvatarHakushiType
data: CharacterBasic
}
export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore();
const text = getNameChar(locale, data)
const { avatarDetail } = useBattleDataStore()
const transI18n = useTranslations("DataAnalysisPage");
const text = getNameChar(locale, transI18n, data)
return (
<li className="z-10 flex flex-col w-28 items-center p-1 rounded-md shadow-lg bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300 hover:scale-105 m-1">
@@ -31,21 +31,27 @@ export default function CharacterCard({ data }: CharacterCardProps) {
<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}`}
className="w-full h-full rounded-md object-cover"
alt="ALT"
/>
<Image
width={48}
height={48}
src={`https://api.hakush.in/hsr/UI/element/${data.damageType.toLowerCase()}.webp`}
unoptimized
crossOrigin="anonymous"
src={`/icon/${data.damageType.toLowerCase()}.webp`}
className="absolute top-0 left-0 w-6 h-6"
alt={data.damageType.toLowerCase()}
/>
<Image
width={48}
height={48}
src={`https://api.hakush.in/hsr/UI/pathicon/${data.baseType.toLowerCase()}.webp`}
unoptimized
crossOrigin="anonymous"
src={`/icon/${data.baseType.toLowerCase()}.webp`}
className="absolute top-0 right-0 w-6 h-6"
alt={data.baseType.toLowerCase()}
/>
+2 -3
View File
@@ -24,14 +24,13 @@ export default function MultiCharLineChart() {
const [mode, setMode] = useState<1 | 2>(2);
const dataByAvatar = useDamageLinesForAll(mode);
const avatarIds = Object.keys(dataByAvatar).map(Number);
const { listAvatar } = useAvatarDataStore()
const { mapAvatar } = useAvatarDataStore()
const { locale } = useLocaleStore();
const transI18n = useTranslations("DataAnalysisPage")
const data = {
datasets: avatarIds.map((id, idx) => ({
label: getNameChar(locale, listAvatar.find(it => it.id == id.toString())),
label: getNameChar(locale, transI18n, mapAvatar?.[id.toString()]),
data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => {
return {
x: x.toFixed(2),
@@ -48,17 +48,16 @@ const borderPalette = colorPalette.map((c) => c.replace('0.6', '1'));
export default function DamagePerAvatarForAll() {
const transI18n = useTranslations("DataAnalysisPage");
const { lineup, skillHistory } = useBattleDataStore();
const { listAvatar } = useAvatarDataStore();
const { mapAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const [mode, setMode] = useState<number>(2);
const avatarMap = lineup.map((avatar) => {
const char = listAvatar.find(it => it.id === avatar.avatarId.toString());
if (!char) return undefined;
if (!mapAvatar?.[avatar.avatarId.toString()]) return undefined;
return {
avatarId: avatar.avatarId,
avatarName: getNameChar(locale, char)
avatarName: getNameChar(locale, transI18n, mapAvatar?.[avatar.avatarId.toString()])
};
}).filter(Boolean) as { avatarId: number, avatarName: string }[];
+2 -2
View File
@@ -27,14 +27,14 @@ export default function DamagePercentChartForAll() {
const [mode, setMode] = useState<1 | 2>(1);
const damageByAvatar = useDamagePercentPerAvatar();
const damageByType = useDamagePercentByType();
const { listAvatar } = useAvatarDataStore();
const { mapAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const transI18n = useTranslations("DataAnalysisPage");
const chartData = {
labels: (mode === 1
? damageByAvatar.map(d =>
getNameChar(locale, listAvatar.find(it => it.id == d.avatarId.toString()))
getNameChar(locale, transI18n, mapAvatar?.[d.avatarId.toString()])
)
: damageByType.map(d => transI18n(d.type.toLowerCase()))
),
+6 -4
View File
@@ -10,7 +10,7 @@ import NameAvatar from "../nameAvatar";
export default function EnemyBar() {
const { enemyDetail } = useBattleDataStore()
const { listEnemy } = useAvatarDataStore()
const { mapEnemy } = useAvatarDataStore()
const { locale } = useLocaleStore()
return (
@@ -21,10 +21,12 @@ export default function EnemyBar() {
<div key={uid} className="bg-base-200 rounded-lg p-3 border border-gray-700 w-52 shrink-0">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
{listEnemy.find((monster) => monster.child.includes(enemy.id))?.icon?.split("/")?.pop()?.replace(".png", "") && (
{mapEnemy?.[enemy.id.toString()]?.icon && (
<Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listEnemy.find((monster) => monster.child.includes(enemy.id))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
src={`${process.env.CDN_URL}/${mapEnemy?.[enemy.id.toString()]?.icon}`}
alt={enemy.name}
unoptimized
crossOrigin="anonymous"
width={40}
height={40}
className="object-cover w-10 h-10 rounded-lg"
@@ -33,7 +35,7 @@ export default function EnemyBar() {
<div className="flex-1 min-w-0">
<NameAvatar
text={getNameEnemy(locale, listEnemy.find((monster) => monster.child.includes(enemy.id)))}
text={getNameEnemy(locale, mapEnemy?.[enemy.id.toString()])}
locale={locale}
className="text-base font-semibold leading-tight truncate overflow-hidden"
/>
+32 -23
View File
@@ -4,7 +4,7 @@ import useBattleDataStore from "@/stores/battleDataStore";
import CharacterCard from "../card/characterCard";
import { useTranslations } from "next-intl";
import { useState, useEffect } from "react";
import { AvatarHakushiType } from "@/types";
import { CharacterBasic } from "@/types";
import useLocaleStore from "@/stores/localeStore";
import { getNameChar } from '@/helper/getNameChar';
import SkillBarChart from "../chart/skillBarChart";
@@ -18,12 +18,12 @@ import NameAvatar from "../nameAvatar";
// import ShowCaseInfo from "../card/showCaseCard";
export default function LineupBar() {
const [selectedCharacter, setSelectedCharacter] = useState<AvatarHakushiType | undefined>(undefined);
const [selectedCharacter, setSelectedCharacter] = useState<CharacterBasic | undefined>(undefined);
const [isModalOpen, setIsModalOpen] = useState(false);
const transI18n = useTranslations("DataAnalysisPage");
const { lineup, turnHistory, dataAvatar } = useBattleDataStore();
const { listAvatar } = useAvatarDataStore();
const { listAvatar, mapAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0);
const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0)
@@ -33,7 +33,7 @@ export default function LineupBar() {
lineup.some(av => av.avatarId.toString() === item.id)
);
const handleShow = (modalId: string, item: AvatarHakushiType) => {
const handleShow = (modalId: string, item: CharacterBasic) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
setSelectedCharacter(item);
@@ -81,16 +81,16 @@ export default function LineupBar() {
<path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
<span className="text-sm truncate flex flex-row">
<div>
{transI18n("lastTurn")}:
</div>
<NameAvatar
locale={locale}
text={getNameChar(locale, listAvatar.find(it => it.id === turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString()))}
/>
</span>
<span className="text-sm truncate flex flex-row">
<div>
{transI18n("lastTurn")}:
</div>
<NameAvatar
locale={locale}
text={getNameChar(locale, transI18n, mapAvatar?.[turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString() || ""])}
/>
</span>
</div>
</div>
@@ -142,10 +142,10 @@ export default function LineupBar() {
</div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<NameAvatar
locale={locale}
text={getNameChar(locale, selectedCharacter).toUpperCase()}
text={getNameChar(locale, transI18n, selectedCharacter).toUpperCase()}
className={"font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"}
/>
</div>
@@ -170,8 +170,9 @@ export default function LineupBar() {
<span className="font-bold">{transI18n(selectedCharacter.baseType.toLowerCase())}</span>
{selectedCharacter.baseType && (
<Image
src={`https://api.hakush.in/hsr/UI/pathicon/${selectedCharacter.baseType.toLowerCase()}.webp`}
unoptimized
crossOrigin="anonymous"
src={`/icon/${selectedCharacter.baseType.toLowerCase()}.webp`}
className="w-6 h-6"
alt={selectedCharacter.baseType.toLowerCase()}
width={24}
@@ -185,7 +186,9 @@ export default function LineupBar() {
<p className="flex items-center space-x-2">
<span>{transI18n("element")}:</span>
<Image
src={`https://api.hakush.in/hsr/UI/element/${selectedCharacter.damageType.toLowerCase()}.webp`}
unoptimized
crossOrigin="anonymous"
src={`/icon/${selectedCharacter.damageType.toLowerCase()}.webp`}
className="w-6 h-6"
alt={selectedCharacter.damageType.toLowerCase()}
width={24}
@@ -212,7 +215,9 @@ export default function LineupBar() {
<div className="flex items-center space-x-2">
<span>{transI18n("lightcones")}:</span>
<Image
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${avatar?.Lightcone?.item_id}.webp`}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemediumicon/${avatar?.Lightcone?.item_id}.png`}
className="w-12 h-12"
alt={avatar?.Lightcone?.item_id?.toString() || ""}
width={200}
@@ -224,8 +229,10 @@ export default function LineupBar() {
<div className="grid grid-cols-3 md:flex md:flex-row w-full">
{relicIds.map(it => (
<Image
unoptimized
crossOrigin="anonymous"
key={it}
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${it}.webp`}
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${it}.png`}
className="w-12 h-12"
alt={avatar?.Lightcone?.item_id?.toString() || ""}
width={200}
@@ -243,8 +250,10 @@ export default function LineupBar() {
</div>
<Image
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectedCharacter.id}.webp`}
alt={getNameChar(locale, selectedCharacter)}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${selectedCharacter.icon}`}
alt={getNameChar(locale, transI18n, selectedCharacter)}
className="h-32 w-32 object-cover rounded-full border-2 border-purple-500"
width={128}
height={128}
@@ -1,5 +1,6 @@
"use client";
import { createContext, PropsWithChildren, useEffect, useState } from "react";
import useLocaleStore from "@/stores/localeStore";
import { createContext, PropsWithChildren, useEffect } from "react";
interface ThemeContextType {
theme?: string;
@@ -8,7 +9,7 @@ interface ThemeContextType {
export const ThemeContext = createContext<ThemeContextType>({});
export const ThemeProvider = ({ children }: PropsWithChildren) => {
const [theme, setTheme] = useState<string>("light");
const { theme, setTheme } = useLocaleStore()
useEffect(() => {
if (typeof window !== "undefined") {
@@ -19,14 +20,13 @@ export const ThemeProvider = ({ children }: PropsWithChildren) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const changeTheme = (nextTheme: string | null) => {
console.log(nextTheme)
if (nextTheme) {
setTheme(nextTheme);
if (typeof window !== "undefined") {
localStorage.setItem("theme", nextTheme);
}
} else {
setTheme((prev) => (prev === "light" ? "night" : "light"));
setTheme(theme === "winter" ? "night" : "winter");
if (typeof window !== "undefined") {
localStorage.setItem("theme", theme);
}