Remove fleet lobby follow notifications

This commit is contained in:
2026-09-21 19:33:57 -04:00
parent e448457d58
commit f875260b7e
5 changed files with 10 additions and 123 deletions
-18
View File
@@ -87,21 +87,3 @@ export function matchesFleet(server: FleetServer, query: string, filter: FleetFi
...(server.roster ?? []).map(player => player.name)].join(' ').toLocaleLowerCase();
return query.trim().toLocaleLowerCase().split(/\s+/).every(term => text.includes(term));
}
export interface LobbyWatch {
id: string;
server: string;
kind: 'empty' | 'online' | 'operation';
operationKey?: string;
href: string;
}
export function watchSatisfied(watch: LobbyWatch, server: FleetServer | undefined,
operations: readonly FleetOperation[], now: number): boolean {
if (watch.kind === 'operation') {
// Disappearance, failure and replacement by another operation are not completion.
return operations.some(op => op.key === watch.operationKey && op.state === 'done');
}
if (!server || !freshServer(server, now)) return false;
return watch.kind === 'online' || server.last_players === 0;
}
+4 -47
View File
@@ -1,9 +1,7 @@
import { derived, get, writable } from 'svelte/store';
import { derived, writable } from 'svelte/store';
import { browser } from '$app/environment';
import { statusByServer, statusReceivedAt, serverTimeNow, syncServerTime, matchEventsByServer } from './servers.js';
import { watchSatisfied, type FleetServer, type FleetOperation, type LobbyWatch } from '$lib/fleet-overview.js';
import { serverTitle } from '../server-labels.js';
import { toast } from '../toast.js';
import { statusByServer, statusReceivedAt, syncServerTime, matchEventsByServer } from './servers.js';
import { type FleetServer, type FleetOperation } from '$lib/fleet-overview.js';
export const fleet = writable<{ servers: FleetServer[]; operations: FleetOperation[]; receivedAt: number }>({
servers: [], operations: [], receivedAt: 0
@@ -11,7 +9,6 @@ export const fleet = writable<{ servers: FleetServer[]; operations: FleetOperati
export const fleetError = writable('');
export const fleetRefreshing = writable(false);
export const fleetIntegrations = writable<{ rcon: boolean; komodo: boolean; rconTimeoutMs: number; defaultRconHost: string } | null>(null);
export const lobbyWatches = writable<LobbyWatch[]>([]);
export const fleetServers = derived([fleet, statusByServer, statusReceivedAt, matchEventsByServer], ([snapshot, live, received, matches]) =>
snapshot.servers.map(server => {
@@ -62,58 +59,18 @@ export function refreshFleet(): Promise<void> {
return inFlight;
}
export function followLobby(server: string, kind: LobbyWatch['kind'], operation?: FleetOperation): void {
const id = `${server}:${kind}:${operation?.key ?? ''}`;
lobbyWatches.update(watches => watches.some(watch => watch.id === id) ? watches : [...watches, {
id, server, kind, operationKey: operation?.key,
href: operation?.href ?? `/servers/${encodeURIComponent(server)}`
}]);
toast('Watching this lobby while the app is open.');
}
export function cancelWatch(id: string): void {
lobbyWatches.update(watches => watches.filter(watch => watch.id !== id));
}
let stop: (() => void) | null = null;
export function startFleetMonitor(): () => void {
if (!browser || stop) return () => {};
try {
const saved: unknown = JSON.parse(sessionStorage.getItem('electric-cherry:lobby-watches') ?? '[]');
if (Array.isArray(saved)) lobbyWatches.set(saved.filter((watch): watch is LobbyWatch =>
watch && typeof watch.id === 'string' && typeof watch.server === 'string'
&& ['empty', 'online', 'operation'].includes(watch.kind)
&& typeof watch.href === 'string' && /^\/(servers|campaigns|deployments)\//.test(watch.href)
&& (watch.kind !== 'operation' || typeof watch.operationKey === 'string')));
} catch { /* Watches still work with session storage disabled. */ }
const stopSaving = lobbyWatches.subscribe(watches => {
try { sessionStorage.setItem('electric-cherry:lobby-watches', JSON.stringify(watches)); } catch { /* Optional storage. */ }
});
void refreshFleet();
const interval = setInterval(() => {
if (document.visibilityState !== 'hidden' || get(lobbyWatches).length) void refreshFleet();
if (document.visibilityState !== 'hidden') void refreshFleet();
}, 10_000);
const onFocus = () => { if (document.visibilityState === 'visible') void refreshFleet(); };
document.addEventListener('visibilitychange', onFocus);
const check = () => {
// Never complete a watch using an operation snapshot from a failed refresh.
if (get(fleetError)) return;
const snapshot = get(fleet);
if (!snapshot.receivedAt || Date.now() - snapshot.receivedAt > 30_000) return;
const servers = get(fleetServers);
for (const watch of get(lobbyWatches)) {
if (!watchSatisfied(watch, servers.find(server => server.name === watch.server), snapshot.operations, serverTimeNow())) continue;
const message = `${serverTitle(watch.server)} ${watch.kind === 'empty' ? 'is empty' : watch.kind === 'online' ? 'is back online' : 'has finished its operation'}.`;
cancelWatch(watch.id);
toast(message, { variant: 'success', ttl: 0, href: watch.href });
}
};
const unsubscribe = fleetServers.subscribe(check);
stop = () => {
clearInterval(interval);
document.removeEventListener('visibilitychange', onFocus);
unsubscribe();
stopSaving();
stop = null;
};
return stop;