[feat] fix notice + add tooltips
This commit is contained in:
217
README.md
217
README.md
@@ -1,216 +1,3 @@
|
||||
# Tauri + Next.js Template
|
||||
# CS工具箱
|
||||
|
||||

|
||||
|
||||
This is a [Tauri](https://tauri.app/) project template using [Next.js](https://nextjs.org/),
|
||||
bootstrapped by combining [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app)
|
||||
and [`create tauri-app`](https://tauri.app/v1/guides/getting-started/setup).
|
||||
|
||||
This template uses [`pnpm`](https://pnpm.io/) as the Node.js dependency
|
||||
manager.
|
||||
|
||||
## Template Features
|
||||
|
||||
- TypeScript frontend using Next.js React framework
|
||||
- [TailwindCSS](https://tailwindcss.com/) as a utility-first atomic CSS framework
|
||||
- The example page in this template app has been updated to use only TailwindCSS
|
||||
- While not included by default, consider using
|
||||
[React Aria components](https://react-spectrum.adobe.com/react-aria/index.html)
|
||||
and/or [HeadlessUI components](https://headlessui.com/) for completely unstyled and
|
||||
fully accessible UI components, which integrate nicely with TailwindCSS
|
||||
- Opinionated formatting and linting already setup and enabled
|
||||
- [ESLint](https://eslint.org/) for pure React + TypeScript linting, and
|
||||
[Biome](https://biomejs.dev/) for a combination of fast formatting, linting, and
|
||||
import sorting of JavaScript and TypeScript code
|
||||
- [clippy](https://github.com/rust-lang/rust-clippy) and
|
||||
[rustfmt](https://github.com/rust-lang/rustfmt) for Rust code
|
||||
- GitHub Actions to check code formatting and linting for both TypeScript and Rust
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Running development server and use Tauri window
|
||||
|
||||
After cloning for the first time, set up git pre-commit hooks:
|
||||
|
||||
```shell
|
||||
pnpm prepare
|
||||
```
|
||||
|
||||
To develop and run the frontend in a Tauri window:
|
||||
|
||||
```shell
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
This will load the Next.js frontend directly in a Tauri webview window, in addition to
|
||||
starting a development server on `localhost:3000`.
|
||||
|
||||
### Building for release
|
||||
|
||||
To export the Next.js frontend via SSG and build the Tauri application for release:
|
||||
|
||||
```shell
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Please remember to change the bundle identifier in
|
||||
`tauri.conf.json > tauri > bundle > identifier`, as the default value will yield an
|
||||
error that prevents you from building the application for release.
|
||||
|
||||
### Source structure
|
||||
|
||||
Next.js frontend source files are located in `src/` and Tauri Rust application source
|
||||
files are located in `src-tauri/`. Please consult the Next.js and Tauri documentation
|
||||
respectively for questions pertaining to either technology.
|
||||
|
||||
## Caveats
|
||||
|
||||
### Static Site Generation / Pre-rendering
|
||||
|
||||
Next.js is a great React frontend framework which supports server-side rendering (SSR)
|
||||
as well as static site generation (SSG or pre-rendering). For the purposes of creating a
|
||||
Tauri frontend, only SSG can be used since SSR requires an active Node.js server.
|
||||
|
||||
Using Next.js and SSG helps to provide a quick and performant single-page-application
|
||||
(SPA) frontend experience. More information regarding this can be found here:
|
||||
https://nextjs.org/docs/basic-features/pages#pre-rendering
|
||||
|
||||
### `next/image`
|
||||
|
||||
The [`next/image` component](https://nextjs.org/docs/basic-features/image-optimization)
|
||||
is an enhancement over the regular `<img>` HTML element with additional optimizations
|
||||
built in. However, because we are not deploying the frontend onto Vercel directly, some
|
||||
optimizations must be disabled to properly build and export the frontend via SSG.
|
||||
As such, the
|
||||
[`unoptimized` property](https://nextjs.org/docs/api-reference/next/image#unoptimized)
|
||||
is set to true for the `next/image` component in the `next.config.js` configuration.
|
||||
This will allow the image to be served as-is from source, without
|
||||
changes to its quality, size, or format.
|
||||
|
||||
### error[E0554]: `#![feature]` may not be used on the stable release channel
|
||||
|
||||
If you are getting this issue when trying to run `pnpm tauri dev`, it may be that you
|
||||
have a newer version of a Rust dependency that uses an unstable feature.
|
||||
`pnpm tauri build` should still work for production builds, but to get the dev command
|
||||
working, either downgrade the dependency or use Rust nightly via
|
||||
`rustup override set nightly`.
|
||||
|
||||
### ReferenceError: navigator is not defined
|
||||
|
||||
If you are using Tauri's `invoke` function or any OS related Tauri function from within
|
||||
JavaScript, you may encounter this error when importing the function in a global,
|
||||
non-browser context. This is due to the nature of Next.js' dev server effectively
|
||||
running a Node.js server for SSR and hot module replacement (HMR), and Node.js does not
|
||||
have a notion of `window` or `navigator`.
|
||||
|
||||
#### Solution 1 - Dependency Injection (may not always work)
|
||||
|
||||
Make sure that you are calling these functions within the browser context, e.g. within a
|
||||
React component inside a `useEffect` hook when the DOM actually exists by then. If you
|
||||
are trying to use a Tauri function in a generalized utility source file, a workaround is
|
||||
to use dependency injection for the function itself to delay the actual importing of the
|
||||
real function (see example below for more info).
|
||||
|
||||
Example using Tauri's `invoke` function:
|
||||
|
||||
`src/lib/some_tauri_functions.ts` (problematic)
|
||||
|
||||
```typescript
|
||||
// Generalized file containing all the invoke functions we need to fetch data from Rust
|
||||
import { invoke } from "@tauri-apps/api/tauri"
|
||||
|
||||
const loadFoo = (): Promise<string> => {
|
||||
return invoke<string>("invoke_handler_foo")
|
||||
}
|
||||
|
||||
const loadBar = (): Promise<string> => {
|
||||
return invoke<string>("invoke_handler_bar")
|
||||
}
|
||||
|
||||
const loadBaz = (): Promise<string> => {
|
||||
return invoke<string>("invoke_handler_baz")
|
||||
}
|
||||
|
||||
// and so on ...
|
||||
```
|
||||
|
||||
`src/lib/some_tauri_functions.ts` (fixed)
|
||||
|
||||
```typescript
|
||||
// Generalized file containing all the invoke functions we need to fetch data from Rust
|
||||
//
|
||||
// We apply the idea of dependency injection to use a supplied invoke function as a
|
||||
// function argument, rather than directly referencing the Tauri invoke function.
|
||||
// Hence, don't import invoke globally in this file.
|
||||
//
|
||||
// import { invoke } from "@tauri-apps/api/tauri" <-- remove this!
|
||||
//
|
||||
|
||||
import { InvokeArgs } from "@tauri-apps/api/tauri"
|
||||
type InvokeFunction = <T>(cmd: string, args?: InvokeArgs | undefined) => Promise<T>
|
||||
|
||||
const loadFoo = (invoke: InvokeFunction): Promise<string> => {
|
||||
return invoke<string>("invoke_handler_foo")
|
||||
}
|
||||
|
||||
const loadBar = (invoke: InvokeFunction): Promise<string> => {
|
||||
return invoke<string>("invoke_handler_bar")
|
||||
}
|
||||
|
||||
const loadBaz = (invoke: InvokeFunction): Promise<string> => {
|
||||
return invoke<string>("invoke_handler_baz")
|
||||
}
|
||||
|
||||
// and so on ...
|
||||
```
|
||||
|
||||
Then, when using `loadFoo`/`loadBar`/`loadBaz` within your React components, import the
|
||||
invoke function from `@tauri-apps/api` and pass `invoke` into the loadXXX function as
|
||||
the `InvokeFunction` argument. This should allow the actual Tauri API to be bundled
|
||||
only within the context of a React component, so it should not be loaded by Next.js upon
|
||||
initial startup until the browser has finished loading the page.
|
||||
|
||||
#### Solution 2: Wrap Tauri API behind dynamic `import()`
|
||||
|
||||
Since the Tauri API needs to read from the browser's `window` and `navigator` object,
|
||||
this data does not exist in a Node.js and hence SSR environment. One can create an
|
||||
exported function that wraps the Tauri API behind a dynamic runtime `import()` call.
|
||||
|
||||
Example: create a `src/lib/tauri.ts` to re-export `invoke`
|
||||
|
||||
```typescript
|
||||
import type { InvokeArgs } from "@tauri-apps/api/tauri"
|
||||
|
||||
const isNode = (): boolean =>
|
||||
Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) ===
|
||||
"[object process]"
|
||||
|
||||
export async function invoke<T>(
|
||||
cmd: string,
|
||||
args?: InvokeArgs | undefined,
|
||||
): Promise<T> {
|
||||
if (isNode()) {
|
||||
// This shouldn't ever happen when React fully loads
|
||||
return Promise.resolve(undefined as unknown as T)
|
||||
}
|
||||
const tauriAppsApi = await import("@tauri-apps/api")
|
||||
const tauriInvoke = tauriAppsApi.invoke
|
||||
return tauriInvoke(cmd, args)
|
||||
}
|
||||
```
|
||||
|
||||
Then, instead of importing `import { invoke } from "@tauri-apps/api/tauri"`, use invoke
|
||||
from `import { invoke } from "@/lib/tauri"`.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and
|
||||
API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
And to learn more about Tauri, take a look at the following resources:
|
||||
|
||||
- [Tauri Documentation - Guides](https://tauri.app/v1/guides/) - learn about the Tauri
|
||||
toolkit.
|
||||
> CS Toolbox
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"swr": "^2.3.3",
|
||||
"tauri-plugin-system-info-api": "^2.0.10"
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"productName": "CS工具箱",
|
||||
"mainBinaryName": "cstb",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"identifier": "upup.cool",
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
|
||||
@@ -7,6 +7,7 @@ import LaunchOption from "@/components/cstb/LaunchOption"
|
||||
import Notice from "@/components/cstb/Notice"
|
||||
import PowerPlan from "@/components/cstb/PowerPlan"
|
||||
import SmartTransfer from "@/components/cstb/SmartTranser"
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<section className="flex flex-col h-full gap-4">
|
||||
|
||||
@@ -49,7 +49,8 @@ export default function PreferenceLayout({
|
||||
<Videocamera /> 录像
|
||||
</CardIcon>
|
||||
|
||||
<CardTool>
|
||||
{/* TODO 完善云同步等功能 */}
|
||||
{/* <CardTool>
|
||||
<ToolButton>
|
||||
<UploadOne />
|
||||
云同步
|
||||
@@ -58,7 +59,7 @@ export default function PreferenceLayout({
|
||||
<HardDisk />
|
||||
保存
|
||||
</ToolButton>
|
||||
</CardTool>
|
||||
</CardTool> */}
|
||||
</CardHeader>
|
||||
<CardBody>{children}</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Plus, SettingConfig, Switch } from "@icon-park/react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
import { input, Textarea } from "@heroui/react"
|
||||
import { input, Textarea, Tooltip } from "@heroui/react"
|
||||
|
||||
const LaunchOption = () => {
|
||||
const tool = useToolStore()
|
||||
@@ -29,10 +29,12 @@ const LaunchOption = () => {
|
||||
<Plus />
|
||||
添加
|
||||
</ToolButton>
|
||||
<Tooltip content="功能测试中,尚未实装" showArrow={true} delay={300}>
|
||||
<ToolButton>
|
||||
<Switch />
|
||||
切换模式
|
||||
</ToolButton>
|
||||
</Tooltip>
|
||||
</CardTool>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
"use client"
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardIcon,
|
||||
CardTool,
|
||||
} from "@/components/window/Card"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "@/components/window/Card"
|
||||
import { useAppStore } from "@/store/app"
|
||||
import { createClient } from "@/utils/supabase/client"
|
||||
import { Skeleton } from "@heroui/react"
|
||||
import { Refresh, VolumeNotice } from "@icon-park/react"
|
||||
import { Button, Link, Skeleton } from "@heroui/react"
|
||||
import { Refresh, VolumeNotice, WebPage } from "@icon-park/react"
|
||||
import useSWR, { useSWRConfig } from "swr"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
import { MarkdownRender } from "../markdown"
|
||||
|
||||
const Notice = () => {
|
||||
const { mutate } = useSWRConfig()
|
||||
@@ -23,6 +18,12 @@ const Notice = () => {
|
||||
<VolumeNotice /> 公告
|
||||
</CardIcon>
|
||||
<CardTool>
|
||||
<Link href="https://cstb.upup.cool" target="_blank" className="dark:text-white text-zinc-800" >
|
||||
<ToolButton>
|
||||
<WebPage />
|
||||
官网
|
||||
</ToolButton>
|
||||
</Link>
|
||||
<ToolButton onClick={() => mutate("/api/notice")}>
|
||||
<Refresh />
|
||||
刷新
|
||||
@@ -45,15 +46,13 @@ const NoticeBody = () => {
|
||||
.from("Notice")
|
||||
.select("created_at, content, url")
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(1)
|
||||
.single()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const { data: notice /* , error */, isLoading } = useSWR(
|
||||
"/api/notice",
|
||||
noticeFetcher,
|
||||
)
|
||||
const { data: notice /* , error */, isLoading } = useSWR("/api/notice", noticeFetcher)
|
||||
|
||||
// if (error) return <>错误:{error}</>
|
||||
if (isLoading)
|
||||
@@ -64,11 +63,28 @@ const NoticeBody = () => {
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-full gap-2">
|
||||
<div className="">
|
||||
<MarkdownRender>
|
||||
{notice?.content ||
|
||||
app.state.notice ||
|
||||
"不会真的有人要更新CSGO工具箱吧,不会吧不会吧 xswl"}
|
||||
</>
|
||||
</MarkdownRender>
|
||||
</div>
|
||||
{/* {notice?.url && (
|
||||
<Button
|
||||
variant="flat"
|
||||
// color="default"
|
||||
as={Link}
|
||||
href={notice?.url}
|
||||
target="_blank"
|
||||
size="sm"
|
||||
className="bg-transparent w-fit"
|
||||
>
|
||||
传送门
|
||||
</Button>
|
||||
)} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { FolderConversion, FolderPlus } from "@icon-park/react"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
import { Tooltip } from "@heroui/react"
|
||||
|
||||
const SmartTransfer = () => {
|
||||
return (
|
||||
<Tooltip content="功能测试中,尚未实装" showArrow={true} delay={300}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardIcon>
|
||||
@@ -22,6 +24,7 @@ const SmartTransfer = () => {
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CloseSmall, Down, Edit, Plus, SettingConfig, Up } from "@icon-park/reac
|
||||
import { useState } from "react"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
import { addToast, NumberInput, Tab, Tabs } from "@heroui/react"
|
||||
import { addToast, NumberInput, Tab, Tabs, Tooltip } from "@heroui/react"
|
||||
import { motion } from "framer-motion"
|
||||
import { useToolStore } from "@/store/tool"
|
||||
|
||||
@@ -21,13 +21,14 @@ const VideoSetting = () => {
|
||||
const videoSettings = [
|
||||
{ type: "", title: "全屏", value: "全屏", options: ["窗口", "全屏"] },
|
||||
{ type: "", title: "垂直同步", value: "关闭", options: ["关闭", "开启"] },
|
||||
{ type: "", title: "低延迟模式", value: "关闭", options: ["关闭", "开启"] },
|
||||
{ type: "", title: "增强角色对比度", value: "禁用", options: ["禁用", "启用"] },
|
||||
{ type: "", title: "CMAA2抗锯齿", value: "关闭", options: ["关闭", "开启"] },
|
||||
{
|
||||
type: "",
|
||||
title: "多重采样抗锯齿",
|
||||
value: "2X MSAA",
|
||||
options: ["无", "CMAA2", "2X MSAA", "4X MSAA", "8X MSAA"],
|
||||
options: ["无", "2X MSAA", "4X MSAA", "8X MSAA"],
|
||||
},
|
||||
{ type: "", title: "全局阴影效果", value: "低", options: ["低", "中", "高", "非常高"] },
|
||||
{ type: "", title: "动态阴影", value: "全部", options: ["仅限日光", "全部"] },
|
||||
@@ -51,6 +52,7 @@ const VideoSetting = () => {
|
||||
]
|
||||
|
||||
return (
|
||||
<Tooltip content="功能测试中,尚未实装" showArrow={true} delay={300}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardIcon>
|
||||
@@ -118,8 +120,9 @@ const VideoSetting = () => {
|
||||
<ul className="flex flex-wrap gap-3 mt-1">
|
||||
<li className="flex flex-col gap-1.5">
|
||||
<span className="ml-2">分辨率</span>
|
||||
<span className="flex gap-1.5">
|
||||
<span className="flex gap-3">
|
||||
<NumberInput
|
||||
aria-label="width"
|
||||
value={tool.state.videoSetting.width}
|
||||
onValueChange={(value) => {
|
||||
tool.setVideoSetting({
|
||||
@@ -128,10 +131,12 @@ const VideoSetting = () => {
|
||||
})
|
||||
}}
|
||||
radius="full"
|
||||
step={10}
|
||||
className="max-w-28"
|
||||
classNames={{ inputWrapper: "h-10" }}
|
||||
/>
|
||||
<NumberInput
|
||||
aria-label="height"
|
||||
value={tool.state.videoSetting.height}
|
||||
onValueChange={(value) => {
|
||||
tool.setVideoSetting({
|
||||
@@ -140,6 +145,7 @@ const VideoSetting = () => {
|
||||
})
|
||||
}}
|
||||
radius="full"
|
||||
step={10}
|
||||
className="max-w-28"
|
||||
classNames={{ inputWrapper: "h-10" }}
|
||||
/>
|
||||
@@ -166,6 +172,7 @@ const VideoSetting = () => {
|
||||
</motion.div>
|
||||
)}
|
||||
</Card>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
27
src/components/markdown/index.tsx
Normal file
27
src/components/markdown/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Code, Link } from '@heroui/react'
|
||||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
export const components = {
|
||||
a: ({ href, children }: { href: string; children: React.ReactNode }) => (
|
||||
<Link href={href} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</Link>
|
||||
),
|
||||
// img: ({ src, alt }: { src: string; alt: string }) => <Image src={src} alt={alt} className="object-cover w-full h-full" />,
|
||||
h1: ({ children }: { children: React.ReactNode }) => <h1 className="text-2xl font-bold mb-2.5">{children}</h1>,
|
||||
h2: ({ children }: { children: React.ReactNode }) => <h2 className="text-xl font-semibold mb-2.5">{children}</h2>,
|
||||
h3: ({ children }: { children: React.ReactNode }) => <h3 className="text-lg font-medium mb-2.5">{children}</h3>,
|
||||
p: ({ children }: { children: React.ReactNode }) => <p className="mb-2.5 text-base">{children}</p>,
|
||||
ul: ({ children }: { children: React.ReactNode }) => <ul className="list-disc pl-6 mb-2.5">{children}</ul>,
|
||||
li: ({ children }: { children: React.ReactNode }) => <li className="mb-2">{children}</li>,
|
||||
code: ({ children }: { children: React.ReactNode }) => <Code size="sm" >{children}</Code>,
|
||||
}
|
||||
|
||||
export function MarkdownRender({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Markdown remarkPlugins={[remarkGfm]} components={components as any}>
|
||||
{children?.toString()}
|
||||
</Markdown>
|
||||
)
|
||||
}
|
||||
@@ -2,8 +2,18 @@
|
||||
import { setTheme as setTauriTheme } from "@/hooks/tauri/theme"
|
||||
import { useAppStore } from "@/store/app"
|
||||
import { useToolStore } from "@/store/tool"
|
||||
import { addToast, Button, useDisclosure } from "@heroui/react"
|
||||
import { Close, Minus, Moon, Refresh, RocketOne, Square, SunOne } from "@icon-park/react"
|
||||
import { addToast, Button, Link, Tooltip, useDisclosure } from "@heroui/react"
|
||||
import {
|
||||
Close,
|
||||
Communication,
|
||||
Minus,
|
||||
Moon,
|
||||
Refresh,
|
||||
RocketOne,
|
||||
Square,
|
||||
SunOne,
|
||||
SurprisedFaceWithOpenBigMouth,
|
||||
} from "@icon-park/react"
|
||||
import { type Theme, getCurrentWindow } from "@tauri-apps/api/window"
|
||||
import { /* relaunch, */ exit } from "@tauri-apps/plugin-process"
|
||||
import { useTheme } from "next-themes"
|
||||
@@ -46,26 +56,39 @@ const Nav = () => {
|
||||
|
||||
return (
|
||||
<nav className="absolute top-0 right-0 flex flex-row h-16 gap-0.5 p-4" data-tauri-drag-region>
|
||||
<Tooltip content="启动页确认设置" showArrow={true} delay={300}>
|
||||
{pathname !== "/" && (
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={() => {
|
||||
app.setInited(false)
|
||||
if(pathname !== "/") router.push("/")
|
||||
if (pathname !== "/") router.push("/")
|
||||
}}
|
||||
>
|
||||
<RocketOne size={16} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
</Tooltip>
|
||||
<Tooltip content="深色模式" showArrow={true} delay={300}>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={() => (theme === "light" ? setAppTheme("dark") : setAppTheme("light"))}
|
||||
>
|
||||
{theme === "light" ? <SunOne size={16} /> : <Moon size={16} />}
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="反馈" showArrow={true} delay={300}>
|
||||
<Link
|
||||
href="/preference"
|
||||
className="px-2 py-0 text-black transition duration-150 rounded dark:text-white hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
>
|
||||
<button type="button">
|
||||
<Communication size={16} />
|
||||
</button>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<ResetModal />
|
||||
|
||||
@@ -73,21 +96,21 @@ const Nav = () => {
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={minimize}
|
||||
>
|
||||
<Minus size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={toggleMaximize}
|
||||
>
|
||||
<Square size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={close}
|
||||
>
|
||||
<Close size={16} />
|
||||
@@ -119,13 +142,15 @@ function ResetModal() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip content="重置设置" showArrow={true} delay={300}>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={onOpen}
|
||||
>
|
||||
<Refresh size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
|
||||
@@ -96,13 +96,11 @@ const currentUser = () => {
|
||||
|
||||
const getUsers = async () => {
|
||||
const users = await invoke<SteamUser[]>("get_steam_users", { steamDir: steamStore.state.steamDir })
|
||||
console.log(users)
|
||||
setUsers(users)
|
||||
}
|
||||
|
||||
const selectUser = (index: number) => {
|
||||
const user = steamStore.state.users.at(index)
|
||||
console.log(index, user)
|
||||
if (user) {
|
||||
setUsers([
|
||||
user,
|
||||
|
||||
Reference in New Issue
Block a user