your personal website on atproto - mirror blento.app
at switch-grid-layout 254 lines 6.4 kB view raw
1import { type LayoutItem, type Layout } from 'react-grid-layout/core'; 2import { 3 collides, 4 moveElement, 5 correctBounds, 6 getFirstCollision, 7 verticalCompactor 8} from 'react-grid-layout/core'; 9import type { Item } from '../types'; 10import { COLUMNS } from '$lib'; 11import { clamp } from '../helper'; 12 13function toLayoutItem(item: Item, mobile: boolean): LayoutItem { 14 if (mobile) { 15 return { 16 x: item.mobileX, 17 y: item.mobileY, 18 w: item.mobileW, 19 h: item.mobileH, 20 i: item.id 21 }; 22 } 23 return { 24 x: item.x, 25 y: item.y, 26 w: item.w, 27 h: item.h, 28 i: item.id 29 }; 30} 31 32function toLayout(items: Item[], mobile: boolean): LayoutItem[] { 33 return items.map((i) => toLayoutItem(i, mobile)); 34} 35 36function applyLayout(items: Item[], layout: LayoutItem[], mobile: boolean): void { 37 const itemsMap: Map<string, Item> = new Map(); 38 39 for (const item of items) { 40 itemsMap.set(item.id, item); 41 } 42 for (const l of layout) { 43 const item = itemsMap.get(l.i); 44 45 if (!item) { 46 console.error('item not found in layout!! this should never happen!'); 47 continue; 48 } 49 50 if (mobile) { 51 item.mobileX = l.x; 52 item.mobileY = l.y; 53 } else { 54 item.x = l.x; 55 item.y = l.y; 56 } 57 } 58} 59 60export function overlaps(a: Item, b: Item, mobile: boolean) { 61 if (a === b) return false; 62 return collides(toLayoutItem(a, mobile), toLayoutItem(b, mobile)); 63} 64 65export function fixCollisions( 66 items: Item[], 67 item: Item, 68 mobile: boolean = false, 69 skipCompact: boolean = false, 70 originalPos?: { x: number; y: number } 71) { 72 if (mobile) item.mobileX = clamp(item.mobileX, 0, COLUMNS - item.mobileW); 73 else item.x = clamp(item.x, 0, COLUMNS - item.w); 74 75 const targetX = mobile ? item.mobileX : item.x; 76 const targetY = mobile ? item.mobileY : item.y; 77 78 let layout = toLayout(items, mobile); 79 80 const movedLayoutItem = layout.find((i) => i.i === item.id); 81 82 if (!movedLayoutItem) { 83 console.error('item not found in layout! this should never happen!'); 84 return; 85 } 86 87 // If we know the original position, set it on the layout item so 88 // moveElement can detect direction and push items properly. 89 if (originalPos) { 90 movedLayoutItem.x = originalPos.x; 91 movedLayoutItem.y = originalPos.y; 92 } 93 94 layout = moveElement(layout, movedLayoutItem, targetX, targetY, true, false, 'vertical', COLUMNS); 95 96 if (!skipCompact) layout = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[]; 97 98 applyLayout(items, layout, mobile); 99} 100 101export function fixAllCollisions(items: Item[], mobile: boolean) { 102 let layout = toLayout(items, mobile); 103 correctBounds(layout as any, { cols: COLUMNS }); 104 layout = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[]; 105 applyLayout(items, layout, mobile); 106} 107 108export function compactItems(items: Item[], mobile: boolean) { 109 const layout = toLayout(items, mobile); 110 const compacted = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[]; 111 applyLayout(items, compacted, mobile); 112} 113 114export function setPositionOfNewItem( 115 newItem: Item, 116 items: Item[], 117 viewportCenter?: { gridY: number; isMobile: boolean } 118) { 119 const desktopLayout = toLayout(items, false); 120 const mobileLayout = toLayout(items, true); 121 122 function hasCollision(mobile: boolean): boolean { 123 const layout = mobile ? mobileLayout : desktopLayout; 124 return getFirstCollision(layout, toLayoutItem(newItem, mobile)) !== undefined; 125 } 126 127 if (viewportCenter) { 128 const { gridY, isMobile } = viewportCenter; 129 130 if (isMobile) { 131 // Place at viewport center Y 132 newItem.mobileY = Math.max(0, Math.round(gridY - newItem.mobileH / 2)); 133 newItem.mobileY = Math.floor(newItem.mobileY / 2) * 2; 134 135 // Try to find a free X at this Y 136 let found = false; 137 for ( 138 newItem.mobileX = 0; 139 newItem.mobileX <= COLUMNS - newItem.mobileW; 140 newItem.mobileX += 2 141 ) { 142 if (!hasCollision(true)) { 143 found = true; 144 break; 145 } 146 } 147 if (!found) { 148 newItem.mobileX = 0; 149 } 150 151 // Desktop: derive from mobile 152 newItem.y = Math.max(0, Math.round(newItem.mobileY / 2)); 153 found = false; 154 for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) { 155 if (!hasCollision(false)) { 156 found = true; 157 break; 158 } 159 } 160 if (!found) { 161 newItem.x = 0; 162 } 163 } else { 164 // Place at viewport center Y 165 newItem.y = Math.max(0, Math.round(gridY - newItem.h / 2)); 166 167 // Try to find a free X at this Y 168 let found = false; 169 for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) { 170 if (!hasCollision(false)) { 171 found = true; 172 break; 173 } 174 } 175 if (!found) { 176 newItem.x = 0; 177 } 178 179 // Mobile: derive from desktop 180 newItem.mobileY = Math.max(0, Math.round(newItem.y * 2)); 181 found = false; 182 for ( 183 newItem.mobileX = 0; 184 newItem.mobileX <= COLUMNS - newItem.mobileW; 185 newItem.mobileX += 2 186 ) { 187 if (!hasCollision(true)) { 188 found = true; 189 break; 190 } 191 } 192 if (!found) { 193 newItem.mobileX = 0; 194 } 195 } 196 return; 197 } 198 199 let foundPosition = false; 200 while (!foundPosition) { 201 for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) { 202 if (!hasCollision(false)) { 203 foundPosition = true; 204 break; 205 } 206 } 207 if (!foundPosition) newItem.y += 1; 208 } 209 210 let foundMobilePosition = false; 211 while (!foundMobilePosition) { 212 for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 1) { 213 if (!hasCollision(true)) { 214 foundMobilePosition = true; 215 break; 216 } 217 } 218 if (!foundMobilePosition) newItem.mobileY! += 1; 219 } 220} 221 222/** 223 * Find a valid position for a new item in a single mode (desktop or mobile). 224 * This modifies the item's position properties in-place. 225 */ 226export function findValidPosition(newItem: Item, items: Item[], mobile: boolean) { 227 const layout = toLayout(items, mobile); 228 229 if (mobile) { 230 let foundPosition = false; 231 newItem.mobileY = 0; 232 while (!foundPosition) { 233 for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX++) { 234 if (!getFirstCollision(layout, toLayoutItem(newItem, true))) { 235 foundPosition = true; 236 break; 237 } 238 } 239 if (!foundPosition) newItem.mobileY! += 1; 240 } 241 } else { 242 let foundPosition = false; 243 newItem.y = 0; 244 while (!foundPosition) { 245 for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) { 246 if (!getFirstCollision(layout, toLayoutItem(newItem, false))) { 247 foundPosition = true; 248 break; 249 } 250 } 251 if (!foundPosition) newItem.y += 1; 252 } 253 } 254}