UPDATE: Update UI
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 50s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 50s
This commit is contained in:
@@ -15,8 +15,7 @@ The application is deployed and available at the following URLs:
|
||||
|
||||
| Role | URL |
|
||||
| :--- | :--- |
|
||||
| **Main Site** | [srtools.kain.id.vn](https://srtools.kain.id.vn) |
|
||||
| **Backup / Vercel Mirror** | [firefly-srtools.vercel.app](https://firefly-srtools.vercel.app) |
|
||||
| **Main Site** | [srtools.punklorde.org](https://srtools.punklorde.org) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"TabTitle": {
|
||||
"title": "Firefly Tools",
|
||||
"description": "Firefly tools by Kain"
|
||||
"description": "Firefly tools by Firefly Shelter"
|
||||
},
|
||||
"DataPage": {
|
||||
"skillType": "技能类型",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"TabTitle": {
|
||||
"title": "Firefly Tools",
|
||||
"description": "Firefly tools by Kain"
|
||||
"description": "Firefly tools by Firefly Shelter"
|
||||
},
|
||||
"DataPage": {
|
||||
"skillType": "Skill Type",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"TabTitle": {
|
||||
"title": "Firefly Tools",
|
||||
"description": "Firefly tools by Kain"
|
||||
"description": "Firefly tools by Firefly Shelter"
|
||||
},
|
||||
"DataPage": {
|
||||
"skillType": "スキルタイプ",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"TabTitle": {
|
||||
"title": "Firefly Tools",
|
||||
"description": "Firefly tools by Kain"
|
||||
"description": "Firefly tools by Firefly Shelter"
|
||||
},
|
||||
"DataPage": {
|
||||
"skillType": "스킬 유형",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"TabTitle": {
|
||||
"title": "Firefly Tools",
|
||||
"description": "Firefly tools by Kain"
|
||||
"description": "Firefly tools by Firefly Shelter"
|
||||
},
|
||||
"DataPage": {
|
||||
"skillType": "Loại kỹ năng",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"TabTitle": {
|
||||
"title": "Firefly Tools",
|
||||
"description": "Firefly tools by Kain"
|
||||
"description": "Firefly tools by Firefly Shelter"
|
||||
},
|
||||
"DataPage": {
|
||||
"skillType": "技能类型",
|
||||
|
||||
@@ -25,7 +25,7 @@ const geistMono = Geist_Mono({
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Firefly SrTools",
|
||||
description: "SrTools by Kain",
|
||||
description: "SrTools by Firefly Shelter",
|
||||
icons: {
|
||||
icon: "/ff-srtool.png",
|
||||
shortcut: "/ff-srtool.ico",
|
||||
@@ -33,12 +33,12 @@ export const metadata: Metadata = {
|
||||
},
|
||||
openGraph: {
|
||||
title: "Firefly SrTools",
|
||||
description: "SrTools by Kain",
|
||||
url: "https://srtools.kain.id.vn",
|
||||
description: "SrTools by Firefly Shelter",
|
||||
url: "https://srtools.punklorde.org",
|
||||
siteName: "Firefly SrTools",
|
||||
images: [
|
||||
{
|
||||
url: "https://srtools.kain.id.vn/ff-srtool.png",
|
||||
url: "https://srtools.punklorde.org/ff-srtool.png",
|
||||
width: 312,
|
||||
height: 312,
|
||||
alt: "Firefly SrTools Logo",
|
||||
@@ -50,8 +50,8 @@ export const metadata: Metadata = {
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Firefly SrTools",
|
||||
description: "SrTools by Kain",
|
||||
images: ["https://srtools.kain.id.vn/ff-srtool.png"],
|
||||
description: "SrTools by Firefly Shelter",
|
||||
images: ["https://srtools.punklorde.org/ff-srtool.png"],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -417,7 +417,7 @@ export default function ActionBar() {
|
||||
|
||||
{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="modal-box w-11/12 max-w-[90%] 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">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { Check, AlertCircle, Sparkles, Bug, Zap, Package, Calendar } from 'lucide-react';
|
||||
import { Check, Sparkles, Bug, Zap, Package, Calendar } from 'lucide-react';
|
||||
|
||||
export default function ChangelogBar() {
|
||||
const { changelog } = useLocaleStore()
|
||||
@@ -47,12 +47,9 @@ export default function ChangelogBar() {
|
||||
return (
|
||||
<div className="md:p-4">
|
||||
{/* Alert */}
|
||||
<div className="alert alert-info mb-4 md:p-2 p-1">
|
||||
{/* <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>
|
||||
</div> */}
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
@@ -2,7 +2,7 @@ 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>
|
||||
<p>Copyright © {new Date().getFullYear()} - Firefly Shelter (Powered by Nextjs & DaisyUi)</p>
|
||||
</aside>
|
||||
</footer>
|
||||
)
|
||||
|
||||
@@ -359,7 +359,7 @@ export default function Header() {
|
||||
Tools
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">By Kain</p>
|
||||
<p className="text-sm text-gray-500">Firefly Shelter</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -567,7 +567,7 @@ export default function Header() {
|
||||
|
||||
{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="modal-box w-11/12 max-w-[90%] 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">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
|
||||
@@ -245,7 +245,7 @@ export default function RelicMaker() {
|
||||
{transI18n("relicMaker")}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-7xl">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-2">
|
||||
|
||||
{/* Left Panel */}
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -155,16 +155,16 @@ export default function RelicsInfo() {
|
||||
// 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);
|
||||
@@ -173,164 +173,162 @@ export default function RelicsInfo() {
|
||||
|
||||
return (
|
||||
<div className="max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
|
||||
<div className="max-w-7xl">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
|
||||
{/* Left Section - Items Grid */}
|
||||
<div className="lg:col-span-2">
|
||||
<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-linear-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("relics")}
|
||||
</h2>
|
||||
{/* Left Section - Items Grid */}
|
||||
<div className="lg:col-span-2">
|
||||
<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-linear-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("relics")}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-6 max-w-2xl">
|
||||
{["1", "2", "3", "4", "5", "6"].map((item, index) => (
|
||||
<div key={index} className="relative group">
|
||||
<div
|
||||
onClick={() => {
|
||||
if (item === selectedRelicSlot) {
|
||||
setSelectedRelicSlot("")
|
||||
} else {
|
||||
setSelectedRelicSlot(item)
|
||||
}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
|
||||
{["1", "2", "3", "4", "5", "6"].map((item, index) => (
|
||||
<div key={index} className="relative group">
|
||||
<div
|
||||
onClick={() => {
|
||||
if (item === selectedRelicSlot) {
|
||||
setSelectedRelicSlot("")
|
||||
} else {
|
||||
setSelectedRelicSlot(item)
|
||||
}
|
||||
handlerChangeRelic(item)
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<RelicCard
|
||||
slot={item}
|
||||
avatarId={avatarSelected?.ID.toString() || ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex gap-1">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handlerChangeRelic(item)
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
className="btn btn-info p-1.5 rounded-full shadow-lg transition-colors duration-200"
|
||||
title={transI18n("changeRelic")}
|
||||
>
|
||||
<RelicCard
|
||||
slot={item}
|
||||
avatarId={avatarSelected?.ID.toString() || ""}
|
||||
/>
|
||||
</div>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex gap-1">
|
||||
{getRelic(item) && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handlerChangeRelic(item)
|
||||
if (window.confirm(`${transI18n("deleteRelicConfirm")} ${item}?`)) {
|
||||
handlerDeleteRelic(item)
|
||||
}
|
||||
}}
|
||||
className="btn btn-info p-1.5 rounded-full shadow-lg transition-colors duration-200"
|
||||
title={transI18n("changeRelic")}
|
||||
className="btn btn-error p-1.5 rounded-full shadow-lg transition-colors duration-200"
|
||||
title={transI18n("deleteRelic")}
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{getRelic(item) && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (window.confirm(`${transI18n("deleteRelicConfirm")} ${item}?`)) {
|
||||
handlerDeleteRelic(item)
|
||||
}
|
||||
}}
|
||||
className="btn btn-error p-1.5 rounded-full shadow-lg transition-colors duration-200"
|
||||
title={transI18n("deleteRelic")}
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 mt-10">
|
||||
<button
|
||||
disabled={!selectedRelicSlot}
|
||||
onClick={() => {
|
||||
handlerChangeRelic(selectedRelicSlot)
|
||||
}}
|
||||
className="btn btn-info"
|
||||
>
|
||||
{transI18n("changeRelic")}
|
||||
</button>
|
||||
<button
|
||||
disabled={!selectedRelicSlot}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (window.confirm(`${transI18n("deleteRelicConfirm")} ${selectedRelicSlot}?`)) {
|
||||
handlerDeleteRelic(selectedRelicSlot)
|
||||
}
|
||||
}}
|
||||
className="btn btn-error"
|
||||
>
|
||||
{transI18n("deleteRelic")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 mt-10">
|
||||
<button
|
||||
disabled={!selectedRelicSlot}
|
||||
onClick={() => {
|
||||
setIsOpenQuickView(true)
|
||||
handleShow("quick_view_modal")
|
||||
handlerChangeRelic(selectedRelicSlot)
|
||||
}}
|
||||
className="btn btn-info w-full mt-2"
|
||||
className="btn btn-info"
|
||||
>
|
||||
{transI18n("quickView")}
|
||||
{transI18n("changeRelic")}
|
||||
</button>
|
||||
<button
|
||||
disabled={!selectedRelicSlot}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (window.confirm(`${transI18n("deleteRelicConfirm")} ${selectedRelicSlot}?`)) {
|
||||
handlerDeleteRelic(selectedRelicSlot)
|
||||
}
|
||||
}}
|
||||
className="btn btn-error"
|
||||
>
|
||||
{transI18n("deleteRelic")}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpenQuickView(true)
|
||||
handleShow("quick_view_modal")
|
||||
}}
|
||||
className="btn btn-info w-full mt-2"
|
||||
>
|
||||
{transI18n("quickView")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Section - Stats and Set Effects */}
|
||||
<div className="space-y-6">
|
||||
{/* Right Section - Stats and Set Effects */}
|
||||
<div className="space-y-6">
|
||||
|
||||
{/* Set Effects Panel */}
|
||||
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
|
||||
<h3 className="flex items-center gap-2 text-xl font-bold mb-4 text-base-content">
|
||||
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("setEffects")}
|
||||
</h3>
|
||||
{/* Set Effects Panel */}
|
||||
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
|
||||
<h3 className="flex items-center gap-2 text-xl font-bold mb-4 text-base-content">
|
||||
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("setEffects")}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
{relicEffects.map((setEffect, index) => {
|
||||
const relicInfo = mapRelicSet[setEffect.key];
|
||||
if (!relicInfo) return null;
|
||||
return (
|
||||
<div key={index} className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="font-bold text-warning"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, relicInfo.Name),
|
||||
[]
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{setEffect.count && (
|
||||
<span className={`text-sm text-info`}>
|
||||
({setEffect.count})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pl-4">
|
||||
{Object.entries(relicInfo.Skills).map(([requireNum, value]) => {
|
||||
if (Number(requireNum) > Number(setEffect.count)) return null;
|
||||
return (
|
||||
<div key={requireNum} className="space-y-1">
|
||||
<div className={`font-medium text-success`}>
|
||||
{requireNum}-PC:
|
||||
</div>
|
||||
<div
|
||||
className="text-sm text-base-content/80 leading-relaxed pl-4"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, value.Desc),
|
||||
value.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{relicEffects.map((setEffect, index) => {
|
||||
const relicInfo = mapRelicSet[setEffect.key];
|
||||
if (!relicInfo) return null;
|
||||
return (
|
||||
<div key={index} className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="font-bold text-warning"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, relicInfo.Name),
|
||||
[]
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}}
|
||||
/>
|
||||
{setEffect.count && (
|
||||
<span className={`text-sm text-info`}>
|
||||
({setEffect.count})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pl-4">
|
||||
{Object.entries(relicInfo.Skills).map(([requireNum, value]) => {
|
||||
if (Number(requireNum) > Number(setEffect.count)) return null;
|
||||
return (
|
||||
<div key={requireNum} className="space-y-1">
|
||||
<div className={`font-medium text-success`}>
|
||||
{requireNum}-PC:
|
||||
</div>
|
||||
<div
|
||||
className="text-sm text-base-content/80 leading-relaxed pl-4"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, value.Desc),
|
||||
value.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -338,7 +336,7 @@ export default function RelicsInfo() {
|
||||
|
||||
{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="modal-box w-11/12 max-w-[90%] 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">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
|
||||
Reference in New Issue
Block a user