refactor: replace input controls with SliderControl for max concurrent requests, reader prefetch count, and circuit breaker settings in AdvancedSettings and BackgroundSettings components

This commit is contained in:
Julien Froidefond
2025-10-26 06:35:02 +01:00
parent 52350a43d9
commit 8376b7e5a1
3 changed files with 149 additions and 307 deletions

View File

@@ -1,11 +1,9 @@
import { useTranslate } from "@/hooks/useTranslate";
import { usePreferences } from "@/contexts/PreferencesContext";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { useToast } from "@/components/ui/use-toast";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Activity, ImageIcon, Shield, Info } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Activity, Shield } from "lucide-react";
import { SliderControl } from "@/components/ui/slider-control";
import logger from "@/lib/logger";
export function AdvancedSettings() {
@@ -87,84 +85,27 @@ export function AdvancedSettings() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Concurrent Requests */}
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<Label htmlFor="maxConcurrentRequests" className="text-base">
{t("settings.advanced.maxConcurrentRequests.label")}
</Label>
<Badge variant="secondary" className="text-xs">
{preferences.komgaMaxConcurrentRequests}
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{t("settings.advanced.maxConcurrentRequests.description")}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<Input
id="maxConcurrentRequests"
type="range"
min="1"
max="10"
<SliderControl
label={t("settings.advanced.maxConcurrentRequests.label")}
value={preferences.komgaMaxConcurrentRequests}
onChange={(e) => handleMaxConcurrentChange(parseInt(e.target.value))}
className="flex-1 cursor-pointer"
min={1}
max={10}
step={1}
description={t("settings.advanced.maxConcurrentRequests.description")}
onChange={handleMaxConcurrentChange}
/>
<Input
type="number"
min="1"
max="10"
value={preferences.komgaMaxConcurrentRequests}
onChange={(e) => handleMaxConcurrentChange(parseInt(e.target.value) || 1)}
className="w-20"
/>
</div>
</div>
<div className="border-t" />
{/* Reader Prefetch Count */}
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<ImageIcon className="h-4 w-4 text-muted-foreground" />
<Label htmlFor="prefetchCount" className="text-base">
{t("settings.advanced.prefetchCount.label")}
</Label>
<Badge variant="secondary" className="text-xs">
{preferences.readerPrefetchCount}
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{t("settings.advanced.prefetchCount.description")}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<Input
id="prefetchCount"
type="range"
min="0"
max="20"
<SliderControl
label={t("settings.advanced.prefetchCount.label")}
value={preferences.readerPrefetchCount}
onChange={(e) => handlePrefetchChange(parseInt(e.target.value))}
className="flex-1 cursor-pointer"
min={0}
max={20}
step={1}
description={t("settings.advanced.prefetchCount.description")}
onChange={handlePrefetchChange}
/>
<Input
type="number"
min="0"
max="20"
value={preferences.readerPrefetchCount}
onChange={(e) => handlePrefetchChange(parseInt(e.target.value) || 0)}
className="w-20"
/>
</div>
</div>
</CardContent>
</Card>
@@ -180,145 +121,41 @@ export function AdvancedSettings() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Threshold */}
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<Label htmlFor="cbThreshold" className="text-base">
{t("settings.advanced.circuitBreaker.threshold.label")}
</Label>
<Badge variant="destructive" className="text-xs">
{preferences.circuitBreakerConfig.threshold}
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{t("settings.advanced.circuitBreaker.threshold.description")}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<Input
id="cbThreshold"
type="range"
min="1"
max="20"
value={preferences.circuitBreakerConfig.threshold}
onChange={(e) =>
handleCircuitBreakerChange("threshold", parseInt(e.target.value))
}
className="flex-1 cursor-pointer"
<SliderControl
label={t("settings.advanced.circuitBreaker.threshold.label")}
value={preferences.circuitBreakerConfig.threshold ?? 5}
min={1}
max={20}
step={1}
description={t("settings.advanced.circuitBreaker.threshold.description")}
onChange={(value) => handleCircuitBreakerChange("threshold", value)}
/>
<Input
type="number"
min="1"
max="20"
value={preferences.circuitBreakerConfig.threshold}
onChange={(e) =>
handleCircuitBreakerChange("threshold", parseInt(e.target.value) || 5)
}
className="w-20"
/>
</div>
</div>
<div className="border-t" />
{/* Timeout */}
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<Label htmlFor="cbTimeout" className="text-base">
{t("settings.advanced.circuitBreaker.timeout.label")}
</Label>
<Badge variant="secondary" className="text-xs">
{(preferences.circuitBreakerConfig.timeout ?? 30000) / 1000}s
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{t("settings.advanced.circuitBreaker.timeout.description")}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<Input
id="cbTimeout"
type="range"
min="1000"
max="120000"
step="1000"
value={preferences.circuitBreakerConfig.timeout}
onChange={(e) =>
handleCircuitBreakerChange("timeout", parseInt(e.target.value))
}
className="flex-1 cursor-pointer"
<SliderControl
label={t("settings.advanced.circuitBreaker.timeout.label")}
value={preferences.circuitBreakerConfig.timeout ?? 30000}
min={1000}
max={120000}
step={1000}
description={t("settings.advanced.circuitBreaker.timeout.description")}
onChange={(value) => handleCircuitBreakerChange("timeout", value)}
formatValue={(value) => `${value / 1000}s`}
/>
<Input
type="number"
min="1"
max="120"
value={(preferences.circuitBreakerConfig.timeout ?? 30000) / 1000}
onChange={(e) =>
handleCircuitBreakerChange("timeout", (parseInt(e.target.value) || 30) * 1000)
}
className="w-20"
/>
</div>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Info className="h-3 w-3" />
<span>{t("settings.advanced.circuitBreaker.timeout.unit")}</span>
</div>
</div>
<div className="border-t" />
{/* Reset Timeout */}
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<Label htmlFor="cbResetTimeout" className="text-base">
{t("settings.advanced.circuitBreaker.resetTimeout.label")}
</Label>
<Badge variant="secondary" className="text-xs">
{(preferences.circuitBreakerConfig.resetTimeout ?? 60000) / 1000}s
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{t("settings.advanced.circuitBreaker.resetTimeout.description")}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<Input
id="cbResetTimeout"
type="range"
min="10000"
max="600000"
step="1000"
<SliderControl
label={t("settings.advanced.circuitBreaker.resetTimeout.label")}
value={preferences.circuitBreakerConfig.resetTimeout ?? 60000}
onChange={(e) =>
handleCircuitBreakerChange("resetTimeout", parseInt(e.target.value))
}
className="flex-1 cursor-pointer"
min={10000}
max={600000}
step={1000}
description={t("settings.advanced.circuitBreaker.resetTimeout.description")}
onChange={(value) => handleCircuitBreakerChange("resetTimeout", value)}
formatValue={(value) => `${value / 1000}s`}
/>
<Input
type="number"
min="10"
max="600"
value={(preferences.circuitBreakerConfig.resetTimeout ?? 60000) / 1000}
onChange={(e) =>
handleCircuitBreakerChange("resetTimeout", (parseInt(e.target.value) || 60) * 1000)
}
className="w-20"
/>
</div>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Info className="h-3 w-3" />
<span>{t("settings.advanced.circuitBreaker.resetTimeout.unit")}</span>
</div>
</div>
</CardContent>
</Card>
</div>

