Serial/parallel transfers setting

Transfers: Single | Multi in the options popover, with an 'At once' picker
(2/3/4/6/8) when Multi. Engine concurrency becomes an instance field applied
live: at boot from settings.json and on every saveSettings (raising the limit
starts queued jobs immediately). Verified headless: 3 slow jobs overlap at
concurrency 3, stay strictly serial at 1, clamp works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jokin
2026-07-11 17:32:21 +02:00
co-authored by Claude Fable 5
parent ff62c07f15
commit c95087c079
6 changed files with 79 additions and 4 deletions
+16
View File
@@ -7,6 +7,8 @@ export class SettingsService {
readonly theme = signal<AppSettings['theme']>(DEFAULT_SETTINGS.theme);
readonly speedUnit = signal<AppSettings['speedUnit']>(DEFAULT_SETTINGS.speedUnit);
readonly textSize = signal<AppSettings['textSize']>(DEFAULT_SETTINGS.textSize);
readonly transferMode = signal<AppSettings['transferMode']>(DEFAULT_SETTINGS.transferMode);
readonly transferConcurrency = signal(DEFAULT_SETTINGS.transferConcurrency);
private readonly rpc = inject(RpcService);
@@ -17,6 +19,8 @@ export class SettingsService {
this.theme.set(res.settings.theme);
this.speedUnit.set(res.settings.speedUnit);
this.textSize.set(res.settings.textSize);
this.transferMode.set(res.settings.transferMode);
this.transferConcurrency.set(res.settings.transferConcurrency);
this.applyTheme();
this.applyTextSize();
});
@@ -39,6 +43,16 @@ export class SettingsService {
void this.persist();
}
setTransferMode(mode: AppSettings['transferMode']): void {
this.transferMode.set(mode);
void this.persist();
}
setTransferConcurrency(count: number): void {
this.transferConcurrency.set(count);
void this.persist();
}
private applyTheme(): void {
const theme = this.theme();
if (theme === 'system') {
@@ -62,6 +76,8 @@ export class SettingsService {
theme: this.theme(),
speedUnit: this.speedUnit(),
textSize: this.textSize(),
transferMode: this.transferMode(),
transferConcurrency: this.transferConcurrency(),
};
}
+40
View File
@@ -10,6 +10,8 @@ const SPEED_UNITS = [
{ value: 'Mbps', label: 'Mbps' },
] as const;
const TEXT_SIZES = ['compact', 'default', 'large'] as const;
const TRANSFER_MODES = ['single', 'multi'] as const;
const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
@Component({
selector: 'app-tab-bar',
@@ -136,6 +138,42 @@ const TEXT_SIZES = ['compact', 'default', 'large'] as const;
}
</div>
</div>
<div class="mt-2.5 flex items-center justify-between gap-2">
<span class="text-xs text-muted-foreground">Transfers</span>
<div class="flex rounded-md border border-border p-0.5">
@for (mode of transferModes; track mode) {
<button
(click)="settings.setTransferMode(mode)"
class="rounded px-2 py-0.5 text-[11px] capitalize"
[class.bg-accent]="settings.transferMode() === mode"
[class.text-foreground]="settings.transferMode() === mode"
[class.text-muted-foreground]="settings.transferMode() !== mode"
>
{{ mode }}
</button>
}
</div>
</div>
@if (settings.transferMode() === 'multi') {
<div class="mt-2.5 flex items-center justify-between gap-2 pl-3">
<span class="text-xs text-muted-foreground">At once</span>
<div class="flex rounded-md border border-border p-0.5">
@for (count of concurrencyChoices; track count) {
<button
(click)="settings.setTransferConcurrency(count)"
class="rounded px-2 py-0.5 text-[11px] tabular-nums"
[class.bg-accent]="settings.transferConcurrency() === count"
[class.text-foreground]="settings.transferConcurrency() === count"
[class.text-muted-foreground]="settings.transferConcurrency() !== count"
>
{{ count }}
</button>
}
</div>
</div>
}
</div>
}
</div>
@@ -149,6 +187,8 @@ export class TabBar {
readonly themes = THEMES;
readonly speedUnits = SPEED_UNITS;
readonly textSizes = TEXT_SIZES;
readonly transferModes = TRANSFER_MODES;
readonly concurrencyChoices = CONCURRENCY_CHOICES;
close(event: MouseEvent, id: number): void {
event.stopPropagation();