update
This commit is contained in:
57
server.js
57
server.js
@@ -207,7 +207,9 @@ const faviconStorage = multer.diskStorage({
|
|||||||
cb(null, UPLOAD_DIR);
|
cb(null, UPLOAD_DIR);
|
||||||
},
|
},
|
||||||
filename: (req, file, cb) => {
|
filename: (req, file, cb) => {
|
||||||
cb(null, 'favicon.png');
|
// Preserve original file extension (e.g., .ico, .png)
|
||||||
|
const ext = path.extname(file.originalname) || '.png';
|
||||||
|
cb(null, `favicon${ext}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const uploadFavicon = multer({ storage: faviconStorage });
|
const uploadFavicon = multer({ storage: faviconStorage });
|
||||||
@@ -215,20 +217,65 @@ const uploadFavicon = multer({ storage: faviconStorage });
|
|||||||
// Upload Favicon
|
// Upload Favicon
|
||||||
app.post('/api/favicon', uploadFavicon.single('favicon'), (req, res) => {
|
app.post('/api/favicon', uploadFavicon.single('favicon'), (req, res) => {
|
||||||
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
||||||
res.json({ success: true, url: '/uploads/favicon.png' });
|
// Respond with the stored filename (including extension)
|
||||||
|
const storedName = req.file.filename;
|
||||||
|
res.json({ success: true, url: `/uploads/${storedName}` });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve Favicon (Dynamic)
|
// Serve Favicon (Dynamic)
|
||||||
app.get('/api/favicon', (req, res) => {
|
app.get('/api/favicon', (req, res) => {
|
||||||
const faviconPath = path.join(UPLOAD_DIR, 'favicon.png');
|
// Determine the stored favicon file (any supported extension)
|
||||||
if (fs.existsSync(faviconPath)) {
|
const possibleExts = ['.ico', '.png', '.svg', '.jpg', '.jpeg'];
|
||||||
|
let faviconPath = null;
|
||||||
|
for (const ext of possibleExts) {
|
||||||
|
const candidate = path.join(UPLOAD_DIR, `favicon${ext}`);
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
faviconPath = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (faviconPath) {
|
||||||
|
// Set appropriate Content-Type based on extension
|
||||||
|
const mimeMap = {
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg'
|
||||||
|
};
|
||||||
|
const ext = path.extname(faviconPath).toLowerCase();
|
||||||
|
res.setHeader('Content-Type', mimeMap[ext] || 'application/octet-stream');
|
||||||
|
// Prevent caching so updates appear immediately
|
||||||
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||||
res.sendFile(faviconPath);
|
res.sendFile(faviconPath);
|
||||||
} else {
|
} else {
|
||||||
// Fallback to default
|
// Fallback to default (404)
|
||||||
res.status(404).send('Not found');
|
res.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fallback for browsers requesting /favicon.ico directly
|
||||||
|
app.get('/favicon.ico', (req, res) => {
|
||||||
|
const possibleExts = ['.ico', '.png', '.svg', '.jpg', '.jpeg'];
|
||||||
|
for (const ext of possibleExts) {
|
||||||
|
const candidate = path.join(UPLOAD_DIR, `favicon${ext}`);
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
const mimeMap = {
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg'
|
||||||
|
};
|
||||||
|
const mime = mimeMap[ext] || 'application/octet-stream';
|
||||||
|
res.setHeader('Content-Type', mime);
|
||||||
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||||
|
return res.sendFile(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.status(404).send('Not found');
|
||||||
|
});
|
||||||
|
|
||||||
// Serve Frontend (Production)
|
// Serve Frontend (Production)
|
||||||
const DIST_DIR = path.join(__dirname, 'dist');
|
const DIST_DIR = path.join(__dirname, 'dist');
|
||||||
if (fs.existsSync(DIST_DIR)) {
|
if (fs.existsSync(DIST_DIR)) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Sun, Moon } from 'lucide-react';
|
||||||
import Timeline from './components/Timeline';
|
import Timeline from './components/Timeline';
|
||||||
import PhotoDetail from './components/PhotoDetail';
|
import PhotoDetail from './components/PhotoDetail';
|
||||||
import Admin from './components/Admin';
|
import Admin from './components/Admin';
|
||||||
import { fetchPhotos } from './data/photos';
|
import { fetchPhotos } from './data/photos';
|
||||||
import { Settings, Sun, Moon } from 'lucide-react';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [view, setView] = useState(() => {
|
const [view, setView] = useState(() => {
|
||||||
@@ -37,7 +37,11 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPhotos();
|
const fetchAndSet = async () => {
|
||||||
|
const data = await fetchPhotos();
|
||||||
|
setPhotos(data);
|
||||||
|
};
|
||||||
|
fetchAndSet();
|
||||||
}, [view]);
|
}, [view]);
|
||||||
|
|
||||||
// Handle browser back/forward
|
// Handle browser back/forward
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { X, Camera, Aperture, Clock, Gauge, ArrowLeft, MapPin } from 'lucide-react';
|
import { X, Camera, Aperture, Clock, Gauge, ArrowLeft, MapPin } from 'lucide-react';
|
||||||
|
|
||||||
const PhotoDetail = ({ photo, onClose }) => {
|
const PhotoDetail = ({ photo, onClose }) => {
|
||||||
const [loaded, setLoaded] = useState(false);
|
// Removed unused loaded state
|
||||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||||
const [showTitle, setShowTitle] = useState(false);
|
const [showTitle, setShowTitle] = useState(false);
|
||||||
|
|
||||||
@@ -14,14 +14,7 @@ const PhotoDetail = ({ photo, onClose }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// Removed effect that set loaded state and handled body scroll
|
||||||
setLoaded(true);
|
|
||||||
// Disable scroll on body when modal is open
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
return () => {
|
|
||||||
document.body.style.overflow = 'auto';
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!photo) return null;
|
if (!photo) return null;
|
||||||
|
|
||||||
@@ -36,7 +29,7 @@ const PhotoDetail = ({ photo, onClose }) => {
|
|||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
opacity: loaded ? 1 : 0,
|
opacity: 1,
|
||||||
transition: 'opacity 0.3s ease',
|
transition: 'opacity 0.3s ease',
|
||||||
overflowY: 'auto'
|
overflowY: 'auto'
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user