your personal website on atproto - mirror blento.app

Merge pull request #45 from flo-bit/drawing-card

Drawing card

authored by Florian and committed by GitHub 814cd48d f22f411c

+116 -10
+1
CLAUDE.md
··· 64 64 - `auth.svelte.ts` - OAuth client state and login/logout flow using `@atcute/oauth-browser-client` 65 65 - `atproto.ts` - ATProto API helpers: `resolveHandle`, `listRecords`, `getRecord`, `putRecord`, `deleteRecord`, `uploadImage` 66 66 - Data is stored in user's PDS under collection `app.blento.card` 67 + - **Important**: ATProto does not allow floating point numbers in records. All numeric values must be integers. 67 68 68 69 **Data Loading (`src/lib/website/`):** 69 70
+2 -2
src/lib/cards/DrawCard/EditingDrawCard.svelte
··· 152 152 {@const pathData = getSvgPathFromStroke( 153 153 getStroke(stroke.points, getStrokeOptions(stroke.size ?? 3)) 154 154 )} 155 - <path d={pathData} class="fill-black accent:fill-white dark:fill-white" /> 155 + <path d={pathData} class="accent:fill-white fill-black dark:fill-white" /> 156 156 {/each} 157 157 {#if currentStroke.length > 0} 158 158 {@const pathData = getSvgPathFromStroke( 159 159 getStroke(currentStroke, getStrokeOptions(strokeSizes[strokeWidth])) 160 160 )} 161 - <path d={pathData} class="fill-black accent:fill-white dark:fill-white" /> 161 + <path d={pathData} class="accent:fill-white fill-black dark:fill-white" /> 162 162 {/if} 163 163 </svg> 164 164
+34
src/lib/helper.ts
··· 268 268 } 269 269 } 270 270 271 + /** 272 + * Find a valid position for a new item in a single mode (desktop or mobile). 273 + * This modifies the item's position properties in-place. 274 + */ 275 + export function findValidPosition(newItem: Item, items: Item[], mobile: boolean) { 276 + if (mobile) { 277 + let foundPosition = false; 278 + newItem.mobileY = 0; 279 + while (!foundPosition) { 280 + for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX++) { 281 + const collision = items.find((item) => overlaps(newItem, item, true)); 282 + if (!collision) { 283 + foundPosition = true; 284 + break; 285 + } 286 + } 287 + if (!foundPosition) newItem.mobileY! += 1; 288 + } 289 + } else { 290 + let foundPosition = false; 291 + newItem.y = 0; 292 + while (!foundPosition) { 293 + for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) { 294 + const collision = items.find((item) => overlaps(newItem, item, false)); 295 + if (!collision) { 296 + foundPosition = true; 297 + break; 298 + } 299 + } 300 + if (!foundPosition) newItem.y += 1; 301 + } 302 + } 303 + } 304 + 271 305 export async function refreshData(data: { updatedAt?: number; handle: string }) { 272 306 const TEN_MINUTES = 10 * 60 * 1000; 273 307 const now = Date.now();
+53 -5
src/lib/website/EditableProfile.svelte
··· 62 62 : '@5xl/wrapper:max-w-4xl @5xl/wrapper:px-12' 63 63 ]} 64 64 > 65 - <div class={['absolute left-2 z-20 flex gap-2', profilePosition === 'side' ? 'top-12' : 'top-4']}> 65 + <div 66 + class={[ 67 + 'absolute left-2 z-20 flex gap-2', 68 + profilePosition === 'side' ? 'top-2 left-14' : 'top-2' 69 + ]} 70 + > 66 71 <Button 67 - size="sm" 72 + size="icon" 68 73 onclick={() => { 69 74 data.publication.preferences ??= {}; 70 75 data.publication.preferences.hideProfileSection = true; ··· 72 77 }} 73 78 variant="ghost" 74 79 > 75 - hide profile 80 + <svg 81 + xmlns="http://www.w3.org/2000/svg" 82 + fill="none" 83 + viewBox="0 0 24 24" 84 + stroke-width="1.5" 85 + stroke="currentColor" 86 + class="size-6" 87 + > 88 + <path 89 + stroke-linecap="round" 90 + stroke-linejoin="round" 91 + d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" 92 + /> 93 + </svg> 76 94 </Button> 77 95 78 96 <!-- Position toggle button (desktop only) --> 79 97 {#if !isMobile()} 80 - <Button size="sm" type="button" onclick={toggleProfilePosition} variant="ghost"> 81 - {profilePosition === 'side' ? 'Move to top' : 'Move to side'} 98 + <Button size="icon" type="button" onclick={toggleProfilePosition} variant="ghost"> 99 + {#if profilePosition === 'side'} 100 + <svg 101 + xmlns="http://www.w3.org/2000/svg" 102 + fill="none" 103 + viewBox="0 0 24 24" 104 + stroke-width="1.5" 105 + stroke="currentColor" 106 + class="size-6" 107 + > 108 + <path 109 + stroke-linecap="round" 110 + stroke-linejoin="round" 111 + d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" 112 + /> 113 + </svg> 114 + {:else} 115 + <svg 116 + xmlns="http://www.w3.org/2000/svg" 117 + fill="none" 118 + viewBox="0 0 24 24" 119 + stroke-width="1.5" 120 + stroke="currentColor" 121 + class="size-6" 122 + > 123 + <path 124 + stroke-linecap="round" 125 + stroke-linejoin="round" 126 + d="m19.5 4.5-15 15m0 0h11.25m-11.25 0V8.25" 127 + /> 128 + </svg> 129 + {/if} 82 130 </Button> 83 131 {/if} 84 132 </div>
+26 -3
src/lib/website/EditableWebsite.svelte
··· 6 6 clamp, 7 7 compactItems, 8 8 createEmptyCard, 9 + findValidPosition, 9 10 fixCollisions, 10 11 getHideProfileSection, 11 12 getProfilePosition, ··· 334 335 if (isMobile) { 335 336 item.mobileX = gridX; 336 337 item.mobileY = gridY; 338 + // Find valid desktop position 339 + findValidPosition(item, items, false); 337 340 } else { 338 341 item.x = gridX; 339 342 item.y = gridY; 343 + // Find valid mobile position 344 + findValidPosition(item, items, true); 340 345 } 341 346 342 347 items = [...items, item]; ··· 576 581 > 577 582 {#if getHideProfileSection(data)} 578 583 <Button 579 - size="sm" 584 + size="icon" 580 585 variant="ghost" 581 586 onclick={() => { 582 587 data.publication.preferences ??= {}; 583 588 data.publication.preferences.hideProfileSection = false; 584 589 data = { ...data }; 585 590 }} 586 - class="pointer-events-auto absolute top-2 left-4 z-20" 591 + class="pointer-events-auto absolute top-2 left-2 z-20" 587 592 > 588 - show profile 593 + <svg 594 + xmlns="http://www.w3.org/2000/svg" 595 + fill="none" 596 + viewBox="0 0 24 24" 597 + stroke-width="1.5" 598 + stroke="currentColor" 599 + class="size-6" 600 + > 601 + <path 602 + stroke-linecap="round" 603 + stroke-linejoin="round" 604 + d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" 605 + /> 606 + <path 607 + stroke-linecap="round" 608 + stroke-linejoin="round" 609 + d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" 610 + /> 611 + </svg> 589 612 </Button> 590 613 {/if} 591 614 <div class="pointer-events-none"></div>