fix: improve service worker offline flow and dev toggle UX

This commit is contained in:
2026-03-01 12:47:58 +01:00
parent 844cd3f58e
commit 5a3b0ace61
9 changed files with 176 additions and 22 deletions

View File

@@ -8,6 +8,8 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
@@ -197,12 +199,15 @@ export function CacheSettings() {
const {
isSupported,
isReady,
isDevModeEnabled,
version,
getCacheStats,
getCacheEntries,
clearCache,
reinstallServiceWorker,
setDevModeEnabled,
} = useServiceWorker();
const isDevelopment = process.env.NODE_ENV === "development";
const [stats, setStats] = useState<CacheStats | null>(null);
const [isLoading, setIsLoading] = useState(false);
@@ -276,6 +281,25 @@ export function CacheSettings() {
}
};
const handleServiceWorkerDevToggle = async (checked: boolean) => {
try {
const success = await setDevModeEnabled(checked);
if (!success) {
throw new Error("Failed to toggle service worker in development");
}
toast({
title: t("settings.title"),
description: t("settings.cache.devServiceWorker.saved"),
});
} catch {
toast({
variant: "destructive",
title: t("settings.error.title"),
description: t("settings.cache.devServiceWorker.error"),
});
}
};
// Calculer le pourcentage du cache utilisé (basé sur 100MB limite images)
const maxCacheSize = 100 * 1024 * 1024; // 100MB
const usagePercent = stats ? Math.min((stats.images.size / maxCacheSize) * 100, 100) : 0;
@@ -328,6 +352,22 @@ export function CacheSettings() {
<CardDescription>{t("settings.cache.description")}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{isDevelopment && (
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label htmlFor="dev-sw-toggle">{t("settings.cache.devServiceWorker.label")}</Label>
<p className="text-sm text-muted-foreground">
{t("settings.cache.devServiceWorker.description")}
</p>
</div>
<Switch
id="dev-sw-toggle"
checked={isDevModeEnabled}
onCheckedChange={handleServiceWorkerDevToggle}
/>
</div>
)}
{/* Barre de progression globale */}
{stats && (
<div className="space-y-2">

View File

@@ -1,5 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import type { KomgaConfig } from "@/types/komga";
import type { KomgaLibrary } from "@/types/komga";
import { useTranslate } from "@/hooks/useTranslate";
@@ -16,15 +17,35 @@ interface ClientSettingsProps {
initialLibraries: KomgaLibrary[];
}
const SETTINGS_TAB_STORAGE_KEY = "stripstream:settings-active-tab";
export function ClientSettings({ initialConfig, initialLibraries }: ClientSettingsProps) {
const { t } = useTranslate();
const [activeTab, setActiveTab] = useState<"display" | "connection">("display");
useEffect(() => {
const savedTab = window.sessionStorage.getItem(SETTINGS_TAB_STORAGE_KEY);
if (savedTab === "display" || savedTab === "connection") {
const rafId = window.requestAnimationFrame(() => {
setActiveTab(savedTab);
});
return () => window.cancelAnimationFrame(rafId);
}
}, []);
const handleTabChange = (tab: string) => {
if (tab === "display" || tab === "connection") {
setActiveTab(tab);
window.sessionStorage.setItem(SETTINGS_TAB_STORAGE_KEY, tab);
}
};
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto space-y-8">
<h1 className="text-3xl font-bold">{t("settings.title")}</h1>
<Tabs defaultValue="display" className="w-full">
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="display" className="flex items-center gap-2">
<Monitor className="h-4 w-4" />

View File

@@ -9,7 +9,7 @@ export function NetworkStatus() {
if (isOnline) return null;
return (
<div className="fixed bottom-4 left-4 z-[100] flex items-center gap-2 rounded-lg bg-destructive/90 backdrop-blur-md px-4 py-2 text-sm text-destructive-foreground shadow-lg">
<div className="fixed right-4 top-[calc(4.5rem+env(safe-area-inset-top,0px))] z-[110] flex items-center gap-2 rounded-full border border-destructive/40 bg-destructive/90 px-3 py-1.5 text-xs font-semibold uppercase tracking-wide text-destructive-foreground shadow-lg backdrop-blur-md animate-in fade-in slide-in-from-top-1">
<WifiOff className="h-4 w-4" />
<span>Hors ligne</span>
</div>