Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca612797ee | |||
| 787962c6d0 | |||
| 09434fcc5b | |||
| edbe04b9fc |
5
Makefile
@@ -22,6 +22,11 @@ build:
|
|||||||
wails3 build
|
wails3 build
|
||||||
@echo Done!
|
@echo Done!
|
||||||
|
|
||||||
|
generate:
|
||||||
|
@echo Generating bindings...
|
||||||
|
wails3 generate bindings -ts
|
||||||
|
@echo Done!
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@echo Building release application...
|
@echo Building release application...
|
||||||
wails3 package
|
wails3 package
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges>
|
<requestedPrivileges>
|
||||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||||
</requestedPrivileges>
|
</requestedPrivileges>
|
||||||
</security>
|
</security>
|
||||||
</trustInfo>
|
</trustInfo>
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
|
*/
|
||||||
|
export function CloseApp() {
|
||||||
|
return $Call.ByID(3422460836);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} timeout
|
* @param {number} timeout
|
||||||
* @returns {$CancellablePromise<[boolean, string]>}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
@@ -20,3 +27,31 @@ export function CloseAppAfterTimeout(timeout) {
|
|||||||
export function GetCurrentLauncherVersion() {
|
export function GetCurrentLauncherVersion() {
|
||||||
return $Call.ByID(3575133982);
|
return $Call.ByID(3575133982);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
|
*/
|
||||||
|
export function HideApp() {
|
||||||
|
return $Call.ByID(88003266);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
|
*/
|
||||||
|
export function MaximizeApp() {
|
||||||
|
return $Call.ByID(1257306588);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
|
*/
|
||||||
|
export function MinimizeApp() {
|
||||||
|
return $Call.ByID(3434614194);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
|
*/
|
||||||
|
export function RestoreApp() {
|
||||||
|
return $Call.ByID(3115625834);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-theme="dracula">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
21
frontend/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-easy-crop": "^5.5.3",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
@@ -3783,6 +3784,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/normalize-wheel": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -4000,6 +4007,20 @@
|
|||||||
"react": "^19.2.0"
|
"react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-easy-crop": {
|
||||||
|
"version": "5.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.5.3.tgz",
|
||||||
|
"integrity": "sha512-iKwFTnAsq+IVuyF6N0Q3zjRx9DG1NMySkwWxVfM/xAOeHYH1vhvM+V2kFiq5HOIQGWouITjfltCx54mbDpMpmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"normalize-wheel": "^1.0.1",
|
||||||
|
"tslib": "^2.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.4.0",
|
||||||
|
"react-dom": ">=16.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-easy-crop": "^5.5.3",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
|
|||||||
BIN
frontend/public/bg-1.jpeg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
frontend/public/bg-10.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
frontend/public/bg-11.jpeg
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
frontend/public/bg-12.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
frontend/public/bg-13.jpg
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
frontend/public/bg-16.jpg
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
frontend/public/bg-2.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
frontend/public/bg-3.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
frontend/public/bg-5.jpeg
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
frontend/public/bg-6.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
frontend/public/bg-7.jpeg
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
frontend/public/bg-8.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/public/bg-9.jpeg
Normal file
|
After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
180
frontend/src/components/backgroudModal/index.tsx
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useRef } from 'react'
|
||||||
|
import { X, Image as ImageIcon, Plus, Upload, Check } from 'lucide-react'
|
||||||
|
import useSettingStore from '@/stores/settingStore'
|
||||||
|
import Cropper from 'react-easy-crop'
|
||||||
|
import getCroppedImg from '@/utils/cropImage'
|
||||||
|
|
||||||
|
const initialImages = {
|
||||||
|
"bg-1": "bg-1.jpeg",
|
||||||
|
"bg-2": "bg-2.png",
|
||||||
|
"bg-3": "bg-3.png",
|
||||||
|
"bg-6": "bg-6.png",
|
||||||
|
"bg-7": "bg-7.jpeg",
|
||||||
|
"bg-8": "bg-8.png",
|
||||||
|
"bg-9": "bg-9.jpeg",
|
||||||
|
"bg-10": "bg-10.jpg",
|
||||||
|
"bg-11": "bg-11.jpeg",
|
||||||
|
"bg-12": "bg-12.jpg",
|
||||||
|
"bg-13": "bg-13.jpg",
|
||||||
|
"bg-16": "bg-16.jpg",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BackgroundSelector = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [newUrl, setNewUrl] = useState('')
|
||||||
|
const [croppingImage, setCroppingImage] = useState<string | null>(null)
|
||||||
|
const [crop, setCrop] = useState({ x: 0, y: 0 })
|
||||||
|
const [zoom, setZoom] = useState(1)
|
||||||
|
const [croppedAreaPixels, setCroppedAreaPixels] = useState<any>(null)
|
||||||
|
const { background, setBackground, extraBackgrounds, setExtraBackgrounds } = useSettingStore()
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const handleSelect = (img: string) => {
|
||||||
|
setIsOpen(false)
|
||||||
|
setBackground(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddUrl = () => {
|
||||||
|
if (!newUrl.trim()) return setCroppingImage(newUrl)
|
||||||
|
setNewUrl('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveExtra = (url: string) => {
|
||||||
|
setExtraBackgrounds(extraBackgrounds.filter(bg => bg !== url))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUploadFile = (file: File) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = () => setCroppingImage(reader.result as string)
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCropComplete = async () => {
|
||||||
|
if (!croppingImage || !croppedAreaPixels) return
|
||||||
|
const croppedBase64 = await getCroppedImg(croppingImage, croppedAreaPixels)
|
||||||
|
setExtraBackgrounds([croppedBase64, ...extraBackgrounds])
|
||||||
|
setCroppingImage(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const allBackgrounds = [...extraBackgrounds, ...Object.values(initialImages)]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
|
<div className="tooltip tooltip-right" data-tip="Select Background">
|
||||||
|
<button
|
||||||
|
className="group btn btn-primary btn-circle flex items-center justify-center shadow-md transition-all duration-300 hover:scale-110 hover:shadow-lg hover:bg-primary/80"
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
>
|
||||||
|
<ImageIcon size={22} className="text-white transition-all duration-300 group-hover:rotate-6 group-hover:scale-110 group-hover:text-yellow-300" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className="fixed inset-0 z-40 flex items-center justify-center bg-base-200/60 pt-10">
|
||||||
|
<div className="bg-base-200 text-white rounded-xl shadow-xl p-6 w-[90%] max-w-2xl relative">
|
||||||
|
<button className="btn btn-ghost btn-circle absolute top-3 right-3" onClick={() => setIsOpen(false)}>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2 className="text-lg font-semibold mb-4">Choose Background</h2>
|
||||||
|
|
||||||
|
{/* Add via URL */}
|
||||||
|
<div className="flex gap-2 mb-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Paste image URL (https://...)"
|
||||||
|
className="input input-bordered w-full text-info"
|
||||||
|
value={newUrl}
|
||||||
|
onChange={(e) => setNewUrl(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button className="btn btn-success flex items-center gap-1" onClick={handleAddUrl}>
|
||||||
|
<Plus size={16} /> Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Upload from computer */}
|
||||||
|
<div className="flex mb-4">
|
||||||
|
<button
|
||||||
|
className="btn btn-warning flex items-center gap-1"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
|
<Upload size={16} /> Upload from computer
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
className="hidden"
|
||||||
|
ref={fileInputRef}
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
if (file) handleUploadFile(file)
|
||||||
|
e.target.value = ''
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Crop Modal */}
|
||||||
|
{croppingImage && (
|
||||||
|
<div className="fixed inset-0 z-60 flex flex-col items-center justify-center bg-black/70 p-4">
|
||||||
|
<div className="relative w-full max-w-3xl h-[400px] bg-gray-800 rounded-lg">
|
||||||
|
<Cropper
|
||||||
|
image={croppingImage}
|
||||||
|
crop={crop}
|
||||||
|
zoom={zoom}
|
||||||
|
aspect={16 / 9}
|
||||||
|
onCropChange={setCrop}
|
||||||
|
onZoomChange={setZoom}
|
||||||
|
onCropComplete={(_, croppedAreaPixels) => setCroppedAreaPixels(croppedAreaPixels)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="absolute bottom-4 left-1/2 -translate-x-1/2 btn btn-success"
|
||||||
|
onClick={handleCropComplete}
|
||||||
|
>
|
||||||
|
<Check size={20} /> Done
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="absolute top-2 right-2 btn btn-ghost btn-circle"
|
||||||
|
onClick={() => setCroppingImage(null)}
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid grid-cols-3 gap-4 max-h-[60vh] overflow-y-auto">
|
||||||
|
{allBackgrounds.map((value, i) => {
|
||||||
|
const isExtra = i < extraBackgrounds.length
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`relative rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${
|
||||||
|
value === background ? 'border-blue-500' : 'border-transparent hover:border-gray-500'
|
||||||
|
}`}
|
||||||
|
onClick={() => handleSelect(value)}
|
||||||
|
>
|
||||||
|
<img src={value} alt={`bg-${i}`} loading="lazy" className="w-full h-28 object-cover" />
|
||||||
|
{isExtra && (
|
||||||
|
<button
|
||||||
|
className="absolute top-1 right-1 bg-black/50 hover:bg-black/70 text-white rounded-full p-1"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleRemoveExtra(value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X size={14} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
83
frontend/src/components/closeModal/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { motion } from "motion/react"
|
||||||
|
import { AppService } from "@bindings/firefly-launcher/internal/app-service"
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
import useSettingStore from "@/stores/settingStore"
|
||||||
|
|
||||||
|
export default function CloseModal({
|
||||||
|
isOpen,
|
||||||
|
onClose
|
||||||
|
}: {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
}) {
|
||||||
|
if (!isOpen) return null
|
||||||
|
const { closingOption, setClosingOption } = useSettingStore()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||||
|
<div className="relative w-[90%] max-w-2xl 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 flex justify-between items-center">
|
||||||
|
<h3 className="font-bold text-xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-600">
|
||||||
|
Confirm Action
|
||||||
|
</h3>
|
||||||
|
<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={onClose}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pt-2 pb-6">
|
||||||
|
<p className="mb-4 text-lg">
|
||||||
|
Do you want to minimize the application to the system tray or close the application?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center mb-4">
|
||||||
|
<input
|
||||||
|
id="dontAskAgain"
|
||||||
|
type="checkbox"
|
||||||
|
className="checkbox checkbox-sm mr-2"
|
||||||
|
checked={!closingOption.isAsk}
|
||||||
|
onChange={(e) => setClosingOption({ isMinimize: closingOption.isMinimize, isAsk: !e.target.checked })}
|
||||||
|
/>
|
||||||
|
<label htmlFor="dontAskAgain" className="text-sm font-semibold text-accent">
|
||||||
|
Do not ask me again
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 justify-end gap-3">
|
||||||
|
<button
|
||||||
|
className="btn btn-warning"
|
||||||
|
onClick={async () => {
|
||||||
|
onClose()
|
||||||
|
const [success, message] = await AppService.HideApp()
|
||||||
|
if (!success) toast.error(message)
|
||||||
|
if (!closingOption.isAsk) {
|
||||||
|
setClosingOption({ isMinimize: true, isAsk: false })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimize
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-error btn-outline"
|
||||||
|
onClick={async () => {
|
||||||
|
onClose()
|
||||||
|
const [success, message] = await AppService.CloseApp()
|
||||||
|
if (!success) toast.error(message)
|
||||||
|
if (!closingOption.isAsk) {
|
||||||
|
setClosingOption({ isMinimize: false, isAsk: false })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
163
frontend/src/components/header/index.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import useModalStore from "@/stores/modalStore";
|
||||||
|
import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react";
|
||||||
|
import { AppService } from "@bindings/firefly-launcher/internal/app-service";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const { setIsOpenSettingModal } = useModalStore()
|
||||||
|
|
||||||
|
const controlButtons = [
|
||||||
|
{
|
||||||
|
icon: <Settings className="w-5 h-5" />,
|
||||||
|
action: () => setIsOpenSettingModal(true),
|
||||||
|
tip: "Settings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Minus className="w-5 h-5" />,
|
||||||
|
action: () => AppService.MinimizeApp(),
|
||||||
|
tip: "Minimize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <X className="w-5 h-5" />,
|
||||||
|
action: () => AppService.CloseApp(),
|
||||||
|
tip: "Close",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="navbar sticky top-0 z-50 px-3" style={{ '--wails-draggable': 'drag' } as any}>
|
||||||
|
<div className="navbar-start">
|
||||||
|
<div className="dropdown">
|
||||||
|
<div tabIndex={0} role="button" className="btn btn-ghost md:hidden">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className="menu menu-sm dropdown-content bg-black/50 backdrop-blur-md rounded-box z-1 mt-3 w-52 p-2 shadow">
|
||||||
|
<li><Link to="/">Home</Link></li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a>Tools</a>
|
||||||
|
<ul className="p-2">
|
||||||
|
<li><Link to="/language">Language</Link></li>
|
||||||
|
<li><Link to="/diff">Diff</Link></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a>Plugins</a>
|
||||||
|
<ul className="p-2">
|
||||||
|
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
||||||
|
<li><Link to="/srtools">SrTools</Link></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><Link to="/howto">How to?</Link></li>
|
||||||
|
<li><Link to="/about">About</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<img src="/appicon.png" alt="Logo" className='w-13 h-13 rounded-lg mx-2' />
|
||||||
|
<div className="flex flex-col justify-center items-start">
|
||||||
|
<h1 className="text-xl font-bold">
|
||||||
|
<span className="text-emerald-500"
|
||||||
|
style={{
|
||||||
|
textShadow: '0 1px 2px rgba(255, 255, 255, 0.2)',
|
||||||
|
}}
|
||||||
|
>Firefly </span>
|
||||||
|
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500"
|
||||||
|
style={{
|
||||||
|
textShadow: '0 1px 2px rgba(255, 255, 255, 0.2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Launcher
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
className="text-white text-sm"
|
||||||
|
style={{
|
||||||
|
textShadow: '0 1px 2px rgba(0, 0, 0, 0.8)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
By Kain
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="navbar-center hidden md:flex bg-black/40 backdrop-blur-sm rounded-lg shadow-lg">
|
||||||
|
<ul className="menu menu-horizontal px-1 gap-4 text-white">
|
||||||
|
<li>
|
||||||
|
<Link to="/" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
|
||||||
|
<Home size={18} /> Home
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<details>
|
||||||
|
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 transition-colors">
|
||||||
|
<Wrench size={18} /> Tools
|
||||||
|
</summary>
|
||||||
|
<ul className="p-2 bg-black/75 text-white rounded-lg">
|
||||||
|
<li>
|
||||||
|
<Link to="/language" className="flex items-center gap-2 hover:text-cyan-300">
|
||||||
|
<Languages size={18} /> Language
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/diff" className="flex items-center gap-2 hover:text-cyan-300">
|
||||||
|
<Diff size={18} /> Client update
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<details>
|
||||||
|
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 transition-colors">
|
||||||
|
<Puzzle size={18} /> Plugins
|
||||||
|
</summary>
|
||||||
|
<ul className="p-2 bg-black/75 text-white rounded-lg">
|
||||||
|
<li>
|
||||||
|
<Link to="/analysis" className="flex items-center gap-2 hover:text-cyan-300">
|
||||||
|
<TrendingUpDown size={18} /> Analysis (Veritas)
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/srtools" className="flex items-center gap-2 hover:text-cyan-300">
|
||||||
|
<Blend size={18} /> Firefly Tools
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/howto" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
|
||||||
|
<BookOpen size={18} /> How to?
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/about" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
|
||||||
|
<Info size={18} /> About
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="navbar-end flex gap-2 z-52">
|
||||||
|
<div className="flex items-center gap-2 bg-black/40 backdrop-blur-sm rounded-lg">
|
||||||
|
{controlButtons.map((btn, i) => (
|
||||||
|
<div key={i} className="tooltip tooltip-bottom" data-tip={btn.tip}>
|
||||||
|
<button
|
||||||
|
onClick={btn.action}
|
||||||
|
className="btn btn-ghost btn-circle bg-transparent border-none flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<div className="hover:text-cyan-300 transition-colors">
|
||||||
|
{btn.icon}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
98
frontend/src/components/settingModal/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { CheckUpdateLauncher } from "@/helper"
|
||||||
|
import useModalStore from "@/stores/modalStore"
|
||||||
|
import useSettingStore from "@/stores/settingStore"
|
||||||
|
import useLauncherStore from "@/stores/launcherStore"
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
|
||||||
|
export default function SettingModal({
|
||||||
|
isOpen,
|
||||||
|
onClose
|
||||||
|
}: {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
}) {
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
const { setIsOpenSelfUpdateModal } = useModalStore()
|
||||||
|
const { closingOption, setClosingOption } = useSettingStore()
|
||||||
|
const { setUpdateData, updateData } = useLauncherStore()
|
||||||
|
const CheckUpdate = async () => {
|
||||||
|
const launcherData = await CheckUpdateLauncher()
|
||||||
|
if (!launcherData.isUpdate) {
|
||||||
|
toast.success("Launcher is already up to date")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setUpdateData({
|
||||||
|
server: updateData.server,
|
||||||
|
proxy: updateData.proxy,
|
||||||
|
launcher: launcherData
|
||||||
|
})
|
||||||
|
|
||||||
|
setIsOpenSelfUpdateModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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-md bg-base-100 text-base-content rounded-2xl border border-purple-500/30 shadow-2xl shadow-purple-500/30 p-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h3 className="font-extrabold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-500">
|
||||||
|
Settings
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
className="btn btn-circle btn-sm bg-red-600 hover:bg-red-700 text-white border-none shadow-lg"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{/* Section 1: Launcher Update */}
|
||||||
|
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
|
||||||
|
<h4 className="font-bold text-lg mb-2">Launcher Update</h4>
|
||||||
|
<p className="text-sm text-info mb-3">
|
||||||
|
Check if your launcher is up to date.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-400 hover:to-red-500 text-white shadow-md hover:shadow-lg transition-all duration-200"
|
||||||
|
onClick={CheckUpdate}
|
||||||
|
>
|
||||||
|
Check for Launcher Updates
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section 2: Closing Option */}
|
||||||
|
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
|
||||||
|
<h4 className="font-bold text-lg mb-2">Closing Options</h4>
|
||||||
|
<label className="flex items-start gap-3 cursor-pointer select-none">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="checkbox checkbox-primary w-5 h-5 mt-1"
|
||||||
|
checked={!closingOption.isAsk}
|
||||||
|
onChange={(e) => {
|
||||||
|
setClosingOption({
|
||||||
|
isMinimize: closingOption.isMinimize,
|
||||||
|
isAsk: !e.target.checked
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-base font-medium text-info">
|
||||||
|
Set do not ask again
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-accent">
|
||||||
|
Next time you close the app, it will automatically{" "}
|
||||||
|
{closingOption.isMinimize ? "minimize to system tray" : "quit the app"}{" "}
|
||||||
|
without asking.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function ThemeController() {
|
|
||||||
const [theme, setTheme] = useState(localStorage.getItem("theme") ?? "light");
|
|
||||||
const handleToggle = (e: any) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
setTheme("cupcake");
|
|
||||||
} else {
|
|
||||||
setTheme("night");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem('theme', theme!)
|
|
||||||
const localTheme = localStorage.getItem('theme')
|
|
||||||
document.querySelector('html')?.setAttribute('data-theme', localTheme!)
|
|
||||||
}, [theme]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label className="toggle text-base-content">
|
|
||||||
<input type="checkbox" onChange={handleToggle} className="theme-controller" />
|
|
||||||
|
|
||||||
<svg aria-label="moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g strokeLinejoin="round" strokeLinecap="round" strokeWidth="2" fill="none" stroke="currentColor"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></g></svg>
|
|
||||||
<svg aria-label="sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g strokeLinejoin="round" strokeLinecap="round" strokeWidth="2" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></g></svg>
|
|
||||||
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
64
frontend/src/components/updateModal/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { motion } from "framer-motion"
|
||||||
|
|
||||||
|
interface UpdateModalProps {
|
||||||
|
isOpen: boolean
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
buttons: {
|
||||||
|
text: string
|
||||||
|
onClick: () => Promise<void> | void
|
||||||
|
variant?: "primary" | "error" | "outline"
|
||||||
|
}[]
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UpdateModal({ isOpen, title, message, buttons, onClose }: UpdateModalProps) {
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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={onClose}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</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">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pb-6">
|
||||||
|
<div className="mb-6">
|
||||||
|
<p className="text-accent text-lg">{message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
{buttons.map((btn, idx) => (
|
||||||
|
<motion.button
|
||||||
|
key={idx}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className={`btn ${
|
||||||
|
btn.variant === "primary"
|
||||||
|
? "btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||||
|
: btn.variant === "error"
|
||||||
|
? "btn-error"
|
||||||
|
: "btn-outline btn-error"
|
||||||
|
}`}
|
||||||
|
onClick={btn.onClick}
|
||||||
|
>
|
||||||
|
{btn.text}
|
||||||
|
</motion.button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,29 +2,17 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Events } from "@wailsio/runtime";
|
import { Events } from "@wailsio/runtime";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import useSettingStore from "@/stores/settingStore";
|
||||||
|
import { AppService } from "@bindings/firefly-launcher/internal/app-service";
|
||||||
|
import useModalStore from "@/stores/modalStore";
|
||||||
|
import useDiffStore from "@/stores/diffStore";
|
||||||
|
import useLauncherStore from "@/stores/launcherStore";
|
||||||
|
|
||||||
export function useGlobalEvents({
|
export function useGlobalEvents() {
|
||||||
setGameRunning,
|
const { setIsOpenCloseModal } = useModalStore()
|
||||||
setServerRunning,
|
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
|
||||||
setProxyRunning,
|
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
|
||||||
setProgressUpdate,
|
|
||||||
setMaxProgressUpdate,
|
|
||||||
setProgressDownload,
|
|
||||||
setDownloadSpeed,
|
|
||||||
setMessageUpdate,
|
|
||||||
setStageType,
|
|
||||||
|
|
||||||
}: {
|
|
||||||
setGameRunning: (v: boolean) => void;
|
|
||||||
setServerRunning: (v: boolean) => void;
|
|
||||||
setProxyRunning: (v: boolean) => void;
|
|
||||||
setProgressUpdate: (v: number) => void;
|
|
||||||
setMaxProgressUpdate: (v: number) => void;
|
|
||||||
setProgressDownload: (v: number) => void;
|
|
||||||
setDownloadSpeed: (v: string) => void;
|
|
||||||
setMessageUpdate: (v: string) => void;
|
|
||||||
setStageType: (v: string) => void,
|
|
||||||
}) {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onGameExit = () => setGameRunning(false);
|
const onGameExit = () => setGameRunning(false);
|
||||||
const onServerExit = () => setServerRunning(false);
|
const onServerExit = () => setServerRunning(false);
|
||||||
@@ -64,6 +52,20 @@ export function useGlobalEvents({
|
|||||||
const { message } = event.data[0];
|
const { message } = event.data[0];
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
});
|
});
|
||||||
|
Events.On("window:close", async () => {
|
||||||
|
const option = useSettingStore.getState().closingOption
|
||||||
|
if (option.isAsk) {
|
||||||
|
setIsOpenCloseModal(true);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (option.isMinimize) {
|
||||||
|
const [success, message] = await AppService.MinimizeApp()
|
||||||
|
if (!success) toast.error(message)
|
||||||
|
} else {
|
||||||
|
const [success, message] = await AppService.CloseApp()
|
||||||
|
if (!success) toast.error(message)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Events.Off("download:server");
|
Events.Off("download:server");
|
||||||
@@ -75,6 +77,7 @@ export function useGlobalEvents({
|
|||||||
Events.Off("diff:message");
|
Events.Off("diff:message");
|
||||||
Events.Off("diff:stage");
|
Events.Off("diff:stage");
|
||||||
Events.Off("version:check");
|
Events.Off("version:check");
|
||||||
|
Events.Off("window:close");
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
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>
|
||||||
<p className="text-lg leading-relaxed">
|
<p className="text-lg leading-relaxed">
|
||||||
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>.
|
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-accent">Tailwind CSS</span> and <span className="text-accent">DaisyUI</span>.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg leading-relaxed">
|
<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.
|
My goal is to make tools that are fast, efficient, and enjoyable to use — and this launcher is just the beginning.
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export default function AnalysisPage() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://sranalysis.kain.id.vn/"
|
href="https://sranalysis.kain.id.vn/"
|
||||||
className="link link-warning font-mono text-sm break-all"
|
className="link link-accent font-mono text-sm break-all"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@@ -79,7 +79,7 @@ export default function AnalysisPage() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://firefly-sranalysis.vercel.app/"
|
href="https://firefly-sranalysis.vercel.app/"
|
||||||
className="link link-warning font-mono text-sm break-all"
|
className="link link-accent font-mono text-sm break-all"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ export default function DiffPage() {
|
|||||||
<div className="w-full p-4">
|
<div className="w-full p-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex justify-center items-center text-sm text-white/80">
|
<div className="flex justify-center items-center text-sm text-white/80">
|
||||||
<span className="font-bold text-lg text-warning">{stageType}:</span>
|
<span className="font-bold text-lg text-accent">{stageType}:</span>
|
||||||
<div className="flex items-center gap-4 ml-2">
|
<div className="flex items-center gap-4 ml-2">
|
||||||
{stageType !== 'Cut Data' && <span className="text-white font-bold">{progressUpdate.toFixed(0)} / {maxProgressUpdate.toFixed(0)}</span>}
|
{stageType !== 'Cut Data' && <span className="text-white font-bold">{progressUpdate.toFixed(0)} / {maxProgressUpdate.toFixed(0)}</span>}
|
||||||
{stageType === 'Cut Data' && <span className="text-white font-bold truncate max-w-full overflow-hidden whitespace-nowrap">{messageUpdate}</span>}
|
{stageType === 'Cut Data' && <span className="text-white font-bold truncate max-w-full overflow-hidden whitespace-nowrap">{messageUpdate}</span>}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
export default function FireflyToolsPage() {
|
export default function FireflyToolsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
||||||
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
||||||
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
|
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
|
||||||
|
|
||||||
{/* Section 1: About SR Tools */}
|
{/* Section 1: About SR Tools */}
|
||||||
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
|
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
|
||||||
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
|
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
|
||||||
<span>ℹ️</span>
|
<span>ℹ️</span>
|
||||||
<span>About Firefly Tools</span>
|
<span>About Firefly Tools</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3 text-blue-700">
|
<div className="space-y-3 text-blue-700">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-blue-600 text-lg">🏠</div>
|
<div className="text-blue-600 text-lg">🏠</div>
|
||||||
<p>
|
<p>
|
||||||
This site is a another version of {" "}
|
This site is a another version of {" "}
|
||||||
<span className="font-semibold text-success">Firefly Tools {" "}</span>
|
<span className="font-semibold text-success">Firefly Tools {" "}</span>
|
||||||
developed by {" "}
|
developed by {" "}
|
||||||
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
<span className="font-semibold text-accent">Me {"(Kain)"}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span className="text-blue-600 text-lg">🏆</span>
|
<span className="text-blue-600 text-lg">🏆</span>
|
||||||
@@ -30,14 +30,14 @@ export default function FireflyToolsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://srtools.kain.id.vn/"
|
href="https://srtools.kain.id.vn/"
|
||||||
className="link link-warning font-mono text-sm break-all"
|
className="link link-accent font-mono text-sm break-all"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
https://srtools.kain.id.vn/
|
https://srtools.kain.id.vn/
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span className="text-blue-600 text-lg">🔄</span>
|
<span className="text-blue-600 text-lg">🔄</span>
|
||||||
@@ -45,7 +45,7 @@ export default function FireflyToolsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://firefly-srtools.vercel.app/"
|
href="https://firefly-srtools.vercel.app/"
|
||||||
className="link link-warning font-mono text-sm break-all"
|
className="link link-accent font-mono text-sm break-all"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@@ -53,106 +53,106 @@ export default function FireflyToolsPage() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-blue-600 text-lg">👨💻</div>
|
<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>
|
<p>The original tool was created by a third-party developer named <span className="font-semibold text-accent">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-blue-600 text-lg">🔗</div>
|
<div className="text-blue-600 text-lg">🔗</div>
|
||||||
<p>There is also a more modern version by the same author available at{" "}
|
<p>There is also a more modern version by the same author available at{" "}
|
||||||
<a
|
<a
|
||||||
href="https://srtools.neonteam.dev/"
|
href="https://srtools.neonteam.dev/"
|
||||||
className="link link-warning"
|
className="link link-accent"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
srtools.neonteam.dev
|
srtools.neonteam.dev
|
||||||
</a>
|
</a>
|
||||||
{" "}and the original version at{" "}
|
{" "}and the original version at{" "}
|
||||||
<a
|
<a
|
||||||
href="https://srtools.pages.dev/"
|
href="https://srtools.pages.dev/"
|
||||||
className="link link-warning"
|
className="link link-accent"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
srtools.pages.dev
|
srtools.pages.dev
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 2: Main Features */}
|
{/* Section 2: Main Features */}
|
||||||
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
|
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
|
||||||
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
|
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
|
||||||
<span>🔧</span>
|
<span>🔧</span>
|
||||||
<span>Main Features</span>
|
<span>Main Features</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3 text-green-700">
|
<div className="space-y-3 text-green-700">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">⚙️</div>
|
<div className="text-green-600 text-lg">⚙️</div>
|
||||||
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p>
|
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">🔌</div>
|
<div className="text-green-600 text-lg">🔌</div>
|
||||||
<p>Instantly apply setups to <span className="font-semibold text-warning">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> — no manual file uploads required.</p>
|
<p>Instantly apply setups to <span className="font-semibold text-accent">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> — no manual file uploads required.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-2xl">✨</div>
|
<div className="text-green-600 text-2xl">✨</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
|
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
|
||||||
<p className="text-green-700 mt-1">
|
<p className="text-green-700 mt-1">
|
||||||
Enhance your <span className="font-semibold text-warning">Firefly GO Server</span> experience with extra features:
|
Enhance your <span className="font-semibold text-accent">Firefly GO Server</span> experience with extra features:
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
|
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
|
||||||
<li>🎭 <span className="font-medium">Hidden Game UI</span> — remove the entire game interface.</li>
|
<li>🎭 <span className="font-medium">Hidden Game UI</span> — remove the entire game interface.</li>
|
||||||
<li>🚫 <span className="font-medium">Disable Censorship</span> — get rid of Lens Flare censor 💀.</li>
|
<li>🚫 <span className="font-medium">Disable Censorship</span> — get rid of Lens Flare censor 💀.</li>
|
||||||
<li>🧪 <span className="font-medium">Theorycraft Mode</span> — configure HP, cycles, and more via the web.</li>
|
<li>🧪 <span className="font-medium">Theorycraft Mode</span> — configure HP, cycles, and more via the web.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">📂</div>
|
<div className="text-green-600 text-lg">📂</div>
|
||||||
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p>
|
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">⚡</div>
|
<div className="text-green-600 text-lg">⚡</div>
|
||||||
<p>Fast testing workflow — no sync cooldowns, instant in-game updates.</p>
|
<p>Fast testing workflow — no sync cooldowns, instant in-game updates.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 3: Getting Started */}
|
{/* Section 3: Getting Started */}
|
||||||
<div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg">
|
<div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg">
|
||||||
<h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4">
|
<h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4">
|
||||||
<span>🚀</span>
|
<span>🚀</span>
|
||||||
<span>Getting Started</span>
|
<span>Getting Started</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3 text-purple-700">
|
<div className="space-y-3 text-purple-700">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-purple-600 text-lg">1️⃣</div>
|
<div className="text-purple-600 text-lg">1️⃣</div>
|
||||||
<p>Access the tool through your browser at the self-hosted instance.</p>
|
<p>Access the tool through your browser at the self-hosted instance.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-purple-600 text-lg">2️⃣</div>
|
<div className="text-purple-600 text-lg">2️⃣</div>
|
||||||
<p>Configure your character builds with the intuitive web interface.</p>
|
<p>Configure your character builds with the intuitive web interface.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-purple-600 text-lg">3️⃣</div>
|
<div className="text-purple-600 text-lg">3️⃣</div>
|
||||||
<p>Use <span className="font-semibold">Connect PS</span> feature to instantly sync with your private server.</p>
|
<p>Use <span className="font-semibold">Connect PS</span> feature to instantly sync with your private server.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-purple-600 text-lg">4️⃣</div>
|
<div className="text-purple-600 text-lg">4️⃣</div>
|
||||||
<p>Test your builds in-game with real-time updates and modifications.</p>
|
<p>Test your builds in-game with real-time updates and modifications.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center pt-4">
|
<div className="text-center pt-4">
|
||||||
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link>
|
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ export default function HowToPage() {
|
|||||||
|
|
||||||
<p className="text-blue-700 mb-4">
|
<p className="text-blue-700 mb-4">
|
||||||
Below are in-game chat commands you can use. Some commands require you to enable{" "}
|
Below are in-game chat commands you can use. Some commands require you to enable{" "}
|
||||||
<span className="font-semibold text-warning">Theorycraft Mode</span>.
|
<span className="font-semibold text-accent">Theorycraft Mode</span>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Theorycraft Mode Warning */}
|
{/* Theorycraft Mode Warning */}
|
||||||
|
|||||||
@@ -208,10 +208,10 @@ export default function LanguagePage() {
|
|||||||
|
|
||||||
<div className="bg-warning/5 rounded-lg p-2 border border-warning/30">
|
<div className="bg-warning/5 rounded-lg p-2 border border-warning/30">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<Mic size={20} className="text-warning" />
|
<Mic size={20} className="text-accent" />
|
||||||
<span className="font-bold text-warning">Voice Language</span>
|
<span className="font-bold text-accent">Voice Language</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-warning">
|
<p className="text-2xl font-bold text-accent">
|
||||||
{getLanguageLabel(voiceLang)}
|
{getLanguageLabel(voiceLang)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,7 +250,7 @@ export default function LanguagePage() {
|
|||||||
|
|
||||||
{/* Voice Language */}
|
{/* Voice Language */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="flex text-sm font-medium text-warning items-center gap-2">
|
<label className="flex text-sm font-medium text-accent items-center gap-2">
|
||||||
<Mic size={16} />
|
<Mic size={16} />
|
||||||
Voice Language
|
Voice Language
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import useLauncherStore from '@/stores/launcherStore';
|
|||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { Link } from '@tanstack/react-router';
|
import { Link } from '@tanstack/react-router';
|
||||||
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
|
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
|
||||||
|
import UpdateModal from '@/components/updateModal';
|
||||||
|
import { BackgroundSelector } from '@/components/backgroudModal';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function LauncherPage() {
|
export default function LauncherPage() {
|
||||||
const { gamePath,
|
const { gamePath,
|
||||||
@@ -20,7 +24,9 @@ export default function LauncherPage() {
|
|||||||
gameDir,
|
gameDir,
|
||||||
serverVersion,
|
serverVersion,
|
||||||
proxyVersion,
|
proxyVersion,
|
||||||
|
background
|
||||||
} = useSettingStore()
|
} = useSettingStore()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpenDownloadDataModal,
|
isOpenDownloadDataModal,
|
||||||
isOpenUpdateDataModal,
|
isOpenUpdateDataModal,
|
||||||
@@ -29,6 +35,7 @@ export default function LauncherPage() {
|
|||||||
setIsOpenUpdateDataModal,
|
setIsOpenUpdateDataModal,
|
||||||
setIsOpenSelfUpdateModal
|
setIsOpenSelfUpdateModal
|
||||||
} = useModalStore()
|
} = useModalStore()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
downloadType,
|
downloadType,
|
||||||
@@ -54,6 +61,33 @@ export default function LauncherPage() {
|
|||||||
setUpdateData,
|
setUpdateData,
|
||||||
} = useLauncherStore()
|
} = useLauncherStore()
|
||||||
|
|
||||||
|
const widgetLinks = [
|
||||||
|
{
|
||||||
|
tooltip: "Firefly SRAnalysis",
|
||||||
|
href: "https://sranalysis.kain.id.vn/",
|
||||||
|
img: "https://sranalysis.kain.id.vn/ff-sranalysis.png",
|
||||||
|
btnClass: "btn-primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tooltip: "Firefly SRTools",
|
||||||
|
href: "https://srtools.kain.id.vn/",
|
||||||
|
img: "https://srtools.kain.id.vn/ff-srtool.png",
|
||||||
|
btnClass: "btn-secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tooltip: "Amazing's SRTools (Original UI)",
|
||||||
|
href: "https://srtools.pages.dev/",
|
||||||
|
img: "https://icons.duckduckgo.com/ip3/srtools.pages.dev.ico",
|
||||||
|
btnClass: "btn-primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tooltip: "Amazing's SRTools (Modern UI)",
|
||||||
|
href: "https://srtools.neonteam.dev/",
|
||||||
|
img: "https://icons.duckduckgo.com/ip3/srtools.neonteam.dev.ico",
|
||||||
|
btnClass: "btn-secondary"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const check = async () => {
|
const check = async () => {
|
||||||
if (!serverVersion || !proxyVersion) {
|
if (!serverVersion || !proxyVersion) {
|
||||||
@@ -200,18 +234,18 @@ export default function LauncherPage() {
|
|||||||
setIsDownloading(true)
|
setIsDownloading(true)
|
||||||
if (updateData.launcher.isUpdate) {
|
if (updateData.launcher.isUpdate) {
|
||||||
await UpdateLauncher(updateData.launcher.version)
|
await UpdateLauncher(updateData.launcher.version)
|
||||||
setUpdateData({...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version }})
|
setUpdateData({ ...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version } })
|
||||||
setIsOpenSelfUpdateModal(true)
|
setIsOpenSelfUpdateModal(true)
|
||||||
}
|
}
|
||||||
if (updateData.server.isUpdate || !updateData.server.isExists) {
|
if (updateData.server.isUpdate || !updateData.server.isExists) {
|
||||||
await UpdateServer(updateData.server.version)
|
await UpdateServer(updateData.server.version)
|
||||||
setServerReady(true)
|
setServerReady(true)
|
||||||
setUpdateData({...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version }})
|
setUpdateData({ ...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version } })
|
||||||
}
|
}
|
||||||
if (updateData.proxy.isUpdate || !updateData.proxy.isExists) {
|
if (updateData.proxy.isUpdate || !updateData.proxy.isExists) {
|
||||||
await UpdateProxy(updateData.proxy.version)
|
await UpdateProxy(updateData.proxy.version)
|
||||||
setProxyReady(true)
|
setProxyReady(true)
|
||||||
setUpdateData({...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version }})
|
setUpdateData({ ...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setDownloadType("")
|
setDownloadType("")
|
||||||
@@ -235,78 +269,39 @@ export default function LauncherPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative min-h-fit overflow-hidden">
|
<div className="relative min-h-fit overflow-hidden">
|
||||||
<div
|
<img
|
||||||
className="fixed inset-0 z-0 w-full h-full"
|
src={background}
|
||||||
style={{
|
alt="background"
|
||||||
backgroundImage: "url('/bg.jpg')",
|
className="fixed inset-0 w-full h-full object-cover z-0"
|
||||||
backgroundSize: "cover",
|
onError={(e) => {
|
||||||
backgroundPosition: "center",
|
const target = e.currentTarget as HTMLImageElement
|
||||||
backgroundRepeat: "no-repeat"
|
target.src = "bg-12.jpg"
|
||||||
}}
|
}}
|
||||||
></div>
|
/>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="hidden sm:flex fixed z-10 items-center justify-between p-6">
|
<header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4 ">
|
||||||
<div className="text-2xl font-bold text-white">Firefly GO</div>
|
<div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">Firefly GO</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="hidden sm:flex fixed top-1/4 left-4 z-10 flex-col space-y-3 bg-black/30 backdrop-blur-md rounded-xl p-3 shadow-lg">
|
<div className="hidden sm:flex fixed top-1/4 right-4 z-10 flex-col space-y-3 bg-white/5 rounded-xl p-3 shadow-lg">
|
||||||
|
{widgetLinks.map((link, idx) => (
|
||||||
|
<div key={idx} className="tooltip tooltip-left" data-tip={link.tooltip}>
|
||||||
|
<a
|
||||||
|
className={`btn btn-circle ${link.btnClass}`}
|
||||||
|
target="_blank"
|
||||||
|
href={link.href}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={link.img}
|
||||||
|
alt={link.tooltip}
|
||||||
|
className="w-8 h-8 rounded-full"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
<div className="tooltip tooltip-right" data-tip="Firefly SRAnalysis">
|
<div className="tooltip tooltip-left" data-tip="How to use all tools & commands">
|
||||||
<a
|
|
||||||
className="btn btn-circle btn-primary"
|
|
||||||
target="_blank"
|
|
||||||
href="https://sranalysis.kain.id.vn/"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://sranalysis.kain.id.vn/ff-sranalysis.png"
|
|
||||||
alt="SRAnalysis Logo"
|
|
||||||
className="w-8 h-8 rounded-full"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="tooltip tooltip-right" data-tip="Firefly SRTools">
|
|
||||||
<a
|
|
||||||
className="btn btn-circle btn-secondary"
|
|
||||||
target="_blank"
|
|
||||||
href="https://srtools.kain.id.vn/"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://srtools.kain.id.vn/ff-srtool.png"
|
|
||||||
alt="SRTools Logo"
|
|
||||||
className="w-8 h-8 rounded-full"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="tooltip tooltip-right" data-tip="Amazing's SRTools (Original UI)">
|
|
||||||
<a
|
|
||||||
className="btn btn-circle btn-primary"
|
|
||||||
target="_blank"
|
|
||||||
href="https://srtools.pages.dev/"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://icons.duckduckgo.com/ip3/srtools.pages.dev.ico"
|
|
||||||
alt="SRTools Logo"
|
|
||||||
className="w-8 h-8 rounded-full"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="tooltip tooltip-right" data-tip="Amazing's SRTools (Modern UI)">
|
|
||||||
<a
|
|
||||||
className="btn btn-circle btn-secondary"
|
|
||||||
target="_blank"
|
|
||||||
href="https://srtools.neonteam.dev/"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://icons.duckduckgo.com/ip3/srtools.neonteam.dev.ico"
|
|
||||||
alt="SRTools Logo"
|
|
||||||
className="w-8 h-8 rounded-full"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="tooltip tooltip-right" data-tip="How to use all tools & commands">
|
|
||||||
<Link
|
<Link
|
||||||
to="/howto"
|
to="/howto"
|
||||||
className="btn btn-warning btn-circle"
|
className="btn btn-warning btn-circle"
|
||||||
@@ -316,6 +311,9 @@ export default function LauncherPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden sm:flex fixed top-1/2 left-4 z-10 ">
|
||||||
|
<BackgroundSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Bottom Panel */}
|
{/* Bottom Panel */}
|
||||||
{serverReady && proxyReady && !isDownloading && (
|
{serverReady && proxyReady && !isDownloading && (
|
||||||
@@ -345,32 +343,33 @@ export default function LauncherPage() {
|
|||||||
</div>
|
</div>
|
||||||
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||||
<li><button onClick={handlePickFile}>Change Game Path</button></li>
|
<li><button onClick={handlePickFile}>Change Game Path</button></li>
|
||||||
<li><button
|
<li>
|
||||||
onClick={async () => {
|
<button
|
||||||
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
onClick={async () => {
|
||||||
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||||
const launcherData = await CheckUpdateLauncher()
|
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||||
setUpdateData({
|
setUpdateData({
|
||||||
server: serverData,
|
server: serverData,
|
||||||
proxy: proxyData,
|
proxy: proxyData,
|
||||||
launcher: launcherData
|
launcher: updateData.launcher
|
||||||
})
|
})
|
||||||
if (launcherData.isUpdate) {
|
|
||||||
setIsOpenSelfUpdateModal(true)
|
if (!serverData.isExists || !proxyData.isExists) {
|
||||||
return
|
setIsOpenDownloadDataModal(true)
|
||||||
}
|
return
|
||||||
if (!serverData.isExists || !proxyData.isExists) {
|
}
|
||||||
setIsOpenDownloadDataModal(true)
|
if (serverData.isUpdate || proxyData.isUpdate) {
|
||||||
return
|
setIsOpenUpdateDataModal(true)
|
||||||
}
|
return
|
||||||
if (serverData.isUpdate || proxyData.isUpdate) {
|
}
|
||||||
setIsOpenUpdateDataModal(true)
|
toast.success("No updates available")
|
||||||
return
|
}}>
|
||||||
}
|
Check for Updates Server & Proxy
|
||||||
toast.success("No updates available")
|
</button>
|
||||||
}}>
|
</li>
|
||||||
Check for Updates
|
<li>
|
||||||
</button></li>
|
|
||||||
|
</li>
|
||||||
<li><button disabled={!serverPath} onClick={() => {
|
<li><button disabled={!serverPath} onClick={() => {
|
||||||
if (serverPath) {
|
if (serverPath) {
|
||||||
FSService.OpenFolder("./server")
|
FSService.OpenFolder("./server")
|
||||||
@@ -395,34 +394,35 @@ export default function LauncherPage() {
|
|||||||
|
|
||||||
{/* Downloading */}
|
{/* Downloading */}
|
||||||
{isDownloading && (
|
{isDownloading && (
|
||||||
updateData.proxy.isUpdate
|
updateData.proxy.isUpdate
|
||||||
|| updateData.server.isUpdate
|
|| updateData.server.isUpdate
|
||||||
|| !updateData.proxy.isExists
|
|| !updateData.proxy.isExists
|
||||||
|| !updateData.server.isExists
|
|| !updateData.server.isExists
|
||||||
) && (
|
) && (
|
||||||
<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="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="space-y-3">
|
||||||
<div className="flex justify-center items-center text-sm text-white/80">
|
<div className="flex justify-center items-center text-sm text-white/80">
|
||||||
<span>{downloadType}</span>
|
<span>{downloadType}</span>
|
||||||
<div className="flex items-center gap-4 ml-4">
|
<div className="flex items-center gap-4 ml-4">
|
||||||
<span className="text-cyan-400 font-semibold">{downloadSpeed}</span>
|
<span className="text-cyan-400 font-semibold">{downloadSpeed}</span>
|
||||||
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
|
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-white/20 rounded-full h-2 overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
animate={{ width: `${progressDownload}%` }}
|
||||||
|
transition={{ type: "tween", ease: "linear", duration: 0.03 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-center text-xs text-white/60">
|
||||||
|
{progressDownload < 100 ? 'Please wait...' : 'Complete!'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-white/20 rounded-full h-2 overflow-hidden">
|
|
||||||
<motion.div
|
|
||||||
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
|
|
||||||
initial={{ width: 0 }}
|
|
||||||
animate={{ width: `${progressDownload}%` }}
|
|
||||||
transition={{ type: "tween", ease: "linear", duration: 0.03 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="text-center text-xs text-white/60">
|
|
||||||
{progressDownload < 100 ? 'Please wait...' : 'Complete!'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
{isDownloading && updateData.launcher.isUpdate && (
|
{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="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">
|
<div className="space-y-3 text-sm text-white/80 text-center">
|
||||||
@@ -430,10 +430,10 @@ export default function LauncherPage() {
|
|||||||
<div className="flex justify-center items-center gap-4 ml-4">
|
<div className="flex justify-center items-center gap-4 ml-4">
|
||||||
<span
|
<span
|
||||||
className={`font-bold ${downloadType === "update:launcher:downloading"
|
className={`font-bold ${downloadType === "update:launcher:downloading"
|
||||||
? "text-yellow-200 text-2xl"
|
? "text-yellow-200 text-2xl"
|
||||||
: downloadType === "update:launcher:success"
|
: downloadType === "update:launcher:success"
|
||||||
? "text-emerald-200 text-xl"
|
? "text-emerald-200 text-xl"
|
||||||
: "text-red-200 text-xl"
|
: "text-red-200 text-xl"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{downloadType === "update:launcher:downloading" && "Updating launcher"}
|
{downloadType === "update:launcher:downloading" && "Updating launcher"}
|
||||||
@@ -468,149 +468,86 @@ export default function LauncherPage() {
|
|||||||
|
|
||||||
{/* Version Info */}
|
{/* Version Info */}
|
||||||
{serverReady && proxyReady && !isDownloading && (
|
{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">
|
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-white/5 rounded-lg p-2 shadow">
|
||||||
<p className="text-primary">Version server: {serverVersion}</p>
|
<p className="text-pink-600 "
|
||||||
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
|
style={{
|
||||||
<p className="mt-2 text-success">Version launcher: {launcherVersion}</p>
|
textShadow: `
|
||||||
|
0.5px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
0 0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0 -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0.5px 0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0.5px -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px 0.5px rgba(255, 255, 255, 0.5)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
>Version server: {serverVersion}</p>
|
||||||
|
<p
|
||||||
|
className="mt-2 text-purple-600"
|
||||||
|
style={{
|
||||||
|
textShadow: `
|
||||||
|
0.5px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
0 0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0 -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0.5px 0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0.5px -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px 0.5px rgba(255, 255, 255, 0.5)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
>Version proxy: {proxyVersion}</p>
|
||||||
|
<p className="mt-2 text-cyan-600 "
|
||||||
|
style={{
|
||||||
|
textShadow: `
|
||||||
|
0.5px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
0 0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0 -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0.5px 0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
0.5px -0.5px rgba(255, 255, 255, 0.5),
|
||||||
|
-0.5px 0.5px rgba(255, 255, 255, 0.5)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
>Version launcher: {launcherVersion}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{isOpenUpdateDataModal && (
|
<UpdateModal
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
isOpen={isOpenUpdateDataModal}
|
||||||
<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">
|
onClose={() => setIsOpenUpdateDataModal(false)}
|
||||||
<motion.button
|
title="Update Data"
|
||||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
message="Do you want to update data server and proxy?"
|
||||||
transition={{ duration: 0.2 }}
|
buttons={[
|
||||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
{ text: "No", onClick: () => setIsOpenUpdateDataModal(false), variant: "outline" },
|
||||||
onClick={() => setIsOpenUpdateDataModal(false)}
|
{ text: "Yes", onClick: async () => { setIsOpenUpdateDataModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||||
>
|
]}
|
||||||
✕
|
/>
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
<UpdateModal
|
||||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
isOpen={isOpenDownloadDataModal}
|
||||||
Update Data
|
onClose={() => setIsOpenDownloadDataModal(false)}
|
||||||
</h3>
|
title="Download Data"
|
||||||
</div>
|
message="Data server and proxy download required"
|
||||||
|
buttons={[
|
||||||
|
{ text: "Download", onClick: async () => { setIsOpenDownloadDataModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="px-6 pb-6">
|
<UpdateModal
|
||||||
<div className="mb-6">
|
isOpen={isOpenSelfUpdateModal}
|
||||||
<p className="text-warning text-lg">
|
onClose={() => setIsOpenSelfUpdateModal(false)}
|
||||||
Do you want to update data server and proxy?
|
title="Update Launcher"
|
||||||
</p>
|
message="Do you want to update launcher?"
|
||||||
</div>
|
buttons={[
|
||||||
|
{ text: "No", onClick: () => setIsOpenSelfUpdateModal(false), variant: "outline" },
|
||||||
|
{ text: "Yes", onClick: async () => { setIsOpenSelfUpdateModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end gap-3">
|
|
||||||
<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 () => {
|
|
||||||
setIsOpenUpdateDataModal(false)
|
|
||||||
await handlerUpdateData()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
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 handlerUpdateData()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
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 handlerUpdateData()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Yes
|
|
||||||
</motion.button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +1,31 @@
|
|||||||
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
|
import { createRootRoute, Outlet } from '@tanstack/react-router'
|
||||||
import ThemeController from '../components/themeController'
|
|
||||||
import { ToastContainer } from 'react-toastify'
|
import { ToastContainer } from 'react-toastify'
|
||||||
import { useGlobalEvents } from '@/hooks';
|
import { useGlobalEvents } from '@/hooks';
|
||||||
import useLauncherStore from '@/stores/launcherStore';
|
import useModalStore from '@/stores/modalStore';;
|
||||||
import useDiffStore from '@/stores/diffStore';
|
import SettingModal from '@/components/settingModal';
|
||||||
|
import CloseModal from '@/components/closeModal';
|
||||||
|
import Header from '@/components/header';
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootLayout
|
component: RootLayout
|
||||||
})
|
})
|
||||||
|
|
||||||
function RootLayout() {
|
function RootLayout() {
|
||||||
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
|
const { setIsOpenCloseModal, isOpenCloseModal, isOpenSettingModal, setIsOpenSettingModal } = useModalStore()
|
||||||
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
|
|
||||||
useGlobalEvents({
|
useGlobalEvents();
|
||||||
setGameRunning,
|
|
||||||
setServerRunning,
|
|
||||||
setProxyRunning,
|
|
||||||
setProgressUpdate,
|
|
||||||
setMaxProgressUpdate,
|
|
||||||
setProgressDownload,
|
|
||||||
setDownloadSpeed,
|
|
||||||
setMessageUpdate,
|
|
||||||
setStageType
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="navbar bg-base-100 shadow-sm sticky top-0 z-50 px-3">
|
<Header />
|
||||||
<div className="navbar-start">
|
|
||||||
<div className="dropdown">
|
|
||||||
<div tabIndex={0} role="button" className="btn btn-ghost md:hidden">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabIndex={0}
|
|
||||||
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow">
|
|
||||||
<li><Link to="/">Home</Link></li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a>Tools</a>
|
|
||||||
<ul className="p-2">
|
|
||||||
<li><Link to="/language">Language</Link></li>
|
|
||||||
<li><Link to="/diff">Diff</Link></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a>Plugins</a>
|
|
||||||
<ul className="p-2">
|
|
||||||
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
|
||||||
<li><Link to="/srtools">SrTools</Link></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><Link to="/howto">How to?</Link></li>
|
|
||||||
<li><Link to="/about">About</Link></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<img src="/appicon.png" alt="Logo" className='w-13 h-13 rounded-lg mx-2' />
|
|
||||||
<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">
|
|
||||||
<ul className="menu menu-horizontal px-1 gap-4">
|
|
||||||
<li><Link to="/">Home</Link></li>
|
|
||||||
<li>
|
|
||||||
<details>
|
|
||||||
<summary>Tools</summary>
|
|
||||||
<ul className="p-2">
|
|
||||||
<li><Link to="/language">Language</Link></li>
|
|
||||||
<li><Link to="/diff">Diff</Link></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<details>
|
|
||||||
<summary>Plugins</summary>
|
|
||||||
<ul className="p-2">
|
|
||||||
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
|
||||||
<li><Link to="/srtools">Firefly Tools</Link></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
<li><Link to="/howto">How to?</Link></li>
|
|
||||||
<li><Link to="/about">About</Link></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="navbar-end">
|
|
||||||
<ThemeController />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="min-h-[78vh]">
|
<div className="min-h-[78vh]">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CloseModal isOpen={isOpenCloseModal} onClose={() => setIsOpenCloseModal(false)} />
|
||||||
|
<SettingModal isOpen={isOpenSettingModal} onClose={() => setIsOpenSettingModal(false)} />
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,18 +5,26 @@ interface ModalState {
|
|||||||
isOpenDownloadDataModal: boolean;
|
isOpenDownloadDataModal: boolean;
|
||||||
isOpenUpdateDataModal: boolean;
|
isOpenUpdateDataModal: boolean;
|
||||||
isOpenSelfUpdateModal: boolean;
|
isOpenSelfUpdateModal: boolean;
|
||||||
|
isOpenCloseModal: boolean;
|
||||||
|
isOpenSettingModal: boolean;
|
||||||
setIsOpenDownloadDataModal: (modal: boolean) => void;
|
setIsOpenDownloadDataModal: (modal: boolean) => void;
|
||||||
setIsOpenUpdateDataModal: (modal: boolean) => void;
|
setIsOpenUpdateDataModal: (modal: boolean) => void;
|
||||||
setIsOpenSelfUpdateModal: (modal: boolean) => void;
|
setIsOpenSelfUpdateModal: (modal: boolean) => void;
|
||||||
|
setIsOpenCloseModal: (modal: boolean) => void;
|
||||||
|
setIsOpenSettingModal: (modal: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useModalStore = create<ModalState>((set, get) => ({
|
const useModalStore = create<ModalState>((set, get) => ({
|
||||||
isOpenDownloadDataModal: false,
|
isOpenDownloadDataModal: false,
|
||||||
isOpenUpdateDataModal: false,
|
isOpenUpdateDataModal: false,
|
||||||
isOpenSelfUpdateModal: false,
|
isOpenSelfUpdateModal: false,
|
||||||
|
isOpenCloseModal: false,
|
||||||
|
isOpenSettingModal: false,
|
||||||
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
|
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
|
||||||
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
|
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
|
||||||
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
|
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
|
||||||
|
setIsOpenCloseModal: (modal: boolean) => set({ isOpenCloseModal: modal }),
|
||||||
|
setIsOpenSettingModal: (modal: boolean) => set({ isOpenSettingModal: modal }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useModalStore;
|
export default useModalStore;
|
||||||
@@ -10,6 +10,15 @@ interface SettingState {
|
|||||||
proxyPath: string;
|
proxyPath: string;
|
||||||
serverVersion: string;
|
serverVersion: string;
|
||||||
proxyVersion: string;
|
proxyVersion: string;
|
||||||
|
closingOption: {
|
||||||
|
isMinimize: boolean;
|
||||||
|
isAsk: boolean;
|
||||||
|
}
|
||||||
|
background: string;
|
||||||
|
extraBackgrounds: string[];
|
||||||
|
setExtraBackgrounds: (newExtraBackgrounds: string[]) => void;
|
||||||
|
setBackground: (newBackground: string) => void;
|
||||||
|
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => void;
|
||||||
setLocale: (newLocale: string) => void;
|
setLocale: (newLocale: string) => void;
|
||||||
setGamePath: (newGamePath: string) => void;
|
setGamePath: (newGamePath: string) => void;
|
||||||
setGameDir: (newGameDir: string) => void;
|
setGameDir: (newGameDir: string) => void;
|
||||||
@@ -29,6 +38,15 @@ const useSettingStore = create<SettingState>()(
|
|||||||
proxyPath: "",
|
proxyPath: "",
|
||||||
serverVersion: "",
|
serverVersion: "",
|
||||||
proxyVersion: "",
|
proxyVersion: "",
|
||||||
|
closingOption: {
|
||||||
|
isMinimize: false,
|
||||||
|
isAsk: true,
|
||||||
|
},
|
||||||
|
background: "bg-12.jpg",
|
||||||
|
extraBackgrounds: [],
|
||||||
|
setExtraBackgrounds: (newExtraBackgrounds: string[]) => set({ extraBackgrounds: newExtraBackgrounds }),
|
||||||
|
setBackground: (newBackground: string) => set({ background: newBackground }),
|
||||||
|
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => set({ closingOption: newClosingOption }),
|
||||||
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
||||||
setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }),
|
setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }),
|
||||||
setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }),
|
setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "daisyui"{
|
@plugin "daisyui"{
|
||||||
themes: night --default, night --prefersdark, cupcake;
|
themes: dracula --default;
|
||||||
}
|
}
|
||||||
|
|||||||
25
frontend/src/utils/cropImage.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
export default function getCroppedImg(imageSrc: string, crop: any): Promise<string> {
|
||||||
|
const image = new Image()
|
||||||
|
image.src = imageSrc
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')!
|
||||||
|
canvas.width = crop.width
|
||||||
|
canvas.height = crop.height
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
image.onload = () => {
|
||||||
|
ctx.drawImage(
|
||||||
|
image,
|
||||||
|
crop.x,
|
||||||
|
crop.y,
|
||||||
|
crop.width,
|
||||||
|
crop.height,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
crop.width,
|
||||||
|
crop.height
|
||||||
|
)
|
||||||
|
resolve(canvas.toDataURL('image/jpeg'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -19,4 +19,45 @@ func (a *AppService) CloseAppAfterTimeout(timeout int) (bool, string) {
|
|||||||
application.Get().Quit()
|
application.Get().Quit()
|
||||||
}()
|
}()
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AppService) CloseApp() (bool, string) {
|
||||||
|
application.Get().Quit()
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppService) HideApp() (bool, string) {
|
||||||
|
window := application.Get().Window.Current()
|
||||||
|
if window == nil {
|
||||||
|
return false, "not found window"
|
||||||
|
}
|
||||||
|
window.Hide()
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppService) MinimizeApp() (bool, string) {
|
||||||
|
window := application.Get().Window.Current()
|
||||||
|
if window == nil {
|
||||||
|
return false, "not found window"
|
||||||
|
}
|
||||||
|
window.Minimise()
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppService) MaximizeApp() (bool, string) {
|
||||||
|
window := application.Get().Window.Current()
|
||||||
|
if window == nil {
|
||||||
|
return false, "not found window"
|
||||||
|
}
|
||||||
|
window.Maximise()
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppService) RestoreApp() (bool, string) {
|
||||||
|
window := application.Get().Window.Current()
|
||||||
|
if window == nil {
|
||||||
|
return false, "not found window"
|
||||||
|
}
|
||||||
|
window.Restore()
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|||||||
18
main.go
@@ -17,6 +17,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:frontend/dist
|
//go:embed all:frontend/dist
|
||||||
@@ -84,7 +85,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create window
|
// Create window
|
||||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||||
Title: "Firefly Launcher - Kain",
|
Title: "Firefly Launcher - Kain",
|
||||||
Mac: application.MacWindow{
|
Mac: application.MacWindow{
|
||||||
InvisibleTitleBarHeight: 50,
|
InvisibleTitleBarHeight: 50,
|
||||||
@@ -92,10 +93,12 @@ func main() {
|
|||||||
TitleBar: application.MacTitleBarHiddenInset,
|
TitleBar: application.MacTitleBarHiddenInset,
|
||||||
},
|
},
|
||||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||||
Width: 1200,
|
Width: 1280,
|
||||||
Height: 600,
|
Height: 720,
|
||||||
URL: "/",
|
URL: "/",
|
||||||
DevToolsEnabled: true,
|
DevToolsEnabled: true,
|
||||||
|
Frameless: true,
|
||||||
|
DisableResize: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
iconBytes, _ := tools.ReadFile("assets/appicon.png")
|
iconBytes, _ := tools.ReadFile("assets/appicon.png")
|
||||||
@@ -106,13 +109,20 @@ func main() {
|
|||||||
// Attach the window to the system tray
|
// Attach the window to the system tray
|
||||||
menu := application.NewMenu()
|
menu := application.NewMenu()
|
||||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||||
// Handle click
|
window.Show()
|
||||||
})
|
})
|
||||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||||
app.Quit()
|
app.Quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
systemTray.SetMenu(menu)
|
systemTray.SetMenu(menu)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||||
|
app.Event.Emit("window:close")
|
||||||
|
e.Cancel()
|
||||||
|
})
|
||||||
|
|
||||||
err = app.Run()
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const ProxyZipFile = "64bit.zip"
|
|||||||
const LauncherFile = "firefly-launcher.exe"
|
const LauncherFile = "firefly-launcher.exe"
|
||||||
const TempUrl = "./temp"
|
const TempUrl = "./temp"
|
||||||
|
|
||||||
const CurrentLauncherVersion = "2.0.1"
|
const CurrentLauncherVersion = "2.2.1"
|
||||||
|
|
||||||
type ToolFile string
|
type ToolFile string
|
||||||
|
|
||||||
|
|||||||