Add icon generation script and new icons for PWA and favicons

- Introduced `generate-icons.mjs` script to automate the creation of PWA icons and favicons from the provided SVG logo.
- Generated icons include:
  - PWA icons: 192x192 and 512x512
  - Maskable icon with 12% padding: 512x512
  - Favicons: 32x32, 16x16
  - Apple-touch icon: 180x180
- Added a new PNG file for the maskable icon.
This commit is contained in:
davidels1 2025-09-25 00:50:36 +02:00
parent 9ee0905d9f
commit 796bac47b4
13 changed files with 5024 additions and 5 deletions

View file

@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f172a" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png" />
<title>AppHub - JEISA</title>
</head>

4921
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@
"dev": "vite",
"build": "vite build",
"build:dev": "vite build --mode development",
"icons": "node scripts/generate-icons.mjs",
"lint": "eslint .",
"preview": "vite preview"
},
@ -82,9 +83,11 @@
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.47",
"sharp": "^0.34.4",
"tailwindcss": "^3.4.11",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^6.3.4"
"vite": "^6.3.4",
"vite-plugin-pwa": "^1.0.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,59 @@
// Generates PWA icons and favicons from public/jei-logo.svg
// Requires: sharp (devDependency)
import fs from 'node:fs/promises'
import path from 'node:path'
import sharp from 'sharp'
const root = path.resolve(process.cwd())
const candidatePng = path.join(root, 'public', 'brand.png')
const srcSvg = path.join(root, 'public', 'jei-logo.svg')
let srcImage = srcSvg
try {
const stat = await fs.stat(candidatePng)
if (stat.isFile()) srcImage = candidatePng
} catch {}
const outDir = path.join(root, 'public')
const iconsDir = path.join(outDir, 'icons')
async function ensureDir(dir) {
await fs.mkdir(dir, { recursive: true })
}
async function makePng(size, outPath, padding = 0) {
// Create a square canvas and fit the SVG inside with optional padding
const inner = Math.round(size * (1 - padding))
const background = { r: 0, g: 0, b: 0, alpha: 0 } // transparent
const buf = await sharp(srcImage)
.resize(inner, inner, { fit: 'contain', background })
.png()
.toBuffer()
// Composite on a square canvas to ensure exact size
await sharp({ create: { width: size, height: size, channels: 4, background } })
.composite([{ input: buf, gravity: 'center' }])
.png()
.toFile(outPath)
}
async function run() {
await ensureDir(iconsDir)
// PWA icons
await makePng(192, path.join(iconsDir, 'icon-192x192.png'))
await makePng(512, path.join(iconsDir, 'icon-512x512.png'))
// Maskable: add ~12% padding
await makePng(512, path.join(iconsDir, 'maskable-512x512.png'), 0.12)
// Favicons and apple-touch icon
await makePng(32, path.join(outDir, 'favicon-32x32.png'))
await makePng(16, path.join(outDir, 'favicon-16x16.png'))
await makePng(180, path.join(outDir, 'apple-touch-icon-180x180.png'))
console.log('Icons generated in public/ and public/icons/')
}
run().catch((err) => {
console.error(err)
process.exit(1)
})

View file

@ -1,5 +1,9 @@
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./globals.css";
import { registerSW } from "virtual:pwa-register";
createRoot(document.getElementById("root")!).render(<App />);
// Register service worker for PWA (auto-updates when new version available)
registerSW({ immediate: true });

1
src/vite-env.d.ts vendored
View file

@ -1,4 +1,5 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />
interface ImportMetaEnv {
readonly MODE: string

View file

@ -1,4 +1,5 @@
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
import dyadComponentTagger from "@dyad-sh/react-vite-component-tagger";
import react from "@vitejs/plugin-react-swc";
import path from "path";
@ -8,7 +9,39 @@ export default defineConfig(() => ({
host: "::",
port: 8080,
},
plugins: [dyadComponentTagger(), react()],
plugins: [
dyadComponentTagger(),
react(),
VitePWA({
registerType: "autoUpdate",
includeAssets: [
"favicon.ico",
"favicon-16x16.png",
"favicon-32x32.png",
"apple-touch-icon-180x180.png",
"robots.txt"
],
manifest: {
name: "JEI Stock App",
short_name: "JEI Stock",
description: "Stock management PWA for JEISA",
theme_color: "#0f172a",
background_color: "#ffffff",
display: "standalone",
scope: "/",
start_url: "/",
icons: [
{ src: "/icons/icon-192x192.png", sizes: "192x192", type: "image/png", purpose: "any" },
{ src: "/icons/icon-512x512.png", sizes: "512x512", type: "image/png", purpose: "any" },
{ src: "/icons/maskable-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" }
],
},
workbox: {
navigateFallback: "/index.html",
cleanupOutdatedCaches: true,
},
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),