mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Add slideshow page
This commit is contained in:
Binary file not shown.
@@ -44,3 +44,8 @@ class CollectionDeleteView(DeleteView):
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(request, _('Collection deleted successfully.'))
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
class CollectionSlideshowView(DetailView):
|
||||
model = ImageCollection
|
||||
template_name = 'links/collection_slideshow.html'
|
||||
context_object_name = 'collection'
|
||||
|
||||
@@ -69,6 +69,15 @@
|
||||
</svg>
|
||||
{% trans "Delete Collection" %}
|
||||
</button>
|
||||
<button type="button"
|
||||
onclick="window.location.href='{% url 'collection-slideshow' collection.pk %}'"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
{% trans "Slideshow" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
{% extends 'base_blank.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
overflow: hidden;
|
||||
}
|
||||
.slideshow-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.slide {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.slide.active {
|
||||
opacity: 1;
|
||||
}
|
||||
.slide img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.controls {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.slideshow-container:hover .controls {
|
||||
opacity: 1;
|
||||
}
|
||||
.control-button {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.control-button:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
}
|
||||
.progress-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background: #3b82f6;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="slideshow-container">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
|
||||
{% for image in collection.images.all %}
|
||||
<div class="slide {% if forloop.first %}active{% endif %}" data-url="{{ image.get_url }}">
|
||||
<img src="{{ image.get_url }}" alt="{{ image.title }}" loading="lazy">
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="controls">
|
||||
<button class="control-button" onclick="togglePlayPause()" id="playPauseBtn">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" id="playPauseIcon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6"/>
|
||||
</svg>
|
||||
<span id="playPauseText">{% trans "Pause" %}</span>
|
||||
</button>
|
||||
<button class="control-button" onclick="previousSlide()">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
<span>{% trans "Previous" %}</span>
|
||||
</button>
|
||||
<button class="control-button" onclick="nextSlide()">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<span>{% trans "Next" %}</span>
|
||||
</button>
|
||||
<button class="control-button" onclick="exitSlideshow()">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
<span>{% trans "Exit" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
let currentSlide = 0;
|
||||
const slides = document.querySelectorAll('.slide');
|
||||
const totalSlides = slides.length;
|
||||
let slideInterval = null;
|
||||
let isPlaying = true;
|
||||
const INTERVAL_TIME = 5000; // 5 seconds
|
||||
let progressWidth = 0;
|
||||
let progressInterval = null;
|
||||
|
||||
function showSlide(index) {
|
||||
slides.forEach(slide => slide.classList.remove('active'));
|
||||
currentSlide = (index + totalSlides) % totalSlides;
|
||||
slides[currentSlide].classList.add('active');
|
||||
resetProgress();
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
showSlide(currentSlide + 1);
|
||||
}
|
||||
|
||||
function previousSlide() {
|
||||
showSlide(currentSlide - 1);
|
||||
}
|
||||
|
||||
function togglePlayPause() {
|
||||
isPlaying = !isPlaying;
|
||||
const icon = document.getElementById('playPauseIcon');
|
||||
const text = document.getElementById('playPauseText');
|
||||
|
||||
if (isPlaying) {
|
||||
startSlideshow();
|
||||
icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6"/>';
|
||||
text.textContent = '{% trans "Pause" %}';
|
||||
} else {
|
||||
stopSlideshow();
|
||||
icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>';
|
||||
text.textContent = '{% trans "Play" %}';
|
||||
}
|
||||
}
|
||||
|
||||
function startSlideshow() {
|
||||
stopSlideshow();
|
||||
slideInterval = setInterval(nextSlide, INTERVAL_TIME);
|
||||
startProgress();
|
||||
}
|
||||
|
||||
function stopSlideshow() {
|
||||
clearInterval(slideInterval);
|
||||
clearInterval(progressInterval);
|
||||
progressWidth = 0;
|
||||
updateProgressBar();
|
||||
}
|
||||
|
||||
function exitSlideshow() {
|
||||
window.location.href = '{% url "collection-detail" collection.pk %}';
|
||||
}
|
||||
|
||||
function startProgress() {
|
||||
progressWidth = 0;
|
||||
const progressStep = 100 / (INTERVAL_TIME / 10); // Update every 10ms
|
||||
progressInterval = setInterval(() => {
|
||||
progressWidth = Math.min(100, progressWidth + progressStep);
|
||||
updateProgressBar();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function resetProgress() {
|
||||
progressWidth = 0;
|
||||
updateProgressBar();
|
||||
if (isPlaying) {
|
||||
startProgress();
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressBar() {
|
||||
document.getElementById('progressBar').style.width = `${progressWidth}%`;
|
||||
}
|
||||
|
||||
// Keyboard controls
|
||||
document.addEventListener('keydown', (e) => {
|
||||
switch(e.key) {
|
||||
case 'ArrowLeft':
|
||||
previousSlide();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
nextSlide();
|
||||
break;
|
||||
case ' ':
|
||||
togglePlayPause();
|
||||
break;
|
||||
case 'Escape':
|
||||
exitSlideshow();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Start slideshow
|
||||
startSlideshow();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -3,6 +3,7 @@ from . import views
|
||||
from . import page_views
|
||||
from . import search_views
|
||||
from . import newsletter_views
|
||||
from . import collection_views
|
||||
|
||||
urlpatterns = [
|
||||
# Regular UI URLs
|
||||
@@ -50,4 +51,9 @@ urlpatterns = [
|
||||
path('<str:alias>/', views.redirect_to_original, name='redirect_to_original'),
|
||||
path('<str:alias>/<str:param>/', views.redirect_to_original, name='redirect_to_original_with_param'),
|
||||
path('alias/<str:alias>/', views.LinkDetailView.as_view(), name='link_detail_by_alias'),
|
||||
|
||||
# Collection slideshow
|
||||
path('ui/collections/<uuid:pk>/slideshow/',
|
||||
collection_views.CollectionSlideshowView.as_view(),
|
||||
name='collection-slideshow'),
|
||||
]
|
||||
|
||||
Vendored
+5
@@ -1340,6 +1340,11 @@ video {
|
||||
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
|
||||
{% load static %}
|
||||
<link href="{% static 'css/dist/styles.css' %}" rel="stylesheet">
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user