feat(offline): hoist sync UI to homepage, slow auto-sync to weekly
Move OfflineSyncIndicator (logo pip) and OfflineSyncBanner from the [recipeLang] layout/page to (main)/+layout.svelte and (main)/+page.svelte. Sync is an app-wide concern, not recipe-specific, and surfacing it on the homepage gives the entry point users actually see when they install the PWA. Indicator pulls language from languageStore since (main) doesn't have data.lang from a recipe-scoped load. Drop the now-unused .banner-wrap CSS and OfflineSyncIndicator/Banner imports from the recipe routes. Auto-sync cadence: - AUTO_SYNC_INTERVAL 30 min -> 1 week. Recipes don't change often enough to justify a half-hourly background download (the user explicitly wanted this dialed back). - Internal poll tick 5 min -> 1 hour. Polling 12x an hour for a weekly event is wasted work; hourly is fine and still responsive when the weekly window opens. Bump 1.65.3 -> 1.66.0.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.65.3",
|
||||
"version": "1.66.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { browser } from '$app/environment';
|
||||
import { isOfflineDataAvailable, getLastSync, clearOfflineData } from '$lib/offline/db';
|
||||
import { downloadAllRecipes, type SyncResult, type SyncProgress } from '$lib/offline/sync';
|
||||
|
||||
const AUTO_SYNC_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
||||
const AUTO_SYNC_INTERVAL = 7 * 24 * 60 * 60 * 1000; // 1 week
|
||||
const LAST_SYNC_KEY = 'bocken-last-sync-time';
|
||||
|
||||
type PWAState = {
|
||||
@@ -152,12 +152,13 @@ function createPWAStore() {
|
||||
startAutoSync() {
|
||||
if (autoSyncInterval) return; // Already running
|
||||
|
||||
// Check every 5 minutes if we should sync
|
||||
// Check hourly if we should sync — actual sync only fires once
|
||||
// AUTO_SYNC_INTERVAL has elapsed since the last sync.
|
||||
autoSyncInterval = setInterval(() => {
|
||||
autoSync();
|
||||
}, 5 * 60 * 1000); // Check every 5 minutes
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
console.log('[PWA] Auto-sync enabled (every 30 minutes)');
|
||||
console.log('[PWA] Auto-sync enabled (weekly)');
|
||||
},
|
||||
|
||||
stopAutoSync() {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import Header from '$lib/components/Header.svelte'
|
||||
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
|
||||
import OfflineSyncIndicator from '$lib/components/OfflineSyncIndicator.svelte';
|
||||
import { languageStore } from '$lib/stores/language.svelte';
|
||||
let { data, children } = $props();
|
||||
|
||||
let user = $derived(data.session?.user);
|
||||
@@ -16,9 +18,25 @@ let user = $derived(data.session?.user);
|
||||
<LanguageSelector />
|
||||
{/snippet}
|
||||
|
||||
{#snippet logo_overlay()}
|
||||
<div class="logo-pip">
|
||||
<OfflineSyncIndicator lang={languageStore.value} />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet right_side()}
|
||||
<UserHeader {user}></UserHeader>
|
||||
{/snippet}
|
||||
|
||||
{@render children()}
|
||||
</Header>
|
||||
|
||||
<style>
|
||||
:global(.logo-pip) {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -7px;
|
||||
z-index: 2;
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import LinksGrid from "$lib/components/LinksGrid.svelte";
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import OfflineSyncBanner from '$lib/components/OfflineSyncBanner.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
let { data } = $props();
|
||||
|
||||
@@ -143,6 +144,8 @@ section h2{
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<OfflineSyncBanner {lang} />
|
||||
|
||||
<section>
|
||||
|
||||
<h2>{labels.pages}</h2>
|
||||
|
||||
@@ -45,7 +45,6 @@ onNavigate((navigation) => {
|
||||
});
|
||||
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
|
||||
import OfflineSyncIndicator from '$lib/components/OfflineSyncIndicator.svelte';
|
||||
import BookOpen from '@lucide/svelte/icons/book-open';
|
||||
import Heart from '@lucide/svelte/icons/heart';
|
||||
import Leaf from '@lucide/svelte/icons/leaf';
|
||||
@@ -134,12 +133,6 @@ const recipeCanonicalPath = $derived(recipeAltPath(page.url.pathname, /** @type
|
||||
<LanguageSelector lang={data.lang} />
|
||||
{/snippet}
|
||||
|
||||
{#snippet logo_overlay()}
|
||||
<div class="logo-pip">
|
||||
<OfflineSyncIndicator lang={data.lang} />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet right_side()}
|
||||
<UserHeader {user} recipeLang={data.recipeLang} lang={data.lang}></UserHeader>
|
||||
{/snippet}
|
||||
@@ -147,12 +140,3 @@ const recipeCanonicalPath = $derived(recipeAltPath(page.url.pathname, /** @type
|
||||
{@render children()}
|
||||
</Header>
|
||||
|
||||
<style>
|
||||
:global(.logo-pip) {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -7px;
|
||||
z-index: 2;
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import AddButton from '$lib/components/AddButton.svelte';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||
import OfflineSyncBanner from '$lib/components/OfflineSyncBanner.svelte';
|
||||
import Search from '$lib/components/recipes/Search.svelte';
|
||||
import { getCategories } from '$lib/js/categories';
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
@@ -315,19 +314,6 @@
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* ─── Offline sync banner — between search and recipe grid ─── */
|
||||
.banner-wrap {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2em;
|
||||
position: relative;
|
||||
z-index: 9;
|
||||
}
|
||||
.banner-wrap.fallback {
|
||||
padding: 0 2em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sentinel {
|
||||
height: 1px;
|
||||
}
|
||||
@@ -442,9 +428,6 @@
|
||||
<div class="hero-search-wrap">
|
||||
<Search lang={data.lang} recipes={data.all_brief} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
||||
</div>
|
||||
<div class="banner-wrap">
|
||||
<OfflineSyncBanner lang={data.lang} />
|
||||
</div>
|
||||
<div class="recipe-grid">
|
||||
{#each visibleRecipes as recipe, i (recipe._id)}
|
||||
<CompactCard
|
||||
@@ -472,9 +455,6 @@
|
||||
<h1>{labels.title}</h1>
|
||||
<p class="subheading">{labels.subheading}</p>
|
||||
</div>
|
||||
<div class="banner-wrap fallback">
|
||||
<OfflineSyncBanner lang={data.lang} />
|
||||
</div>
|
||||
<Search lang={data.lang} recipes={data.all_brief} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
||||
|
||||
<div class="recipe-grid">
|
||||
|
||||
Reference in New Issue
Block a user