Skip to content

Commit

Permalink
feat(ui): add network settings
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
skjsjhb committed Jan 12, 2025
1 parent 8cf3a85 commit 4eb62ea
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 15 deletions.
18 changes: 17 additions & 1 deletion public/i18n/zh-CN/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ launch:

settings:
title: 设置
hint: 默认设置即是推荐设置。若要修改设置项,请确保了解其功能。
hint: 默认设置即是推荐设置。若要修改设置项,请确保了解其功能。部分选项在下一次启动时才会生效。
tabs:
pref: 个性化
launch: 启动
network: 网络
dev: 开发
entries:
Expand All @@ -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: 关于
Expand Down
4 changes: 2 additions & 2 deletions src/main/net/aria2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand All @@ -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...");
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ html, body {
}

::-webkit-scrollbar {
width: 0.5rem;
width: 0.4rem;
}

::-webkit-scrollbar-track {
Expand Down
59 changes: 59 additions & 0 deletions src/renderer/pages/settings/NetworkTab.tsx
Original file line number Diff line number Diff line change
@@ -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 <>
<OnOffEntry
icon={MoveToBottomIcon}
id="aria2"
value={config.net.downloader === "aria2"}
onChange={makeReduce((c, isAria2) => c.net.downloader = isAria2 ? "aria2" : "nextdl")}
/>

<Divider/>

{
config.net.downloader === "aria2" &&
<>
<NumberSliderEntry
icon={UnfoldIcon}
id="aria2-concurrency"
value={config.net.aria2.concurrency}
max={32}
min={1}
onChange={makeReduce((c, a) => c.net.aria2.concurrency = a > 1 ? a : 1)}
/>

<Divider/>

<MultilineTextEntry
icon={CodeIcon}
id="aria2-args"
value={config.net.aria2.args}
onChange={makeReduce((c, a) => c.net.aria2.args = a)}
/>

<Divider/>
</>
}

<NumberSliderEntry
icon={UnfoldIcon}
id="nextdl-concurrency"
value={config.net.next.concurrency}
max={64}
min={1}
onChange={makeReduce((c, a) => c.net.next.concurrency = a > 1 ? a : 1)}
/>
</>;
};
35 changes: 26 additions & 9 deletions src/renderer/pages/settings/Settings.tsx
Original file line number Diff line number Diff line change
@@ -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[] = [
Expand All @@ -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
}
];

Expand All @@ -34,6 +43,7 @@ const settingsTabs: SettingsPage[] = [
export const Settings: FC = () => {
const { t } = useTranslation("pages", { keyPrefix: "settings" });
const [config, setConfig] = useState<UserConfig>();
const [tab, setTab] = useSessionStorage("settings.tab", settingsTabs[0].id);

useEffect(() => {
native.conf.get().then(setConfig);
Expand Down Expand Up @@ -62,7 +72,14 @@ export const Settings: FC = () => {
</div>

<div className="grow w-full flex flex-col min-h-0">
<Tabs isVertical>
<Tabs
isVertical
selectedKey={tab}
onSelectionChange={(s) => setTab(s.toString())}
classNames={{
wrapper: "h-full"
}}
>
{
settingsTabs.map(({ id, icon, content }) => {
return <Tab
Expand All @@ -75,13 +92,13 @@ export const Settings: FC = () => {
</div>
}
>
<div className="w-full h-full overflow-y-auto">
<ScrollShadow className="w-full h-full overflow-y-auto" size={10}>
<div className="flex flex-col gap-6 w-full px-4 py-2">
<ConfigContext1 value={context}>
{content && React.createElement(content)}
{React.createElement(content)}
</ConfigContext1>
</div>
</div>
</ScrollShadow>
</Tab>;
})
}
Expand Down
35 changes: 33 additions & 2 deletions src/renderer/pages/settings/SettingsEntry.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 <div className="text-sm text-foreground-400">{t(`${id}.sub`)}</div>;
return <div className="text-sm text-foreground-400 whitespace-pre-line">{t(`${id}.sub`)}</div>;
};

const EntryLabel = ({ id, icon }: { id: string; icon?: Icon }) => {
Expand All @@ -43,6 +43,37 @@ export const TextEntry: FC<SettingsEntryProps<string>> = ({ id, icon, value, onC
</div>;
};

export const NumberTextEntry: FC<SettingsEntryProps<number>> = ({ id, icon, value, onChange }) => {
return <div className="flex flex-col gap-2 w-full">
<EntryLabel id={id} icon={icon}/>
<Input fullWidth type="number" value={value.toString()}
onValueChange={(s) => onChange(parseInt(s || "0", 10))}/>
</div>;
};


type NumberSliderEntryProps = SettingsEntryProps<number> & { max: number; min: number; }

export const NumberSliderEntry: FC<NumberSliderEntryProps> = ({ id, icon, value, onChange, max, min }) => {
return <div className="flex flex-col gap-4 w-full">
<EntryLabel id={id} icon={icon}/>
<Slider maxValue={max} minValue={min} value={value} hideThumb
showTooltip
tooltipProps={{ size: "lg", radius: "full" }}
startContent={<span className="text-foreground-400">{min}</span>}
endContent={<span className="text-foreground-400">{max}</span>}
onChange={(v) => onChange(Array.isArray(v) ? v[0] : v)}/>
</div>;
};


export const MultilineTextEntry: FC<SettingsEntryProps<string>> = ({ id, icon, value, onChange }) => {
return <div className="flex flex-col gap-2 w-full">
<EntryLabel id={id} icon={icon}/>
<Textarea fullWidth value={value} onValueChange={onChange}/>
</div>;
};

export const OnOffEntry: FC<SettingsEntryProps<boolean>> = ({ id, icon, value, onChange }) => {
return <div className="flex flex-col gap-2 w-full">

Expand Down

0 comments on commit 4eb62ea

Please sign in to comment.