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:
parent
9ee0905d9f
commit
796bac47b4
13 changed files with 5024 additions and 5 deletions
|
|
@ -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
4921
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
public/apple-touch-icon-180x180.png
Normal file
BIN
public/apple-touch-icon-180x180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/favicon-16x16.png
Normal file
BIN
public/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 236 B |
BIN
public/favicon-32x32.png
Normal file
BIN
public/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 376 B |
BIN
public/icons/icon-192x192.png
Normal file
BIN
public/icons/icon-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/icons/icon-512x512.png
Normal file
BIN
public/icons/icon-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
public/icons/maskable-512x512.png
Normal file
BIN
public/icons/maskable-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
59
scripts/generate-icons.mjs
Normal file
59
scripts/generate-icons.mjs
Normal 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)
|
||||
})
|
||||
|
|
@ -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
1
src/vite-env.d.ts
vendored
|
|
@ -1,4 +1,5 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly MODE: string
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue