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

View File

@@ -10,10 +10,10 @@ import { Button } from "@/components/ui/button";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { GRADIENT_PRESETS } from "@/types/preferences"; import { GRADIENT_PRESETS } from "@/types/preferences";
import type { BackgroundType } 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 { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Slider } from "@/components/ui/slider";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { SliderControl } from "@/components/ui/slider-control";
import type { KomgaLibrary } from "@/types/komga"; import type { KomgaLibrary } from "@/types/komga";
import logger from "@/lib/logger"; 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 handleLibraryToggle = async (libraryId: string) => {
const newSelection = selectedLibraries.includes(libraryId) const newSelection = selectedLibraries.includes(libraryId)
@@ -317,81 +298,27 @@ export function BackgroundSettings() {
preferences.background.type === "image" || preferences.background.type === "image" ||
preferences.background.type === "komga-random") && ( preferences.background.type === "komga-random") && (
<> <>
<div className="space-y-3"> <SliderControl
<div className="flex items-center justify-between"> label="Opacité du contenu"
<Label>Opacité du contenu</Label> value={preferences.background.opacity || 10}
<span className="text-sm text-muted-foreground">{preferences.background.opacity || 10}%</span> min={0}
</div> max={100}
<div className="space-y-3"> step={5}
<div className="flex items-center gap-3"> unit="%"
<Button description="Contrôle la transparence du contenu par rapport au background"
variant="outline" onChange={(value) => handleOpacityChange([value])}
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}
min={0}
max={100}
step={5}
className="flex-1"
/>
<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"> <SliderControl
<div className="flex items-center justify-between"> label="Flou du background"
<Label>Flou du background</Label> value={preferences.background.blur || 0}
<span className="text-sm text-muted-foreground">{preferences.background.blur || 0}px</span> min={0}
</div> max={20}
<div className="space-y-3"> step={1}
<div className="flex items-center gap-3"> unit="px"
<Button description="Applique un effet de flou au background"
variant="outline" onChange={(value) => handleBlurChange([value])}
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}
min={0}
max={20}
step={1}
className="flex-1"
/>
<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> </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>
);
}