analogGallery/generateSite.js

221 lines
7.5 KiB
JavaScript

import { promises as fs } from 'fs';
import path from 'path';
import http from 'http';
import { fileURLToPath } from 'url';
// Get current directory name in ES modules
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function generateSite() {
try {
// Check if images.json exists before trying to read it
await fs.access('images.json', fs.constants.F_OK);
// Read the images data
const imagesData = JSON.parse(
await fs.readFile('images.json', 'utf-8')
);
console.log(imagesData);
// Create output directory if it doesn't exist
await fs.mkdir('public', { recursive: true });
// Copy the CSS file to public directory
await fs.copyFile('public/styles.css', 'public/styles.css').catch(err => {
console.error('Error copying CSS file:', err);
});
// Copy the favicon
await fs.copyFile('public/AnalogCameraS.png', 'public/favicon.png').catch(err => {
console.error('Error copying favicon:', err);
});
// Generate individual pages for each image
for (let i = 0; i < imagesData.length; i++) {
const image = imagesData[i];
const prevImage = imagesData[i > 0 ? i - 1 : imagesData.length - 1];
const nextImage = imagesData[(i + 1) % imagesData.length];
// Use image ID for filename
const fileName = `${image.id}.html`;
const prevFileName = `${prevImage.id}.html`;
const nextFileName = `${nextImage.id}.html`;
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${image.description || `Image ${i + 1}`}</title>
<link rel="stylesheet" href="/styles.css">
<link rel="icon" type="image/png" href="/favicon.png">
<style>
.progressive-image-container {
position: relative;
width: 100%;
height: 100%;
aspect-ratio: 1;
}
.progressive-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
transition: opacity 0.5s ease-in-out;
}
.thumbnail {
filter: blur(10px);
transform: scale(1.1);
opacity: 1;
}
.full-image {
opacity: 0;
}
.full-image.loaded {
opacity: 1;
}
</style>
</head>
<body>
<div class="image-container">
${image?.localPath ? `
<div class="progressive-image-container">
<img
class="progressive-image thumbnail"
src="${image.localPath}?quality=10&width=100"
alt="${image.metadata.description || ''}"
onload="this.parentElement.style.aspectRatio = this.naturalWidth + '/' + this.naturalHeight;"
>
<img
class="progressive-image full-image"
src="${image.localPath}"
alt="${image.metadata.description || ''}"
onload="this.classList.add('loaded')"
>
</div>
` : '<p>Image not available</p>'}
${image.description ? `<p class="description">${image.description}</p>` : ''}
<div class="navigation">
<a href="${prevFileName}">←</a>
<a href="${nextFileName}">→</a>
</div>
</div>
</body>
</html>`;
await fs.writeFile(`public/${fileName}`, html);
}
// Update index.html to redirect to first image's ID
const indexHtml = `
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url=${imagesData[0].id}.html">
</head>
<body>
Redirecting...
</body>
</html>`;
await fs.writeFile('public/index.html', indexHtml);
console.log('Site generated successfully!');
} catch (error) {
if (error.code === 'ENOENT') {
console.error('images.json does not exist. Please run fetchImages.js first.');
} else {
console.error('Error generating site:', error);
}
throw error;
}
}
async function serveStaticSite(port = 3000) {
const server = http.createServer(async (req, res) => {
try {
// Parse URL and query parameters
const url = new URL(req.url, `http://localhost:${port}`);
const quality = url.searchParams.get('quality');
const width = url.searchParams.get('width');
// Convert URL to filesystem path
let filePath;
if (url.pathname.startsWith('/images/')) {
// Serve directly from images folder
filePath = url.pathname.slice(1); // Remove leading slash
// If quality parameter is present, serve a resized version
if (quality && width && (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg') || filePath.endsWith('.png'))) {
try {
const Sharp = (await import('sharp')).default;
const image = await Sharp(filePath)
.resize({
width: parseInt(width),
height: parseInt(width),
fit: 'inside'
})
.jpeg({
quality: parseInt(quality),
progressive: true
})
.toBuffer();
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'public, max-age=31536000'
});
res.end(image);
return;
} catch (error) {
console.error('Error processing image:', error);
// Fall through to serve original image
}
}
} else {
// Serve from public folder
filePath = path.join('public', req.url === '/' ? 'index.html' : req.url);
// Add .html extension if no extension exists
if (!path.extname(filePath)) {
filePath += '.html';
}
}
const content = await fs.readFile(filePath);
// Set content type based on file extension
const ext = path.extname(filePath).toLowerCase();
const contentType = {
'.html': 'text/html',
'.css': 'text/css',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
}[ext] || 'application/octet-stream';
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);
} catch (error) {
console.error('Error serving file:', error);
res.writeHead(404);
res.end('Not found');
}
});
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
}
// Generate the site and then serve it
generateSite()
.then(() => serveStaticSite())
.catch(console.error);