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