Implement image gallery with dynamic loading and copy URL functionality; add styles for improved layout and user interaction
All checks were successful
CI / build (push) Successful in 30s
All checks were successful
CI / build (push) Successful in 30s
This commit is contained in:
28
public/app.js
Normal file
28
public/app.js
Normal file
@@ -0,0 +1,28 @@
|
||||
fetch("/images")
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error("Failed to fetch images.");
|
||||
return response.json();
|
||||
})
|
||||
.then(images => {
|
||||
const gallery = document.getElementById("gallery");
|
||||
images.forEach(img => {
|
||||
const imgContainer = document.createElement("div");
|
||||
imgContainer.className = "image-container";
|
||||
imgContainer.innerHTML = `
|
||||
<a href="${img.url}" target="_blank">
|
||||
<img src="${img.url}" alt="${img.name}">
|
||||
</a>
|
||||
<button class="copy-btn" title="Copy URL">Copy URL</button>
|
||||
`;
|
||||
const copyBtn = imgContainer.querySelector('.copy-btn');
|
||||
copyBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(img.url).then(() => {
|
||||
copyBtn.textContent = 'Copied!';
|
||||
setTimeout(() => copyBtn.textContent = 'Copy URL', 1200);
|
||||
});
|
||||
});
|
||||
gallery.appendChild(imgContainer);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error("Error loading images:", error));
|
||||
@@ -4,37 +4,14 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PictShare Gallery</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; }
|
||||
#gallery { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
|
||||
.image-container { width: 150px; cursor: pointer; }
|
||||
.image-container img { width: 100%; border-radius: 5px; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>PictShare Gallery</h1>
|
||||
<div id="gallery"></div>
|
||||
|
||||
<script>
|
||||
fetch("/images")
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error("Failed to fetch images.");
|
||||
return response.json();
|
||||
})
|
||||
.then(images => {
|
||||
const gallery = document.getElementById("gallery");
|
||||
images.forEach(img => {
|
||||
const imgContainer = document.createElement("div");
|
||||
imgContainer.className = "image-container";
|
||||
imgContainer.innerHTML = `<a href="${img.url}" target="_blank">
|
||||
<img src="${img.url}" alt="${img.name}">
|
||||
</a>`;
|
||||
gallery.appendChild(imgContainer);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error("Error loading images:", error));
|
||||
</script>
|
||||
<script src="/app.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
62
public/style.css
Normal file
62
public/style.css
Normal file
@@ -0,0 +1,62 @@
|
||||
body { font-family: Arial, sans-serif; text-align: center; margin: 0; padding: 0; }
|
||||
|
||||
h1 { margin-top: 1.5rem; }
|
||||
|
||||
|
||||
#gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 22px;
|
||||
justify-items: center;
|
||||
align-items: start;
|
||||
/* max-width: 1400px; */
|
||||
margin: auto 10rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
|
||||
.image-container {
|
||||
width: 200px;
|
||||
aspect-ratio: 1 / 1;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
||||
transition: transform 0.15s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-container:hover {
|
||||
transform: scale(1.04);
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.image-container:hover .copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px 8px 0 0;
|
||||
display: block;
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
const express = require("express");
|
||||
const path = require("path");
|
||||
const galleryRoutes = require("./galleryRoutes");
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Serve static files from public directory
|
||||
app.use(express.static(path.join(__dirname, "../public")));
|
||||
|
||||
app.use(galleryRoutes);
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const express = require("express");
|
||||
|
||||
const getImages = require("./getImages");
|
||||
const renderGallery = require("./renderGallery");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Route: Serve Image Gallery at `/`
|
||||
|
||||
// Serve static index.html for the root route
|
||||
const path = require("path");
|
||||
router.get("/", (req, res) => {
|
||||
const images = getImages();
|
||||
res.send(renderGallery(images));
|
||||
res.sendFile(path.join(__dirname, "../public/index.html"));
|
||||
});
|
||||
|
||||
// API Route: Return JSON List of Images
|
||||
@@ -15,4 +16,5 @@ router.get("/images", (req, res) => {
|
||||
res.json(getImages());
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
module.exports = function renderGallery(images) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PictShare Gallery</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; }
|
||||
#gallery { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
|
||||
.image-container { max-width: 150px; max-height: 150px; cursor: pointer; }
|
||||
.image-container img { width: 100%; border-radius: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>PictShare Gallery</h1>
|
||||
<div id="gallery">
|
||||
${images
|
||||
.map(img => `<a href="${img.url}" target="_blank">
|
||||
<img src="${img.url}" alt="${img.name}" class="image-container">
|
||||
</a>`)
|
||||
.join("")}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
@@ -1,3 +1,26 @@
|
||||
0799fec84a8ace495e1cb631db19f944dadb330b;rkijhb.png
|
||||
c8155c5afce62d096283fc3a7d5dc09e46714ffe;u71mru.png
|
||||
443e983d9dd5ce40a42eb4be98a0facd8e080888;qojsy0.png
|
||||
|
||||
0799fec84a8ace495e1cb631db19f944dadb330b;000001.png
|
||||
c8155c5afce62d096283fc3a7d5dc09e46714ffe;000002.png
|
||||
443e983d9dd5ce40a42eb4be98a0facd8e080888;000003.png
|
||||
e1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1;000004.png
|
||||
f2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2;000005.png
|
||||
03c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3;000006.png
|
||||
14d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4;000007.png
|
||||
25e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5;000008.png
|
||||
36f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6;000009.png
|
||||
4707070707070707070707070707070707070707;000010.png
|
||||
5818181818181818181818181818181818181818;000011.png
|
||||
6929292929292929292929292929292929292929;000012.png
|
||||
7a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3;000013.png
|
||||
8b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4;000014.png
|
||||
9c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5;000015.png
|
||||
ad6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6;000016.png
|
||||
be7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7;000017.png
|
||||
cf8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8;000018.png
|
||||
d0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a;000019.png
|
||||
e1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b;000020.png
|
||||
f2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c;000021.png
|
||||
03d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d;000022.png
|
||||
14e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e;000023.png
|
||||
25f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f;000024.png
|
||||
3606060606060606060606060606060606060606;000025.png
|
||||
|
||||
|
Reference in New Issue
Block a user