lambda-live-debugger
Version:
Debug Lambda functions locally like it is running in the cloud
122 lines (121 loc) • 6.36 kB
JavaScript
import { useRef } from "../use-ref.js";
import { readlineWidth, breakLines } from "../utils.js";
function usePointerPosition({ active, renderedItems, pageSize, loop, }) {
const state = useRef({
lastPointer: active,
lastActive: undefined,
});
const { lastPointer, lastActive } = state.current;
const middle = Math.floor(pageSize / 2);
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const defaultPointerPosition = renderedItems
.slice(0, active)
.reduce((acc, item) => acc + item.length, 0);
let pointer = defaultPointerPosition;
if (renderedLength > pageSize) {
if (loop) {
/**
* Creates the next position for the pointer considering an infinitely
* looping list of items to be rendered on the page.
*
* The goal is to progressively move the cursor to the middle position as the user move down, and then keep
* the cursor there. When the user move up, maintain the cursor position.
*/
// By default, keep the cursor position as-is.
pointer = lastPointer;
if (
// First render, skip this logic.
lastActive != null &&
// Only move the pointer down when the user moves down.
lastActive < active &&
// Check user didn't move up across page boundary.
active - lastActive < pageSize) {
pointer = Math.min(
// Furthest allowed position for the pointer is the middle of the list
middle, Math.abs(active - lastActive) === 1
? Math.min(
// Move the pointer at most the height of the last active item.
lastPointer + (renderedItems[lastActive]?.length ?? 0),
// If the user moved by one item, move the pointer to the natural position of the active item as
// long as it doesn't move the cursor up.
Math.max(defaultPointerPosition, lastPointer))
: // Otherwise, move the pointer down by the difference between the active and last active item.
lastPointer + active - lastActive);
}
}
else {
/**
* Creates the next position for the pointer considering a finite list of
* items to be rendered on a page.
*
* The goal is to keep the pointer in the middle of the page whenever possible, until
* we reach the bounds of the list (top or bottom). In which case, the cursor moves progressively
* to the bottom or top of the list.
*/
const spaceUnderActive = renderedItems
.slice(active)
.reduce((acc, item) => acc + item.length, 0);
pointer =
spaceUnderActive < pageSize - middle
? // If the active item is near the end of the list, progressively move the cursor towards the end.
pageSize - spaceUnderActive
: // Otherwise, progressively move the pointer to the middle of the list.
Math.min(defaultPointerPosition, middle);
}
}
// Save state for the next render
state.current.lastPointer = pointer;
state.current.lastActive = active;
return pointer;
}
export function usePagination({ items, active, renderItem, pageSize, loop = true, }) {
const width = readlineWidth();
const bound = (num) => ((num % items.length) + items.length) % items.length;
const renderedItems = items.map((item, index) => {
if (item == null)
return [];
return breakLines(renderItem({ item, index, isActive: index === active }), width).split('\n');
});
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const renderItemAtIndex = (index) => renderedItems[index] ?? [];
const pointer = usePointerPosition({ active, renderedItems, pageSize, loop });
// Render the active item to decide the position.
// If the active item fits under the pointer, we render it there.
// Otherwise, we need to render it to fit at the bottom of the page; moving the pointer up.
const activeItem = renderItemAtIndex(active).slice(0, pageSize);
const activeItemPosition = pointer + activeItem.length <= pageSize ? pointer : pageSize - activeItem.length;
// Create an array of lines for the page, and add the lines of the active item into the page
const pageBuffer = Array.from({ length: pageSize });
pageBuffer.splice(activeItemPosition, activeItem.length, ...activeItem);
// Store to prevent rendering the same item twice
const itemVisited = new Set([active]);
// Fill the page under the active item
let bufferPointer = activeItemPosition + activeItem.length;
let itemPointer = bound(active + 1);
while (bufferPointer < pageSize &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer > active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(0, pageSize - bufferPointer);
pageBuffer.splice(bufferPointer, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer += linesToAdd.length;
itemPointer = bound(itemPointer + 1);
}
// Fill the page over the active item
bufferPointer = activeItemPosition - 1;
itemPointer = bound(active - 1);
while (bufferPointer >= 0 &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer < active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(Math.max(0, lines.length - bufferPointer - 1));
pageBuffer.splice(bufferPointer - linesToAdd.length + 1, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer -= linesToAdd.length;
itemPointer = bound(itemPointer - 1);
}
return pageBuffer.filter((line) => typeof line === 'string').join('\n');
}