PhotoGallery/src/main/resources/templates/photo.html

251 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html data-bs-theme="dark" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Photo Viewer | Premium Gallery</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<style>
body {
background-color: #0b0c10;
color: #ffffff;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
/* Glassmorphism Navbar */
.navbar {
background: rgba(11, 12, 16, 0.8) !important;
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
}
/* Photo Display Stage */
.photo-stage {
background: radial-gradient(circle, #1f2833 0%, #0b0c10 100%);
border-radius: 1.5rem;
padding: 2rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 500px;
box-shadow: inset 0 0 50px rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.05);
}
.photo-stage img {
max-height: 75vh;
object-fit: contain;
border-radius: 0.5rem;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
}
/* Refined Cards */
.card {
background: rgba(31, 40, 51, 0.4);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 1rem;
}
.form-control, .form-control:focus {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
border-radius: 0.5rem;
}
.form-label {
color: rgba(255,255,255,0.6);
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Action Buttons */
.btn-action-group .btn {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
color: white;
padding: 0.5rem 1rem;
transition: all 0.3s ease;
}
.btn-action-group .btn:hover {
background: rgba(255,255,255,0.2);
transform: translateY(-2px);
}
/* Metadata List */
.meta-row {
display: flex;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.meta-label { color: rgba(255,255,255,0.5); font-size: 0.9rem; }
.meta-value { font-weight: 500; font-size: 0.9rem; }
@media (max-width: 991px) {
.photo-stage { min-height: 350px; padding: 1rem; }
}
</style>
</head>
<body hx-boost="true">
<nav class="navbar navbar-expand-md sticky-top">
<div class="container">
<a class="navbar-brand fw-bold text-uppercase tracking-wider" href="/home">
<i class="bi bi-camera-reels me-2 text-primary"></i>Gallery
</a>
<div class="ms-auto">
<a class="btn btn-sm btn-outline-light rounded-pill px-4" href="/home">
<i class="bi bi-arrow-left me-2"></i>Back to Grid
</a>
</div>
</div>
</nav>
<div class="container py-4 py-md-5">
<div class="row g-4">
<div class="col-lg-8">
<div class="photo-stage mb-4">
<img th:src="@{${photo.filePath}}" alt="Main photo" class="img-fluid">
</div>
<div class="d-flex justify-content-center">
<div class="btn-group btn-action-group rounded-pill overflow-hidden shadow">
<a th:href="@{${photo.filePath}}" class="btn" download title="Download">
<i class="bi bi-download"></i>
</a>
<a th:href="@{${photo.filePath}}" class="btn" target="_blank" title="Open original">
<i class="bi bi-box-arrow-up-right"></i>
</a>
<button class="btn" th:fragment="favButton"
th:attr="hx-post=@{/photo/favorite/{id}(id=${photo.id})}"
hx-swap="outerHTML" hx-boost="false">
<i th:if="${!photo.favourite}" class="bi bi-star"></i>
<i th:if="${photo.favourite}" class="bi bi-star-fill text-warning"></i>
</button>
<button class="btn" title="Fullscreen"
onclick="document.querySelector('.photo-stage img').requestFullscreen()">
<i class="bi bi-arrows-fullscreen"></i>
</button>
</div>
</div>
</div>
<div class="col-lg-4">
<form th:object="${photo}" method="post" th:action="@{/photo/{id}(id=${photo.id})}" class="card mb-4 shadow-sm" hx-boost="false">
<div class="card-body p-4">
<h5 class="fw-bold mb-4"><i class="bi bi-pencil-square me-2 text-primary"></i>Properties</h5>
<div class="mb-3">
<label class="form-label">Title</label>
<input th:field="*{title}" class="form-control" type="text" placeholder="Add a title...">
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea th:field="*{description}" class="form-control" rows="3" placeholder="Describe this moment..."></textarea>
</div>
<div class="mb-3">
<label class="form-label">Tags</label>
<div class="input-group">
<span class="input-group-text bg-transparent border-end-0 text-white-50"><i class="bi bi-tag"></i></span>
<input name="newTag" class="form-control border-start-0" maxlength="20"
placeholder="Add tag & hit Enter"
th:attr="hx-post=@{/tags/add(photoId=${photo.id})}"
hx-trigger="keyup[key=='Enter']"
hx-target="#tagContainer"
hx-swap="beforeend"
hx-on::after-request="this.value=''"
hx-boost="false" />
</div>
<div id="tagContainer" class="mt-3 d-flex flex-wrap gap-2">
<th:block th:each="tag : *{tags}">
<div th:replace="~{:: tagBadge(tag=${tag})}"></div>
</th:block>
</div>
</div>
<div class="mb-4">
<label class="form-label">Copyright</label>
<input th:field="*{copyRight}" class="form-control" type="text">
</div>
<div class="row g-2">
<div class="col-6">
<button class="btn btn-outline-light w-100 rounded-pill" type="reset">Reset</button>
</div>
<div class="col-6">
<button class="btn btn-primary w-100 rounded-pill" type="submit">Save</button>
</div>
</div>
</div>
</form>
<span th:fragment="tagBadge(tag)" class="badge rounded-pill bg-primary bg-opacity-25 border border-primary border-opacity-50 px-3 py-2 d-flex align-items-center">
<span th:text="${tag}"></span>
<input type="hidden" name="tags" th:value="${tag}">
<button type="button" class="btn-close btn-close-white ms-2" style="font-size: 0.6rem;"
hx-post="/tags/remove"
hx-target="closest .badge"
hx-swap="outerHTML"
hx-boost="false">
</button>
</span>
<div class="card mb-4 shadow-sm">
<div class="card-body p-4">
<h6 class="fw-bold mb-3"><i class="bi bi-info-circle me-2 text-primary"></i>Technical Info</h6>
<div class="meta-row">
<span class="meta-label">Format</span>
<span class="meta-value text-uppercase" th:text="${photo.mimeType}"></span>
</div>
<div class="meta-row">
<span class="meta-label">Resolution</span>
<span class="meta-value" th:text="${photo.width} + ' × ' + ${photo.height}"></span>
</div>
<div class="meta-row">
<span class="meta-label">Size</span>
<span class="meta-value" th:text="${photo.file_size} + ' MB'"></span>
</div>
<div class="meta-row border-0">
<span class="meta-label">Color Space</span>
<span class="meta-value" th:text="${photo.colourSpace}"></span>
</div>
</div>
</div>
<div class="card overflow-hidden shadow-sm">
<div class="card-body p-0">
<iframe src="https://cdn.bootstrapstudio.io/placeholders/map.html" width="100%" height="200" style="border:0; filter: invert(90%) hue-rotate(180deg);" loading="lazy"></iframe>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("keydown", function(e) {
if (e.target.name === "newTag" && e.key === "Enter") {
e.preventDefault();
}
});
});
</script>
</body>
</html>