UPDATE: Skilltree
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m46s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m46s
This commit is contained in:
@@ -293,7 +293,7 @@ export default function ActionBar() {
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('')}>{transI18n("characterInformation")}</button>
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('relics-info')}>{transI18n("relics")}</button>
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('eidolons-info')}>{transI18n("eidolons")}</button>
|
||||
<button disabled className="btn btn-success btn-sm cursor-not-allowed italic">{transI18n("skills")} ({transI18n("comingSoon")})</button>
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('skills-info')}>{transI18n("skills")}</button>
|
||||
<button disabled className="btn btn-success btn-sm cursor-not-allowed italic">{transI18n("showcaseCard")} {transI18n("comingSoon")}</button>
|
||||
<button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm"> {isConnectPS ? transI18n("sync") : transI18n("connectPs")}</button>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useFetchASData, useFetchAvatarData, useFetchConfigData, useFetchLightco
|
||||
export default function AvatarBar() {
|
||||
const [listElement, setListElement] = useState<Record<string, boolean>>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false })
|
||||
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
|
||||
const { listAvatar, setAvatarSelected, setFilter, filter } = useAvatarStore()
|
||||
const { listAvatar, setAvatarSelected, setSkillSelected, setFilter, filter } = useAvatarStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { locale } = useLocaleStore()
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function AvatarBar() {
|
||||
<div className="flex items-start h-full">
|
||||
<ul className="grid grid-cols-1 md: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)}>
|
||||
<div key={index} onClick={() => {setAvatarSelected(item); setSkillSelected(null)}}>
|
||||
<CharacterCard data={item} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -21,37 +21,73 @@ const getRarityName = (slot: string) => {
|
||||
switch (slot) {
|
||||
case '1': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/HEAD.png" alt="Head" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/HEAD.png"
|
||||
alt="Head"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Head</h2>
|
||||
</div>
|
||||
);
|
||||
case '2': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/HAND.png" alt="Hand" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/HAND.png"
|
||||
alt="Hand"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Hands</h2>
|
||||
</div>
|
||||
);
|
||||
case '3': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/BODY.png" alt="Body" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/BODY.png"
|
||||
alt="Body"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Body</h2>
|
||||
</div>
|
||||
);
|
||||
case '4': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/FOOT.png" alt="Foot" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/FOOT.png"
|
||||
alt="Foot"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Feet</h2>
|
||||
</div>
|
||||
);
|
||||
case '5': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/NECK.png" alt="Neck" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/NECK.png"
|
||||
alt="Neck"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Planar sphere</h2>
|
||||
</div>
|
||||
);
|
||||
case '6': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/OBJECT.png" alt="Object" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/OBJECT.png"
|
||||
alt="Object"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Link rope</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { connectToPS, downloadJson, syncDataToPS } from "@/helper";
|
||||
import { converterToFreeSRJson } from "@/helper/converterToFreeSRJson";
|
||||
import { useChangeTheme } from "@/hooks/useChangeTheme";
|
||||
import { listCurrentLanguage } from "@/lib/constant";
|
||||
import { listCurrentLanguage } from "@/constant/constant";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { motion } from "framer-motion";
|
||||
@@ -264,7 +264,7 @@ export default function Header() {
|
||||
{/* Logo */}
|
||||
|
||||
<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">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Image src="/ff-srtool.png" alt="Logo" width={50} height={50} />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<h1 className="text-xl font-bold">
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function EnkaImport() {
|
||||
}
|
||||
const newProfile = converterOneEnkaDataToAvatarStore(character, newAvatar.profileList.length)
|
||||
if (newProfile) {
|
||||
newAvatar.profileList.push()
|
||||
newAvatar.profileList.push(newProfile)
|
||||
newAvatar.profileSelect = newAvatar.profileList.length - 1
|
||||
}
|
||||
setAvatar(newAvatar)
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useEffect, useMemo } from 'react';
|
||||
import SelectCustomImage from '../select/customSelectImage';
|
||||
import { calcAffixBonus, randomPartition, randomStep, replaceByParam } from '@/helper';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import { mapingStats } from '@/lib/constant';
|
||||
import { mappingStats } from '@/constant/constant';
|
||||
import useAvatarStore from '@/stores/avatarStore';
|
||||
import useModelStore from '@/stores/modelStore';
|
||||
import useRelicMakerStore from '@/stores/relicMakerStore';
|
||||
@@ -118,7 +118,7 @@ export default function RelicMaker() {
|
||||
const data = affixSet[selectedMainStat];
|
||||
if (!data) return 0;
|
||||
|
||||
const stat = mapingStats?.[data.property];
|
||||
const stat = mappingStats?.[data.property];
|
||||
if (!stat) return 0;
|
||||
|
||||
const value = data.base + data.step * selectedRelicLevel;
|
||||
@@ -257,8 +257,8 @@ export default function RelicMaker() {
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(mapMainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
|
||||
imageUrl: mapingStats[value.property].icon
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={selectedMainStat}
|
||||
@@ -361,13 +361,13 @@ export default function RelicMaker() {
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
|
||||
imageUrl: mapingStats[value.property].icon
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
}))}
|
||||
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
|
||||
imageUrl: mapingStats[value.property].icon
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
}))}
|
||||
selectedCustomSet={v.affixId}
|
||||
placeholder={transI18n("selectASubStat")}
|
||||
@@ -378,7 +378,7 @@ export default function RelicMaker() {
|
||||
{/* Current Value */}
|
||||
<div className="col-span-4 text-center flex items-center justify-center gap-2">
|
||||
<span className="text-2xl font-mono">+{ }</span>
|
||||
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div>
|
||||
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div>
|
||||
</div>
|
||||
|
||||
{/* Roll Values */}
|
||||
@@ -387,19 +387,19 @@ export default function RelicMaker() {
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
|
||||
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0 , v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0 , v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)}
|
||||
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)}
|
||||
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
326
src/components/skillsInfo/index.tsx
Normal file
326
src/components/skillsInfo/index.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
"use client"
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { traceButtonsInfo, traceLink } from "@/constant/traceConstant";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import Image from "next/image";
|
||||
import { replaceByParam } from "@/helper";
|
||||
import { mappingStats } from "@/constant/constant";
|
||||
import { StatusAddType } from "@/types";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export default function SkillsInfo() {
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { theme } = useLocaleStore()
|
||||
const { avatarSelected, mapAvatarInfo, skillSelected, setSkillSelected } = useAvatarStore()
|
||||
const { avatars, setAvatar } = useUserDataStore()
|
||||
|
||||
const traceButtons = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return traceButtonsInfo[avatarSelected.baseType]
|
||||
}, [avatarSelected])
|
||||
|
||||
const avatarInfo = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return mapAvatarInfo[avatarSelected.id]
|
||||
}, [avatarSelected, mapAvatarInfo])
|
||||
|
||||
|
||||
|
||||
const avatarData = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return avatars[avatarSelected.id]
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const avatarSkillTree = useMemo(() => {
|
||||
if (!avatarSelected || !avatars[avatarSelected.id]) return {}
|
||||
if (avatars[avatarSelected.id].enhanced) {
|
||||
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {}
|
||||
}
|
||||
return avatarInfo?.SkillTrees || {}
|
||||
}, [avatarSelected, avatarInfo, avatars])
|
||||
|
||||
const skillInfo = useMemo(() => {
|
||||
if (!avatarSelected || !skillSelected) return
|
||||
return avatarSkillTree?.[skillSelected || ""]?.["1"]
|
||||
}, [avatarSelected, avatarSkillTree, skillSelected])
|
||||
|
||||
const getImageSkill = useCallback((icon: string | undefined, status: StatusAddType | undefined) => {
|
||||
if (!icon) return
|
||||
if (icon.startsWith("SkillIcon")) {
|
||||
if (Number(avatarSelected?.id) > 8000 && Number(avatarSelected?.id) % 2 === 0) {
|
||||
return `https://homdgcat.wiki/images/skillicons/avatar/${Number(avatarSelected?.id) - 1}/${icon.replaceAll(avatarSelected?.id || "", (Number(avatarSelected?.id) - 1).toString())}`
|
||||
}
|
||||
return `https://homdgcat.wiki/images/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")}`
|
||||
}
|
||||
}, [avatarSelected])
|
||||
|
||||
const getTraceBuffDisplay = useCallback((status: StatusAddType) => {
|
||||
const dataDisplay = mappingStats[status.PropertyType]
|
||||
if (!dataDisplay) return ""
|
||||
if (dataDisplay.unit === "%") {
|
||||
return `${(status.Value * 100).toFixed(1)}${dataDisplay.unit}`
|
||||
}
|
||||
if (dataDisplay.name === "SPD") {
|
||||
return `${status.Value.toFixed(1)}${dataDisplay.unit}`
|
||||
}
|
||||
return `${status.Value.toFixed(0)}${dataDisplay.unit}`
|
||||
}, [])
|
||||
|
||||
const dataLevelUpSkill = useMemo(() => {
|
||||
const skillIds: number[] = skillInfo?.LevelUpSkillID || []
|
||||
if (!avatarSelected || !avatarInfo || !avatarData) return
|
||||
let result = Object.values(avatarInfo.Skills || {})?.filter((skill) => skillIds.includes(skill.Id))
|
||||
if (avatarData.enhanced) {
|
||||
result = Object.values(avatarInfo.Enhanced[avatarData.enhanced.toString()].Skills || {})?.filter((skill) => skillIds.includes(skill.Id))
|
||||
}
|
||||
if (result && result.length > 0) {
|
||||
return {
|
||||
isServant: false,
|
||||
data: result,
|
||||
servantData: null,
|
||||
}
|
||||
}
|
||||
const resultServant = Object.entries(avatarInfo.Memosprite?.Skills || {})
|
||||
?.filter(([skillId]) => skillIds.includes(Number(skillId)))
|
||||
?.map(([skillId, skillData]) => ({
|
||||
Id: Number(skillId),
|
||||
...skillData,
|
||||
}))
|
||||
if (resultServant && resultServant.length > 0) {
|
||||
return {
|
||||
isServant: true,
|
||||
data: resultServant,
|
||||
servantData: avatarInfo.Memosprite,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarInfo, avatarData])
|
||||
|
||||
|
||||
const handlerMaxAll = () => {
|
||||
if (!avatarInfo || !avatarData || !avatarSkillTree) {
|
||||
toast.error(transI18n("maxAllFailed"))
|
||||
return
|
||||
}
|
||||
const newData = cloneDeep(avatarData)
|
||||
newData.data.skills = Object.values(avatarSkillTree).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
toast.success(transI18n("maxAllSuccess"))
|
||||
setAvatar(newData)
|
||||
}
|
||||
|
||||
const handlerChangeStatusTrace = (status: boolean) => {
|
||||
if (!avatarData || !skillInfo) return
|
||||
const newData = cloneDeep(avatarData)
|
||||
newData.data.skills[skillInfo?.PointID] = status ? 1 : 0
|
||||
|
||||
if (!status && traceLink?.[avatarSelected?.baseType || ""]?.[skillSelected || ""]) {
|
||||
traceLink[avatarSelected?.baseType || ""][skillSelected || ""].forEach((pointId) => {
|
||||
if (avatarSkillTree?.[pointId]?.["1"]) {
|
||||
console.log(avatarSkillTree?.[pointId]?.["1"].PointID)
|
||||
newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
setAvatar(newData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div className="rounded-xl p-6 shadow-lg">
|
||||
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
|
||||
<div className="w-2 h-6 bg-gradient-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("skills")}
|
||||
</h2>
|
||||
<div className="flex flex-col items-center">
|
||||
<button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button>
|
||||
{traceButtons && avatarInfo && (
|
||||
<div className="grid col-span-4 relative w-full aspect-square">
|
||||
<Image
|
||||
src={`/skilltree/${avatarSelected?.baseType?.toUpperCase()}.webp`}
|
||||
alt=""
|
||||
width={612}
|
||||
priority={true}
|
||||
height={612}
|
||||
style={{
|
||||
filter: (theme === "winter" || theme === "cupcake") ? "invert(1)" : "none"
|
||||
}}
|
||||
className={`w-full h-full object-cover rounded-xl`}
|
||||
/>
|
||||
{traceButtons.map((btn, index) => (
|
||||
<div
|
||||
key={`${btn.id} + ${index}`}
|
||||
id={btn.id}
|
||||
className={`
|
||||
absolute rounded-full border border-black
|
||||
bg-no-repeat bg-contain
|
||||
cursor-pointer transition-all duration-200 ease-in-out
|
||||
shadow-[0_0_5px_white] flex justify-center items-center
|
||||
hover:scale-110
|
||||
${btn.size === "small" ? "w-[2vw] h-[2vw] bg-white" : ""}
|
||||
${btn.size === "medium" ? "w-[3vw] h-[3vw] bg-white" : ""}
|
||||
${btn.size === "big" ? "w-[3.5vw] h-[3.5vw] bg-black" : ""}
|
||||
${skillSelected === btn.id ? "border-4 border-primary" : ""}
|
||||
${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] === 0 ? "opacity-50 cursor-not-allowed" : ""}
|
||||
`}
|
||||
onClick={() => {
|
||||
setSkillSelected(btn.id === skillSelected ? null : btn.id)
|
||||
}}
|
||||
style={{
|
||||
left: `calc(${btn.left} - var(--size-${btn.size}) / 2)`,
|
||||
top: `calc(${btn.top} - var(--size-${btn.size}) / 2)`,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={getImageSkill(avatarInfo?.SkillTrees?.[btn.id]?.["1"]?.Icon, avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0]) || ""}
|
||||
alt={btn.id.replaceAll("Point", "")}
|
||||
priority={true}
|
||||
width={124}
|
||||
height={124}
|
||||
style={{
|
||||
filter: btn.size !== "big"
|
||||
? 'brightness(0%)'
|
||||
: 'brightness(200%)'
|
||||
}}
|
||||
/>
|
||||
{btn.size === "big" && (
|
||||
<p className="text-xs md:text-base font-bold text-center rounded-full absolute bottom-[-1.4vw] left-1/2 transform -translate-x-1/2">{`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
|
||||
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
|
||||
<div className="w-2 h-6 bg-gradient-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("details")}
|
||||
</h2>
|
||||
{skillSelected && avatarInfo?.SkillTrees && avatarData && (
|
||||
<div>
|
||||
{skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? (
|
||||
<div>
|
||||
<div className="font-bold text-success">{transI18n("level")}</div>
|
||||
<div className="w-full max-w-xs">
|
||||
<input type="range"
|
||||
min={1}
|
||||
max={skillInfo?.MaxLevel || 1}
|
||||
value={avatarData?.data.skills?.[skillInfo?.PointID] || 1}
|
||||
onChange={(e) => {
|
||||
const newData = cloneDeep(avatarData)
|
||||
newData.data.skills[skillInfo?.PointID] = parseInt(e.target.value)
|
||||
setAvatar(newData)
|
||||
}}
|
||||
className="range range-success"
|
||||
step="1" />
|
||||
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
||||
{Array.from({ length: skillInfo?.MaxLevel }, (_, index) => index + 1).map((index) => (
|
||||
<span key={index}>{index}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillSelected)?.size !== "big" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1}
|
||||
className="toggle toggle-success"
|
||||
onChange={(e) => {
|
||||
handlerChangeStatusTrace(e.target.checked)
|
||||
}}
|
||||
/>
|
||||
<div className="font-bold text-success">
|
||||
{avatarData?.data.skills?.[skillInfo?.PointID] === 1 ? transI18n("active") : transI18n("inactive")}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
null
|
||||
)}
|
||||
|
||||
{((skillInfo?.PointName && skillInfo?.PointDesc) ||
|
||||
(skillInfo?.PointName && skillInfo?.StatusAddList.length > 0))
|
||||
&& (
|
||||
<div className="text-xl font-bold flex items-center gap-2 mt-2">
|
||||
{skillInfo.PointName}
|
||||
{skillInfo.StatusAddList.length > 0 && (
|
||||
<div>
|
||||
{skillInfo.StatusAddList.map((status, index) => (
|
||||
<div key={index}>
|
||||
<div className="text-xl font-bold">{getTraceBuffDisplay(status)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{skillInfo?.PointDesc && (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
skillInfo?.PointDesc || "",
|
||||
skillInfo?.ParamList || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{skillInfo?.LevelUpSkillID
|
||||
&& skillInfo?.LevelUpSkillID.length > 0
|
||||
&& dataLevelUpSkill
|
||||
&& (
|
||||
<div className="mt-2 flex flex-col gap-2">
|
||||
|
||||
{dataLevelUpSkill?.data?.map((skill, index) => (
|
||||
<div key={index}>
|
||||
|
||||
<div className="text-xl font-bold text-primary">
|
||||
{transI18n(dataLevelUpSkill.isServant ? `${skill?.Type ? "severaltalent" : "servantskill"}` : `${skill?.Type ? skill?.Type.toLowerCase() : "talent"}`)}
|
||||
{` (${transI18n(skill.Tag.toLowerCase())})`}
|
||||
</div>
|
||||
|
||||
<div className="text-lg font-bold" dangerouslySetInnerHTML={{ __html: replaceByParam(skill.Name, []) }}>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
skill.Desc || skill.SimpleDesc,
|
||||
skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.ParamList || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { createContext, PropsWithChildren, useEffect, useState } from "react";
|
||||
import { createContext, PropsWithChildren, useEffect } from "react";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
|
||||
interface ThemeContextType {
|
||||
theme?: string;
|
||||
@@ -8,7 +9,8 @@ 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 +21,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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user