mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(epg): estimate the ribbon window once per channel or mount
The live estimate followed the centred day and the 30 s now tick while the ribbon was not yet scrolled: a small scroll across midnight re-centred the window on the next day's noon and left the visible range empty, and the host width was re-read (a forced layout) on every tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
97d9847aa5
commit
ca72d75e86
2 files changed
+33
-7
No files matched your search
@@ -331,6 +331,29 @@ describe('EpgTimelineComponent ribbon windowing', () => {
|
||||
expect(renderedTitles()).not.toContain('Slot 0');
|
||||
});
|
||||
|
||||
it('keeps the window where the ribbon is when a small scroll commits another day', () => {
|
||||
// A scroll under the re-window step can still cross midnight; the
|
||||
// committed day must not re-centre the window on its noon.
|
||||
render(mockSchedule());
|
||||
const tomorrow = new Date(Date.now() + 24 * 60 * TIMELINE_MINUTE_MS);
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
component.viewDayKey.set(
|
||||
`${tomorrow.getFullYear()}-${pad(tomorrow.getMonth() + 1)}-${pad(tomorrow.getDate())}`
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(renderedTitles()).toContain('Slot 96');
|
||||
});
|
||||
|
||||
it('does not follow the now tick away from an unscrolled ribbon', () => {
|
||||
render(mockSchedule());
|
||||
|
||||
component.nowMs.set(Date.now() + 12 * 60 * TIMELINE_MINUTE_MS);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(renderedTitles()).toContain('Slot 96');
|
||||
});
|
||||
|
||||
it('starts over around now when another channel loads', () => {
|
||||
render(mockSchedule());
|
||||
scrollRibbonTo(blockLeftPx('Slot 230'));
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
inject,
|
||||
linkedSignal,
|
||||
Signal,
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import { EpgProgram } from '@iptvnator/shared/interfaces';
|
||||
import { getTodayEpgDateKey, parseEpgDateKey } from '../epg-date';
|
||||
@@ -154,13 +155,15 @@ export class TimelineWindowController {
|
||||
left.ribbon === right.ribbon && left.key === right.key,
|
||||
}
|
||||
);
|
||||
private readonly measured = linkedSignal<unknown, TimelineViewport | null>({
|
||||
/**
|
||||
* Estimated once per channel or ribbon mount, then moved only by the
|
||||
* scroll and resize measurements: a live estimate would follow the 30 s
|
||||
* now tick and the centred day away from what the ribbon shows.
|
||||
*/
|
||||
private readonly viewport = linkedSignal<unknown, TimelineViewport>({
|
||||
source: this.identity,
|
||||
computation: () => null,
|
||||
computation: () => untracked(() => this.initialViewport()),
|
||||
});
|
||||
private readonly viewport = computed(
|
||||
() => this.measured() ?? this.initialViewport()
|
||||
);
|
||||
private readonly range = computed(() =>
|
||||
timelineWindowRange(this.viewport(), this.ctx.axis(), this.ctx.scale())
|
||||
);
|
||||
@@ -221,7 +224,7 @@ export class TimelineWindowController {
|
||||
const widthPx = this.scroller()?.clientWidth ?? 0;
|
||||
const current = this.viewport();
|
||||
if (widthPx > current.widthPx) {
|
||||
this.measured.set({ centreMs: current.centreMs, widthPx });
|
||||
this.viewport.set({ centreMs: current.centreMs, widthPx });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +242,7 @@ export class TimelineWindowController {
|
||||
scale
|
||||
);
|
||||
if (viewportNeedsWindow(this.viewport(), next, scale)) {
|
||||
this.measured.set(next);
|
||||
this.viewport.set(next);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user