View File

@@ -10,10 +10,10 @@ import { Button } from "@/components/ui/button";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { GRADIENT_PRESETS } from "@/types/preferences";
import type { BackgroundType } from "@/types/preferences";
import { Check, Minus, Plus } from "lucide-react";
import { Check } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Slider } from "@/components/ui/slider";
import { Checkbox } from "@/components/ui/checkbox";
import { SliderControl } from "@/components/ui/slider-control";
import type { KomgaLibrary } from "@/types/komga";
import logger from "@/lib/logger";
@@ -147,25 +147,6 @@ export function BackgroundSettings() {
}
};
const adjustOpacity = async (delta: number) => {
try {
const currentOpacity = preferences.background.opacity || 10;
const newOpacity = Math.max(0, Math.min(100, currentOpacity + delta));
await handleOpacityChange([newOpacity]);
} catch (error) {
logger.error({ err: error }, "Erreur ajustement opacité:");
}
};
const adjustBlur = async (delta: number) => {
try {
const currentBlur = preferences.background.blur || 0;
const newBlur = Math.max(0, Math.min(20, currentBlur + delta));
await handleBlurChange([newBlur]);
} catch (error) {
logger.error({ err: error }, "Erreur ajustement flou:");
}
};
const handleLibraryToggle = async (libraryId: string) => {
const newSelection = selectedLibraries.includes(libraryId)
@@ -317,81 +298,27 @@ export function BackgroundSettings() {
preferences.background.type === "image" ||
preferences.background.type === "komga-random") && (
<>
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>Opacité du contenu</Label>
<span className="text-sm text-muted-foreground">{preferences.background.opacity || 10}%</span>
</div>
<div className="space-y-3">
<div className="flex items-center gap-3">
<Button
variant="outline"
size="sm"
onClick={() => adjustOpacity(-5)}
className="h-10 w-10 p-0"
>
<Minus className="h-4 w-4" />
</Button>
<Slider
value={[preferences.background.opacity || 10]}
onValueChange={handleOpacityChange}
<SliderControl
label="Opacité du contenu"
value={preferences.background.opacity || 10}
min={0}
max={100}
step={5}
className="flex-1"
unit="%"
description="Contrôle la transparence du contenu par rapport au background"
onChange={(value) => handleOpacityChange([value])}
/>
<Button
variant="outline"
size="sm"
onClick={() => adjustOpacity(5)}
className="h-10 w-10 p-0"
>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
<p className="text-xs text-muted-foreground">
Contrôle la transparence du contenu par rapport au background
</p>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>Flou du background</Label>
<span className="text-sm text-muted-foreground">{preferences.background.blur || 0}px</span>
</div>
<div className="space-y-3">
<div className="flex items-center gap-3">
<Button
variant="outline"
size="sm"
onClick={() => adjustBlur(-1)}
className="h-10 w-10 p-0"
>
<Minus className="h-4 w-4" />
</Button>
<Slider
value={[preferences.background.blur || 0]}
onValueChange={handleBlurChange}
<SliderControl
label="Flou du background"
value={preferences.background.blur || 0}
min={0}
max={20}
step={1}
className="flex-1"
unit="px"
description="Applique un effet de flou au background"
onChange={(value) => handleBlurChange([value])}
/>
<Button
variant="outline"
size="sm"
onClick={() => adjustBlur(1)}
className="h-10 w-10 p-0"
>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
<p className="text-xs text-muted-foreground">
Applique un effet de flou au background
</p>
</div>
</>
)}
</div>

