mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
- Extract `prioritizeEntries` logic to `js/gridHealthLogic.js` for testability - Add comprehensive unit tests in `tests/grid-health.test.mjs` - Improve `getIntersectionRect` to robustly handle invalid rects - Ensure 0x0 rects are filtered out explicitly Co-authored-by: PR0M3TH3AN <[email protected]>
135 lines
3.5 KiB
JavaScript
135 lines
3.5 KiB
JavaScript
export const PRIORITY_BASELINE = 1_000_000;
|
|
|
|
export function getViewportCenter() {
|
|
if (typeof window === "undefined") {
|
|
return null;
|
|
}
|
|
const width = Number(window.innerWidth) || 0;
|
|
const height = Number(window.innerHeight) || 0;
|
|
if (width <= 0 && height <= 0) {
|
|
return null;
|
|
}
|
|
return {
|
|
x: width > 0 ? width / 2 : 0,
|
|
y: height > 0 ? height / 2 : 0,
|
|
};
|
|
}
|
|
|
|
export function getIntersectionRect(entry) {
|
|
if (!entry) {
|
|
return null;
|
|
}
|
|
const rect = entry.intersectionRect;
|
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
return rect;
|
|
}
|
|
const fallback = entry.boundingClientRect;
|
|
if (fallback && fallback.width > 0 && fallback.height > 0) {
|
|
return fallback;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function prioritizeEntries(entries, viewportCenter) {
|
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
return [];
|
|
}
|
|
const filtered = entries
|
|
.filter((entry) => entry.isIntersecting && entry.target instanceof HTMLElement)
|
|
.map((entry) => {
|
|
const rect = getIntersectionRect(entry);
|
|
if (!rect) {
|
|
return null;
|
|
}
|
|
const ratio =
|
|
typeof entry.intersectionRatio === "number" ? entry.intersectionRatio : 0;
|
|
const centerY = rect.top + rect.height / 2;
|
|
const verticalDistance = viewportCenter
|
|
? Math.abs(centerY - viewportCenter.y)
|
|
: Number.POSITIVE_INFINITY;
|
|
return { entry, ratio, centerY, verticalDistance };
|
|
})
|
|
.filter(Boolean);
|
|
|
|
if (filtered.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
if (!viewportCenter) {
|
|
return filtered
|
|
.sort((a, b) => {
|
|
if (b.ratio !== a.ratio) {
|
|
return b.ratio - a.ratio;
|
|
}
|
|
return a.centerY - b.centerY;
|
|
})
|
|
.map((item, index) => ({
|
|
entry: item.entry,
|
|
priority: PRIORITY_BASELINE - index,
|
|
}));
|
|
}
|
|
|
|
const ordered = filtered
|
|
.slice()
|
|
.sort((a, b) => a.centerY - b.centerY);
|
|
|
|
let centerIndex = 0;
|
|
let minDistance = Number.POSITIVE_INFINITY;
|
|
for (let i = 0; i < ordered.length; i += 1) {
|
|
const candidate = ordered[i];
|
|
if (candidate.verticalDistance < minDistance) {
|
|
minDistance = candidate.verticalDistance;
|
|
centerIndex = i;
|
|
}
|
|
}
|
|
|
|
const prioritized = [];
|
|
const pushCandidate = (candidate) => {
|
|
if (!candidate) {
|
|
return;
|
|
}
|
|
prioritized.push(candidate);
|
|
};
|
|
|
|
pushCandidate(ordered[centerIndex]);
|
|
|
|
let left = centerIndex - 1;
|
|
let right = centerIndex + 1;
|
|
while (left >= 0 || right < ordered.length) {
|
|
const leftCandidate = left >= 0 ? ordered[left] : null;
|
|
const rightCandidate = right < ordered.length ? ordered[right] : null;
|
|
|
|
if (leftCandidate && rightCandidate) {
|
|
const leftDistance = leftCandidate.verticalDistance;
|
|
const rightDistance = rightCandidate.verticalDistance;
|
|
const distanceDelta = Math.abs(leftDistance - rightDistance);
|
|
if (distanceDelta <= 0.5) {
|
|
if (rightCandidate.ratio > leftCandidate.ratio) {
|
|
pushCandidate(rightCandidate);
|
|
right += 1;
|
|
} else {
|
|
pushCandidate(leftCandidate);
|
|
left -= 1;
|
|
}
|
|
} else if (leftDistance < rightDistance) {
|
|
pushCandidate(leftCandidate);
|
|
left -= 1;
|
|
} else {
|
|
pushCandidate(rightCandidate);
|
|
right += 1;
|
|
}
|
|
} else if (rightCandidate) {
|
|
pushCandidate(rightCandidate);
|
|
right += 1;
|
|
} else if (leftCandidate) {
|
|
pushCandidate(leftCandidate);
|
|
left -= 1;
|
|
}
|
|
}
|
|
|
|
return prioritized.map((candidate, index) => ({
|
|
entry: candidate.entry,
|
|
priority: PRIORITY_BASELINE - index,
|
|
}));
|
|
}
|