UPDATE: Support skills_by_anchor_type
Some checks failed
Gitea Auto Deploy / Deploy-Container (push) Failing after 1m4s
Some checks failed
Gitea Auto Deploy / Deploy-Container (push) Failing after 1m4s
This commit is contained in:
@@ -275,5 +275,6 @@
|
||||
"detailHiddenUi": "Enabling this feature will hide the game UI.",
|
||||
"detailDisableCensorship": "Enabling this feature will disable in-game censorship.",
|
||||
"detailMultipathCharacter": "Allows changing the Path of certain characters."
|
||||
|
||||
}
|
||||
}
|
||||
10
public/data/changelog.json
Normal file
10
public/data/changelog.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"version": "3.8.5",
|
||||
"date": "11/1/2026",
|
||||
"type": "improvement",
|
||||
"items": [
|
||||
"Support skills_by_anchor_type"
|
||||
]
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client";
|
||||
|
||||
import useListAvatarStore from "@/stores/avatarStore";
|
||||
@@ -11,7 +12,7 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import useModelStore from "@/stores/modelStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { RelicStore } from "@/types";
|
||||
import { ModalConfig, RelicStore } from "@/types";
|
||||
import { toast } from "react-toastify";
|
||||
import useGlobalStore from "@/stores/globalStore";
|
||||
import { connectToPS, syncDataToPS } from "@/helper";
|
||||
@@ -91,40 +92,6 @@ export default function ActionBar() {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
|
||||
if (!isOpenCreateProfile) {
|
||||
handleCloseModal("update_profile_modal");
|
||||
return;
|
||||
}
|
||||
if (!isOpenCopy) {
|
||||
handleCloseModal("copy_profile_modal");
|
||||
return;
|
||||
}
|
||||
console.log(isOpenAvatars)
|
||||
if (!isOpenAvatars) {
|
||||
|
||||
handleCloseModal("avatars_modal");
|
||||
return;
|
||||
}
|
||||
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && isOpenCreateProfile) {
|
||||
handleCloseModal("update_profile_modal");
|
||||
}
|
||||
if (event.key === 'Escape' && isOpenCopy) {
|
||||
handleCloseModal("copy_profile_modal");
|
||||
}
|
||||
if (event.key === 'Escape' && isOpenAvatars) {
|
||||
handleCloseModal("avatars_modal");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenCopy, isOpenCreateProfile, isOpenAvatars]);
|
||||
|
||||
const actionMove = (path: string) => {
|
||||
router.push(`/${path}`)
|
||||
@@ -168,11 +135,11 @@ export default function ActionBar() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const modalConfigs = [
|
||||
const modalConfigs: ModalConfig[] = [
|
||||
{
|
||||
id: "update_profile_modal",
|
||||
title: formState === "CREATE" ? transI18n("createNewProfile") : transI18n("editProfile"),
|
||||
isOpen: isOpenCreateProfile,
|
||||
onClose: () => {
|
||||
setIsOpenCreateProfile(false)
|
||||
handleCloseModal("update_profile_modal")
|
||||
@@ -204,6 +171,7 @@ export default function ActionBar() {
|
||||
{
|
||||
id: "copy_profile_modal",
|
||||
title: transI18n("copyProfiles").toUpperCase(),
|
||||
isOpen: isOpenCopy,
|
||||
onClose: () => {
|
||||
setIsOpenCopy(false)
|
||||
handleCloseModal("copy_profile_modal")
|
||||
@@ -213,6 +181,7 @@ export default function ActionBar() {
|
||||
{
|
||||
id: "avatars_modal",
|
||||
title: transI18n("avatars").toUpperCase(),
|
||||
isOpen: isOpenAvatars,
|
||||
onClose: () => {
|
||||
setIsOpenAvatars(false)
|
||||
handleCloseModal("avatars_modal")
|
||||
@@ -221,6 +190,26 @@ export default function ActionBar() {
|
||||
}
|
||||
]
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
for (const item of modalConfigs) {
|
||||
if (!item?.isOpen) {
|
||||
handleCloseModal(item?.id || "")
|
||||
}
|
||||
}
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
for (const item of modalConfigs) {
|
||||
handleCloseModal(item?.id || "")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenCopy, isOpenCreateProfile, isOpenAvatars]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full px-4 pb-4 bg-base-200">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client"
|
||||
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
@@ -69,7 +70,6 @@ export default function AvatarInfo() {
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOpenLightcone]);
|
||||
|
||||
return (
|
||||
|
||||
94
src/components/changelog/index.tsx
Normal file
94
src/components/changelog/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { Check, AlertCircle, Sparkles, Bug, Zap, Package, Calendar } from 'lucide-react';
|
||||
|
||||
export default function ChangelogBar() {
|
||||
const { changelog } = useLocaleStore()
|
||||
|
||||
const getIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'feature':
|
||||
return <Sparkles className="w-3 h-3 md:w-4 md:h-4" />;
|
||||
case 'fix':
|
||||
return <Bug className="w-3 h-3 md:w-4 md:h-4" />;
|
||||
case 'improvement':
|
||||
return <Zap className="w-3 h-3 md:w-4 md:h-4" />;
|
||||
default:
|
||||
return <Package className="w-3 h-3 md:w-4 md:h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getBadgeClass = (type: string) => {
|
||||
switch (type) {
|
||||
case 'feature':
|
||||
return 'badge-success';
|
||||
case 'fix':
|
||||
return 'badge-error';
|
||||
case 'improvement':
|
||||
return 'badge-warning';
|
||||
default:
|
||||
return 'badge-info';
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case 'feature':
|
||||
return 'Feature';
|
||||
case 'fix':
|
||||
return 'Fix';
|
||||
case 'improvement':
|
||||
return 'Improvement';
|
||||
default:
|
||||
return 'Update';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="md:p-4">
|
||||
{/* Alert */}
|
||||
<div className="alert alert-info mb-4 md:p-2 p-1">
|
||||
<AlertCircle className="w-6 h-6" />
|
||||
<div>
|
||||
<h3 className="font-bold text-sm">If you have any suggestions or problems, please contact me! @kain0304</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{changelog.map((change, index) => (
|
||||
<div key={index} className="bg-base-100 shadow-sm">
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Version Header */}
|
||||
<div className="flex flex-row items-center gap-2 md:gap-3">
|
||||
<div className={`badge ${getBadgeClass(change.type)} gap-1 md:gap-2 p-1`}>
|
||||
{getIcon(change.type)}
|
||||
{getTypeLabel(change.type)}
|
||||
</div>
|
||||
<h2 className="card-title text-sm md:text-lg">
|
||||
Version {change.version}
|
||||
</h2>
|
||||
<div className="flex items-center gap-1 md:gap-2 text-base-content/60 ml-auto">
|
||||
<Calendar className="w-3 h-3 md:w-4 md:h-4" />
|
||||
<span className="text-sm">{change.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Changes List */}
|
||||
<ul className="space-y-3">
|
||||
{change.items.map((item, itemIndex) => (
|
||||
<li key={itemIndex} className="flex items-start gap-3">
|
||||
<div className="mt-1">
|
||||
<Check className="w-5 h-5 text-success" />
|
||||
</div>
|
||||
<span className="text-base-content">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import {
|
||||
useFetchASData,
|
||||
useFetchAvatarData,
|
||||
useFetchChangelog,
|
||||
useFetchConfigData,
|
||||
useFetchLightconeData,
|
||||
useFetchMOCData,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
useFetchPEAKData,
|
||||
useFetchPFData,
|
||||
useFetchRelicData
|
||||
} from "@/hooks";
|
||||
} from "@/lib/hooks";
|
||||
|
||||
export default function ClientDataFetcher() {
|
||||
useFetchConfigData();
|
||||
@@ -22,6 +23,7 @@ export default function ClientDataFetcher() {
|
||||
useFetchMOCData();
|
||||
useFetchASData();
|
||||
useFetchPEAKData();
|
||||
useFetchChangelog();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function ExtraSettingBar() {
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-bold flex items-center gap-2">
|
||||
{transI18n("theoryCraft")}
|
||||
<div className="tooltip tooltip-info" data-tip={transI18n("detailTheoryCraft")}>
|
||||
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailTheoryCraft")}>
|
||||
<Info className="text-primary" size={20} />
|
||||
</div>
|
||||
</h3>
|
||||
@@ -78,13 +78,11 @@ export default function ExtraSettingBar() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/*MULTIPATH CHAR */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-bold flex items-center gap-2">
|
||||
{transI18n("multipathCharacter")}
|
||||
<div className="tooltip tooltip-info" data-tip={transI18n("detailMultipathCharacter")}>
|
||||
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailMultipathCharacter")}>
|
||||
<Info className="text-primary" size={20} />
|
||||
</div>
|
||||
</h3>
|
||||
@@ -157,7 +155,7 @@ export default function ExtraSettingBar() {
|
||||
<label className="flex items-center gap-3">
|
||||
<Swords className="text-error" size={20} />
|
||||
<span className="label-text font-semibold">{transI18n("anomalyArbitration")}</span>
|
||||
<div className="tooltip tooltip-info" data-tip={transI18n("detailChallengePeak")}>
|
||||
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailChallengePeak")}>
|
||||
<Info className="text-primary" size={20} />
|
||||
</div>
|
||||
<select
|
||||
@@ -187,7 +185,7 @@ export default function ExtraSettingBar() {
|
||||
<label className="flex items-center gap-3">
|
||||
<SkipForward className="text-warning" size={20} />
|
||||
<span className="label-text font-semibold">{transI18n("skipNode")}</span>
|
||||
<div className="tooltip tooltip-info" data-tip={transI18n("detailSkipNode")}>
|
||||
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailSkipNode")}>
|
||||
<Info className="text-primary" size={20} />
|
||||
</div>
|
||||
<select
|
||||
@@ -241,7 +239,7 @@ export default function ExtraSettingBar() {
|
||||
}
|
||||
/>
|
||||
<span className="label-text font-semibold">{transI18n("hideUI")}</span>
|
||||
<div className="tooltip tooltip-info" data-tip={transI18n("detailHiddenUi")}>
|
||||
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailHiddenUi")}>
|
||||
<Info className="text-primary" size={20} />
|
||||
</div>
|
||||
</label>
|
||||
@@ -269,7 +267,7 @@ export default function ExtraSettingBar() {
|
||||
}
|
||||
/>
|
||||
<span className="label-text font-semibold">{transI18n("disableCensorship")}</span>
|
||||
<div className="tooltip tooltip-info" data-tip={transI18n("detailDisableCensorship")}>
|
||||
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailDisableCensorship")}>
|
||||
<Info className="text-primary" size={20} />
|
||||
</div>
|
||||
</label>
|
||||
@@ -277,8 +275,6 @@ export default function ExtraSettingBar() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10">
|
||||
|
||||
<aside>
|
||||
<p>Copyright © {new Date().getFullYear()} - Kain (Powered by Nextjs & DaisyUi)</p>
|
||||
</aside>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client"
|
||||
import { downloadJson } from "@/helper";
|
||||
import { converterToFreeSRJson } from "@/helper/converterToFreeSRJson";
|
||||
@@ -20,6 +21,9 @@ import MonsterBar from "../monsterBar";
|
||||
import Image from "next/image";
|
||||
import ConnectBar from "../connectBar";
|
||||
import ExtraSettingBar from "../extraSettingBar";
|
||||
import ChangelogBar from "../changelog";
|
||||
import { ModalConfig } from "@/types";
|
||||
import { ScrollText } from "lucide-react";
|
||||
|
||||
const themes = [
|
||||
{ label: "Winter" },
|
||||
@@ -58,7 +62,9 @@ export default function Header() {
|
||||
setIsOpenConnect,
|
||||
isOpenConnect,
|
||||
setIsOpenExtra,
|
||||
isOpenExtra
|
||||
isOpenExtra,
|
||||
setIsChangelog,
|
||||
isChangelog
|
||||
} = useModelStore()
|
||||
|
||||
const [importModal, setImportModal] = useState("enka");
|
||||
@@ -111,76 +117,7 @@ export default function Header() {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
if (!isOpenImport) {
|
||||
handleCloseModal("import_modal");
|
||||
return;
|
||||
}
|
||||
if (!isOpenMonster) {
|
||||
handleCloseModal("monster_modal");
|
||||
return;
|
||||
}
|
||||
if (!isOpenConnect) {
|
||||
handleCloseModal("connect_modal");
|
||||
return;
|
||||
}
|
||||
if (!isOpenExtra) {
|
||||
handleCloseModal("extra_modal");
|
||||
return;
|
||||
}
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
handleCloseModal("connect_modal");
|
||||
handleCloseModal("import_modal");
|
||||
handleCloseModal("monster_modal");
|
||||
handleCloseModal("extra_modal");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenImport, isOpenMonster, isOpenConnect, isOpenExtra]);
|
||||
|
||||
const handleImportDatabase = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) {
|
||||
toast.error(transI18n("pleaseSelectAFile"))
|
||||
|
||||
return
|
||||
}
|
||||
if (!file.name.endsWith(".json") || file.type !== "application/json") {
|
||||
toast.error(transI18n("fileMustBeAValidJsonFile"))
|
||||
return
|
||||
}
|
||||
|
||||
if (file) {
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.target?.result as string);
|
||||
const parsed = micsSchema.parse(data)
|
||||
setAvatars(parsed.avatars)
|
||||
setBattleType(parsed.battle_type)
|
||||
setMocConfig(parsed.moc_config)
|
||||
setPfConfig(parsed.pf_config)
|
||||
setAsConfig(parsed.as_config)
|
||||
setCeConfig(parsed.ce_config)
|
||||
setPeakConfig(parsed.peak_config)
|
||||
toast.success(transI18n("importDatabaseSuccess"))
|
||||
} catch {
|
||||
toast.error(transI18n("fileMustBeAValidJsonFile"))
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
const modalConfigs = [
|
||||
const modalConfigs: ModalConfig[] = [
|
||||
{
|
||||
id: "connect_modal",
|
||||
title: transI18n("psConnection"),
|
||||
@@ -225,9 +162,75 @@ export default function Header() {
|
||||
handleCloseModal("extra_modal")
|
||||
},
|
||||
content: <ExtraSettingBar />
|
||||
},
|
||||
{
|
||||
id: "changelog_modal",
|
||||
title: "Changelog",
|
||||
isOpen: isChangelog,
|
||||
onClose: () => {
|
||||
setIsChangelog(false)
|
||||
handleCloseModal("changelog_modal")
|
||||
},
|
||||
content: <ChangelogBar />
|
||||
}
|
||||
]
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
for (const item of modalConfigs) {
|
||||
if (!item?.isOpen) {
|
||||
handleCloseModal(item?.id || "")
|
||||
} else {
|
||||
handleShow(item?.id || "")
|
||||
}
|
||||
}
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
for (const item of modalConfigs) {
|
||||
handleCloseModal(item?.id || "")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenImport, isOpenMonster, isOpenConnect, isOpenExtra, isChangelog]);
|
||||
|
||||
const handleImportDatabase = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) {
|
||||
toast.error(transI18n("pleaseSelectAFile"))
|
||||
return
|
||||
}
|
||||
if (!file.name.endsWith(".json") || file.type !== "application/json") {
|
||||
toast.error(transI18n("fileMustBeAValidJsonFile"))
|
||||
return
|
||||
}
|
||||
|
||||
if (file) {
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.target?.result as string);
|
||||
const parsed = micsSchema.parse(data)
|
||||
setAvatars(parsed.avatars)
|
||||
setBattleType(parsed.battle_type)
|
||||
setMocConfig(parsed.moc_config)
|
||||
setPfConfig(parsed.pf_config)
|
||||
setAsConfig(parsed.as_config)
|
||||
setCeConfig(parsed.ce_config)
|
||||
setPeakConfig(parsed.peak_config)
|
||||
toast.success(transI18n("importDatabaseSuccess"))
|
||||
} catch {
|
||||
toast.error(transI18n("fileMustBeAValidJsonFile"))
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="navbar bg-base-100 shadow-md sticky top-0 z-50 px-3 py-1">
|
||||
@@ -250,7 +253,6 @@ export default function Header() {
|
||||
<li><a onClick={() => {
|
||||
setImportModal("freesr")
|
||||
setIsOpenImport(true)
|
||||
handleShow("import_modal")
|
||||
}}>{transI18n("freeSr")}</a></li>
|
||||
<li>
|
||||
<>
|
||||
@@ -271,7 +273,6 @@ export default function Header() {
|
||||
<li><a onClick={() => {
|
||||
setImportModal("enka")
|
||||
setIsOpenImport(true)
|
||||
handleShow("import_modal")
|
||||
}}>{transI18n("enka")}</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -308,7 +309,6 @@ export default function Header() {
|
||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
onClick={() => {
|
||||
setIsOpenConnect(true)
|
||||
handleShow("connect_modal")
|
||||
}}
|
||||
>
|
||||
{transI18n("connectSetting")}
|
||||
@@ -318,7 +318,6 @@ export default function Header() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpenMonster(true)
|
||||
handleShow("monster_modal")
|
||||
}}
|
||||
className="disabled px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
>
|
||||
@@ -331,7 +330,6 @@ export default function Header() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpenExtra(true)
|
||||
handleShow("extra_modal")
|
||||
}}
|
||||
className="disabled px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
>
|
||||
@@ -346,9 +344,9 @@ 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={50} height={50} />
|
||||
<Image 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-xl font-bold">
|
||||
<h1 className="text-lg xl:text-xl font-bold leading-tight">
|
||||
<span className="text-emerald-500">Firefly Sr</span>
|
||||
<span className="bg-clip-text text-transparent bg-linear-to-r from-emerald-400 via-orange-500 to-red-500">
|
||||
Tools
|
||||
@@ -371,7 +369,6 @@ export default function Header() {
|
||||
<li><a onClick={() => {
|
||||
setImportModal("freesr")
|
||||
setIsOpenImport(true)
|
||||
handleShow("import_modal")
|
||||
}}>{transI18n("freeSr")}</a></li>
|
||||
<li>
|
||||
<>
|
||||
@@ -392,7 +389,6 @@ export default function Header() {
|
||||
<li><a onClick={() => {
|
||||
setImportModal("enka")
|
||||
setIsOpenImport(true)
|
||||
handleShow("import_modal")
|
||||
}}>{transI18n("enka")}</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -429,7 +425,6 @@ export default function Header() {
|
||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
onClick={() => {
|
||||
setIsOpenConnect(true)
|
||||
handleShow("connect_modal")
|
||||
}}
|
||||
>
|
||||
{transI18n("connectSetting")}
|
||||
@@ -439,7 +434,6 @@ export default function Header() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpenMonster(true)
|
||||
handleShow("monster_modal")
|
||||
}}
|
||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
>
|
||||
@@ -451,7 +445,6 @@ export default function Header() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpenExtra(true)
|
||||
handleShow("extra_modal")
|
||||
}}
|
||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
>
|
||||
@@ -462,18 +455,27 @@ export default function Header() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Right side items */}
|
||||
<div className="navbar-end gap-2">
|
||||
<div className="px-2">
|
||||
<div className="navbar-end gap-2 pr-1 ">
|
||||
<div data-tip="Connection Status" className="tooltip tooltip-bottom">
|
||||
<div className="flex items-center space-x-2 p-1.5 rounded-full shadow-md">
|
||||
<div className={`hidden lg:block text-sm italic ${isConnectPS ? 'text-green-500' : 'text-red-500'}`}>
|
||||
<div className={`hidden xl:block text-sm italic ${isConnectPS ? 'text-green-500' : 'text-red-500'}`}>
|
||||
{isConnectPS ? transI18n("connected") : transI18n("unconnected")}
|
||||
</div>
|
||||
<div className={`w-3 h-3 rounded-full ${isConnectPS ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tip="View Logs"
|
||||
className="btn btn-ghost btn-sm btn-circle items-center justify-center w-fit tooltip tooltip-bottom"
|
||||
onClick={() => {
|
||||
setIsChangelog(true)
|
||||
}}
|
||||
>
|
||||
<ScrollText className="w-5 h-5 text-warning" />
|
||||
</div>
|
||||
|
||||
{/* Language selector - REFINED */}
|
||||
<div className="dropdown dropdown-end">
|
||||
<div className="flex items-center gap-1 border border-base-300 rounded text-sm px-1.5 py-0.5 hover:bg-base-200 cursor-pointer transition-all duration-200">
|
||||
@@ -493,7 +495,7 @@ export default function Header() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div title="Change Theme" className="dropdown dropdown-end">
|
||||
<div className="dropdown dropdown-end">
|
||||
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm hover:bg-base-200 transition-all duration-200 px-2">
|
||||
<svg
|
||||
width={16}
|
||||
@@ -557,10 +559,11 @@ export default function Header() {
|
||||
|
||||
{/* GitHub Link */}
|
||||
<Link
|
||||
className='hidden sm:flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white transition-all duration-200 items-center justify-center'
|
||||
className='flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white transition-all duration-200 items-center justify-center tooltip tooltip-bottom'
|
||||
href={"https://github.com/AzenKain/Firefly-Srtools"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-tip="Github"
|
||||
>
|
||||
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
|
||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
||||
@@ -568,7 +571,7 @@ export default function Header() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{modalConfigs.map(({ id, title, onClose, content }) => (
|
||||
{modalConfigs?.map(({ id, title, onClose, content }) => (
|
||||
<dialog key={id} id={id} className="modal">
|
||||
<div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<div className="sticky top-0 z-10">
|
||||
|
||||
@@ -270,6 +270,14 @@ export default function QuickView() {
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
ElationAdd: {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Elation Boost",
|
||||
icon: "/icon/IconJoy.webp",
|
||||
unit: "%",
|
||||
round: 1
|
||||
}
|
||||
}
|
||||
|
||||
if (avatarProfile?.lightcone && mapLightconeInfo[avatarProfile?.lightcone?.item_id]) {
|
||||
|
||||
@@ -238,7 +238,7 @@ export default function RelicMaker() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div>
|
||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
|
||||
{transI18n("relicMaker")}
|
||||
@@ -357,7 +357,7 @@ export default function RelicMaker() {
|
||||
{/* Right Panel - Sub Stats */}
|
||||
<div className="space-y-4">
|
||||
{/* Total Roll */}
|
||||
<div className="bg-base-100 rounded-xl p-4 border border-slate-700 z-[1]">
|
||||
<div className="bg-base-100 rounded-xl p-4 border border-slate-700 z-1">
|
||||
<h3 className="text-lg font-bold mb-4">{transI18n("totalRoll")} {listSelectedSubStats.reduce((a, b) => a + b.rollCount, 0)}</h3>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import RelicMaker from "../relicBar";
|
||||
@@ -12,6 +13,7 @@ import { replaceByParam } from '@/helper';
|
||||
import useRelicMakerStore from '@/stores/relicMakerStore';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import QuickView from "../quickView";
|
||||
import { ModalConfig } from "@/types";
|
||||
|
||||
export default function RelicsInfo() {
|
||||
const { avatars, setAvatars } = useUserDataStore()
|
||||
@@ -53,30 +55,6 @@ export default function RelicsInfo() {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
if (!isOpenRelic) {
|
||||
handleCloseModal("action_detail_modal");
|
||||
return;
|
||||
};
|
||||
if (!isOpenQuickView) {
|
||||
handleCloseModal("quick_view_modal");
|
||||
return;
|
||||
};
|
||||
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && isOpenRelic) {
|
||||
handleCloseModal("action_detail_modal");
|
||||
}
|
||||
if (event.key === 'Escape' && isOpenQuickView) {
|
||||
handleCloseModal("quick_view_modal");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOpenRelic]);
|
||||
|
||||
const getRelic = useCallback((slot: string) => {
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
@@ -150,10 +128,11 @@ export default function RelicsInfo() {
|
||||
return listEffects;
|
||||
}, [avatars, avatarSelected]);
|
||||
|
||||
const modalConfigs = [
|
||||
const modalConfigs: ModalConfig[] = [
|
||||
{
|
||||
id: "action_detail_modal",
|
||||
title: null, // không có title
|
||||
title: "",
|
||||
isOpen: isOpenRelic,
|
||||
onClose: () => {
|
||||
setIsOpenRelic(false)
|
||||
handleCloseModal("action_detail_modal")
|
||||
@@ -163,6 +142,7 @@ export default function RelicsInfo() {
|
||||
{
|
||||
id: "quick_view_modal",
|
||||
title: transI18n("quickView").toUpperCase(),
|
||||
isOpen: isOpenQuickView,
|
||||
onClose: () => {
|
||||
setIsOpenQuickView(false)
|
||||
handleCloseModal("quick_view_modal")
|
||||
@@ -171,6 +151,24 @@ export default function RelicsInfo() {
|
||||
}
|
||||
]
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
for (const item of modalConfigs) {
|
||||
if (!item?.isOpen) {
|
||||
handleCloseModal(item?.id || "")
|
||||
}
|
||||
}
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
for (const item of modalConfigs) {
|
||||
handleCloseModal(item?.id || "")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenRelic]);
|
||||
|
||||
return (
|
||||
<div className="max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import useMazeStore from "@/stores/mazeStore";
|
||||
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, RelicJson } from "@/types";
|
||||
|
||||
|
||||
@@ -10,6 +11,7 @@ export function converterToFreeSRJson(
|
||||
ce_config: CEConfigStore,
|
||||
peak_config: PEAKConfigStore,
|
||||
): FreeSRJson {
|
||||
const { SkillTree } = useMazeStore.getState()
|
||||
const lightcones: LightconeJson[] = []
|
||||
const relics: RelicJson[] = []
|
||||
let battleJson: BattleConfigJson
|
||||
@@ -78,11 +80,22 @@ export function converterToFreeSRJson(
|
||||
const avatarsJson: { [key: string]: AvatarJson } = {}
|
||||
let internalUidLightcone = 0
|
||||
let internalUidRelic = 0
|
||||
|
||||
Object.entries(avatars).forEach(([avatarId, avatar]) => {
|
||||
const skillsByAnchorType: Record<string, number> = {}
|
||||
for (const [skillId, level] of Object.entries(avatar.data.skills)) {
|
||||
if (SkillTree[skillId]) {
|
||||
skillsByAnchorType[SkillTree[skillId].index_slot] = level > SkillTree[skillId].max_level ? SkillTree[skillId].max_level : level
|
||||
}
|
||||
}
|
||||
avatarsJson[avatarId] = {
|
||||
owner_uid: Number(avatar.owner_uid || 0),
|
||||
avatar_id: Number(avatar.avatar_id || 0),
|
||||
data: avatar.data,
|
||||
data: {
|
||||
rank: Number(avatar.data.rank || 0),
|
||||
skills: avatar.data.skills,
|
||||
skills_by_anchor_type: Object.keys(skillsByAnchorType).length > 0 ? skillsByAnchorType : undefined,
|
||||
},
|
||||
level: Number(avatar.level || 0),
|
||||
promotion: Number(avatar.promotion || 0),
|
||||
techniques: avatar.techniques,
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
export * from "./useFetchConfigData";
|
||||
export * from "./useChangeTheme";
|
||||
export * from "./useFetchAvatarData";
|
||||
export * from "./useFetchLightconeData";
|
||||
export * from "./useFetchRelicData";
|
||||
export * from "./useFetchMonsterData";
|
||||
export * from "./useFetchPFData";
|
||||
export * from "./useFetchMOCData";
|
||||
export * from "./useFetchASData";
|
||||
export * from "./useFetchPEAKData";
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { AffixDetail, ASDetail, CharacterDetail, ConfigMaze, FreeSRJson, LightConeDetail, MocDetail, MonsterDetail, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
|
||||
import { AffixDetail, ASDetail, ChangelogItemType, CharacterDetail, ConfigMaze, FreeSRJson, LightConeDetail, MocDetail, MonsterDetail, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
|
||||
import axios from 'axios';
|
||||
import { psResponseSchema } from "@/zod";
|
||||
import { ExtraData } from "@/types";
|
||||
@@ -151,6 +151,16 @@ export async function fetchMonstersApi(locale: string): Promise<Record<string, M
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchChangelog(): Promise<ChangelogItemType[] | null> {
|
||||
try {
|
||||
const res = await axios.get<ChangelogItemType[]>(`/data/changelog.json`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch monster:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function SendDataToServer(
|
||||
username: string,
|
||||
password: string,
|
||||
|
||||
10
src/lib/hooks/index.ts
Normal file
10
src/lib/hooks/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from "./useFetchConfigData";
|
||||
export * from "./useFetchAvatarData";
|
||||
export * from "./useFetchLightconeData";
|
||||
export * from "./useFetchRelicData";
|
||||
export * from "./useFetchMonsterData";
|
||||
export * from "./useFetchPFData";
|
||||
export * from "./useFetchMOCData";
|
||||
export * from "./useFetchASData";
|
||||
export * from "./useFetchPEAKData";
|
||||
export * from "./useFetchChangelog";
|
||||
31
src/lib/hooks/useFetchChangelog.ts
Normal file
31
src/lib/hooks/useFetchChangelog.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchChangelog } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import useModelStore from '@/stores/modelStore'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
|
||||
export const useFetchChangelog = () => {
|
||||
const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore()
|
||||
const { setIsChangelog } = useModelStore()
|
||||
const { data: dataChangelog, error: errorChangelog } = useQuery({
|
||||
queryKey: ['changelog'],
|
||||
queryFn: fetchChangelog,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (dataChangelog && !errorChangelog) {
|
||||
setChangelog(dataChangelog)
|
||||
if (dataChangelog?.[0] && dataChangelog[0].version != currentVersion) {
|
||||
setIsChangelog(true)
|
||||
setCurrentVersion(dataChangelog[0].version)
|
||||
}
|
||||
} else if (errorChangelog) {
|
||||
toast.error("Failed to load changelog data")
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataChangelog, errorChangelog, setChangelog, setCurrentVersion, setIsChangelog])
|
||||
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChangelogItemType } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
|
||||
@@ -5,6 +6,10 @@ import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
interface LocaleState {
|
||||
locale: string;
|
||||
theme: string;
|
||||
currentVersion: string
|
||||
changelog: ChangelogItemType[]
|
||||
setCurrentVersion: (currentVersion: string) => void;
|
||||
setChangelog: (newChangelog: ChangelogItemType[]) => void;
|
||||
setTheme: (newTheme: string) => void;
|
||||
setLocale: (newLocale: string) => void;
|
||||
}
|
||||
@@ -14,6 +19,10 @@ const useLocaleStore = create<LocaleState>()(
|
||||
(set) => ({
|
||||
locale: "en",
|
||||
theme: "night",
|
||||
currentVersion: "",
|
||||
changelog: [],
|
||||
setCurrentVersion: (currentVersion: string) => set({ currentVersion }),
|
||||
setChangelog: (newChangelog: ChangelogItemType[]) => set({ changelog: newChangelog }),
|
||||
setTheme: (newTheme: string) => set({ theme: newTheme }),
|
||||
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ASConfigMaze, AvatarConfigMaze, ConfigMaze, MOCConfigMaze, PFConfigMaze, StageConfigMaze } from '@/types';
|
||||
import { ASConfigMaze, AvatarConfigMaze, ConfigMaze, MOCConfigMaze, PFConfigMaze, SkillConfigMaze, StageConfigMaze } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface MazeState {
|
||||
@@ -7,11 +7,13 @@ interface MazeState {
|
||||
AS: Record<string, ASConfigMaze>;
|
||||
PF: Record<string, PFConfigMaze>;
|
||||
Stage: Record<string, StageConfigMaze>;
|
||||
SkillTree: Record<string, SkillConfigMaze>;
|
||||
setTechnique: (newTechnique: Record<string, AvatarConfigMaze>) => void;
|
||||
setMOC: (newMOC: Record<string, MOCConfigMaze>) => void;
|
||||
setAS: (newAS: Record<string, ASConfigMaze>) => void;
|
||||
setPF: (newPF: Record<string, PFConfigMaze>) => void;
|
||||
setStage: (newStage: Record<string, StageConfigMaze>) => void;
|
||||
setSkillTree: (newSkillTree: Record<string, SkillConfigMaze>) => void;
|
||||
setAllMazeData: (newData: ConfigMaze) => void;
|
||||
}
|
||||
|
||||
@@ -21,12 +23,21 @@ const useMazeStore = create<MazeState>((set) => ({
|
||||
AS: {},
|
||||
PF: {},
|
||||
Stage: {},
|
||||
SkillTree: {},
|
||||
setSkillTree: (newSkillTree: Record<string, SkillConfigMaze>) => set({ SkillTree: newSkillTree }),
|
||||
setTechnique: (newTechnique: Record<string, AvatarConfigMaze>) => set({ Technique: newTechnique }),
|
||||
setMOC: (newMOC: Record<string, MOCConfigMaze>) => set({ MOC: newMOC }),
|
||||
setAS: (newAS: Record<string, ASConfigMaze>) => set({ AS: newAS }),
|
||||
setPF: (newPF: Record<string, PFConfigMaze>) => set({ PF: newPF }),
|
||||
setStage: (newStage: Record<string, StageConfigMaze>) => set({ Stage: newStage }),
|
||||
setAllMazeData: (newData: ConfigMaze) => set({ Technique: newData.Avatar, MOC: newData.MOC, AS: newData.AS, PF: newData.PF, Stage: newData.Stage }),
|
||||
setAllMazeData: (newData: ConfigMaze) => set({
|
||||
Technique: newData.Avatar,
|
||||
MOC: newData.MOC,
|
||||
AS: newData.AS,
|
||||
PF: newData.PF,
|
||||
Stage: newData.Stage,
|
||||
SkillTree: newData.Skill
|
||||
}),
|
||||
}));
|
||||
|
||||
export default useMazeStore;
|
||||
@@ -13,6 +13,7 @@ interface ModelState {
|
||||
isOpenAvatars: boolean;
|
||||
isOpenQuickView: boolean;
|
||||
isOpenExtra: boolean;
|
||||
isChangelog: boolean;
|
||||
setIsOpenExtra: (newIsOpenExtra: boolean) => void;
|
||||
setIsOpenQuickView: (newIsOpenQuickView: boolean) => void;
|
||||
setIsOpenAvatars: (newIsOpenAvatars: boolean) => void;
|
||||
@@ -24,6 +25,7 @@ interface ModelState {
|
||||
setIsOpenCreateProfile: (newIsOpenCreateProfile: boolean) => void;
|
||||
setIsOpenImport: (newIsOpenImport: boolean) => void;
|
||||
setIsOpenCopy: (newIsOpenCopy: boolean) => void;
|
||||
setIsChangelog: (newChangelog: boolean) => void;
|
||||
}
|
||||
|
||||
const useModelStore = create<ModelState>((set) => ({
|
||||
@@ -38,6 +40,7 @@ const useModelStore = create<ModelState>((set) => ({
|
||||
isOpenAvatars: false,
|
||||
isOpenQuickView: false,
|
||||
isOpenExtra: false,
|
||||
isChangelog: false,
|
||||
setIsOpenExtra: (newIsOpenExtra: boolean) => set({ isOpenExtra: newIsOpenExtra }),
|
||||
setIsOpenQuickView: (newIsOpenQuickView: boolean) => set({ isOpenQuickView: newIsOpenQuickView }),
|
||||
setIsOpenAvatars: (newIsOpenAvatars: boolean) => set({ isOpenAvatars: newIsOpenAvatars }),
|
||||
@@ -49,6 +52,7 @@ const useModelStore = create<ModelState>((set) => ({
|
||||
setIsOpenCreateProfile: (newIsOpenCreateProfile: boolean) => set({ isOpenCreateProfile: newIsOpenCreateProfile }),
|
||||
setIsOpenImport: (newIsOpenImport: boolean) => set({ isOpenImport: newIsOpenImport }),
|
||||
setIsOpenCopy: (newIsOpenCopy: boolean) => set({ isOpenCopy: newIsOpenCopy }),
|
||||
setIsChangelog: (newChangelog: boolean) => set({ isChangelog: newChangelog }),
|
||||
}));
|
||||
|
||||
export default useModelStore;
|
||||
7
src/types/changelog.ts
Normal file
7
src/types/changelog.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
export interface ChangelogItemType {
|
||||
version: string,
|
||||
date: string,
|
||||
type: string,
|
||||
items: string[]
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export type MOCConfigMaze = {
|
||||
export type AvatarConfigMaze = {
|
||||
maze_buff: number[];
|
||||
}
|
||||
|
||||
export type StageConfigMaze = {
|
||||
stage_id: number;
|
||||
stage_type: string;
|
||||
@@ -24,11 +25,18 @@ export type StageConfigMaze = {
|
||||
monster_list: Array<Record<string, number>>;
|
||||
};
|
||||
|
||||
export type SkillConfigMaze = {
|
||||
max_level: number;
|
||||
index_slot: number;
|
||||
}
|
||||
|
||||
export type ConfigMaze = {
|
||||
Avatar: Record<string, AvatarConfigMaze>;
|
||||
MOC: Record<string, MOCConfigMaze>;
|
||||
AS: Record<string, ASConfigMaze>;
|
||||
PF: Record<string, PFConfigMaze>;
|
||||
Stage: Record<string, StageConfigMaze>;
|
||||
Skill: Record<string, SkillConfigMaze>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -22,3 +22,5 @@ export * from "./monsterDetail"
|
||||
export * from "./extraData"
|
||||
export * from "./showcase"
|
||||
export * from "./srtools"
|
||||
export * from "./changelog"
|
||||
export * from "./modelConfig"
|
||||
7
src/types/modelConfig.ts
Normal file
7
src/types/modelConfig.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type ModalConfig = {
|
||||
id: string
|
||||
title: string
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
content: React.ReactNode
|
||||
}
|
||||
@@ -27,6 +27,7 @@ export interface LightconeJson {
|
||||
export interface AvatarData {
|
||||
rank: number,
|
||||
skills: Record<string, number>
|
||||
skills_by_anchor_type?: Record<string,number>
|
||||
}
|
||||
|
||||
export interface AvatarJson {
|
||||
|
||||
@@ -27,10 +27,16 @@ export const stageConfigMazeSchema = z.object({
|
||||
monster_list: z.array(z.record(z.string(), z.number()))
|
||||
});
|
||||
|
||||
export const skillConfigMazeSchema = z.object({
|
||||
max_level: z.number(),
|
||||
index_slot: z.number()
|
||||
});
|
||||
|
||||
export const configMazeSchema = z.object({
|
||||
Avatar: z.record(z.string(), avatarConfigMazeSchema),
|
||||
MOC: z.record(z.string(), mocConfigMazeSchema),
|
||||
AS: z.record(z.string(), asConfigMazeSchema),
|
||||
PF: z.record(z.string(), pfConfigMazeSchema),
|
||||
Stage: z.record(z.string(), stageConfigMazeSchema)
|
||||
Stage: z.record(z.string(), stageConfigMazeSchema),
|
||||
Skill: z.record(z.string(), skillConfigMazeSchema)
|
||||
});
|
||||
|
||||
@@ -28,7 +28,8 @@ export const lightconeJsonSchema = z.object({
|
||||
|
||||
export const avatarDataSchema = z.object({
|
||||
rank: z.number(),
|
||||
skills: z.record(z.string(), z.number())
|
||||
skills: z.record(z.string(), z.number()),
|
||||
skills_by_anchor_type: z.record(z.string(), z.number()).optional()
|
||||
});
|
||||
|
||||
export const avatarJsonSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user