[feat] use heroui + setup persistore + setup toast

todo: fix launchoption problem
This commit is contained in:
Purp1e
2025-03-12 11:22:32 +08:00
parent 2ef6d2c298
commit 9103150562
27 changed files with 745 additions and 264 deletions

View File

@@ -2,7 +2,7 @@
import Header from "@/components/window/Header"
import Nav from "@/components/window/Nav"
import SideBar from "@/components/window/SideBar"
import clsx from "clsx"
import { cn } from "@heroui/react"
// import { platform } from "@tauri-apps/plugin-os"
export default function BaseLayout({
@@ -11,7 +11,7 @@ export default function BaseLayout({
children: React.ReactNode
}) {
return (
<div className="w-full h-full bg-transparent">
<div className="w-full h-full p-0 m-0 bg-transparent">
{/* bg-[#f1f0f2] */}
<Nav />
@@ -19,7 +19,7 @@ export default function BaseLayout({
<SideBar />
<main
className={clsx(
className={cn(
"flex flex-col h-full pb-5 pr-5 ml-20 overflow-hidden",
// platform() === "windows" ? "ml-20" : "ml-[4.25rem]"
)}

View File

@@ -1,11 +1,12 @@
"use client"
import useAppStore from "@/store/app"
import { appStore } from "@/store/app"
import { useSnapshot } from "valtio"
export default function Page() {
const app = useAppStore()
const app = useSnapshot(appStore.state)
return (
<div className="flex flex-col items-start gap-3 pt-2 pb-1">
<div className="flex flex-col gap-3 items-start pt-2 pb-1">
<p>{app.version}</p>
<p>{app.hasUpdate ? "有" : "无"}</p>
<p>{app.inited ? "是" : "否"}</p>

View File

@@ -14,7 +14,7 @@ import {
UploadOne,
Videocamera,
} from "@icon-park/react"
import clsx from "clsx"
import { cn } from "@heroui/react"
import { usePathname, useRouter } from "next/navigation"
// import { platform } from "@tauri-apps/plugin-os"
@@ -30,21 +30,21 @@ export default function PreferenceLayout({
<CardIcon
type="menu"
onClick={() => router.push("/preference/general")}
className={clsx(pathname === "/preference/general" && "bg-black/5")}
className={cn(pathname === "/preference/general" && "bg-black/5")}
>
<SettingConfig />
</CardIcon>
<CardIcon
type="menu"
onClick={() => router.push("/preference/path")}
className={clsx(pathname === "/preference/path" && "bg-black/5")}
className={cn(pathname === "/preference/path" && "bg-black/5")}
>
<AssemblyLine />
</CardIcon>
<CardIcon
type="menu"
onClick={() => router.push("/preference/replay")}
className={clsx(pathname === "/preference/replay" && "bg-black/5")}
className={cn(pathname === "/preference/replay" && "bg-black/5")}
>
<Videocamera />
</CardIcon>

View File

@@ -1,8 +0,0 @@
import "@/styles/globals.css"
import type { AppProps } from "next/app"
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp

View File

@@ -1,10 +1,12 @@
export const metadata = {
title: "CS工具箱",
description: "Generated by Next.js",
icons: ["/favicon.ico"],
}
"use client"
// export const metadata = {
// title: "CS工具箱",
// description: "Generated by Next.js",
// icons: ["/favicon.ico"],
// }
import "./globals.css"
import Providers from "./providers"
export default function RootLayout({
children,
@@ -13,7 +15,9 @@ export default function RootLayout({
}) {
return (
<html lang="en">
<body>{children}</body>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}

12
src/app/providers.tsx Normal file
View File

@@ -0,0 +1,12 @@
"use client"
import { HeroUIProvider } from "@heroui/react"
import { ToastProvider } from "@heroui/toast"
export default function Providers({ children }: { children: React.ReactNode }) {
return (
<HeroUIProvider className="h-full bg-transparent">
<ToastProvider toastOffset={10} placement="top-center" />
{children}
</HeroUIProvider>
)
}

View File

@@ -1,11 +1,17 @@
import useToolStore from "@/store/tool"
import { toolStore, setLaunchOption, setLaunchIndex } from "@/store/tool"
import { Plus, SettingConfig, Switch } from "@icon-park/react"
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
import { ToolButton } from "../window/ToolButton"
import { useSnapshot } from "valtio"
import { useEffect, useState } from "react"
const LaunchOption = () => {
const { launchOptions, setLaunchOption, launchIndex, setLaunchIndex } =
useToolStore()
toolStore.start()
const { launchOptions, launchIndex } = useSnapshot(toolStore.state)
const [currentLaunchOption, setCurrentLaunchOption] = useState(launchOptions[0])
useEffect(() => {
setCurrentLaunchOption(launchOptions[launchIndex] || '')
}, [launchIndex])
return (
<Card>
@@ -30,10 +36,11 @@ const LaunchOption = () => {
<CardBody>
<textarea
placeholder="请输入启动选项"
value={launchOptions[launchIndex]}
onChange={(e) => setLaunchOption(e.target.value, launchIndex)}
value={currentLaunchOption}
onChange={(e) => launchIndex !== -1 && setLaunchOption(e.target.value, launchIndex)}
className="w-full font-mono text-base bg-transparent outline-none resize-none min-h-20"
/>
<p>{launchIndex}</p>
</CardBody>
</Card>
)

View File

@@ -1,16 +1,13 @@
import {
Card,
CardBody,
CardHeader,
CardIcon,
CardTool,
} from "@/components/window/Card"
import useAppStore from "@/store/app"
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "@/components/window/Card"
import { appStore } from "@/store/app"
import { Refresh, VolumeNotice } from "@icon-park/react"
import { ToolButton } from "../window/ToolButton"
import { useSnapshot } from "valtio"
const Notice = () => {
const app = useAppStore()
appStore.start()
const app = useSnapshot(appStore.state)
return (
<Card>
<CardHeader>
@@ -24,9 +21,7 @@ const Notice = () => {
</ToolButton>
</CardTool>
</CardHeader>
<CardBody>
{app.notice || "不会真的有人要更新CSGO工具箱吧不会吧不会吧 xswl"}
</CardBody>
<CardBody>{app.notice || "不会真的有人要更新CSGO工具箱吧不会吧不会吧 xswl"}</CardBody>
</Card>
)
}

View File

@@ -1,4 +1,4 @@
import clsx from "clsx"
import { cn } from "@heroui/react"
import type { ReactNode } from "react"
interface CardProps {
@@ -28,7 +28,7 @@ const CardHeader = ({ children }: CardProps) => {
const CardIcon = ({ children, type, className, ...rest }: CardProps) => {
return (
<div
className={clsx(
className={cn(
"flex gap-1.5 items-center font-semibold",
type === "menu" &&
"transition cursor-pointer hover:bg-black/5 px-2 py-1 rounded-md active:scale-95",

View File

@@ -1,5 +1,8 @@
"use client"
import { Close, Minus, RocketOne, Square } from "@icon-park/react"
import { resetAppStore } from "@/store/app"
import { resetToolStore } from "@/store/tool"
import { addToast } from "@heroui/react"
import { Close, Minus, Refresh, RocketOne, Square } from "@icon-park/react"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { /* relaunch, */ exit } from "@tauri-apps/plugin-process"
// import { platform } from "@tauri-apps/plugin-os"
@@ -35,16 +38,26 @@ const Nav = () => {
const pathname = usePathname()
return (
<nav
className="absolute top-0 right-0 flex flex-row h-16 gap-0.5 p-4"
data-tauri-drag-region
>
<nav className="absolute top-0 right-0 flex flex-row h-16 gap-0.5 p-4" data-tauri-drag-region>
<button
type="button"
className="px-2 py-0 transition rounded hover:bg-zinc-200/80 active:scale-95"
onClick={() =>
pathname !== "/prepare" ? router.push("/prepare") : router.back()
}
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 active:scale-95"
onClick={() => {
resetAppStore()
resetToolStore()
addToast({
title: "重置成功",
description: "已重置所有设置,请重新启动程序",
})
}}
>
<Refresh size={16} />
</button>
<button
type="button"
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 active:scale-95"
onClick={() => (pathname !== "/prepare" ? router.push("/prepare") : router.back())}
>
<RocketOne size={16} />
</button>
@@ -53,21 +66,21 @@ const Nav = () => {
<>
<button
type="button"
className="px-2 py-0 transition rounded hover:bg-zinc-200/80 active:scale-95"
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 active:scale-95"
onClick={minimize}
>
<Minus size={16} />
</button>
<button
type="button"
className="px-2 py-0 transition rounded hover:bg-zinc-200/80 active:scale-95"
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 active:scale-95"
onClick={toggleMaximize}
>
<Square size={16} />
</button>
<button
type="button"
className="px-2 py-0 transition rounded hover:bg-zinc-200/80 active:scale-95"
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 active:scale-95"
onClick={close}
>
<Close size={16} />

View File

@@ -1,18 +1,12 @@
"use client"
import {
Home,
MonitorOne,
Movie,
Setting,
Terminal,
Toolkit,
} from "@icon-park/react"
import clsx from "clsx"
import { Home, MonitorOne, Movie, Setting, Terminal, Toolkit } from "@icon-park/react"
import { Button, cn } from "@heroui/react"
import { usePathname, useRouter } from "next/navigation"
import type { ReactNode } from "react"
// import { platform } from "@tauri-apps/plugin-os"
import useAppStore from "@/store/app"
import { appStore, setVersion } from "@/store/app"
import { useSnapshot } from "valtio"
interface SideButtonProps {
route: string
@@ -33,18 +27,18 @@ const SideButton = ({
<button
type="button"
onClick={() => router.push(route || "/")}
className={clsx(
className={cn(
className,
"p-2.5 hover:bg-black/5 rounded-lg transition relative",
path.startsWith(route) && "bg-black/5",
path.startsWith(route) && "bg-black/5"
)}
{...rest}
>
{children}
<div
className={clsx(
className={cn(
path.startsWith(route) && "opacity-100",
"transition-opacity duration-300 opacity-0 h-3.5 w-0.5 absolute left-0.5 bg-purple-500 rounded-full top-1/2 -translate-y-1/2",
"transition-opacity duration-300 opacity-0 h-3.5 w-0.5 absolute left-0.5 bg-purple-500 rounded-full top-1/2 -translate-y-1/2"
)}
/>
</button>
@@ -57,10 +51,10 @@ const Avatar = () => {
return (
<div
onClick={() => router.push("/users")}
className={clsx(
onKeyUp={() => router.push("/users")}
className={cn(
"w-12 h-12 bg-gray-700 rounded-full shadow-2xl cursor-pointer transition active:scale-95 shadow-purple-800",
path.startsWith("/users") && "shadow-sm",
path.startsWith("/users") && "shadow-sm"
)}
>
<img src="favicon.ico" alt="avatar" draggable={false} />
@@ -69,12 +63,13 @@ const Avatar = () => {
}
const SideBar = () => {
const { version, setVersion } = useAppStore()
appStore.start()
const { version } = useSnapshot(appStore.state)
return (
<div
className={clsx(
"absolute left-0 flex flex-col h-full select-none w-20 py-7",
className={cn(
"absolute left-0 flex flex-col h-full select-none w-20 pt-7 pb-5"
// platform() === "windows" ? "w-20" : "w-[4.25rem]"
)}
data-tauri-drag-region
@@ -107,12 +102,11 @@ const SideBar = () => {
</SideButton>
</section>
<div
className="mx-auto text-sm text-center text-zinc-500"
data-tauri-drag-region
>
<div className="mx-auto text-sm text-center text-zinc-500" data-tauri-drag-region>
<p></p>
<p onClick={() => setVersion("x.y.z")}>{version}</p>
<Button variant="light" size="sm" className="mt-0.5 text-zinc-600" onPress={() => setVersion("x.y.z")}>
{version}
</Button>
</div>
</div>
)

View File

@@ -1,80 +1,33 @@
import { Store } from "@tauri-apps/plugin-store"
// import { create } from "zustand"
// import { createJSONStorage, persist } from "zustand/middleware"
import { store } from 'tauri-plugin-valtio';
import { DEFAULT_STORE_CONFIG } from '.';
// interface AppState {
// version: string
// hasUpdate: boolean
// inited: boolean
// notice: string
// useMirror: boolean
// }
// Usage:
// import {appStore} from "@/store/app"
// import { useSnapshot } from "valtio"
// const app = useSnapshot(appStore.state)
// { app.version }
// () => appStore.setVersion("0.0.1")
// interface AppAction {
// setVersion: (version: string) => void
// setHasUpdate: (hasUpdate: boolean) => void
// setInited: (hasInit: boolean) => void
// setNotice: (notice: string) => void
// setUseMirror: (useMirror: boolean) => void
// }
const STORE_NAME = "app"
// 节流设置
export const THROTTLE_TIME = 300
export const THROTTLE_TIME_STORE = 2000
export const THROTTLE_LEADING = true
export const THROTTLE_TRAILING = true
// const { /* store, */ storage } = await tauriStore(STORE_NAME)
// const useAppStore = create<AppState & AppAction>()(
// persist(
// (set /* , get */) => ({
// version: "0.0.1",
// hasUpdate: false,
// inited: false,
// notice: "Man! What can I say?",
// useMirror: true,
// setVersion: (version: string) => set(() => ({ version: version })),
// setHasUpdate: (hasUpdate: boolean) =>
// set(() => ({ hasUpdate: hasUpdate })),
// setInited: (inited: boolean) => set(() => ({ inited: inited })),
// setNotice: (notice: string) => set(() => ({ notice: notice })),
// setUseMirror: (useMirror: boolean) =>
// set(() => ({ useMirror: useMirror })),
// }),
// {
// name: STORE_NAME, // name of item in the storage (must be unique)
// storage: createJSONStorage(() => storage), // (optional) by default the 'localStorage' is used
// },
// ),
// )
const useAppStore = async (): Promise<any> => {
let store = await Store.get(`${STORE_NAME || "store"}.settings.json`);
if (!store) {
store = await Store.load(`${STORE_NAME || "store"}.settings.json`, {
autoSave: THROTTLE_TIME_STORE,
});
}
// 生成随机字符串每1s打印
const randomString = Math.random().toString(36).substring(2, 15)
setInterval(() => {
console.log("app store", randomString)
}, 1000)
return {
version: async () => (await store.get<string>("version")) || "0.0.1",
hasUpdate: async () => (await store.get<boolean>("hasUpdate")) || false,
inited: async () => (await store.get<boolean>("inited")) || false,
notice: async () => (await store.get<string>("notice")) || "",
useMirror: async () => (await store.get<boolean>("useMirror")) || true,
setVersion: async (version: string) => await store.set("version", version),
setHasUpdate: async (hasUpdate: boolean) => await store.set("hasUpdate", hasUpdate),
setInited: async (inited: boolean) => await store.set("inited", inited),
setNotice: async (notice: string) => await store.set("notice", notice),
setUseMirror: async (useMirror: boolean) => await store.set("useMirror", useMirror),
}
const defaultValue = {
version: "0.0.1",
hasUpdate: false,
inited: false,
notice: "",
useMirror: true
}
export default useAppStore
export const appStore = store('app', { ...defaultValue }, DEFAULT_STORE_CONFIG);
export const setVersion = (version: string) => { appStore.state.version = version }
export const setHasUpdate = (hasUpdate: boolean) => { appStore.state.hasUpdate = hasUpdate }
export const setInited = (inited: boolean) => { appStore.state.inited = inited }
export const setNotice = (notice: string) => { appStore.state.notice = notice }
export const setUseMirror = (useMirror: boolean) => { appStore.state.useMirror = useMirror }
export const resetAppStore = () => {
setVersion(defaultValue.version)
setHasUpdate(defaultValue.hasUpdate)
setInited(defaultValue.inited)
setNotice(defaultValue.notice)
setUseMirror(defaultValue.useMirror)
}

6
src/store/index.ts Normal file
View File

@@ -0,0 +1,6 @@
export const DEFAULT_STORE_CONFIG = {
saveOnChange: true,
saveOnExit: true,
saveStrategy: 'debounce' as const,
saveInterval: 3000,
};

View File

@@ -1,60 +1,37 @@
import type { SteamUser } from "@/types/steam"
import { tauriStore } from "@/utils/old"
import { create } from "zustand"
import { createJSONStorage, persist } from "zustand/middleware"
import { store } from 'tauri-plugin-valtio';
import { DEFAULT_STORE_CONFIG } from '.';
const mock_user: SteamUser = {
steamID64: "76561198052315353",
steamID32: "STEAM_0:0:46157676",
accountName: "wrr",
personaName: "wrr",
recent: 0,
avatar: "",
// "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/0d/0d1d4b9e1a9e8b2b3d4d0a7e1b3d6b1e3f8f4f7.jpg",
const defaultValue = {
dir: "C:\\Program Files (x86)\\Steam",
csDir: "",
users: [{
steamID64: "76561198052315353",
steamID32: "STEAM_0:0:46157676",
accountName: "wrr",
personaName: "wrr",
recent: 0,
avatar: ""
}] as SteamUser[],
isDirValid: false,
isCsDirValid: false
}
export const steamStore = store('steam', {...defaultValue}, DEFAULT_STORE_CONFIG);
export const setDir = (dir: string) => { steamStore.state.dir = dir }
export const setCsDir = (dir: string) => { steamStore.state.csDir = dir }
export const setUsers = (users: SteamUser[]) => { steamStore.state.users = users }
export const setIsDirValid = (valid: boolean) => { steamStore.state.isDirValid = valid }
export const setIsCsDirValid = (valid: boolean) => { steamStore.state.isCsDirValid = valid }
export const currentUser = () => {
return steamStore.state.users[0] || defaultValue.users[0]
}
interface SteamState {
dir: string
csDir: string
users: SteamUser[]
isDirValid: boolean
isCsDirValid: boolean
}
interface SteamAction {
setDir: (dir: string) => void
setCsDir: (dir: string) => void
setUsers: (users: SteamUser[]) => void
setIsDirValid: (valid: boolean) => void
setIsCsDirValid: (valid: boolean) => void
currentUser: () => SteamUser
}
const STORE_NAME = "steam"
const { /* store, */ storage } = await tauriStore(STORE_NAME)
const useSteamStore = create<SteamState & SteamAction>()(
persist(
(set, get) => ({
dir: "C:\\Program Files (x86)\\Steam",
csDir: "",
users: [mock_user] as SteamUser[],
isDirValid: false,
isCsDirValid: false,
setDir: (dir: string) => set(() => ({ dir: dir })),
setCsDir: (dir: string) => set(() => ({ csDir: dir })),
setUsers: (users: SteamUser[]) => set(() => ({ users: users })),
setIsDirValid: (valid: boolean) => set(() => ({ isDirValid: valid })),
setIsCsDirValid: (valid: boolean) => set(() => ({ isCsDirValid: valid })),
currentUser: () => {
return get().users[0] || mock_user
},
}),
{
name: STORE_NAME, // name of item in the storage (must be unique)
storage: createJSONStorage(() => storage), // (optional) by default the 'localStorage' is used
},
),
)
export default useSteamStore
export const resetSteamStore = () => {
setDir(defaultValue.dir)
setCsDir(defaultValue.csDir)
setUsers(defaultValue.users)
setIsDirValid(defaultValue.isDirValid)
setIsCsDirValid(defaultValue.isCsDirValid)
}

View File

@@ -1,51 +1,36 @@
import { tauriStore } from "@/utils/old"
import { create } from "zustand"
import { createJSONStorage, persist } from "zustand/middleware"
import { store } from 'tauri-plugin-valtio';
import { DEFAULT_STORE_CONFIG } from '.';
interface ToolState {
launchOptions: string[]
launchIndex: number
powerPlan: number
const defaultValue = {
launchOptions: [] as string[],
launchIndex: 0,
powerPlan: 0
}
interface ToolAction {
setLaunchOption: (option: string, index: number) => void
setLaunchOptions: (options: string[]) => void
setLaunchIndex: (index: number) => void
setPowerPlan: (index: number) => void
export const toolStore = store('tool', { ...defaultValue }, DEFAULT_STORE_CONFIG);
export const setLaunchOption = (option: string, index: number) => {
toolStore.state.launchOptions = [
...toolStore.state.launchOptions.slice(0, index),
option,
...toolStore.state.launchOptions.slice(index + 1)
]
}
const STORE_NAME = "tool"
const { /* store, */ storage } = await tauriStore(STORE_NAME)
export const setLaunchOptions = (options: string[]) => {
toolStore.state.launchOptions = options
}
const useToolStore = create<ToolState & ToolAction>()(
persist(
(set /* , get */) => ({
launchOptions: [
"-high -refresh 120 -novid -nojoy -tickrate 128 +cl_cmdrate 128 +cl_updaterate 128 +exec auto.cfg +test",
"",
"",
],
launchIndex: 0,
powerPlan: 0,
setLaunchOption: (option: string, index: number) =>
set((state) => ({
launchOptions: [
...state.launchOptions.slice(0, index),
option,
...state.launchOptions.slice(index + 1),
],
})),
setLaunchOptions: (options: string[]) =>
set(() => ({ launchOptions: options })),
setLaunchIndex: (index: number) => set(() => ({ launchIndex: index })),
setPowerPlan: (index: number) => set(() => ({ powerPlan: index })),
}),
{
name: STORE_NAME, // name of item in the storage (must be unique)
storage: createJSONStorage(() => storage), // (optional) by default the 'localStorage' is used
},
),
)
export const setLaunchIndex = (index: number) => {
toolStore.state.launchIndex = index
}
export default useToolStore
export const setPowerPlan = (plan: number) => {
toolStore.state.powerPlan = plan
}
export const resetToolStore = () => {
toolStore.state.launchOptions = defaultValue.launchOptions
toolStore.state.launchIndex = defaultValue.launchIndex
toolStore.state.powerPlan = defaultValue.powerPlan
}