your personal website on atproto - mirror blento.app

dragging and stuff

Florian 0eb04ab3 061333eb

+184 -55
+78 -45
src/lib/EditableWebsite.svelte
··· 8 8 import { 9 9 cardsEqual, 10 10 clamp, 11 + compactItems, 11 12 fixCollisions, 12 13 setCanEdit, 13 14 setIsMobile, 14 - setPositionOfNewItem 15 + setPositionOfNewItem, 16 + simulateFinalPosition 15 17 } from './helper'; 16 18 import Profile from './Profile.svelte'; 17 19 import type { Item } from './types'; ··· 187 189 ); 188 190 189 191 let showSettings = $state(false); 192 + 193 + let debugPoint = $state({ x: 0, y: 0 }); 194 + 195 + function getDragXY( 196 + e: DragEvent & { 197 + currentTarget: EventTarget & HTMLDivElement; 198 + } 199 + ) { 200 + if (!container) return; 201 + 202 + const x = e.clientX + activeDragElement.mouseDeltaX; 203 + const y = e.clientY + activeDragElement.mouseDeltaY; 204 + 205 + const rect = container.getBoundingClientRect(); 206 + 207 + debugPoint.x = x - rect.left; 208 + debugPoint.y = y - rect.top + margin; 209 + console.log(rect.top); 210 + 211 + let gridX = clamp( 212 + Math.floor(((x - rect.left) / rect.width) * 4), 213 + 0, 214 + 4 - (activeDragElement.w ?? 0) 215 + ); 216 + let gridY = Math.max(Math.round(((y - rect.top + margin) / (rect.width - margin)) * 4), 0); 217 + if (isMobile) { 218 + gridX = Math.floor(gridX / 2) * 2; 219 + gridY = Math.floor(gridY / 2) * 2; 220 + } 221 + return { x: gridX, y: gridY }; 222 + } 190 223 </script> 191 224 192 225 {#if !dev} ··· 234 267 bind:this={container} 235 268 ondragover={(e) => { 236 269 e.preventDefault(); 237 - if (!container) return; 270 + 271 + const cell = getDragXY(e); 272 + if (!cell) return; 238 273 239 - const x = e.clientX + activeDragElement.mouseDeltaX; 240 - const y = e.clientY + activeDragElement.mouseDeltaY; 241 - const rect = container.getBoundingClientRect(); 274 + activeDragElement.x = cell.x; 275 + activeDragElement.y = cell.y; 276 + 277 + // Auto-scroll when dragging near top or bottom of viewport 278 + const scrollZone = 150; 279 + const scrollSpeed = 15; 280 + const viewportHeight = window.innerHeight; 242 281 243 - let gridX = clamp( 244 - Math.floor(((x - rect.left) / rect.width) * 4), 245 - 0, 246 - 4 - (activeDragElement.w ?? 0) 247 - ); 248 - let gridY = Math.max(Math.floor(((y - rect.top) / rect.width) * 4), 0); 249 - if (isMobile) { 250 - gridX = Math.floor(gridX / 2) * 2; 251 - gridY = Math.floor(gridY / 2) * 2; 282 + if (e.clientY < scrollZone) { 283 + // Near top - scroll up 284 + const intensity = 1 - e.clientY / scrollZone; 285 + window.scrollBy(0, -scrollSpeed * intensity); 286 + } else if (e.clientY > viewportHeight - scrollZone) { 287 + // Near bottom - scroll down 288 + const intensity = 1 - (viewportHeight - e.clientY) / scrollZone; 289 + window.scrollBy(0, scrollSpeed * intensity); 252 290 } 253 - 254 - activeDragElement.x = gridX; 255 - activeDragElement.y = gridY; 256 291 }} 257 292 ondragend={async (e) => { 258 293 e.preventDefault(); 259 - if (!container) return; 260 - 261 - const x = e.clientX + activeDragElement.mouseDeltaX; 262 - const y = e.clientY + activeDragElement.mouseDeltaY; 263 - const rect = container.getBoundingClientRect(); 264 - 265 - let gridX = clamp( 266 - Math.floor(((x - rect.left) / rect.width) * 4), 267 - 0, 268 - 4 - (activeDragElement.w ?? 0) 269 - ); 270 - let gridY = Math.max(Math.floor(((y - rect.top) / rect.width) * 4), 0); 271 - if (isMobile) { 272 - gridX = Math.floor(gridX / 2) * 2; 273 - gridY = Math.floor(gridY / 2) * 2; 274 - } 294 + const cell = getDragXY(e); 295 + if (!cell) return; 275 296 276 297 if (activeDragElement.item) { 277 298 if (isMobile) { 278 - activeDragElement.item.mobileX = gridX; 279 - activeDragElement.item.mobileY = gridY; 299 + activeDragElement.item.mobileX = cell.x; 300 + activeDragElement.item.mobileY = cell.y; 280 301 } else { 281 - activeDragElement.item.x = gridX; 282 - activeDragElement.item.y = gridY; 302 + activeDragElement.item.x = cell.x; 303 + activeDragElement.item.y = cell.y; 283 304 } 284 305 285 306 fixCollisions(items, activeDragElement.item, isMobile); ··· 296 317 bind:item={items[i]} 297 318 ondelete={() => { 298 319 items = items.filter((it) => it !== item); 320 + compactItems(items, isMobile); 299 321 }} 300 322 onsetsize={(newW: number, newH: number) => { 301 323 if (isMobile) { ··· 309 331 fixCollisions(items, item, isMobile); 310 332 }} 311 333 ondragstart={(e) => { 312 - const target = e.target as HTMLDivElement; 334 + const target = e.currentTarget as HTMLDivElement; 313 335 activeDragElement.element = target; 314 336 activeDragElement.w = item.w; 315 337 activeDragElement.h = item.h; 316 338 activeDragElement.item = item; 317 339 318 340 const rect = target.getBoundingClientRect(); 319 - activeDragElement.mouseDeltaX = rect.left + margin - e.clientX; 341 + activeDragElement.mouseDeltaX = rect.left - e.clientX; 320 342 activeDragElement.mouseDeltaY = rect.top - e.clientY; 343 + console.log(activeDragElement.mouseDeltaY); 344 + console.log(rect.width); 321 345 }} 322 346 > 323 347 <EditingCard bind:item={items[i]} /> ··· 325 349 {/each} 326 350 327 351 {#if activeDragElement.element && activeDragElement.x >= 0 && activeDragElement.item} 328 - {@const item = activeDragElement} 352 + {@const finalPos = simulateFinalPosition(items, activeDragElement.item, activeDragElement.x, activeDragElement.y, isMobile)} 329 353 <div 330 - class={['bg-base-500/10 absolute aspect-square rounded-2xl']} 331 - style={`translate: calc(${(item.x / 4) * 100}cqw + ${margin / 2}px) calc(${(item.y / 4) * 100}cqw + ${margin / 2}px); 332 - 333 - width: calc(${(getW(activeDragElement.item) / 4) * 100}cqw - ${margin}px); 334 - height: calc(${(getH(activeDragElement.item) / 4) * 100}cqw - ${margin}px);`} 354 + class={[ 355 + 'bg-base-500/10 absolute aspect-square rounded-2xl transition-transform duration-100' 356 + ]} 357 + style={`translate: calc(${(finalPos.x / 4) * 100}cqw + ${margin}px) calc(${(finalPos.y / 4) * 100}cqw + ${margin}px); 358 + 359 + width: calc(${(getW(activeDragElement.item) / 4) * 100}cqw - ${margin * 2}px); 360 + height: calc(${(getH(activeDragElement.item) / 4) * 100}cqw - ${margin * 2}px);`} 361 + ></div> 362 + {/if} 363 + 364 + {#if dev} 365 + <div 366 + class="absolute size-4 rounded-full bg-red-500" 367 + style={`translate: ${debugPoint.x}px ${debugPoint.y}px;`} 335 368 ></div> 336 369 {/if} 337 370 <div style="height: {((maxHeight + 1) / 4) * 100}cqw;"></div>
+1 -1
src/lib/cards/BaseCard/BaseCard.svelte
··· 66 66 height: calc((var(--mh) / 8) * 100cqw - (var(--mm) * 2)); 67 67 } 68 68 69 - @container wrapper (width >= 64rem) { 69 + @container grid (width >= 42rem) { 70 70 .card { 71 71 translate: calc((var(--dx) / 8) * 100cqw + var(--dm)) 72 72 calc((var(--dy) / 8) * 100cqw + var(--dm));
+71
src/lib/helper.ts
··· 94 94 if (mobile) it.mobileX = clamp(it.mobileX, 0, COLS - it.mobileW); 95 95 else it.x = clamp(it.x, 0, COLS - it.w); 96 96 } 97 + 98 + compactItems(items, mobile); 99 + } 100 + 101 + // Move all items up as far as possible without collisions 102 + export function compactItems(items: Item[], mobile: boolean = false) { 103 + // Sort by Y position (top-to-bottom) so upper items settle first. 104 + const sortedItems = items.toSorted((a, b) => 105 + mobile ? a.mobileY - b.mobileY || a.mobileX - b.mobileX : a.y - b.y || a.x - b.x 106 + ); 107 + 108 + for (const item of sortedItems) { 109 + // Try moving item up row by row until we hit y=0 or a collision 110 + while (true) { 111 + const currentY = mobile ? item.mobileY : item.y; 112 + if (currentY <= 0) break; 113 + 114 + // Temporarily move up by 1 115 + if (mobile) item.mobileY -= 1; 116 + else item.y -= 1; 117 + 118 + // Check for collision with any other item 119 + const hasCollision = items.some((other) => other !== item && overlaps(item, other, mobile)); 120 + 121 + if (hasCollision) { 122 + // Revert the move 123 + if (mobile) item.mobileY += 1; 124 + else item.y += 1; 125 + break; 126 + } 127 + // No collision, keep the new position and try moving up again 128 + } 129 + } 130 + } 131 + 132 + // Simulate where an item would end up after fixCollisions + compaction 133 + export function simulateFinalPosition( 134 + items: Item[], 135 + movedItem: Item, 136 + newX: number, 137 + newY: number, 138 + mobile: boolean = false 139 + ): { x: number; y: number } { 140 + // Deep clone positions for simulation 141 + const clonedItems: Item[] = items.map((item) => ({ 142 + ...item, 143 + x: item.x, 144 + y: item.y, 145 + mobileX: item.mobileX, 146 + mobileY: item.mobileY 147 + })); 148 + 149 + const clonedMovedItem = clonedItems.find((item) => item.id === movedItem.id); 150 + if (!clonedMovedItem) return { x: newX, y: newY }; 151 + 152 + // Set the new position 153 + if (mobile) { 154 + clonedMovedItem.mobileX = newX; 155 + clonedMovedItem.mobileY = newY; 156 + } else { 157 + clonedMovedItem.x = newX; 158 + clonedMovedItem.y = newY; 159 + } 160 + 161 + // Run fixCollisions on the cloned data 162 + fixCollisions(clonedItems, clonedMovedItem, mobile); 163 + 164 + // Return the final position of the moved item 165 + return mobile 166 + ? { x: clonedMovedItem.mobileX, y: clonedMovedItem.mobileY } 167 + : { x: clonedMovedItem.x, y: clonedMovedItem.y }; 97 168 } 98 169 99 170 export function sortItems(a: Item, b: Item) {
src/routes/[handle]/+page.server.ts src/routes/[handle]/+layout.server.ts
-9
src/routes/[handle]/edit/+page.server.ts
··· 1 - import { loadData } from '$lib/website/load'; 2 - import { env } from '$env/dynamic/private'; 3 - import { error } from '@sveltejs/kit'; 4 - 5 - export async function load({ params }) { 6 - if (env.PUBLIC_IS_SELFHOSTED) error(404); 7 - const data = await loadData(params.handle); 8 - return { ...data, handle: params.handle }; 9 - }
+34
src/routes/api/instagram/info/+server.ts
··· 1 + import { json } from '@sveltejs/kit'; 2 + 3 + export async function GET({ url }) { 4 + const username = url.searchParams.get('username'); 5 + if (!username) { 6 + return json({ error: 'No username provided' }, { status: 400 }); 7 + } 8 + 9 + const requestUrl = `https://i.instagram.com/api/v1/users/web_profile_info/?username=${encodeURIComponent(username)}`; 10 + 11 + try { 12 + const response = await fetch(requestUrl, { 13 + headers: { 14 + 'User-Agent': 15 + 'Instagram 76.0.0.15.395 Android (24/7.0; 640dpi; 1440x2560; samsung; SM-G930F; herolte; samsungexynos8890; en_US; 138226743)', 16 + Origin: 'https://www.instagram.com', 17 + Referer: 'https://www.instagram.com/' 18 + } 19 + }); 20 + 21 + if (!response.ok) { 22 + return json( 23 + { error: 'Failed to fetch Instagram profile', status: response.status }, 24 + { status: response.status } 25 + ); 26 + } 27 + 28 + const data = await response.json(); 29 + return json({ follower_count: data['data']['user']['edge_followed_by']['count'] }); 30 + } catch (error) { 31 + console.error('Error fetching Instagram profile:', error); 32 + return json({ error: 'Failed to fetch Instagram profile' }, { status: 500 }); 33 + } 34 + }