View File

@@ -0,0 +1,78 @@
"use client";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Slider } from "@/components/ui/slider";
import { Minus, Plus } from "lucide-react";
interface SliderControlProps {
label: string;
value: number;
min: number;
max: number;
step?: number;
unit?: string;
description?: string;
onChange: (value: number) => void;
formatValue?: (value: number) => string;
}
export function SliderControl({
label,
value,
min,
max,
step = 1,
unit = "",
description,
onChange,
formatValue,
}: SliderControlProps) {
const adjust = (delta: number) => {
const newValue = Math.max(min, Math.min(max, value + delta));
onChange(newValue);
};
const displayValue = formatValue ? formatValue(value) : `${value}${unit}`;
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>{label}</Label>
<span className="text-sm text-muted-foreground">{displayValue}</span>
</div>
<div className="flex items-center gap-3">
<Button
variant="outline"
size="sm"
onClick={() => adjust(-step)}
className="h-10 w-10 p-0 shrink-0"
disabled={value <= min}
>
<Minus className="h-4 w-4" />
</Button>
<Slider
value={[value]}
onValueChange={(values) => onChange(values[0])}
min={min}
max={max}
step={step}
className="flex-1"
/>
<Button
variant="outline"
size="sm"
onClick={() => adjust(step)}
className="h-10 w-10 p-0 shrink-0"
disabled={value >= max}
>
<Plus className="h-4 w-4" />
</Button>
</div>
{description && (
<p className="text-xs text-muted-foreground">{description}</p>
)}
</div>
);
}