Something went wrong. Try again.
your personal website on atproto - mirror blento.app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275import { type LayoutItem, type Layout } from 'react-grid-layout/core';import { collides, moveElement, correctBounds, getFirstCollision, verticalCompactor} from 'react-grid-layout/core';import type { Item } from '../types';import { COLUMNS } from '$lib';import { clamp } from '../helpers/utils';
function toLayoutItem(item: Item, mobile: boolean): LayoutItem { if (mobile) { return { x: item.mobileX, y: item.mobileY, w: item.mobileW, h: item.mobileH, i: item.id }; } return { x: item.x, y: item.y, w: item.w, h: item.h, i: item.id };}
function toLayout(items: Item[], mobile: boolean): LayoutItem[] { return items.map((i) => toLayoutItem(i, mobile));}
function applyLayout(items: Item[], layout: LayoutItem[], mobile: boolean): void { const itemsMap: Map<string, Item> = new Map();
for (const item of items) { itemsMap.set(item.id, item); } for (const l of layout) { const item = itemsMap.get(l.i);
if (!item) { console.error('item not found in layout!! this should never happen!'); continue; }
if (mobile) { item.mobileX = l.x; item.mobileY = l.y; } else { item.x = l.x; item.y = l.y; } }}
export function overlaps(a: Item, b: Item, mobile: boolean) { if (a === b) return false; return collides(toLayoutItem(a, mobile), toLayoutItem(b, mobile));}
/** Returns true if any two items overlap in the given layout. */export function hasOverlaps(items: Item[], mobile: boolean): boolean { for (let i = 0; i < items.length; i++) { for (let j = i + 1; j < items.length; j++) { if (overlaps(items[i], items[j], mobile)) return true; } } return false;}
export function fixCollisions( items: Item[], item: Item, mobile: boolean = false, skipCompact: boolean = false, originalPos?: { x: number; y: number }) { if (mobile) item.mobileX = clamp(item.mobileX, 0, COLUMNS - item.mobileW); else item.x = clamp(item.x, 0, COLUMNS - item.w);
const targetX = mobile ? item.mobileX : item.x; const targetY = mobile ? item.mobileY : item.y;
let layout = toLayout(items, mobile);
const movedLayoutItem = layout.find((i) => i.i === item.id);
if (!movedLayoutItem) { console.error('item not found in layout! this should never happen!'); return; }
// If we know the original position, set it on the layout item so // moveElement can detect direction and push items properly. if (originalPos) { movedLayoutItem.x = originalPos.x; movedLayoutItem.y = originalPos.y; }
layout = moveElement(layout, movedLayoutItem, targetX, targetY, true, false, 'vertical', COLUMNS);
if (!skipCompact) layout = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[];
applyLayout(items, layout, mobile);}
export function fixAllCollisions(items: Item[], mobile: boolean) { let layout = toLayout(items, mobile); correctBounds(layout as any, { cols: COLUMNS }); layout = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[]; applyLayout(items, layout, mobile);}
/** * Only fix items that are out of grid bounds, without compacting or resolving overlaps. * This is safe to call on load — it won't shift already-valid layouts. */export function sanitizeBounds(items: Item[], mobile: boolean) { const layout = toLayout(items, mobile); correctBounds(layout as any, { cols: COLUMNS }); applyLayout(items, layout, mobile);}
export function compactItems(items: Item[], mobile: boolean) { const layout = toLayout(items, mobile); const compacted = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[]; applyLayout(items, compacted, mobile);}
export function setPositionOfNewItem( newItem: Item, items: Item[], viewportCenter?: { gridY: number; isMobile: boolean }) { const desktopLayout = toLayout(items, false); const mobileLayout = toLayout(items, true);
function hasCollision(mobile: boolean): boolean { const layout = mobile ? mobileLayout : desktopLayout; return getFirstCollision(layout, toLayoutItem(newItem, mobile)) !== undefined; }
if (viewportCenter) { const { gridY, isMobile } = viewportCenter;
if (isMobile) { // Place at viewport center Y newItem.mobileY = Math.max(0, Math.round(gridY - newItem.mobileH / 2)); newItem.mobileY = Math.floor(newItem.mobileY / 2) * 2;
// Try to find a free X at this Y let found = false; for ( newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 2 ) { if (!hasCollision(true)) { found = true; break; } } if (!found) { newItem.mobileX = 0; }
// Desktop: derive from mobile newItem.y = Math.max(0, Math.round(newItem.mobileY / 2)); found = false; for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) { if (!hasCollision(false)) { found = true; break; } } if (!found) { newItem.x = 0; } } else { // Place at viewport center Y newItem.y = Math.max(0, Math.round(gridY - newItem.h / 2));
// Try to find a free X at this Y let found = false; for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) { if (!hasCollision(false)) { found = true; break; } } if (!found) { newItem.x = 0; }
// Mobile: derive from desktop newItem.mobileY = Math.max(0, Math.round(newItem.y * 2)); found = false; for ( newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 2 ) { if (!hasCollision(true)) { found = true; break; } } if (!found) { newItem.mobileX = 0; } } return; }
let foundPosition = false; while (!foundPosition) { for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) { if (!hasCollision(false)) { foundPosition = true; break; } } if (!foundPosition) newItem.y += 1; }
let foundMobilePosition = false; while (!foundMobilePosition) { for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 1) { if (!hasCollision(true)) { foundMobilePosition = true; break; } } if (!foundMobilePosition) newItem.mobileY! += 1; }}
/** * Find a valid position for a new item in a single mode (desktop or mobile). * This modifies the item's position properties in-place. */export function findValidPosition(newItem: Item, items: Item[], mobile: boolean) { const layout = toLayout(items, mobile);
if (mobile) { let foundPosition = false; newItem.mobileY = 0; while (!foundPosition) { for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX++) { if (!getFirstCollision(layout, toLayoutItem(newItem, true))) { foundPosition = true; break; } } if (!foundPosition) newItem.mobileY! += 1; } } else { let foundPosition = false; newItem.y = 0; while (!foundPosition) { for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) { if (!getFirstCollision(layout, toLayoutItem(newItem, false))) { foundPosition = true; break; } } if (!foundPosition) newItem.y += 1; } }}