mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
fix(dashboard): stop the expiry clock while the sources rail is disabled
The expiry facts live in a root service and outlive the rail, so the clock kept arming its boundary/hourly timeout with no badge on screen. Gate it on dashboardRails().recentSources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b15c758788
commit
97ad694549
4 files changed
+26
-10
No files matched your search
@@ -407,7 +407,7 @@ full-playlist read. The chip is not a separate account-refresh request.
|
||||
The badge only changes at day boundaries, so the rails do not poll the clock:
|
||||
`createSourceExpiryClock` arms one timer for the earliest boundary among the
|
||||
known facts (`nextSourceExpiryChangeMs`), capped at an hour because timers do
|
||||
not follow system sleep. It arms no timer while the page is hidden and
|
||||
re-reads the clock when the page becomes visible. It schedules from the real
|
||||
not follow system sleep. It arms no timer while the page is hidden or the
|
||||
sources rail is disabled, and re-reads the clock when the page becomes visible. It schedules from the real
|
||||
time, so facts that arrive long after the last tick are not scheduled late.
|
||||
Facts whose badge can no longer change arm no timer.
|
||||
+14
-1
@@ -14,6 +14,7 @@ describe('createSourceExpiryClock', () => {
|
||||
typeof signal<ReadonlyMap<string, SourceExpiryFacts>>
|
||||
>;
|
||||
let clock: Signal<number>;
|
||||
let active: ReturnType<typeof signal<boolean>>;
|
||||
|
||||
const expiringIn = (ms: number): SourceExpiryFacts => ({
|
||||
expiresAtSeconds: (nowMs + ms) / 1000,
|
||||
@@ -31,8 +32,9 @@ describe('createSourceExpiryClock', () => {
|
||||
get: () => hidden,
|
||||
});
|
||||
facts = signal<ReadonlyMap<string, SourceExpiryFacts>>(new Map());
|
||||
active = signal(true);
|
||||
clock = TestBed.runInInjectionContext(() =>
|
||||
createSourceExpiryClock(facts)
|
||||
createSourceExpiryClock(facts, active)
|
||||
);
|
||||
TestBed.tick();
|
||||
});
|
||||
@@ -113,4 +115,15 @@ describe('createSourceExpiryClock', () => {
|
||||
expect(clock()).toBe(Date.now());
|
||||
expect(jest.getTimerCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('arms no timer while the sources rail is disabled', () => {
|
||||
active.set(false);
|
||||
facts.set(new Map([['xtream', expiringIn(2 * DAY_MS + 20 * 60_000)]]));
|
||||
TestBed.tick();
|
||||
expect(jest.getTimerCount()).toBe(0);
|
||||
|
||||
active.set(true);
|
||||
TestBed.tick();
|
||||
expect(jest.getTimerCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -16,12 +16,14 @@ const BOUNDARY_SLACK_MS = 1_000;
|
||||
* one timer for the earliest badge boundary among the known facts (none at
|
||||
* all when no badge can change), capped at {@link SOURCE_EXPIRY_MAX_WAIT_MS}
|
||||
* because timers do not follow system sleep or clock changes. No timer is
|
||||
* armed while the document is hidden; becoming visible re-reads the clock
|
||||
* at once. Must be created in
|
||||
* armed while the document is hidden or `active` is false; becoming visible
|
||||
* re-reads the clock at once. Must be created in
|
||||
* an injection context.
|
||||
*/
|
||||
export function createSourceExpiryClock(
|
||||
facts: Signal<ReadonlyMap<string, SourceExpiryFacts>>
|
||||
facts: Signal<ReadonlyMap<string, SourceExpiryFacts>>,
|
||||
/** False while no badge can render (the sources rail is disabled). */
|
||||
active: Signal<boolean> = signal(true)
|
||||
): Signal<number> {
|
||||
const document = inject(DOCUMENT);
|
||||
const now = signal(Date.now());
|
||||
@@ -31,9 +33,9 @@ export function createSourceExpiryClock(
|
||||
// Read for the dependency; schedule from the real time, because the
|
||||
// facts can change long after the clock last moved.
|
||||
now();
|
||||
// Hidden: no badge is on screen. Becoming visible moves the clock,
|
||||
// which re-runs this effect.
|
||||
if (!visible()) return;
|
||||
// Hidden, or the sources rail is off: no badge is on screen. Becoming
|
||||
// visible moves the clock and re-runs this effect; so does `active`.
|
||||
if (!visible() || !active()) return;
|
||||
const nowMs = Date.now();
|
||||
let next: number | null = null;
|
||||
for (const entry of facts().values()) {
|
||||
|
||||
+2
-1
@@ -269,7 +269,8 @@ export class WorkspaceDashboardRailsComponent {
|
||||
// open needs a reactive clock to cross a day-countdown or expiration
|
||||
// boundary. It moves only at those boundaries, not on a polling tick.
|
||||
private readonly sourceExpiryNow = createSourceExpiryClock(
|
||||
this.sourceExpiry.facts
|
||||
this.sourceExpiry.facts,
|
||||
computed(() => this.dashboardRails().recentSources)
|
||||
);
|
||||
|
||||
readonly sourceCards = computed<DashboardRailCard[]>(() => {
|
||||
|
||||
Reference in new issue
Block a user