Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca612797ee | |||
| 787962c6d0 | |||
| 09434fcc5b | |||
| edbe04b9fc | |||
| a6b49bef24 |
5
Makefile
@@ -22,6 +22,11 @@ build:
|
||||
wails3 build
|
||||
@echo Done!
|
||||
|
||||
generate:
|
||||
@echo Generating bindings...
|
||||
wails3 generate bindings -ts
|
||||
@echo Done!
|
||||
|
||||
release:
|
||||
@echo Building release application...
|
||||
wails3 package
|
||||
|
||||
@@ -8,9 +8,9 @@ info:
|
||||
companyName: "Firefly Shelter" # The name of the company
|
||||
productName: "Firefly Launcher" # The name of the application
|
||||
productIdentifier: "com.fireflyshelter.fireflylauncher" # The unique product identifier
|
||||
description: "Custom game launcher built for convenience and quick access" # The application description
|
||||
description: "Firefly Launcher" # The application description
|
||||
copyright: "@ 2025, Firefly Shelter" # Copyright text
|
||||
comments: "Custom game launcher built for convenience and quick access" # Comments
|
||||
comments: "Firefly Launcher" # Comments
|
||||
version: "1.0.0" # The application version
|
||||
|
||||
# Dev mode configuration
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Custom game launcher built for convenience and quick access</string>
|
||||
<string>Firefly Launcher</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Custom game launcher built for convenience and quick access</string>
|
||||
<string>Firefly Launcher</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=Firefly Launcher
|
||||
Comment=Custom game launcher built for convenience and quick access
|
||||
Comment=Firefly Launcher
|
||||
# The Exec line includes %u to pass the URL to the application
|
||||
Exec=/usr/local/bin/firefly-launcher %u
|
||||
Terminal=false
|
||||
|
||||
@@ -10,7 +10,7 @@ version: "1.0.0"
|
||||
section: "default"
|
||||
priority: "extra"
|
||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||
description: "Custom game launcher built for convenience and quick access"
|
||||
description: "Firefly Launcher"
|
||||
vendor: "Firefly Shelter"
|
||||
homepage: "https://wails.io"
|
||||
license: "MIT"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"0000": {
|
||||
"ProductVersion": "1.0.0",
|
||||
"CompanyName": "Firefly Shelter",
|
||||
"FileDescription": "Custom game launcher built for convenience and quick access",
|
||||
"FileDescription": "Firefly Launcher",
|
||||
"LegalCopyright": "@ 2025, Firefly Shelter",
|
||||
"ProductName": "Firefly Launcher",
|
||||
"Comments": "Custom game launcher built for convenience and quick access"
|
||||
"Comments": "Firefly Launcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,13 @@
|
||||
// @ts-ignore: Unused imports
|
||||
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
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
@@ -20,3 +27,31 @@ export function CloseAppAfterTimeout(timeout) {
|
||||
export function GetCurrentLauncherVersion() {
|
||||
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>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dracula">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
21
frontend/package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-easy-crop": "^5.5.3",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"zustand": "^5.0.8"
|
||||
@@ -3783,6 +3784,12 @@
|
||||
"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": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -4000,6 +4007,20 @@
|
||||
"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": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-easy-crop": "^5.5.3",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"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 { Events } from "@wailsio/runtime";
|
||||
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({
|
||||
setGameRunning,
|
||||
setServerRunning,
|
||||
setProxyRunning,
|
||||
setProgressUpdate,
|
||||
setMaxProgressUpdate,
|
||||
setProgressDownload,
|
||||
setDownloadSpeed,
|
||||
setMessageUpdate,
|
||||
setStageType,
|
||||
export function useGlobalEvents() {
|
||||
const { setIsOpenCloseModal } = useModalStore()
|
||||
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
|
||||
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
|
||||
|
||||
}: {
|
||||
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(() => {
|
||||
const onGameExit = () => setGameRunning(false);
|
||||
const onServerExit = () => setServerRunning(false);
|
||||
@@ -64,6 +52,20 @@ export function useGlobalEvents({
|
||||
const { message } = event.data[0];
|
||||
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 () => {
|
||||
Events.Off("download:server");
|
||||
@@ -75,6 +77,7 @@ export function useGlobalEvents({
|
||||
Events.Off("diff:message");
|
||||
Events.Off("diff:stage");
|
||||
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.
|
||||
</p>
|
||||
<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 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.
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function AnalysisPage() {
|
||||
</div>
|
||||
<a
|
||||
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"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -79,7 +79,7 @@ export default function AnalysisPage() {
|
||||
</div>
|
||||
<a
|
||||
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"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -309,7 +309,7 @@ export default function DiffPage() {
|
||||
<div className="w-full p-4">
|
||||
<div className="space-y-3">
|
||||
<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">
|
||||
{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>}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
export default function FireflyToolsPage() {
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
|
||||
|
||||
{/* Section 1: About SR Tools */}
|
||||
<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">
|
||||
<span>ℹ️</span>
|
||||
<span>About Firefly Tools</span>
|
||||
</h2>
|
||||
<div className="space-y-3 text-blue-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">🏠</div>
|
||||
<p>
|
||||
This site is a another version of {" "}
|
||||
<span className="font-semibold text-success">Firefly Tools {" "}</span>
|
||||
developed by {" "}
|
||||
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{/* Section 1: About SR Tools */}
|
||||
<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">
|
||||
<span>ℹ️</span>
|
||||
<span>About Firefly Tools</span>
|
||||
</h2>
|
||||
<div className="space-y-3 text-blue-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">🏠</div>
|
||||
<p>
|
||||
This site is a another version of {" "}
|
||||
<span className="font-semibold text-success">Firefly Tools {" "}</span>
|
||||
developed by {" "}
|
||||
<span className="font-semibold text-accent">Me {"(Kain)"}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-blue-600 text-lg">🏆</span>
|
||||
@@ -30,7 +30,7 @@ export default function FireflyToolsPage() {
|
||||
</div>
|
||||
<a
|
||||
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"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -45,7 +45,7 @@ export default function FireflyToolsPage() {
|
||||
</div>
|
||||
<a
|
||||
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"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -53,106 +53,106 @@ export default function FireflyToolsPage() {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">👨💻</div>
|
||||
<p>The original tool was created by a third-party developer named <span className="font-semibold text-warning">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">🔗</div>
|
||||
<p>There is also a more modern version by the same author available at{" "}
|
||||
<a
|
||||
href="https://srtools.neonteam.dev/"
|
||||
className="link link-warning"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
srtools.neonteam.dev
|
||||
</a>
|
||||
{" "}and the original version at{" "}
|
||||
<a
|
||||
href="https://srtools.pages.dev/"
|
||||
className="link link-warning"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
srtools.pages.dev
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">👨💻</div>
|
||||
<p>The original tool was created by a third-party developer named <span className="font-semibold text-accent">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">🔗</div>
|
||||
<p>There is also a more modern version by the same author available at{" "}
|
||||
<a
|
||||
href="https://srtools.neonteam.dev/"
|
||||
className="link link-accent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
srtools.neonteam.dev
|
||||
</a>
|
||||
{" "}and the original version at{" "}
|
||||
<a
|
||||
href="https://srtools.pages.dev/"
|
||||
className="link link-accent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
srtools.pages.dev
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 2: Main Features */}
|
||||
<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">
|
||||
<span>🔧</span>
|
||||
<span>Main Features</span>
|
||||
</h2>
|
||||
<div className="space-y-3 text-green-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">⚙️</div>
|
||||
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-2xl">✨</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
|
||||
<p className="text-green-700 mt-1">
|
||||
Enhance your <span className="font-semibold text-warning">Firefly GO Server</span> experience with extra features:
|
||||
</p>
|
||||
<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">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>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/* Section 2: Main Features */}
|
||||
<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">
|
||||
<span>🔧</span>
|
||||
<span>Main Features</span>
|
||||
</h2>
|
||||
<div className="space-y-3 text-green-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">⚙️</div>
|
||||
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">🔌</div>
|
||||
<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 className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-2xl">✨</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
|
||||
<p className="text-green-700 mt-1">
|
||||
Enhance your <span className="font-semibold text-accent">Firefly GO Server</span> experience with extra features:
|
||||
</p>
|
||||
<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">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>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">⚡</div>
|
||||
<p>Fast testing workflow — no sync cooldowns, instant in-game updates.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">⚡</div>
|
||||
<p>Fast testing workflow — no sync cooldowns, instant in-game updates.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 3: Getting Started */}
|
||||
<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">
|
||||
<span>🚀</span>
|
||||
<span>Getting Started</span>
|
||||
</h2>
|
||||
<div className="space-y-3 text-purple-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-purple-600 text-lg">1️⃣</div>
|
||||
<p>Access the tool through your browser at the self-hosted instance.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-purple-600 text-lg">2️⃣</div>
|
||||
<p>Configure your character builds with the intuitive web interface.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-purple-600 text-lg">4️⃣</div>
|
||||
<p>Test your builds in-game with real-time updates and modifications.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Section 3: Getting Started */}
|
||||
<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">
|
||||
<span>🚀</span>
|
||||
<span>Getting Started</span>
|
||||
</h2>
|
||||
<div className="space-y-3 text-purple-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-purple-600 text-lg">1️⃣</div>
|
||||
<p>Access the tool through your browser at the self-hosted instance.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-purple-600 text-lg">2️⃣</div>
|
||||
<p>Configure your character builds with the intuitive web interface.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-purple-600 text-lg">4️⃣</div>
|
||||
<p>Test your builds in-game with real-time updates and modifications.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center pt-4">
|
||||
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="text-center pt-4">
|
||||
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export default function HowToPage() {
|
||||
|
||||
<p className="text-blue-700 mb-4">
|
||||
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>
|
||||
|
||||
{/* 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="flex items-center gap-2 mb-1">
|
||||
<Mic size={20} className="text-warning" />
|
||||
<span className="font-bold text-warning">Voice Language</span>
|
||||
<Mic size={20} className="text-accent" />
|
||||
<span className="font-bold text-accent">Voice Language</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-warning">
|
||||
<p className="text-2xl font-bold text-accent">
|
||||
{getLanguageLabel(voiceLang)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -250,7 +250,7 @@ export default function LanguagePage() {
|
||||
|
||||
{/* Voice Language */}
|
||||
<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} />
|
||||
Voice Language
|
||||
</label>
|
||||
|
||||
@@ -10,6 +10,10 @@ import useLauncherStore from '@/stores/launcherStore';
|
||||
import { motion } from 'motion/react';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
|
||||
import UpdateModal from '@/components/updateModal';
|
||||
import { BackgroundSelector } from '@/components/backgroudModal';
|
||||
|
||||
|
||||
|
||||
export default function LauncherPage() {
|
||||
const { gamePath,
|
||||
@@ -20,7 +24,9 @@ export default function LauncherPage() {
|
||||
gameDir,
|
||||
serverVersion,
|
||||
proxyVersion,
|
||||
background
|
||||
} = useSettingStore()
|
||||
|
||||
const {
|
||||
isOpenDownloadDataModal,
|
||||
isOpenUpdateDataModal,
|
||||
@@ -29,6 +35,7 @@ export default function LauncherPage() {
|
||||
setIsOpenUpdateDataModal,
|
||||
setIsOpenSelfUpdateModal
|
||||
} = useModalStore()
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
downloadType,
|
||||
@@ -54,6 +61,33 @@ export default function LauncherPage() {
|
||||
setUpdateData,
|
||||
} = 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(() => {
|
||||
const check = async () => {
|
||||
if (!serverVersion || !proxyVersion) {
|
||||
@@ -200,18 +234,18 @@ export default function LauncherPage() {
|
||||
setIsDownloading(true)
|
||||
if (updateData.launcher.isUpdate) {
|
||||
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)
|
||||
}
|
||||
if (updateData.server.isUpdate || !updateData.server.isExists) {
|
||||
await UpdateServer(updateData.server.version)
|
||||
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) {
|
||||
await UpdateProxy(updateData.proxy.version)
|
||||
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("")
|
||||
@@ -235,78 +269,39 @@ export default function LauncherPage() {
|
||||
|
||||
return (
|
||||
<div className="relative min-h-fit overflow-hidden">
|
||||
<div
|
||||
className="fixed inset-0 z-0 w-full h-full"
|
||||
style={{
|
||||
backgroundImage: "url('/bg.jpg')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat"
|
||||
<img
|
||||
src={background}
|
||||
alt="background"
|
||||
className="fixed inset-0 w-full h-full object-cover z-0"
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget as HTMLImageElement
|
||||
target.src = "bg-12.jpg"
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
|
||||
{/* Header */}
|
||||
<header className="hidden sm:flex fixed z-10 items-center justify-between p-6">
|
||||
<div className="text-2xl font-bold text-white">Firefly GO</div>
|
||||
<header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4 ">
|
||||
<div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">Firefly GO</div>
|
||||
</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">
|
||||
<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">
|
||||
<div className="tooltip tooltip-left" data-tip="How to use all tools & commands">
|
||||
<Link
|
||||
to="/howto"
|
||||
className="btn btn-warning btn-circle"
|
||||
@@ -316,6 +311,9 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex fixed top-1/2 left-4 z-10 ">
|
||||
<BackgroundSelector />
|
||||
</div>
|
||||
|
||||
{/* Bottom Panel */}
|
||||
{serverReady && proxyReady && !isDownloading && (
|
||||
@@ -345,32 +343,33 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
<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={async () => {
|
||||
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||
const launcherData = await CheckUpdateLauncher()
|
||||
setUpdateData({
|
||||
server: serverData,
|
||||
proxy: proxyData,
|
||||
launcher: launcherData
|
||||
})
|
||||
if (launcherData.isUpdate) {
|
||||
setIsOpenSelfUpdateModal(true)
|
||||
return
|
||||
}
|
||||
if (!serverData.isExists || !proxyData.isExists) {
|
||||
setIsOpenDownloadDataModal(true)
|
||||
return
|
||||
}
|
||||
if (serverData.isUpdate || proxyData.isUpdate) {
|
||||
setIsOpenUpdateDataModal(true)
|
||||
return
|
||||
}
|
||||
toast.success("No updates available")
|
||||
}}>
|
||||
Check for Updates
|
||||
</button></li>
|
||||
<li>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||
setUpdateData({
|
||||
server: serverData,
|
||||
proxy: proxyData,
|
||||
launcher: updateData.launcher
|
||||
})
|
||||
|
||||
if (!serverData.isExists || !proxyData.isExists) {
|
||||
setIsOpenDownloadDataModal(true)
|
||||
return
|
||||
}
|
||||
if (serverData.isUpdate || proxyData.isUpdate) {
|
||||
setIsOpenUpdateDataModal(true)
|
||||
return
|
||||
}
|
||||
toast.success("No updates available")
|
||||
}}>
|
||||
Check for Updates Server & Proxy
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
</li>
|
||||
<li><button disabled={!serverPath} onClick={() => {
|
||||
if (serverPath) {
|
||||
FSService.OpenFolder("./server")
|
||||
@@ -400,29 +399,30 @@ export default function LauncherPage() {
|
||||
|| !updateData.proxy.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="space-y-3">
|
||||
<div className="flex justify-center items-center text-sm text-white/80">
|
||||
<span>{downloadType}</span>
|
||||
<div className="flex items-center gap-4 ml-4">
|
||||
<span className="text-cyan-400 font-semibold">{downloadSpeed}</span>
|
||||
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-center items-center text-sm text-white/80">
|
||||
<span>{downloadType}</span>
|
||||
<div className="flex items-center gap-4 ml-4">
|
||||
<span className="text-cyan-400 font-semibold">{downloadSpeed}</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 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>
|
||||
)}
|
||||
)}
|
||||
|
||||
{isDownloading && updateData.launcher.isUpdate && (
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||
<div className="space-y-3 text-sm text-white/80 text-center">
|
||||
@@ -430,10 +430,10 @@ export default function LauncherPage() {
|
||||
<div className="flex justify-center items-center gap-4 ml-4">
|
||||
<span
|
||||
className={`font-bold ${downloadType === "update:launcher:downloading"
|
||||
? "text-yellow-200 text-2xl"
|
||||
: downloadType === "update:launcher:success"
|
||||
? "text-emerald-200 text-xl"
|
||||
: "text-red-200 text-xl"
|
||||
? "text-yellow-200 text-2xl"
|
||||
: downloadType === "update:launcher:success"
|
||||
? "text-emerald-200 text-xl"
|
||||
: "text-red-200 text-xl"
|
||||
}`}
|
||||
>
|
||||
{downloadType === "update:launcher:downloading" && "Updating launcher"}
|
||||
@@ -468,149 +468,86 @@ export default function LauncherPage() {
|
||||
|
||||
{/* Version Info */}
|
||||
{serverReady && proxyReady && !isDownloading && (
|
||||
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-black/20 backdrop-blur-sm rounded-lg p-2 shadow">
|
||||
<p className="text-primary">Version server: {serverVersion}</p>
|
||||
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
|
||||
<p className="mt-2 text-success">Version launcher: {launcherVersion}</p>
|
||||
<div 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-pink-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 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>
|
||||
)}
|
||||
|
||||
{/* Modal */}
|
||||
{isOpenUpdateDataModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
<UpdateModal
|
||||
isOpen={isOpenUpdateDataModal}
|
||||
onClose={() => setIsOpenUpdateDataModal(false)}
|
||||
title="Update Data"
|
||||
message="Do you want to update data server and proxy?"
|
||||
buttons={[
|
||||
{ text: "No", onClick: () => setIsOpenUpdateDataModal(false), variant: "outline" },
|
||||
{ text: "Yes", onClick: async () => { setIsOpenUpdateDataModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||
]}
|
||||
/>
|
||||
|
||||
<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 Data
|
||||
</h3>
|
||||
</div>
|
||||
<UpdateModal
|
||||
isOpen={isOpenDownloadDataModal}
|
||||
onClose={() => setIsOpenDownloadDataModal(false)}
|
||||
title="Download Data"
|
||||
message="Data server and proxy download required"
|
||||
buttons={[
|
||||
{ text: "Download", onClick: async () => { setIsOpenDownloadDataModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
Do you want to update data server and proxy?
|
||||
</p>
|
||||
</div>
|
||||
<UpdateModal
|
||||
isOpen={isOpenSelfUpdateModal}
|
||||
onClose={() => setIsOpenSelfUpdateModal(false)}
|
||||
title="Update Launcher"
|
||||
message="Do you want to update launcher?"
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,107 +1,31 @@
|
||||
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
|
||||
import ThemeController from '../components/themeController'
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
import { useGlobalEvents } from '@/hooks';
|
||||
import useLauncherStore from '@/stores/launcherStore';
|
||||
import useDiffStore from '@/stores/diffStore';
|
||||
import useModalStore from '@/stores/modalStore';;
|
||||
import SettingModal from '@/components/settingModal';
|
||||
import CloseModal from '@/components/closeModal';
|
||||
import Header from '@/components/header';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootLayout
|
||||
})
|
||||
|
||||
function RootLayout() {
|
||||
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
|
||||
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
|
||||
useGlobalEvents({
|
||||
setGameRunning,
|
||||
setServerRunning,
|
||||
setProxyRunning,
|
||||
setProgressUpdate,
|
||||
setMaxProgressUpdate,
|
||||
setProgressDownload,
|
||||
setDownloadSpeed,
|
||||
setMessageUpdate,
|
||||
setStageType
|
||||
});
|
||||
const { setIsOpenCloseModal, isOpenCloseModal, isOpenSettingModal, setIsOpenSettingModal } = useModalStore()
|
||||
|
||||
useGlobalEvents();
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="navbar bg-base-100 shadow-sm sticky top-0 z-50 px-3">
|
||||
<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>
|
||||
<Header />
|
||||
|
||||
<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]">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
<CloseModal isOpen={isOpenCloseModal} onClose={() => setIsOpenCloseModal(false)} />
|
||||
<SettingModal isOpen={isOpenSettingModal} onClose={() => setIsOpenSettingModal(false)} />
|
||||
<ToastContainer />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -5,18 +5,26 @@ interface ModalState {
|
||||
isOpenDownloadDataModal: boolean;
|
||||
isOpenUpdateDataModal: boolean;
|
||||
isOpenSelfUpdateModal: boolean;
|
||||
isOpenCloseModal: boolean;
|
||||
isOpenSettingModal: boolean;
|
||||
setIsOpenDownloadDataModal: (modal: boolean) => void;
|
||||
setIsOpenUpdateDataModal: (modal: boolean) => void;
|
||||
setIsOpenSelfUpdateModal: (modal: boolean) => void;
|
||||
setIsOpenCloseModal: (modal: boolean) => void;
|
||||
setIsOpenSettingModal: (modal: boolean) => void;
|
||||
}
|
||||
|
||||
const useModalStore = create<ModalState>((set, get) => ({
|
||||
isOpenDownloadDataModal: false,
|
||||
isOpenUpdateDataModal: false,
|
||||
isOpenSelfUpdateModal: false,
|
||||
isOpenCloseModal: false,
|
||||
isOpenSettingModal: false,
|
||||
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
|
||||
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
|
||||
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
|
||||
setIsOpenCloseModal: (modal: boolean) => set({ isOpenCloseModal: modal }),
|
||||
setIsOpenSettingModal: (modal: boolean) => set({ isOpenSettingModal: modal }),
|
||||
}));
|
||||
|
||||
export default useModalStore;
|
||||
@@ -10,6 +10,15 @@ interface SettingState {
|
||||
proxyPath: string;
|
||||
serverVersion: 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;
|
||||
setGamePath: (newGamePath: string) => void;
|
||||
setGameDir: (newGameDir: string) => void;
|
||||
@@ -29,6 +38,15 @@ const useSettingStore = create<SettingState>()(
|
||||
proxyPath: "",
|
||||
serverVersion: "",
|
||||
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 }),
|
||||
setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }),
|
||||
setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@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'))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -20,3 +20,44 @@ func (a *AppService) CloseAppAfterTimeout(timeout int) (bool, string) {
|
||||
}()
|
||||
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"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
@@ -84,7 +85,7 @@ func main() {
|
||||
})
|
||||
|
||||
// Create window
|
||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Firefly Launcher - Kain",
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
@@ -92,10 +93,12 @@ func main() {
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
},
|
||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||
Width: 1200,
|
||||
Height: 600,
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
URL: "/",
|
||||
DevToolsEnabled: true,
|
||||
Frameless: true,
|
||||
DisableResize: true,
|
||||
})
|
||||
|
||||
iconBytes, _ := tools.ReadFile("assets/appicon.png")
|
||||
@@ -106,7 +109,7 @@ func main() {
|
||||
// Attach the window to the system tray
|
||||
menu := application.NewMenu()
|
||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
window.Show()
|
||||
})
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
@@ -114,6 +117,13 @@ func main() {
|
||||
|
||||
systemTray.SetMenu(menu)
|
||||
|
||||
|
||||
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Event.Emit("window:close")
|
||||
e.Cancel()
|
||||
})
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@@ -10,7 +10,7 @@ const ProxyZipFile = "64bit.zip"
|
||||
const LauncherFile = "firefly-launcher.exe"
|
||||
const TempUrl = "./temp"
|
||||
|
||||
const CurrentLauncherVersion = "2.0.0"
|
||||
const CurrentLauncherVersion = "2.2.1"
|
||||
|
||||
type ToolFile string
|
||||
|
||||
|
||||