feat(recipe): Wake-Lock-Schalter + Profil-Chip nur Lucide + Save-Text
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m16s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m16s
1) ProfileSwitcher-Chip: Profil-Emoji (avatar_emoji) ist jetzt aus dem
Header-Badge raus — immer CircleUser-Icon vor dem Namen. Im Profil-
Auswahl-Modal bleiben die individuellen Emojis erhalten, damit User
ihr Profil dort weiterhin erkennen. Unused .emoji CSS entfernt.
2) Preview-Button: "In meine Sammlung speichern" → "Rezept in Kochwas
speichern". Klarer, was die App heißt.
3) Wake-Lock-Schalter:
- Erklärung: navigator.wakeLock.request('screen') hindert Android/iOS
daran, das Display zu dimmen/zu sperren, solange der Tab sichtbar
ist. Beim Kochen sehr nützlich — Hände sind klebrig.
- Neuer Toggle-Button im Rezept-Detail, zweite Aktion-Zeile zwischen
"Heute gekocht" und "Löschen": Lightbulb (an, gelb-gehighlighted)
oder LightbulbOff (aus).
- Preference wird in localStorage persistiert (kochwas.wakeLock),
Default an. Gilt für alle Rezepte.
- visibilitychange-Handler re-requestet den Sentinel, wenn User den
Tab wieder nach vorne holt und die Pref an ist.
- release-Event räumt wakeLock-Variable sauber auf.
This commit is contained in:
@@ -24,11 +24,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="chip" onclick={() => (showModal = true)} aria-label="Profil wechseln">
|
<button class="chip" onclick={() => (showModal = true)} aria-label="Profil wechseln">
|
||||||
|
<span class="icon"><CircleUser size={20} strokeWidth={1.75} /></span>
|
||||||
{#if profileStore.active}
|
{#if profileStore.active}
|
||||||
<span class="emoji">{profileStore.active.avatar_emoji ?? '🙂'}</span>
|
|
||||||
<span class="name">{profileStore.active.name}</span>
|
<span class="name">{profileStore.active.name}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="icon"><CircleUser size={20} strokeWidth={1.75} /></span>
|
|
||||||
<span class="name">Profil wählen</span>
|
<span class="name">Profil wählen</span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
@@ -105,9 +104,6 @@
|
|||||||
.chip:hover {
|
.chip:hover {
|
||||||
background: #f4f8f5;
|
background: #f4f8f5;
|
||||||
}
|
}
|
||||||
.emoji {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
.icon {
|
.icon {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
<span>Speichern…</span>
|
<span>Speichern…</span>
|
||||||
{:else}
|
{:else}
|
||||||
<BookmarkPlus size={18} strokeWidth={2} />
|
<BookmarkPlus size={18} strokeWidth={2} />
|
||||||
<span>In meine Sammlung speichern</span>
|
<span>Rezept in Kochwas speichern</span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn ghost" onclick={() => history.back()}>Zurück</button>
|
<button type="button" class="btn ghost" onclick={() => history.back()}>Zurück</button>
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
Trash2,
|
Trash2,
|
||||||
ChefHat,
|
ChefHat,
|
||||||
Check,
|
Check,
|
||||||
X
|
X,
|
||||||
|
Lightbulb,
|
||||||
|
LightbulbOff
|
||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
import RecipeView from '$lib/components/RecipeView.svelte';
|
import RecipeView from '$lib/components/RecipeView.svelte';
|
||||||
import StarRating from '$lib/components/StarRating.svelte';
|
import StarRating from '$lib/components/StarRating.svelte';
|
||||||
@@ -225,29 +227,62 @@
|
|||||||
void wishlistStore.refresh();
|
void wishlistStore.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wake-Lock
|
// Wake-Lock — Bildschirm beim Kochen nicht dimmen lassen.
|
||||||
|
// Browser-API navigator.wakeLock.request('screen') verhindert auto-lock
|
||||||
|
// und -dimmen, solange der Tab sichtbar ist. Sobald der Tab in den
|
||||||
|
// Hintergrund geht, verliert der Sentinel seine Wirkung von selbst; wir
|
||||||
|
// re-requesten bei visibilitychange.
|
||||||
|
let wakeLockEnabled = $state(true);
|
||||||
let wakeLock: WakeLockSentinel | null = null;
|
let wakeLock: WakeLockSentinel | null = null;
|
||||||
async function requestWakeLock() {
|
|
||||||
|
async function acquireWakeLock() {
|
||||||
|
if (wakeLock || !wakeLockEnabled) return;
|
||||||
try {
|
try {
|
||||||
if ('wakeLock' in navigator) {
|
if ('wakeLock' in navigator) {
|
||||||
wakeLock = await navigator.wakeLock.request('screen');
|
wakeLock = await navigator.wakeLock.request('screen');
|
||||||
|
wakeLock.addEventListener('release', () => {
|
||||||
|
wakeLock = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// silently ignore
|
// User hat es gecancelt oder Browser unterstützt es nicht — ignorieren
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function releaseWakeLock() {
|
||||||
|
if (!wakeLock) return;
|
||||||
|
try {
|
||||||
|
await wakeLock.release();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
wakeLock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWakeLock() {
|
||||||
|
wakeLockEnabled = !wakeLockEnabled;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('kochwas.wakeLock', wakeLockEnabled ? '1' : '0');
|
||||||
|
}
|
||||||
|
if (wakeLockEnabled) void acquireWakeLock();
|
||||||
|
else void releaseWakeLock();
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
void requestWakeLock();
|
const stored = localStorage.getItem('kochwas.wakeLock');
|
||||||
|
if (stored !== null) wakeLockEnabled = stored === '1';
|
||||||
|
if (wakeLockEnabled) void acquireWakeLock();
|
||||||
const onVisibility = () => {
|
const onVisibility = () => {
|
||||||
if (document.visibilityState === 'visible' && !wakeLock) void requestWakeLock();
|
if (document.visibilityState === 'visible' && wakeLockEnabled && !wakeLock) {
|
||||||
|
void acquireWakeLock();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('visibilitychange', onVisibility);
|
document.addEventListener('visibilitychange', onVisibility);
|
||||||
return () => document.removeEventListener('visibilitychange', onVisibility);
|
return () => document.removeEventListener('visibilitychange', onVisibility);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (wakeLock) void wakeLock.release();
|
void releaseWakeLock();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -314,6 +349,22 @@
|
|||||||
<span class="count">({cookingLog.length})</span>
|
<span class="count">({cookingLog.length})</span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
class:screen-on={wakeLockEnabled}
|
||||||
|
onclick={toggleWakeLock}
|
||||||
|
aria-label={wakeLockEnabled
|
||||||
|
? 'Bildschirm bleibt an — zum Deaktivieren klicken'
|
||||||
|
: 'Bildschirm darf dimmen — zum Aktivieren klicken'}
|
||||||
|
>
|
||||||
|
{#if wakeLockEnabled}
|
||||||
|
<Lightbulb size={18} strokeWidth={2} />
|
||||||
|
<span>Bildschirm an</span>
|
||||||
|
{:else}
|
||||||
|
<LightbulbOff size={18} strokeWidth={2} />
|
||||||
|
<span>Bildschirm aus</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
<button class="btn danger" onclick={deleteRecipe}>
|
<button class="btn danger" onclick={deleteRecipe}>
|
||||||
<Trash2 size={18} strokeWidth={2} />
|
<Trash2 size={18} strokeWidth={2} />
|
||||||
<span>Löschen</span>
|
<span>Löschen</span>
|
||||||
@@ -476,6 +527,11 @@
|
|||||||
border-color: #b7d6c2;
|
border-color: #b7d6c2;
|
||||||
background: #eaf4ed;
|
background: #eaf4ed;
|
||||||
}
|
}
|
||||||
|
.btn.screen-on {
|
||||||
|
color: #b07e00;
|
||||||
|
border-color: #e6d48a;
|
||||||
|
background: #fff6d7;
|
||||||
|
}
|
||||||
.btn.primary {
|
.btn.primary {
|
||||||
background: #2b6a3d;
|
background: #2b6a3d;
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
Reference in New Issue
Block a user