UPDATE: System tray setting

This commit is contained in:
2025-10-07 13:14:18 +07:00
parent a6b49bef24
commit edbe04b9fc
15 changed files with 682 additions and 477 deletions

View 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.MinimizeApp()
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>
)
}

View File

@@ -0,0 +1,86 @@
import { Link } from "@tanstack/react-router";
import ThemeController from "../themeController";
import useModalStore from "@/stores/modalStore";
import { Settings2 } from "lucide-react";
export default function Header() {
const { setIsOpenSettingModal } = useModalStore()
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>
<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 flex gap-2">
<ThemeController />
<button className="btn btn-ghost btn-circle" onClick={() => setIsOpenSettingModal(true)}>
<Settings2 className="w-5 h-5" />
</button>
</div>
</div>
)
}

View File

@@ -0,0 +1,99 @@
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) => {
console.log(!e.target.checked)
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-warning">
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>
)
}

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from "react";
export default function ThemeController() {
const [theme, setTheme] = useState(localStorage.getItem("theme") ?? "light");
const [theme, setTheme] = useState(localStorage.getItem("theme") ?? "night");
const handleToggle = (e: any) => {
if (e.target.checked) {
setTheme("cupcake");

View 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-warning 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>
)
}