autosync toggle, configurable sync interval, 30-day session
- header Sync button now has a paired autosync toggle to its right — reads as one button with no inner seam (outer border only). Dot inside pulses once per actual sync (bursts on every backgroundSync / triggerSync / collab pollOnce via a new sync.pulse hook) so the user can see polls happen. Dot + data-autosync attribute persist the enabled state in localStorage; paused state stops both the CalDAV/relay background timer and the collab poll via a broadcast autosync.changed hook. - hover polish: Sync regains a full accent border (the missing inner edge re-appears only on its hover); the toggle fills with solid accent as hover treatment so the pair never shows a half-outline. - sync interval moved out of hardcode into a settings field under Settings → Relay: number input, 5s..3600s, default 15s (was 45s on caldav / 30s on collab). Changes broadcast via sync.interval.changed and restart both timers without reload. - session cookie lifetime raised to 30 days with matching gc_maxlifetime and SameSite=Lax (was 0 / Strict — some users got kicked to login on every reload when gc cleaned up the /tmp/sess file or a cross-link return blocked the cookie). - search button's kbd element was pushing the button ~3px taller than Sync; leading-none + tighter padding puts them on the same height. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6cd00f9660
commit
b7f450e41f
6 files changed
+222
-12
No files matched your search
@@ -173,6 +173,7 @@ $('caldav-save')?.addEventListener('click', async () => {
|
||||
$('caldav-sync-now')?.addEventListener('click', () => triggerSync());
|
||||
|
||||
export async function triggerSync() {
|
||||
pulseAutosyncDot();
|
||||
const btn = $('cal-sync');
|
||||
if (btn) btn.style.opacity = '0.5';
|
||||
const headerIcon = $('header-sync-icon');
|
||||
@@ -285,6 +286,19 @@ export async function loadRelaySettings() {
|
||||
keyInput.value = s.key || '';
|
||||
keyInput.placeholder = 'shared secret';
|
||||
}
|
||||
// Sync-interval number input (shared setting, persisted in localStorage).
|
||||
// `change` commits on blur or Enter; `input` would save on every keystroke
|
||||
// and restart the timer on each digit typed — noisy and pointless.
|
||||
const intervalInp = $('setting-sync-interval');
|
||||
if (intervalInp) {
|
||||
intervalInp.value = String(getSyncIntervalSec());
|
||||
intervalInp.onchange = () => {
|
||||
setSyncIntervalSec(intervalInp.value);
|
||||
// Reflect the clamped value back so users see when we refused a 2
|
||||
// or 5000.
|
||||
intervalInp.value = String(getSyncIntervalSec());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$('cal-caldav-settings')?.addEventListener('click', openCaldavSettings);
|
||||
@@ -350,6 +364,7 @@ function drainSyncEvents(relayRes) {
|
||||
async function backgroundSync() {
|
||||
if (state.bgSyncRunning) return;
|
||||
state.bgSyncRunning = true;
|
||||
pulseAutosyncDot();
|
||||
try {
|
||||
const headers = { 'X-CSRF-Token': getCsrf() };
|
||||
const [res, relayRes] = await Promise.all([
|
||||
@@ -369,13 +384,89 @@ async function backgroundSync() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Configurable sync interval ────────────────────────────────
|
||||
// One setting drives both the CalDAV/relay background sync and the
|
||||
// collab-buckets plugin poll. Stored in localStorage as seconds.
|
||||
// Changed via the Relay settings panel → emits `sync.interval.changed`
|
||||
// so every poller can restart with the new cadence without reload.
|
||||
const SYNC_INTERVAL_KEY = 'hyprcrd-sync-interval-sec';
|
||||
const SYNC_INTERVAL_DEFAULT = 15; // snappy default — tune up on slow machines or paid relays
|
||||
const SYNC_INTERVAL_MIN = 5;
|
||||
const SYNC_INTERVAL_MAX = 3600;
|
||||
export function getSyncIntervalSec() {
|
||||
const v = parseInt(localStorage.getItem(SYNC_INTERVAL_KEY));
|
||||
return Number.isFinite(v) && v >= SYNC_INTERVAL_MIN ? v : SYNC_INTERVAL_DEFAULT;
|
||||
}
|
||||
export function setSyncIntervalSec(sec) {
|
||||
const n = parseInt(sec);
|
||||
const clamped = Math.max(SYNC_INTERVAL_MIN, Math.min(SYNC_INTERVAL_MAX, Number.isFinite(n) ? n : SYNC_INTERVAL_DEFAULT));
|
||||
localStorage.setItem(SYNC_INTERVAL_KEY, String(clamped));
|
||||
window.hyprcrd?.hooks?.emit?.('sync.interval.changed', clamped);
|
||||
}
|
||||
|
||||
// ── Autosync on/off toggle ────────────────────────────────────
|
||||
// User can pause background polling from the header (play/pause icon
|
||||
// next to the Sync button). Setting persists in localStorage.
|
||||
const AUTOSYNC_KEY = 'hyprcrd-autosync-enabled';
|
||||
export function isAutoSyncEnabled() {
|
||||
return localStorage.getItem(AUTOSYNC_KEY) !== '0';
|
||||
}
|
||||
export function setAutoSyncEnabled(on) {
|
||||
localStorage.setItem(AUTOSYNC_KEY, on ? '1' : '0');
|
||||
if (on) startBackgroundSync();
|
||||
else stopBackgroundSync();
|
||||
updateAutosyncButton();
|
||||
// Broadcast so collab-buckets (and any future pollers) pause alongside.
|
||||
window.hyprcrd?.hooks?.emit?.('autosync.changed', on);
|
||||
}
|
||||
|
||||
// Start background sync after app is ready
|
||||
let bgSyncInterval = null;
|
||||
export function startBackgroundSync() {
|
||||
if (bgSyncInterval) return;
|
||||
if (!isAutoSyncEnabled()) { updateAutosyncButton(); return; }
|
||||
backgroundSync();
|
||||
bgSyncInterval = setInterval(backgroundSync, 45000); // every 45 seconds
|
||||
bgSyncInterval = setInterval(backgroundSync, getSyncIntervalSec() * 1000);
|
||||
updateAutosyncButton();
|
||||
}
|
||||
export function stopBackgroundSync() {
|
||||
if (bgSyncInterval) { clearInterval(bgSyncInterval); bgSyncInterval = null; }
|
||||
updateAutosyncButton();
|
||||
}
|
||||
|
||||
// Restart the background timer when the user changes the interval.
|
||||
window.hyprcrd?.hooks?.on?.('sync.interval.changed', () => {
|
||||
if (bgSyncInterval) {
|
||||
clearInterval(bgSyncInterval);
|
||||
bgSyncInterval = setInterval(backgroundSync, getSyncIntervalSec() * 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Header autosync button ────────────────────────────────────
|
||||
// Accent dot when on, grey when off; a single burst-pulse fires at the
|
||||
// start of every actual sync so the user sees the poll happen.
|
||||
function updateAutosyncButton() {
|
||||
const btn = $('header-autosync');
|
||||
if (!btn) return;
|
||||
const on = isAutoSyncEnabled();
|
||||
btn.dataset.autosync = on ? 'on' : 'off';
|
||||
btn.setAttribute('data-tip', on
|
||||
? `auto-sync: on (every ${getSyncIntervalSec()}s) — click to pause`
|
||||
: 'auto-sync: paused — click to resume');
|
||||
}
|
||||
|
||||
// Pulse the dot once; retriggers cleanly even if called mid-animation by
|
||||
// ripping the class and forcing a reflow before re-adding.
|
||||
export function pulseAutosyncDot() {
|
||||
const dot = $('header-autosync-dot');
|
||||
if (!dot) return;
|
||||
dot.classList.remove('pulsing');
|
||||
void dot.offsetWidth;
|
||||
dot.classList.add('pulsing');
|
||||
}
|
||||
// Also accept pulses from other pollers (collab-buckets, etc.) via hooks.
|
||||
window.hyprcrd?.hooks?.on?.('sync.pulse', pulseAutosyncDot);
|
||||
$('header-autosync')?.addEventListener('click', () => setAutoSyncEnabled(!isAutoSyncEnabled()));
|
||||
// Also refresh tooltip when interval changes
|
||||
window.hyprcrd?.hooks?.on?.('sync.interval.changed', updateAutosyncButton);
|
||||
updateAutosyncButton();
|
||||
Reference in new issue
Block a user