252 lines
9.3 KiB
JavaScript
252 lines
9.3 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 {
|
|
width: 100%;
|
|
max-width: 100vw;
|
|
margin: 0 auto;
|
|
display: grid; /* Use grid for overlay */
|
|
}
|
|
.progressive-image {
|
|
width: 100%;
|
|
height: auto;
|
|
max-width: 100%;
|
|
grid-area: 1 / 1; /* Stack images in same grid cell */
|
|
transition: opacity 0.5s ease-in-out;
|
|
}
|
|
.thumbnail {
|
|
filter: blur(10px);
|
|
transform: scale(.95);
|
|
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 || ''}"
|
|
>
|
|
<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
|
|
console.log(`Request for image: ${filePath}`);
|
|
console.log(`Parameters: quality=${quality}, width=${width}`);
|
|
|
|
// If quality parameter is present, serve a resized version
|
|
if (quality && width && /\.(jpe?g|png)$/i.test(filePath)) { // Case-insensitive check
|
|
try {
|
|
const Sharp = (await import('sharp')).default;
|
|
|
|
// Get original image info
|
|
const originalImage = await Sharp(filePath);
|
|
const originalMetadata = await originalImage.metadata();
|
|
console.log('Original image metadata:', {
|
|
width: originalMetadata.width,
|
|
height: originalMetadata.height,
|
|
size: originalMetadata.size,
|
|
format: originalMetadata.format,
|
|
orientation: originalMetadata.orientation
|
|
});
|
|
|
|
// Process the image
|
|
const processedImage = await Sharp(filePath)
|
|
.rotate()
|
|
.resize({
|
|
width: parseInt(width),
|
|
height: parseInt(width),
|
|
fit: 'inside'
|
|
})
|
|
.jpeg({
|
|
quality: parseInt(quality),
|
|
progressive: true,
|
|
force: true // Force JPEG output regardless of input format
|
|
});
|
|
|
|
// Get processed image info
|
|
const processedMetadata = await processedImage.metadata();
|
|
console.log('Processed image metadata:', {
|
|
width: processedMetadata.width,
|
|
height: processedMetadata.height,
|
|
format: processedMetadata.format
|
|
});
|
|
|
|
// Convert to buffer and get size
|
|
const buffer = await processedImage.toBuffer();
|
|
console.log('Processed image buffer size:', buffer.length);
|
|
|
|
res.writeHead(200, {
|
|
'Content-Type': 'image/jpeg',
|
|
'Cache-Control': 'public, max-age=31536000',
|
|
'Content-Length': buffer.length
|
|
});
|
|
res.end(buffer);
|
|
return;
|
|
} catch (error) {
|
|
console.error('Error processing image:', error);
|
|
// Fall through to serve original image
|
|
}
|
|
} else {
|
|
// Log info for original image request
|
|
console.log('Serving original image');
|
|
const stats = await fs.stat(filePath);
|
|
console.log('Original file size:', stats.size);
|
|
}
|
|
} 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);
|
|
|