feat(pwa): Update-Toast zeigt neue Version an
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m15s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m15s
pwaStore ($lib/client/pwa.svelte.ts): - Hängt sich an navigator.serviceWorker.ready, hört auf updatefound und setzt updateAvailable = true, sobald ein neuer SW im Status 'installed' ist UND es einen aktiven controller gibt (= Update eines bestehenden Tabs, nicht die erste Installation). - Polling alle 30 Minuten via registration.update(), damit der User den Toast auch sieht, wenn er die Seite lange offen hat ohne zu navigieren. - reload() ruft location.reload(); dismiss() schließt den Toast nur. UpdateToast.svelte: - Schwarzer Pill-Toast unten zentriert, mit Text, grünem "Neu laden"- Button (RefreshCw-Icon) und X zum Wegklicken. - Slide-Up-Animation beim Erscheinen. - Responsive: auf Mobile (<420px) wird's zum vollbreiten Banner statt Pill. Root-Layout mountet <UpdateToast /> direkt neben <ConfirmDialog />. onMount ruft pwaStore.init(). Status-Check der Live-Instanz https://kochwas.siegeln.net: - manifest.webmanifest wird korrekt als JSON ausgeliefert - service-worker.js (3.4 KB) ist verfügbar - iOS Apple-Meta-Tags + Android theme-color sind im HTML <head> PWA selbst funktioniert also bereits; der Toast war das fehlende Teil für transparente User-seitige Updates.
This commit is contained in:
110
src/lib/components/UpdateToast.svelte
Normal file
110
src/lib/components/UpdateToast.svelte
Normal file
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { RefreshCw, X } from 'lucide-svelte';
|
||||
import { pwaStore } from '$lib/client/pwa.svelte';
|
||||
</script>
|
||||
|
||||
{#if pwaStore.updateAvailable}
|
||||
<div class="toast" role="status" aria-live="polite">
|
||||
<span class="msg">Neue Kochwas-Version verfügbar</span>
|
||||
<button class="reload" onclick={() => pwaStore.reload()}>
|
||||
<RefreshCw size={16} strokeWidth={2.2} />
|
||||
<span>Neu laden</span>
|
||||
</button>
|
||||
<button class="dismiss" aria-label="Später" onclick={() => pwaStore.dismiss()}>
|
||||
<X size={16} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 0.85rem 0.6rem 1.1rem;
|
||||
background: #1a1a1a;
|
||||
color: white;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
z-index: 500;
|
||||
max-width: calc(100% - 2rem);
|
||||
animation: slide-up 0.3s ease-out;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
transform: translate(-50%, 130%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translate(-50%, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.msg {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.reload {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.4rem 0.85rem;
|
||||
background: #2b6a3d;
|
||||
color: white;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
font-size: 0.88rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.reload:hover {
|
||||
background: #235532;
|
||||
}
|
||||
.dismiss {
|
||||
background: transparent;
|
||||
color: #aaa;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dismiss:hover {
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
@media (max-width: 420px) {
|
||||
.toast {
|
||||
left: 0.5rem;
|
||||
right: 0.5rem;
|
||||
transform: none;
|
||||
max-width: none;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.msg {
|
||||
flex: 1;
|
||||
white-space: normal;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
transform: translateY(130%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user