pixel-forge
Version:
A comprehensive generator for social media previews, favicons, and visual assets across all platforms
387 lines (386 loc) • 16.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PWAGenerator = void 0;
const path_1 = __importDefault(require("path"));
const fs_1 = require("fs");
const image_processor_1 = require("../../core/image-processor");
class PWAGenerator {
constructor(sourceImage, config) {
this.config = config;
this.sourceImage = sourceImage;
}
/**
* Generate all PWA assets
*/
async generate(options = {}) {
const { includeManifest = true, includeIcons = true, includeSplashScreens = true, includeAppleIcons = true, includeAndroidIcons = true, includeShortcuts = false } = options;
if (includeIcons) {
if (includeAppleIcons) {
await this.generateAppleIcons();
}
if (includeAndroidIcons) {
await this.generateAndroidIcons();
}
await this.generatePWAIcons();
}
if (includeSplashScreens) {
await this.generateSplashScreens();
}
if (includeManifest) {
await this.generateManifest(includeShortcuts);
}
}
/**
* Generate PWA icons in standard sizes
*/
async generatePWAIcons() {
const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
for (const size of iconSizes) {
const processor = new image_processor_1.ImageProcessor(this.sourceImage);
const outputPath = path_1.default.join(this.config.output.path, `pwa-${size}x${size}.png`);
const resizedFile = await processor.resize(size, size, {
fit: 'contain',
background: this.config.backgroundColor
});
const finalProcessor = new image_processor_1.ImageProcessor(resizedFile);
await finalProcessor.save(outputPath);
await processor.cleanup();
}
// Generate maskable icons (with safe area)
await this.generateMaskableIcons();
}
/**
* Generate maskable icons for adaptive icons
*/
async generateMaskableIcons() {
const maskableSizes = [192, 512];
for (const size of maskableSizes) {
const processor = new image_processor_1.ImageProcessor(this.sourceImage);
const outputPath = path_1.default.join(this.config.output.path, `pwa-maskable-${size}x${size}.png`);
// For maskable icons, we would typically need to ensure the icon fits within a safe area
// But since we're using 'contain' fit, the icon will be properly centered and sized
// const safeSize = Math.round(size * 0.6);
// const padding = Math.round((size - safeSize) / 2);
const resizedFile = await processor.resize(size, size, {
fit: 'contain',
background: this.config.backgroundColor
});
const finalProcessor = new image_processor_1.ImageProcessor(resizedFile);
await finalProcessor.save(outputPath);
await processor.cleanup();
}
}
/**
* Generate Apple-specific icons
*/
async generateAppleIcons() {
const appleSizes = [57, 60, 72, 76, 114, 120, 144, 152, 180];
for (const size of appleSizes) {
const processor = new image_processor_1.ImageProcessor(this.sourceImage);
const outputPath = path_1.default.join(this.config.output.path, `apple-icon-${size}x${size}.png`);
const resizedFile = await processor.resize(size, size, {
fit: 'contain',
background: this.config.backgroundColor
});
const finalProcessor = new image_processor_1.ImageProcessor(resizedFile);
await finalProcessor.save(outputPath);
await processor.cleanup();
}
}
/**
* Generate Android-specific icons
*/
async generateAndroidIcons() {
const androidSizes = [36, 48, 72, 96, 144, 192, 256, 384, 512];
for (const size of androidSizes) {
const processor = new image_processor_1.ImageProcessor(this.sourceImage);
const outputPath = path_1.default.join(this.config.output.path, `android-icon-${size}x${size}.png`);
const resizedFile = await processor.resize(size, size, {
fit: 'contain',
background: this.config.backgroundColor
});
const finalProcessor = new image_processor_1.ImageProcessor(resizedFile);
await finalProcessor.save(outputPath);
await processor.cleanup();
}
}
/**
* Generate splash screens for various devices
*/
async generateSplashScreens() {
const splashSizes = [
// iPhone sizes
{ width: 1125, height: 2436, name: 'iphone-x' },
{ width: 1242, height: 2688, name: 'iphone-xs-max' },
{ width: 828, height: 1792, name: 'iphone-xr' },
{ width: 1170, height: 2532, name: 'iphone-12' },
{ width: 1284, height: 2778, name: 'iphone-12-pro-max' },
// iPad sizes
{ width: 1536, height: 2048, name: 'ipad' },
{ width: 1620, height: 2160, name: 'ipad-air' },
{ width: 2048, height: 2732, name: 'ipad-pro' },
// Android/Generic sizes
{ width: 1080, height: 1920, name: 'android-portrait' },
{ width: 1920, height: 1080, name: 'android-landscape' },
];
for (const splash of splashSizes) {
await this.generateSplashScreen(splash.width, splash.height, splash.name);
}
}
/**
* Generate individual splash screen
*/
async generateSplashScreen(width, height, name) {
const processor = new image_processor_1.ImageProcessor(this.sourceImage);
const outputPath = path_1.default.join(this.config.output.path, `splash-${name}-${width}x${height}.png`);
// For splash screens, we would typically calculate logo size
// but since we're using 'contain' fit, the image will be properly sized
// const logoSize = Math.min(width, height) * 0.3; // Logo is 30% of smallest dimension
const resizedFile = await processor.resize(width, height, {
fit: 'contain',
background: this.config.backgroundColor
});
const finalProcessor = new image_processor_1.ImageProcessor(resizedFile);
await finalProcessor.save(outputPath);
await processor.cleanup();
}
/**
* Generate PWA manifest.json
*/
async generateManifest(includeShortcuts = false) {
const prefix = this.config.output.prefix || '/';
const manifest = {
name: this.config.appName,
short_name: this.config.appName,
description: this.config.description || '',
theme_color: this.config.themeColor,
background_color: this.config.backgroundColor,
display: 'standalone',
orientation: 'portrait-primary',
scope: '/',
start_url: '/',
id: '/',
icons: [
// Standard icons
{
src: `${prefix}pwa-192x192.png`,
sizes: '192x192',
type: 'image/png'
},
{
src: `${prefix}pwa-512x512.png`,
sizes: '512x512',
type: 'image/png'
},
// Maskable icons
{
src: `${prefix}pwa-maskable-192x192.png`,
sizes: '192x192',
type: 'image/png',
purpose: 'maskable'
},
{
src: `${prefix}pwa-maskable-512x512.png`,
sizes: '512x512',
type: 'image/png',
purpose: 'maskable'
},
// Any purpose icons
{
src: `${prefix}pwa-192x192.png`,
sizes: '192x192',
type: 'image/png',
purpose: 'any'
},
{
src: `${prefix}pwa-512x512.png`,
sizes: '512x512',
type: 'image/png',
purpose: 'any'
}
],
// Screenshots for app stores
screenshots: [
{
src: `${prefix}splash-android-portrait-1080x1920.png`,
sizes: '1080x1920',
type: 'image/png',
form_factor: 'narrow'
},
{
src: `${prefix}splash-android-landscape-1920x1080.png`,
sizes: '1920x1080',
type: 'image/png',
form_factor: 'wide'
}
],
// Categories
categories: ['productivity', 'business'],
// Language
lang: 'en',
dir: 'ltr'
};
// Add shortcuts if requested
if (includeShortcuts) {
manifest.shortcuts = this.getDefaultShortcuts();
}
const manifestPath = path_1.default.join(this.config.output.path, 'manifest.json');
await fs_1.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
}
/**
* Get default app shortcuts
*/
getDefaultShortcuts() {
const prefix = this.config.output.prefix || '/';
return [
{
name: 'Home',
short_name: 'Home',
description: 'Go to home page',
url: '/',
icons: [
{
src: `${prefix}pwa-192x192.png`,
sizes: '192x192',
type: 'image/png'
}
]
}
];
}
/**
* Get HTML meta tags for PWA
*/
getMetaTags() {
const prefix = this.config.output.prefix || '/';
return [
// PWA manifest
`<link rel="manifest" href="${prefix}manifest.json">`,
// Theme colors
`<meta name="theme-color" content="${this.config.themeColor}">`,
`<meta name="msapplication-navbutton-color" content="${this.config.themeColor}">`,
`<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">`,
// PWA capabilities
`<meta name="apple-mobile-web-app-capable" content="yes">`,
`<meta name="apple-mobile-web-app-title" content="${this.config.appName}">`,
`<meta name="mobile-web-app-capable" content="yes">`,
`<meta name="application-name" content="${this.config.appName}">`,
// Apple icons
`<link rel="apple-touch-icon" sizes="180x180" href="${prefix}apple-icon-180x180.png">`,
`<link rel="apple-touch-icon" sizes="152x152" href="${prefix}apple-icon-152x152.png">`,
`<link rel="apple-touch-icon" sizes="144x144" href="${prefix}apple-icon-144x144.png">`,
`<link rel="apple-touch-icon" sizes="120x120" href="${prefix}apple-icon-120x120.png">`,
`<link rel="apple-touch-icon" sizes="114x114" href="${prefix}apple-icon-114x114.png">`,
`<link rel="apple-touch-icon" sizes="76x76" href="${prefix}apple-icon-76x76.png">`,
`<link rel="apple-touch-icon" sizes="72x72" href="${prefix}apple-icon-72x72.png">`,
`<link rel="apple-touch-icon" sizes="60x60" href="${prefix}apple-icon-60x60.png">`,
`<link rel="apple-touch-icon" sizes="57x57" href="${prefix}apple-icon-57x57.png">`,
// Splash screens
`<link rel="apple-touch-startup-image" href="${prefix}splash-iphone-x-1125x2436.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)">`,
`<link rel="apple-touch-startup-image" href="${prefix}splash-iphone-xs-max-1242x2688.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)">`,
`<link rel="apple-touch-startup-image" href="${prefix}splash-iphone-xr-828x1792.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)">`,
// Android icons
`<link rel="icon" type="image/png" sizes="192x192" href="${prefix}android-icon-192x192.png">`,
`<link rel="icon" type="image/png" sizes="144x144" href="${prefix}android-icon-144x144.png">`,
`<link rel="icon" type="image/png" sizes="96x96" href="${prefix}android-icon-96x96.png">`,
`<link rel="icon" type="image/png" sizes="72x72" href="${prefix}android-icon-72x72.png">`,
`<link rel="icon" type="image/png" sizes="48x48" href="${prefix}android-icon-48x48.png">`,
`<link rel="icon" type="image/png" sizes="36x36" href="${prefix}android-icon-36x36.png">`,
];
}
/**
* Get Next.js metadata configuration for PWA
*/
getNextMetadata() {
const prefix = this.config.output.prefix || '/';
return {
manifest: `${prefix}manifest.json`,
themeColor: this.config.themeColor,
applicationName: this.config.appName,
appleWebApp: {
capable: true,
title: this.config.appName,
statusBarStyle: 'black-translucent',
startupImage: [
{
url: `${prefix}splash-iphone-x-1125x2436.png`,
media: '(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)'
},
{
url: `${prefix}splash-iphone-xs-max-1242x2688.png`,
media: '(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)'
}
]
},
icons: {
apple: [
{ url: `${prefix}apple-icon-180x180.png`, sizes: '180x180', type: 'image/png' },
{ url: `${prefix}apple-icon-152x152.png`, sizes: '152x152', type: 'image/png' },
{ url: `${prefix}apple-icon-144x144.png`, sizes: '144x144', type: 'image/png' },
],
other: [
{ url: `${prefix}pwa-192x192.png`, sizes: '192x192', type: 'image/png' },
{ url: `${prefix}pwa-512x512.png`, sizes: '512x512', type: 'image/png' },
{ url: `${prefix}pwa-maskable-192x192.png`, sizes: '192x192', type: 'image/png', purpose: 'maskable' },
{ url: `${prefix}pwa-maskable-512x512.png`, sizes: '512x512', type: 'image/png', purpose: 'maskable' },
]
}
};
}
/**
* Get list of generated files
*/
getGeneratedFiles() {
return [
// PWA icons
'pwa-72x72.png',
'pwa-96x96.png',
'pwa-128x128.png',
'pwa-144x144.png',
'pwa-152x152.png',
'pwa-192x192.png',
'pwa-384x384.png',
'pwa-512x512.png',
// Maskable icons
'pwa-maskable-192x192.png',
'pwa-maskable-512x512.png',
// Apple icons
'apple-icon-57x57.png',
'apple-icon-60x60.png',
'apple-icon-72x72.png',
'apple-icon-76x76.png',
'apple-icon-114x114.png',
'apple-icon-120x120.png',
'apple-icon-144x144.png',
'apple-icon-152x152.png',
'apple-icon-180x180.png',
// Android icons
'android-icon-36x36.png',
'android-icon-48x48.png',
'android-icon-72x72.png',
'android-icon-96x96.png',
'android-icon-144x144.png',
'android-icon-192x192.png',
'android-icon-256x256.png',
'android-icon-384x384.png',
'android-icon-512x512.png',
// Splash screens
'splash-iphone-x-1125x2436.png',
'splash-iphone-xs-max-1242x2688.png',
'splash-iphone-xr-828x1792.png',
'splash-iphone-12-1170x2532.png',
'splash-iphone-12-pro-max-1284x2778.png',
'splash-ipad-1536x2048.png',
'splash-ipad-air-1620x2160.png',
'splash-ipad-pro-2048x2732.png',
'splash-android-portrait-1080x1920.png',
'splash-android-landscape-1920x1080.png',
// Manifest
'manifest.json',
];
}
}
exports.PWAGenerator = PWAGenerator;