fix(PWA): icons

This commit is contained in:
Julien Froidefond
2025-02-13 14:21:40 +01:00
parent 7a79da017a
commit dc36ccaae0
16 changed files with 147 additions and 79 deletions

18
public/apple-icon.svg Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Fond carré -->
<rect width="32" height="32" fill="#4F46E5"/>
<!-- Lettre S stylisée -->
<path
d="M21 12C21 10.3431 19.6569 9 18 9H14C12.3431 9 11 10.3431 11 12V12.5C11 14.1569 12.3431 15.5 14 15.5H18C19.6569 15.5 21 16.8431 21 18.5V19C21 20.6569 19.6569 22 18 22H14C12.3431 22 11 20.6569 11 19"
stroke="white"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<!-- Points décoratifs -->
<circle cx="11" cy="24" r="1.5" fill="white"/>
<circle cx="21" cy="8" r="1.5" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

View File

@@ -1,16 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Fond --> <!-- Fond avec coins arrondis -->
<rect width="32" height="32" rx="8" fill="#4F46E5"/> <path
d="M8 0H24C28.4183 0 32 3.58172 32 8V24C32 28.4183 28.4183 32 24 32H8C3.58172 32 0 28.4183 0 24V8C0 3.58172 3.58172 0 8 0Z"
fill="#4F46E5"
/>
<!-- Lettre S stylisée --> <!-- Lettre S stylisée -->
<path d="M20.5 11C20.5 9.61929 19.3807 8.5 18 8.5H14C12.6193 8.5 11.5 9.61929 11.5 11V11.5C11.5 12.8807 12.6193 14 14 14H18C19.3807 14 20.5 15.1193 20.5 16.5V17C20.5 18.3807 19.3807 19.5 18 19.5H14C12.6193 19.5 11.5 18.3807 11.5 17" <path
d="M21 12C21 10.3431 19.6569 9 18 9H14C12.3431 9 11 10.3431 11 12V12.5C11 14.1569 12.3431 15.5 14 15.5H18C19.6569 15.5 21 16.8431 21 18.5V19C21 20.6569 19.6569 22 18 22H14C12.3431 22 11 20.6569 11 19"
stroke="white" stroke="white"
stroke-width="3" stroke-width="3"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round"/> stroke-linejoin="round"
/>
<!-- Points décoratifs --> <!-- Points décoratifs -->
<circle cx="11.5" cy="22" r="1.5" fill="white"/> <circle cx="11" cy="24" r="1.5" fill="white"/>
<circle cx="20.5" cy="6" r="1.5" fill="white"/> <circle cx="21" cy="8" r="1.5" fill="white"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -36,72 +36,72 @@ self.addEventListener("activate", (event) => {
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
// Pour les requêtes API, on utilise "Network First" avec un timeout // Pour les requêtes API, on utilise "Network First" avec un timeout
if (event.request.url.includes("/api/")) { if (event.request.url.includes("/api/")) {
event.respondWith( // event.respondWith(
Promise.race([ // Promise.race([
fetch(event.request.clone()) // fetch(event.request.clone())
.then((response) => { // .then((response) => {
// Ne mettre en cache que les réponses réussies // // Ne mettre en cache que les réponses réussies
if (response.ok) { // if (response.ok) {
const responseToCache = response.clone(); // const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => { // caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache); // cache.put(event.request, responseToCache);
}); // });
} // }
return response; // return response;
}) // })
.catch(() => { // .catch(() => {
// En cas d'erreur réseau, essayer le cache // // En cas d'erreur réseau, essayer le cache
return caches.match(event.request).then((cachedResponse) => { // return caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) { // if (cachedResponse) {
return cachedResponse; // return cachedResponse;
} // }
// Si pas de cache, renvoyer une erreur appropriée // // Si pas de cache, renvoyer une erreur appropriée
return new Response(JSON.stringify({ error: "Hors ligne" }), { // return new Response(JSON.stringify({ error: "Hors ligne" }), {
status: 503, // status: 503,
headers: { "Content-Type": "application/json" }, // headers: { "Content-Type": "application/json" },
}); // });
}); // });
}), // }),
// Timeout après 5 secondes // // Timeout après 5 secondes
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5000)).catch( // new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5000)).catch(
() => { // () => {
return caches.match(event.request).then((cachedResponse) => { // return caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) { // if (cachedResponse) {
return cachedResponse; // return cachedResponse;
} // }
return new Response(JSON.stringify({ error: "Timeout" }), { // return new Response(JSON.stringify({ error: "Timeout" }), {
status: 504, // status: 504,
headers: { "Content-Type": "application/json" }, // headers: { "Content-Type": "application/json" },
}); // });
}); // });
} // }
), // ),
]) // ])
); // );
} else { } else {
// Pour les autres ressources, on garde la stratégie "Cache First" // Pour les autres ressources, on garde la stratégie "Cache First"
event.respondWith( // event.respondWith(
caches.match(event.request).then((cachedResponse) => { // caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) { // if (cachedResponse) {
return cachedResponse; // return cachedResponse;
} // }
return fetch(event.request) // return fetch(event.request)
.then((response) => { // .then((response) => {
// Mettre en cache la nouvelle réponse // // Mettre en cache la nouvelle réponse
const responseToCache = response.clone(); // const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => { // caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache); // cache.put(event.request, responseToCache);
}); // });
return response; // return response;
}) // })
.catch(() => { // .catch(() => {
// Si la requête échoue et que c'est une page, renvoyer la page hors ligne // // Si la requête échoue et que c'est une page, renvoyer la page hors ligne
if (event.request.mode === "navigate") { // if (event.request.mode === "navigate") {
return caches.match(OFFLINE_PAGE); // return caches.match(OFFLINE_PAGE);
} // }
return new Response("Hors ligne", { status: 503 }); // return new Response("Hors ligne", { status: 503 });
}); // });
}) // })
); // );
} }
}); });

View File

@@ -4,6 +4,7 @@ const path = require("path");
const sizes = [72, 96, 128, 144, 152, 192, 384, 512]; const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
const inputSvg = path.join(__dirname, "../public/favicon.svg"); const inputSvg = path.join(__dirname, "../public/favicon.svg");
const inputAppleSvg = path.join(__dirname, "../public/apple-icon.svg");
const outputDir = path.join(__dirname, "../public/images/icons"); const outputDir = path.join(__dirname, "../public/images/icons");
async function generateIcons() { async function generateIcons() {
@@ -11,13 +12,41 @@ async function generateIcons() {
// Créer le dossier de sortie s'il n'existe pas // Créer le dossier de sortie s'il n'existe pas
await fs.mkdir(outputDir, { recursive: true }); await fs.mkdir(outputDir, { recursive: true });
// Générer les icônes pour chaque taille // Générer les icônes Android (avec bords arrondis)
for (const size of sizes) { for (const size of sizes) {
const outputPath = path.join(outputDir, `icon-${size}x${size}.png`); const outputPath = path.join(outputDir, `icon-${size}x${size}.png`);
await sharp(inputSvg).resize(size, size).png().toFile(outputPath); await sharp(inputSvg)
.resize(size, size, {
fit: "contain",
background: { r: 0, g: 0, b: 0, alpha: 0 }, // Fond transparent
})
.png({
compressionLevel: 9,
palette: true,
})
.toFile(outputPath);
console.log(`✓ Icône ${size}x${size} générée`); console.log(`✓ Icône Android ${size}x${size} générée`);
}
// Générer les icônes Apple (carrées)
const appleSizes = [152, 167, 180];
for (const size of appleSizes) {
const outputPath = path.join(outputDir, `apple-icon-${size}x${size}.png`);
await sharp(inputAppleSvg)
.resize(size, size, {
fit: "contain",
background: { r: 0, g: 0, b: 0, alpha: 0 }, // Fond transparent
})
.png({
compressionLevel: 9,
palette: true,
})
.toFile(outputPath);
console.log(`✓ Icône Apple ${size}x${size} générée`);
} }
console.log("\n✨ Toutes les icônes ont été générées avec succès !"); console.log("\n✨ Toutes les icônes ont été générées avec succès !");

View File

@@ -16,12 +16,28 @@ export const metadata: Metadata = {
], ],
apple: [ apple: [
{ {
url: "/favicon.svg", url: "/images/icons/apple-icon-180x180.png",
type: "image/svg+xml", sizes: "180x180",
type: "image/png",
},
{
url: "/images/icons/apple-icon-167x167.png",
sizes: "167x167",
type: "image/png",
},
{
url: "/images/icons/apple-icon-152x152.png",
sizes: "152x152",
type: "image/png",
}, },
], ],
}, },
manifest: "/manifest.json", manifest: "/manifest.json",
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "StripStream",
},
}; };
// Composant client séparé pour le layout // Composant client séparé pour le layout