From 4eb62ea36355fbebcb075e4a95a822c03fde8d68 Mon Sep 17 00:00:00 2001 From: "Ted \"skjsjhb\" Gao" Date: Mon, 13 Jan 2025 01:58:24 +0800 Subject: [PATCH] feat(ui): add network settings - Add partial settings for network-related configurations. - Make scrollbar thinner. - Filter args passed to aria2. - Add launch settings tab. - Add multiline text input. - Add slider-based number input. --- public/i18n/zh-CN/pages.yml | 18 +++++- src/main/net/aria2.ts | 4 +- src/renderer/global.css | 2 +- src/renderer/pages/settings/NetworkTab.tsx | 59 +++++++++++++++++++ src/renderer/pages/settings/Settings.tsx | 35 ++++++++--- src/renderer/pages/settings/SettingsEntry.tsx | 35 ++++++++++- 6 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 src/renderer/pages/settings/NetworkTab.tsx diff --git a/public/i18n/zh-CN/pages.yml b/public/i18n/zh-CN/pages.yml index 10462052..f6a4319c 100644 --- a/public/i18n/zh-CN/pages.yml +++ b/public/i18n/zh-CN/pages.yml @@ -3,9 +3,10 @@ launch: settings: title: 设置 - hint: 默认设置即是推荐设置。若要修改设置项,请确保了解其功能。 + hint: 默认设置即是推荐设置。若要修改设置项,请确保了解其功能。部分选项在下一次启动时才会生效。 tabs: pref: 个性化 + launch: 启动 network: 网络 dev: 开发 entries: @@ -22,6 +23,21 @@ settings: zh-CN: 简体中文 en: English + aria2: + title: aria2 下载器 + sub: |- + 启用此选项以自动使用随附或系统中的 aria2 以改进下载性能。 + 若禁用,Alicorn 将使用内置的下载程序。 + aria2-concurrency: + title: aria2 并行数 + sub: aria2 最多可同时下载的任务个数。 + aria2-args: + title: aria2 启动选项 + sub: 在启动 aria2 时添加以下命令行选项,一行填写一个。 + nextdl-concurrency: + title: 默认并行数 + sub: 内置下载器可同时下载的任务个数。 + about: title: 关于 diff --git a/src/main/net/aria2.ts b/src/main/net/aria2.ts index 989fa4f2..1989e711 100644 --- a/src/main/net/aria2.ts +++ b/src/main/net/aria2.ts @@ -141,7 +141,7 @@ async function init() { const cert = paths.app.to("vendor", "ca-cert.pem"); aria2cProcess = child_process.spawn(pt, [ - `--max-concurrent-downloads=${concurrency}`, + `--max-concurrent-downloads=${concurrency > 1 ? concurrency : 1}`, "--max-connection-per-server=16", `--connect-timeout=${requestTimeout}`, `--timeout=${transferTimeout}`, @@ -150,7 +150,7 @@ async function init() { "--rpc-max-request-size=32M", `--rpc-secret=${aria2cToken}`, `--ca-certificate=${cert}`, - ...args.split("\n").filter(Boolean) + ...args.split("\n").map(a => a.trim()).filter(Boolean) ]); console.debug("Connecting to aria2 RPC interface..."); diff --git a/src/renderer/global.css b/src/renderer/global.css index b4044fef..50294789 100644 --- a/src/renderer/global.css +++ b/src/renderer/global.css @@ -17,7 +17,7 @@ html, body { } ::-webkit-scrollbar { - width: 0.5rem; + width: 0.4rem; } ::-webkit-scrollbar-track { diff --git a/src/renderer/pages/settings/NetworkTab.tsx b/src/renderer/pages/settings/NetworkTab.tsx new file mode 100644 index 00000000..b13878d4 --- /dev/null +++ b/src/renderer/pages/settings/NetworkTab.tsx @@ -0,0 +1,59 @@ +import { Divider } from "@nextui-org/react"; +import { MultilineTextEntry, NumberSliderEntry, OnOffEntry } from "@pages/settings/SettingsEntry"; +import { useConfig } from "@pages/settings/use-config"; +import { CodeIcon, MoveToBottomIcon, UnfoldIcon } from "@primer/octicons-react"; +import React, { FC } from "react"; + +/** + * Network configuration page. + */ +export const NetworkTab: FC = () => { + const [config, makeReduce] = useConfig(); + + if (!config) return; + + return <> + c.net.downloader = isAria2 ? "aria2" : "nextdl")} + /> + + + + { + config.net.downloader === "aria2" && + <> + c.net.aria2.concurrency = a > 1 ? a : 1)} + /> + + + + c.net.aria2.args = a)} + /> + + + + } + + c.net.next.concurrency = a > 1 ? a : 1)} + /> + ; +}; \ No newline at end of file diff --git a/src/renderer/pages/settings/Settings.tsx b/src/renderer/pages/settings/Settings.tsx index c2c21541..b97d333f 100644 --- a/src/renderer/pages/settings/Settings.tsx +++ b/src/renderer/pages/settings/Settings.tsx @@ -1,15 +1,17 @@ import type { UserConfig } from "@/main/conf/conf"; -import { Alert, Tab, Tabs } from "@nextui-org/react"; +import { Alert, ScrollShadow, Tab, Tabs } from "@nextui-org/react"; +import { NetworkTab } from "@pages/settings/NetworkTab"; import { PreferencesTab } from "@pages/settings/PreferencesTab"; import { ConfigContext as ConfigContext1, type ConfigContextContent } from "@pages/settings/use-config"; -import { CodeIcon, GlobeIcon, type Icon, PaintbrushIcon } from "@primer/octicons-react"; +import { CodeIcon, GlobeIcon, type Icon, PaintbrushIcon, RocketIcon } from "@primer/octicons-react"; import React, { type FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useSessionStorage } from "react-use"; interface SettingsPage { id: string; icon: Icon; - content?: React.ComponentType; + content: React.ComponentType; } const settingsTabs: SettingsPage[] = [ @@ -18,13 +20,20 @@ const settingsTabs: SettingsPage[] = [ icon: PaintbrushIcon, content: PreferencesTab }, + { + id: "launch", + icon: RocketIcon, + content: () => null + }, { id: "network", - icon: GlobeIcon + icon: GlobeIcon, + content: NetworkTab }, { id: "dev", - icon: CodeIcon + icon: CodeIcon, + content: () => null } ]; @@ -34,6 +43,7 @@ const settingsTabs: SettingsPage[] = [ export const Settings: FC = () => { const { t } = useTranslation("pages", { keyPrefix: "settings" }); const [config, setConfig] = useState(); + const [tab, setTab] = useSessionStorage("settings.tab", settingsTabs[0].id); useEffect(() => { native.conf.get().then(setConfig); @@ -62,7 +72,14 @@ export const Settings: FC = () => {
- + setTab(s.toString())} + classNames={{ + wrapper: "h-full" + }} + > { settingsTabs.map(({ id, icon, content }) => { return {
} > -
+
- {content && React.createElement(content)} + {React.createElement(content)}
-
+ ; }) } diff --git a/src/renderer/pages/settings/SettingsEntry.tsx b/src/renderer/pages/settings/SettingsEntry.tsx index 2d583f2a..d54abd4c 100644 --- a/src/renderer/pages/settings/SettingsEntry.tsx +++ b/src/renderer/pages/settings/SettingsEntry.tsx @@ -1,7 +1,7 @@ /** * Various entry widgets for manipulating the settings. */ -import { Input, Select, SelectItem, type SharedSelection, Switch } from "@nextui-org/react"; +import { Input, Select, SelectItem, type SharedSelection, Slider, Switch, Textarea } from "@nextui-org/react"; import type { Icon } from "@primer/octicons-react"; import React, { type FC } from "react"; import { useTranslation } from "react-i18next"; @@ -25,7 +25,7 @@ const Title = ({ id, icon }: { id: string, icon?: Icon }) => { const Subtitle = ({ id }: { id: string }) => { const { t } = useTranslation("pages", { keyPrefix: "settings.entries" }); - return
{t(`${id}.sub`)}
; + return
{t(`${id}.sub`)}
; }; const EntryLabel = ({ id, icon }: { id: string; icon?: Icon }) => { @@ -43,6 +43,37 @@ export const TextEntry: FC> = ({ id, icon, value, onC ; }; +export const NumberTextEntry: FC> = ({ id, icon, value, onChange }) => { + return
+ + onChange(parseInt(s || "0", 10))}/> +
; +}; + + +type NumberSliderEntryProps = SettingsEntryProps & { max: number; min: number; } + +export const NumberSliderEntry: FC = ({ id, icon, value, onChange, max, min }) => { + return
+ + {min}} + endContent={{max}} + onChange={(v) => onChange(Array.isArray(v) ? v[0] : v)}/> +
; +}; + + +export const MultilineTextEntry: FC> = ({ id, icon, value, onChange }) => { + return
+ +