From b4864bfa343276a88a5bffd0817a46b2a607e6da Mon Sep 17 00:00:00 2001 From: Toria Date: Fri, 24 Apr 2026 20:57:08 +0100 Subject: [PATCH] ProfilePicture Bounce State PR. - Add a bounce state to the ProfilePicture element - Also, use a defined "placeholder" class that handles the svg for us, as well as the animations and colours. - Move the import from PortMaintainers.svelte to ProfilePicture.svelte to follow this new modular structure. - Add some optimisations as well - Add a bespoke tooltip. Signed-off-by: Toria --- src/components/ProfilePicture.svelte | 167 +++++++++++++++--- src/data/icons.json | 16 +- src/data/scripts/fetchMaintainerAvatars.ts | 67 +++++-- .../ports/_components/PortMaintainers.svelte | 6 +- src/pages/ports/index.astro | 4 +- 5 files changed, 213 insertions(+), 47 deletions(-) diff --git a/src/components/ProfilePicture.svelte b/src/components/ProfilePicture.svelte index a9a943a6..eda16b17 100644 --- a/src/components/ProfilePicture.svelte +++ b/src/components/ProfilePicture.svelte @@ -1,44 +1,171 @@ -{#if isPlaceholder} - Placeholder Avatar -{:else} - {username}'s Avatar -{/if} + + {#if isPlaceholder} +
+ {@html NoMaintainerIcon} +
+ {:else} + {username}'s Avatar + {/if} +
diff --git a/src/data/icons.json b/src/data/icons.json index 3e8f0faf..661b0732 100644 --- a/src/data/icons.json +++ b/src/data/icons.json @@ -1,6 +1,6 @@ { "prefix": "ctp", - "lastModified": 1733796097, + "lastModified": 1777055725, "icons": { "core": { "body": "", @@ -23,15 +23,18 @@ "body": "" }, "logo": { - "body": "", + "body": "", "width": 1042, "height": 1042 }, "logo-text": { - "body": "", + "body": "", "width": 1171, "height": 205 }, + "lucide-user-round-x": { + "body": "" + }, "lxqt": { "body": "" }, @@ -61,16 +64,11 @@ "width": 1707, "height": 1707 }, - "userstyles": { + "userstyles-staff": { "body": "", "width": 1707, "height": 1707 }, - "vercel-badge": { - "body": "", - "width": 212, - "height": 44 - }, "windows-files": { "body": "" }, diff --git a/src/data/scripts/fetchMaintainerAvatars.ts b/src/data/scripts/fetchMaintainerAvatars.ts index a874a2ff..5bbe4e4d 100644 --- a/src/data/scripts/fetchMaintainerAvatars.ts +++ b/src/data/scripts/fetchMaintainerAvatars.ts @@ -18,23 +18,64 @@ async function maintainersToFetch() { } async function fetchAndProcessImage(maintainer: Collaborator) { - const response = await fetch(`${maintainer.url}.png?size=${REQUEST_SIZE}`); - if (!response.ok) { - console.warn(`Failed to fetch ${maintainer.url}: ${response.status} ${response.statusText}`); + const MAX_RETRIES = 3; + const RETRY_DELAY = 500; + let response; + let avatarUrl: string; + + try { + const apiResponse = await fetch(`https://api.github.com/users/${maintainer.username}`); + if (!apiResponse.ok) { + console.warn(`Failed to fetch GitHub API for ${maintainer.username}: ${apiResponse.status}`); + maintainersWithoutAvatars.push(maintainer.username); + return; + } + const userData = await apiResponse.json(); + avatarUrl = userData.avatar_url; + } catch (error) { + console.warn( + `Failed to get avatar URL for ${maintainer.username}:`, + error instanceof Error ? error.message : String(error), + ); + maintainersWithoutAvatars.push(maintainer.username); + return; + } + + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + response = await fetch(avatarUrl); + break; + } catch (error) { + console.warn(`Attempt ${attempt}/${MAX_RETRIES} failed for ${maintainer.username}`); + if (attempt === MAX_RETRIES) { + maintainersWithoutAvatars.push(maintainer.username); + return; + } + await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); + } + } + + if (!response!.ok) { + console.warn(`Failed to fetch avatar for ${maintainer.username}: ${response!.status}`); maintainersWithoutAvatars.push(maintainer.username); return; } - const buffer = await response.arrayBuffer(); + try { + const buffer = await response!.arrayBuffer(); - await Promise.all( - SIZES.map((size) => - sharp(buffer) - .resize(size, size) - .webp({ quality: IMAGE_QUALITY }) - .toFile(`${PUBLIC_MAINTAINERS_DIR}/${size}x${size}/${maintainer.username}.webp`), - ), - ); + await Promise.all( + SIZES.map((size) => + sharp(buffer) + .resize(size, size) + .webp({ quality: IMAGE_QUALITY }) + .toFile(`${PUBLIC_MAINTAINERS_DIR}/${size}x${size}/${maintainer.username}.webp`), + ), + ); + } catch (error) { + console.error(`Failed to process ${maintainer.username}:`, error instanceof Error ? error.message : String(error)); + maintainersWithoutAvatars.push(maintainer.username); + } } try { @@ -46,7 +87,7 @@ try { console.info(`[INFO]: fetching ${maintainers.length} maintainers`); await Promise.all(maintainers.map((maintainer) => fetchAndProcessImage(maintainer))); - + maintainersWithoutAvatars.push("__placeholder__"); await fs.writeFile(`${MAINTAINERS_DIR}/maintainersWithoutAvatars.json`, JSON.stringify(maintainersWithoutAvatars)); } catch (e) { console.error("Processing failed: ", e); diff --git a/src/pages/ports/_components/PortMaintainers.svelte b/src/pages/ports/_components/PortMaintainers.svelte index 6cc63839..ee8d4904 100644 --- a/src/pages/ports/_components/PortMaintainers.svelte +++ b/src/pages/ports/_components/PortMaintainers.svelte @@ -1,20 +1,20 @@ {#if port.repository["current-maintainers"].length > 0} -
+
{#each port.repository["current-maintainers"] as maintainer} {/each}
{:else}
- {@html NoMaintainerIcon} +
{/if} diff --git a/src/pages/ports/index.astro b/src/pages/ports/index.astro index aa7b7d6b..42726c1e 100644 --- a/src/pages/ports/index.astro +++ b/src/pages/ports/index.astro @@ -14,8 +14,8 @@ import PortCard from "./_components/PortCard.svelte"; description="Explore Catppuccin's extensive range of ports. From applications and tools to websites, we have a port for just about anything!">

- Catppuccin provides {ports.length} ports, covering a wide range of applications, tools, websites, - and just about anything you can imagine! + Catppuccin provides {ports.length} ports, covering a wide range of applications, tools, websites, and + just about anything you can imagine!