your personal website on atproto - mirror blento.app

commit

+79 -40
+64 -40
src/lib/cards/GitHubContributorsCard/GitHubContributorsCard.svelte
··· 44 44 ); 45 45 if (response.ok) { 46 46 const data = await response.json(); 47 - clientContributors = [ 48 - ...data, 49 - ...data.map((v) => { 50 - return { ...v, username: v.username + '1' }; 51 - }) 52 - ]; 47 + clientContributors = data; 53 48 } 54 49 } catch (error) { 55 50 console.error('Failed to fetch GitHub contributors:', error); ··· 65 60 const MIN_SIZE = 16; 66 61 const MAX_SIZE = 80; 67 62 63 + function hexCapacity(size: number, availW: number, availH: number): number { 64 + const colsWide = Math.floor((availW + GAP) / (size + GAP)); 65 + if (colsWide < 1) return 0; 66 + const colsNarrow = Math.max(1, colsWide - 1); 67 + const maxRows = Math.floor((availH + GAP) / (size + GAP)); 68 + let capacity = 0; 69 + for (let r = 0; r < maxRows; r++) { 70 + capacity += r % 2 === 0 ? colsWide : colsNarrow; 71 + } 72 + return capacity; 73 + } 74 + 68 75 let computedSize = $derived.by(() => { 69 76 if (!containerWidth || !containerHeight || totalItems === 0) return 40; 70 77 ··· 73 80 74 81 while (lo <= hi) { 75 82 const mid = Math.floor((lo + hi) / 2); 76 - // Reserve ~1 avatar of padding on each side 77 - const availW = containerWidth - mid * 2; 78 - const availH = containerHeight - mid * 2; 83 + const availW = containerWidth - mid; 84 + const availH = containerHeight - mid; 79 85 if (availW <= 0 || availH <= 0) { 80 86 hi = mid - 1; 81 87 continue; 82 88 } 83 - const cols = Math.floor((availW + GAP) / (mid + GAP)); 84 - const rows = Math.floor((availH + GAP) / (mid + GAP)); 85 - if (cols > 0 && rows > 0 && cols * rows >= totalItems) { 89 + if (hexCapacity(mid, availW, availH) >= totalItems) { 86 90 lo = mid + 1; 87 91 } else { 88 92 hi = mid - 1; ··· 94 98 95 99 let padding = $derived(computedSize / 2); 96 100 101 + let rows = $derived.by(() => { 102 + const availW = containerWidth - computedSize; 103 + if (availW <= 0) return [] as GitHubContributor[][]; 104 + const colsWide = Math.floor((availW + GAP) / (computedSize + GAP)); 105 + const colsNarrow = Math.max(1, colsWide - 1); 106 + 107 + const result: GitHubContributor[][] = []; 108 + let idx = 0; 109 + let rowNum = 0; 110 + while (idx < namedContributors.length) { 111 + const cols = rowNum % 2 === 0 ? colsWide : colsNarrow; 112 + result.push(namedContributors.slice(idx, idx + cols)); 113 + idx += cols; 114 + rowNum++; 115 + } 116 + return result; 117 + }); 118 + 97 119 let textSize = $derived( 98 120 computedSize < 24 ? 'text-[10px]' : computedSize < 40 ? 'text-xs' : 'text-sm' 99 121 ); ··· 112 134 {/if} 113 135 {:else if totalItems > 0} 114 136 <div style="padding: {padding}px;"> 115 - <div class="flex flex-wrap items-center justify-center" style="gap: {GAP}px;"> 116 - {#each namedContributors as contributor (contributor.username)} 117 - <div class="relative"> 118 - <a 119 - href="https://github.com/{contributor.username}" 120 - target="_blank" 121 - rel="noopener noreferrer" 122 - class="accent:ring-accent-500 relative block rounded-full ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 123 - > 124 - {#if contributor.avatarUrl} 125 - <img 126 - src={contributor.avatarUrl} 127 - alt={contributor.username} 128 - class="rounded-full object-cover" 129 - style="width: {computedSize}px; height: {computedSize}px;" 130 - /> 131 - {:else} 132 - <div 133 - class="bg-base-200 dark:bg-base-700 accent:bg-accent-400 flex items-center justify-center rounded-full" 134 - style="width: {computedSize}px; height: {computedSize}px;" 135 - > 136 - <span 137 - class="text-base-500 dark:text-base-400 accent:text-accent-100 {textSize} font-medium" 137 + <div class="flex flex-col items-center" style="gap: {GAP}px;"> 138 + {#each rows as row, rowIdx (rowIdx)} 139 + <div class="flex justify-center" style="gap: {GAP}px;"> 140 + {#each row as contributor (contributor.username)} 141 + <a 142 + href="https://github.com/{contributor.username}" 143 + target="_blank" 144 + rel="noopener noreferrer" 145 + class="accent:ring-accent-500 block rounded-full ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 146 + > 147 + {#if contributor.avatarUrl} 148 + <img 149 + src={contributor.avatarUrl} 150 + alt={contributor.username} 151 + class="rounded-full object-cover" 152 + style="width: {computedSize}px; height: {computedSize}px;" 153 + /> 154 + {:else} 155 + <div 156 + class="bg-base-200 dark:bg-base-700 accent:bg-accent-400 flex items-center justify-center rounded-full" 157 + style="width: {computedSize}px; height: {computedSize}px;" 138 158 > 139 - {contributor.username.charAt(0).toUpperCase()} 140 - </span> 141 - </div> 142 - {/if} 143 - </a> 159 + <span 160 + class="text-base-500 dark:text-base-400 accent:text-accent-100 {textSize} font-medium" 161 + > 162 + {contributor.username.charAt(0).toUpperCase()} 163 + </span> 164 + </div> 165 + {/if} 166 + </a> 167 + {/each} 144 168 </div> 145 169 {/each} 146 170 </div>
+15
src/lib/cards/GitHubContributorsCard/index.ts
··· 43 43 } 44 44 return contributorsData; 45 45 }, 46 + onUrlHandler: (url, item) => { 47 + const match = url.match(/github\.com\/([^/]+)\/([^/]+)/); 48 + if (!match) return null; 49 + 50 + item.cardData.owner = match[1]; 51 + item.cardData.repo = match[2]; 52 + 53 + item.w = 4; 54 + item.h = 2; 55 + item.mobileW = 8; 56 + item.mobileH = 4; 57 + 58 + return item; 59 + }, 60 + urlHandlerPriority: 1, 46 61 allowSetColor: true, 47 62 defaultColor: 'base', 48 63 minW: 2,