diff --git a/README.md b/README.md index 3fdbfc9..1d298d6 100644 --- a/README.md +++ b/README.md @@ -1,216 +1,3 @@ -# Tauri + Next.js Template +# CS工具箱 -![Tauri window screenshot](public/tauri-nextjs-template_screenshot.png) - -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 `` 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 => { - return invoke("invoke_handler_foo") -} - -const loadBar = (): Promise => { - return invoke("invoke_handler_bar") -} - -const loadBaz = (): Promise => { - return invoke("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 = (cmd: string, args?: InvokeArgs | undefined) => Promise - -const loadFoo = (invoke: InvokeFunction): Promise => { - return invoke("invoke_handler_foo") -} - -const loadBar = (invoke: InvokeFunction): Promise => { - return invoke("invoke_handler_bar") -} - -const loadBaz = (invoke: InvokeFunction): Promise => { - return invoke("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( - cmd: string, - args?: InvokeArgs | undefined, -): Promise { - 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 diff --git a/bun.lockb b/bun.lockb index e0720c7..e92ab49 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 41d7df1..5e99e40 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2e7d58a..ed82778 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -42,7 +42,7 @@ }, "productName": "CS工具箱", "mainBinaryName": "cstb", - "version": "0.0.4", + "version": "0.0.5", "identifier": "upup.cool", "plugins": { "deep-link": { diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index 09c402d..b0f9bd5 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -7,12 +7,13 @@ 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 (
- +
diff --git a/src/app/(main)/preference/layout.tsx b/src/app/(main)/preference/layout.tsx index ec05683..41f9b00 100644 --- a/src/app/(main)/preference/layout.tsx +++ b/src/app/(main)/preference/layout.tsx @@ -49,7 +49,8 @@ export default function PreferenceLayout({ 录像 - + {/* TODO 完善云同步等功能 */} + {/* 云同步 @@ -58,7 +59,7 @@ export default function PreferenceLayout({ 保存 - + */} {children} diff --git a/src/components/cstb/LaunchOption.tsx b/src/components/cstb/LaunchOption.tsx index d62b378..bc50318 100644 --- a/src/components/cstb/LaunchOption.tsx +++ b/src/components/cstb/LaunchOption.tsx @@ -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 = () => { 添加 - - - 切换模式 - + + + + 切换模式 + + diff --git a/src/components/cstb/Notice.tsx b/src/components/cstb/Notice.tsx index 1943010..23e315e 100644 --- a/src/components/cstb/Notice.tsx +++ b/src/components/cstb/Notice.tsx @@ -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 = () => { 公告 + + + + 官网 + + mutate("/api/notice")}> 刷新 @@ -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 ( - <> - {notice?.content || - app.state.notice || - "不会真的有人要更新CSGO工具箱吧,不会吧不会吧 xswl"} - +
+
+ + {notice?.content || + app.state.notice || + "不会真的有人要更新CSGO工具箱吧,不会吧不会吧 xswl"} + +
+ {/* {notice?.url && ( + + )} */} +
) } diff --git a/src/components/cstb/SmartTranser.tsx b/src/components/cstb/SmartTranser.tsx index 65b8acc..9808e17 100644 --- a/src/components/cstb/SmartTranser.tsx +++ b/src/components/cstb/SmartTranser.tsx @@ -1,27 +1,30 @@ 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 ( - - - - 智能中转 - - - - 选择文件{" "} - - - - -
-

点击或拖拽

-

智能中转 .dem .cfg

-
-
-
+ + + + + 智能中转 + + + + 选择文件{" "} + + + + +
+

点击或拖拽

+

智能中转 .dem .cfg

+
+
+
+
) } diff --git a/src/components/cstb/VideoSetting.tsx b/src/components/cstb/VideoSetting.tsx index 0908f5e..aec9ab5 100644 --- a/src/components/cstb/VideoSetting.tsx +++ b/src/components/cstb/VideoSetting.tsx @@ -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,121 +52,127 @@ const VideoSetting = () => { ] return ( - - - - 视频设置 - - - {/* {tool.state.VideoSettings.map((option, index) => ( + + + + + 视频设置 + + + {/* {tool.state.VideoSettings.map((option, index) => ( tool.setLaunchIndex(index)}> {index + 1} ))} */} - {edit && ( - <> - - - - 非常高 - 推荐 - { - addToast({ title: "测试中 功能完成后可应用设置到游戏" }) - }} - > - - 应用 - - - )} - setEdit(!edit)}> - {edit ? ( + {edit && ( <> - - 取消编辑 - - ) : ( - <> - - 编辑 + + + + 非常高 + 推荐 + { + addToast({ title: "测试中 功能完成后可应用设置到游戏" }) + }} + > + + 应用 + )} - - setHide(!hide)}> - {hide ? ( - <> - - 显示 - - ) : ( - <> - - 隐藏 - - )} - - - - {!hide && ( - - -
    -
  • - 分辨率 - - { - tool.setVideoSetting({ - ...tool.state.videoSetting, - width: value, - }) - }} - radius="full" - className="max-w-28" - classNames={{ inputWrapper: "h-10" }} - /> - { - tool.setVideoSetting({ - ...tool.state.videoSetting, - height: value, - }) - }} - radius="full" - className="max-w-28" - classNames={{ inputWrapper: "h-10" }} - /> - -
  • - {videoSettings.map((vid, index) => ( -
  • - {vid.title} - - {vid.options.map((opt, index) => ( - - ))} - + setEdit(!edit)}> + {edit ? ( + <> + + 取消编辑 + + ) : ( + <> + + 编辑 + + )} + + setHide(!hide)}> + {hide ? ( + <> + + 显示 + + ) : ( + <> + + 隐藏 + + )} + + + + {!hide && ( + + +
      +
    • + 分辨率 + + { + tool.setVideoSetting({ + ...tool.state.videoSetting, + width: value, + }) + }} + radius="full" + step={10} + className="max-w-28" + classNames={{ inputWrapper: "h-10" }} + /> + { + tool.setVideoSetting({ + ...tool.state.videoSetting, + height: value, + }) + }} + radius="full" + step={10} + className="max-w-28" + classNames={{ inputWrapper: "h-10" }} + /> +
    • - ))} -
    -
    -
    - )} - + {videoSettings.map((vid, index) => ( +
  • + {vid.title} + + {vid.options.map((opt, index) => ( + + ))} + +
  • + ))} +
+
+
+ )} +
+
) } diff --git a/src/components/markdown/index.tsx b/src/components/markdown/index.tsx new file mode 100644 index 0000000..0908091 --- /dev/null +++ b/src/components/markdown/index.tsx @@ -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 }) => ( + + {children} + + ), + // img: ({ src, alt }: { src: string; alt: string }) => {alt}, + h1: ({ children }: { children: React.ReactNode }) =>

{children}

, + h2: ({ children }: { children: React.ReactNode }) =>

{children}

, + h3: ({ children }: { children: React.ReactNode }) =>

{children}

, + p: ({ children }: { children: React.ReactNode }) =>

{children}

, + ul: ({ children }: { children: React.ReactNode }) =>
    {children}
, + li: ({ children }: { children: React.ReactNode }) =>
  • {children}
  • , + code: ({ children }: { children: React.ReactNode }) => {children}, +} + +export function MarkdownRender({ children }: { children: React.ReactNode }) { + return ( + + {children?.toString()} + + ) +} diff --git a/src/components/window/Nav.tsx b/src/components/window/Nav.tsx index d267232..0ecde4b 100644 --- a/src/components/window/Nav.tsx +++ b/src/components/window/Nav.tsx @@ -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 (