UPDATE: Add self update
This commit is contained in:
4
frontend/src/helper/index.ts
Normal file
4
frontend/src/helper/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./server"
|
||||
export * from "./proxy"
|
||||
export * from "./launcher"
|
||||
export * from "./sleep"
|
||||
38
frontend/src/helper/launcher.ts
Normal file
38
frontend/src/helper/launcher.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import useLauncherStore from "@/stores/launcherStore";
|
||||
import { AppService, GitService } from "@bindings/firefly-launcher/internal";
|
||||
import { toast } from "react-toastify";
|
||||
import { sleep } from "./sleep";
|
||||
|
||||
export async function CheckUpdateLauncher() : Promise<{isUpdate: boolean, version: string}> {
|
||||
let isUpdateLauncher = false
|
||||
let version = ""
|
||||
const [launcherCurrentOk, launcherCurrentVersion] = await AppService.GetCurrentLauncherVersion()
|
||||
if (!launcherCurrentOk) {
|
||||
toast.error("Launcher error: cannot get current version")
|
||||
} else {
|
||||
const [launcherNewOk, launcherNewVersion, launcherNewError] = await GitService.GetLatestLauncherVersion()
|
||||
version = launcherCurrentVersion
|
||||
if (launcherNewOk && launcherNewVersion && launcherNewVersion !== launcherCurrentVersion) {
|
||||
isUpdateLauncher = true
|
||||
version = launcherNewVersion
|
||||
} else if (!launcherNewOk) {
|
||||
toast.error("Launcher error: " + launcherNewError)
|
||||
}
|
||||
}
|
||||
return { isUpdate: isUpdateLauncher, version: version }
|
||||
}
|
||||
|
||||
export async function UpdateLauncher(launcherVersion: string) : Promise<void> {
|
||||
const {setDownloadType } = useLauncherStore.getState()
|
||||
setDownloadType("update:launcher:downloading")
|
||||
const [ok, error] = await GitService.UpdateLauncherProgress(launcherVersion)
|
||||
if (ok) {
|
||||
setDownloadType("update:launcher:success")
|
||||
|
||||
} else {
|
||||
toast.error(error)
|
||||
setDownloadType("update:launcher:failed")
|
||||
}
|
||||
AppService.CloseAppAfterTimeout(5)
|
||||
await sleep(5000)
|
||||
}
|
||||
35
frontend/src/helper/proxy.ts
Normal file
35
frontend/src/helper/proxy.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import useLauncherStore from "@/stores/launcherStore";
|
||||
import useSettingStore from "@/stores/settingStore";
|
||||
import { FSService, GitService } from "@bindings/firefly-launcher/internal";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export async function CheckUpdateProxy(proxyPath: string, proxyVersion: string) : Promise<{isUpdate: boolean, version: string}> {
|
||||
let isUpdateProxy = false
|
||||
let version = ""
|
||||
const [proxyOk, proxyNewVersion, proxyError] = await GitService.GetLatestProxyVersion()
|
||||
const proxyExists = await FSService.FileExists(proxyPath)
|
||||
if (proxyOk && proxyNewVersion && proxyNewVersion !== proxyVersion || !proxyExists) {
|
||||
isUpdateProxy = true
|
||||
version = proxyNewVersion
|
||||
} else if (!proxyOk) {
|
||||
toast.error("Proxy error: " + proxyError)
|
||||
}
|
||||
return { isUpdate: isUpdateProxy, version: version }
|
||||
}
|
||||
|
||||
export async function UpdateProxy(proxyVersion: string) : Promise<void> {
|
||||
const {setDownloadType } = useLauncherStore.getState()
|
||||
const {setProxyPath, setProxyVersion} = useSettingStore.getState()
|
||||
setDownloadType("Downloading proxy...")
|
||||
const [ok, error] = await GitService.DownloadProxyProgress(proxyVersion)
|
||||
if (ok) {
|
||||
setDownloadType("Unzipping proxy...")
|
||||
GitService.UnzipProxy()
|
||||
setDownloadType("Download proxy successfully")
|
||||
setProxyVersion(proxyVersion)
|
||||
setProxyPath("./proxy/FireflyProxy.exe")
|
||||
} else {
|
||||
toast.error(error)
|
||||
setDownloadType("Download proxy failed")
|
||||
}
|
||||
}
|
||||
35
frontend/src/helper/server.ts
Normal file
35
frontend/src/helper/server.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import useLauncherStore from '@/stores/launcherStore';
|
||||
import useSettingStore from '@/stores/settingStore';
|
||||
import { FSService, GitService } from '@bindings/firefly-launcher/internal';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export async function CheckUpdateServer(serverPath: string, serverVersion: string) : Promise<{isUpdate: boolean, version: string}> {
|
||||
let isUpdateServer = false
|
||||
let version = ""
|
||||
const [serverOk, serverNewVersion, serverError] = await GitService.GetLatestServerVersion()
|
||||
const serverExists = await FSService.FileExists(serverPath)
|
||||
if (serverOk && serverNewVersion && serverNewVersion !== serverVersion || !serverExists) {
|
||||
isUpdateServer = true
|
||||
version = serverNewVersion
|
||||
} else if (!serverOk) {
|
||||
toast.error("Server error: " + serverError)
|
||||
}
|
||||
return { isUpdate: isUpdateServer, version: version }
|
||||
}
|
||||
|
||||
export async function UpdateServer(serverVersion: string) : Promise<void> {
|
||||
const {setDownloadType } = useLauncherStore.getState()
|
||||
const {setServerPath, setServerVersion} = useSettingStore.getState()
|
||||
setDownloadType("Downloading server...")
|
||||
const [ok, error] = await GitService.DownloadServerProgress(serverVersion)
|
||||
if (ok) {
|
||||
setDownloadType("Unzipping server...")
|
||||
GitService.UnzipServer()
|
||||
setDownloadType("Download server successfully")
|
||||
setServerVersion(serverVersion)
|
||||
setServerPath("./server/firefly-go_win.exe")
|
||||
} else {
|
||||
toast.error(error)
|
||||
setDownloadType("Download server failed")
|
||||
}
|
||||
}
|
||||
3
frontend/src/helper/sleep.ts
Normal file
3
frontend/src/helper/sleep.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -65,6 +65,7 @@ export function useGlobalEvents({
|
||||
Events.Off("proxy:exit");
|
||||
Events.Off("hdiffz:progress");
|
||||
Events.Off("hdiffz:message");
|
||||
Events.Off("version:check");
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function AboutPage() {
|
||||
I created a lightweight and modern <span className="font-semibold text-success">Game Launcher</span> to help users easily launch and manage their games with better performance and simplicity.
|
||||
</p>
|
||||
<p className="text-lg leading-relaxed">
|
||||
The launcher is built using <span className="font-mono text-info">Go + Wails</span>, with a clean and responsive interface styled with <span className="text-warning">Tailwind CSS</span> and <span className="text-warning">DaisyUI</span>.
|
||||
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-warning">Tailwind CSS</span> and <span className="text-warning">DaisyUI</span>.
|
||||
</p>
|
||||
<p className="text-lg leading-relaxed">
|
||||
My goal is to make tools that are fast, efficient, and enjoyable to use — and this launcher is just the beginning.
|
||||
|
||||
@@ -78,12 +78,12 @@ export default function AnalysisPage() {
|
||||
<span className="font-semibold text-blue-800">Backup Website</span>
|
||||
</div>
|
||||
<a
|
||||
href="https://sr-analysis.vercel.app/"
|
||||
href="https://firefly-sranalysis.vercel.app/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://sr-analysis.vercel.app/
|
||||
https://firefly-sranalysis.vercel.app/
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Play, Menu, FolderOpen, MessageCircleQuestionMark } from 'lucide-react';
|
||||
import { FSService, GitService } from '@bindings/firefly-launcher/internal';
|
||||
import { FSService, AppService } from '@bindings/firefly-launcher/internal';
|
||||
import { toast } from 'react-toastify';
|
||||
import path from 'path-browserify'
|
||||
import useSettingStore from '@/stores/settingStore';
|
||||
@@ -8,6 +8,7 @@ import useModalStore from '@/stores/modalStore';
|
||||
import useLauncherStore from '@/stores/launcherStore';
|
||||
import { motion } from 'motion/react';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
|
||||
|
||||
export default function LauncherPage() {
|
||||
const { gamePath,
|
||||
@@ -18,15 +19,17 @@ export default function LauncherPage() {
|
||||
gameDir,
|
||||
serverVersion,
|
||||
proxyVersion,
|
||||
setServerVersion,
|
||||
setProxyVersion,
|
||||
setServerPath,
|
||||
setProxyPath
|
||||
} = useSettingStore()
|
||||
const { isOpenNotification, setIsOpenNotification } = useModalStore()
|
||||
const {
|
||||
isOpenDownloadDataModal,
|
||||
isOpenUpdateDataModal,
|
||||
isOpenSelfUpdateModal,
|
||||
setIsOpenDownloadDataModal,
|
||||
setIsOpenUpdateDataModal,
|
||||
setIsOpenSelfUpdateModal
|
||||
} = useModalStore()
|
||||
const {
|
||||
isLoading,
|
||||
modelName,
|
||||
downloadType,
|
||||
serverReady,
|
||||
proxyReady,
|
||||
@@ -36,8 +39,10 @@ export default function LauncherPage() {
|
||||
gameRunning,
|
||||
progressDownload,
|
||||
downloadSpeed,
|
||||
updateData,
|
||||
launcherVersion,
|
||||
setLauncherVersion,
|
||||
setIsLoading,
|
||||
setModelName,
|
||||
setDownloadType,
|
||||
setServerReady,
|
||||
setProxyReady,
|
||||
@@ -45,9 +50,9 @@ export default function LauncherPage() {
|
||||
setServerRunning,
|
||||
setProxyRunning,
|
||||
setGameRunning,
|
||||
setUpdateData,
|
||||
} = useLauncherStore()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const check = async () => {
|
||||
if (!serverPath || !proxyPath || !serverVersion || !proxyVersion) {
|
||||
@@ -67,30 +72,49 @@ export default function LauncherPage() {
|
||||
|
||||
useEffect(() => {
|
||||
const checkStartUp = async (): Promise<void> => {
|
||||
const [_, version] = await AppService.GetCurrentLauncherVersion()
|
||||
setLauncherVersion(version)
|
||||
let isExists = false
|
||||
if (serverPath && proxyPath && serverVersion && proxyVersion) {
|
||||
isExists = true
|
||||
setServerReady(true)
|
||||
setProxyReady(true)
|
||||
}
|
||||
const dataUpdate = await handlerCheckUpdate()
|
||||
const launcherData = await CheckUpdateLauncher()
|
||||
if (launcherData.isUpdate) {
|
||||
setUpdateData({
|
||||
server: { isUpdate: false, version: "" },
|
||||
proxy: { isUpdate: false, version: "" },
|
||||
launcher: launcherData
|
||||
})
|
||||
setIsOpenSelfUpdateModal(true)
|
||||
return
|
||||
}
|
||||
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||
const exitGame = await FSService.FileExists(gamePath)
|
||||
if (!exitGame) {
|
||||
setGameRunning(false)
|
||||
setGamePath("")
|
||||
setGameDir("")
|
||||
}
|
||||
if (!dataUpdate.server.isUpdate && !dataUpdate.proxy.isUpdate) {
|
||||
if (!serverData.isUpdate && !proxyData.isUpdate) {
|
||||
setServerReady(true)
|
||||
setProxyReady(true)
|
||||
return
|
||||
}
|
||||
if (!isExists) {
|
||||
setUpdateData({
|
||||
server: serverData,
|
||||
proxy: proxyData,
|
||||
launcher: launcherData
|
||||
})
|
||||
setServerReady(false)
|
||||
setProxyReady(false)
|
||||
setIsOpenDownloadDataModal(true)
|
||||
return
|
||||
}
|
||||
setModelName(!isExists ? "Download Data" : "Update Data")
|
||||
setIsOpenNotification(true)
|
||||
setIsOpenUpdateDataModal(true)
|
||||
}
|
||||
checkStartUp()
|
||||
}, []);
|
||||
@@ -121,9 +145,7 @@ export default function LauncherPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
|
||||
const handleStartGame = async () => {
|
||||
if (!gamePath) {
|
||||
@@ -174,84 +196,43 @@ export default function LauncherPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handlerCheckUpdate = async (): Promise<Record<string, { isUpdate: boolean, version: string }>> => {
|
||||
let isUpdateServer = false
|
||||
let isUpdateProxy = false
|
||||
const [serverOk, serverNewVersion, serverError] = await GitService.GetLatestServerVersion(serverVersion)
|
||||
const serverExists = await FSService.FileExists(serverPath)
|
||||
if (serverOk) {
|
||||
if (serverNewVersion !== serverVersion || !serverExists) {
|
||||
isUpdateServer = true
|
||||
}
|
||||
} else {
|
||||
toast.error("Server error: " + serverError)
|
||||
}
|
||||
const [proxyOk, proxyNewVersion, proxyError] = await GitService.GetLatestProxyVersion(proxyVersion)
|
||||
const proxyExists = await FSService.FileExists(proxyPath)
|
||||
if (proxyOk) {
|
||||
if (proxyNewVersion !== proxyVersion || !proxyExists) {
|
||||
isUpdateProxy = true
|
||||
}
|
||||
} else {
|
||||
toast.error("Proxy error: " + proxyError)
|
||||
}
|
||||
return { server: { isUpdate: isUpdateServer, version: serverNewVersion }, proxy: { isUpdate: isUpdateProxy, version: proxyNewVersion } }
|
||||
}
|
||||
|
||||
const handlerUpdateServer = async (updateInfo: Record<string, { isUpdate: boolean, version: string }>) => {
|
||||
const handlerUpdateServer = async () => {
|
||||
setIsDownloading(true)
|
||||
for (const [key, value] of Object.entries(updateInfo)) {
|
||||
if (value.isUpdate) {
|
||||
for (const [key, value] of Object.entries(updateData)) {
|
||||
if (!value.isUpdate) {
|
||||
if (key === "server") {
|
||||
setDownloadType("Downloading server...")
|
||||
const [ok, error] = await GitService.DownloadServerProgress(value.version)
|
||||
if (ok) {
|
||||
setDownloadType("Unzipping server...")
|
||||
GitService.UnzipServer()
|
||||
setDownloadType("Download server successfully")
|
||||
setServerVersion(value.version)
|
||||
|
||||
setServerPath("./server/firefly-go_win.exe")
|
||||
} else {
|
||||
toast.error(error)
|
||||
setDownloadType("Download server failed")
|
||||
}
|
||||
setServerReady(true)
|
||||
} else if (key === "proxy") {
|
||||
setDownloadType("Downloading proxy...")
|
||||
const [ok, error] = await GitService.DownloadProxyProgress(value.version)
|
||||
if (ok) {
|
||||
setDownloadType("Unzipping proxy...")
|
||||
GitService.UnzipProxy()
|
||||
setDownloadType("Download proxy successfully")
|
||||
setProxyVersion(value.version)
|
||||
setProxyPath("./proxy/FireflyProxy.exe")
|
||||
|
||||
} else {
|
||||
toast.error(error)
|
||||
setDownloadType("Download proxy failed")
|
||||
}
|
||||
setProxyReady(true)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
if (key === "server") {
|
||||
await UpdateServer(value.version)
|
||||
setServerReady(true)
|
||||
} else if (key === "proxy") {
|
||||
await UpdateProxy(value.version)
|
||||
setProxyReady(true)
|
||||
} else if (key === "launcher") {
|
||||
await UpdateLauncher(value.version)
|
||||
}
|
||||
}
|
||||
setDownloadType("")
|
||||
setIsDownloading(false)
|
||||
setServerReady(true)
|
||||
setProxyReady(true)
|
||||
}
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsOpenNotification(false);
|
||||
setIsOpenDownloadDataModal(false);
|
||||
setIsOpenUpdateDataModal(false);
|
||||
setIsOpenSelfUpdateModal(false);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleEscKey);
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenNotification]);
|
||||
}, [isOpenDownloadDataModal, isOpenUpdateDataModal, isOpenSelfUpdateModal]);
|
||||
|
||||
|
||||
return (
|
||||
@@ -280,11 +261,10 @@ export default function LauncherPage() {
|
||||
href="https://sranalysis.kain.id.vn/"
|
||||
>
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/sranalysis.kain.id.vn.ico"
|
||||
src="https://sranalysis.kain.id.vn/ff-sranalysis.png"
|
||||
alt="SRAnalysis Logo"
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -295,7 +275,7 @@ export default function LauncherPage() {
|
||||
href="https://srtools.kain.id.vn/"
|
||||
>
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/srtools.kain.id.vn.ico"
|
||||
src="https://srtools.kain.id.vn/ff-srtool.png"
|
||||
alt="SRTools Logo"
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
@@ -369,10 +349,16 @@ export default function LauncherPage() {
|
||||
<li><button onClick={handlePickFile}>Change Game Path</button></li>
|
||||
<li><button
|
||||
onClick={async () => {
|
||||
const updateInfo = await handlerCheckUpdate()
|
||||
if (updateInfo.server.isUpdate || updateInfo.proxy.isUpdate) {
|
||||
setModelName("Update Data")
|
||||
setIsOpenNotification(true)
|
||||
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||
const launcherData = await CheckUpdateLauncher()
|
||||
if (serverData.isUpdate || proxyData.isUpdate || launcherData.isUpdate) {
|
||||
setUpdateData({
|
||||
server: serverData,
|
||||
proxy: proxyData,
|
||||
launcher: launcherData
|
||||
})
|
||||
setIsOpenUpdateDataModal(true)
|
||||
} else {
|
||||
toast.success("No updates available")
|
||||
}
|
||||
@@ -402,7 +388,7 @@ export default function LauncherPage() {
|
||||
)}
|
||||
|
||||
{/* Downloading */}
|
||||
{isDownloading && (
|
||||
{isDownloading && (updateData.proxy.isUpdate || updateData.server.isUpdate) && (
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-center items-center text-sm text-white/80">
|
||||
@@ -426,64 +412,185 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isDownloading && updateData.launcher.isUpdate && (
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||
<div className="space-y-3 text-sm text-white/80 text-center">
|
||||
{["update:launcher:downloading", "update:launcher:success", "update:launcher:failed"].includes(downloadType) && (
|
||||
<div className="flex justify-center items-center gap-4 ml-4">
|
||||
<span
|
||||
className={`font-bold ${downloadType === "update:launcher:downloading"
|
||||
? "text-yellow-200 text-2xl"
|
||||
: downloadType === "update:launcher:success"
|
||||
? "text-emerald-200 text-xl"
|
||||
: "text-red-200 text-xl"
|
||||
}`}
|
||||
>
|
||||
{downloadType === "update:launcher:downloading" && "Updating launcher"}
|
||||
{downloadType === "update:launcher:success" && "Launcher updated successfully, auto closing after 5s"}
|
||||
{downloadType === "update:launcher:failed" && "Launcher update failed, auto closing after 5s"}
|
||||
<span className="dot-animation ml-1"></span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-xs text-white/60">
|
||||
{progressDownload < 100 ? "Please wait..." : "Complete!"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.dot-animation::after {
|
||||
content: '';
|
||||
animation: dots 1.2s steps(4, end) infinite;
|
||||
}
|
||||
@keyframes dots {
|
||||
0% { content: ''; }
|
||||
25% { content: '.'; }
|
||||
50% { content: '..'; }
|
||||
75% { content: '...'; }
|
||||
100% { content: ''; }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Version Info */}
|
||||
{serverReady && proxyReady && !isDownloading && (
|
||||
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-black/20 backdrop-blur-sm rounded-lg p-2 shadow">
|
||||
<p className="text-primary">Version server: {serverVersion}</p>
|
||||
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
|
||||
<p className="mt-2 text-success">Version launcher: {launcherVersion}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal */}
|
||||
{isOpenNotification && (
|
||||
{isOpenUpdateDataModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
{modelName !== "Download Data" && (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={() => setIsOpenNotification(false)}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
)}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
|
||||
<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-gradient-to-r from-pink-400 to-cyan-400">
|
||||
{modelName}
|
||||
Update Data
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
{modelName === "Download Data"
|
||||
? "Download required data!"
|
||||
: "Do you want to update data?"}
|
||||
Do you want to update data server and proxy?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
{modelName !== "Download Data" && (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-outline btn-error"
|
||||
onClick={() => setIsOpenNotification(false)}
|
||||
>
|
||||
Cancel
|
||||
</motion.button>
|
||||
)}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-outline btn-error"
|
||||
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||
>
|
||||
No
|
||||
</motion.button>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
onClick={async () => {
|
||||
setIsOpenNotification(false)
|
||||
const dataCheck = await handlerCheckUpdate()
|
||||
await handlerUpdateServer(dataCheck)
|
||||
setIsOpenUpdateDataModal(false)
|
||||
await handlerUpdateServer()
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOpenDownloadDataModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<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-gradient-to-r from-pink-400 to-cyan-400">
|
||||
Download Data
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
Data server and proxy download required
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
onClick={async () => {
|
||||
setIsOpenDownloadDataModal(false)
|
||||
await handlerUpdateServer()
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOpenSelfUpdateModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={() => setIsOpenSelfUpdateModal(false)}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
|
||||
<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-gradient-to-r from-pink-400 to-cyan-400">
|
||||
Update Launcher
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
Do you want to update launcher?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-outline btn-error"
|
||||
onClick={() => setIsOpenSelfUpdateModal(false)}
|
||||
>
|
||||
No
|
||||
</motion.button>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
onClick={async () => {
|
||||
setIsOpenSelfUpdateModal(false)
|
||||
await handlerUpdateServer()
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
|
||||
@@ -15,17 +15,44 @@ export default function SrToolsPage() {
|
||||
<div className="space-y-3 text-blue-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">🏠</div>
|
||||
<p>This site uses a self-hosted version of <span className="font-semibold text-success">SR Tools</span> available at{" "}
|
||||
<a
|
||||
href="https://srtools.kain.id.vn/"
|
||||
className="link link-info"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
srtools.kain.id.vn
|
||||
</a>
|
||||
<p>
|
||||
This site is a another version of {" "}
|
||||
<span className="font-semibold text-success">SR Tools {" "}</span>
|
||||
developed by {" "}
|
||||
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-blue-600 text-lg">🏆</span>
|
||||
<span className="font-semibold text-blue-800">Master Website</span>
|
||||
</div>
|
||||
<a
|
||||
href="https://srtools.kain.id.vn/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://srtools.kain.id.vn/
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-blue-600 text-lg">🔄</span>
|
||||
<span className="font-semibold text-blue-800">Backup Website</span>
|
||||
</div>
|
||||
<a
|
||||
href="https://firefly-srtools.vercel.app/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://firefly-srtools.vercel.app/
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">👨💻</div>
|
||||
<p>The original tool was created by a third-party developer named <span className="font-semibold text-warning">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||
|
||||
@@ -22,7 +22,7 @@ function RootLayout() {
|
||||
setDownloadSpeed,
|
||||
setMessageUpdate,
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="navbar bg-base-100 shadow-sm sticky top-0 z-50 px-3">
|
||||
@@ -55,13 +55,18 @@ function RootLayout() {
|
||||
</ul>
|
||||
</div>
|
||||
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
||||
<h1 className="text-lg font-bold">
|
||||
<span className="text-emerald-500">Firefly Lauc</span>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
|
||||
her
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">By Kain</p>
|
||||
<div className="flex items-center justify-center">
|
||||
<img src="/ff-launcher.png" alt="Logo" className='w-13 h-13' />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<h1 className="text-xl font-bold">
|
||||
<span className="text-emerald-500">Firefly </span>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
|
||||
Launcher
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">By Kain</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="navbar-center hidden md:flex">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface LauncherState {
|
||||
modelName: string;
|
||||
downloadType: string;
|
||||
serverReady: boolean;
|
||||
proxyReady: boolean;
|
||||
@@ -13,7 +12,8 @@ interface LauncherState {
|
||||
gameRunning: boolean;
|
||||
progressDownload: number;
|
||||
downloadSpeed: number;
|
||||
setModelName: (value: string) => void;
|
||||
launcherVersion: string;
|
||||
updateData: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, version: string }>;
|
||||
setDownloadType: (value: string) => void;
|
||||
setServerReady: (value: boolean) => void;
|
||||
setProxyReady: (value: boolean) => void;
|
||||
@@ -23,12 +23,13 @@ interface LauncherState {
|
||||
setIsLoading: (value: boolean) => void;
|
||||
setGameRunning: (value: boolean) => void;
|
||||
setProgressDownload: (value: number) => void;
|
||||
setLauncherVersion: (value: string) => void;
|
||||
setDownloadSpeed: (value: number) => void;
|
||||
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, version: string }>) => void;
|
||||
}
|
||||
|
||||
const useLauncherStore = create<LauncherState>((set, get) => ({
|
||||
isLoading: false,
|
||||
modelName: "Download Data",
|
||||
downloadType: "",
|
||||
serverReady: false,
|
||||
proxyReady: false,
|
||||
@@ -38,8 +39,13 @@ const useLauncherStore = create<LauncherState>((set, get) => ({
|
||||
gameRunning: false,
|
||||
progressDownload: 0,
|
||||
downloadSpeed: 0,
|
||||
launcherVersion: "",
|
||||
updateData: {
|
||||
server: { isUpdate: false, version: "" },
|
||||
proxy: { isUpdate: false, version: "" },
|
||||
launcher: { isUpdate: false, version: "" },
|
||||
},
|
||||
setIsLoading: (value: boolean) => set({ isLoading: value }),
|
||||
setModelName: (value: string) => set({ modelName: value }),
|
||||
setDownloadType: (value: string) => set({ downloadType: value }),
|
||||
setServerReady: (value: boolean) => set({ serverReady: value }),
|
||||
setProxyReady: (value: boolean) => set({ proxyReady: value }),
|
||||
@@ -48,7 +54,9 @@ const useLauncherStore = create<LauncherState>((set, get) => ({
|
||||
setProxyRunning: (value: boolean) => set({ proxyRunning: value }),
|
||||
setGameRunning: (value: boolean) => set({ gameRunning: value }),
|
||||
setProgressDownload: (value: number) => set({ progressDownload: value }),
|
||||
setLauncherVersion: (value: string) => set({ launcherVersion: value }),
|
||||
setDownloadSpeed: (value: number) => set({ downloadSpeed: value }),
|
||||
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, version: string }>) => set({ updateData: value }),
|
||||
}));
|
||||
|
||||
export default useLauncherStore;
|
||||
@@ -2,13 +2,21 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface ModalState {
|
||||
isOpenNotification: boolean;
|
||||
setIsOpenNotification: (modal: boolean) => void;
|
||||
isOpenDownloadDataModal: boolean;
|
||||
isOpenUpdateDataModal: boolean;
|
||||
isOpenSelfUpdateModal: boolean;
|
||||
setIsOpenDownloadDataModal: (modal: boolean) => void;
|
||||
setIsOpenUpdateDataModal: (modal: boolean) => void;
|
||||
setIsOpenSelfUpdateModal: (modal: boolean) => void;
|
||||
}
|
||||
|
||||
const useModalStore = create<ModalState>((set, get) => ({
|
||||
isOpenNotification: false,
|
||||
setIsOpenNotification: (modal: boolean) => set({ isOpenNotification: modal }),
|
||||
isOpenDownloadDataModal: false,
|
||||
isOpenUpdateDataModal: false,
|
||||
isOpenSelfUpdateModal: false,
|
||||
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
|
||||
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
|
||||
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
|
||||
}));
|
||||
|
||||
export default useModalStore;
|
||||
Reference in New Issue
Block a user